feat(macos): local ASR model manager, menu-bar polish, and release 0.5.2
Adds a bundled local ASR model catalog for the macOS app with one-click Sherpa Qwen3 / SenseVoice downloads (pause/resume, inline actions) and a shared model storage directory used by MLX Qwen3. Fixes the light-mode sidebar material and makes the menu-bar icon follow the system appearance with a refreshed status mark. Renames the built product to OSGKeyboard.app. Bumps version to 0.5.2 (build 19).
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
// LocalASRBiasAdapterTests.swift
|
||||
// OSGKeyboardTests
|
||||
|
||||
import XCTest
|
||||
@testable import OSGKeyboardShared
|
||||
|
||||
final class LocalASRBiasAdapterTests: XCTestCase {
|
||||
|
||||
private func makeFixtureLexicon() throws -> BuiltinLexiconIndex {
|
||||
let dir = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("osg-phrases-\(UUID().uuidString)", isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
||||
let url = dir.appendingPathComponent("phrases.tsv")
|
||||
let tsv = """
|
||||
word\tpinyin\tsource\tweight
|
||||
SwiftUI\tswift ui\tcomputer_terms\t5
|
||||
Kubernetes\tku bo ne si\tcomputer_terms\t5
|
||||
一致性\tyi zhi xing\tcomputer_terms\t5
|
||||
"""
|
||||
try tsv.write(to: url, atomically: true, encoding: .utf8)
|
||||
addTeardownBlock {
|
||||
try? FileManager.default.removeItem(at: dir)
|
||||
}
|
||||
return BuiltinLexiconIndex(fixtureURL: url)
|
||||
}
|
||||
|
||||
func testAdaptBuildsPromptBiasForQwen3MLX() throws {
|
||||
let lexicon = try makeFixtureLexicon()
|
||||
var dict = PersonalDictionary.empty
|
||||
_ = dict.upsertManual(term: "Cursor")
|
||||
dict.updateAliases(for: dict.entries[0].id, aliases: ["cursor"])
|
||||
|
||||
let payload = LocalASRBiasAdapter.adapt(
|
||||
LocalASRBiasRequest(
|
||||
dictionary: dict,
|
||||
locale: Locale(identifier: "zh-CN"),
|
||||
capabilities: .qwen3MLX
|
||||
),
|
||||
lexicon: lexicon
|
||||
)
|
||||
|
||||
XCTAssertNotNil(payload.promptBias)
|
||||
XCTAssertTrue(payload.promptBias?.contains("Cursor") == true)
|
||||
XCTAssertTrue(payload.promptBias?.contains("SwiftUI") == true)
|
||||
XCTAssertEqual(payload.diagnostics.userTermCount, 2) // OSGKeyboard system + Cursor
|
||||
XCTAssertGreaterThan(payload.diagnostics.builtinTermCount, 0)
|
||||
}
|
||||
|
||||
func testAdaptProducesPolishFragmentWithoutUserDuplicates() throws {
|
||||
let lexicon = try makeFixtureLexicon()
|
||||
var dict = PersonalDictionary.empty
|
||||
_ = dict.upsertManual(term: "SwiftUI")
|
||||
|
||||
let payload = LocalASRBiasAdapter.adapt(
|
||||
LocalASRBiasRequest(
|
||||
dictionary: dict,
|
||||
locale: Locale(identifier: "zh-CN"),
|
||||
capabilities: .qwen3MLX
|
||||
),
|
||||
lexicon: lexicon
|
||||
)
|
||||
|
||||
XCTAssertFalse(payload.polishFragment.contains("SwiftUI"))
|
||||
XCTAssertTrue(payload.polishFragment.contains("Kubernetes"))
|
||||
}
|
||||
|
||||
func testCorrectionPairsFromAliases() {
|
||||
var dict = PersonalDictionary.empty
|
||||
_ = dict.upsertManual(term: "Kubernetes")
|
||||
dict.updateAliases(for: dict.entries[0].id, aliases: ["k8s"])
|
||||
|
||||
let payload = LocalASRBiasAdapter.adapt(
|
||||
LocalASRBiasRequest(
|
||||
dictionary: dict,
|
||||
locale: Locale(identifier: "zh-CN"),
|
||||
capabilities: .qwen3MLX
|
||||
),
|
||||
lexicon: BuiltinLexiconIndex.shared
|
||||
)
|
||||
|
||||
XCTAssertEqual(payload.correctionPairs.count, 1)
|
||||
XCTAssertEqual(payload.correctionPairs[0].alias, "k8s")
|
||||
XCTAssertEqual(payload.correctionPairs[0].term, "Kubernetes")
|
||||
}
|
||||
|
||||
func testTranscriptCorrectorReplacesASCIIAlias() {
|
||||
let pairs = [LocalASRCorrectionPair(alias: "k8s", term: "Kubernetes")]
|
||||
let result = LocalASRTranscriptCorrector.apply(
|
||||
"部署 k8s 集群",
|
||||
pairs: pairs
|
||||
)
|
||||
XCTAssertEqual(result, "部署 Kubernetes 集群")
|
||||
}
|
||||
|
||||
func testTranscriptCorrectorSkipsPartialASCIIMatch() {
|
||||
let pairs = [LocalASRCorrectionPair(alias: "k8s", term: "Kubernetes")]
|
||||
let result = LocalASRTranscriptCorrector.apply(
|
||||
"xk8s集群",
|
||||
pairs: pairs
|
||||
)
|
||||
XCTAssertEqual(result, "xk8s集群")
|
||||
}
|
||||
|
||||
func testBuiltinLexiconParsesTSV() {
|
||||
let terms = BuiltinLexiconIndex.parseTSV(
|
||||
"word\tpinyin\tsource\tweight\nFoo\tfoo\tcomputer_terms\t5\n"
|
||||
)
|
||||
XCTAssertEqual(terms.count, 1)
|
||||
XCTAssertEqual(terms[0].word, "Foo")
|
||||
XCTAssertEqual(terms[0].weight, 5)
|
||||
}
|
||||
|
||||
func testPolishingServiceMergesDictionarySupplement() {
|
||||
let dict = PersonalDictionary(entries: [
|
||||
PersonalDictionary.Entry(term: "Cursor", category: .productName, source: .manual),
|
||||
])
|
||||
let merged = PolishingService.mergedDictionaryBlock(
|
||||
dictionary: dict,
|
||||
supplement: "内置技术词汇参考:SwiftUI"
|
||||
)
|
||||
XCTAssertTrue(merged.contains("Cursor"))
|
||||
XCTAssertTrue(merged.contains("SwiftUI"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user