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>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import Foundation
|
||||
import FoundationModels
|
||||
|
||||
/// Errors that can occur when using Apple Intelligence
|
||||
public enum AppleIntelligenceError: Error, CustomStringConvertible, Sendable {
|
||||
case modelNotAvailable
|
||||
case generationFailed(String)
|
||||
case sessionCreationFailed
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .modelNotAvailable:
|
||||
return "Apple Intelligence model is not available on this device"
|
||||
case .generationFailed(let reason):
|
||||
return "Generation failed: \(reason)"
|
||||
case .sessionCreationFailed:
|
||||
return "Failed to create language model session"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Service wrapper for Apple Intelligence Foundation Models
|
||||
public actor AppleIntelligenceService {
|
||||
/// The language model session
|
||||
private var session: LanguageModelSession?
|
||||
|
||||
/// Whether the model is available
|
||||
public private(set) var isAvailable: Bool = false
|
||||
|
||||
/// Initialize and check model availability
|
||||
public init() async {
|
||||
await checkAvailability()
|
||||
}
|
||||
|
||||
/// Check if Apple Intelligence is available
|
||||
private func checkAvailability() async {
|
||||
let availability = SystemLanguageModel.default.availability
|
||||
switch availability {
|
||||
case .available:
|
||||
isAvailable = true
|
||||
session = LanguageModelSession()
|
||||
case .unavailable:
|
||||
isAvailable = false
|
||||
@unknown default:
|
||||
isAvailable = false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current model status as a string
|
||||
public func getModelStatus() -> String {
|
||||
let availability = SystemLanguageModel.default.availability
|
||||
switch availability {
|
||||
case .available:
|
||||
return "available"
|
||||
case .unavailable(let reason):
|
||||
return "unavailable: \(reason)"
|
||||
@unknown default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a completion for the given prompt (non-streaming)
|
||||
public func complete(prompt: String, temperature: Float?, maxTokens: Int?) async throws -> String {
|
||||
guard isAvailable, let session = session else {
|
||||
throw AppleIntelligenceError.modelNotAvailable
|
||||
}
|
||||
|
||||
let response = try await session.respond(to: prompt)
|
||||
return response.content
|
||||
}
|
||||
|
||||
/// Generate a streaming completion for the given prompt
|
||||
public func streamComplete(
|
||||
prompt: String,
|
||||
temperature: Float?,
|
||||
maxTokens: Int?
|
||||
) -> AsyncThrowingStream<String, Error> {
|
||||
AsyncThrowingStream { continuation in
|
||||
Task {
|
||||
guard self.isAvailable, let session = self.session else {
|
||||
continuation.finish(throwing: AppleIntelligenceError.modelNotAvailable)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let stream = session.streamResponse(to: prompt)
|
||||
for try await partialResponse in stream {
|
||||
continuation.yield(partialResponse.content)
|
||||
}
|
||||
continuation.finish()
|
||||
} catch {
|
||||
continuation.finish(throwing: AppleIntelligenceError.generationFailed(error.localizedDescription))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user