SPB Git

spb/zyquo-mlx Public MIT

The local MLX foundry for your Mac — run, fine-tune, quantize, and ship models. Nothing leaves your machine.

Swift 93.4% Python 3.8% Makefile 2.2% Shell 0.5%
6.2 KB · 192 lines swift
Raw Blame History
1//2//  SettingsView.swift3//  Zyquo MLX4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//89import SwiftUI1011/// Settings window (charter §4.2): native tabs, 760×560.12/// Functional depth (HF token vault, compute limits) lands in Phase 6.13struct SettingsView: View {14    var body: some View {15        TabView {16            GeneralSettings()17                .tabItem { Label("General", systemImage: "gearshape") }18            ComputeSettings()19                .tabItem { Label("Compute", systemImage: "cpu") }20            PythonSettings()21                .tabItem { Label("Python", systemImage: "terminal") }22            StorageSettings()23                .tabItem { Label("Storage", systemImage: "internaldrive") }24            HuggingFaceSettings()25                .tabItem { Label("Hugging Face", systemImage: "key") }26            AppearanceSettings()27                .tabItem { Label("Appearance", systemImage: "paintpalette") }28        }29        .frame(width: 760, height: 560)30    }31}3233private struct GeneralSettings: View {34    var body: some View {35        Form {36            LabeledContent("Models") {37                PathLabel(url: PersistenceService.modelsDirectory)38            }39            LabeledContent("Datasets") {40                PathLabel(url: PersistenceService.datasetsDirectory)41            }42            LabeledContent("Training runs") {43                PathLabel(url: PersistenceService.runsDirectory)44            }45        }46        .formStyle(.grouped)47    }48}4950private struct ComputeSettings: View {51    var body: some View {52        Form {53            Section("GPU") {54                LabeledContent("Unified memory") {55                    Text(ByteCountFormatter.string(56                        fromByteCount: MemoryAdvisor.physicalMemory, countStyle: .memory))57                }58                LabeledContent("Recommended working set") {59                    Text(ByteCountFormatter.string(60                        fromByteCount: MemoryAdvisor.recommendedWorkingSet, countStyle: .memory))61                }62            }63        }64        .formStyle(.grouped)65    }66}6768private struct PythonSettings: View {69    @State private var status = "Checking…"7071    var body: some View {72        Form {73            Section("Environment") {74                LabeledContent("Status") { Text(status) }75                LabeledContent("Location") {76                    PathLabel(url: PersistenceService.pythonDirectory)77                }78                LabeledContent("Pinned packages") {79                    Text(PythonEnvironment.corePins.joined(separator: ", "))80                        .font(ZyquoTheme.monoSmallFont)81                }82            }83        }84        .formStyle(.grouped)85        .task {86            status = await PythonEnvironment.shared.isProvisioned87                ? "Ready (Python \(PythonEnvironment.pythonVersion))"88                : "Not provisioned — installs automatically on first use"89        }90    }91}9293private struct StorageSettings: View {94    @State private var usage: [(String, Int64)] = []9596    var body: some View {97        Form {98            Section("Disk usage") {99                ForEach(usage, id: \.0) { name, bytes in100                    LabeledContent(name) {101                        Text(ByteCountFormatter.string(fromByteCount: bytes, countStyle: .file))102                    }103                }104            }105        }106        .formStyle(.grouped)107        .task {108            usage = [109                ("Models", directorySize(PersistenceService.modelsDirectory)),110                ("Datasets", directorySize(PersistenceService.datasetsDirectory)),111                ("Runs", directorySize(PersistenceService.runsDirectory)),112                ("Python", directorySize(PersistenceService.pythonDirectory)),113            ]114        }115    }116117    private func directorySize(_ url: URL) -> Int64 {118        let fm = FileManager.default119        guard let files = try? fm.subpathsOfDirectory(atPath: url.path) else { return 0 }120        return files.reduce(Int64(0)) { total, path in121            let size = (try? url.appendingPathComponent(path)122                .resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? 0123            return total + Int64(size)124        }125    }126}127128private struct HuggingFaceSettings: View {129    @State private var token = HFTokenStore.token ?? ""130    @State private var saved = false131132    var body: some View {133        Form {134            Section("Access token") {135                SecureField("hf_…", text: $token)136                Text("Stored in your login Keychain — used for gated models and higher rate limits.")137                    .font(ZyquoTheme.captionFont)138                    .foregroundStyle(ZyquoTheme.textSecondary)139                HStack {140                    Button("Save") {141                        HFTokenStore.save(token.trimmingCharacters(in: .whitespaces))142                        saved = true143                    }144                    if saved {145                        Text("Saved ✓")146                            .font(ZyquoTheme.captionFont)147                            .foregroundStyle(ZyquoTheme.success)148                    }149                }150            }151        }152        .formStyle(.grouped)153    }154}155156private struct AppearanceSettings: View {157    @AppStorage("appearance") private var appearance = "system"158159    var body: some View {160        Form {161            Picker("Appearance", selection: $appearance) {162                Text("System").tag("system")163                Text("Light").tag("light")164                Text("Dark").tag("dark")165            }166            .pickerStyle(.inline)167        }168        .formStyle(.grouped)169    }170}171172private struct PathLabel: View {173    let url: URL174175    var body: some View {176        HStack {177            Text(url.path.replacingOccurrences(of: NSHomeDirectory(), with: "~"))178                .font(ZyquoTheme.monoSmallFont)179                .foregroundStyle(ZyquoTheme.textSecondary)180                .lineLimit(1)181                .truncationMode(.middle)182            Button {183                NSWorkspace.shared.activateFileViewerSelecting([url])184            } label: {185                Image(systemName: "arrow.right.circle")186            }187            .buttonStyle(.plain)188            .help("Reveal in Finder")189        }190    }191}192