// ============================================================ // 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 }); } } });