Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions desktop/native-asr-utils.ts
Original file line number Diff line number Diff line change
@@ -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<Record<string, string>> = {
'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 };
Expand Down
15 changes: 4 additions & 11 deletions desktop/native-asr-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<string, { fileName: string }> = {
'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 {
Expand Down Expand Up @@ -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;
}

Expand Down
25 changes: 24 additions & 1 deletion desktop/native-asr-worker.verify.ts
Original file line number Diff line number Diff line change
@@ -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).
Expand Down