// ============================================================ // VOICE PREFERENCES — STT Model & TTS Voice selection // ============================================================ (function() { var _inited = false; // Listen for tab changes (correct event name is 'tabChanged' not 'tab-loaded') document.addEventListener('tabChanged', function(e) { if (e.detail.tab !== 'settings' || _inited) return; _inited = true; console.log('[VoicePrefs] Initializing on settings tab load...'); // Small delay to ensure DOM is ready setTimeout(function() { initVoicePreferences(); }, 100); }); // Also init on DOMContentLoaded as backup for direct page load if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { setTimeout(function() { if (!_inited && document.getElementById('btn-preview-voice')) { console.log('[VoicePrefs] Init via DOMContentLoaded (direct settings page load)'); _inited = true; initVoicePreferences(); } }, 500); }); } else { // Page already loaded, init immediately if on settings setTimeout(function() { if (!_inited && document.getElementById('btn-preview-voice')) { console.log('[VoicePrefs] Init immediate (page already loaded)'); _inited = true; initVoicePreferences(); } }, 500); } 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 = ''; 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 = ''; 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; // Allow "Server default" (empty value) to preview var displayVoice = voice || 'server default'; var text = 'Hello, this is a preview of the ' + displayVoice + ' voice. This is how your read-aloud feature will sound.'; var btnPreview = document.getElementById('btn-preview-voice'); if (btnPreview) { btnPreview.disabled = true; btnPreview.innerHTML = ' Loading...'; } // Save current preference temporarily (or clear it if "server default" selected) fetch('/api/user/preferences', { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ tts_voice: voice || null }) }) .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 = ' Preview'; } }); } })();