// // SettingsView.swift // Zyquo MLX // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // import SwiftUI /// Settings window (charter §4.2): native tabs, 760×560. /// Functional depth (HF token vault, compute limits) lands in Phase 6. struct SettingsView: View { var body: some View { TabView { GeneralSettings() .tabItem { Label("General", systemImage: "gearshape") } ComputeSettings() .tabItem { Label("Compute", systemImage: "cpu") } PythonSettings() .tabItem { Label("Python", systemImage: "terminal") } StorageSettings() .tabItem { Label("Storage", systemImage: "internaldrive") } HuggingFaceSettings() .tabItem { Label("Hugging Face", systemImage: "key") } AppearanceSettings() .tabItem { Label("Appearance", systemImage: "paintpalette") } } .frame(width: 760, height: 560) } } private struct GeneralSettings: View { var body: some View { Form { LabeledContent("Models") { PathLabel(url: PersistenceService.modelsDirectory) } LabeledContent("Datasets") { PathLabel(url: PersistenceService.datasetsDirectory) } LabeledContent("Training runs") { PathLabel(url: PersistenceService.runsDirectory) } } .formStyle(.grouped) } } private struct ComputeSettings: View { var body: some View { Form { Section("GPU") { LabeledContent("Unified memory") { Text(ByteCountFormatter.string( fromByteCount: MemoryAdvisor.physicalMemory, countStyle: .memory)) } LabeledContent("Recommended working set") { Text(ByteCountFormatter.string( fromByteCount: MemoryAdvisor.recommendedWorkingSet, countStyle: .memory)) } } } .formStyle(.grouped) } } private struct PythonSettings: View { @State private var status = "Checking…" var body: some View { Form { Section("Environment") { LabeledContent("Status") { Text(status) } LabeledContent("Location") { PathLabel(url: PersistenceService.pythonDirectory) } LabeledContent("Pinned packages") { Text(PythonEnvironment.corePins.joined(separator: ", ")) .font(ZyquoTheme.monoSmallFont) } } } .formStyle(.grouped) .task { status = await PythonEnvironment.shared.isProvisioned ? "Ready (Python \(PythonEnvironment.pythonVersion))" : "Not provisioned — installs automatically on first use" } } } private struct StorageSettings: View { @State private var usage: [(String, Int64)] = [] var body: some View { Form { Section("Disk usage") { ForEach(usage, id: \.0) { name, bytes in LabeledContent(name) { Text(ByteCountFormatter.string(fromByteCount: bytes, countStyle: .file)) } } } } .formStyle(.grouped) .task { usage = [ ("Models", directorySize(PersistenceService.modelsDirectory)), ("Datasets", directorySize(PersistenceService.datasetsDirectory)), ("Runs", directorySize(PersistenceService.runsDirectory)), ("Python", directorySize(PersistenceService.pythonDirectory)), ] } } private func directorySize(_ url: URL) -> Int64 { let fm = FileManager.default guard let files = try? fm.subpathsOfDirectory(atPath: url.path) else { return 0 } return files.reduce(Int64(0)) { total, path in let size = (try? url.appendingPathComponent(path) .resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? 0 return total + Int64(size) } } } private struct HuggingFaceSettings: View { @State private var token = HFTokenStore.token ?? "" @State private var saved = false var body: some View { Form { Section("Access token") { SecureField("hf_…", text: $token) Text("Stored in your login Keychain — used for gated models and higher rate limits.") .font(ZyquoTheme.captionFont) .foregroundStyle(ZyquoTheme.textSecondary) HStack { Button("Save") { HFTokenStore.save(token.trimmingCharacters(in: .whitespaces)) saved = true } if saved { Text("Saved ✓") .font(ZyquoTheme.captionFont) .foregroundStyle(ZyquoTheme.success) } } } } .formStyle(.grouped) } } private struct AppearanceSettings: View { @AppStorage("appearance") private var appearance = "system" var body: some View { Form { Picker("Appearance", selection: $appearance) { Text("System").tag("system") Text("Light").tag("light") Text("Dark").tag("dark") } .pickerStyle(.inline) } .formStyle(.grouped) } } private struct PathLabel: View { let url: URL var body: some View { HStack { Text(url.path.replacingOccurrences(of: NSHomeDirectory(), with: "~")) .font(ZyquoTheme.monoSmallFont) .foregroundStyle(ZyquoTheme.textSecondary) .lineLimit(1) .truncationMode(.middle) Button { NSWorkspace.shared.activateFileViewerSelecting([url]) } label: { Image(systemName: "arrow.right.circle") } .buttonStyle(.plain) .help("Reveal in Finder") } } }