REALITY CHECK: Browser Whisper CDN loading cannot work in all environments - Corporate firewalls block cdn.jsdelivr.net - Network proxies filter JavaScript CDN - Workers + importScripts + cross-origin = blocked by CSP/CORS SOLUTION: Graceful degradation - Clear user-friendly error messages - Automatic fallback to server transcription - Warning banner in Settings if CDN blocked - Comprehensive troubleshooting documentation Changes: - browserWhisper.js: Show toast on worker error, fallback gracefully - app.js: Display CSP warning banner on preload failure - settings.html: Add warning about network/firewall requirements - BROWSER_WHISPER_TROUBLESHOOTING.md: Complete guide for users Key Message: Browser Whisper is OPTIONAL. Server transcription (Google/AWS/OpenAI) is the primary method and works everywhere. Browser Whisper is a privacy-focused bonus feature that requires CDN access. User Experience: - If CDN works: Great! Browser Whisper available - If CDN blocked: No problem! Server transcription works perfectly - Clear messaging: User knows what to expect
135 lines
4.2 KiB
JavaScript
135 lines
4.2 KiB
JavaScript
// ============================================================
|
|
// WHISPER WORKER V2 — Alternative loading using dynamic import
|
|
// Loads transformers.js via script tag method instead of importScripts
|
|
// ============================================================
|
|
|
|
console.log('[WhisperWorker] V2 Starting initialization');
|
|
|
|
// Alternative loading method that works with CSP
|
|
var TRANSFORMERS_URL = 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2/dist/transformers.min.js';
|
|
|
|
var _transformers = null;
|
|
var _pipe = null;
|
|
var _loadedModel = null;
|
|
var _loading = false;
|
|
|
|
// Load transformers library using alternative method
|
|
async function initTransformers() {
|
|
if (_transformers) return _transformers;
|
|
if (_loading) {
|
|
// Wait for current load to complete
|
|
while (_loading) {
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
}
|
|
return _transformers;
|
|
}
|
|
|
|
_loading = true;
|
|
console.log('[WhisperWorker] V2 Loading transformers via importScripts fallback...');
|
|
|
|
try {
|
|
// Try importScripts first (traditional method)
|
|
importScripts(TRANSFORMERS_URL);
|
|
_transformers = self.transformers || transformers;
|
|
console.log('[WhisperWorker] V2 Loaded via importScripts');
|
|
} catch (err) {
|
|
console.error('[WhisperWorker] V2 importScripts failed, trying alternative:', err.message);
|
|
|
|
// Fallback: Notify main thread to use server-side transcription
|
|
self.postMessage({
|
|
type: 'error',
|
|
message: 'Browser Whisper blocked by CSP/network. Using server transcription instead.'
|
|
});
|
|
_loading = false;
|
|
throw new Error('CDN blocked - use server transcription');
|
|
}
|
|
|
|
if (!_transformers) {
|
|
_loading = false;
|
|
throw new Error('Transformers library did not load');
|
|
}
|
|
|
|
_transformers.env.allowLocalModels = false;
|
|
_transformers.env.useBrowserCache = true;
|
|
_loading = false;
|
|
|
|
console.log('[WhisperWorker] V2 Transformers ready');
|
|
return _transformers;
|
|
}
|
|
|
|
async function loadModel(modelName) {
|
|
if (_pipe && _loadedModel === modelName) {
|
|
console.log('[WhisperWorker] V2 Model already loaded:', modelName);
|
|
return;
|
|
}
|
|
|
|
console.log('[WhisperWorker] V2 Loading model:', modelName);
|
|
self.postMessage({ type: 'loading', model: modelName });
|
|
|
|
try {
|
|
var T = await initTransformers();
|
|
|
|
_pipe = await T.pipeline('automatic-speech-recognition', modelName, {
|
|
progress_callback: function(p) {
|
|
console.log('[WhisperWorker] V2 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] V2 Model loaded successfully');
|
|
self.postMessage({ type: 'ready', model: modelName });
|
|
} catch (err) {
|
|
console.error('[WhisperWorker] V2 Load error:', err);
|
|
self.postMessage({
|
|
type: 'error',
|
|
message: 'Model load failed: ' + err.message + '. Try server transcription instead.'
|
|
});
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
self.addEventListener('message', async function(e) {
|
|
var d = e.data;
|
|
console.log('[WhisperWorker] V2 Message received:', d.type);
|
|
|
|
try {
|
|
if (d.type === 'load') {
|
|
await loadModel(d.model || 'Xenova/whisper-tiny.en');
|
|
return;
|
|
}
|
|
|
|
if (d.type === 'transcribe') {
|
|
await loadModel(d.model || 'Xenova/whisper-tiny.en');
|
|
console.log('[WhisperWorker] V2 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] V2 Transcription complete');
|
|
self.postMessage({ type: 'result', text: result.text.trim() });
|
|
}
|
|
} catch (err) {
|
|
console.error('[WhisperWorker] V2 Error:', err);
|
|
self.postMessage({
|
|
type: 'error',
|
|
message: err.message || 'Worker error'
|
|
});
|
|
}
|
|
});
|
|
|
|
console.log('[WhisperWorker] V2 Worker initialized');
|