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
200 lines
6.2 KiB
JavaScript
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';
|
|
}
|
|
});
|
|
}
|
|
|
|
})();
|