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// CommandPalette.swift3// Zyquo Router4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//8// ⌘K: fuzzy access to everything — start/stop, sections, copy endpoint,9// and copy any model ID. Return runs the highlighted action.10//1112import SwiftUI1314struct CommandPalette: View {15 @Binding var isPresented: Bool16 @EnvironmentObject private var server: ServerController17 @EnvironmentObject private var catalog: ModelCatalog1819 @State private var query = ""20 @State private var highlighted = 021 @FocusState private var fieldFocused: Bool2223 private struct Action: Identifiable {24 let id: String25 let title: String26 let subtitle: String?27 let systemImage: String28 let run: () -> Void29 }3031 private var actions: [Action] {32 var all: [Action] = [33 Action(34 id: "server",35 title: server.isRunning ? "Stop Server" : "Start Server",36 subtitle: server.isRunning ? "Port \(server.port)" : nil,37 systemImage: "power",38 run: { server.toggle() }39 ),40 Action(41 id: "copy-endpoint",42 title: "Copy Endpoint URL",43 subtitle: server.endpointURL,44 systemImage: "doc.on.doc",45 run: {46 NSPasteboard.general.clearContents()47 NSPasteboard.general.setString(server.endpointURL, forType: .string)48 }49 ),50 ]51 for section in AppSection.allCases {52 all.append(Action(53 id: "go-\(section.rawValue)",54 title: "Go to \(section.rawValue)",55 subtitle: nil,56 systemImage: section.systemImage,57 run: { UserDefaults.standard.set(section.rawValue, forKey: "selectedSection") }58 ))59 }60 for model in catalog.all {61 let id = RequestRouter.namespacedID(for: model)62 all.append(Action(63 id: "model-\(id)",64 title: id,65 subtitle: "Copy model ID",66 systemImage: "square.grid.2x2",67 run: {68 NSPasteboard.general.clearContents()69 NSPasteboard.general.setString(id, forType: .string)70 }71 ))72 }73 return all74 }7576 private var filtered: [Action] {77 let trimmed = query.trimmingCharacters(in: .whitespaces)78 guard !trimmed.isEmpty else {79 return Array(actions.prefix(9))80 }81 return Array(actions.filter {82 $0.title.localizedCaseInsensitiveContains(trimmed)83 }.prefix(9))84 }8586 var body: some View {87 VStack(spacing: 0) {88 TextField("Type a command or model…", text: $query)89 .textFieldStyle(.plain)90 .font(ZyquoFont.body(size: 15))91 .padding(ZyquoSpacing.md)92 .focused($fieldFocused)93 .onSubmit { runHighlighted() }94 .onChange(of: query) { _ in highlighted = 0 }9596 ZyquoHairline()9798 VStack(spacing: 0) {99 ForEach(Array(filtered.enumerated()), id: \.element.id) { index, action in100 HStack(spacing: ZyquoSpacing.sm) {101 Image(systemName: action.systemImage)102 .font(.system(size: 12))103 .foregroundStyle(index == highlighted ? ZyquoColor.accent : ZyquoColor.textSecondary)104 .frame(width: 18)105 Text(action.title)106 .font(action.id.hasPrefix("model-") ? ZyquoFont.mono(size: 12.5) : ZyquoFont.body())107 .foregroundStyle(ZyquoColor.textPrimary)108 .lineLimit(1)109 .truncationMode(.middle)110 Spacer()111 if let subtitle = action.subtitle {112 Text(subtitle)113 .font(ZyquoFont.caption)114 .foregroundStyle(ZyquoColor.textTertiary)115 .lineLimit(1)116 }117 }118 .padding(.horizontal, ZyquoSpacing.md)119 .padding(.vertical, ZyquoSpacing.xs)120 .background(index == highlighted ? ZyquoColor.accentSubtle : .clear)121 .contentShape(Rectangle())122 .onTapGesture {123 action.run()124 isPresented = false125 }126 .onHover { hover in127 if hover { highlighted = index }128 }129 }130 }131 .padding(.vertical, ZyquoSpacing.xxs)132 }133 .frame(width: 520)134 .background(135 RoundedRectangle(cornerRadius: ZyquoRadius.large, style: .continuous)136 .fill(ZyquoColor.surface)137 .overlay(138 RoundedRectangle(cornerRadius: ZyquoRadius.large, style: .continuous)139 .strokeBorder(ZyquoColor.border, lineWidth: ZyquoMetrics.hairline)140 )141 )142 .zyquoSoftShadow()143 .onAppear { fieldFocused = true }144 .background(paletteKeyHandlers)145 }146147 /// Hidden buttons carrying the arrow-key/escape shortcuts.148 private var paletteKeyHandlers: some View {149 Group {150 Button("") { highlighted = min(highlighted + 1, filtered.count - 1) }151 .keyboardShortcut(.downArrow, modifiers: [])152 Button("") { highlighted = max(highlighted - 1, 0) }153 .keyboardShortcut(.upArrow, modifiers: [])154 Button("") { isPresented = false }155 .keyboardShortcut(.escape, modifiers: [])156 }157 .hidden()158 }159160 private func runHighlighted() {161 guard filtered.indices.contains(highlighted) else { return }162 filtered[highlighted].run()163 isPresented = false164 }165}166