SPB Git

Repository Public

ForgeStudioApp.swift 3.4 KB 98 lines swift

1// Author: Simon-Pierre Boucher — contact@spboucher.ai2//3// Forge Studio — GUI companion for the Forge LLM training framework.4import SwiftUI56@main7struct ForgeStudioApp: App {8    @State private var model = AppModel()910    var body: some Scene {11        WindowGroup("Forge Studio") {12            ContentView()13                .environment(model)14                .frame(minWidth: 980, minHeight: 640)15        }16        Settings {17            SettingsView()18                .environment(model)19        }20    }21}2223@Observable24@MainActor25final class AppModel {26    var store: RunStore27    var supervisor: RunSupervisor28    var forgeDevice: String?29    var forgeError: String?3031    init() {32        let workspace = ForgeBinaryLocator.savedWorkspaceURL33            ?? FileManager.default.homeDirectoryForCurrentUser34                .appendingPathComponent("ForgeStudioWorkspace")35        let store = RunStore(workspace: workspace)36        self.store = store37        self.supervisor = RunSupervisor(store: store)38        recoverOrphans()39        Task { await self.validateForge() }40    }4142    /// Crash recovery: a run persisted as active means the app died mid-run.43    /// If its forge PID is still alive we leave the process alone and record44    /// the truth (stopped tracking); if it's gone, the run is marked stopped45    /// with an honest reason. A run never stays in a lying state.46    private func recoverOrphans() {47        for var run in store.runs where run.state.isActive {48            if let pid = run.pid, kill(pid, 0) == 0 {49                run.failureReason =50                    "L'app a quitté pendant le run ; forge (pid \(pid)) tourne encore — "51                    + "métriques reprises depuis log.csv, contrôle du process perdu."52            } else {53                run.failureReason =54                    "L'app a quitté pendant le run ; forge n'est plus actif. "55                    + "Reprise possible depuis ckpt_latest.bin."56            }57            run.state = .stopped58            run.pid = nil59            store.upsert(run)60        }61    }6263    func validateForge() async {64        guard let binary = ForgeBinaryLocator.savedBinaryURL65            ?? ForgeBinaryLocator.candidates().first else {66            forgeError = "Binaire forge introuvable — choisissez-le dans les Réglages."67            return68        }69        switch await ForgeBinaryLocator.validate(binary: binary) {70        case .success(let v):71            forgeDevice = v.version72            forgeError = nil73            ForgeBinaryLocator.save(binary: binary)74        case .failure(let e):75            forgeError = e.localizedDescription76        }77    }7879    func datasets() -> [Dataset] {80        let dataDir = store.workspaceURL.appendingPathComponent("data")81        let forgeData = ForgeBinaryLocator.savedBinaryURL?82            .deletingLastPathComponent().deletingLastPathComponent()83            .appendingPathComponent("data")84        var found: [Dataset] = []85        for root in [dataDir, forgeData].compactMap({ $0 }) {86            guard let subdirs = try? FileManager.default.contentsOfDirectory(87                at: root, includingPropertiesForKeys: nil) else { continue }88            for dir in subdirs {89                if let ds = Dataset.scan(directory: dir),90                   !found.contains(where: { $0.path == ds.path }) {91                    found.append(ds)92                }93            }94        }95        return found.sorted { $0.name < $1.name }96    }97}98
Files 22ada2e0aade629007456631dbb3e14fbba9ef7b
Menu

Theme