v15: Fix TTS preview + Browser Whisper preload with extensive debugging

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
This commit is contained in:
ifedan-ed 2026-03-31 15:28:38 +00:00
parent 42daff2343
commit d96a008dfe
6 changed files with 107 additions and 33 deletions

View file

@ -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:

View file

@ -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": {

View file

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

View file

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

View file

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

View file

@ -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',