mirror of
				https://github.com/SillyTavern/SillyTavern.git
				synced 2025-06-05 21:59:27 +02:00 
			
		
		
		
	1. Core Feature Addition:
   - Added AllTalk V1/V2 server version selection
   - Added RVC (Realistic Voice Conversion) support for V2
   - RVC features are automatically disabled when V1 is selected
2. Code Improvements:
   - Replaced custom debounce implementation with shared utils.js debounce utility
   - Fixed linting issues:
     - Converted HTML attribute quotes in template literals to single quotes
     - Added trailing commas where required
     - Fixed console.error message formatting
3. New Settings/Properties Added:
   ```javascript
   server_version: 'v2' (default)
   rvc_character_voice: 'Disabled'
   rvc_character_pitch: '0'
   rvc_narrator_voice: 'Disabled'
   rvc_narrator_pitch: '0'
   ```
4. Bug Fixes/Improvements:
   - Better error handling for RVC voice fetching
   - Improved URL handling for V1/V2 differences in API responses
   - Enhanced settings initialization and validation
5. Structural Changes:
   - Added RVC-specific UI elements and controls
   - Added version-specific logic for API endpoints
   - Improved settings synchronization between UI and backend
**NOTE** On line 70 there is an eslint bypass:
```javascript
        // HTML template literals can trigger ESLint quotes warnings when quotes are used in HTML attributes.
        // Disabling quotes rule for this one line as it's a false positive with HTML template literals.
        // eslint-disable-next-line quotes
        let html = `<div class="at-settings-separator">AllTalk V2 Settings</div>`;
```
The reason is:
1. ESLint's quotes rule wants all strings to use single quotes
2. However, this is a template literal containing HTML, where double quotes are standard for attributes
3. I tried various solutions:
   - Using single quotes: `<div class='at-settings-separator'>`
   - Using double quotes: `<div class="at-settings-separator">`
   - Even tried escaping quotes
   But ESLint just kept flagging it as an error
		
	
		
			
				
	
	
		
			1073 lines
		
	
	
		
			46 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1073 lines
		
	
	
		
			46 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { doExtrasFetch } from '../../extensions.js';
 | |
| import { debounce } from '../../utils.js';
 | |
| import { saveTtsProviderSettings } from './index.js';
 | |
| 
 | |
| export { AllTalkTtsProvider };
 | |
| 
 | |
| class AllTalkTtsProvider {
 | |
|     //########//
 | |
|     // Config //
 | |
|     //########//
 | |
| 
 | |
|     settings = {};
 | |
|     constructor() {
 | |
|         // Initialize with default settings if they are not already set
 | |
|         this.settings = {
 | |
|             provider_endpoint: this.settings.provider_endpoint || 'http://localhost:7851',
 | |
|             server_version: this.settings.server_version || 'v2',
 | |
|             language: this.settings.language || 'en',
 | |
|             voiceMap: this.settings.voiceMap || {},
 | |
|             at_generation_method: this.settings.at_generation_method || 'standard_generation',
 | |
|             narrator_enabled: this.settings.narrator_enabled || 'false',
 | |
|             at_narrator_text_not_inside: this.settings.at_narrator_text_not_inside || 'narrator',
 | |
|             narrator_voice_gen: this.settings.narrator_voice_gen || 'Please set a voice',
 | |
|             rvc_character_voice: this.settings.rvc_character_voice || 'Disabled',
 | |
|             rvc_character_pitch: this.settings.rvc_character_pitch || '0',
 | |
|             rvc_narrator_voice: this.settings.rvc_narrator_voice || 'Disabled',
 | |
|             rvc_narrator_pitch: this.settings.rvc_narrator_pitch || '0',
 | |
|             finetuned_model: this.settings.finetuned_model || 'false',
 | |
|         };
 | |
|         // Separate property for dynamically updated settings from the server
 | |
|         this.dynamicSettings = {
 | |
|             modelsAvailable: [],
 | |
|             currentModel: '',
 | |
|             deepspeed_available: false,
 | |
|             deepspeed_enabled: false,
 | |
|             lowvram_capable: false,
 | |
|             lowvram_enabled: false,
 | |
|         };
 | |
|         this.rvcVoices = []; // Initialize rvcVoices as an empty array
 | |
|     }
 | |
|     ready = false;
 | |
|     voices = [];
 | |
|     separator = '. ';
 | |
|     audioElement = document.createElement('audio');
 | |
| 
 | |
|     languageLabels = {
 | |
|         'Arabic': 'ar',
 | |
|         'Brazilian Portuguese': 'pt',
 | |
|         'Chinese': 'zh-cn',
 | |
|         'Czech': 'cs',
 | |
|         'Dutch': 'nl',
 | |
|         'English': 'en',
 | |
|         'French': 'fr',
 | |
|         'German': 'de',
 | |
|         'Italian': 'it',
 | |
|         'Polish': 'pl',
 | |
|         'Russian': 'ru',
 | |
|         'Spanish': 'es',
 | |
|         'Turkish': 'tr',
 | |
|         'Japanese': 'ja',
 | |
|         'Korean': 'ko',
 | |
|         'Hungarian': 'hu',
 | |
|         'Hindi': 'hi',
 | |
|     };
 | |
| 
 | |
|     get settingsHtml() {
 | |
|         // HTML template literals can trigger ESLint quotes warnings when quotes are used in HTML attributes.
 | |
|         // Disabling quotes rule for this one line as it's a false positive with HTML template literals.
 | |
|         // eslint-disable-next-line quotes
 | |
|         let html = `<div class="at-settings-separator">AllTalk V2 Settings</div>`;
 | |
| 
 | |
|         html += `<div class='at-settings-row'>
 | |
|         <div class='at-settings-option'>
 | |
|             <label for='at_generation_method'>AllTalk TTS Generation Method</label>
 | |
|                 <select id='at_generation_method'>
 | |
|                 <option value='standard_generation'>Standard Audio Generation (AT Narrator - Optional)</option>
 | |
|                 <option value='streaming_enabled'>Streaming Audio Generation (AT Narrator - Disabled)</option>
 | |
|         </select>
 | |
|         </div>
 | |
|         </div>`;
 | |
| 
 | |
|         html += `<div class='at-settings-row'>
 | |
|         <div class='at-settings-option'>
 | |
|             <label for='at_narrator_enabled'>AT Narrator</label>
 | |
|                 <select id='at_narrator_enabled'>
 | |
|                 <option value='true'>Enabled</option>
 | |
|                 <option value='silent'>Enabled (Silenced)</option>
 | |
|                 <option value='false'>Disabled</option>
 | |
|         </select>
 | |
|         </div>
 | |
| 
 | |
|         <div class='at-settings-option'>
 | |
|             <label for='at_narrator_text_not_inside'>Text Not Inside * or " is</label>
 | |
|                 <select id='at_narrator_text_not_inside'>
 | |
|                 <option value='character'>Character</option>
 | |
|                 <option value='narrator'>Narrator</option>
 | |
|                 <option value='silent'>Silent</option>
 | |
|         </select>
 | |
|         </div>
 | |
|     </div>`;
 | |
| 
 | |
|         html += `<div class='at-settings-row'>
 | |
|         <div class='at-settings-option'>
 | |
|             <label for='narrator_voice'>Narrator Voice</label>
 | |
|             <select id='narrator_voice'>`;
 | |
|         if (this.voices) {
 | |
|             for (let voice of this.voices) {
 | |
|                 html += `<option value='${voice.voice_id}'>${voice.name}</option>`;
 | |
|             }
 | |
|         }
 | |
|         html += `</select>
 | |
|         </div>
 | |
|         <div class='at-settings-option'>
 | |
|             <label for='language_options'>Language</label>
 | |
|             <select id='language_options'>`;
 | |
|         for (let language in this.languageLabels) {
 | |
|             html += `<option value='${this.languageLabels[language]}' ${this.languageLabels[language] === this.settings?.language ? 'selected="selected"' : ''}>${language}</option>`;
 | |
|         }
 | |
|         html += `</select>
 | |
|         </div>
 | |
|     </div>`;
 | |
| 
 | |
|         html += `<div class='at-settings-row'>
 | |
|     <div class='at-settings-option'>
 | |
|         <label for='rvc_character_voice'>RVC Character</label>
 | |
|         <select id='rvc_character_voice'>`;
 | |
|         if (this.rvcVoices) {
 | |
|             for (let rvccharvoice of this.rvcVoices) {
 | |
|                 html += `<option value='${rvccharvoice.voice_id}'>${rvccharvoice.name}</option>`;
 | |
|             }
 | |
|         }
 | |
|         html += `</select>
 | |
|     </div>
 | |
|     <div class='at-settings-option'>
 | |
|         <label for='rvc_narrator_voice'>RVC Narrator</label>
 | |
|         <select id='rvc_narrator_voice'>`;
 | |
|         if (this.rvcVoices) {
 | |
|             for (let rvcnarrvoice of this.rvcVoices) {
 | |
|                 html += `<option value='${rvcnarrvoice.voice_id}'>${rvcnarrvoice.name}</option>`;
 | |
|             }
 | |
|         }
 | |
|         html += `</select>
 | |
|     </div>
 | |
| </div>`;
 | |
| 
 | |
|         html += `<div class='at-settings-row'>
 | |
|         <div class='at-settings-option'>
 | |
|             <label for='rvc_character_pitch'>RVC Character Pitch</label>
 | |
|             <select id='rvc_character_pitch'>`;
 | |
|         for (let i = -24; i <= 24; i++) {
 | |
|             const selected = i === 0 ? 'selected="selected"' : '';
 | |
|             html += `<option value='${i}' ${selected}>${i}</option>`;
 | |
|         }
 | |
|         html += `</select>
 | |
|         </div>
 | |
|         <div class='at-settings-option'>
 | |
|             <label for='rvc_narrator_pitch'>RVC Narrator Pitch</label>
 | |
|             <select id='rvc_narrator_pitch'>`;
 | |
|         for (let i = -24; i <= 24; i++) {
 | |
|             const selected = i === 0 ? 'selected="selected"' : '';
 | |
|             html += `<option value='${i}' ${selected}>${i}</option>`;
 | |
|         }
 | |
|         html += `</select>
 | |
|         </div>
 | |
|     </div>`;
 | |
| 
 | |
|         html += `<div class='at-model-endpoint-row'>
 | |
|         <div class='at-model-option'>
 | |
|         <label for='switch_model'>Switch Model</label>
 | |
|         <select id='switch_model'>
 | |
|             <!-- Options will be dynamically populated -->
 | |
|         </select>
 | |
|         </div>
 | |
| 
 | |
|         <div class='at-endpoint-option'>
 | |
|             <label for='at_server'>AllTalk Endpoint:</label>
 | |
|             <input id='at_server' type='text' class='text_pole' maxlength='80' value='${this.settings.provider_endpoint}'/>
 | |
|         </div>
 | |
|    </div>`;
 | |
| 
 | |
|         html += `<div class='at-settings-row'>
 | |
|         <div class='at-settings-option'>
 | |
|             <label for='server_version'>AllTalk Server Version</label>
 | |
|             <select id='server_version'>
 | |
|                 <option value='v1'>AllTalk V1</option>
 | |
|                 <option value='v2'>AllTalk V2</option>
 | |
|             </select>
 | |
|         </div>
 | |
|     </div>`;
 | |
| 
 | |
|         html += `<div class='at-model-endpoint-row'>
 | |
|     <div class='at-settings-option'>
 | |
|         <label for='low_vram'>Low VRAM</label>
 | |
|         <input id='low_vram' type='checkbox'/>
 | |
|     </div>
 | |
|     <div class='at-settings-option'>
 | |
|         <label for='deepspeed'>DeepSpeed</label>
 | |
|         <input id='deepspeed' type='checkbox'/>
 | |
|     </div>
 | |
|     <div class='at-settings-option status-option'>
 | |
|         <span>Status: <span id='status_info'>Ready</span></span>
 | |
|     </div>
 | |
|     <div class='at-settings-option empty-option'>
 | |
|         <!-- This div remains empty for spacing -->
 | |
|     </div>
 | |
| </div>`;
 | |
| 
 | |
|         html += `<div class='at-website-row'>
 | |
|         <div class='at-website-option'>
 | |
|         <span>AllTalk V2<a target='_blank' href='${this.settings.provider_endpoint}'>Config & Docs</a>.</span>
 | |
|     </div>
 | |
| 
 | |
|     <div class='at-website-option'>
 | |
|         <span>AllTalk <a target='_blank' href='https://github.com/erew123/alltalk_tts/'>Website</a>.</span>
 | |
|     </div>
 | |
| </div>`;
 | |
| 
 | |
|         html += `<div class='at-website-row'>
 | |
| <div class='at-website-option'>
 | |
| <span>- If you <strong>change your TTS engine</strong> in AllTalk, you will need to <strong>Reload</strong> (button above) and re-select your voices.</span><br><br>
 | |
| <span>- Assuming the server is <strong>Status: Ready</strong>, most problems will be resolved by hitting Reload and selecting voices that match the loaded TTS engine.</span><br><br>
 | |
| <span>- <strong>Text-generation-webui</strong> users - Uncheck <strong>Enable TTS</strong> in the TGWUI interface, or you will hear 2x voices and file names being generated.</span>
 | |
| </div>
 | |
| </div>`;
 | |
| 
 | |
|         return html;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //#################//
 | |
|     // Startup ST & AT //
 | |
|     //#################//
 | |
| 
 | |
|     async loadSettings(settings) {
 | |
|         updateStatus('Offline');
 | |
| 
 | |
|         if (Object.keys(settings).length === 0) {
 | |
|             console.info('Using default AllTalk TTS Provider settings');
 | |
|         } else {
 | |
|             // Populate settings with provided values, ignoring server-provided settings
 | |
|             for (const key in settings) {
 | |
|                 if (key in this.settings) {
 | |
|                     this.settings[key] = settings[key];
 | |
|                 } else {
 | |
|                     console.debug(`Ignoring non-user-configurable setting: ${key}`);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Update UI elements to reflect the loaded settings
 | |
|         $('#at_server').val(this.settings.provider_endpoint);
 | |
|         $('#language_options').val(this.settings.language);
 | |
|         $('#at_generation_method').val(this.settings.at_generation_method);
 | |
|         $('#at_narrator_enabled').val(this.settings.narrator_enabled);
 | |
|         $('#at_narrator_text_not_inside').val(this.settings.at_narrator_text_not_inside);
 | |
|         $('#narrator_voice').val(this.settings.narrator_voice_gen);
 | |
|         $('#rvc_character_voice').val(this.settings.rvc_character_voice);
 | |
|         $('#rvc_narrator_voice').val(this.settings.rvc_narrator_voice);
 | |
|         $('#rvc_character_pitch').val(this.settings.rvc_character_pitch);
 | |
|         $('#rvc_narrator_pitch').val(this.settings.rvc_narrator_pitch);
 | |
| 
 | |
|         console.debug('AllTalkTTS: Settings loaded');
 | |
|         try {
 | |
|             // Check if TTS provider is ready
 | |
|             await this.checkReady();
 | |
|             await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server
 | |
|             await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready
 | |
|             await this.fetchRvcVoiceObjects(); // Fetch RVC voices
 | |
|             this.updateNarratorVoicesDropdown();
 | |
|             this.updateLanguageDropdown();
 | |
|             this.setupEventListeners();
 | |
|             this.applySettingsToHTML();
 | |
|             updateStatus('Ready');
 | |
|         } catch (error) {
 | |
|             console.error('Error loading settings:', error);
 | |
|             updateStatus('Offline');
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     applySettingsToHTML() {
 | |
|         const narratorVoiceSelect = document.getElementById('narrator_voice');
 | |
|         const atNarratorSelect = document.getElementById('at_narrator_enabled');
 | |
|         const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside');
 | |
|         const generationMethodSelect = document.getElementById('at_generation_method');
 | |
|         this.settings.narrator_voice = this.settings.narrator_voice_gen;
 | |
|         // Apply settings to Narrator Voice dropdown
 | |
|         if (narratorVoiceSelect && this.settings.narrator_voice) {
 | |
|             narratorVoiceSelect.value = this.settings.narrator_voice; // Remove the parentheses
 | |
|         }
 | |
|         // Apply settings to AT Narrator Enabled dropdown
 | |
|         if (atNarratorSelect) {
 | |
|             const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks');
 | |
|             const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted');
 | |
|             const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues');
 | |
|             if (this.settings.narrator_enabled) {
 | |
|                 ttsPassAsterisksCheckbox.checked = false;
 | |
|                 $('#tts_pass_asterisks').click();
 | |
|                 $('#tts_pass_asterisks').trigger('change');
 | |
|             }
 | |
|             if (!this.settings.narrator_enabled) {
 | |
|                 ttsPassAsterisksCheckbox.checked = true;
 | |
|                 $('#tts_pass_asterisks').click();
 | |
|                 $('#tts_pass_asterisks').trigger('change');
 | |
|             }
 | |
|             if (this.settings.narrator_enabled) {
 | |
|                 ttsNarrateQuotedCheckbox.checked = true;
 | |
|                 ttsNarrateDialoguesCheckbox.checked = true;
 | |
|                 $('#tts_narrate_quoted').click();
 | |
|                 $('#tts_narrate_quoted').trigger('change');
 | |
|                 $('#tts_narrate_dialogues').click();
 | |
|                 $('#tts_narrate_dialogues').trigger('change');
 | |
|             }
 | |
|             atNarratorSelect.value = this.settings.narrator_enabled.toString();
 | |
|             this.settings.narrator_enabled = this.settings.narrator_enabled.toString();
 | |
|         }
 | |
|         const languageSelect = document.getElementById('language_options');
 | |
|         if (languageSelect && this.settings.language) {
 | |
|             languageSelect.value = this.settings.language;
 | |
|         }
 | |
|         if (textNotInsideSelect && this.settings.text_not_inside) {
 | |
|             textNotInsideSelect.value = this.settings.text_not_inside;
 | |
|             this.settings.at_narrator_text_not_inside = this.settings.text_not_inside;
 | |
|         }
 | |
|         if (generationMethodSelect && this.settings.at_generation_method) {
 | |
|             generationMethodSelect.value = this.settings.at_generation_method;
 | |
|         }
 | |
|         const isStreamingEnabled = this.settings.at_generation_method === 'streaming_enabled';
 | |
|         if (isStreamingEnabled) {
 | |
|             if (atNarratorSelect) atNarratorSelect.disabled = true;
 | |
|             if (textNotInsideSelect) textNotInsideSelect.disabled = true;
 | |
|             if (narratorVoiceSelect) narratorVoiceSelect.disabled = true;
 | |
|         } else {
 | |
|             if (atNarratorSelect) atNarratorSelect.disabled = false;
 | |
|             if (textNotInsideSelect) textNotInsideSelect.disabled = !this.settings.narrator_enabled;
 | |
|             if (narratorVoiceSelect) narratorVoiceSelect.disabled = !this.settings.narrator_enabled;
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //##############################//
 | |
|     // Check AT Server is Available //
 | |
|     //##############################//
 | |
| 
 | |
|     async checkReady() {
 | |
|         try {
 | |
|             const response = await fetch(`${this.settings.provider_endpoint}/api/ready`);
 | |
|             // Check if the HTTP request was successful
 | |
|             if (!response.ok) {
 | |
|                 throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`);
 | |
|             }
 | |
|             const statusText = await response.text();
 | |
|             // Check if the response is 'Ready'
 | |
|             if (statusText === 'Ready') {
 | |
|                 this.ready = true; // Set the ready flag to true
 | |
|                 console.log('TTS service is ready.');
 | |
|             } else {
 | |
|                 this.ready = false;
 | |
|                 console.log('TTS service is not ready.');
 | |
|             }
 | |
|         } catch (error) {
 | |
|             console.error('Error checking TTS service readiness:', error);
 | |
|             this.ready = false; // Ensure ready flag is set to false in case of error
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //######################//
 | |
|     // Get Available Voices //
 | |
|     //######################//
 | |
| 
 | |
|     async fetchTtsVoiceObjects() {
 | |
|         const response = await fetch(`${this.settings.provider_endpoint}/api/voices`);
 | |
|         if (!response.ok) {
 | |
|             const errorText = await response.text();
 | |
|             throw new Error(`HTTP ${response.status}: ${errorText}`);
 | |
|         }
 | |
|         const data = await response.json();
 | |
|         const voices = data.voices.map(filename => {
 | |
|             return {
 | |
|                 name: filename,
 | |
|                 voice_id: filename,
 | |
|                 preview_url: null, // Preview URL will be dynamically generated
 | |
|                 lang: 'en', // Default language
 | |
|             };
 | |
|         });
 | |
|         this.voices = voices; // Assign to the class property
 | |
|         return voices; // Also return this list
 | |
|     }
 | |
| 
 | |
|     async fetchRvcVoiceObjects() {
 | |
|         if (this.settings.server_version !== 'v2') {
 | |
|             console.log('Skipping RVC voices fetch for V1 server');
 | |
|             return [];
 | |
|         }
 | |
| 
 | |
|         console.log('Fetching RVC Voices');
 | |
|         try {
 | |
|             const response = await fetch(`${this.settings.provider_endpoint}/api/rvcvoices`);
 | |
|             if (!response.ok) {
 | |
|                 const errorText = await response.text();
 | |
|                 console.error('Error text:', errorText);
 | |
|                 throw new Error(`HTTP ${response.status}: ${errorText}`);
 | |
|             }
 | |
| 
 | |
|             const data = await response.json();
 | |
|             if (!data || !data.rvcvoices) {
 | |
|                 console.error('Invalid data format:', data);
 | |
|                 throw new Error('Invalid data format received from /api/rvcvoices');
 | |
|             }
 | |
| 
 | |
|             const voices = data.rvcvoices.map(filename => {
 | |
|                 return {
 | |
|                     name: filename,
 | |
|                     voice_id: filename,
 | |
|                 };
 | |
|             });
 | |
| 
 | |
|             console.log('RVC voices:', voices);
 | |
|             this.rvcVoices = voices; // Assign to the class property
 | |
|             this.updateRvcVoiceDropdowns(); // Update UI after fetching voices
 | |
|             return voices; // Also return this list
 | |
|         } catch (error) {
 | |
|             console.error('Error fetching RVC voices:', error);
 | |
|             this.rvcVoices = [{ name: 'Disabled', voice_id: 'Disabled' }]; // Set default on error
 | |
|             throw error;
 | |
|         } finally {
 | |
|             // Ensure dropdowns are updated even if there was an error
 | |
|             this.updateRvcVoiceDropdowns();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //##########################################//
 | |
|     // Get Current AT Server Config & Update ST //
 | |
|     //##########################################//
 | |
| 
 | |
|     async updateSettingsFromServer() {
 | |
|         try {
 | |
|             const response = await fetch(`${this.settings.provider_endpoint}/api/currentsettings`);
 | |
|             if (!response.ok) {
 | |
|                 throw new Error(`Failed to fetch current settings: ${response.statusText}`);
 | |
|             }
 | |
|             const currentSettings = await response.json();
 | |
|             currentSettings.models_available.sort((a, b) => a.name.localeCompare(b.name));
 | |
| 
 | |
|             this.settings.enginesAvailable = currentSettings.engines_available;
 | |
|             this.settings.currentEngineLoaded = currentSettings.current_engine_loaded;
 | |
|             this.settings.modelsAvailable = currentSettings.models_available;
 | |
|             this.settings.currentModel = currentSettings.current_model_loaded;
 | |
|             this.settings.deepspeed_capable = currentSettings.deepspeed_capable;
 | |
|             this.settings.deepspeed_available = currentSettings.deepspeed_available;
 | |
|             this.settings.deepspeed_enabled = currentSettings.deepspeed_enabled;
 | |
|             this.settings.lowvram_capable = currentSettings.lowvram_capable;
 | |
|             this.settings.lowvram_enabled = currentSettings.lowvram_enabled;
 | |
| 
 | |
|             await this.fetchRvcVoiceObjects(); // Fetch RVC voices
 | |
| 
 | |
|             this.updateModelDropdown();
 | |
|             this.updateCheckboxes();
 | |
|             this.updateRvcVoiceDropdowns(); // Update the RVC voice dropdowns
 | |
|         } catch (error) {
 | |
|             console.error(`Error updating settings from server: ${error}`);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     updateRvcVoiceDropdowns() {
 | |
|         // Handle all RVC-related elements
 | |
|         const rvcElements = document.querySelectorAll('.rvc-setting');
 | |
|         const isV2 = this.settings.server_version === 'v2';
 | |
| 
 | |
|         rvcElements.forEach(element => {
 | |
|             element.style.display = isV2 ? 'block' : 'none';
 | |
|         });
 | |
| 
 | |
|         // Update and disable/enable character voice dropdown
 | |
|         const rvcCharacterVoiceSelect = document.getElementById('rvc_character_voice');
 | |
|         if (rvcCharacterVoiceSelect) {
 | |
|             rvcCharacterVoiceSelect.disabled = !isV2;
 | |
|             if (this.rvcVoices) {
 | |
|                 rvcCharacterVoiceSelect.innerHTML = '';
 | |
|                 for (let voice of this.rvcVoices) {
 | |
|                     const option = document.createElement('option');
 | |
|                     option.value = voice.voice_id;
 | |
|                     option.textContent = voice.name;
 | |
|                     if (voice.voice_id === this.settings.rvc_character_voice) {
 | |
|                         option.selected = true;
 | |
|                     }
 | |
|                     rvcCharacterVoiceSelect.appendChild(option);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Update and disable/enable narrator voice dropdown
 | |
|         const rvcNarratorVoiceSelect = document.getElementById('rvc_narrator_voice');
 | |
|         if (rvcNarratorVoiceSelect) {
 | |
|             rvcNarratorVoiceSelect.disabled = !isV2;
 | |
|             if (this.rvcVoices) {
 | |
|                 rvcNarratorVoiceSelect.innerHTML = '';
 | |
|                 for (let voice of this.rvcVoices) {
 | |
|                     const option = document.createElement('option');
 | |
|                     option.value = voice.voice_id;
 | |
|                     option.textContent = voice.name;
 | |
|                     if (voice.voice_id === this.settings.rvc_narrator_voice) {
 | |
|                         option.selected = true;
 | |
|                     }
 | |
|                     rvcNarratorVoiceSelect.appendChild(option);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Update pitch inputs
 | |
|         const characterPitch = document.getElementById('rvc_character_pitch');
 | |
|         if (characterPitch) {
 | |
|             characterPitch.disabled = !isV2;
 | |
|         }
 | |
| 
 | |
|         const narratorPitch = document.getElementById('rvc_narrator_pitch');
 | |
|         if (narratorPitch) {
 | |
|             narratorPitch.disabled = !isV2;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //###################################################//
 | |
|     // Get Current AT Server Config & Update ST (Models) //
 | |
|     //###################################################//
 | |
| 
 | |
|     updateModelDropdown() {
 | |
|         const modelSelect = document.getElementById('switch_model');
 | |
|         if (modelSelect) {
 | |
|             modelSelect.innerHTML = ''; // Clear existing options
 | |
|             this.settings.modelsAvailable.forEach(model => {
 | |
|                 const option = document.createElement('option');
 | |
|                 option.value = model.name;
 | |
|                 option.textContent = model.name; // Use model name directly
 | |
|                 option.selected = model.name === this.settings.currentModel;
 | |
|                 modelSelect.appendChild(option);
 | |
|             });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //#######################################################//
 | |
|     // Get Current AT Server Config & Update ST (DS and LVR) //
 | |
|     //#######################################################//
 | |
| 
 | |
|     updateCheckboxes() {
 | |
|         const deepspeedCheckbox = document.getElementById('deepspeed');
 | |
|         const lowVramCheckbox = document.getElementById('low_vram');
 | |
| 
 | |
|         // Handle DeepSpeed checkbox
 | |
|         if (deepspeedCheckbox) {
 | |
|             if (this.settings.deepspeed_capable) {
 | |
|                 // If TTS engine is capable of using DeepSpeed
 | |
|                 deepspeedCheckbox.disabled = !this.settings.deepspeed_available;
 | |
|                 this.settings.deepspeed_enabled = this.settings.deepspeed_available && this.settings.deepspeed_enabled;
 | |
|             } else {
 | |
|                 // If TTS engine is NOT capable of using DeepSpeed
 | |
|                 deepspeedCheckbox.disabled = true;
 | |
|                 this.settings.deepspeed_enabled = false;
 | |
|             }
 | |
|             deepspeedCheckbox.checked = this.settings.deepspeed_enabled;
 | |
|         }
 | |
| 
 | |
|         // Handle Low VRAM checkbox
 | |
|         if (lowVramCheckbox) {
 | |
|             if (this.settings.lowvram_capable) {
 | |
|                 // If TTS engine is capable of low VRAM
 | |
|                 lowVramCheckbox.disabled = false;
 | |
|             } else {
 | |
|                 // If TTS engine is NOT capable of low VRAM
 | |
|                 lowVramCheckbox.disabled = true;
 | |
|                 this.settings.lowvram_enabled = false;
 | |
|             }
 | |
|             lowVramCheckbox.checked = this.settings.lowvram_enabled;
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //###############################################################//
 | |
|     // Get Current AT Server Config & Update ST (AT Narrator Voices) //
 | |
|     //###############################################################//
 | |
| 
 | |
|     updateNarratorVoicesDropdown() {
 | |
|         const narratorVoiceSelect = document.getElementById('narrator_voice');
 | |
|         if (narratorVoiceSelect && this.voices) {
 | |
|             // Clear existing options
 | |
|             narratorVoiceSelect.innerHTML = '';
 | |
|             // Add new options
 | |
|             for (let voice of this.voices) {
 | |
|                 const option = document.createElement('option');
 | |
|                 option.value = voice.voice_id;
 | |
|                 option.textContent = voice.name;
 | |
|                 narratorVoiceSelect.appendChild(option);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //######################################################//
 | |
|     // Get Current AT Server Config & Update ST (Languages) //
 | |
|     //######################################################//
 | |
| 
 | |
|     updateLanguageDropdown() {
 | |
|         const languageSelect = document.getElementById('language_options');
 | |
|         if (languageSelect) {
 | |
|             // Ensure default language is set
 | |
|             this.settings.language = this.settings.language || 'en';
 | |
| 
 | |
|             languageSelect.innerHTML = '';
 | |
|             for (let language in this.languageLabels) {
 | |
|                 const option = document.createElement('option');
 | |
|                 option.value = this.languageLabels[language];
 | |
|                 option.textContent = language;
 | |
|                 if (this.languageLabels[language] === this.settings.language) {
 | |
|                     option.selected = true;
 | |
|                 }
 | |
|                 languageSelect.appendChild(option);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //########################################//
 | |
|     // Start AT TTS extenstion page listeners //
 | |
|     //########################################//
 | |
| 
 | |
|     setupEventListeners() {
 | |
|         // Define the event handler function
 | |
|         const onModelSelectChange = async (event) => {
 | |
|             console.log('Model select change event triggered');
 | |
|             const selectedModel = event.target.value;
 | |
|             console.log(`Selected model: ${selectedModel}`);
 | |
|             updateStatus('Processing');
 | |
|             try {
 | |
|                 const response = await fetch(`${this.settings.provider_endpoint}/api/reload?tts_method=${encodeURIComponent(selectedModel)}`, {
 | |
|                     method: 'POST',
 | |
|                 });
 | |
|                 if (!response.ok) {
 | |
|                     throw new Error(`HTTP Error: ${response.status}`);
 | |
|                 }
 | |
|                 const data = await response.json();
 | |
|                 console.log('POST response data:', data);
 | |
|                 updateStatus('Ready');
 | |
|             } catch (error) {
 | |
|                 console.error('POST request error:', error);
 | |
|                 updateStatus('Error');
 | |
|             }
 | |
|         };
 | |
| 
 | |
|         // Switch Model Listener with debounce
 | |
|         const modelSelect = document.getElementById('switch_model');
 | |
|         if (modelSelect) {
 | |
|             const debouncedModelSelectChange = debounce(onModelSelectChange, 1400);
 | |
|             modelSelect.addEventListener('change', debouncedModelSelectChange);
 | |
|         }
 | |
| 
 | |
|         // AllTalk Server version change listener
 | |
|         const serverVersionSelect = document.getElementById('server_version');
 | |
|         if (serverVersionSelect) {
 | |
|             serverVersionSelect.addEventListener('change', async (event) => {
 | |
|                 this.settings.server_version = event.target.value;
 | |
|                 if (event.target.value === 'v2') {
 | |
|                     await this.fetchRvcVoiceObjects();
 | |
|                 }
 | |
|                 this.updateRvcVoiceDropdowns();
 | |
|                 this.onSettingsChange();
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         // RVC Voice and Pitch listeners
 | |
|         const rvcCharacterVoiceSelect = document.getElementById('rvc_character_voice');
 | |
|         if (rvcCharacterVoiceSelect) {
 | |
|             rvcCharacterVoiceSelect.addEventListener('change', (event) => {
 | |
|                 this.settings.rvccharacter_voice_gen = event.target.value;
 | |
|                 this.onSettingsChange();
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         const rvcNarratorVoiceSelect = document.getElementById('rvc_narrator_voice');
 | |
|         if (rvcNarratorVoiceSelect) {
 | |
|             rvcNarratorVoiceSelect.addEventListener('change', (event) => {
 | |
|                 this.settings.rvcnarrator_voice_gen = event.target.value;
 | |
|                 this.onSettingsChange();
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         const rvcCharacterPitchSelect = document.getElementById('rvc_character_pitch');
 | |
|         if (rvcCharacterPitchSelect) {
 | |
|             rvcCharacterPitchSelect.addEventListener('change', (event) => {
 | |
|                 this.settings.rvc_character_pitch = event.target.value;
 | |
|                 this.onSettingsChange();
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         const rvcNarratorPitchSelect = document.getElementById('rvc_narrator_pitch');
 | |
|         if (rvcNarratorPitchSelect) {
 | |
|             rvcNarratorPitchSelect.addEventListener('change', (event) => {
 | |
|                 this.settings.rvc_narrator_pitch = event.target.value;
 | |
|                 this.onSettingsChange();
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         // DeepSpeed Listener
 | |
|         const deepspeedCheckbox = document.getElementById('deepspeed');
 | |
|         if (deepspeedCheckbox) {
 | |
|             const handleDeepSpeedChange = async (event) => {
 | |
|                 const deepSpeedValue = event.target.checked ? 'True' : 'False';
 | |
|                 updateStatus('Processing');
 | |
|                 try {
 | |
|                     const response = await fetch(`${this.settings.provider_endpoint}/api/deepspeed?new_deepspeed_value=${deepSpeedValue}`, {
 | |
|                         method: 'POST',
 | |
|                     });
 | |
|                     if (!response.ok) {
 | |
|                         throw new Error(`HTTP Error: ${response.status}`);
 | |
|                     }
 | |
|                     const data = await response.json();
 | |
|                     console.log('POST response data:', data);
 | |
|                     updateStatus('Ready');
 | |
|                 } catch (error) {
 | |
|                     console.error('POST request error:', error);
 | |
|                     updateStatus('Error');
 | |
|                 }
 | |
|             };
 | |
| 
 | |
|             const debouncedHandleDeepSpeedChange = debounce(handleDeepSpeedChange, 300);
 | |
|             deepspeedCheckbox.addEventListener('change', debouncedHandleDeepSpeedChange);
 | |
|         }
 | |
| 
 | |
|         // Low VRAM Listener
 | |
|         const lowVramCheckbox = document.getElementById('low_vram');
 | |
|         if (lowVramCheckbox) {
 | |
|             const handleLowVramChange = async (event) => {
 | |
|                 const lowVramValue = event.target.checked ? 'True' : 'False';
 | |
|                 updateStatus('Processing');
 | |
|                 try {
 | |
|                     const response = await fetch(`${this.settings.provider_endpoint}/api/lowvramsetting?new_low_vram_value=${lowVramValue}`, {
 | |
|                         method: 'POST',
 | |
|                     });
 | |
|                     if (!response.ok) {
 | |
|                         throw new Error(`HTTP Error: ${response.status}`);
 | |
|                     }
 | |
|                     const data = await response.json();
 | |
|                     console.log('POST response data:', data);
 | |
|                     updateStatus('Ready');
 | |
|                 } catch (error) {
 | |
|                     console.error('POST request error:', error);
 | |
|                     updateStatus('Error');
 | |
|                 }
 | |
|             };
 | |
| 
 | |
|             const debouncedHandleLowVramChange = debounce(handleLowVramChange, 300);
 | |
|             lowVramCheckbox.addEventListener('change', debouncedHandleLowVramChange);
 | |
|         }
 | |
| 
 | |
|         // Other listeners without debounce since they don't need it
 | |
|         const narratorVoiceSelect = document.getElementById('narrator_voice');
 | |
|         if (narratorVoiceSelect) {
 | |
|             narratorVoiceSelect.addEventListener('change', (event) => {
 | |
|                 this.settings.narrator_voice_gen = `${event.target.value}`;
 | |
|                 this.onSettingsChange();
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside');
 | |
|         if (textNotInsideSelect) {
 | |
|             textNotInsideSelect.addEventListener('change', (event) => {
 | |
|                 this.settings.text_not_inside = event.target.value;
 | |
|                 this.onSettingsChange();
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         // AT Narrator Dropdown Listener
 | |
|         const atNarratorSelect = document.getElementById('at_narrator_enabled');
 | |
|         const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks');
 | |
|         const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted');
 | |
|         const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues');
 | |
| 
 | |
|         if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) {
 | |
|             atNarratorSelect.addEventListener('change', (event) => {
 | |
|                 const narratorOption = event.target.value;
 | |
|                 this.settings.narrator_enabled = narratorOption;
 | |
| 
 | |
|                 const isNarratorDisabled = narratorOption === 'false';
 | |
|                 textNotInsideSelect.disabled = isNarratorDisabled;
 | |
|                 narratorVoiceSelect.disabled = isNarratorDisabled;
 | |
| 
 | |
|                 if (narratorOption === 'true') {
 | |
|                     ttsPassAsterisksCheckbox.checked = false;
 | |
|                     $('#tts_pass_asterisks').click();
 | |
|                     $('#tts_pass_asterisks').trigger('change');
 | |
|                     ttsNarrateQuotedCheckbox.checked = true;
 | |
|                     ttsNarrateDialoguesCheckbox.checked = true;
 | |
|                     $('#tts_narrate_quoted').click();
 | |
|                     $('#tts_narrate_quoted').trigger('change');
 | |
|                     $('#tts_narrate_dialogues').click();
 | |
|                     $('#tts_narrate_dialogues').trigger('change');
 | |
|                 } else if (narratorOption === 'silent') {
 | |
|                     ttsPassAsterisksCheckbox.checked = false;
 | |
|                     $('#tts_pass_asterisks').click();
 | |
|                     $('#tts_pass_asterisks').trigger('change');
 | |
|                 } else {
 | |
|                     ttsPassAsterisksCheckbox.checked = true;
 | |
|                     $('#tts_pass_asterisks').click();
 | |
|                     $('#tts_pass_asterisks').trigger('change');
 | |
|                 }
 | |
| 
 | |
|                 this.onSettingsChange();
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         // Event Listener for AT Generation Method Dropdown
 | |
|         const atGenerationMethodSelect = document.getElementById('at_generation_method');
 | |
|         if (atGenerationMethodSelect) {
 | |
|             atGenerationMethodSelect.addEventListener('change', (event) => {
 | |
|                 const selectedMethod = event.target.value;
 | |
| 
 | |
|                 if (selectedMethod === 'streaming_enabled') {
 | |
|                     // Disable and unselect AT Narrator
 | |
|                     atNarratorSelect.disabled = true;
 | |
|                     atNarratorSelect.value = 'false';
 | |
|                     textNotInsideSelect.disabled = true;
 | |
|                     narratorVoiceSelect.disabled = true;
 | |
|                 } else if (selectedMethod === 'standard_generation') {
 | |
|                     // Enable AT Narrator
 | |
|                     atNarratorSelect.disabled = false;
 | |
|                 }
 | |
|                 this.settings.at_generation_method = selectedMethod;
 | |
|                 this.onSettingsChange();
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         // Language Dropdown Listener
 | |
|         const languageSelect = document.getElementById('language_options');
 | |
|         if (languageSelect) {
 | |
|             languageSelect.addEventListener('change', (event) => {
 | |
|                 this.settings.language = event.target.value;
 | |
|                 this.onSettingsChange();
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         // AllTalk Endpoint Input Listener
 | |
|         const atServerInput = document.getElementById('at_server');
 | |
|         if (atServerInput) {
 | |
|             atServerInput.addEventListener('input', (event) => {
 | |
|                 this.settings.provider_endpoint = event.target.value;
 | |
|                 this.onSettingsChange();
 | |
|             });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //#############################//
 | |
|     // Store ST interface settings //
 | |
|     //#############################//
 | |
| 
 | |
|     onSettingsChange() {
 | |
|         // Update settings based on the UI elements
 | |
|         //this.settings.provider_endpoint = $('#at_server').val();
 | |
|         this.settings.language = $('#language_options').val();
 | |
|         //this.settings.voiceMap = $('#voicemap').val();
 | |
|         this.settings.at_generation_method = $('#at_generation_method').val();
 | |
|         this.settings.narrator_enabled = $('#at_narrator_enabled').val();
 | |
|         this.settings.at_narrator_text_not_inside = $('#at_narrator_text_not_inside').val();
 | |
|         this.settings.rvc_character_voice = $('#rvc_character_voice').val();
 | |
|         this.settings.rvc_narrator_voice = $('#rvc_narrator_voice').val();
 | |
|         this.settings.rvc_character_pitch = $('#rvc_character_pitch').val();
 | |
|         this.settings.rvc_narrator_pitch = $('#rvc_narrator_pitch').val();
 | |
|         this.settings.narrator_voice_gen = $('#narrator_voice').val();
 | |
|         // Save the updated settings
 | |
|         saveTtsProviderSettings();
 | |
|     }
 | |
| 
 | |
|     //#########################//
 | |
|     // ST Handle Reload button //
 | |
|     //#########################//
 | |
| 
 | |
|     async onRefreshClick() {
 | |
|         try {
 | |
|             updateStatus('Processing'); // Set status to Processing while refreshing
 | |
|             await this.checkReady(); // Check if the TTS provider is ready
 | |
|             await this.loadSettings(this.settings); // Reload the settings
 | |
|             await this.checkReady(); // Check if the TTS provider is ready
 | |
|             updateStatus(this.ready ? 'Ready' : 'Offline'); // Update the status based on readiness
 | |
|         } catch (error) {
 | |
|             console.error('Error during refresh:', error);
 | |
|             updateStatus('Error'); // Set status to Error in case of failure
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //##################//
 | |
|     // Preview AT Voice //
 | |
|     //##################//
 | |
| 
 | |
|     async previewTtsVoice(voiceName) {
 | |
|         try {
 | |
|             // Prepare data for POST request
 | |
|             const postData = new URLSearchParams();
 | |
|             postData.append('voice', `${voiceName}`);
 | |
| 
 | |
|             // Add RVC parameters for V2 if applicable
 | |
|             if (this.settings.server_version === 'v2' && this.settings.rvc_character_voice !== 'Disabled') {
 | |
|                 postData.append('rvccharacter_voice_gen', this.settings.rvc_character_voice);
 | |
|                 postData.append('rvccharacter_pitch', this.settings.rvc_character_pitch || '0');
 | |
|             }
 | |
| 
 | |
|             // Making the POST request
 | |
|             const response = await fetch(`${this.settings.provider_endpoint}/api/previewvoice/`, {
 | |
|                 method: 'POST',
 | |
|                 headers: {
 | |
|                     'Content-Type': 'application/x-www-form-urlencoded',
 | |
|                 },
 | |
|                 body: postData,
 | |
|             });
 | |
| 
 | |
|             if (!response.ok) {
 | |
|                 const errorText = await response.text();
 | |
|                 console.error('previewTtsVoice Error Response Text:', errorText);
 | |
|                 throw new Error(`HTTP ${response.status}: ${errorText}`);
 | |
|             }
 | |
| 
 | |
|             const data = await response.json();
 | |
|             if (data.output_file_url) {
 | |
|                 // Handle V1/V2 URL differences
 | |
|                 const fullUrl = this.settings.server_version === 'v1'
 | |
|                     ? data.output_file_url
 | |
|                     : `${this.settings.provider_endpoint}${data.output_file_url}`;
 | |
| 
 | |
|                 const audioElement = new Audio(fullUrl);
 | |
|                 audioElement.play().catch(e => console.error('Error playing audio:', e));
 | |
|             } else {
 | |
|                 console.warn('previewTtsVoice No output file URL received in the response');
 | |
|                 throw new Error('No output file URL received in the response');
 | |
|             }
 | |
|         } catch (error) {
 | |
|             console.error('previewTtsVoice Exception caught during preview generation:', error);
 | |
|             throw error;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //#####################//
 | |
|     //  Populate ST voices //
 | |
|     //#####################//
 | |
| 
 | |
|     async getVoice(voiceName, generatePreview = false) {
 | |
|         // Ensure this.voices is populated
 | |
|         if (this.voices.length === 0) {
 | |
|             // Fetch voice objects logic
 | |
|         }
 | |
|         // Find the object where the name matches voiceName
 | |
|         const match = this.voices.find(voice => voice.name === voiceName);
 | |
|         if (!match) {
 | |
|             // Error handling
 | |
|         }
 | |
|         // Generate preview URL only if requested
 | |
|         if (!match.preview_url && generatePreview) {
 | |
|             // Generate preview logic
 | |
|         }
 | |
|         return match; // Return the found voice object
 | |
|     }
 | |
| 
 | |
|     //##########################################//
 | |
|     //  Generate TTS Streaming or call Standard //
 | |
|     //##########################################//
 | |
| 
 | |
|     async generateTts(inputText, voiceId) {
 | |
|         try {
 | |
|             if (this.settings.at_generation_method === 'streaming_enabled') {
 | |
|                 // Construct the streaming URL
 | |
|                 const streamingUrl = `${this.settings.provider_endpoint}/api/tts-generate-streaming?text=${encodeURIComponent(inputText)}&voice=${encodeURIComponent(voiceId)}&language=${encodeURIComponent(this.settings.language)}&output_file=stream_output.wav`;
 | |
|                 console.log('Streaming URL:', streamingUrl);
 | |
| 
 | |
|                 // Return the streaming URL directly
 | |
|                 return streamingUrl;
 | |
|             } else {
 | |
|                 // For standard method
 | |
|                 const outputUrl = await this.fetchTtsGeneration(inputText, voiceId);
 | |
|                 const audioResponse = await fetch(outputUrl);
 | |
|                 if (!audioResponse.ok) {
 | |
|                     throw new Error(`HTTP ${audioResponse.status}: Failed to fetch audio data`);
 | |
|                 }
 | |
|                 return audioResponse; // Return the fetch response directly
 | |
|             }
 | |
|         } catch (error) {
 | |
|             console.error('Error in generateTts:', error);
 | |
|             throw error;
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     //####################//
 | |
|     //  Generate Standard //
 | |
|     //####################//
 | |
| 
 | |
|     async fetchTtsGeneration(inputText, voiceId) {
 | |
|         const requestBody = new URLSearchParams({
 | |
|             'text_input': inputText,
 | |
|             'text_filtering': 'standard',
 | |
|             'character_voice_gen': voiceId,
 | |
|             'narrator_enabled': this.settings.narrator_enabled,
 | |
|             'narrator_voice_gen': this.settings.narrator_voice_gen,
 | |
|             'text_not_inside': this.settings.at_narrator_text_not_inside,
 | |
|             'language': this.settings.language,
 | |
|             'output_file_name': 'st_output',
 | |
|             'output_file_timestamp': 'true',
 | |
|             'autoplay': 'false',
 | |
|             'autoplay_volume': '0.8',
 | |
|         });
 | |
| 
 | |
|         // Add RVC parameters only for V2
 | |
|         if (this.settings.server_version === 'v2') {
 | |
|             if (this.settings.rvc_character_voice !== 'Disabled') {
 | |
|                 requestBody.append('rvccharacter_voice_gen', this.settings.rvc_character_voice);
 | |
|                 requestBody.append('rvccharacter_pitch', this.settings.rvc_character_pitch || '0');
 | |
|             }
 | |
|             if (this.settings.rvc_narrator_voice !== 'Disabled') {
 | |
|                 requestBody.append('rvcnarrator_voice_gen', this.settings.rvc_narrator_voice);
 | |
|                 requestBody.append('rvcnarrator_pitch', this.settings.rvc_narrator_pitch || '0');
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             const response = await doExtrasFetch(
 | |
|                 `${this.settings.provider_endpoint}/api/tts-generate`,
 | |
|                 {
 | |
|                     method: 'POST',
 | |
|                     headers: {
 | |
|                         'Content-Type': 'application/x-www-form-urlencoded',
 | |
|                         'Cache-Control': 'no-cache',
 | |
|                     },
 | |
|                     body: requestBody,
 | |
|                 },
 | |
|             );
 | |
| 
 | |
|             if (!response.ok) {
 | |
|                 const errorText = await response.text();
 | |
|                 console.error('fetchTtsGeneration Error Response Text:', errorText);
 | |
|                 throw new Error(`HTTP ${response.status}: ${errorText}`);
 | |
|             }
 | |
|             const data = await response.json();
 | |
| 
 | |
|             // Handle V1/V2 URL differences
 | |
|             const outputUrl = this.settings.server_version === 'v1'
 | |
|                 ? data.output_file_url  // V1 returns full URL
 | |
|                 : `${this.settings.provider_endpoint}${data.output_file_url}`; // V2 returns relative path
 | |
| 
 | |
|             return outputUrl;
 | |
|         } catch (error) {
 | |
|             console.error('[fetchTtsGeneration] Exception caught:', error);
 | |
|             throw error;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| //#########################//
 | |
| //  Update Status Messages //
 | |
| //#########################//
 | |
| 
 | |
| function updateStatus(message) {
 | |
|     const statusElement = document.getElementById('status_info');
 | |
|     if (statusElement) {
 | |
|         statusElement.textContent = message;
 | |
|         switch (message) {
 | |
|             case 'Offline':
 | |
|                 statusElement.style.color = 'red';
 | |
|                 break;
 | |
|             case 'Ready':
 | |
|                 statusElement.style.color = 'lightgreen';
 | |
|                 break;
 | |
|             case 'Processing':
 | |
|                 statusElement.style.color = 'blue';
 | |
|                 break;
 | |
|             case 'Error':
 | |
|                 statusElement.style.color = 'red';
 | |
|                 break;
 | |
|         }
 | |
|     }
 | |
| }
 |