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%
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