Fixed option ui binidngs.
This commit is contained in:
+73
-105
@@ -89,6 +89,12 @@ export class KokoroHandler extends TTSHandler {
|
|||||||
return true;
|
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
|
// Set loading flag
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.isReady = false; // Explicitly set to false during initialization
|
this.isReady = false; // Explicitly set to false during initialization
|
||||||
@@ -169,12 +175,13 @@ export class KokoroHandler extends TTSHandler {
|
|||||||
} else {
|
} else {
|
||||||
console.warn('Kokoro TTS: No voices received during initialization or invalid voices data');
|
console.warn('Kokoro TTS: No voices received during initialization or invalid voices data');
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
// If initialization was successful but no voices were received,
|
// Even though we already set the default voices, check and update if needed
|
||||||
// use default voices
|
if (!this.voices || this.voices.length === 0) {
|
||||||
this.voices = this.getDefaultVoices();
|
this.voices = this.getDefaultVoices();
|
||||||
console.log('Kokoro TTS: Using default voices as fallback');
|
console.log('Kokoro TTS: Using default voices as fallback');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set up voice from preferences
|
// Set up voice from preferences
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
@@ -182,7 +189,7 @@ export class KokoroHandler extends TTSHandler {
|
|||||||
console.log('Kokoro TTS: Voice set up from preferences during initialization');
|
console.log('Kokoro TTS: Voice set up from preferences during initialization');
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('Kokoro TTS: Error setting up voice from preferences during initialization:',
|
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);
|
window.addEventListener('message', messageHandler);
|
||||||
|
|
||||||
// Send initialization message to iframe
|
// Initial progress update
|
||||||
if (this.iframe.contentWindow) {
|
handleProgress(0.1, 'Starting Kokoro initialization');
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} 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.loading = false;
|
||||||
this.available = false;
|
|
||||||
this.isReady = false;
|
this.isReady = false;
|
||||||
|
this.available = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,91 +218,56 @@ export class KokoroHandler extends TTSHandler {
|
|||||||
* @param {MessageEvent} event - Message event
|
* @param {MessageEvent} event - Message event
|
||||||
*/
|
*/
|
||||||
handleIframeMessage(event) {
|
handleIframeMessage(event) {
|
||||||
// Ignore messages from other sources
|
// Only process messages from our iframe
|
||||||
if (!this.iframe || event.source !== this.iframe.contentWindow) {
|
if (!this.iframe || event.source !== this.iframe.contentWindow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = event.data;
|
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) {
|
switch (data.type) {
|
||||||
|
case 'kokoro-log':
|
||||||
|
console.log(`Kokoro Loader: ${data.message}`);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'kokoro-ready':
|
case 'kokoro-ready':
|
||||||
console.log('Kokoro TTS: Received ready message with success =', data.success);
|
console.log('Kokoro TTS: Received ready message from iframe. Success:', data.success, 'Voices:', data.voices ? data.voices.length : 0);
|
||||||
|
|
||||||
// Store voices if provided
|
// 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)) {
|
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;
|
this.voices = data.voices;
|
||||||
|
} else if (data.success) {
|
||||||
// Set availability and ready flags
|
// If success but no voices, use defaults
|
||||||
this.available = true;
|
console.warn('Kokoro TTS: No voices received from iframe, using defaults');
|
||||||
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.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' }
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
break;
|
||||||
|
|
||||||
case 'kokoro-generated':
|
case 'kokoro-generated':
|
||||||
// Handle generated speech
|
// Handle generated speech
|
||||||
const pendingGeneration = this.pendingGenerations.get(data.id);
|
if (data.id && this.pendingGenerations.has(data.id)) {
|
||||||
if (pendingGeneration) {
|
const { resolve, reject } = this.pendingGenerations.get(data.id);
|
||||||
this.pendingGenerations.delete(data.id);
|
this.pendingGenerations.delete(data.id);
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success && data.result) {
|
||||||
// Create audio element from the result
|
// Create an audio element from the result
|
||||||
try {
|
try {
|
||||||
// Create a blob from the buffer
|
// Create a blob from the buffer
|
||||||
const blob = new Blob([data.result.buffer], { type: 'audio/wav' });
|
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) {
|
} catch (error) {
|
||||||
console.error('Error processing Kokoro audio:', error);
|
console.error('Error processing Kokoro audio:', error);
|
||||||
pendingGeneration.reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
} else {
|
} 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;
|
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':
|
case 'kokoro-progress':
|
||||||
// Progress updates are handled during initialization
|
// Progress updates are handled during initialization
|
||||||
break;
|
break;
|
||||||
@@ -440,7 +391,7 @@ export class KokoroHandler extends TTSHandler {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log detailed error information
|
// 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
|
// Default to first voice if available
|
||||||
if (this.voices && this.voices.length > 0) {
|
if (this.voices && this.voices.length > 0) {
|
||||||
@@ -505,9 +456,12 @@ export class KokoroHandler extends TTSHandler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get available voices
|
* Get available voices
|
||||||
* @returns {Array} - Array of available voices
|
* @returns {Array} - Array of voice objects
|
||||||
*/
|
*/
|
||||||
getVoices() {
|
getVoices() {
|
||||||
|
if (!this.voices || this.voices.length === 0) {
|
||||||
|
return this.getDefaultVoices();
|
||||||
|
}
|
||||||
return this.voices;
|
return this.voices;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -710,11 +664,14 @@ export class KokoroHandler extends TTSHandler {
|
|||||||
if (this.currentVoice && this.currentVoice.id) {
|
if (this.currentVoice && this.currentVoice.id) {
|
||||||
voiceId = this.currentVoice.id;
|
voiceId = this.currentVoice.id;
|
||||||
} else if (this.voices && this.voices.length > 0) {
|
} else if (this.voices && this.voices.length > 0) {
|
||||||
// If currentVoice is not set but we have voices, use the first one
|
// Default to first voice if none selected
|
||||||
voiceId = this.voices[0].id;
|
|
||||||
this.currentVoice = this.voices[0];
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Generate unique ID for this request
|
// Generate unique ID for this request
|
||||||
const id = `gen-${++this.generationCounter}`;
|
const id = `gen-${++this.generationCounter}`;
|
||||||
@@ -787,11 +744,22 @@ export class KokoroHandler extends TTSHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get default voices
|
* Get default voices for current locale
|
||||||
* @returns {Array} - Array of default voices
|
* @returns {Array} Default voices
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
getDefaultVoices() {
|
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 [
|
return [
|
||||||
// American Female voices
|
// American Female voices
|
||||||
{ id: 'af_heart', name: 'Heart', lang: 'en-US', gender: 'female' },
|
{ id: 'af_heart', name: 'Heart', lang: 'en-US', gender: 'female' },
|
||||||
|
|||||||
+19
-37
@@ -13,12 +13,12 @@ class LocalizationModule extends BaseModule {
|
|||||||
super('localization', 'Localization');
|
super('localization', 'Localization');
|
||||||
|
|
||||||
// Current locale
|
// Current locale
|
||||||
this.currentLocale = 'en-us';
|
this.translations = {};
|
||||||
|
this.defaultLocale = 'en-us';
|
||||||
|
this.currentLocale = this.defaultLocale;
|
||||||
|
this.dependencies = ['persistence-manager'];
|
||||||
|
|
||||||
// Available translations
|
// Available translations
|
||||||
this.translations = {};
|
|
||||||
|
|
||||||
// Language names mapping
|
|
||||||
this.languageNames = {
|
this.languageNames = {
|
||||||
'en-us': 'English (US)',
|
'en-us': 'English (US)',
|
||||||
'en-gb': 'English (UK)',
|
'en-gb': 'English (UK)',
|
||||||
@@ -44,41 +44,31 @@ class LocalizationModule extends BaseModule {
|
|||||||
try {
|
try {
|
||||||
this.reportProgress(10, "Initializing localization");
|
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
|
// Get stored locale from persistence manager if available
|
||||||
const persistenceManager = this.getModule('persistence-manager');
|
const persistenceManager = this.getModule('persistence-manager');
|
||||||
let storedLocale = null;
|
|
||||||
|
|
||||||
if (persistenceManager) {
|
if (persistenceManager) {
|
||||||
try {
|
const storedLocale = persistenceManager.getPreference('app', 'locale');
|
||||||
storedLocale = persistenceManager.getPreference('app', 'locale');
|
|
||||||
if (storedLocale) {
|
if (storedLocale) {
|
||||||
console.log(`Localization: Found stored locale: ${storedLocale}`);
|
console.log(`Localization: Found stored locale: ${storedLocale}`);
|
||||||
await this.loadTranslations(storedLocale);
|
await this.loadTranslations(storedLocale);
|
||||||
this.currentLocale = storedLocale;
|
this.currentLocale = storedLocale;
|
||||||
|
this.reportProgress(80, `Loaded stored locale: ${storedLocale}`);
|
||||||
} else {
|
} else {
|
||||||
// If no stored locale, ensure English is the default and persist it
|
// If no stored locale, ensure en-us is the default and persist it
|
||||||
console.log('Localization: No stored locale found, defaulting to en-us');
|
console.log('Localization: No stored locale found, using default en-us');
|
||||||
await this.loadTranslations('en-us');
|
|
||||||
persistenceManager.updatePreference('app', 'locale', 'en-us');
|
persistenceManager.updatePreference('app', 'locale', 'en-us');
|
||||||
persistenceManager.updatePreference('tts', 'language', 'en-us');
|
persistenceManager.updatePreference('tts', 'language', 'en-us');
|
||||||
this.currentLocale = 'en-us';
|
this.currentLocale = 'en-us';
|
||||||
}
|
this.reportProgress(80, "Using default locale: en-us");
|
||||||
} catch (persistError) {
|
|
||||||
console.warn(`Failed to load stored locale:`, persistError);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If browser locale is available, just load it as a fallback but keep English as default
|
console.log('Localization: Persistence manager not available, using default en-us locale');
|
||||||
const browserLocale = navigator.language.toLowerCase();
|
this.reportProgress(80, "Using default locale: en-us");
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch event to notify about loaded locale
|
// Dispatch event to notify about loaded locale
|
||||||
@@ -116,26 +106,18 @@ class LocalizationModule extends BaseModule {
|
|||||||
const translations = await response.json();
|
const translations = await response.json();
|
||||||
this.translations[normalizedLocale] = translations;
|
this.translations[normalizedLocale] = translations;
|
||||||
} else {
|
} else {
|
||||||
// Try to load the language part only
|
// Don't try to load language part without region (e.g., "en") - we only support full locales
|
||||||
const langPart = normalizedLocale.split('-')[0];
|
// Fallback to en-us if the requested locale isn't found
|
||||||
if (langPart !== normalizedLocale) {
|
if (normalizedLocale !== 'en-us') {
|
||||||
const langResponse = await fetch(`/locales/${langPart}.json`);
|
console.warn(`Translations for ${normalizedLocale} not found, falling back to en-us`);
|
||||||
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');
|
await this.loadTranslations('en-us');
|
||||||
this.translations[normalizedLocale] = this.translations['en-us'];
|
this.translations[normalizedLocale] = this.translations['en-us'];
|
||||||
} else {
|
} else {
|
||||||
// If English is not found, create an empty translation set
|
// If en-us is not found, create an empty translation set
|
||||||
console.warn('English translations not found, using empty set');
|
console.warn('English translations not found, using empty set');
|
||||||
this.translations[normalizedLocale] = {};
|
this.translations[normalizedLocale] = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error loading translations for ${locale}:`, error);
|
console.error(`Error loading translations for ${locale}:`, error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
+140
-32
@@ -34,15 +34,17 @@ class OptionsUIModule extends BaseModule {
|
|||||||
'handleTtsSystemChanged',
|
'handleTtsSystemChanged',
|
||||||
'showReloadNotice',
|
'showReloadNotice',
|
||||||
'toggle',
|
'toggle',
|
||||||
'setupEventListeners'
|
'setupEventListeners',
|
||||||
|
'saveCurrentSettings'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the options UI
|
* Initialize the options UI
|
||||||
* @returns {boolean} - True if initialization was successful
|
* @returns {Promise<boolean>} - Resolves with success status
|
||||||
*/
|
*/
|
||||||
initialize() {
|
async initialize() {
|
||||||
|
try {
|
||||||
console.log('Initializing Options UI Module');
|
console.log('Initializing Options UI Module');
|
||||||
|
|
||||||
// Set up dependencies
|
// Set up dependencies
|
||||||
@@ -87,7 +89,25 @@ class OptionsUIModule extends BaseModule {
|
|||||||
}, 1000); // 1 second delay
|
}, 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;
|
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');
|
const closeButton = document.createElement('button');
|
||||||
closeButton.className = 'options-close';
|
closeButton.className = 'options-close';
|
||||||
closeButton.innerHTML = '×';
|
closeButton.innerHTML = '×';
|
||||||
closeButton.addEventListener('click', () => this.hide());
|
closeButton.addEventListener('click', () => {
|
||||||
|
// Save all current settings when closing
|
||||||
|
this.saveCurrentSettings();
|
||||||
|
this.hide();
|
||||||
|
});
|
||||||
header.appendChild(closeButton);
|
header.appendChild(closeButton);
|
||||||
|
|
||||||
content.appendChild(header);
|
content.appendChild(header);
|
||||||
@@ -524,17 +548,11 @@ class OptionsUIModule extends BaseModule {
|
|||||||
noneOption.textContent = 'None (Disable TTS)';
|
noneOption.textContent = 'None (Disable TTS)';
|
||||||
this.elements.ttsSystem.appendChild(noneOption);
|
this.elements.ttsSystem.appendChild(noneOption);
|
||||||
|
|
||||||
// Debug log for troubleshooting
|
|
||||||
console.log('Options UI: Populating TTS systems');
|
|
||||||
|
|
||||||
// Get available handlers
|
// Get available handlers
|
||||||
const handlers = ttsFactory.getAvailableHandlers();
|
const handlers = ttsFactory.getAvailableHandlers();
|
||||||
console.log('Options UI: Available handlers:', handlers);
|
|
||||||
|
|
||||||
// Add all registered handlers
|
// Add all registered handlers
|
||||||
for (const id in 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');
|
const option = document.createElement('option');
|
||||||
option.value = id;
|
option.value = id;
|
||||||
option.textContent = this.getTtsSystemName(id);
|
option.textContent = this.getTtsSystemName(id);
|
||||||
@@ -543,13 +561,23 @@ class OptionsUIModule extends BaseModule {
|
|||||||
|
|
||||||
// If no handlers available, add a disabled option
|
// If no handlers available, add a disabled option
|
||||||
if (this.elements.ttsSystem.options.length === 1) {
|
if (this.elements.ttsSystem.options.length === 1) {
|
||||||
console.log('Options UI: No TTS systems available, adding disabled option');
|
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = '';
|
option.value = '';
|
||||||
option.textContent = 'No TTS systems available';
|
option.textContent = 'No TTS systems available';
|
||||||
option.disabled = true;
|
option.disabled = true;
|
||||||
this.elements.ttsSystem.appendChild(option);
|
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
|
* Populate voices dropdown for the current TTS system
|
||||||
*/
|
*/
|
||||||
populateVoices() {
|
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 ttsFactory = this.getModule('tts-factory');
|
||||||
const localization = this.getModule('localization');
|
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
|
// Clear existing options
|
||||||
this.elements.ttsVoice.innerHTML = '';
|
this.elements.ttsVoice.innerHTML = '';
|
||||||
|
|
||||||
// Get current locale
|
// Get current locale
|
||||||
const currentLocale = localization.getLocale();
|
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
|
// 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
|
// Add available voices to dropdown
|
||||||
const filteredVoices = allVoices.filter(voice => {
|
if (voices && voices.length > 0) {
|
||||||
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 options for each voice
|
// Add options for each voice
|
||||||
filteredVoices.forEach(voice => {
|
voices.forEach(voice => {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = voice.id || voice.name;
|
option.value = voice.id || voice.name;
|
||||||
option.textContent = voice.name;
|
option.textContent = voice.name;
|
||||||
@@ -604,13 +639,15 @@ class OptionsUIModule extends BaseModule {
|
|||||||
}
|
}
|
||||||
this.elements.ttsVoice.appendChild(option);
|
this.elements.ttsVoice.appendChild(option);
|
||||||
});
|
});
|
||||||
|
console.log(`Options UI: Added ${voices.length} voice options to the dropdown`);
|
||||||
} else {
|
} else {
|
||||||
// No voices available for current locale
|
// No voices available
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = '';
|
option.value = '';
|
||||||
option.textContent = `No voices available for ${currentLocale}`;
|
option.textContent = `No voices available for ${currentLocale}`;
|
||||||
option.disabled = true;
|
option.disabled = true;
|
||||||
this.elements.ttsVoice.appendChild(option);
|
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;
|
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() {
|
setupEventListeners() {
|
||||||
// Listen for language change events
|
// Listen for language change events
|
||||||
document.addEventListener('localization:languageChanged', () => {
|
document.addEventListener('localization:languageChanged', () => {
|
||||||
// Update the language selection in options panel
|
this.populateLanguages();
|
||||||
const localization = this.getModule('localization');
|
this.populateVoices();
|
||||||
if (localization && this.elements && this.elements.language) {
|
});
|
||||||
const currentLocale = localization.getLocale();
|
|
||||||
if (currentLocale && this.elements.language.value !== currentLocale) {
|
// Listen for TTS state changes
|
||||||
this.elements.language.value = currentLocale;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-populate TTS voices for new language
|
// Refresh voices when handler changes
|
||||||
this.populateVoices();
|
this.populateVoices();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for TTS availability events
|
// Listen for TTS availability events
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ class PersistenceManagerModule extends BaseModule {
|
|||||||
'getAllSaveSlots'
|
'getAllSaveSlots'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Add localization as a dependency
|
// Remove circular dependency
|
||||||
this.dependencies = ['localization'];
|
this.dependencies = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,38 +92,6 @@ class PersistenceManagerModule extends BaseModule {
|
|||||||
// Load save slots
|
// Load save slots
|
||||||
this.loadSaveSlots();
|
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");
|
this.reportProgress(100, "Persistence manager ready");
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
+52
-28
@@ -177,37 +177,39 @@ class TTSFactoryModule extends BaseModule {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a specific TTS handler
|
* Initialize a specific TTS handler
|
||||||
* @param {string} id - Handler ID
|
* @param {string} id - Handler ID to initialize
|
||||||
* @returns {Promise<boolean>} - Success status
|
* @returns {Promise<boolean>} - Resolves with success status
|
||||||
*/
|
*/
|
||||||
async initializeHandler(id) {
|
async initializeHandler(id) {
|
||||||
if (!id || !this.handlers[id]) {
|
if (!this.handlers[id]) {
|
||||||
console.error(`TTS Factory: Handler '${id}' not found`);
|
console.error(`TTS Factory: Handler ${id} not found`);
|
||||||
return false;
|
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 {
|
try {
|
||||||
this.reportProgress(0, `Initializing ${id} TTS handler`);
|
// Initialize the handler with progress callback
|
||||||
|
const success = await this.handlers[id].initialize(progressCallback);
|
||||||
// Initialize the handler
|
|
||||||
const success = await this.handlers[id].initialize(
|
|
||||||
(progress, message) => {
|
|
||||||
this.reportProgress(progress, message);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update initialization status
|
|
||||||
this.initStatus[id] = success;
|
this.initStatus[id] = success;
|
||||||
|
|
||||||
if (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 {
|
} else {
|
||||||
console.error(`TTS Factory: Failed to initialize ${id} TTS handler`);
|
console.warn(`TTS Factory: Handler ${id} initialization failed`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
} catch (error) {
|
} 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;
|
this.initStatus[id] = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -229,11 +231,9 @@ class TTSFactoryModule extends BaseModule {
|
|||||||
* @returns {boolean} - Success status
|
* @returns {boolean} - Success status
|
||||||
*/
|
*/
|
||||||
setActiveHandler(id) {
|
setActiveHandler(id) {
|
||||||
// Handle 'none' option specially
|
// If 'none' is passed, disable TTS
|
||||||
if (id === 'none') {
|
if (id === 'none') {
|
||||||
this.activeHandler = null;
|
this.activeHandler = null;
|
||||||
|
|
||||||
// Update TTS availability state
|
|
||||||
this.ttsAvailable = false;
|
this.ttsAvailable = false;
|
||||||
|
|
||||||
// Notify about TTS availability change
|
// Notify about TTS availability change
|
||||||
@@ -241,10 +241,16 @@ class TTSFactoryModule extends BaseModule {
|
|||||||
detail: { available: false }
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the handler exists
|
||||||
if (!this.handlers[id]) {
|
if (!this.handlers[id]) {
|
||||||
console.error(`TTS Factory: Handler not found: ${id}`);
|
console.error(`TTS Factory: Handler not found: ${id}`);
|
||||||
return false;
|
return false;
|
||||||
@@ -265,6 +271,11 @@ class TTSFactoryModule extends BaseModule {
|
|||||||
detail: { available: true }
|
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}`);
|
console.log(`TTS Factory: Active handler set to ${id}`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -366,18 +377,31 @@ class TTSFactoryModule extends BaseModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get available voices for the active TTS handler
|
* Get voices from the active handler
|
||||||
* @returns {Array} - Array of voice objects
|
* @returns {Array} - Array of voices
|
||||||
*/
|
*/
|
||||||
getVoices() {
|
getVoices() {
|
||||||
if (!this.activeHandler) return [];
|
// Get the active handler
|
||||||
|
const handler = this.getActiveHandler();
|
||||||
|
|
||||||
try {
|
// Check if we have an active handler
|
||||||
return this.handlers[this.activeHandler].getVoices();
|
if (!handler) {
|
||||||
} catch (error) {
|
console.log('TTS Factory: No active handler, returning empty voices array');
|
||||||
console.error("Error getting voices:", error);
|
|
||||||
return [];
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -250,11 +250,15 @@
|
|||||||
window.KokoroLoader.instance.generate(data.text, { voice: data.voice, speed: data.speed })
|
window.KokoroLoader.instance.generate(data.text, { voice: data.voice, speed: data.speed })
|
||||||
.then(result => {
|
.then(result => {
|
||||||
log(`Generation successful for request ${data.id}`, 'success');
|
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({
|
window.parent.postMessage({
|
||||||
type: 'kokoro-generated',
|
type: 'kokoro-generated',
|
||||||
id: data.id,
|
id: data.id,
|
||||||
success: true,
|
success: true,
|
||||||
result: result
|
result: { buffer: audio.buffer }
|
||||||
}, '*');
|
}, '*');
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
|||||||
Reference in New Issue
Block a user