- whisperWorker.js: Web Worker running @xenova/transformers Whisper in WASM - browserWhisper.js: main-thread manager — audio→Float32 conversion, worker lifecycle - transcribeAudio() checks BrowserWhisper.isEnabled() first, falls back to server - Settings UI: enable/disable, model picker (tiny/base/small), pre-download button - CSP: add wasm-unsafe-eval, cdn.jsdelivr.net, HuggingFace CDN domains - Default: whisper-tiny.en (~39MB, ~2-3s per clip)
53 lines
1.7 KiB
JavaScript
53 lines
1.7 KiB
JavaScript
// ============================================================
|
|
// WHISPER WORKER — runs @xenova/transformers in a Web Worker
|
|
// Audio never leaves the device. Model cached in IndexedDB.
|
|
// ============================================================
|
|
|
|
importScripts('https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2');
|
|
|
|
var T = self.transformers || transformers;
|
|
T.env.allowLocalModels = false;
|
|
T.env.useBrowserCache = true;
|
|
|
|
var _pipe = null;
|
|
var _loadedModel = null;
|
|
|
|
async function load(modelName) {
|
|
if (_pipe && _loadedModel === modelName) return;
|
|
self.postMessage({ type: 'loading', model: modelName });
|
|
_pipe = await T.pipeline('automatic-speech-recognition', modelName, {
|
|
progress_callback: function(p) {
|
|
if (p.status === 'downloading' || p.status === 'progress') {
|
|
self.postMessage({ type: 'progress', file: p.file || '', progress: Math.round(p.progress || 0) });
|
|
}
|
|
}
|
|
});
|
|
_loadedModel = modelName;
|
|
self.postMessage({ type: 'ready', model: modelName });
|
|
}
|
|
|
|
self.addEventListener('message', async function(e) {
|
|
var d = e.data;
|
|
|
|
if (d.type === 'load') {
|
|
try { await load(d.model || 'Xenova/whisper-tiny.en'); }
|
|
catch(err) { self.postMessage({ type: 'error', message: err.message }); }
|
|
return;
|
|
}
|
|
|
|
if (d.type === 'transcribe') {
|
|
try {
|
|
await load(d.model || 'Xenova/whisper-tiny.en');
|
|
var result = await _pipe(d.audio, {
|
|
language: 'english',
|
|
task: 'transcribe',
|
|
chunk_length_s: 30,
|
|
stride_length_s: 5,
|
|
return_timestamps: false
|
|
});
|
|
self.postMessage({ type: 'result', text: result.text.trim() });
|
|
} catch(err) {
|
|
self.postMessage({ type: 'error', message: err.message });
|
|
}
|
|
}
|
|
});
|