// Author: Simon-Pierre Boucher — contact@spboucher.ai // // Forge Studio — GUI companion for the Forge LLM training framework. import SwiftUI @main struct ForgeStudioApp: App { @State private var model = AppModel() var body: some Scene { WindowGroup("Forge Studio") { ContentView() .environment(model) .frame(minWidth: 980, minHeight: 640) } Settings { SettingsView() .environment(model) } } } @Observable @MainActor final class AppModel { var store: RunStore var supervisor: RunSupervisor var forgeDevice: String? var forgeError: String? init() { let workspace = ForgeBinaryLocator.savedWorkspaceURL ?? FileManager.default.homeDirectoryForCurrentUser .appendingPathComponent("ForgeStudioWorkspace") let store = RunStore(workspace: workspace) self.store = store self.supervisor = RunSupervisor(store: store) recoverOrphans() RunSupervisor.requestNotificationAuth() Task { await self.validateForge() } } /// Crash recovery: a run persisted as active means the app died mid-run. /// If its forge PID is still alive we leave the process alone and record /// the truth (stopped tracking); if it's gone, the run is marked stopped /// with an honest reason. A run never stays in a lying state. private func recoverOrphans() { for var run in store.runs where run.state.isActive { if let pid = run.pid, kill(pid, 0) == 0 { run.failureReason = "L'app a quitté pendant le run ; forge (pid \(pid)) tourne encore — " + "métriques reprises depuis log.csv, contrôle du process perdu." } else { run.failureReason = "L'app a quitté pendant le run ; forge n'est plus actif. " + "Reprise possible depuis ckpt_latest.bin." } run.state = .stopped run.pid = nil store.upsert(run) } } func validateForge() async { guard let binary = ForgeBinaryLocator.savedBinaryURL ?? ForgeBinaryLocator.candidates().first else { forgeError = "Binaire forge introuvable — choisissez-le dans les Réglages." return } switch await ForgeBinaryLocator.validate(binary: binary) { case .success(let v): forgeDevice = v.version forgeError = nil ForgeBinaryLocator.save(binary: binary) case .failure(let e): forgeError = e.localizedDescription } } func datasets() -> [Dataset] { let dataDir = store.workspaceURL.appendingPathComponent("data") let forgeData = ForgeBinaryLocator.savedBinaryURL? .deletingLastPathComponent().deletingLastPathComponent() .appendingPathComponent("data") var found: [Dataset] = [] for root in [dataDir, forgeData].compactMap({ $0 }) { guard let subdirs = try? FileManager.default.contentsOfDirectory( at: root, includingPropertiesForKeys: nil) else { continue } for dir in subdirs { if let ds = Dataset.scan(directory: dir), !found.contains(where: { $0.path == ds.path }) { found.append(ds) } } } return found.sorted { $0.name < $1.name } } }