Support for AllTalk V1 and V2 Select the version in the interface. RVC is not supported on AllTalk V1

3138252928875c0aeeb5130188c62f49b6501c47

erew123 <35898566+erew123@users.noreply.github.com>

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