// // ZyquoLocalApp.swift // Zyquo Local // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // import SwiftUI struct ZyquoLocalApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate @State private var app = AppModel() @Environment(\.openWindow) private var openWindow var body: some Scene { WindowGroup { if HardwareGate.isAppleSilicon { RootView() .environment(app) .frame(minWidth: 980, minHeight: 640) .onAppear { QuickChatPanelController.shared.setup(app: app) } } else { UnsupportedHardwareView() } } .defaultSize(width: 1240, height: 800) .windowStyle(.hiddenTitleBar) .windowToolbarStyle(.unifiedCompact) .commands { CommandGroup(replacing: .newItem) { Button("New Chat") { app.newConversation() } .keyboardShortcut("n", modifiers: .command) } CommandMenu("Model") { Button("Model Switcher…") { NotificationCenter.default.post(name: .zyquoOpenModelSwitcher, object: nil) } .keyboardShortcut("k", modifiers: .command) Button("Library") { app.route = .library } .keyboardShortcut("l", modifiers: .command) Divider() Button("Compare Models…") { openWindow(id: "compare") } Button("Quick Chat") { QuickChatPanelController.shared.toggle() } .keyboardShortcut(.space, modifiers: .option) Divider() Button("Unload Model") { Task { await app.unloadModel() } } .disabled(app.loadedModelID == nil) } CommandGroup(after: .textEditing) { Button("Search Chats") { NotificationCenter.default.post(name: .zyquoFocusSearch, object: nil) } .keyboardShortcut("f", modifiers: .command) } CommandGroup(replacing: .importExport) { Button("Export Conversation…") { NotificationCenter.default.post(name: .zyquoExportConversation, object: nil) } .keyboardShortcut("e", modifiers: [.command, .shift]) } } Window("Compare Models", id: "compare") { CompareView() .environment(app) } .defaultSize(width: 1100, height: 700) Settings { SettingsView() .environment(app) } MenuBarExtra(isInserted: menuBarBinding) { MenuBarExtraContent() .environment(app) } label: { Image(nsImage: menuBarIcon) } .menuBarExtraStyle(.menu) } private var menuBarBinding: Binding { Binding( get: { app.settings.menuBarExtraEnabled }, set: { app.settings.menuBarExtraEnabled = $0 } ) } private var menuBarIcon: NSImage { if let url = Bundle.main.url(forResource: "MenuBarIcon", withExtension: "png"), let image = NSImage(contentsOf: url) { image.isTemplate = true image.size = NSSize(width: 18, height: 18) return image } let fallback = NSImage( systemSymbolName: "bolt.circle", accessibilityDescription: "Zyquo Local")! fallback.isTemplate = true return fallback } } /// Menu bar extra: state at a glance + quick actions. struct MenuBarExtraContent: View { @Environment(AppModel.self) private var app @Environment(\.openWindow) private var openWindow var body: some View { Group { if let repoID = app.loadedModelID { Text("Loaded: \(shortModelName(repoID))") } else { Text("No model loaded") } if app.downloads.activeCount > 0 { Text("Downloading \(app.downloads.activeCount) model\(app.downloads.activeCount == 1 ? "" : "s") · \(Int(app.downloads.overallFraction * 100))%") } Divider() Button("Quick Chat ⌥Space") { QuickChatPanelController.shared.toggle() } Button("Open Zyquo Local") { NSApp.activate(ignoringOtherApps: true) NSApp.windows.first { $0.canBecomeMain }?.makeKeyAndOrderFront(nil) } Divider() Button("Quit Zyquo Local") { NSApp.terminate(nil) } } } } final class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ notification: Notification) { // Proper foreground activation when launched from a terminal. NSApp.setActivationPolicy(.regular) NSApp.activate(ignoringOtherApps: true) } }