swift-apple-intelligence-grpc/Sources/AppleIntelligenceApp/App.swift
Mathias Beaulieu-Duncan e0bf17da3d Add macOS menu bar app with chat and settings
- Restructure project into three targets:
  - AppleIntelligenceCore: Shared gRPC service code
  - AppleIntelligenceServer: CLI server
  - AppleIntelligenceApp: Menu bar app

- Menu bar app features:
  - Toggle server on/off from menu bar
  - Chat window with streaming AI responses
  - Settings: host, port, API key, auto-start, launch at login
  - Proper window focus handling for menu bar apps

- Add build scripts for distribution:
  - build-app.sh: Creates signed .app bundle
  - create-dmg.sh: Creates distributable DMG

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 04:31:31 -05:00

123 lines
3.4 KiB
Swift

import SwiftUI
@main
struct AppleIntelligenceApp: App {
@State private var settings = AppSettings()
@State private var serverManager: ServerManager?
@State private var chatViewModel = ChatViewModel()
@State private var didAutoStart = false
var body: some Scene {
MenuBarExtra {
if let serverManager {
MenuView(
serverManager: serverManager,
settings: settings
)
}
} label: {
Image(systemName: serverManager?.state.isRunning == true ? "brain.fill" : "brain")
.onAppear {
// Auto-start server on app launch if enabled
if !didAutoStart {
didAutoStart = true
if settings.autoStartServer, let serverManager {
serverManager.start()
}
}
}
}
Window("Chat", id: "chat") {
ChatView(viewModel: chatViewModel)
}
.defaultSize(width: 500, height: 600)
Window("Settings", id: "settings") {
SettingsView(settings: settings)
}
.windowResizability(.contentSize)
}
init() {
let settings = AppSettings()
_settings = State(initialValue: settings)
_serverManager = State(initialValue: ServerManager(settings: settings))
}
}
struct MenuView: View {
@Bindable var serverManager: ServerManager
@Bindable var settings: AppSettings
@Environment(\.openWindow) private var openWindow
var body: some View {
VStack(alignment: .leading, spacing: 4) {
// Status section
HStack {
Circle()
.fill(statusColor)
.frame(width: 8, height: 8)
Text(serverManager.state.statusText)
.font(.headline)
}
.padding(.bottom, 4)
// Model status
Text("Model: \(modelStatusText)")
.font(.caption)
.foregroundStyle(.secondary)
Divider()
.padding(.vertical, 4)
// Toggle button
Button(serverManager.state.isRunning ? "Stop Server" : "Start Server") {
serverManager.toggle()
}
.keyboardShortcut("s", modifiers: .command)
Divider()
.padding(.vertical, 4)
Button("Open Chat...") {
openWindow(id: "chat")
NSApp.activate(ignoringOtherApps: true)
}
.keyboardShortcut("c", modifiers: .command)
Button("Settings...") {
openWindow(id: "settings")
NSApp.activate(ignoringOtherApps: true)
}
.keyboardShortcut(",", modifiers: .command)
Divider()
.padding(.vertical, 4)
Button("Quit") {
NSApplication.shared.terminate(nil)
}
.keyboardShortcut("q", modifiers: .command)
}
.padding(8)
}
private var statusColor: Color {
switch serverManager.state {
case .stopped:
return .gray
case .starting:
return .yellow
case .running:
return .green
case .error:
return .red
}
}
private var modelStatusText: String {
"Internal"
}
}