diff --git a/desktop/native-asr-utils.ts b/desktop/native-asr-utils.ts index f758c6b4..facc1fcf 100644 --- a/desktop/native-asr-utils.ts +++ b/desktop/native-asr-utils.ts @@ -1,9 +1,22 @@ // Pure helpers shared between the native-ASR worker and its verification. import type { DesktopAsrChunk } from '../shared/desktop-inference.ts'; import { ASR_INFERENCE_CONTRACT } from '../shared/asr-inference-contract.ts'; +import { ASR_MODELS } from '../shared/asr-models.ts'; export const NATIVE_ASR_SAMPLE_RATE = ASR_INFERENCE_CONTRACT.sampleRate; +// Preserve the desktop-native filenames supported before GGML companions were +// recorded in the shared catalog. New companions should come from the catalog. +const LEGACY_GGML_MODEL_FILES: Readonly> = { + 'Xenova/whisper-small': 'ggml-small-q5_1.bin', + 'Xenova/whisper-medium': 'ggml-medium-q5_1.bin', +}; + +export function nativeGgmlFileName(modelId: string): string | undefined { + return ASR_MODELS.find((model) => model.modelId === modelId)?.ggmlFile?.fileName + ?? LEGACY_GGML_MODEL_FILES[modelId]; +} + export interface WhisperWordToken { readonly text?: string; readonly offsets?: { from: number; to: number }; diff --git a/desktop/native-asr-worker.ts b/desktop/native-asr-worker.ts index 7d87f534..91a18f68 100644 --- a/desktop/native-asr-worker.ts +++ b/desktop/native-asr-worker.ts @@ -23,6 +23,7 @@ import { import { ASR_INFERENCE_CONTRACT } from '../shared/asr-inference-contract.ts'; import { NativeAsrWorkerLifecycle } from './native-asr-worker-lifecycle.ts'; import { + nativeGgmlFileName, whisperLanguage, whisperTokensToChunks, whisperWordsToChunks, @@ -41,14 +42,6 @@ interface NativeWorkerData { readonly platform: NodeJS.Platform; } -// ONNX modelId (browser catalog) -> GGML model file for the desktop engine. -const GGML_MODELS: Record = { - 'Xenova/whisper-tiny': { fileName: 'ggml-tiny-q5_1.bin' }, - 'onnx-community/whisper-base_timestamped': { fileName: 'ggml-base-q5_1.bin' }, - 'Xenova/whisper-small': { fileName: 'ggml-small-q5_1.bin' }, - 'Xenova/whisper-medium': { fileName: 'ggml-medium-q5_1.bin' }, -}; - const SERVER_READY_TIMEOUT_MS = 20_000; interface LoadedEngine { @@ -339,9 +332,9 @@ async function transcribeWithEngine( } function ggmlPathFor(modelId: string): string | null { - const spec = GGML_MODELS[modelId]; - if (!spec) return null; - const path = join(requireRuntime().cacheDir, 'ggml', spec.fileName); + const fileName = nativeGgmlFileName(modelId); + if (!fileName) return null; + const path = join(requireRuntime().cacheDir, 'ggml', fileName); return existsSync(path) ? path : null; } diff --git a/desktop/native-asr-worker.verify.ts b/desktop/native-asr-worker.verify.ts index 80d57c29..0523a0f0 100644 --- a/desktop/native-asr-worker.verify.ts +++ b/desktop/native-asr-worker.verify.ts @@ -1,5 +1,28 @@ import assert from 'node:assert/strict'; -import { whisperTokensToChunks, whisperWordsToChunks } from './native-asr-utils'; +import { + nativeGgmlFileName, + whisperTokensToChunks, + whisperWordsToChunks, +} from './native-asr-utils'; + +assert.deepEqual( + [ + 'Xenova/whisper-tiny', + 'onnx-community/whisper-base_timestamped', + 'Xenova/whisper-small', + 'Xenova/whisper-medium', + 'onnx-community/whisper-large-v3-turbo_timestamped', + ].map(nativeGgmlFileName), + [ + 'ggml-tiny-q5_1.bin', + 'ggml-base-q5_1.bin', + 'ggml-small-q5_1.bin', + 'ggml-medium-q5_1.bin', + 'ggml-large-v3-turbo-q5_0.bin', + ], + 'desktop-native GGML resolution covers every supported tier', +); +assert.equal(nativeGgmlFileName('unknown/model'), undefined, 'unknown models stay unsupported'); // Language mapping and token->chunk projection are pure; the real whisper-cli // invocation is exercised by the desktop smoke (native-asr-service).