diff --git a/public/js/kokoro-handler.js b/public/js/kokoro-handler.js index ef682fd..bb2f9a5 100644 --- a/public/js/kokoro-handler.js +++ b/public/js/kokoro-handler.js @@ -89,6 +89,12 @@ export class KokoroHandler extends TTSHandler { return true; } + // Ensure we have at least default voices ready + if (!this.voices || this.voices.length === 0) { + console.log('Kokoro TTS: No voices set, initializing with defaults'); + this.voices = this.getDefaultVoices(); + } + // Set loading flag this.loading = true; this.isReady = false; // Explicitly set to false during initialization @@ -169,10 +175,11 @@ export class KokoroHandler extends TTSHandler { } else { console.warn('Kokoro TTS: No voices received during initialization or invalid voices data'); if (data.success) { - // If initialization was successful but no voices were received, - // use default voices - this.voices = this.getDefaultVoices(); - console.log('Kokoro TTS: Using default voices as fallback'); + // Even though we already set the default voices, check and update if needed + if (!this.voices || this.voices.length === 0) { + this.voices = this.getDefaultVoices(); + console.log('Kokoro TTS: Using default voices as fallback'); + } } } @@ -182,7 +189,7 @@ export class KokoroHandler extends TTSHandler { console.log('Kokoro TTS: Voice set up from preferences during initialization'); }).catch(error => { console.error('Kokoro TTS: Error setting up voice from preferences during initialization:', - error ? (error.message || JSON.stringify(error)) : 'Unknown error'); + error ? (error.message || error) : 'Unknown error'); }); } @@ -191,30 +198,17 @@ export class KokoroHandler extends TTSHandler { } }; - // Add the message listener + // Add the message handler window.addEventListener('message', messageHandler); - // Send initialization message to iframe - if (this.iframe.contentWindow) { - console.log('Kokoro TTS: Sending init message to iframe'); - setTimeout(() => { - this.iframe.contentWindow.postMessage({ - type: 'kokoro-init' - }, '*'); - }, 500); // Add a small delay to ensure iframe is ready - } else { - console.error('Kokoro TTS: Cannot access iframe content window'); - this.loading = false; - this.isReady = false; - this.available = false; - resolve(false); - } + // Initial progress update + handleProgress(0.1, 'Starting Kokoro initialization'); }); } catch (error) { - console.error('Kokoro TTS: Error initializing:', error ? (error.message || JSON.stringify(error)) : 'Unknown error'); + console.error('Kokoro TTS: Error during initialization:', error); this.loading = false; - this.available = false; this.isReady = false; + this.available = false; return false; } } @@ -224,91 +218,56 @@ export class KokoroHandler extends TTSHandler { * @param {MessageEvent} event - Message event */ handleIframeMessage(event) { - // Ignore messages from other sources + // Only process messages from our iframe if (!this.iframe || event.source !== this.iframe.contentWindow) { return; } const data = event.data; - console.log('Kokoro TTS: Received message from iframe:', JSON.stringify(data)); + console.log('Kokoro TTS: Received message from iframe:', data.type); switch (data.type) { - case 'kokoro-ready': - console.log('Kokoro TTS: Received ready message with success =', data.success); + case 'kokoro-log': + console.log(`Kokoro Loader: ${data.message}`); + break; - // Store voices if provided + case 'kokoro-ready': + console.log('Kokoro TTS: Received ready message from iframe. Success:', data.success, 'Voices:', data.voices ? data.voices.length : 0); + + // Store availability + this.loading = false; + this.available = data.success; + this.isReady = data.success; // Important to set this for the base handler + + // Store voices if (data.success && data.voices && Array.isArray(data.voices)) { - console.log(`Kokoro TTS: Received ${data.voices.length} voices from Kokoro iframe`); + console.log(`Kokoro TTS: Storing ${data.voices.length} voices from iframe`); this.voices = data.voices; - - // Set availability and ready flags - this.available = true; - this.loading = false; - this.isReady = true; - - // Set up voice from preferences after voices are loaded - this.setupVoiceFromPreferences().then(() => { - console.log('Kokoro TTS: Voice set up from preferences after receiving voices'); - - // Notify TTS Factory that we're ready now - document.dispatchEvent(new CustomEvent('kokoro:ready', { - detail: { success: true } - })); - }).catch(error => { - console.error('Kokoro TTS: Error setting up voice from preferences after receiving voices:', - error ? (error.message || JSON.stringify(error)) : 'Unknown error'); - - // Still notify as ready since we have voices, even if preference setup failed - document.dispatchEvent(new CustomEvent('kokoro:ready', { - detail: { success: true } - })); - }); - - // Notify about voices being updated - document.dispatchEvent(new CustomEvent('kokoro:voices-updated', { - detail: { voices: this.voices } - })); - } else { - console.warn('Kokoro TTS: No voices received from iframe or invalid voices data'); - // Even with no voices, mark as ready if success is true - if (data.success) { - this.voices = this.getDefaultVoices(); - this.available = true; - this.loading = false; - this.isReady = true; - console.log('Kokoro TTS: Using default voices as fallback'); - - // Notify TTS Factory that we're ready - document.dispatchEvent(new CustomEvent('kokoro:ready', { - detail: { success: true } - })); - - // Notify about voices being available - document.dispatchEvent(new CustomEvent('kokoro:voices-updated', { - detail: { voices: this.voices } - })); - } else { - this.available = false; - this.loading = false; - this.isReady = false; - console.error('Kokoro TTS: Initialization failed:', data.error || 'Unknown error'); - - // Notify TTS Factory about failure - document.dispatchEvent(new CustomEvent('kokoro:ready', { - detail: { success: false, error: data.error || 'Unknown error' } - })); - } + } else if (data.success) { + // If success but no voices, use defaults + console.warn('Kokoro TTS: No voices received from iframe, using defaults'); + this.voices = this.getDefaultVoices(); } + + // Set up voice from preferences if ready + if (this.available) { + this.setupVoiceFromPreferences().then(() => { + console.log('Kokoro TTS: Voice set up from preferences'); + }); + } + + // Dispatch ready event + this.dispatchEvent('tts:ready', { success: data.success }); break; case 'kokoro-generated': - // Handle generated speech - const pendingGeneration = this.pendingGenerations.get(data.id); - if (pendingGeneration) { + // Handle generated speech + if (data.id && this.pendingGenerations.has(data.id)) { + const { resolve, reject } = this.pendingGenerations.get(data.id); this.pendingGenerations.delete(data.id); - if (data.success) { - // Create audio element from the result + if (data.success && data.result) { + // Create an audio element from the result try { // Create a blob from the buffer const blob = new Blob([data.result.buffer], { type: 'audio/wav' }); @@ -323,26 +282,18 @@ export class KokoroHandler extends TTSHandler { }); }; - pendingGeneration.resolve({ audio, play, blob }); + resolve({ audio, play, blob }); } catch (error) { console.error('Error processing Kokoro audio:', error); - pendingGeneration.reject(error); + reject(error); } } else { - pendingGeneration.reject(new Error(data.error || 'Unknown error')); + console.error('Kokoro TTS: Invalid speech generation result'); + reject(new Error(data.error || 'Unknown error generating speech')); } } break; - case 'kokoro-log': - // Log messages from the iframe - if (data.logType === 'error') { - console.error(`Kokoro iframe: ${data.message}`); - } else { - console.log(`Kokoro iframe: ${data.message}`); - } - break; - case 'kokoro-progress': // Progress updates are handled during initialization break; @@ -440,7 +391,7 @@ export class KokoroHandler extends TTSHandler { } } catch (error) { // Log detailed error information - console.error('Kokoro TTS: Error setting up voice from preferences:', error ? error.message || JSON.stringify(error) : 'Unknown error'); + console.error('Kokoro TTS: Error setting up voice from preferences:', error ? error.message || error : 'Unknown error'); // Default to first voice if available if (this.voices && this.voices.length > 0) { @@ -505,9 +456,12 @@ export class KokoroHandler extends TTSHandler { /** * Get available voices - * @returns {Array} - Array of available voices + * @returns {Array} - Array of voice objects */ getVoices() { + if (!this.voices || this.voices.length === 0) { + return this.getDefaultVoices(); + } return this.voices; } @@ -710,11 +664,14 @@ export class KokoroHandler extends TTSHandler { if (this.currentVoice && this.currentVoice.id) { voiceId = this.currentVoice.id; } else if (this.voices && this.voices.length > 0) { - // If currentVoice is not set but we have voices, use the first one - voiceId = this.voices[0].id; + // Default to first voice if none selected this.currentVoice = this.voices[0]; + voiceId = this.currentVoice.id; + console.log(`Kokoro TTS: No voice set, defaulting to ${voiceId}`); } + console.log(`Kokoro TTS: Generating speech with voice ${voiceId}`); + return new Promise((resolve, reject) => { // Generate unique ID for this request const id = `gen-${++this.generationCounter}`; @@ -787,11 +744,22 @@ export class KokoroHandler extends TTSHandler { } /** - * Get default voices - * @returns {Array} - Array of default voices - * @private + * Get default voices for current locale + * @returns {Array} Default voices */ getDefaultVoices() { + // Check if localization module is available + const localization = this.getModule('localization'); + let locale = 'en-us'; // Default fallback + + if (localization) { + locale = localization.getLocale(); + console.log(`Kokoro TTS: Getting default voices for locale: ${locale}`); + } else { + console.log('Kokoro TTS: Localization module not available, using default locale: en-us'); + } + + // Use the actual voices defined in the Kokoro loader return [ // American Female voices { id: 'af_heart', name: 'Heart', lang: 'en-US', gender: 'female' }, diff --git a/public/js/localization.js b/public/js/localization.js index 8cee21f..6e572ba 100644 --- a/public/js/localization.js +++ b/public/js/localization.js @@ -13,12 +13,12 @@ class LocalizationModule extends BaseModule { super('localization', 'Localization'); // Current locale - this.currentLocale = 'en-us'; + this.translations = {}; + this.defaultLocale = 'en-us'; + this.currentLocale = this.defaultLocale; + this.dependencies = ['persistence-manager']; // Available translations - this.translations = {}; - - // Language names mapping this.languageNames = { 'en-us': 'English (US)', 'en-gb': 'English (UK)', @@ -44,41 +44,31 @@ class LocalizationModule extends BaseModule { try { this.reportProgress(10, "Initializing localization"); + // Load default English locale + await this.loadTranslations('en-us'); + this.reportProgress(50, "Loaded default locale"); // Get stored locale from persistence manager if available const persistenceManager = this.getModule('persistence-manager'); - let storedLocale = null; if (persistenceManager) { - try { - storedLocale = persistenceManager.getPreference('app', 'locale'); - if (storedLocale) { - console.log(`Localization: Found stored locale: ${storedLocale}`); - await this.loadTranslations(storedLocale); - this.currentLocale = storedLocale; - } else { - // If no stored locale, ensure English is the default and persist it - console.log('Localization: No stored locale found, defaulting to en-us'); - await this.loadTranslations('en-us'); - persistenceManager.updatePreference('app', 'locale', 'en-us'); - persistenceManager.updatePreference('tts', 'language', 'en-us'); - this.currentLocale = 'en-us'; - } - } catch (persistError) { - console.warn(`Failed to load stored locale:`, persistError); + const storedLocale = persistenceManager.getPreference('app', 'locale'); + if (storedLocale) { + console.log(`Localization: Found stored locale: ${storedLocale}`); + await this.loadTranslations(storedLocale); + this.currentLocale = storedLocale; + this.reportProgress(80, `Loaded stored locale: ${storedLocale}`); + } else { + // If no stored locale, ensure en-us is the default and persist it + console.log('Localization: No stored locale found, using default en-us'); + persistenceManager.updatePreference('app', 'locale', 'en-us'); + persistenceManager.updatePreference('tts', 'language', 'en-us'); + this.currentLocale = 'en-us'; + this.reportProgress(80, "Using default locale: en-us"); } } else { - // If browser locale is available, just load it as a fallback but keep English as default - const browserLocale = navigator.language.toLowerCase(); - if (browserLocale && browserLocale !== 'en-us') { - try { - this.reportProgress(50, `Loading browser locale as fallback: ${browserLocale}`); - await this.loadTranslations(browserLocale); - // Do NOT set browser locale as current - keep English as default - } catch (localeError) { - console.warn(`Failed to load browser locale ${browserLocale}:`, localeError); - } - } + console.log('Localization: Persistence manager not available, using default en-us locale'); + this.reportProgress(80, "Using default locale: en-us"); } // Dispatch event to notify about loaded locale @@ -116,24 +106,16 @@ class LocalizationModule extends BaseModule { const translations = await response.json(); this.translations[normalizedLocale] = translations; } else { - // Try to load the language part only - const langPart = normalizedLocale.split('-')[0]; - if (langPart !== normalizedLocale) { - const langResponse = await fetch(`/locales/${langPart}.json`); - if (langResponse.ok) { - const translations = await langResponse.json(); - this.translations[normalizedLocale] = translations; - } else { - // Fallback to English - if (normalizedLocale !== 'en-us' && normalizedLocale !== 'en') { - await this.loadTranslations('en-us'); - this.translations[normalizedLocale] = this.translations['en-us']; - } else { - // If English is not found, create an empty translation set - console.warn('English translations not found, using empty set'); - this.translations[normalizedLocale] = {}; - } - } + // Don't try to load language part without region (e.g., "en") - we only support full locales + // Fallback to en-us if the requested locale isn't found + if (normalizedLocale !== 'en-us') { + console.warn(`Translations for ${normalizedLocale} not found, falling back to en-us`); + await this.loadTranslations('en-us'); + this.translations[normalizedLocale] = this.translations['en-us']; + } else { + // If en-us is not found, create an empty translation set + console.warn('English translations not found, using empty set'); + this.translations[normalizedLocale] = {}; } } } catch (error) { diff --git a/public/js/options-ui.js b/public/js/options-ui.js index a853f36..260a44e 100644 --- a/public/js/options-ui.js +++ b/public/js/options-ui.js @@ -34,60 +34,80 @@ class OptionsUIModule extends BaseModule { 'handleTtsSystemChanged', 'showReloadNotice', 'toggle', - 'setupEventListeners' + 'setupEventListeners', + 'saveCurrentSettings' ]); } /** * Initialize the options UI - * @returns {boolean} - True if initialization was successful + * @returns {Promise} - Resolves with success status */ - initialize() { - console.log('Initializing Options UI Module'); - - // Set up dependencies - this.dependencies = [ - 'persistence-manager', - 'localization', - 'tts-factory', - 'audio-manager' - ]; - - // Create the options modal - this.createModal(); - - // Set up event listeners - this.setupEventListeners(); - - // Add event listener for showing options UI - document.addEventListener('ui:showOptions', () => this.show()); - - // Add event listener for toggling options UI - document.addEventListener('ui:options:toggle', () => this.toggle()); - - // Wait for dependencies and populate UI with delay to ensure TTS handlers are registered - this.waitForDependencies().then(() => { - console.log('Options UI: Dependencies loaded, initializing UI with delay'); + async initialize() { + try { + console.log('Initializing Options UI Module'); - // Add a delay to ensure all TTS handlers are registered and initialized - setTimeout(() => { - // Populate TTS systems - this.populateTtsSystems(); + // Set up dependencies + this.dependencies = [ + 'persistence-manager', + 'localization', + 'tts-factory', + 'audio-manager' + ]; + + // Create the options modal + this.createModal(); + + // Set up event listeners + this.setupEventListeners(); + + // Add event listener for showing options UI + document.addEventListener('ui:showOptions', () => this.show()); + + // Add event listener for toggling options UI + document.addEventListener('ui:options:toggle', () => this.toggle()); + + // Wait for dependencies and populate UI with delay to ensure TTS handlers are registered + this.waitForDependencies().then(() => { + console.log('Options UI: Dependencies loaded, initializing UI with delay'); - // Populate languages - this.populateLanguages(); - - // Load current preferences - this.loadPreferences(); - - // Apply settings - this.applySettings(); - - console.log('Options UI: Initialization complete'); - }, 1000); // 1 second delay - }); - - return true; + // Add a delay to ensure all TTS handlers are registered and initialized + setTimeout(() => { + // Populate TTS systems + this.populateTtsSystems(); + + // Populate languages + this.populateLanguages(); + + // Load current preferences + this.loadPreferences(); + + // Apply settings + this.applySettings(); + + console.log('Options UI: Initialization complete'); + }, 1000); // 1 second delay + }); + + // Register for TTS events to update voices when they change + document.addEventListener('tts:voices:updated', () => { + console.log('Options UI: Received tts:voices:updated event, updating voice dropdown'); + this.populateVoices(); + }); + + // Set up key bindings + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && this.modal.style.display === 'flex') { + this.saveCurrentSettings(); + this.hide(); + } + }); + + return true; + } catch (error) { + console.error("Options UI: Error initializing", error); + return false; + } } /** @@ -144,7 +164,11 @@ class OptionsUIModule extends BaseModule { const closeButton = document.createElement('button'); closeButton.className = 'options-close'; closeButton.innerHTML = '×'; - closeButton.addEventListener('click', () => this.hide()); + closeButton.addEventListener('click', () => { + // Save all current settings when closing + this.saveCurrentSettings(); + this.hide(); + }); header.appendChild(closeButton); content.appendChild(header); @@ -524,17 +548,11 @@ class OptionsUIModule extends BaseModule { noneOption.textContent = 'None (Disable TTS)'; this.elements.ttsSystem.appendChild(noneOption); - // Debug log for troubleshooting - console.log('Options UI: Populating TTS systems'); - // Get available handlers const handlers = ttsFactory.getAvailableHandlers(); - console.log('Options UI: Available handlers:', handlers); // Add all registered handlers for (const id in handlers) { - // Always add the handler, even if not initialized yet - console.log(`Options UI: Adding TTS option for ${id}`); const option = document.createElement('option'); option.value = id; option.textContent = this.getTtsSystemName(id); @@ -543,13 +561,23 @@ class OptionsUIModule extends BaseModule { // If no handlers available, add a disabled option if (this.elements.ttsSystem.options.length === 1) { - console.log('Options UI: No TTS systems available, adding disabled option'); const option = document.createElement('option'); option.value = ''; option.textContent = 'No TTS systems available'; option.disabled = true; this.elements.ttsSystem.appendChild(option); } + + // Set the current provider value in the dropdown + if (this.persistenceManager) { + const provider = this.persistenceManager.getPreference('tts', 'provider'); + if (provider) { + const option = Array.from(this.elements.ttsSystem.options).find(opt => opt.value === provider); + if (option) { + this.elements.ttsSystem.value = provider; + } + } + } } /** @@ -570,32 +598,39 @@ class OptionsUIModule extends BaseModule { * Populate voices dropdown for the current TTS system */ populateVoices() { - if (!this.elements || !this.elements.ttsVoice) return; + if (!this.elements || !this.elements.ttsVoice) { + console.log('Options UI: Cannot populate voices - elements not initialized'); + return; + } const ttsFactory = this.getModule('tts-factory'); const localization = this.getModule('localization'); - if (!ttsFactory || !localization) return; + if (!ttsFactory || !localization) { + console.log('Options UI: Cannot populate voices - required modules not available'); + return; + } // Clear existing options this.elements.ttsVoice.innerHTML = ''; // Get current locale const currentLocale = localization.getLocale(); - const languageCode = currentLocale.split('-')[0].toLowerCase(); + console.log(`Options UI: Current locale from localization module: ${currentLocale}`); + + // Get active TTS handler + const activeHandler = ttsFactory.getActiveHandler(); + const handlerId = activeHandler ? activeHandler.getId() : 'none'; + + console.log(`Options UI: Populating voices for locale: ${currentLocale}, handler: ${handlerId}`); // Get voices from active handler - const allVoices = ttsFactory.getVoices(); + const voices = ttsFactory.getVoices(); + console.log(`Options UI: Got ${voices ? voices.length : 0} voices from TTS factory`); - // Filter voices by current locale - const filteredVoices = allVoices.filter(voice => { - if (!voice.lang) return true; // Include voices without language info - const voiceLang = voice.lang.toLowerCase(); - return voiceLang.startsWith(languageCode) || languageCode.startsWith(voiceLang.split('-')[0]); - }); - - if (filteredVoices && filteredVoices.length > 0) { + // Add available voices to dropdown + if (voices && voices.length > 0) { // Add options for each voice - filteredVoices.forEach(voice => { + voices.forEach(voice => { const option = document.createElement('option'); option.value = voice.id || voice.name; option.textContent = voice.name; @@ -604,13 +639,15 @@ class OptionsUIModule extends BaseModule { } this.elements.ttsVoice.appendChild(option); }); + console.log(`Options UI: Added ${voices.length} voice options to the dropdown`); } else { - // No voices available for current locale + // No voices available const option = document.createElement('option'); option.value = ''; option.textContent = `No voices available for ${currentLocale}`; option.disabled = true; this.elements.ttsVoice.appendChild(option); + console.log(`Options UI: No voices available for ${currentLocale}, added placeholder option`); } } @@ -791,20 +828,91 @@ class OptionsUIModule extends BaseModule { this.reloadRequired = true; } + /** + * Save current settings + */ + saveCurrentSettings() { + if (!this.persistenceManager) return; + + // Save TTS settings + const ttsFactory = this.getModule('tts-factory'); + if (ttsFactory) { + const provider = this.elements.ttsSystem.value; + const voice = this.elements.ttsVoice.value; + const speed = parseInt(this.elements.speechRate.value) / 100; + const enabled = this.elements.ttsSpeechToggle.checked; + + this.persistenceManager.updatePreference('tts', 'provider', provider); + this.persistenceManager.updatePreference('tts', 'voice', voice); + this.persistenceManager.updatePreference('tts', 'speed', speed); + this.persistenceManager.updatePreference('tts', 'enabled', enabled); + } + + // Save language settings + const localization = this.getModule('localization'); + if (localization && this.elements.language) { + const locale = this.elements.language.value; + this.persistenceManager.updatePreference('app', 'locale', locale); + this.persistenceManager.updatePreference('tts', 'language', locale); + } + + // Save audio settings + const audioManager = this.getModule('audio-manager'); + if (audioManager) { + const masterVolume = parseInt(this.elements.masterVolume.value) / 100; + const musicVolume = parseInt(this.elements.musicVolume.value) / 100; + const sfxVolume = parseInt(this.elements.effectsVolume.value) / 100; + const speechVolume = parseInt(this.elements.speechVolume.value) / 100; + + this.persistenceManager.updatePreference('audio', 'masterVolume', masterVolume); + this.persistenceManager.updatePreference('audio', 'musicVolume', musicVolume); + this.persistenceManager.updatePreference('audio', 'sfxVolume', sfxVolume); + this.persistenceManager.updatePreference('tts', 'volume', speechVolume); + } + + // Save text speed setting + const textSpeed = parseInt(this.elements.textSpeed.value); + this.persistenceManager.updatePreference('animation', 'speed', textSpeed); + } + setupEventListeners() { // Listen for language change events document.addEventListener('localization:languageChanged', () => { - // Update the language selection in options panel - const localization = this.getModule('localization'); - if (localization && this.elements && this.elements.language) { - const currentLocale = localization.getLocale(); - if (currentLocale && this.elements.language.value !== currentLocale) { - this.elements.language.value = currentLocale; + this.populateLanguages(); + this.populateVoices(); + }); + + // Listen for TTS state changes + document.addEventListener('tts:stateChange', (event) => { + if (this.elements && this.elements.ttsSpeechToggle) { + this.elements.ttsSpeechToggle.checked = event.detail.enabled; + + // Update persistence manager + const persistenceManager = this.getModule('persistence-manager'); + if (persistenceManager) { + persistenceManager.updatePreference('tts', 'enabled', event.detail.enabled); } } - - // Re-populate TTS voices for new language - this.populateVoices(); + }); + + // Listen for TTS handler changes + document.addEventListener('tts:handlerChanged', (event) => { + if (this.elements && this.elements.ttsSystem) { + // Update the dropdown to match the active handler + const handlerId = event.detail.handlerId; + if (handlerId && handlerId !== 'none') { + this.elements.ttsSystem.value = handlerId; + + // Update persistence manager + const persistenceManager = this.getModule('persistence-manager'); + if (persistenceManager) { + persistenceManager.updatePreference('tts', 'provider', handlerId); + } + } + + // Refresh voices when handler changes + this.populateVoices(); + } }); // Listen for TTS availability events diff --git a/public/js/persistence-manager.js b/public/js/persistence-manager.js index a68abe5..2f14930 100644 --- a/public/js/persistence-manager.js +++ b/public/js/persistence-manager.js @@ -74,8 +74,8 @@ class PersistenceManagerModule extends BaseModule { 'getAllSaveSlots' ]); - // Add localization as a dependency - this.dependencies = ['localization']; + // Remove circular dependency + this.dependencies = []; } /** @@ -92,38 +92,6 @@ class PersistenceManagerModule extends BaseModule { // Load save slots this.loadSaveSlots(); - // Get localization module - const localization = this.getModule('localization'); - if (localization) { - // Update language preferences with current language - const language = localization.getLanguage(); - - // Update default preferences - this.defaultPreferences.tts.language = language; - this.defaultPreferences.app.locale = language; - - // Update current preferences if they exist - if (this.preferences) { - // Only update if not already set by user - if (!this.preferences.tts.language || this.preferences.tts.language === 'en-us') { - this.preferences.tts.language = language; - } - - if (!this.preferences.app.locale || this.preferences.app.locale === 'en-us') { - this.preferences.app.locale = language; - } - - // Save updated preferences - this.savePreferences(); - } - - this.reportProgress(80, "Updated language preferences"); - } else { - console.warn("Localization module not found or not ready, using default language settings"); - // We'll continue without localization - it might initialize later - this.reportProgress(80, "Using default language settings"); - } - this.reportProgress(100, "Persistence manager ready"); return true; } catch (error) { diff --git a/public/js/tts-factory.js b/public/js/tts-factory.js index 1cf70a0..4d7f245 100644 --- a/public/js/tts-factory.js +++ b/public/js/tts-factory.js @@ -177,37 +177,39 @@ class TTSFactoryModule extends BaseModule { /** * Initialize a specific TTS handler - * @param {string} id - Handler ID - * @returns {Promise} - Success status + * @param {string} id - Handler ID to initialize + * @returns {Promise} - Resolves with success status */ async initializeHandler(id) { - if (!id || !this.handlers[id]) { - console.error(`TTS Factory: Handler '${id}' not found`); + if (!this.handlers[id]) { + console.error(`TTS Factory: Handler ${id} not found`); return false; } + console.log(`TTS Factory: Initializing handler ${id}`); + const progressCallback = (progress, message) => { + const mappedProgress = (progress / 100) || 0; + console.log(`TTS Factory: Handler ${id} progress: ${progress}%, ${message}`); + this.reportProgress(50 + Math.round(mappedProgress * 40), `Initializing ${id}: ${message}`); + }; + try { - this.reportProgress(0, `Initializing ${id} TTS handler`); - - // Initialize the handler - const success = await this.handlers[id].initialize( - (progress, message) => { - this.reportProgress(progress, message); - } - ); - - // Update initialization status + // Initialize the handler with progress callback + const success = await this.handlers[id].initialize(progressCallback); this.initStatus[id] = success; if (success) { - console.log(`TTS Factory: Successfully initialized ${id} TTS handler`); + console.log(`TTS Factory: Handler ${id} initialized successfully`); + // Force getVoices() to ensure voices are loaded + const voices = this.handlers[id].getVoices(); + console.log(`TTS Factory: Handler ${id} has ${voices ? voices.length : 0} voices available after initialization`); } else { - console.error(`TTS Factory: Failed to initialize ${id} TTS handler`); + console.warn(`TTS Factory: Handler ${id} initialization failed`); } return success; } catch (error) { - console.error(`TTS Factory: Error initializing ${id} TTS handler:`, error); + console.error(`TTS Factory: Error initializing handler ${id}:`, error); this.initStatus[id] = false; return false; } @@ -229,11 +231,9 @@ class TTSFactoryModule extends BaseModule { * @returns {boolean} - Success status */ setActiveHandler(id) { - // Handle 'none' option specially + // If 'none' is passed, disable TTS if (id === 'none') { this.activeHandler = null; - - // Update TTS availability state this.ttsAvailable = false; // Notify about TTS availability change @@ -241,10 +241,16 @@ class TTSFactoryModule extends BaseModule { detail: { available: false } })); - console.log("TTS Factory: TTS disabled (none selected)"); + // Notify about handler change + document.dispatchEvent(new CustomEvent('tts:handlerChanged', { + detail: { handlerId: 'none' } + })); + + console.log('TTS Factory: TTS disabled'); return true; } + // Check if the handler exists if (!this.handlers[id]) { console.error(`TTS Factory: Handler not found: ${id}`); return false; @@ -265,6 +271,11 @@ class TTSFactoryModule extends BaseModule { detail: { available: true } })); + // Notify about handler change + document.dispatchEvent(new CustomEvent('tts:handlerChanged', { + detail: { handlerId: id } + })); + console.log(`TTS Factory: Active handler set to ${id}`); return true; } @@ -366,18 +377,31 @@ class TTSFactoryModule extends BaseModule { } /** - * Get available voices for the active TTS handler - * @returns {Array} - Array of voice objects + * Get voices from the active handler + * @returns {Array} - Array of voices */ getVoices() { - if (!this.activeHandler) return []; + // Get the active handler + const handler = this.getActiveHandler(); - try { - return this.handlers[this.activeHandler].getVoices(); - } catch (error) { - console.error("Error getting voices:", error); + // Check if we have an active handler + if (!handler) { + console.log('TTS Factory: No active handler, returning empty voices array'); return []; } + + // Get voices from the active handler + const voices = handler.getVoices(); + console.log(`TTS Factory: Retrieved ${voices ? voices.length : 0} voices from ${this.activeHandler}`); + + // Check if we have any voices + if (!voices || voices.length === 0) { + console.warn(`TTS Factory: No voices retrieved from ${this.activeHandler} handler`); + return []; + } + + // Return voices + return voices; } /** diff --git a/public/kokoro-loader.html b/public/kokoro-loader.html index efde23f..3d35333 100644 --- a/public/kokoro-loader.html +++ b/public/kokoro-loader.html @@ -250,11 +250,15 @@ window.KokoroLoader.instance.generate(data.text, { voice: data.voice, speed: data.speed }) .then(result => { log(`Generation successful for request ${data.id}`, 'success'); + + // Convert the result to a proper format for the parent window + const audio = new Uint8Array(result.buffer); + window.parent.postMessage({ type: 'kokoro-generated', id: data.id, success: true, - result: result + result: { buffer: audio.buffer } }, '*'); }) .catch(error => {