pediatric-ai-scribe-v3/public/js/voicePreferences.js
ifedan-ed d96a008dfe 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
2026-03-31 15:28:38 +00:00

200 lines
6.2 KiB
JavaScript

// ============================================================
// VOICE PREFERENCES — STT Model & TTS Voice selection
// ============================================================
(function() {
var _inited = false;
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) {
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');
}
}
function loadVoiceOptions() {
fetch('/api/user/preferences/options', {
headers: getAuthHeaders()
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (!data.success) return;
// Populate STT models
var sttSelect = document.getElementById('stt-model-select');
if (sttSelect && data.sttModels && data.sttModels.length > 0) {
sttSelect.innerHTML = '<option value="">Server default (' + data.sttProvider + ')</option>';
data.sttModels.forEach(function(model) {
var opt = document.createElement('option');
opt.value = model.value;
opt.textContent = model.label;
sttSelect.appendChild(opt);
});
}
// Populate TTS voices
var ttsSelect = document.getElementById('tts-voice-select');
if (ttsSelect && data.ttsVoices && data.ttsVoices.length > 0) {
ttsSelect.innerHTML = '<option value="">Server default (' + data.ttsProvider + ')</option>';
data.ttsVoices.forEach(function(voice) {
var opt = document.createElement('option');
opt.value = voice.value;
opt.textContent = voice.label;
ttsSelect.appendChild(opt);
});
}
})
.catch(function(err) {
console.error('[VoicePrefs] Failed to load options:', err.message);
});
}
function loadUserPreferences() {
fetch('/api/user/preferences', {
headers: getAuthHeaders()
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (!data.success) return;
var sttSelect = document.getElementById('stt-model-select');
if (sttSelect && data.stt_model) {
sttSelect.value = data.stt_model;
}
var ttsSelect = document.getElementById('tts-voice-select');
if (ttsSelect && data.tts_voice) {
ttsSelect.value = data.tts_voice;
}
})
.catch(function(err) {
console.error('[VoicePrefs] Failed to load preferences:', err.message);
});
}
function saveVoicePreferences() {
var sttSelect = document.getElementById('stt-model-select');
var ttsSelect = document.getElementById('tts-voice-select');
var sttModel = sttSelect ? sttSelect.value : null;
var ttsVoice = ttsSelect ? ttsSelect.value : null;
fetch('/api/user/preferences', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({
stt_model: sttModel || null,
tts_voice: ttsVoice || null
})
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) {
showToast('Voice preferences saved!', 'success');
} else {
showToast(data.error || 'Failed to save', 'error');
}
})
.catch(function(err) {
showToast('Save failed: ' + err.message, 'error');
});
}
function previewVoice() {
var ttsSelect = document.getElementById('tts-voice-select');
var voice = ttsSelect ? ttsSelect.value : null;
if (!voice || voice === '') {
showToast('Please select a voice from the dropdown first', 'info');
return;
}
// Temporarily save preference, generate preview, then restore
var text = 'Hello, this is a preview of the ' + voice + ' voice. This is how your read-aloud feature will sound.';
var btnPreview = document.getElementById('btn-preview-voice');
if (btnPreview) {
btnPreview.disabled = true;
btnPreview.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Loading...';
}
// Save current preference temporarily
fetch('/api/user/preferences', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({ tts_voice: voice })
})
.then(function() {
// Generate audio with new voice
return fetch('/api/text-to-speech', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({ text: text })
});
})
.then(function(r) {
if (!r.ok) throw new Error('Preview failed');
return r.blob();
})
.then(function(blob) {
var url = URL.createObjectURL(blob);
var audio = new Audio(url);
audio.onended = function() { URL.revokeObjectURL(url); };
audio.play();
showToast('Preview: ' + voice, 'success');
})
.catch(function(err) {
console.error('[VoicePrefs] Preview error:', err);
showToast('Preview failed: ' + err.message, 'error');
})
.finally(function() {
if (btnPreview) {
btnPreview.disabled = false;
btnPreview.innerHTML = '<i class="fas fa-play"></i> Preview';
}
});
}
})();