feat: migrate on-device Qwen3 ASR to CoreML for background Flow dictation

Replace MLX GPU inference with CoreML bundles so transcription continues
while the host app is backgrounded. Adds model download and warm-up,
vendored Qwen3Speech, and updates onboarding, settings, and copy for the
~1.6 GB CoreML package (iOS 18+).
This commit is contained in:
Rocky
2026-06-23 00:46:58 +08:00
parent 5e5122f172
commit df1c5ff32c
160 changed files with 22080 additions and 492 deletions
@@ -0,0 +1,44 @@
// ProgressiveDictationTranscriptAccumulatorTests.swift
// OSGKeyboardTests
import CoreMedia
import XCTest
@testable import OSGKeyboardShared
final class ProgressiveDictationTranscriptAccumulatorTests: XCTestCase {
private func range(start: Double, duration: Double = 30) -> CMTimeRange {
CMTimeRange(
start: CMTime(seconds: start, preferredTimescale: 600),
duration: CMTime(seconds: duration, preferredTimescale: 600)
)
}
func testCumulativePartialsWithinSameRangeUpdateSegment() {
var acc = ProgressiveDictationTranscriptAccumulator()
let r0 = range(start: 0)
XCTAssertEqual(acc.ingest(range: r0, text: "今天天气"), "今天天气")
XCTAssertEqual(acc.ingest(range: r0, text: "今天天气很好"), "今天天气很好")
XCTAssertEqual(acc.finalize(), "今天天气很好")
}
func testNewRangeAppendsInsteadOfReplacingEarlierSpeech() {
var acc = ProgressiveDictationTranscriptAccumulator()
let r0 = range(start: 0)
let r30 = range(start: 30)
_ = acc.ingest(range: r0, text: "前三十秒的内容")
_ = acc.ingest(range: r30, text: "后二十秒的内容")
XCTAssertEqual(acc.finalize(), "前三十秒的内容 后二十秒的内容")
}
func testDuplicateEmissionIsSuppressed() {
var acc = ProgressiveDictationTranscriptAccumulator()
let r0 = range(start: 0)
XCTAssertNotNil(acc.ingest(range: r0, text: "hello"))
XCTAssertNil(acc.ingest(range: r0, text: "hello"))
}
}