SPB Git

spb/zyquo-router Public MIT

One local endpoint, every AI provider — a private OpenAI-compatible LLM gateway for your Mac (170 models, 12 providers).

Swift 95.7% Python 2.3% Shell 1.2% Makefile 0.9%
6.1 KB · 173 lines swift
Raw Blame History
1//2//  ZyquoRouterApp.swift3//  Zyquo Router4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//89import SwiftUI1011struct ZyquoRouterApp: App {12    @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate13    @StateObject private var environment = AppEnvironment()14    @AppStorage("autoStartServer") private var autoStart = false1516    @AppStorage("menuBarExtraEnabled") private var menuBarExtraEnabled = true1718    var body: some Scene {19        WindowGroup("Zyquo Router") {20            MainWindowView()21                .environmentObject(environment.server)22                .environmentObject(environment.catalog)23                .environmentObject(environment.appearance)24                .environmentObject(environment.vault)25                .environmentObject(environment.localKeys)26                .environmentObject(environment.routerConfig)27                .onAppear {28                    if autoStart, !environment.server.isRunning {29                        environment.server.start()30                    }31                }32        }33        .defaultSize(34            width: ZyquoMetrics.windowDefaultWidth,35            height: ZyquoMetrics.windowDefaultHeight36        )37        .commands {38            AppCommands(server: environment.server)39        }4041        Settings {42            SettingsView()43                .environmentObject(environment.server)44                .environmentObject(environment.catalog)45                .environmentObject(environment.appearance)46                .environmentObject(environment.vault)47                .environmentObject(environment.localKeys)48                .environmentObject(environment.routerConfig)49        }5051        MenuBarExtra(isInserted: $menuBarExtraEnabled) {52            MenuBarContent()53                .environmentObject(environment.server)54        } label: {55            Image(nsImage: Self.menuBarImage)56        }57    }5859    /// Template glyph rendered by scripts/generate-icon.sh; falls back to an60    /// SF Symbol during `swift run` (no bundle resources).61    private static let menuBarImage: NSImage = {62        if let url = Bundle.main.url(forResource: "MenuBarIcon", withExtension: "png"),63           let image = NSImage(contentsOf: url) {64            image.isTemplate = true65            image.size = NSSize(width: 18, height: 18)66            return image67        }68        let fallback = NSImage(69            systemSymbolName: "point.3.connected.trianglepath.dotted",70            accessibilityDescription: "Zyquo Router"71        ) ?? NSImage()72        fallback.isTemplate = true73        return fallback74    }()75}7677/// Menu bar extra: status, Start/Stop, live numbers, copy endpoint.78private struct MenuBarContent: View {79    @EnvironmentObject private var server: ServerController80    @State private var summary = ""8182    var body: some View {83        Group {84            Text(statusLine)85            if !summary.isEmpty {86                Text(summary)87            }88            Divider()89            Button(server.isRunning ? "Stop Server" : "Start Server") {90                server.toggle()91            }92            .keyboardShortcut("r")93            Button("Copy Endpoint URL") {94                NSPasteboard.general.clearContents()95                NSPasteboard.general.setString(server.endpointURL, forType: .string)96            }97            Divider()98            Button("Open Zyquo Router") {99                NSApp.activate(ignoringOtherApps: true)100            }101            Button("Quit") {102                NSApp.terminate(nil)103            }104        }105        .task {106            while !Task.isCancelled {107                await refreshSummary()108                try? await Task.sleep(nanoseconds: 5_000_000_000)109            }110        }111    }112113    private var statusLine: String {114        switch server.state {115        case .running(let port): return "● Running on :\(port)"116        case .starting: return "◐ Starting…"117        case .failed: return "○ Failed to start"118        case .stopped: return "○ Stopped"119        }120    }121122    private func refreshSummary() async {123        let meter = server.usageMeter124        let today = await meter.totals(since: Calendar.current.startOfDay(for: Date()))125        let lastMinute = await meter.requestsPerMinute(minutes: 1).first ?? 0126        summary = String(format: "%d req/min · $%.2f today", lastMinute, today.cost)127    }128}129130/// App-level menu commands: ⌘1–6 sections, ⌘⇧C copy endpoint.131/// (⌘R start/stop lives on the dashboard's Start button.)132struct AppCommands: Commands {133    let server: ServerController134135    var body: some Commands {136        CommandMenu("Server") {137            Button(server.isRunning ? "Stop Server" : "Start Server") {138                server.toggle()139            }140            .keyboardShortcut("r", modifiers: .command)141        }142        CommandMenu("Go") {143            ForEach(Array(AppSection.allCases.enumerated()), id: \.element.id) { index, section in144                Button(section.rawValue) {145                    UserDefaults.standard.set(section.rawValue, forKey: "selectedSection")146                }147                .keyboardShortcut(KeyEquivalent(Character("\(index + 1)")), modifiers: .command)148            }149        }150        CommandGroup(after: .pasteboard) {151            Button("Copy Endpoint URL") {152                NSPasteboard.general.clearContents()153                NSPasteboard.general.setString(server.endpointURL, forType: .string)154            }155            .keyboardShortcut("c", modifiers: [.command, .shift])156        }157    }158}159160final class AppDelegate: NSObject, NSApplicationDelegate {161    func applicationDidFinishLaunching(_ notification: Notification) {162        // Ensure the app fronts correctly when launched outside Finder163        // (e.g. `swift run` or `open` from a terminal during development).164        NSApplication.shared.setActivationPolicy(.regular)165        NSApplication.shared.activate(ignoringOtherApps: true)166    }167168    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {169        // "Keep serving when window closed" — the server survives by default.170        !UserDefaults.standard.bool(forKey: "keepServingWhenClosed")171    }172}173