swift-apple-intelligence-grpc/Sources/AppleIntelligenceCore/Services/AppleIntelligenceService.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

98 lines
3.2 KiB
Swift

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))
}
}
}
}
}