feat(flow): add whole-utterance batch ASR fallback (P1)

- Accumulate utterance PCM in FlowContinuousCapture for batch retry
- Run full-utterance transcribeChunk when stitched final lags partial
- Refactor Mac MLX tail drain to shared FlowUtteranceEndCoordinator
- Add FlowUtterancePCMStore, UtteranceBatchFallbackPolicy, and tests

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-07-26 10:15:31 +00:00
parent c7ee891a90
commit f0240c3088
9 changed files with 256 additions and 3 deletions
@@ -0,0 +1,23 @@
// FlowUtterancePCMStoreTests.swift
// OSGKeyboardTests
import XCTest
@testable import OSGKeyboardShared
final class FlowUtterancePCMStoreTests: XCTestCase {
func testAppendAndConsume() {
let store = FlowUtterancePCMStore(maxSampleCount: 100)
store.append([1, 2, 3])
store.append([4, 5])
XCTAssertEqual(store.sampleCount, 5)
XCTAssertEqual(store.consume(), [1, 2, 3, 4, 5])
XCTAssertEqual(store.sampleCount, 0)
}
func testTrimsOldestWhenOverCap() {
let store = FlowUtterancePCMStore(maxSampleCount: 4)
store.append([1, 2, 3, 4, 5])
XCTAssertEqual(store.consume(), [2, 3, 4, 5])
}
}
@@ -0,0 +1,45 @@
// UtteranceBatchFallbackPolicyTests.swift
// OSGKeyboardTests
import XCTest
@testable import OSGKeyboardShared
final class UtteranceBatchFallbackPolicyTests: XCTestCase {
func testShouldRunWhenPartialClearlyLonger() {
XCTAssertTrue(
UtteranceBatchFallbackPolicy.shouldRunBatchFallback(
stitchedFinal: "今天很好",
partialSnapshot: "今天很好,我们一起去公园吧"
)
)
}
func testShouldRunWhenFinalEmptyButPartialPresent() {
XCTAssertTrue(
UtteranceBatchFallbackPolicy.shouldRunBatchFallback(
stitchedFinal: "",
partialSnapshot: "最后一段"
)
)
}
func testShouldNotRunWhenPartialNotLonger() {
XCTAssertFalse(
UtteranceBatchFallbackPolicy.shouldRunBatchFallback(
stitchedFinal: "今天很好,我们一起去公园吧",
partialSnapshot: "今天很好"
)
)
}
func testPreferredTranscriptPicksLongestCandidate() {
let resolved = UtteranceBatchFallbackPolicy.preferredTranscript(
batch: "今天很好,我们一起去公园吧",
stitchedFinal: "今天很好",
partialSnapshot: "今天很好,我们",
current: "今天很好,我们"
)
XCTAssertEqual(resolved, "今天很好,我们一起去公园吧")
}
}