- Allow empty voice value to preview server default - Display 'server default' in preview text - Clears user preference (sets to null) when testing default
209 lines
6.7 KiB
JavaScript
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';
|
|
}
|
|
});
|
|
}
|
|
|
|
})();
|