pediatric-ai-scribe-v3/public/js/whisperWorkerV2.js
ifedan-ed c38ce9445e
Some checks failed
Build TWA APK / build-apk (push) Has been cancelled
Build & Push Docker Image / build (push) Has been cancelled
v16: Make Browser Whisper CDN failure graceful with clear warnings
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
2026-03-31 16:18:46 +00:00

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');