// ============================================================ // WHISPER WORKER — runs @xenova/transformers in a Web Worker // Audio never leaves the device. Model cached in IndexedDB. // ============================================================ console.log('[WhisperWorker] Starting worker initialization'); // Load transformers.js from our server (bundled, no external CDN dependency) importScripts('/models/transformers.min.js'); var env = self.transformers.env; var pipeline = self.transformers.pipeline; // Configure transformers.js to load models from our local server env.allowLocalModels = false; env.useBrowserCache = true; env.allowRemoteModels = false; env.localModelPath = '/models/'; console.log('[WhisperWorker] Transformers loaded, models path: /models/'); var _pipe = null; var _loadedModel = null; async function load(modelName) { if (_pipe && _loadedModel === modelName) { console.log('[WhisperWorker] Model already loaded:', modelName); return; } console.log('[WhisperWorker] Loading model:', modelName); self.postMessage({ type: 'loading', model: modelName }); try { _pipe = await pipeline('automatic-speech-recognition', modelName, { progress_callback: function(p) { console.log('[WhisperWorker] Progress:', p.status, p.file, p.progress); if (p.status === 'downloading' || p.status === 'progress') { self.postMessage({ type: 'progress', file: p.file || '', progress: Math.round(p.progress || 0) }); } if (p.status === 'done' || p.status === 'ready') { self.postMessage({ type: 'progress', file: p.file || '', progress: 100 }); } } }); _loadedModel = modelName; console.log('[WhisperWorker] Model loaded successfully'); self.postMessage({ type: 'ready', model: modelName }); } catch (err) { console.error('[WhisperWorker] Load error:', err); self.postMessage({ type: 'error', message: 'Model load failed: ' + err.message }); } } self.addEventListener('message', async function(e) { var d = e.data; console.log('[WhisperWorker] Message received:', d.type); if (d.type === 'load') { try { await load(d.model || 'Xenova/whisper-tiny.en'); } catch(err) { console.error('[WhisperWorker] Load error:', err); self.postMessage({ type: 'error', message: 'Load failed: ' + err.message }); } return; } if (d.type === 'transcribe') { try { await load(d.model || 'Xenova/whisper-tiny.en'); console.log('[WhisperWorker] Starting transcription...'); var result = await _pipe(d.audio, { language: 'english', task: 'transcribe', chunk_length_s: 30, stride_length_s: 5, return_timestamps: false }); console.log('[WhisperWorker] Transcription complete'); self.postMessage({ type: 'result', text: result.text.trim() }); } catch(err) { console.error('[WhisperWorker] Transcribe error:', err); self.postMessage({ type: 'error', message: 'Transcription failed: ' + err.message }); } } }); console.log('[WhisperWorker] Worker initialized');