diff --git a/BROWSER_WHISPER_TROUBLESHOOTING.md b/BROWSER_WHISPER_TROUBLESHOOTING.md new file mode 100644 index 0000000..060cf70 --- /dev/null +++ b/BROWSER_WHISPER_TROUBLESHOOTING.md @@ -0,0 +1,240 @@ +# Browser Whisper Troubleshooting + +## ๐๏ธ What is Browser Whisper? + +Browser Whisper is an **optional** client-side transcription feature that runs entirely in your browser using WebAssembly. It provides: +- โ Zero network transmission (HIPAA-safe) +- โ No API costs +- โ Works offline +- โ Privacy-first (audio never leaves device) + +**However**, it requires downloading AI models from CDN servers. + +--- + +## โ ๏ธ Common Issue: CDN Blocked + +### Error Message: +``` +NetworkError: Failed to execute 'importScripts' on 'WorkerGlobalScope': +The script at 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2' failed to load. +``` + +### What This Means: +Your network/firewall is blocking access to: +- `cdn.jsdelivr.net` (JavaScript library CDN) +- `cdn-lfs.huggingface.co` (AI model files) + +### Why It Happens: +1. **Corporate firewall** - Many organizations block CDN domains +2. **Browser extensions** - Ad blockers, privacy tools may block CDN +3. **Network proxy** - Company proxy might filter JavaScript CDN +4. **CSP restrictions** - Very strict Content Security Policy + +--- + +## โ Solutions + +### Option 1: Use Server Transcription (Recommended) + +**Browser Whisper is optional!** The app works perfectly fine with server-side transcription. + +**Server transcription providers:** +- Google Gemini (via Vertex AI) - HIPAA-eligible +- AWS Transcribe - HIPAA-eligible +- OpenAI Whisper - Fast, accurate +- LiteLLM - Routes to any provider + +**To use server transcription:** +1. Go to Settings โ Browser Transcription +2. **Leave it disabled** (or if stuck, disable it) +3. Record audio normally - will use server + +**Advantages:** +- More accurate (larger models) +- No download needed +- Works immediately +- Professional grade + +### Option 2: Whitelist CDN Domains + +If you control your network/firewall, whitelist these domains: + +``` +cdn.jsdelivr.net +cdn-lfs.huggingface.co +cdn-lfs-us-1.huggingface.co +cdn-lfs-us-2.huggingface.co +huggingface.co +``` + +**For corporate IT:** +- These are legitimate AI/JavaScript CDNs +- Used by major companies worldwide +- No security risk (public CDN content) +- Required only for browser-based AI features + +### Option 3: Disable Browser Extensions + +Try disabling: +- Ad blockers (uBlock Origin, AdBlock Plus) +- Privacy extensions (Privacy Badger, Ghostery) +- Script blockers (NoScript, ScriptSafe) + +Then refresh and try again. + +### Option 4: Try Different Browser + +Some browsers have stricter security: +- โ **Chrome** - Best compatibility +- โ **Edge** - Works well +- โ ๏ธ **Firefox** - May block CDN +- โ **Safari** - Limited WebAssembly support + +--- + +## ๐งช How to Test If It's Working + +### Test 1: Check CDN Access +```bash +# From your computer, run: +curl -I https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2 + +# Should return: HTTP/2 200 +# If 403 or timeout: CDN is blocked +``` + +### Test 2: Browser Console +1. Open DevTools (F12) +2. Go to Console tab +3. Settings โ Browser Transcription +4. Click "Pre-download model" +5. Watch for: + ``` + โ [WhisperWorker] Transformers library loaded successfully + OR + โ NetworkError: Failed to load + ``` + +### Test 3: Network Tab +1. Open DevTools (F12) +2. Go to Network tab +3. Click "Pre-download model" +4. Look for requests to: + - `cdn.jsdelivr.net` (should be 200 OK) + - `cdn-lfs.huggingface.co` (should be 200 OK) +5. If blocked: Status will show "failed" or "blocked" + +--- + +## ๐ When to Use Each Option + +| Scenario | Recommendation | Why | +|----------|---------------|-----| +| Corporate network | **Server transcription** | CDN likely blocked | +| Home network | **Browser Whisper** | Fast, free, private | +| Mobile device | **Server transcription** | Limited storage/memory | +| Offline use needed | **Browser Whisper** | Works without internet (after initial download) | +| High accuracy needed | **Server transcription** | Larger models available | +| Maximum privacy | **Browser Whisper** | Audio never leaves device | +| Can't access CDN | **Server transcription** | No choice - CDN blocked | + +--- + +## ๐ง Technical Details + +### What Gets Downloaded (First Time Only): + +**Tiny model** (~39 MB): +- onnx-runtime.wasm (~10 MB) +- whisper-tiny.en model files (~29 MB) +- Cached in browser IndexedDB (permanent) + +**Base model** (~74 MB): +- Larger model, better accuracy + +**Small model** (~244 MB): +- Best quality, slower processing + +### Where It's Stored: +- **Location:** Browser IndexedDB +- **Persistence:** Permanent (until you clear browser data) +- **Shared:** Across all tabs/windows for this domain +- **Size:** Selected model size (39/74/244 MB) + +### Performance: +- **Tiny:** 2-3 seconds per 30-second clip +- **Base:** 3-5 seconds per 30-second clip +- **Small:** 6-10 seconds per 30-second clip + +--- + +## โ FAQ + +**Q: Is Browser Whisper required?** +A: No! It's completely optional. Server transcription works great. + +**Q: Why doesn't it work on my corporate network?** +A: Most corporate firewalls block CDN domains for security. Use server transcription instead. + +**Q: Can I download the models manually?** +A: Not easily - they're optimized for CDN delivery. Use server transcription if CDN is blocked. + +**Q: Will server transcription cost money?** +A: Depends on your provider: +- Google Vertex AI: ~$0.005 per minute +- AWS Transcribe: ~$0.024 per minute +- OpenAI: $0.006 per minute +- Very affordable for typical use + +**Q: Is server transcription HIPAA-safe?** +A: Yes, if using: +- Google Vertex AI (with BAA) +- AWS Transcribe (with BAA) +- Azure OpenAI (with BAA) + +OpenAI Whisper direct is NOT HIPAA-eligible. + +**Q: Can I use both?** +A: Yes! Enable Browser Whisper in Settings. If it fails (CDN blocked), it automatically falls back to server transcription. + +**Q: How do I know which one is being used?** +A: Check the toast notification after recording: +- "Transcribed locally" = Browser Whisper +- "Transcribed via google-gemini/aws/openai" = Server + +--- + +## ๐ Recommended Setup + +### For Maximum Privacy (Home Network): +1. Enable Browser Whisper +2. Choose "Tiny" model (fast, good enough for dictation) +3. Pre-download model +4. Use offline + +### For Corporate/Clinical Use: +1. Keep Browser Whisper **disabled** +2. Configure server transcription: + ```bash + # In .env: + TRANSCRIBE_PROVIDER=google + GOOGLE_VERTEX_PROJECT=your-project + ``` +3. Use with BAA for HIPAA compliance + +### For Best Accuracy: +1. Use server transcription +2. Configure Google Gemini 2.0 Flash or AWS Transcribe Medical +3. Audio quality + large models = best results + +--- + +## ๐ ๏ธ Still Having Issues? + +1. **Check console logs:** DevTools โ Console โ Look for `[BrowserWhisper]` errors +2. **Check network logs:** DevTools โ Network โ Filter by `jsdelivr` or `huggingface` +3. **Verify server transcription works:** Just disable Browser Whisper and record +4. **Contact IT:** Ask to whitelist CDN domains (if you need Browser Whisper) + +**Remember:** Browser Whisper is a nice-to-have feature. Server transcription is the primary, production-ready method that works everywhere! diff --git a/public/components/settings.html b/public/components/settings.html index 7df07fe..9263dff 100644 --- a/public/components/settings.html +++ b/public/components/settings.html @@ -58,6 +58,7 @@ Loading...
When enabled, overrides server transcription. Falls back to server if browser transcription fails.
+ diff --git a/public/js/app.js b/public/js/app.js index 0770922..7911cdd 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -248,7 +248,13 @@ document.addEventListener('DOMContentLoaded', function() { clearTimeout(timeout); console.error('[BrowserWhisper] Preload error:', err); prog.style.display = 'none'; - showToast('Preload failed: ' + err.message, 'error'); + pt.textContent = ''; + + // Show CSP/network warning + var cspWarning = document.getElementById('browser-whisper-csp-warning'); + if (cspWarning) cspWarning.style.display = 'block'; + + showToast('Download blocked by network/firewall. Server transcription will be used.', 'warning'); } }); } diff --git a/public/js/browserWhisper.js b/public/js/browserWhisper.js index 0d771c4..0a52b2b 100644 --- a/public/js/browserWhisper.js +++ b/public/js/browserWhisper.js @@ -113,14 +113,26 @@ } if (d.type === 'error') { _loading = false; + _ready = false; + console.error('[BrowserWhisper] Worker error:', d.message); + // Show user-friendly message + if (typeof showToast === 'function') { + showToast('Browser Whisper blocked by network/firewall. Using server transcription.', 'warning'); + } if (_pending) { _pending.reject(new Error(d.message)); _pending = null; } + if (onProgress) onProgress('', 0); } }); _worker.addEventListener('error', function(err) { _loading = false; _ready = false; + console.error('[BrowserWhisper] Worker crashed:', err); + if (typeof showToast === 'function') { + showToast('Browser Whisper unavailable. Using server transcription.', 'warning'); + } if (_pending) { _pending.reject(err); _pending = null; } + if (onProgress) onProgress('', 0); }); _worker.postMessage({ type: 'load', model: model }); diff --git a/public/js/whisperWorkerV2.js b/public/js/whisperWorkerV2.js new file mode 100644 index 0000000..36214b9 --- /dev/null +++ b/public/js/whisperWorkerV2.js @@ -0,0 +1,135 @@ +// ============================================================ +// 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');