* feat: add Apple Intelligence post-processing provider Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat: guide apple intelligence output Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * fix(build): add fallback stub for Apple Intelligence on older SDKs - Checks if 'FoundationModels.framework' is present in the macOS SDK. - If missing, compiles a stub Swift file that returns 'unavailable' errors instead of failing the build. - Prevents build errors on older Xcode versions (or macOS versions < 26.0) where the macros are not supported. * fix(ui): hide Apple Intelligence option when unavailable - Checks for runtime availability of Apple Intelligence (via 'check_apple_intelligence_availability') in 'settings.rs'. - Only adds the Apple Intelligence provider to the default settings if it is actually available on the device. - Ensures the option does not appear in the UI for unsupported Macs (e.g., Intel or older macOS versions), even if the app was built with support enabled. * move some files around as well as some ui text * format --------- Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> Co-authored-by: CJ Pais <cj@cjpais.com>
139 lines
No EOL
4.1 KiB
Swift
139 lines
No EOL
4.1 KiB
Swift
import Dispatch
|
|
import Foundation
|
|
import FoundationModels
|
|
|
|
@available(macOS 26.0, *)
|
|
@Generable
|
|
private struct CleanedTranscript: Sendable {
|
|
let cleanedText: String
|
|
}
|
|
|
|
// MARK: - Swift implementation for Apple LLM integration
|
|
// This file is compiled via Cargo build script for Apple Silicon targets
|
|
|
|
private typealias ResponsePointer = UnsafeMutablePointer<AppleLLMResponse>
|
|
|
|
private func duplicateCString(_ text: String) -> UnsafeMutablePointer<CChar>? {
|
|
return text.withCString { basePointer in
|
|
guard let duplicated = strdup(basePointer) else {
|
|
return nil
|
|
}
|
|
return duplicated
|
|
}
|
|
}
|
|
|
|
private func truncatedText(_ text: String, limit: Int) -> String {
|
|
guard limit > 0 else { return text }
|
|
let words = text.split(
|
|
maxSplits: .max,
|
|
omittingEmptySubsequences: true,
|
|
whereSeparator: { $0.isWhitespace || $0.isNewline }
|
|
)
|
|
if words.count <= limit {
|
|
return text
|
|
}
|
|
return words.prefix(limit).joined(separator: " ")
|
|
}
|
|
|
|
@_cdecl("is_apple_intelligence_available")
|
|
public func isAppleIntelligenceAvailable() -> Int32 {
|
|
guard #available(macOS 26.0, *) else {
|
|
return 0
|
|
}
|
|
|
|
let model = SystemLanguageModel.default
|
|
switch model.availability {
|
|
case .available:
|
|
return 1
|
|
case .unavailable:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
@_cdecl("process_text_with_apple_llm")
|
|
public func processTextWithAppleLLM(
|
|
_ prompt: UnsafePointer<CChar>,
|
|
maxTokens: Int32
|
|
) -> UnsafeMutablePointer<AppleLLMResponse> {
|
|
let swiftPrompt = String(cString: prompt)
|
|
let responsePtr = ResponsePointer.allocate(capacity: 1)
|
|
responsePtr.initialize(to: AppleLLMResponse(response: nil, success: 0, error_message: nil))
|
|
|
|
guard #available(macOS 26.0, *) else {
|
|
responsePtr.pointee.error_message = duplicateCString(
|
|
"Apple Intelligence requires macOS 26 or newer."
|
|
)
|
|
return responsePtr
|
|
}
|
|
|
|
let model = SystemLanguageModel.default
|
|
guard model.availability == .available else {
|
|
responsePtr.pointee.error_message = duplicateCString(
|
|
"Apple Intelligence is not currently available on this device."
|
|
)
|
|
return responsePtr
|
|
}
|
|
|
|
let tokenLimit = max(0, Int(maxTokens))
|
|
let semaphore = DispatchSemaphore(value: 0)
|
|
|
|
// Thread-safe container to pass results from async task back to calling thread
|
|
final class ResultBox: @unchecked Sendable {
|
|
var response: String?
|
|
var error: String?
|
|
}
|
|
let box = ResultBox()
|
|
|
|
Task.detached(priority: .userInitiated) {
|
|
defer { semaphore.signal() }
|
|
do {
|
|
let session = LanguageModelSession(model: model)
|
|
var output: String
|
|
|
|
do {
|
|
let structured = try await session.respond(
|
|
to: swiftPrompt,
|
|
generating: CleanedTranscript.self
|
|
)
|
|
output = structured.content.cleanedText
|
|
} catch {
|
|
let fallbackGeneration = try await session.respond(to: swiftPrompt)
|
|
output = fallbackGeneration.content
|
|
}
|
|
|
|
if tokenLimit > 0 {
|
|
output = truncatedText(output, limit: tokenLimit)
|
|
}
|
|
box.response = output
|
|
} catch {
|
|
box.error = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
semaphore.wait()
|
|
|
|
// Write to responsePtr on the calling thread after task completes
|
|
if let response = box.response {
|
|
responsePtr.pointee.response = duplicateCString(response)
|
|
responsePtr.pointee.success = 1
|
|
} else {
|
|
responsePtr.pointee.error_message = duplicateCString(box.error ?? "Unknown error")
|
|
}
|
|
|
|
return responsePtr
|
|
}
|
|
|
|
@_cdecl("free_apple_llm_response")
|
|
public func freeAppleLLMResponse(_ response: UnsafeMutablePointer<AppleLLMResponse>?) {
|
|
guard let response = response else { return }
|
|
|
|
if let responseStr = response.pointee.response {
|
|
free(UnsafeMutablePointer(mutating: responseStr))
|
|
}
|
|
|
|
if let errorStr = response.pointee.error_message {
|
|
free(UnsafeMutablePointer(mutating: errorStr))
|
|
}
|
|
|
|
response.deallocate()
|
|
} |