pediatric-ai-scribe-v3/public/js/voicePreferences.js
ifedan-ed 6edcb8c215 Fix TTS preview for 'Server default' voice option
- Allow empty voice value to preview server default
- Display 'server default' in preview text
- Clears user preference (sets to null) when testing default
2026-03-31 16:15:41 +00:00

209 lines
6.7 KiB
JavaScript

// ============================================================
// 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 = '<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;
// 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 = '<i class="fas fa-spinner fa-spin"></i> 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 = '<i class="fas fa-play"></i> Preview';
}
});
}
})();