Blame Raw
Cohee · e3f41666 · · 1076 lines (44.9 KB)
1 contributor
1import { doExtrasFetch } from '../../extensions.js';
2import { debounce } from '../../utils.js';
3import { saveTtsProviderSettings } from './index.js';
4
5export { AllTalkTtsProvider };
6
7class AllTalkTtsProvider {
8 //########//
9 // Config //
10 //########//
11
12 settings = {};
13 constructor() {
14 // Initialize with default settings if they are not already set
15 this.settings = {
16 provider_endpoint: this.settings.provider_endpoint || 'http://localhost:7851',
17 server_version: this.settings.server_version || 'v2',
18 language: this.settings.language || 'en',
19 voiceMap: this.settings.voiceMap || {},
20 at_generation_method: this.settings.at_generation_method || 'standard_generation',
21 narrator_enabled: this.settings.narrator_enabled || 'false',
22 at_narrator_text_not_inside: this.settings.at_narrator_text_not_inside || 'narrator',
23 narrator_voice_gen: this.settings.narrator_voice_gen || 'Please set a voice',
24 rvc_character_voice: this.settings.rvc_character_voice || 'Disabled',
25 rvc_character_pitch: this.settings.rvc_character_pitch || '0',
26 rvc_narrator_voice: this.settings.rvc_narrator_voice || 'Disabled',
27 rvc_narrator_pitch: this.settings.rvc_narrator_pitch || '0',
28 finetuned_model: this.settings.finetuned_model || 'false',
29 };
30 // Separate property for dynamically updated settings from the server
31 this.dynamicSettings = {
32 modelsAvailable: [],
33 currentModel: '',
34 deepspeed_available: false,
35 deepspeed_enabled: false,
36 lowvram_capable: false,
37 lowvram_enabled: false,
38 };
39 this.rvcVoices = []; // Initialize rvcVoices as an empty array
40 }
41 ready = false;
42 voices = [];
43 separator = '. ';
44 audioElement = document.createElement('audio');
45
46 languageLabels = {
47 'Arabic': 'ar',
48 'Brazilian Portuguese': 'pt',
49 'Chinese': 'zh-cn',
50 'Czech': 'cs',
51 'Dutch': 'nl',
52 'English': 'en',
53 'French': 'fr',
54 'German': 'de',
55 'Italian': 'it',
56 'Polish': 'pl',
57 'Russian': 'ru',
58 'Spanish': 'es',
59 'Turkish': 'tr',
60 'Japanese': 'ja',
61 'Korean': 'ko',
62 'Hungarian': 'hu',
63 'Hindi': 'hi',
64 };
65
66 get settingsHtml() {
67 // HTML template literals can trigger ESLint quotes warnings when quotes are used in HTML attributes.
68 // Disabling quotes rule for this one line as it's a false positive with HTML template literals.
69 // eslint-disable-next-line quotes
70 let html = `<div class="at-settings-separator">AllTalk V2 Settings</div>`;
71
72 html += `<div class='at-settings-row'>
73 <div class='at-settings-option'>
74 <label for='at_generation_method'>AllTalk TTS Generation Method</label>
75 <select id='at_generation_method'>
76 <option value='standard_generation'>Standard Audio Generation (AT Narrator - Optional)</option>
77 <option value='streaming_enabled'>Streaming Audio Generation (AT Narrator - Disabled)</option>
78 </select>
79 </div>
80 </div>`;
81
82 html += `<div class='at-settings-row'>
83 <div class='at-settings-option'>
84 <label for='at_narrator_enabled'>AT Narrator</label>
85 <select id='at_narrator_enabled'>
86 <option value='true'>Enabled</option>
87 <option value='silent'>Enabled (Silenced)</option>
88 <option value='false'>Disabled</option>
89 </select>
90 </div>
91
92 <div class='at-settings-option'>
93 <label for='at_narrator_text_not_inside'>Text Not Inside * or " is</label>
94 <select id='at_narrator_text_not_inside'>
95 <option value='character'>Character</option>
96 <option value='narrator'>Narrator</option>
97 <option value='silent'>Silent</option>
98 </select>
99 </div>
100 </div>`;
101
102 html += `<div class='at-settings-row'>
103 <div class='at-settings-option'>
104 <label for='narrator_voice'>Narrator Voice</label>
105 <select id='narrator_voice'>`;
106 if (this.voices) {
107 for (let voice of this.voices) {
108 html += `<option value='${voice.voice_id}'>${voice.name}</option>`;
109 }
110 }
111 html += `</select>
112 </div>
113 <div class='at-settings-option'>
114 <label for='language_options'>Language</label>
115 <select id='language_options'>`;
116 for (let language in this.languageLabels) {
117 html += `<option value='${this.languageLabels[language]}' ${this.languageLabels[language] === this.settings?.language ? 'selected="selected"' : ''}>${language}</option>`;
118 }
119 html += `</select>
120 </div>
121 </div>`;
122
123 html += `<div class='at-settings-row'>
124 <div class='at-settings-option'>
125 <label for='rvc_character_voice'>RVC Character</label>
126 <select id='rvc_character_voice'>`;
127 if (this.rvcVoices) {
128 for (let rvccharvoice of this.rvcVoices) {
129 html += `<option value='${rvccharvoice.voice_id}'>${rvccharvoice.name}</option>`;
130 }
131 }
132 html += `</select>
133 </div>
134 <div class='at-settings-option'>
135 <label for='rvc_narrator_voice'>RVC Narrator</label>
136 <select id='rvc_narrator_voice'>`;
137 if (this.rvcVoices) {
138 for (let rvcnarrvoice of this.rvcVoices) {
139 html += `<option value='${rvcnarrvoice.voice_id}'>${rvcnarrvoice.name}</option>`;
140 }
141 }
142 html += `</select>
143 </div>
144</div>`;
145
146 html += `<div class='at-settings-row'>
147 <div class='at-settings-option'>
148 <label for='rvc_character_pitch'>RVC Character Pitch</label>
149 <select id='rvc_character_pitch'>`;
150 for (let i = -24; i <= 24; i++) {
151 const selected = i === 0 ? 'selected="selected"' : '';
152 html += `<option value='${i}' ${selected}>${i}</option>`;
153 }
154 html += `</select>
155 </div>
156 <div class='at-settings-option'>
157 <label for='rvc_narrator_pitch'>RVC Narrator Pitch</label>
158 <select id='rvc_narrator_pitch'>`;
159 for (let i = -24; i <= 24; i++) {
160 const selected = i === 0 ? 'selected="selected"' : '';
161 html += `<option value='${i}' ${selected}>${i}</option>`;
162 }
163 html += `</select>
164 </div>
165 </div>`;
166
167 html += `<div class='at-model-endpoint-row'>
168 <div class='at-model-option'>
169 <label for='switch_model'>Switch Model</label>
170 <select id='switch_model'>
171 <!-- Options will be dynamically populated -->
172 </select>
173 </div>
174
175 <div class='at-endpoint-option'>
176 <label for='at_server'>AllTalk Endpoint:</label>
177 <input id='at_server' type='text' class='text_pole' maxlength='80' value='${this.settings.provider_endpoint}'/>
178 </div>
179 </div>`;
180
181 html += `<div class='at-settings-row'>
182 <div class='at-settings-option'>
183 <label for='server_version'>AllTalk Server Version</label>
184 <select id='server_version'>
185 <option value='v1'>AllTalk V1</option>
186 <option value='v2'>AllTalk V2</option>
187 </select>
188 </div>
189 </div>`;
190
191 html += `<div class='at-model-endpoint-row'>
192 <div class='at-settings-option'>
193 <label for='low_vram'>Low VRAM</label>
194 <input id='low_vram' type='checkbox'/>
195 </div>
196 <div class='at-settings-option'>
197 <label for='deepspeed'>DeepSpeed</label>
198 <input id='deepspeed' type='checkbox'/>
199 </div>
200 <div class='at-settings-option status-option'>
201 <span>Status: <span id='status_info'>Ready</span></span>
202 </div>
203 <div class='at-settings-option empty-option'>
204 <!-- This div remains empty for spacing -->
205 </div>
206</div>`;
207
208 html += `<div class='at-website-row'>
209 <div class='at-website-option'>
210 <span>AllTalk V2<a target='_blank' href='${this.settings.provider_endpoint}'>Config & Docs</a>.</span>
211 </div>
212
213 <div class='at-website-option'>
214 <span>AllTalk <a target='_blank' href='https://github.com/erew123/alltalk_tts/'>Website</a>.</span>
215 </div>
216</div>`;
217
218 html += `<div class='at-website-row'>
219<div class='at-website-option'>
220<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>
221<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>
222<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>
223</div>
224</div>`;
225
226 return html;
227 }
228
229
230 //#################//
231 // Startup ST & AT //
232 //#################//
233
234 async loadSettings(settings) {
235 updateStatus('Offline');
236
237 if (Object.keys(settings).length === 0) {
238 console.info('Using default AllTalk TTS Provider settings');
239 } else {
240 // Populate settings with provided values, ignoring server-provided settings
241 for (const key in settings) {
242 if (key in this.settings) {
243 this.settings[key] = settings[key];
244 } else {
245 console.debug(`Ignoring non-user-configurable setting: ${key}`);
246 }
247 }
248 }
249
250 // Update UI elements to reflect the loaded settings
251 $('#at_server').val(this.settings.provider_endpoint);
252 $('#language_options').val(this.settings.language);
253 $('#at_generation_method').val(this.settings.at_generation_method);
254 $('#at_narrator_enabled').val(this.settings.narrator_enabled);
255 $('#at_narrator_text_not_inside').val(this.settings.at_narrator_text_not_inside);
256 $('#narrator_voice').val(this.settings.narrator_voice_gen);
257 $('#rvc_character_voice').val(this.settings.rvc_character_voice);
258 $('#rvc_narrator_voice').val(this.settings.rvc_narrator_voice);
259 $('#rvc_character_pitch').val(this.settings.rvc_character_pitch);
260 $('#rvc_narrator_pitch').val(this.settings.rvc_narrator_pitch);
261 $('#server_version').val(this.settings.server_version);
262
263 console.debug('AllTalkTTS: Settings loaded');
264 try {
265 // Check if TTS provider is ready
266 this.setupEventListeners();
267 this.updateLanguageDropdown();
268 await this.checkReady();
269 await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server
270 await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready
271 await this.fetchRvcVoiceObjects(); // Fetch RVC voices
272 this.updateNarratorVoicesDropdown();
273 this.applySettingsToHTML();
274 updateStatus('Ready');
275 } catch (error) {
276 console.error('Error loading settings:', error);
277 updateStatus('Offline');
278 }
279 }
280
281
282 applySettingsToHTML() {
283 const narratorVoiceSelect = document.getElementById('narrator_voice');
284 const atNarratorSelect = document.getElementById('at_narrator_enabled');
285 const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside');
286 const generationMethodSelect = document.getElementById('at_generation_method');
287 this.settings.narrator_voice = this.settings.narrator_voice_gen;
288 // Apply settings to Narrator Voice dropdown
289 if (narratorVoiceSelect && this.settings.narrator_voice) {
290 narratorVoiceSelect.value = this.settings.narrator_voice; // Remove the parentheses
291 }
292 // Apply settings to AT Narrator Enabled dropdown
293 if (atNarratorSelect) {
294 const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks');
295 const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted');
296 const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues');
297 if (this.settings.narrator_enabled) {
298 ttsPassAsterisksCheckbox.checked = false;
299 $('#tts_pass_asterisks').trigger('click');
300 $('#tts_pass_asterisks').trigger('change');
301 }
302 if (!this.settings.narrator_enabled) {
303 ttsPassAsterisksCheckbox.checked = true;
304 $('#tts_pass_asterisks').trigger('click');
305 $('#tts_pass_asterisks').trigger('change');
306 }
307 if (this.settings.narrator_enabled) {
308 ttsNarrateQuotedCheckbox.checked = true;
309 ttsNarrateDialoguesCheckbox.checked = true;
310 $('#tts_narrate_quoted').trigger('click');
311 $('#tts_narrate_quoted').trigger('change');
312 $('#tts_narrate_dialogues').trigger('click');
313 $('#tts_narrate_dialogues').trigger('change');
314 }
315 atNarratorSelect.value = this.settings.narrator_enabled.toString();
316 this.settings.narrator_enabled = this.settings.narrator_enabled.toString();
317 }
318 const languageSelect = document.getElementById('language_options');
319 if (languageSelect && this.settings.language) {
320 languageSelect.value = this.settings.language;
321 }
322 if (textNotInsideSelect && this.settings.text_not_inside) {
323 textNotInsideSelect.value = this.settings.text_not_inside;
324 this.settings.at_narrator_text_not_inside = this.settings.text_not_inside;
325 }
326 if (generationMethodSelect && this.settings.at_generation_method) {
327 generationMethodSelect.value = this.settings.at_generation_method;
328 }
329 const isStreamingEnabled = this.settings.at_generation_method === 'streaming_enabled';
330 if (isStreamingEnabled) {
331 if (atNarratorSelect) atNarratorSelect.disabled = true;
332 if (textNotInsideSelect) textNotInsideSelect.disabled = true;
333 if (narratorVoiceSelect) narratorVoiceSelect.disabled = true;
334 } else {
335 if (atNarratorSelect) atNarratorSelect.disabled = false;
336 if (textNotInsideSelect) textNotInsideSelect.disabled = !this.settings.narrator_enabled;
337 if (narratorVoiceSelect) narratorVoiceSelect.disabled = !this.settings.narrator_enabled;
338 }
339 }
340
341
342 //##############################//
343 // Check AT Server is Available //
344 //##############################//
345
346 async checkReady() {
347 try {
348 const response = await fetch(`${this.settings.provider_endpoint}/api/ready`);
349 // Check if the HTTP request was successful
350 if (!response.ok) {
351 throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`);
352 }
353 const statusText = await response.text();
354 // Check if the response is 'Ready'
355 if (statusText === 'Ready') {
356 this.ready = true; // Set the ready flag to true
357 console.log('TTS service is ready.');
358 } else {
359 this.ready = false;
360 console.log('TTS service is not ready.');
361 }
362 } catch (error) {
363 console.error('Error checking TTS service readiness:', error);
364 this.ready = false; // Ensure ready flag is set to false in case of error
365 }
366 }
367
368 //######################//
369 // Get Available Voices //
370 //######################//
371
372 async fetchTtsVoiceObjects() {
373 const response = await fetch(`${this.settings.provider_endpoint}/api/voices`);
374 if (!response.ok) {
375 const errorText = await response.text();
376 throw new Error(`HTTP ${response.status}: ${errorText}`);
377 }
378 const data = await response.json();
379 const voices = data.voices.map(filename => {
380 return {
381 name: filename,
382 voice_id: filename,
383 preview_url: null, // Preview URL will be dynamically generated
384 lang: 'en', // Default language
385 };
386 });
387 this.voices = voices; // Assign to the class property
388 return voices; // Also return this list
389 }
390
391 async fetchRvcVoiceObjects() {
392 if (this.settings.server_version == 'v1') {
393 console.log('Skipping RVC voices fetch for V1 server');
394 return [];
395 }
396
397 console.log('Fetching RVC Voices');
398 try {
399 const response = await fetch(`${this.settings.provider_endpoint}/api/rvcvoices`);
400 if (!response.ok) {
401 const errorText = await response.text();
402 console.error('Error text:', errorText);
403 throw new Error(`HTTP ${response.status}: ${errorText}`);
404 }
405
406 const data = await response.json();
407 if (!data || !data.rvcvoices) {
408 console.error('Invalid data format:', data);
409 throw new Error('Invalid data format received from /api/rvcvoices');
410 }
411
412 const voices = data.rvcvoices.map(filename => {
413 return {
414 name: filename,
415 voice_id: filename,
416 };
417 });
418
419 console.log('RVC voices:', voices);
420 this.rvcVoices = voices; // Assign to the class property
421 this.updateRvcVoiceDropdowns(); // Update UI after fetching voices
422 return voices; // Also return this list
423 } catch (error) {
424 console.error('Error fetching RVC voices:', error);
425 this.rvcVoices = [{ name: 'Disabled', voice_id: 'Disabled' }]; // Set default on error
426 throw error;
427 } finally {
428 // Ensure dropdowns are updated even if there was an error
429 this.updateRvcVoiceDropdowns();
430 }
431 }
432
433 //##########################################//
434 // Get Current AT Server Config & Update ST //
435 //##########################################//
436
437 async updateSettingsFromServer() {
438 try {
439 const response = await fetch(`${this.settings.provider_endpoint}/api/currentsettings`);
440 if (!response.ok) {
441 throw new Error(`Failed to fetch current settings: ${response.statusText}`);
442 }
443 const currentSettings = await response.json();
444 currentSettings.models_available.sort((a, b) => a.name.localeCompare(b.name));
445
446 this.settings.enginesAvailable = currentSettings.engines_available;
447 this.settings.currentEngineLoaded = currentSettings.current_engine_loaded;
448 this.settings.modelsAvailable = currentSettings.models_available;
449 this.settings.currentModel = currentSettings.current_model_loaded;
450 this.settings.deepspeed_capable = currentSettings.deepspeed_capable;
451 this.settings.deepspeed_available = currentSettings.deepspeed_available;
452 this.settings.deepspeed_enabled = currentSettings.deepspeed_enabled;
453 this.settings.lowvram_capable = currentSettings.lowvram_capable;
454 this.settings.lowvram_enabled = currentSettings.lowvram_enabled;
455
456 await this.fetchRvcVoiceObjects(); // Fetch RVC voices
457
458 this.updateModelDropdown();
459 this.updateCheckboxes();
460 this.updateRvcVoiceDropdowns(); // Update the RVC voice dropdowns
461 } catch (error) {
462 console.error(`Error updating settings from server: ${error}`);
463 }
464 }
465
466 updateRvcVoiceDropdowns() {
467 // Handle all RVC-related elements
468 const rvcElements = document.querySelectorAll('.rvc-setting');
469 const isV2 = this.settings.server_version === 'v2';
470
471 rvcElements.forEach(element => {
472 element.style.display = isV2 ? 'block' : 'none';
473 });
474
475 // Update and disable/enable character voice dropdown
476 const rvcCharacterVoiceSelect = document.getElementById('rvc_character_voice');
477 if (rvcCharacterVoiceSelect) {
478 rvcCharacterVoiceSelect.disabled = !isV2;
479 if (this.rvcVoices) {
480 rvcCharacterVoiceSelect.innerHTML = '';
481 for (let voice of this.rvcVoices) {
482 const option = document.createElement('option');
483 option.value = voice.voice_id;
484 option.textContent = voice.name;
485 if (voice.voice_id === this.settings.rvc_character_voice) {
486 option.selected = true;
487 }
488 rvcCharacterVoiceSelect.appendChild(option);
489 }
490 }
491 }
492
493 // Update and disable/enable narrator voice dropdown
494 const rvcNarratorVoiceSelect = document.getElementById('rvc_narrator_voice');
495 if (rvcNarratorVoiceSelect) {
496 rvcNarratorVoiceSelect.disabled = !isV2;
497 if (this.rvcVoices) {
498 rvcNarratorVoiceSelect.innerHTML = '';
499 for (let voice of this.rvcVoices) {
500 const option = document.createElement('option');
501 option.value = voice.voice_id;
502 option.textContent = voice.name;
503 if (voice.voice_id === this.settings.rvc_narrator_voice) {
504 option.selected = true;
505 }
506 rvcNarratorVoiceSelect.appendChild(option);
507 }
508 }
509 }
510
511 // Update pitch inputs
512 const characterPitch = document.getElementById('rvc_character_pitch');
513 if (characterPitch) {
514 characterPitch.disabled = !isV2;
515 }
516
517 const narratorPitch = document.getElementById('rvc_narrator_pitch');
518 if (narratorPitch) {
519 narratorPitch.disabled = !isV2;
520 }
521 }
522
523 //###################################################//
524 // Get Current AT Server Config & Update ST (Models) //
525 //###################################################//
526
527 updateModelDropdown() {
528 const modelSelect = document.getElementById('switch_model');
529 if (modelSelect) {
530 modelSelect.innerHTML = ''; // Clear existing options
531 this.settings.modelsAvailable.forEach(model => {
532 const option = document.createElement('option');
533 option.value = model.name;
534 option.textContent = model.name; // Use model name directly
535 option.selected = model.name === this.settings.currentModel;
536 modelSelect.appendChild(option);
537 });
538 }
539 }
540
541 //#######################################################//
542 // Get Current AT Server Config & Update ST (DS and LVR) //
543 //#######################################################//
544
545 updateCheckboxes() {
546 const deepspeedCheckbox = document.getElementById('deepspeed');
547 const lowVramCheckbox = document.getElementById('low_vram');
548
549 // Handle DeepSpeed checkbox
550 if (deepspeedCheckbox) {
551 if (this.settings.deepspeed_capable) {
552 // If TTS engine is capable of using DeepSpeed
553 deepspeedCheckbox.disabled = !this.settings.deepspeed_available;
554 this.settings.deepspeed_enabled = this.settings.deepspeed_available && this.settings.deepspeed_enabled;
555 } else {
556 // If TTS engine is NOT capable of using DeepSpeed
557 deepspeedCheckbox.disabled = true;
558 this.settings.deepspeed_enabled = false;
559 }
560 deepspeedCheckbox.checked = this.settings.deepspeed_enabled;
561 }
562
563 // Handle Low VRAM checkbox
564 if (lowVramCheckbox) {
565 if (this.settings.lowvram_capable) {
566 // If TTS engine is capable of low VRAM
567 lowVramCheckbox.disabled = false;
568 } else {
569 // If TTS engine is NOT capable of low VRAM
570 lowVramCheckbox.disabled = true;
571 this.settings.lowvram_enabled = false;
572 }
573 lowVramCheckbox.checked = this.settings.lowvram_enabled;
574 }
575 }
576
577
578 //###############################################################//
579 // Get Current AT Server Config & Update ST (AT Narrator Voices) //
580 //###############################################################//
581
582 updateNarratorVoicesDropdown() {
583 const narratorVoiceSelect = document.getElementById('narrator_voice');
584 if (narratorVoiceSelect && this.voices) {
585 // Clear existing options
586 narratorVoiceSelect.innerHTML = '';
587 // Add new options
588 for (let voice of this.voices) {
589 const option = document.createElement('option');
590 option.value = voice.voice_id;
591 option.textContent = voice.name;
592 narratorVoiceSelect.appendChild(option);
593 }
594 }
595 }
596
597 //######################################################//
598 // Get Current AT Server Config & Update ST (Languages) //
599 //######################################################//
600
601 updateLanguageDropdown() {
602 const languageSelect = document.getElementById('language_options');
603 if (languageSelect) {
604 // Ensure default language is set
605 this.settings.language = this.settings.language || 'en';
606
607 languageSelect.innerHTML = '';
608 for (let language in this.languageLabels) {
609 const option = document.createElement('option');
610 option.value = this.languageLabels[language];
611 option.textContent = language;
612 if (this.languageLabels[language] === this.settings.language) {
613 option.selected = true;
614 }
615 languageSelect.appendChild(option);
616 }
617 }
618 }
619
620 //########################################//
621 // Start AT TTS extenstion page listeners //
622 //########################################//
623
624 setupEventListeners() {
625 // Define the event handler function
626 const onModelSelectChange = async (event) => {
627 console.log('Model select change event triggered');
628 const selectedModel = event.target.value;
629 console.log(`Selected model: ${selectedModel}`);
630 updateStatus('Processing');
631 try {
632 const response = await fetch(`${this.settings.provider_endpoint}/api/reload?tts_method=${encodeURIComponent(selectedModel)}`, {
633 method: 'POST',
634 });
635 if (!response.ok) {
636 throw new Error(`HTTP Error: ${response.status}`);
637 }
638 const data = await response.json();
639 console.log('POST response data:', data);
640 updateStatus('Ready');
641 } catch (error) {
642 console.error('POST request error:', error);
643 updateStatus('Error');
644 }
645 };
646
647 // Switch Model Listener with debounce
648 const modelSelect = document.getElementById('switch_model');
649 if (modelSelect) {
650 const debouncedModelSelectChange = debounce(onModelSelectChange, 1400);
651 modelSelect.addEventListener('change', debouncedModelSelectChange);
652 }
653
654 // AllTalk Server version change listener
655 const serverVersionSelect = document.getElementById('server_version');
656 if (serverVersionSelect) {
657 serverVersionSelect.addEventListener('change', async (event) => {
658 this.settings.server_version = event.target.value;
659 this.onSettingsChange();
660 if (event.target.value === 'v2') {
661 await this.fetchRvcVoiceObjects();
662 }
663 this.updateRvcVoiceDropdowns();
664 });
665 }
666
667 // RVC Voice and Pitch listeners
668 const rvcCharacterVoiceSelect = document.getElementById('rvc_character_voice');
669 if (rvcCharacterVoiceSelect) {
670 rvcCharacterVoiceSelect.addEventListener('change', (event) => {
671 this.settings.rvccharacter_voice_gen = event.target.value;
672 this.onSettingsChange();
673 });
674 }
675
676 const rvcNarratorVoiceSelect = document.getElementById('rvc_narrator_voice');
677 if (rvcNarratorVoiceSelect) {
678 rvcNarratorVoiceSelect.addEventListener('change', (event) => {
679 this.settings.rvcnarrator_voice_gen = event.target.value;
680 this.onSettingsChange();
681 });
682 }
683
684 const rvcCharacterPitchSelect = document.getElementById('rvc_character_pitch');
685 if (rvcCharacterPitchSelect) {
686 rvcCharacterPitchSelect.addEventListener('change', (event) => {
687 this.settings.rvc_character_pitch = event.target.value;
688 this.onSettingsChange();
689 });
690 }
691
692 const rvcNarratorPitchSelect = document.getElementById('rvc_narrator_pitch');
693 if (rvcNarratorPitchSelect) {
694 rvcNarratorPitchSelect.addEventListener('change', (event) => {
695 this.settings.rvc_narrator_pitch = event.target.value;
696 this.onSettingsChange();
697 });
698 }
699
700 // DeepSpeed Listener
701 const deepspeedCheckbox = document.getElementById('deepspeed');
702 if (deepspeedCheckbox) {
703 const handleDeepSpeedChange = async (event) => {
704 const deepSpeedValue = event.target.checked ? 'True' : 'False';
705 updateStatus('Processing');
706 try {
707 const response = await fetch(`${this.settings.provider_endpoint}/api/deepspeed?new_deepspeed_value=${deepSpeedValue}`, {
708 method: 'POST',
709 });
710 if (!response.ok) {
711 throw new Error(`HTTP Error: ${response.status}`);
712 }
713 const data = await response.json();
714 console.log('POST response data:', data);
715 updateStatus('Ready');
716 } catch (error) {
717 console.error('POST request error:', error);
718 updateStatus('Error');
719 }
720 };
721
722 const debouncedHandleDeepSpeedChange = debounce(handleDeepSpeedChange, 300);
723 deepspeedCheckbox.addEventListener('change', debouncedHandleDeepSpeedChange);
724 }
725
726 // Low VRAM Listener
727 const lowVramCheckbox = document.getElementById('low_vram');
728 if (lowVramCheckbox) {
729 const handleLowVramChange = async (event) => {
730 const lowVramValue = event.target.checked ? 'True' : 'False';
731 updateStatus('Processing');
732 try {
733 const response = await fetch(`${this.settings.provider_endpoint}/api/lowvramsetting?new_low_vram_value=${lowVramValue}`, {
734 method: 'POST',
735 });
736 if (!response.ok) {
737 throw new Error(`HTTP Error: ${response.status}`);
738 }
739 const data = await response.json();
740 console.log('POST response data:', data);
741 updateStatus('Ready');
742 } catch (error) {
743 console.error('POST request error:', error);
744 updateStatus('Error');
745 }
746 };
747
748 const debouncedHandleLowVramChange = debounce(handleLowVramChange, 300);
749 lowVramCheckbox.addEventListener('change', debouncedHandleLowVramChange);
750 }
751
752 // Other listeners without debounce since they don't need it
753 const narratorVoiceSelect = document.getElementById('narrator_voice');
754 if (narratorVoiceSelect) {
755 narratorVoiceSelect.addEventListener('change', (event) => {
756 this.settings.narrator_voice_gen = `${event.target.value}`;
757 this.onSettingsChange();
758 });
759 }
760
761 const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside');
762 if (textNotInsideSelect) {
763 textNotInsideSelect.addEventListener('change', (event) => {
764 this.settings.text_not_inside = event.target.value;
765 this.onSettingsChange();
766 });
767 }
768
769 // AT Narrator Dropdown Listener
770 const atNarratorSelect = document.getElementById('at_narrator_enabled');
771 const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks');
772 const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted');
773 const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues');
774
775 if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) {
776 atNarratorSelect.addEventListener('change', (event) => {
777 const narratorOption = event.target.value;
778 this.settings.narrator_enabled = narratorOption;
779
780 const isNarratorDisabled = narratorOption === 'false';
781 textNotInsideSelect.disabled = isNarratorDisabled;
782 narratorVoiceSelect.disabled = isNarratorDisabled;
783
784 if (narratorOption === 'true') {
785 ttsPassAsterisksCheckbox.checked = false;
786 $('#tts_pass_asterisks').trigger('click');
787 $('#tts_pass_asterisks').trigger('change');
788 ttsNarrateQuotedCheckbox.checked = true;
789 ttsNarrateDialoguesCheckbox.checked = true;
790 $('#tts_narrate_quoted').trigger('click');
791 $('#tts_narrate_quoted').trigger('change');
792 $('#tts_narrate_dialogues').trigger('click');
793 $('#tts_narrate_dialogues').trigger('change');
794 } else if (narratorOption === 'silent') {
795 ttsPassAsterisksCheckbox.checked = false;
796 $('#tts_pass_asterisks').trigger('click');
797 $('#tts_pass_asterisks').trigger('change');
798 } else {
799 ttsPassAsterisksCheckbox.checked = true;
800 $('#tts_pass_asterisks').trigger('click');
801 $('#tts_pass_asterisks').trigger('change');
802 }
803
804 this.onSettingsChange();
805 });
806 }
807
808 // Event Listener for AT Generation Method Dropdown
809 const atGenerationMethodSelect = document.getElementById('at_generation_method');
810 if (atGenerationMethodSelect) {
811 atGenerationMethodSelect.addEventListener('change', (event) => {
812 const selectedMethod = event.target.value;
813
814 if (selectedMethod === 'streaming_enabled') {
815 // Disable and unselect AT Narrator
816 atNarratorSelect.disabled = true;
817 atNarratorSelect.value = 'false';
818 textNotInsideSelect.disabled = true;
819 narratorVoiceSelect.disabled = true;
820 } else if (selectedMethod === 'standard_generation') {
821 // Enable AT Narrator
822 atNarratorSelect.disabled = false;
823 }
824 this.settings.at_generation_method = selectedMethod;
825 this.onSettingsChange();
826 });
827 }
828
829 // Language Dropdown Listener
830 const languageSelect = document.getElementById('language_options');
831 if (languageSelect) {
832 languageSelect.addEventListener('change', (event) => {
833 this.settings.language = event.target.value;
834 this.onSettingsChange();
835 });
836 }
837
838 // AllTalk Endpoint Input Listener
839 const atServerInput = document.getElementById('at_server');
840 if (atServerInput) {
841 atServerInput.addEventListener('input', (event) => {
842 this.settings.provider_endpoint = event.target.value;
843 this.onSettingsChange();
844 });
845 }
846 }
847
848 //#############################//
849 // Store ST interface settings //
850 //#############################//
851
852 onSettingsChange() {
853 // Update settings based on the UI elements
854 //this.settings.provider_endpoint = $('#at_server').val();
855 this.settings.language = $('#language_options').val();
856 //this.settings.voiceMap = $('#voicemap').val();
857 this.settings.at_generation_method = $('#at_generation_method').val();
858 this.settings.narrator_enabled = $('#at_narrator_enabled').val();
859 this.settings.at_narrator_text_not_inside = $('#at_narrator_text_not_inside').val();
860 this.settings.rvc_character_voice = $('#rvc_character_voice').val();
861 this.settings.rvc_narrator_voice = $('#rvc_narrator_voice').val();
862 this.settings.rvc_character_pitch = $('#rvc_character_pitch').val();
863 this.settings.rvc_narrator_pitch = $('#rvc_narrator_pitch').val();
864 this.settings.narrator_voice_gen = $('#narrator_voice').val();
865 // Save the updated settings
866 saveTtsProviderSettings();
867 }
868
869 //#########################//
870 // ST Handle Reload button //
871 //#########################//
872
873 async onRefreshClick() {
874 try {
875 updateStatus('Processing'); // Set status to Processing while refreshing
876 await this.checkReady(); // Check if the TTS provider is ready
877 await this.loadSettings(this.settings); // Reload the settings
878 await this.checkReady(); // Check if the TTS provider is ready
879 updateStatus(this.ready ? 'Ready' : 'Offline'); // Update the status based on readiness
880 } catch (error) {
881 console.error('Error during refresh:', error);
882 updateStatus('Error'); // Set status to Error in case of failure
883 }
884 }
885
886 //##################//
887 // Preview AT Voice //
888 //##################//
889
890 async previewTtsVoice(voiceName) {
891 try {
892 // Prepare data for POST request
893 const postData = new URLSearchParams();
894 postData.append('voice', `${voiceName}`);
895
896 // Add RVC parameters for V2 if applicable
897 if (this.settings.server_version === 'v2' && this.settings.rvc_character_voice !== 'Disabled') {
898 postData.append('rvccharacter_voice_gen', this.settings.rvc_character_voice);
899 postData.append('rvccharacter_pitch', this.settings.rvc_character_pitch || '0');
900 }
901
902 // Making the POST request
903 const response = await fetch(`${this.settings.provider_endpoint}/api/previewvoice/`, {
904 method: 'POST',
905 headers: {
906 'Content-Type': 'application/x-www-form-urlencoded',
907 },
908 body: postData,
909 });
910
911 if (!response.ok) {
912 const errorText = await response.text();
913 console.error('previewTtsVoice Error Response Text:', errorText);
914 throw new Error(`HTTP ${response.status}: ${errorText}`);
915 }
916
917 const data = await response.json();
918 if (data.output_file_url) {
919 // Handle V1/V2 URL differences
920 const fullUrl = this.settings.server_version === 'v1'
921 ? data.output_file_url
922 : `${this.settings.provider_endpoint}${data.output_file_url}`;
923
924 const audioElement = new Audio(fullUrl);
925 audioElement.play().catch(e => console.error('Error playing audio:', e));
926 } else {
927 console.warn('previewTtsVoice No output file URL received in the response');
928 throw new Error('No output file URL received in the response');
929 }
930 } catch (error) {
931 console.error('previewTtsVoice Exception caught during preview generation:', error);
932 throw error;
933 }
934 }
935
936 //#####################//
937 // Populate ST voices //
938 //#####################//
939
940 async getVoice(voiceName, generatePreview = false) {
941 // Ensure this.voices is populated
942 if (this.voices.length === 0) {
943 // Fetch voice objects logic
944 }
945 // Find the object where the name matches voiceName
946 const match = this.voices.find(voice => voice.name === voiceName);
947 if (!match) {
948 // Error handling
949 }
950 // Generate preview URL only if requested
951 if (!match.preview_url && generatePreview) {
952 // Generate preview logic
953 }
954 return match; // Return the found voice object
955 }
956
957 //##########################################//
958 // Generate TTS Streaming or call Standard //
959 //##########################################//
960
961 async generateTts(inputText, voiceId) {
962 try {
963 if (this.settings.at_generation_method === 'streaming_enabled') {
964 // Construct the streaming URL
965 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`;
966 console.log('Streaming URL:', streamingUrl);
967
968 // Return the streaming URL directly
969 return streamingUrl;
970 } else {
971 // For standard method
972 const outputUrl = await this.fetchTtsGeneration(inputText, voiceId);
973 const audioResponse = await fetch(outputUrl);
974 if (!audioResponse.ok) {
975 throw new Error(`HTTP ${audioResponse.status}: Failed to fetch audio data`);
976 }
977 return audioResponse; // Return the fetch response directly
978 }
979 } catch (error) {
980 console.error('Error in generateTts:', error);
981 throw error;
982 }
983 }
984
985
986 //####################//
987 // Generate Standard //
988 //####################//
989
990 async fetchTtsGeneration(inputText, voiceId) {
991 const requestBody = new URLSearchParams({
992 'text_input': inputText,
993 'text_filtering': 'standard',
994 'character_voice_gen': voiceId,
995 'narrator_enabled': this.settings.narrator_enabled,
996 'narrator_voice_gen': this.settings.narrator_voice_gen,
997 'text_not_inside': this.settings.at_narrator_text_not_inside,
998 'language': this.settings.language,
999 'output_file_name': 'st_output',
1000 'output_file_timestamp': 'true',
1001 'autoplay': 'false',
1002 'autoplay_volume': '0.8',
1003 });
1004
1005 // Add RVC parameters only for V2
1006 if (this.settings.server_version === 'v2') {
1007 if (this.settings.rvc_character_voice !== 'Disabled') {
1008 requestBody.append('rvccharacter_voice_gen', this.settings.rvc_character_voice);
1009 requestBody.append('rvccharacter_pitch', this.settings.rvc_character_pitch || '0');
1010 }
1011 if (this.settings.rvc_narrator_voice !== 'Disabled') {
1012 requestBody.append('rvcnarrator_voice_gen', this.settings.rvc_narrator_voice);
1013 requestBody.append('rvcnarrator_pitch', this.settings.rvc_narrator_pitch || '0');
1014 }
1015 }
1016
1017 try {
1018 const response = await doExtrasFetch(
1019 `${this.settings.provider_endpoint}/api/tts-generate`,
1020 {
1021 method: 'POST',
1022 headers: {
1023 'Content-Type': 'application/x-www-form-urlencoded',
1024 'Cache-Control': 'no-cache',
1025 },
1026 body: requestBody,
1027 },
1028 );
1029
1030 if (!response.ok) {
1031 const errorText = await response.text();
1032 console.error('fetchTtsGeneration Error Response Text:', errorText);
1033 throw new Error(`HTTP ${response.status}: ${errorText}`);
1034 }
1035
1036 const data = await response.json();
1037
1038 // V1 returns a complete URL, V2 returns a relative path
1039 if (this.settings.server_version === 'v1') {
1040 // V1: Use the complete URL directly from the response
1041 return data.output_file_url;
1042 } else {
1043 // V2: Combine the endpoint with the relative path
1044 return `${this.settings.provider_endpoint}${data.output_file_url}`;
1045 }
1046 } catch (error) {
1047 console.error('[fetchTtsGeneration] Exception caught:', error);
1048 throw error;
1049 }
1050 }
1051}
1052
1053//#########################//
1054// Update Status Messages //
1055//#########################//
1056
1057function updateStatus(message) {
1058 const statusElement = document.getElementById('status_info');
1059 if (statusElement) {
1060 statusElement.textContent = message;
1061 switch (message) {
1062 case 'Offline':
1063 statusElement.style.color = 'red';
1064 break;
1065 case 'Ready':
1066 statusElement.style.color = 'lightgreen';
1067 break;
1068 case 'Processing':
1069 statusElement.style.color = 'blue';
1070 break;
1071 case 'Error':
1072 statusElement.style.color = 'red';
1073 break;
1074 }
1075 }
1076}