Files
OSGKeyboard/OSGKeyboardExt/Services/PolishingService.swift
T
Zhongshu 2e2d8e33b3 [P0-③] API Key data flow fix
- AppGroup.defaults: in DEBUG, missing App Group is a hard fatalError
  with a precise remediation message (was a soft print + .standard
  fallback, which desynced the keyboard extension from the main App).
  Release keeps the fallback + NSLog so end-users still get a usable app.
- KeyboardViewController.loadPersistedLocale now prints a masked DEBUG
  view of the live App Group config (provider, baseURL, masked key,
  model, mode, locale) so the extension's view is visible in the
  device console.
- KeyboardViewController.handleFinalTranscript now routes by typed error:
    noAPIKey  → red error '未配置 API Key · 请在主 App 设置中填写'
    http 401  → red error 'API Key 无效 (401) · 请检查主 App 设置'
    http 429  → red error 'API 限流 (429) · 请稍后再试'
    other     → insert raw transcript + generic error badge
- APISettingsCard gains a 'Test connection' button that runs a single
  client.polish('ping') round-trip and surfaces the typed result inline.
- PolishingService.timeout raised 12s → 15s to match LLMClient.request
  timeout (was racing and discarding successful responses in 12–15s).
- Tests: 4 new cases (HTTP 429, transport timeout, App Group cross-process,
  AppGroupStore→LLMClient noAPIKey). All 8 tests pass on iPhone 16e sim.

xcodebuild iOS Simulator: SUCCEEDED
xcodebuild test: 8/8 passed
2026-06-18 10:50:02 +08:00

47 lines
1.4 KiB
Swift

// PolishingService.swift
// OSGKeyboard · Keyboard Extension
//
// Takes raw ASR transcript and runs it through the user's configured LLM
// to produce polished, well-punctuated text. Falls back to the raw transcript
// if the LLM call fails or times out.
import Foundation
import OSGKeyboardShared
public actor PolishingService {
public enum PolishError: Error {
case noTranscript
case timeout
}
private let store: AppGroupStore
private let timeout: TimeInterval
public init(store: AppGroupStore = AppGroupStore(), timeout: TimeInterval = 15) {
self.store = store
self.timeout = timeout
}
public func polish(_ raw: String) async throws -> String {
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { throw PolishError.noTranscript }
let client = store.makeClient()
let prompt = store.systemPrompt
return try await withThrowingTaskGroup(of: String.self) { group in
group.addTask {
try await client.polish(trimmed, systemPrompt: prompt)
}
group.addTask {
try await Task.sleep(nanoseconds: UInt64(self.timeout * 1_000_000_000))
throw PolishError.timeout
}
let result = try await group.next()!
group.cancelAll()
return result
}
}
}