Refactored modules and updated loader.

This commit is contained in:
2025-04-06 18:35:04 +00:00
parent fc693ae695
commit 0ab639fd25
37 changed files with 3530 additions and 5989 deletions
+88 -39
View File
@@ -8,6 +8,9 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
constructor(id, name) {
super(id, name);
// Declare proper dependencies according to architecture principles
this.dependencies = ['persistence-manager', 'localization'];
// Basic voice options
this.voiceOptions = {
speed: 1.0,
@@ -86,8 +89,13 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
// Check if we have an API key
this.isReady = !!this.apiKey;
// Always mark as available for UI configuration purposes
// (even if not ready due to missing API key)
if (!this.isReady) {
console.error(`${this.name}: Missing API key, initialization failed`);
this.reportProgress(100, `${this.name} initialization failed - missing API key`);
return false; // Properly report failure when API key is missing
}
// Only mark as complete if we have an API key
this.reportProgress(100, `${this.name} initialization complete`);
return true;
@@ -111,6 +119,7 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
const localization = this.getModule('localization');
if (!persistenceManager || !localization) {
console.error(`${this.name}: Required dependencies not found`);
return false;
}
@@ -120,16 +129,13 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
// Get current locale
const currentLocale = localization.getLocale();
// If we have a preferred voice and available voices, use it
if (preferredVoiceId && this.voices && this.voices.length > 0) {
const voice = this.voices.find(v => v.id === preferredVoiceId);
if (voice) {
this.voiceOptions.voice = voice;
return true;
}
// If we have a preferred voice ID, use it
if (preferredVoiceId && this.voices.some(v => v.id === preferredVoiceId)) {
this.voiceOptions.voice = this.voices.find(v => v.id === preferredVoiceId);
return true;
}
// Otherwise select a voice based on locale
// Otherwise, select voice based on locale
if (currentLocale) {
return this.selectVoiceForLocale(currentLocale);
}
@@ -163,7 +169,7 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
* @returns {boolean} - Success status
*/
selectDefaultVoice() {
if (this.voices && this.voices.length > 0) {
if (this.voices.length > 0) {
this.voiceOptions.voice = this.voices[0];
return true;
}
@@ -188,50 +194,42 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
*/
speakPreloaded(preloadData, callback = null) {
if (!preloadData || !preloadData.audioData) {
if (callback) {
callback({ success: false, reason: 'invalid_data' });
}
console.error(`${this.name}: Invalid preloaded data`);
if (callback) callback({ success: false, reason: 'invalid_data' });
return false;
}
// Stop any ongoing speech
this.stop();
// Create audio blob
// Create an audio element to play the audio
const audioBlob = new Blob([preloadData.audioData], { type: 'audio/mp3' });
const audioUrl = URL.createObjectURL(audioBlob);
// Create audio element
const audio = new Audio(audioUrl);
// Set up state
this.isSpeaking = true;
this.currentAudio = audio;
// Set up event handlers
audio.onended = () => {
this.isSpeaking = false;
if (callback) {
callback({ success: true });
}
this.currentAudio = null;
URL.revokeObjectURL(audioUrl);
if (callback) callback({ success: true });
};
audio.onerror = (error) => {
console.error(`${this.name}: Audio playback error:`, error);
this.isSpeaking = false;
if (callback) {
callback({ success: false, reason: 'playback_error', error });
}
this.currentAudio = null;
URL.revokeObjectURL(audioUrl);
if (callback) callback({ success: false, reason: 'playback_error', error });
};
// Start playback
this.currentAudio = audio;
this.isSpeaking = true;
// Handle play error
// Play the audio
audio.play().catch(error => {
this.isSpeaking = false;
if (callback) {
callback({ success: false, reason: 'playback_error', error });
}
URL.revokeObjectURL(audioUrl);
console.error(`${this.name}: Failed to play audio:`, error);
if (callback) callback({ success: false, reason: 'playback_error', error });
});
return true;
@@ -244,17 +242,21 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
stop() {
if (this.currentAudio) {
try {
// Stop current audio
this.currentAudio.pause();
this.currentAudio.currentTime = 0;
this.currentAudio = null;
// Clean up
this.isSpeaking = false;
this.currentAudio = null;
return true;
} catch (error) {
console.error(`${this.name}: Error stopping speech:`, error);
console.error(`${this.name}: Error stopping audio:`, error);
return false;
}
}
return true;
return true; // Already stopped
}
/**
@@ -351,6 +353,7 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
handleApiKeyChanged(event) {
if (event && event.detail && event.detail.provider === this.id) {
const newKey = event.detail.key || '';
const oldKey = this.apiKey;
// Security check - never use a URL as an API key
if (newKey && newKey.startsWith('http')) {
@@ -368,7 +371,33 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
}
// Update ready state
const wasReady = this.isReady;
this.isReady = !!this.apiKey;
// If state changed (now ready/not-ready), notify the TTS factory
if (wasReady !== this.isReady) {
console.log(`${this.name}: TTS ready state changed to ${this.isReady ? 'ready' : 'not ready'} after API key change`);
// Find and notify the TTS factory
const ttsFactory = this.getModule('tts-factory');
if (ttsFactory) {
// If we have a key now (and didn't before), try initializing voices
if (this.isReady && !wasReady) {
// Reload voices with the new API key
this.loadVoices().then(() => {
// Then set up voice from preferences
this.setupVoiceFromPreferences().then(() => {
console.log(`${this.name}: Successfully initialized with new API key`);
// Notify the factory of our readiness change
ttsFactory.updateTTSAvailability();
});
});
} else {
// Just update the availability
ttsFactory.updateTTSAvailability();
}
}
}
}
}
@@ -378,6 +407,7 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
*/
handleApiUrlChanged(event) {
if (event && event.detail && event.detail.provider === this.id) {
const oldUrl = this.apiBaseUrl;
const newUrl = event.detail.url || this.getDefaultApiBaseUrl();
// Update API URL
@@ -388,6 +418,25 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
if (persistenceManager) {
persistenceManager.updatePreference('tts', `${this.id}_api_url`, newUrl);
}
// Only reinitialize if the URL actually changed and we have an API key
if (oldUrl !== newUrl && this.isReady) {
console.log(`${this.name}: API URL changed, reinitializing`);
// Reload voices with the new API URL if we're ready
this.loadVoices().then(() => {
// Then set up voice from preferences
this.setupVoiceFromPreferences().then(() => {
console.log(`${this.name}: Successfully reinitialized with new API URL`);
// Notify the TTS factory
const ttsFactory = this.getModule('tts-factory');
if (ttsFactory) {
ttsFactory.updateTTSAvailability();
}
});
});
}
}
}
}