- Add Vision framework integration for image analysis (OCR, classification) - Add image attachment support in chat UI with drag & drop - Add recent images sidebar from Downloads/Desktop - Add copy to clipboard button for assistant responses - Add gRPC reflection service with toggle in settings - Create proper .proto file and generate Swift code - Add server restart when toggling reflection setting - Fix port number formatting in settings (remove comma grouping) - Update gRPC dependencies to v2.x 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
123 lines
3.5 KiB
Swift
123 lines
3.5 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, serverManager: serverManager)
|
|
}
|
|
.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"
|
|
}
|
|
}
|