perf(asr): speed up local Flow dictation and land CLM/keyboard refactor

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.
This commit is contained in:
Rocky
2026-07-06 00:00:19 +08:00
parent cfbfb542cc
commit 537a68552a
76 changed files with 3456 additions and 121086 deletions
+54 -16
View File
@@ -60,23 +60,61 @@ final class KeyboardStateTests: XCTestCase {
}
}
func testModeSwitchFromPolishToOff() {
func testFlowStructuredErrorKinds() {
let s = KeyboardState()
XCTAssertEqual(s.mode, .polish)
s.mode = .off
XCTAssertEqual(s.mode, .off)
s.mode = .transcribe
XCTAssertEqual(s.mode, .transcribe)
s.mode = .polish
XCTAssertEqual(s.mode, .polish)
s.phase = .error(.manualOpenRequired, message: "open app")
if case .error(.manualOpenRequired, let msg) = s.phase {
XCTAssertEqual(msg, "open app")
} else {
XCTFail("expected manualOpenRequired")
}
s.phase = .error(.polishDegraded("warn"), message: "warn")
if case .error(.polishDegraded("warn"), _) = s.phase {} else {
XCTFail("expected polishDegraded")
}
s.phase = .error(.flowResultTimeout, message: "timeout")
if case .error(.flowResultTimeout, _) = s.phase {} else {
XCTFail("expected flowResultTimeout")
}
s.phase = .error(.flowSessionExpired, message: "expired")
if case .error(.flowSessionExpired, _) = s.phase {} else {
XCTFail("expected flowSessionExpired")
}
s.phase = .error(.fullAccessRequired, message: "full access")
if case .error(.fullAccessRequired, _) = s.phase {} else {
XCTFail("expected fullAccessRequired")
}
s.phase = .error(.noSpeechDetected, message: "no speech")
if case .error(.noSpeechDetected, _) = s.phase {} else {
XCTFail("expected noSpeechDetected")
}
s.phase = .error(.recognitionInterrupted, message: "interrupted")
if case .error(.recognitionInterrupted, _) = s.phase {} else {
XCTFail("expected recognitionInterrupted")
}
s.phase = .error(.hostAudioUnavailable, message: "audio")
if case .error(.hostAudioUnavailable, _) = s.phase {} else {
XCTFail("expected hostAudioUnavailable")
}
let flowError = FlowTranscriptionError(message: "asr failed", kind: .asrFailed)
XCTAssertEqual(
KeyboardState.Phase.ErrorKind.fromFlowTranscription(flowError),
.hostTranscriptionFailed("asr failed")
)
}
func testInputModeRoundTripsThroughRawValue() {
// The mode is persisted by rawValue (see `AppGroupStore.setModeId`)
// so the round-trip is part of the public contract.
for mode in KeyboardState.InputMode.allCases {
let raw = mode.rawValue
XCTAssertNotNil(KeyboardState.InputMode(rawValue: raw))
}
func testInputModeIsPolishOnly() {
let s = KeyboardState()
XCTAssertEqual(s.mode, .polish)
XCTAssertEqual(KeyboardState.InputMode.allCases, [.polish])
XCTAssertEqual(KeyboardState.InputMode(rawValue: "polish"), .polish)
}
}
}