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.
125 lines
4.0 KiB
Swift
125 lines
4.0 KiB
Swift
// KeyboardConfigSync.swift
|
|
// OSGKeyboard · Keyboard Extension
|
|
//
|
|
// App Group config hydration, Darwin observers, and onboarding mirroring.
|
|
|
|
import Foundation
|
|
import OSGKeyboardShared
|
|
|
|
@MainActor
|
|
final class KeyboardConfigSync {
|
|
private let state: KeyboardState
|
|
private let persistor: AppGroupPersistor
|
|
private let onFlowSessionChanged: () -> Void
|
|
|
|
/// Grace period after a chip-side translation write during which the
|
|
/// 1 Hz App Group poll must not overwrite `translationTargetLocaleId`.
|
|
var translationConfigProtectedUntil: Date?
|
|
|
|
private var flowSessionDarwinObserver: FlowSessionDarwinObserver?
|
|
private var transcriptionDarwinObserver: FlowSessionDarwinObserver?
|
|
private var configDarwinObserver: FlowSessionDarwinObserver?
|
|
|
|
init(
|
|
state: KeyboardState,
|
|
persistor: AppGroupPersistor,
|
|
onFlowSessionChanged: @escaping () -> Void
|
|
) {
|
|
self.state = state
|
|
self.persistor = persistor
|
|
self.onFlowSessionChanged = onFlowSessionChanged
|
|
}
|
|
|
|
func installDarwinObservers() {
|
|
flowSessionDarwinObserver = FlowSessionDarwinObserver { [weak self] in
|
|
self?.onFlowSessionChanged()
|
|
}
|
|
transcriptionDarwinObserver = FlowSessionDarwinObserver(
|
|
notificationName: FlowSessionDarwin.transcriptionNotificationName
|
|
) { [weak self] in
|
|
self?.onFlowSessionChanged()
|
|
}
|
|
configDarwinObserver = FlowSessionDarwinObserver(
|
|
notificationName: AppGroupConfigDarwin.notificationName
|
|
) { [weak self] in
|
|
self?.refreshConfigFromAppGroup()
|
|
}
|
|
}
|
|
|
|
func loadPersistedConfig() -> AppGroupLoadResult {
|
|
switch persistor.load(into: state) {
|
|
case .loaded:
|
|
OSGLog.keyboardExt.info(
|
|
"config loaded — cursorDragNavigationEnabled=\(self.state.cursorDragNavigationEnabled)"
|
|
)
|
|
syncOnboardingStateFromAppGroup()
|
|
return .loaded
|
|
case .unavailable:
|
|
state.phase = .error(
|
|
.appGroupUnavailable,
|
|
message: ExtL10n.string("keyboard.error.appGroupUnavailable")
|
|
)
|
|
return .unavailable
|
|
}
|
|
}
|
|
|
|
func refreshConfigFromAppGroup() {
|
|
persistor.refreshRuntimeFlags(
|
|
into: state,
|
|
protectTranslationUntil: translationConfigProtectedUntil
|
|
)
|
|
}
|
|
|
|
func syncOnboardingStateFromAppGroup() {
|
|
let store = AppGroupStore()
|
|
state.hasCompletedOnboarding = store.hasCompletedOnboarding
|
|
state.onboardingPage = store.onboardingPage
|
|
}
|
|
|
|
func autoAdvancePastKeyboardSetupStepIfNeeded() {
|
|
guard !state.hasCompletedOnboarding else { return }
|
|
guard state.onboardingPage == 3 else { return }
|
|
guard KeyboardSetupBridge.isReadyForOnboardingSkip else { return }
|
|
let store = AppGroupStore()
|
|
store.setOnboardingPage(4)
|
|
state.onboardingPage = 4
|
|
}
|
|
|
|
func advanceOnboarding() {
|
|
let store = AppGroupStore()
|
|
let nextPage = min(4, store.onboardingPage + 1)
|
|
store.setOnboardingPage(nextPage)
|
|
state.onboardingPage = nextPage
|
|
}
|
|
|
|
func completeOnboarding() {
|
|
let store = AppGroupStore()
|
|
store.setHasCompletedOnboarding(true)
|
|
store.setOnboardingPage(4)
|
|
state.hasCompletedOnboarding = true
|
|
state.onboardingPage = 4
|
|
}
|
|
|
|
func persistLocale(_ id: String) {
|
|
state.localeId = id
|
|
persistor.persist(localeId: id)
|
|
}
|
|
|
|
func persistEngineMode(_ mode: String) {
|
|
state.engineMode = mode
|
|
persistor.persist(engineMode: mode)
|
|
}
|
|
|
|
func persistTranslationTargetLocaleId(_ id: String) {
|
|
let resolved = TranslationLanguageCatalog.resolve(id).id
|
|
state.translationTargetLocaleId = resolved
|
|
translationConfigProtectedUntil = Date().addingTimeInterval(2.5)
|
|
persistor.persist(translationTargetLocaleId: resolved)
|
|
}
|
|
|
|
func persistMode(_ mode: KeyboardState.InputMode) {
|
|
state.mode = mode
|
|
persistor.persist(mode: mode)
|
|
}
|
|
}
|