537a68552a
Reduce perceived latency from key release to final text: - Adaptive chunking: 2.5s first chunk + 5s follow-ups so short utterances start on-device recognition while still recording. - Session-level ASR warmup and audio-format cache reuse to remove per-utterance cold-start of SpeechAnalyzer. - Mirror live pipelined partials to the keyboard transcript line via a new flow.transcriptionPartial App Group key + Darwin ping. Also commits the accumulated custom language model, Flow session, keyboard extension restructure, and Xiaomi MiMo provider work in progress on this branch.
166 lines
5.9 KiB
Swift
166 lines
5.9 KiB
Swift
// AppGroupStore.swift
|
|
// OSGKeyboard · Shared
|
|
//
|
|
// Thin read/write facade over `AppGroupConfiguration` for the keyboard
|
|
// extension (no SwiftUI) and other non-ObservableObject call sites.
|
|
//
|
|
// `apiKey` is NOT stored in UserDefaults — see `Keychain.swift`.
|
|
|
|
import Foundation
|
|
|
|
public struct AppGroupStore: @unchecked Sendable {
|
|
public let defaults: UserDefaults
|
|
|
|
public init(defaults: UserDefaults? = nil) {
|
|
if let defaults {
|
|
self.defaults = defaults
|
|
return
|
|
}
|
|
guard let available = AppGroup.defaultsIfAvailable else {
|
|
#if DEBUG
|
|
fatalError("App Group unavailable — inject UserDefaults in tests or fix entitlements.")
|
|
#else
|
|
// Callers must check `AppGroup.isAvailable` before constructing.
|
|
fatalError("App Group unavailable.")
|
|
#endif
|
|
}
|
|
self.defaults = available
|
|
}
|
|
|
|
private var configuration: AppGroupConfiguration {
|
|
AppGroupConfiguration.load(fromAvailable: defaults)
|
|
}
|
|
|
|
private func mutateConfiguration(_ transform: (inout AppGroupConfiguration) -> Void) {
|
|
var config = AppGroupConfiguration.load(fromAvailable: defaults)
|
|
transform(&config)
|
|
config.save(to: defaults)
|
|
}
|
|
|
|
// MARK: - Reads
|
|
|
|
public var providerId: String { configuration.providerId }
|
|
public var baseURL: String { configuration.baseURL }
|
|
public var apiKey: String { configuration.apiKey }
|
|
public var model: String { configuration.model }
|
|
public var modeId: String { configuration.modeId }
|
|
public var localeId: String { configuration.localeId }
|
|
public var engineMode: String { configuration.engineMode }
|
|
public var uiLanguage: AppUILanguage { configuration.uiLanguage }
|
|
public var translationEnabled: Bool { configuration.translationEnabled }
|
|
public var translationTargetLocaleId: String { configuration.translationTargetLocaleId }
|
|
public var handednessPreference: HandednessPreference { configuration.handednessPreference }
|
|
public var cursorDragNavigationEnabled: Bool { configuration.cursorDragNavigationEnabled }
|
|
public var polishIntensity: PolishIntensity { configuration.polishIntensity }
|
|
public var isTranslationEffective: Bool { configuration.isTranslationEffective }
|
|
public var isLocalEngine: Bool { configuration.isLocalEngine }
|
|
public var polishModeForPipeline: PolishingService.PolishMode { configuration.polishModeForPipeline }
|
|
public var polishProviderIdOverride: String? { configuration.polishProviderIdOverride }
|
|
public var isCloudAPIKeyMissingForVoiceInput: Bool { configuration.isCloudAPIKeyMissingForVoiceInput }
|
|
|
|
/// Whether the keyboard top-bar translation chip should render.
|
|
public var isTranslationChipVisible: Bool { true }
|
|
|
|
// MARK: - Writes
|
|
|
|
public func setModeId(_ id: String) {
|
|
mutateConfiguration { $0.modeId = id }
|
|
}
|
|
|
|
public func setLocaleId(_ id: String) {
|
|
mutateConfiguration { $0.localeId = id }
|
|
}
|
|
|
|
public func setEngineMode(_ mode: String) {
|
|
mutateConfiguration { config in
|
|
config.engineMode = mode
|
|
if mode == "cloud", config.providerId == "deepseek" {
|
|
let openAI = LLMProvider.provider(id: "openai")
|
|
config.providerId = openAI.id
|
|
config.baseURL = openAI.defaultBaseURL
|
|
config.model = openAI.defaultModel
|
|
}
|
|
}
|
|
}
|
|
|
|
public func setUILanguage(_ language: AppUILanguage) {
|
|
mutateConfiguration { $0.uiLanguage = language }
|
|
}
|
|
|
|
public func setTranslationEnabled(_ enabled: Bool) {
|
|
setTranslationTargetLocaleId(
|
|
enabled ? TranslationLanguageCatalog.defaultLocaleId : TranslationLanguageCatalog.offLocaleId
|
|
)
|
|
}
|
|
|
|
public func setTranslationTargetLocaleId(_ id: String) {
|
|
mutateConfiguration { $0.translationTargetLocaleId = id }
|
|
AppGroupConfigDarwin.postConfigChanged()
|
|
}
|
|
|
|
public func setHandednessPreference(_ preference: HandednessPreference) {
|
|
mutateConfiguration { $0.handednessPreference = preference }
|
|
AppGroupConfigDarwin.postConfigChanged()
|
|
}
|
|
|
|
public func setCursorDragNavigationEnabled(_ enabled: Bool) {
|
|
mutateConfiguration { $0.cursorDragNavigationEnabled = enabled }
|
|
AppGroupConfigDarwin.postConfigChanged()
|
|
}
|
|
|
|
public func setPolishIntensity(_ intensity: PolishIntensity) {
|
|
mutateConfiguration { $0.polishIntensity = intensity }
|
|
}
|
|
|
|
public var hasCompletedOnboarding: Bool {
|
|
get { configuration.hasCompletedOnboarding }
|
|
set { setHasCompletedOnboarding(newValue) }
|
|
}
|
|
|
|
public var onboardingPage: Int {
|
|
get { configuration.onboardingPage }
|
|
set { setOnboardingPage(newValue) }
|
|
}
|
|
|
|
public func setHasCompletedOnboarding(_ completed: Bool) {
|
|
mutateConfiguration { config in
|
|
config.hasCompletedOnboarding = completed
|
|
if completed {
|
|
config.onboardingPage = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
public func setOnboardingPage(_ page: Int) {
|
|
mutateConfiguration { $0.onboardingPage = page }
|
|
}
|
|
|
|
// MARK: - Detected app context
|
|
|
|
public var detectedAppContext: (context: AppContext, observedAt: Date)? {
|
|
configuration.detectedAppContext(from: defaults)
|
|
}
|
|
|
|
public func setDetectedAppContext(_ context: AppContext, at date: Date = Date()) {
|
|
var config = configuration
|
|
config.setDetectedAppContext(context, at: date, to: defaults)
|
|
}
|
|
|
|
// MARK: - Personal dictionary
|
|
|
|
public var personalDictionary: PersonalDictionary {
|
|
get { configuration.personalDictionary }
|
|
set { setPersonalDictionary(newValue) }
|
|
}
|
|
|
|
public func setPersonalDictionary(_ dictionary: PersonalDictionary) {
|
|
mutateConfiguration { $0.personalDictionary = dictionary }
|
|
}
|
|
|
|
// MARK: - Client
|
|
|
|
public func makeClient() -> LLMClient {
|
|
configuration.makeClient()
|
|
}
|
|
}
|