diff --git a/public/js/base-module.js b/public/js/base-module.js index ca262fb..bbc5d19 100644 --- a/public/js/base-module.js +++ b/public/js/base-module.js @@ -188,6 +188,22 @@ export class BaseModule { return this.state; } + /** + * Get the module ID + * @returns {string} - Module ID + */ + getId() { + return this.id; + } + + /** + * Get the module name + * @returns {string} - Module name + */ + getName() { + return this.name; + } + /** * Dispatch a module event * @param {string} name - Event name diff --git a/public/js/elevenlabs-tts-module.js b/public/js/elevenlabs-tts-module.js index ab7b92c..9b32a36 100644 --- a/public/js/elevenlabs-tts-module.js +++ b/public/js/elevenlabs-tts-module.js @@ -38,9 +38,9 @@ export class ElevenLabsTTSModule extends ApiTTSModuleBase { return false; } - // Check for API key - const apiKey = persistenceManager.getPreference('elevenlabs', 'api_key', ''); - if (!apiKey) { + // API key is already loaded in parent initialize() method + // Just check if it's available + if (!this.apiKey) { console.error('ElevenLabs TTS: API key not configured'); return false; } @@ -48,24 +48,24 @@ export class ElevenLabsTTSModule extends ApiTTSModuleBase { // Load voices from ElevenLabs try { this.reportProgress(50, 'Loading ElevenLabs voices'); - await this.loadVoices(apiKey); + await this.loadVoices(this.apiKey); } catch (error) { console.error('ElevenLabs TTS: Failed to load voices:', error); return false; } // Load preferences - const preferredVoice = persistenceManager.getPreference('elevenlabs', 'voice', this.voiceOptions.voice); + const preferredVoice = persistenceManager.getPreference('tts', `${this.id}_voice`, this.voiceOptions.voice); if (preferredVoice) { this.voiceOptions.voice = preferredVoice; } - const preferredModel = persistenceManager.getPreference('elevenlabs', 'model', this.voiceOptions.model); + const preferredModel = persistenceManager.getPreference('tts', `${this.id}_model`, this.voiceOptions.model); if (preferredModel) { this.voiceOptions.model = preferredModel; } - const preferredSpeed = persistenceManager.getPreference('elevenlabs', 'speed', this.voiceOptions.speed); + const preferredSpeed = persistenceManager.getPreference('tts', `${this.id}_speed`, this.voiceOptions.speed); if (typeof preferredSpeed === 'number') { this.voiceOptions.speed = preferredSpeed; } diff --git a/public/js/openai-tts-module.js b/public/js/openai-tts-module.js index 35e98f5..0ea4261 100644 --- a/public/js/openai-tts-module.js +++ b/public/js/openai-tts-module.js @@ -57,28 +57,25 @@ export class OpenAITTSModule extends ApiTTSModuleBase { return false; } - // Check for API key - const apiKey = persistenceManager.getPreference('openai', 'api_key', ''); - if (!apiKey) { + // API key is already loaded in parent initialize() method + // Just check if it's available + if (!this.apiKey) { console.error('OpenAI TTS: API key not configured'); return false; } - // Set API key - this.apiKey = apiKey; - // Load preferences - const preferredVoice = persistenceManager.getPreference('openai', 'voice', this.voiceOptions.voice); + const preferredVoice = persistenceManager.getPreference('tts', `${this.id}_voice`, this.voiceOptions.voice); if (preferredVoice) { this.voiceOptions.voice = preferredVoice; } - const preferredModel = persistenceManager.getPreference('openai', 'model', this.voiceOptions.model); + const preferredModel = persistenceManager.getPreference('tts', `${this.id}_model`, this.voiceOptions.model); if (preferredModel) { this.voiceOptions.model = preferredModel; } - const preferredSpeed = persistenceManager.getPreference('openai', 'speed', this.voiceOptions.speed); + const preferredSpeed = persistenceManager.getPreference('tts', `${this.id}_speed`, this.voiceOptions.speed); if (typeof preferredSpeed === 'number') { this.voiceOptions.speed = preferredSpeed; } @@ -206,7 +203,7 @@ export class OpenAITTSModule extends ApiTTSModuleBase { // Save voice preference const persistenceManager = this.getModule('persistence-manager'); if (persistenceManager) { - persistenceManager.updatePreference('tts', 'openai_voice', options.voice); + persistenceManager.updatePreference('tts', `${this.id}_voice`, options.voice); } } @@ -221,7 +218,7 @@ export class OpenAITTSModule extends ApiTTSModuleBase { // Save the model preference const persistenceManager = this.getModule('persistence-manager'); if (persistenceManager) { - persistenceManager.updatePreference('tts', 'openai_model', options.model); + persistenceManager.updatePreference('tts', `${this.id}_model`, options.model); } } @@ -234,7 +231,7 @@ export class OpenAITTSModule extends ApiTTSModuleBase { // Save the format preference const persistenceManager = this.getModule('persistence-manager'); if (persistenceManager) { - persistenceManager.updatePreference('tts', 'openai_format', options.response_format); + persistenceManager.updatePreference('tts', `${this.id}_format`, options.response_format); } } } diff --git a/public/js/options-ui-module.js b/public/js/options-ui-module.js index fed4470..ee2034d 100644 --- a/public/js/options-ui-module.js +++ b/public/js/options-ui-module.js @@ -60,7 +60,9 @@ class OptionsUIModule extends BaseModule { * @param {string} value - Value to dispatch */ dispatchApiChangeEvent(eventType, provider, valueType, value) { - document.dispatchEvent(new CustomEvent(eventType, { + const eventName = `tts:${eventType}`; + console.log(`Options UI: Dispatching event ${eventName} for provider ${provider}`); + document.dispatchEvent(new CustomEvent(eventName, { detail: { provider, [valueType]: value } })); } @@ -135,7 +137,16 @@ class OptionsUIModule extends BaseModule { // Create settings container const settings = createUIElement('div', { className: 'options-settings' }, null, modalContent); + + // Language Section + const languageSection = createUIElement('div', { className: 'options-section' }, null, settings); + createUIElement('h3', {}, 'Language Settings', languageSection); + // Language selection + const languageContainer = createUIElement('div', { className: 'options-row' }, null, languageSection); + createUIElement('label', {}, 'Language:', languageContainer); + this.elements.language = createUIElement('select', { id: 'app-language' }, null, languageContainer); + // TTS Settings const ttsSection = createUIElement('div', { className: 'options-section' }, null, settings); createUIElement('h3', {}, 'Text-to-Speech', ttsSection); @@ -212,15 +223,6 @@ class OptionsUIModule extends BaseModule { max: '100' }, null, ambienceVolumeContainer); - // Language Section - const languageSection = createUIElement('div', { className: 'options-section' }, null, settings); - createUIElement('h3', {}, 'Language Settings', languageSection); - - // Language selection - const languageContainer = createUIElement('div', { className: 'options-row' }, null, languageSection); - createUIElement('label', {}, 'Language:', languageContainer); - this.elements.language = createUIElement('select', { id: 'app-language' }, null, languageContainer); - // Initialize with display: none this.modal.style.display = 'none'; @@ -240,8 +242,8 @@ class OptionsUIModule extends BaseModule { // ElevenLabs settings // API Key const elevenLabsApiKeyContainer = createUIElement('div', { - className: 'options-row elevenlabs-setting', - 'data-provider': 'elevenlabs' + className: 'options-row elevenlabs-tts-setting', + 'data-provider': 'elevenlabs-tts' }, null, parentSection); createUIElement('label', {}, 'ElevenLabs API Key:', elevenLabsApiKeyContainer); @@ -252,8 +254,8 @@ class OptionsUIModule extends BaseModule { // API URL const elevenLabsApiUrlContainer = createUIElement('div', { - className: 'options-row elevenlabs-setting', - 'data-provider': 'elevenlabs' + className: 'options-row elevenlabs-tts-setting', + 'data-provider': 'elevenlabs-tts' }, null, parentSection); createUIElement('label', {}, 'ElevenLabs API URL:', elevenLabsApiUrlContainer); @@ -265,8 +267,8 @@ class OptionsUIModule extends BaseModule { // OpenAI settings // API Key const openaiApiKeyContainer = createUIElement('div', { - className: 'options-row openai-setting', - 'data-provider': 'openai' + className: 'options-row openai-tts-setting', + 'data-provider': 'openai-tts' }, null, parentSection); createUIElement('label', {}, 'OpenAI API Key:', openaiApiKeyContainer); @@ -277,8 +279,8 @@ class OptionsUIModule extends BaseModule { // API URL const openaiApiUrlContainer = createUIElement('div', { - className: 'options-row openai-setting', - 'data-provider': 'openai' + className: 'options-row openai-tts-setting', + 'data-provider': 'openai-tts' }, null, parentSection); createUIElement('label', {}, 'OpenAI API URL:', openaiApiUrlContainer); @@ -288,7 +290,7 @@ class OptionsUIModule extends BaseModule { }, null, openaiApiUrlContainer); // Initially hide API settings - const apiSettings = document.querySelectorAll('.elevenlabs-setting, .openai-setting'); + const apiSettings = document.querySelectorAll('.elevenlabs-tts-setting, .openai-tts-setting'); apiSettings.forEach(setting => { setting.style.display = 'none'; }); @@ -364,7 +366,6 @@ class OptionsUIModule extends BaseModule { // Save settings this.updatePreference('app', 'locale', locale); - this.updatePreference('tts', 'language', locale); // Update Localization module const localization = this.getModule('localization'); @@ -429,22 +430,6 @@ class OptionsUIModule extends BaseModule { }); } - // Ambience Volume - if (this.elements.ambienceVolume) { - this.elements.ambienceVolume.addEventListener('input', (event) => { - const volume = parseInt(event.target.value) / 100; - console.log('Options UI: Ambience volume changed to', volume); - - // Save setting - this.updatePreference('audio', 'ambienceVolume', volume); - - // Update Audio Manager - const audioManager = this.getModule('audio-manager'); - if (audioManager) { - audioManager.setAmbienceVolume(volume); - } - }); - } } /** @@ -476,15 +461,15 @@ class OptionsUIModule extends BaseModule { * @param {string} selectedSystem - Selected TTS system */ updateApiSettingsVisibility(selectedSystem) { - const elevenLabsSettings = document.querySelectorAll('.elevenlabs-setting'); - const openaiSettings = document.querySelectorAll('.openai-setting'); + const elevenLabsSettings = document.querySelectorAll('.elevenlabs-tts-setting'); + const openaiSettings = document.querySelectorAll('.openai-tts-setting'); elevenLabsSettings.forEach(setting => { - setting.style.display = selectedSystem === 'elevenlabs' ? 'flex' : 'none'; + setting.style.display = selectedSystem === 'elevenlabs-tts' ? 'flex' : 'none'; }); openaiSettings.forEach(setting => { - setting.style.display = selectedSystem === 'openai' ? 'flex' : 'none'; + setting.style.display = selectedSystem === 'openai-tts' ? 'flex' : 'none'; }); } @@ -535,7 +520,7 @@ class OptionsUIModule extends BaseModule { // Format for display const systems = handlers.map(handler => ({ id: handler.id, - name: this.getTtsSystemName(handler.id) + name: handler.displayName || handler.id })); // Populate dropdown @@ -551,23 +536,6 @@ class OptionsUIModule extends BaseModule { this.updateApiSettingsVisibility(this.elements.ttsSystem.value); } - /** - * Get a friendly name for a TTS system - * @param {string} id - TTS system ID - * @returns {string} - Friendly name - */ - getTtsSystemName(id) { - const names = { - 'none': 'None', - 'browser': 'Browser', - 'kokoro': 'Kokoro', - 'elevenlabs': 'ElevenLabs', - 'openai': 'OpenAI' - }; - - return names[id] || id; - } - /** * Populate the voices dropdown */ @@ -645,6 +613,27 @@ class OptionsUIModule extends BaseModule { this.elements.ttsSpeed.value = Math.round(speed * 100); } + // API Keys and URLs + // ElevenLabs API Key + if (this.elements.elevenLabsApiKey) { + this.elements.elevenLabsApiKey.value = this.getPreference('tts', 'elevenlabs-tts_api_key', ''); + } + + // ElevenLabs API URL + if (this.elements.elevenLabsApiUrl) { + this.elements.elevenLabsApiUrl.value = this.getPreference('tts', 'elevenlabs-tts_api_url', 'https://api.elevenlabs.io/v1'); + } + + // OpenAI API Key + if (this.elements.openaiApiKey) { + this.elements.openaiApiKey.value = this.getPreference('tts', 'openai-tts_api_key', ''); + } + + // OpenAI API URL + if (this.elements.openaiApiUrl) { + this.elements.openaiApiUrl.value = this.getPreference('tts', 'openai-tts_api_url', 'https://api.openai.com/v1'); + } + // Audio Settings // Master Volume if (this.elements.masterVolume) { @@ -754,10 +743,10 @@ class OptionsUIModule extends BaseModule { this.elements.elevenLabsApiKey, persistenceManager, 'tts', - 'elevenlabs_api_key', + 'elevenlabs-tts_api_key', null, (value) => { - this.dispatchApiChangeEvent('api:key:change', 'elevenlabs', 'key', value); + this.dispatchApiChangeEvent('api:keyChanged', 'elevenlabs-tts', 'key', value); return value; } ); @@ -767,10 +756,10 @@ class OptionsUIModule extends BaseModule { this.elements.elevenLabsApiUrl, persistenceManager, 'tts', - 'elevenlabs_api_url', + 'elevenlabs-tts_api_url', null, (value) => { - this.dispatchApiChangeEvent('api:url:change', 'elevenlabs', 'url', value); + this.dispatchApiChangeEvent('api:urlChanged', 'elevenlabs-tts', 'url', value); return value; } ); @@ -780,10 +769,10 @@ class OptionsUIModule extends BaseModule { this.elements.openaiApiKey, persistenceManager, 'tts', - 'openai_api_key', + 'openai-tts_api_key', null, (value) => { - this.dispatchApiChangeEvent('api:key:change', 'openai', 'key', value); + this.dispatchApiChangeEvent('api:keyChanged', 'openai-tts', 'key', value); return value; } ); @@ -793,10 +782,10 @@ class OptionsUIModule extends BaseModule { this.elements.openaiApiUrl, persistenceManager, 'tts', - 'openai_api_url', + 'openai-tts_api_url', null, (value) => { - this.dispatchApiChangeEvent('api:url:change', 'openai', 'url', value); + this.dispatchApiChangeEvent('api:urlChanged', 'openai-tts', 'url', value); return value; } ); @@ -873,35 +862,35 @@ class OptionsUIModule extends BaseModule { setupApiUrlFields() { // Set up ElevenLabs API URL if (this.elements.elevenLabsApiUrl) { - const savedUrl = this.getPreference('tts', 'elevenlabs_api_url'); + const savedUrl = this.getPreference('tts', 'elevenlabs-tts_api_url'); const defaultUrl = 'https://api.elevenlabs.io/v1'; // If no saved URL, set the default if (!savedUrl) { console.log('Options UI: Setting default ElevenLabs API URL:', defaultUrl); - this.updatePreference('tts', 'elevenlabs_api_url', defaultUrl); + this.updatePreference('tts', 'elevenlabs-tts_api_url', defaultUrl); } } // Set up OpenAI API URL if (this.elements.openaiApiUrl) { - const savedUrl = this.getPreference('tts', 'openai_api_url'); + const savedUrl = this.getPreference('tts', 'openai-tts_api_url'); const defaultUrl = 'https://api.openai.com/v1'; // If no saved URL, set the default if (!savedUrl) { console.log('Options UI: Setting default OpenAI API URL:', defaultUrl); - this.updatePreference('tts', 'openai_api_url', defaultUrl); + this.updatePreference('tts', 'openai-tts_api_url', defaultUrl); } } // Make sure API keys are initialized if not already set - if (!this.getPreference('tts', 'elevenlabs_api_key')) { - this.updatePreference('tts', 'elevenlabs_api_key', ''); + if (!this.getPreference('tts', 'elevenlabs-tts_api_key')) { + this.updatePreference('tts', 'elevenlabs-tts_api_key', ''); } - if (!this.getPreference('tts', 'openai_api_key')) { - this.updatePreference('tts', 'openai_api_key', ''); + if (!this.getPreference('tts', 'openai-tts_api_key')) { + this.updatePreference('tts', 'openai-tts_api_key', ''); } } diff --git a/public/js/persistence-manager-module.js b/public/js/persistence-manager-module.js index dd6ee43..cba6f58 100644 --- a/public/js/persistence-manager-module.js +++ b/public/js/persistence-manager-module.js @@ -29,32 +29,20 @@ class PersistenceManagerModule extends BaseModule { // Default preferences this.defaultPreferences = { - animation: { - enabled: true, - speed: 50 // 0-100 scale, 50 is default - }, tts: { enabled: false, - provider: 'browser', // 'browser', 'api', 'kokoro' + provider: 'none', voice: '', - volume: 1.0, - rate: 1.0, - language: 'en-us' // Default language, will be updated during initialization }, audio: { masterVolume: 1.0, + ttsVolume: 1.0, musicVolume: 0.7, sfxVolume: 1.0, - musicEnabled: true, - sfxEnabled: true - }, - accessibility: { - highContrast: false, - largerText: false }, app: { locale: 'en-us', - theme: 'default' + speed: 1.0, } }; @@ -275,6 +263,7 @@ class PersistenceManagerModule extends BaseModule { // Save preferences const success = this.savePreferences(); + console.log("Saved preferences: ", category, setting, value, this.preferences) // Dispatch event this.dispatchEvent('preference-updated', { diff --git a/public/js/tts-factory-module.js b/public/js/tts-factory-module.js index 86a76c0..ac36c58 100644 --- a/public/js/tts-factory-module.js +++ b/public/js/tts-factory-module.js @@ -314,14 +314,15 @@ class TTSFactoryModule extends BaseModule { for (const id in this.handlers) { const handler = this.handlers[id]; availableHandlers.push({ - id: id, + id: handler.getId(), handler: handler, + displayName: handler.getName(), isReady: handler.isReady === true }); } // Add placeholder entries for important API handlers that might not be registered yet - const apiHandlerIds = ['elevenlabs', 'openai']; + const apiHandlerIds = ['elevenlabs-tts', 'openai-tts']; for (const id of apiHandlerIds) { // Only add if not already in the list if (!this.handlers[id] && !availableHandlers.some(h => h.id === id)) { @@ -329,6 +330,7 @@ class TTSFactoryModule extends BaseModule { availableHandlers.push({ id: id, handler: null, + displayName: id.split('-')[0].charAt(0).toUpperCase() + id.split('-')[0].slice(1), isReady: false }); } @@ -431,10 +433,10 @@ class TTSFactoryModule extends BaseModule { // Register handlers (in order of preference) const handlers = [ - { id: 'kokoro', displayName: 'Kokoro TTS' }, - { id: 'browser', displayName: 'Browser TTS' }, - { id: 'elevenlabs', displayName: 'ElevenLabs TTS' }, - { id: 'openai', displayName: 'OpenAI TTS' } + { id: 'kokoro-tts', displayName: 'Kokoro TTS' }, + { id: 'browser-tts', displayName: 'Browser TTS' }, + { id: 'elevenlabs-tts', displayName: 'ElevenLabs TTS' }, + { id: 'openai-tts', displayName: 'OpenAI TTS' } ]; // Register each handler