Skip to content

Commit ab9779d

Browse files
Merge pull request #252 from azooKey/fix/use_foundation_models_for_text_selection_magic_conversion
fix: 選択テキストに対するいい感じ変換でもFoundationModelsを利用
2 parents 9bc089a + ec07c16 commit ab9779d

3 files changed

Lines changed: 123 additions & 18 deletions

File tree

Core/Sources/Core/MagicConversion/AIBackend.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,25 @@ public enum AIClient {
3232
return try await OpenAIClient.sendRequest(request, apiKey: apiKey, apiEndpoint: apiEndpoint, logger: logger)
3333
}
3434
}
35+
36+
public static func sendTextTransformRequest(
37+
_ prompt: String,
38+
backend: AIBackend,
39+
modelName: String = "",
40+
apiKey: String = "",
41+
apiEndpoint: String = "",
42+
logger: ((String) -> Void)? = nil
43+
) async throws -> String {
44+
switch backend {
45+
case .foundationModels:
46+
return try await FoundationModelsClientCompat.sendTextTransformRequest(prompt, logger: logger)
47+
case .openAI:
48+
return try await OpenAIClient.sendTextTransformRequest(
49+
prompt: prompt,
50+
modelName: modelName,
51+
apiKey: apiKey,
52+
apiEndpoint: apiEndpoint
53+
)
54+
}
55+
}
3556
}

Core/Sources/Core/MagicConversion/FoundationModelsClient.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ public enum FoundationModelsClient {
8888
@Guide(description: "Array of prediction strings", .count(3...5))
8989
public var predictions: [String]
9090
}
91+
92+
@Generable
93+
public struct TextTransformResponse: Codable {
94+
@Guide(description: "The transformed text")
95+
public var result: String
96+
}
9197
#endif
9298

9399
public static func sendRequest(_ request: OpenAIRequest, logger: ((String) -> Void)? = nil) async throws -> [String] {
@@ -138,6 +144,43 @@ public enum FoundationModelsClient {
138144
throw FoundationModelsError.unavailable(.frameworkNotAvailable)
139145
#endif
140146
}
147+
148+
public static func sendTextTransformRequest(_ prompt: String, logger: ((String) -> Void)? = nil) async throws -> String {
149+
#if canImport(FoundationModels)
150+
logger?("Foundation Models text transform request started")
151+
152+
let systemModel = SystemLanguageModel.default
153+
154+
switch systemModel.availability {
155+
case .available:
156+
break
157+
case .unavailable(let reason):
158+
logger?("Foundation Models not available: \(reason)")
159+
let mappedReason: FoundationModelsAvailability.UnavailabilityReason = switch reason {
160+
case .deviceNotEligible:
161+
.deviceNotEligible
162+
case .appleIntelligenceNotEnabled:
163+
.appleIntelligenceNotEnabled
164+
case .modelNotReady:
165+
.modelNotReady
166+
@unknown default:
167+
.deviceNotEligible
168+
}
169+
throw FoundationModelsError.unavailable(mappedReason)
170+
@unknown default:
171+
logger?("Foundation Models availability unknown")
172+
throw FoundationModelsError.unavailable(.deviceNotEligible)
173+
}
174+
175+
let session = LanguageModelSession(model: systemModel)
176+
let response = try await session.respond(to: prompt, generating: TextTransformResponse.self)
177+
178+
logger?("Received structured response for text transform")
179+
return response.content.result.trimmingCharacters(in: .whitespacesAndNewlines)
180+
#else
181+
throw FoundationModelsError.unavailable(.frameworkNotAvailable)
182+
#endif
183+
}
141184
}
142185

143186
// Compatibility wrapper for older macOS versions
@@ -157,4 +200,12 @@ public enum FoundationModelsClientCompat {
157200
throw FoundationModelsError.unavailable(.osVersionTooOld)
158201
}
159202
}
203+
204+
public static func sendTextTransformRequest(_ prompt: String, logger: ((String) -> Void)? = nil) async throws -> String {
205+
if #available(macOS 26.0, *) {
206+
return try await FoundationModelsClient.sendTextTransformRequest(prompt, logger: logger)
207+
} else {
208+
throw FoundationModelsError.unavailable(.osVersionTooOld)
209+
}
210+
}
160211
}

azooKeyMac/InputController/azooKeyMacInputController+SelectedTextTransform.swift

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -227,25 +227,43 @@ extension azooKeyMacInputController {
227227
self.segmentsManager.appendDebugMessage("transformSelectedText: Created system prompt")
228228
}
229229

230-
// Get API key from Config
230+
let backend: AIBackend
231+
switch aiBackend {
232+
case .foundationModels:
233+
backend = .foundationModels
234+
case .openAI:
235+
backend = .openAI
236+
case .off:
237+
return
238+
}
239+
231240
let apiKey = Config.OpenAiApiKey().value
232-
guard !apiKey.isEmpty else {
233-
await MainActor.run {
234-
self.segmentsManager.appendDebugMessage("transformSelectedText: No OpenAI API key configured")
241+
if backend == .openAI {
242+
guard !apiKey.isEmpty else {
243+
await MainActor.run {
244+
self.segmentsManager.appendDebugMessage("transformSelectedText: No OpenAI API key configured")
245+
}
246+
return
235247
}
236-
return
237248
}
238249

239250
await MainActor.run {
240-
self.segmentsManager.appendDebugMessage("transformSelectedText: API key found, making request")
251+
let message = backend == .openAI
252+
? "transformSelectedText: API key found, making request"
253+
: "transformSelectedText: Using Foundation Models, making request"
254+
self.segmentsManager.appendDebugMessage(message)
241255
}
242256

243257
let modelName = Config.OpenAiModelName().value
244-
let result = try await OpenAIClient.sendTextTransformRequest(
245-
prompt: systemPrompt,
258+
let result = try await AIClient.sendTextTransformRequest(
259+
systemPrompt,
260+
backend: backend,
246261
modelName: modelName,
247262
apiKey: apiKey,
248-
apiEndpoint: self.endpoint
263+
apiEndpoint: self.endpoint,
264+
logger: { [weak self] message in
265+
self?.segmentsManager.appendDebugMessage(message)
266+
}
249267
)
250268

251269
await MainActor.run {
@@ -396,25 +414,40 @@ extension azooKeyMacInputController {
396414

397415
systemPrompt += "\n\nUser instructions: \(prompt)"
398416

399-
// Get API key from Config
417+
let backend: AIBackend
418+
switch aiBackend {
419+
case .foundationModels:
420+
backend = .foundationModels
421+
case .openAI:
422+
backend = .openAI
423+
case .off:
424+
throw NSError(domain: "TransformationError", code: -1, userInfo: [NSLocalizedDescriptionKey: "AI transformation is not available. Please enable AI backend in preferences."])
425+
}
426+
400427
let apiKey = Config.OpenAiApiKey().value
401-
guard !apiKey.isEmpty else {
402-
await MainActor.run {
403-
self.segmentsManager.appendDebugMessage("getTransformationPreview: No OpenAI API key configured")
428+
if backend == .openAI {
429+
guard !apiKey.isEmpty else {
430+
await MainActor.run {
431+
self.segmentsManager.appendDebugMessage("getTransformationPreview: No OpenAI API key configured")
432+
}
433+
throw NSError(domain: "TransformationError", code: -2, userInfo: [NSLocalizedDescriptionKey: "OpenAI API key is missing. Please configure your API key in preferences."])
404434
}
405-
throw NSError(domain: "TransformationError", code: -2, userInfo: [NSLocalizedDescriptionKey: "OpenAI API key is missing. Please configure your API key in preferences."])
406435
}
407436

408437
await MainActor.run {
409-
self.segmentsManager.appendDebugMessage("getTransformationPreview: Sending preview request to API")
438+
self.segmentsManager.appendDebugMessage("getTransformationPreview: Sending preview request (\(backend.rawValue))")
410439
}
411440

412441
let modelName = Config.OpenAiModelName().value
413-
let result = try await OpenAIClient.sendTextTransformRequest(
414-
prompt: systemPrompt,
442+
let result = try await AIClient.sendTextTransformRequest(
443+
systemPrompt,
444+
backend: backend,
415445
modelName: modelName,
416446
apiKey: apiKey,
417-
apiEndpoint: self.endpoint
447+
apiEndpoint: self.endpoint,
448+
logger: { [weak self] message in
449+
self?.segmentsManager.appendDebugMessage(message)
450+
}
418451
)
419452

420453
await MainActor.run {

0 commit comments

Comments
 (0)