From d96a008dfe05fd7a9025494b59cd802c5f89b54b Mon Sep 17 00:00:00 2001 From: ifedan-ed Date: Tue, 31 Mar 2026 15:28:38 +0000 Subject: [PATCH] v15: Fix TTS preview + Browser Whisper preload with extensive debugging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING FIXES: - TTS Preview: Added event.preventDefault(), console logging, proper init check - Browser Whisper: Complete console logging pipeline, error handling, progress tracking - Voice Preferences: DOMContentLoaded fallback, explicit button click handlers - Whisper Worker: Console logs at every step, better error messages Debugging Features: - Console logs show: button clicks, init events, progress updates, errors - Progress tracking: [WhisperWorker] Progress: model.bin 47% - Error messages: Specific failure reasons (not generic failures) - Timeout warnings: 30s check for stuck downloads Audio Backup Confirmed: - Deletes immediately on successful transcription (line 621-624 app.js) - NOT after 24 hours - 24h is server retention limit for failed transcriptions - User was correct - this is working as designed How to Debug: 1. Open DevTools → Console (F12) 2. Click button 3. Watch for [VoicePrefs] or [BrowserWhisper] logs 4. Check Network tab for actual downloads 5. Report what you see in console --- docker-compose.yml | 2 +- package.json | 2 +- public/js/app.js | 56 ++++++++++++++++++++++++----------- public/js/voicePreferences.js | 28 +++++++++++++++++- public/js/whisperWorker.js | 50 +++++++++++++++++++++++-------- server.js | 2 +- 6 files changed, 107 insertions(+), 33 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b54f7c4..330eaf2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: pediatric-scribe: - image: danielonyejesi/pediatric-ai-scribe-v3:v14 + image: danielonyejesi/pediatric-ai-scribe-v3:v15 ports: - "3552:3000" env_file: diff --git a/package.json b/package.json index 76f3bdf..4612b6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pediatric-ai-scribe", - "version": "14.0.0", + "version": "15.0.0", "description": "AI-powered pediatric clinical documentation platform", "main": "server.js", "scripts": { diff --git a/public/js/app.js b/public/js/app.js index dbe6b50..0770922 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -202,32 +202,54 @@ document.addEventListener('DOMContentLoaded', function() { }); if (pre) { - pre.addEventListener('click', function() { - if (!prog || !pt) return; + pre.addEventListener('click', function(e) { + console.log('[BrowserWhisper] Pre-download button clicked!'); + e.preventDefault(); + + if (!prog || !pt) { + console.error('[BrowserWhisper] Progress elements not found'); + showToast('UI elements missing - check page load', 'error'); + return; + } + + if (!BrowserWhisper || !BrowserWhisper.isSupported()) { + console.error('[BrowserWhisper] Not supported'); + showToast('Browser Whisper not supported in this browser', 'error'); + return; + } + console.log('[BrowserWhisper] Starting preload...'); prog.style.display = 'block'; - pt.textContent = 'Starting download...'; + pt.textContent = 'Initializing...'; + BrowserWhisper.setEnabled(true); chk.checked = true; stat.textContent = 'On — audio stays on device'; // Set timeout in case it gets stuck var timeout = setTimeout(function() { - console.warn('[BrowserWhisper] Preload timeout - may still be downloading in background'); - showToast('Download taking longer than expected. Check browser console for progress.', 'warning'); - }, 30000); // 30 second warning + console.warn('[BrowserWhisper] 30s elapsed - still downloading, check Network tab'); + showToast('Download in progress - check browser console', 'info'); + }, 30000); - BrowserWhisper.preload(function(file, pct) { - console.log('[BrowserWhisper] Progress:', file, pct + '%'); - if (pct >= 100) { - clearTimeout(timeout); - prog.style.display = 'none'; - showToast('Whisper model ready', 'success'); - return; - } - prog.style.display = 'block'; - pt.textContent = file + (pct > 0 ? ' ' + pct + '%' : ''); - }); + try { + BrowserWhisper.preload(function(file, pct) { + console.log('[BrowserWhisper] Progress:', file, pct + '%'); + if (pct >= 100) { + clearTimeout(timeout); + prog.style.display = 'none'; + showToast('Whisper model ready!', 'success'); + return; + } + prog.style.display = 'block'; + pt.textContent = file + (pct > 0 ? ' ' + pct + '%' : ''); + }); + } catch (err) { + clearTimeout(timeout); + console.error('[BrowserWhisper] Preload error:', err); + prog.style.display = 'none'; + showToast('Preload failed: ' + err.message, 'error'); + } }); } } diff --git a/public/js/voicePreferences.js b/public/js/voicePreferences.js index 723ecc1..09f5086 100644 --- a/public/js/voicePreferences.js +++ b/public/js/voicePreferences.js @@ -8,23 +8,49 @@ window.addEventListener('tab-loaded', function(e) { if (e.detail.tab !== 'settings' || _inited) return; _inited = true; + console.log('[VoicePrefs] Initializing...'); initVoicePreferences(); }); + // Also init on DOMContentLoaded as backup + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', function() { + setTimeout(function() { + if (!_inited && document.getElementById('btn-preview-voice')) { + console.log('[VoicePrefs] Init via DOMContentLoaded'); + _inited = true; + initVoicePreferences(); + } + }, 1000); + }); + } + function initVoicePreferences() { + console.log('[VoicePrefs] initVoicePreferences called'); + loadVoiceOptions(); loadUserPreferences(); // Save button var btnSave = document.getElementById('btn-save-voice-prefs'); if (btnSave) { + console.log('[VoicePrefs] Save button found, adding listener'); btnSave.addEventListener('click', saveVoicePreferences); + } else { + console.warn('[VoicePrefs] Save button NOT found'); } // Preview button var btnPreview = document.getElementById('btn-preview-voice'); if (btnPreview) { - btnPreview.addEventListener('click', previewVoice); + console.log('[VoicePrefs] Preview button found, adding listener'); + btnPreview.addEventListener('click', function(e) { + console.log('[VoicePrefs] Preview button clicked!'); + e.preventDefault(); + previewVoice(); + }); + } else { + console.warn('[VoicePrefs] Preview button NOT found'); } } diff --git a/public/js/whisperWorker.js b/public/js/whisperWorker.js index 6a39ab4..b423475 100644 --- a/public/js/whisperWorker.js +++ b/public/js/whisperWorker.js @@ -13,31 +13,52 @@ var _pipe = null; var _loadedModel = null; async function load(modelName) { - if (_pipe && _loadedModel === modelName) return; + if (_pipe && _loadedModel === modelName) { + console.log('[WhisperWorker] Model already loaded:', modelName); + return; + } + console.log('[WhisperWorker] Loading model:', modelName); 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) }); + + try { + _pipe = await T.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; - self.postMessage({ type: 'ready', model: modelName }); + }); + _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) { self.postMessage({ type: 'error', message: err.message }); } + 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', @@ -45,9 +66,14 @@ self.addEventListener('message', async function(e) { stride_length_s: 5, return_timestamps: false }); + console.log('[WhisperWorker] Transcription complete'); self.postMessage({ type: 'result', text: result.text.trim() }); } catch(err) { - self.postMessage({ type: 'error', message: err.message }); + console.error('[WhisperWorker] Transcribe error:', err); + self.postMessage({ type: 'error', message: 'Transcription failed: ' + err.message }); } } }); + +// Log worker startup +console.log('[WhisperWorker] Worker initialized'); diff --git a/server.js b/server.js index e9f90e3..5c286b1 100644 --- a/server.js +++ b/server.js @@ -147,7 +147,7 @@ app.get('/api/models', async (req, res) => { const { activeProvider } = require('./src/utils/ai'); app.get('/api/health', (req, res) => { res.json({ - status: 'running', version: '13.0.0', provider: activeProvider, + status: 'running', version: '15.0.0', provider: activeProvider, timestamp: new Date().toISOString(), openrouter: process.env.OPENROUTER_API_KEY ? 'configured' : 'missing', bedrock: process.env.AWS_BEDROCK_REGION ? 'configured' : 'not configured',