Merge pull request #3060 from erew123/release Support for AllTalk V1 and V2

85d25a8e1372d5b5c47d0c45138d81019923b622

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
1 files changed, +1072 -840Ignore whitespace
public/scripts/extensions/tts/alltalk.js+1072 -840
@@ -1,840 +1,1072 @@
1import { doExtrasFetch } from '../../extensions.js';1import { doExtrasFetch } from '../../extensions.js';
2import { saveTtsProviderSettings } from './index.js';2import { debounce } from '../../utils.js';
33import { saveTtsProviderSettings } from './index.js';
4export { AllTalkTtsProvider };4
55export { AllTalkTtsProvider };
6class AllTalkTtsProvider {6
7 //########//7class AllTalkTtsProvider {
8 // Config //8 //########//
9 //########//9 // Config //
1010 //########//
11 settings = {};11
12 constructor() {12 settings = {};
13 // Initialize with default settings if they are not already set13 constructor() {
14 this.settings = {14 // Initialize with default settings if they are not already set
15 provider_endpoint: this.settings.provider_endpoint || 'http://localhost:7851',15 this.settings = {
16 language: this.settings.language || 'en',16 provider_endpoint: this.settings.provider_endpoint || 'http://localhost:7851',
17 voiceMap: this.settings.voiceMap || {},17 server_version: this.settings.server_version || 'v2',
18 at_generation_method: this.settings.at_generation_method || 'standard_generation',18 language: this.settings.language || 'en',
19 narrator_enabled: this.settings.narrator_enabled || 'false',19 voiceMap: this.settings.voiceMap || {},
20 at_narrator_text_not_inside: this.settings.at_narrator_text_not_inside || 'narrator',20 at_generation_method: this.settings.at_generation_method || 'standard_generation',
21 narrator_voice_gen: this.settings.narrator_voice_gen || 'female_01.wav',21 narrator_enabled: this.settings.narrator_enabled || 'false',
22 finetuned_model: this.settings.finetuned_model || 'false',22 at_narrator_text_not_inside: this.settings.at_narrator_text_not_inside || 'narrator',
23 };23 narrator_voice_gen: this.settings.narrator_voice_gen || 'Please set a voice',
24 // Separate property for dynamically updated settings from the server24 rvc_character_voice: this.settings.rvc_character_voice || 'Disabled',
25 this.dynamicSettings = {25 rvc_character_pitch: this.settings.rvc_character_pitch || '0',
26 modelsAvailable: [],26 rvc_narrator_voice: this.settings.rvc_narrator_voice || 'Disabled',
27 currentModel: '',27 rvc_narrator_pitch: this.settings.rvc_narrator_pitch || '0',
28 deepspeed_available: false,28 finetuned_model: this.settings.finetuned_model || 'false',
29 deepSpeedEnabled: false,29 };
30 lowVramEnabled: false,30 // Separate property for dynamically updated settings from the server
31 };31 this.dynamicSettings = {
32 }32 modelsAvailable: [],
33 ready = false;33 currentModel: '',
34 voices = [];34 deepspeed_available: false,
35 separator = '. ';35 deepspeed_enabled: false,
36 audioElement = document.createElement('audio');36 lowvram_capable: false,
3737 lowvram_enabled: false,
38 languageLabels = {38 };
39 'Arabic': 'ar',39 this.rvcVoices = []; // Initialize rvcVoices as an empty array
40 'Brazilian Portuguese': 'pt',40 }
41 'Chinese': 'zh-cn',41 ready = false;
42 'Czech': 'cs',42 voices = [];
43 'Dutch': 'nl',43 separator = '. ';
44 'English': 'en',44 audioElement = document.createElement('audio');
45 'French': 'fr',45
46 'German': 'de',46 languageLabels = {
47 'Italian': 'it',47 'Arabic': 'ar',
48 'Polish': 'pl',48 'Brazilian Portuguese': 'pt',
49 'Russian': 'ru',49 'Chinese': 'zh-cn',
50 'Spanish': 'es',50 'Czech': 'cs',
51 'Turkish': 'tr',51 'Dutch': 'nl',
52 'Japanese': 'ja',52 'English': 'en',
53 'Korean': 'ko',53 'French': 'fr',
54 'Hungarian': 'hu',54 'German': 'de',
55 'Hindi': 'hi',55 'Italian': 'it',
56 };56 'Polish': 'pl',
5757 'Russian': 'ru',
58 get settingsHtml() {58 'Spanish': 'es',
59 let html = '<div class="at-settings-separator">AllTalk Settings</div>';59 'Turkish': 'tr',
6060 'Japanese': 'ja',
61 html += `<div class="at-settings-row">61 'Korean': 'ko',
6262 'Hungarian': 'hu',
63 <div class="at-settings-option">63 'Hindi': 'hi',
64 <label for="at_generation_method">AllTalk TTS Generation Method</label>64 };
65 <select id="at_generation_method">65
66 <option value="standard_generation">Standard Audio Generation (AT Narrator - Optional)</option>66 get settingsHtml() {
67 <option value="streaming_enabled">Streaming Audio Generation (AT Narrator - Disabled)</option>67 // HTML template literals can trigger ESLint quotes warnings when quotes are used in HTML attributes.
68 </select>68 // Disabling quotes rule for this one line as it's a false positive with HTML template literals.
69 </div>69 // eslint-disable-next-line quotes
70 </div>`;70 let html = `<div class="at-settings-separator">AllTalk V2 Settings</div>`;
7171
72 html += `<div class="at-settings-row">72 html += `<div class='at-settings-row'>
7373 <div class='at-settings-option'>
74 <div class="at-settings-option">74 <label for='at_generation_method'>AllTalk TTS Generation Method</label>
75 <label for="at_narrator_enabled">AT Narrator</label>75 <select id='at_generation_method'>
76 <select id="at_narrator_enabled">76 <option value='standard_generation'>Standard Audio Generation (AT Narrator - Optional)</option>
77 <option value="true">Enabled</option>77 <option value='streaming_enabled'>Streaming Audio Generation (AT Narrator - Disabled)</option>
78 <option value="false">Disabled</option>78 </select>
79 </select>79 </div>
80 </div>80 </div>`;
8181
82 <div class="at-settings-option">82 html += `<div class='at-settings-row'>
83 <label for="at_narrator_text_not_inside">Text Not Inside * or " is</label>83 <div class='at-settings-option'>
84 <select id="at_narrator_text_not_inside">84 <label for='at_narrator_enabled'>AT Narrator</label>
85 <option value="narrator">Narrator</option>85 <select id='at_narrator_enabled'>
86 <option value="character">Character</option>86 <option value='true'>Enabled</option>
87 </select>87 <option value='silent'>Enabled (Silenced)</option>
88 </div>88 <option value='false'>Disabled</option>
8989 </select>
90 </div>`;90 </div>
9191
92 html += `<div class="at-settings-row">92 <div class='at-settings-option'>
93 <div class="at-settings-option">93 <label for='at_narrator_text_not_inside'>Text Not Inside * or " is</label>
94 <label for="narrator_voice">Narrator Voice</label>94 <select id='at_narrator_text_not_inside'>
95 <select id="narrator_voice">`;95 <option value='character'>Character</option>
96 if (this.voices) {96 <option value='narrator'>Narrator</option>
97 for (let voice of this.voices) {97 <option value='silent'>Silent</option>
98 html += `<option value="${voice.voice_id}">${voice.name}</option>`;98 </select>
99 }99 </div>
100 }100 </div>`;
101 html += `</select>101
102 </div>102 html += `<div class='at-settings-row'>
103 <div class="at-settings-option">103 <div class='at-settings-option'>
104 <label for="language_options">Language</label>104 <label for='narrator_voice'>Narrator Voice</label>
105 <select id="language_options">`;105 <select id='narrator_voice'>`;
106 for (let language in this.languageLabels) {106 if (this.voices) {
107 html += `<option value="${this.languageLabels[language]}" ${this.languageLabels[language] === this.settings?.language ? 'selected="selected"' : ''}>${language}</option>`;107 for (let voice of this.voices) {
108 }108 html += `<option value='${voice.voice_id}'>${voice.name}</option>`;
109 html += `</select>109 }
110 </div>110 }
111 </div>`;111 html += `</select>
112112 </div>
113113 <div class='at-settings-option'>
114 html += `<div class="at-model-endpoint-row">114 <label for='language_options'>Language</label>
115 <div class="at-model-option">115 <select id='language_options'>`;
116 <label for="switch_model">Switch Model</label>116 for (let language in this.languageLabels) {
117 <select id="switch_model">117 html += `<option value='${this.languageLabels[language]}' ${this.languageLabels[language] === this.settings?.language ? 'selected="selected"' : ''}>${language}</option>`;
118 <option value="api_tts">API TTS</option>118 }
119 <option value="api_local">API Local</option>119 html += `</select>
120 <option value="xttsv2_local">XTTSv2 Local</option>120 </div>
121 </select>121 </div>`;
122 </div>122
123 </div>123 html += `<div class='at-settings-row'>
124124 <div class='at-settings-option'>
125 <div class="at-model-endpoint-row">125 <label for='rvc_character_voice'>RVC Character</label>
126126 <select id='rvc_character_voice'>`;
127 <div class="at-endpoint-option">127 if (this.rvcVoices) {
128 <label for="at_server">AllTalk Endpoint:</label>128 for (let rvccharvoice of this.rvcVoices) {
129 <input id="at_server" type="text" class="text_pole" maxlength="80" value="${this.settings.provider_endpoint}"/>129 html += `<option value='${rvccharvoice.voice_id}'>${rvccharvoice.name}</option>`;
130 <i><b>Important:</b> Must match IP address & port in AllTalk settings.</i>130 }
131 </div>131 }
132 </div>`;132 html += `</select>
133133 </div>
134134 <div class='at-settings-option'>
135 html += `<div class="at-model-endpoint-row">135 <label for='rvc_narrator_voice'>RVC Narrator</label>
136 <div class="at-settings-option">136 <select id='rvc_narrator_voice'>`;
137 <label for="low_vram">Low VRAM</label>137 if (this.rvcVoices) {
138 <input id="low_vram" type="checkbox"/>138 for (let rvcnarrvoice of this.rvcVoices) {
139 </div>139 html += `<option value='${rvcnarrvoice.voice_id}'>${rvcnarrvoice.name}</option>`;
140 <div class="at-settings-option">140 }
141 <label for="deepspeed">DeepSpeed</label>141 }
142 <input id="deepspeed" type="checkbox"/>142 html += `</select>
143 </div>143 </div>
144 <div class="at-settings-option status-option">144</div>`;
145 <span>Status: <span id="status_info">Ready</span></span>145
146 </div>146 html += `<div class='at-settings-row'>
147 <div class="at-settings-option empty-option">147 <div class='at-settings-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++) {
151151 const selected = i === 0 ? 'selected="selected"' : '';
152152 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'>
157157 <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>`;
162162 }
163 html += `<div class="at-website-row">163 html += `</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'>
168168 <div class='at-model-option'>
169 return html;169 <label for='switch_model'>Switch Model</label>
170 }170 <select id='switch_model'>
171171 <!-- Options will be dynamically populated -->
172172 </select>
173 //#################//173 </div>
174 // Startup ST & AT //174
175 //#################//175 <div class='at-endpoint-option'>
176176 <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>
179179 </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 settings183 <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'>
192192 <div class='at-settings-option'>
193 // Update UI elements to reflect the loaded settings193 <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'>
201201 <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 -->
205205 </div>
206 async initEndpoint() {206</div>`;
207 try {207
208 // Check if TTS provider is ready208 html += `<div class='at-website-row'>
209 this.setupEventListeners();209 <div class='at-website-option'>
210 this.updateLanguageDropdown();210 <span>AllTalk V2<a target='_blank' href='${this.settings.provider_endpoint}'>Config & Docs</a>.</span>
211 await this.checkReady();211 </div>
212 await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server212
213 await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready213 <div class='at-website-option'>
214 this.updateNarratorVoicesDropdown();214 <span>AllTalk <a target='_blank' href='https://github.com/erew123/alltalk_tts/'>Website</a>.</span>
215 this.applySettingsToHTML();215 </div>
216 updateStatus('Ready');216</div>`;
217 } catch (error) {217
218 console.error('Error loading settings:', error);218 html += `<div class='at-website-row'>
219 updateStatus('Offline');219<div class='at-website-option'>
220 }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 }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>
222222<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 applySettingsToHTML() {223</div>
224 // Apply loaded settings or use defaults224</div>`;
225 const narratorVoiceSelect = document.getElementById('narrator_voice');225
226 const atNarratorSelect = document.getElementById('at_narrator_enabled');226 return html;
227 const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside');227 }
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 dropdown230 //#################//
231 if (narratorVoiceSelect && this.settings.narrator_voice) {231 // Startup ST & AT //
232 narratorVoiceSelect.value = this.settings.narrator_voice.replace('.wav', '');232 //#################//
233 }233
234 // Apply settings to AT Narrator Enabled dropdown234 async loadSettings(settings) {
235 if (atNarratorSelect) {235 updateStatus('Offline');
236 // Sync the state with the checkbox in index.js236
237 const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); // Access the checkbox from index.js237 if (Object.keys(settings).length === 0) {
238 const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); // Access the checkbox from index.js238 console.info('Using default AllTalk TTS Provider settings');
239 const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js239 } else {
240 // Sync the state with the checkbox in index.js240 // Populate settings with provided values, ignoring server-provided settings
241 if (this.settings.narrator_enabled) {241 for (const key in settings) {
242 ttsPassAsterisksCheckbox.checked = false;242 if (key in this.settings) {
243 $('#tts_pass_asterisks').click(); // Simulate a click event243 this.settings[key] = settings[key];
244 $('#tts_pass_asterisks').trigger('change');244 } else {
245 }245 console.debug(`Ignoring non-user-configurable setting: ${key}`);
246 if (!this.settings.narrator_enabled) {246 }
247 ttsPassAsterisksCheckbox.checked = true;247 }
248 $('#tts_pass_asterisks').click(); // Simulate a click event248 }
249 $('#tts_pass_asterisks').trigger('change');249
250 }250 // Update UI elements to reflect the loaded settings
251 // Uncheck and set tts_narrate_quoted to false if narrator is enabled251 $('#at_server').val(this.settings.provider_endpoint);
252 if (this.settings.narrator_enabledd) {252 $('#language_options').val(this.settings.language);
253 ttsNarrateQuotedCheckbox.checked = true;253 $('#at_generation_method').val(this.settings.at_generation_method);
254 ttsNarrateDialoguesCheckbox.checked = true;254 $('#at_narrator_enabled').val(this.settings.narrator_enabled);
255 // Trigger click events instead of change events255 $('#at_narrator_text_not_inside').val(this.settings.at_narrator_text_not_inside);
256 $('#tts_narrate_quoted').click();256 $('#narrator_voice').val(this.settings.narrator_voice_gen);
257 $('#tts_narrate_quoted').trigger('change');257 $('#rvc_character_voice').val(this.settings.rvc_character_voice);
258 $('#tts_narrate_dialogues').click();258 $('#rvc_narrator_voice').val(this.settings.rvc_narrator_voice);
259 $('#tts_narrate_dialogues').trigger('change');259 $('#rvc_character_pitch').val(this.settings.rvc_character_pitch);
260 }260 $('#rvc_narrator_pitch').val(this.settings.rvc_narrator_pitch);
261 atNarratorSelect.value = this.settings.narrator_enabled.toString();261
262 this.settings.narrator_enabled = this.settings.narrator_enabled.toString();262 console.debug('AllTalkTTS: Settings loaded');
263 }263 try {
264 // Apply settings to the Language dropdown264 // Check if TTS provider is ready
265 const languageSelect = document.getElementById('language_options');265 await this.checkReady();
266 if (languageSelect && this.settings.language) {266 await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server
267 languageSelect.value = this.settings.language;267 await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready
268 }268 await this.fetchRvcVoiceObjects(); // Fetch RVC voices
269 // Apply settings to Text Not Inside dropdown269 this.updateNarratorVoicesDropdown();
270 if (textNotInsideSelect && this.settings.text_not_inside) {270 this.updateLanguageDropdown();
271 textNotInsideSelect.value = this.settings.text_not_inside;271 this.setupEventListeners();
272 this.settings.at_narrator_text_not_inside = this.settings.text_not_inside;272 this.applySettingsToHTML();
273 }273 updateStatus('Ready');
274 // Apply settings to Generation Method dropdown274 } catch (error) {
275 if (generationMethodSelect && this.settings.at_generation_method) {275 console.error('Error loading settings:', error);
276 generationMethodSelect.value = this.settings.at_generation_method;276 updateStatus('Offline');
277 }277 }
278 // Additional logic to disable/enable dropdowns based on the selected generation method278 }
279 const isStreamingEnabled = this.settings.at_generation_method === 'streaming_enabled';279
280 if (isStreamingEnabled) {280
281 // Disable certain dropdowns when streaming is enabled281 applySettingsToHTML() {
282 if (atNarratorSelect) atNarratorSelect.disabled = true;282 const narratorVoiceSelect = document.getElementById('narrator_voice');
283 if (textNotInsideSelect) textNotInsideSelect.disabled = true;283 const atNarratorSelect = document.getElementById('at_narrator_enabled');
284 if (narratorVoiceSelect) narratorVoiceSelect.disabled = true;284 const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside');
285 } else {285 const generationMethodSelect = document.getElementById('at_generation_method');
286 // Enable dropdowns for standard generation286 this.settings.narrator_voice = this.settings.narrator_voice_gen;
287 if (atNarratorSelect) atNarratorSelect.disabled = false;287 // Apply settings to Narrator Voice dropdown
288 if (textNotInsideSelect) textNotInsideSelect.disabled = !this.settings.narrator_enabled;288 if (narratorVoiceSelect && this.settings.narrator_voice) {
289 if (narratorVoiceSelect) narratorVoiceSelect.disabled = !this.settings.narrator_enabled;289 narratorVoiceSelect.value = this.settings.narrator_voice; // Remove the parentheses
290 }290 }
291 const modelSelect = document.getElementById('switch_model');291 // Apply settings to AT Narrator Enabled dropdown
292 if (this.settings.finetuned_model === 'true') {292 if (atNarratorSelect) {
293 const ftOption = document.createElement('option');293 const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks');
294 ftOption.value = 'XTTSv2 FT';294 const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted');
295 ftOption.textContent = 'XTTSv2 FT';295 const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues');
296 modelSelect.appendChild(ftOption);296 if (this.settings.narrator_enabled) {
297 }297 ttsPassAsterisksCheckbox.checked = false;
298 }298 $('#tts_pass_asterisks').click();
299299 $('#tts_pass_asterisks').trigger('change');
300 //##############################//300 }
301 // Check AT Server is Available //301 if (!this.settings.narrator_enabled) {
302 //##############################//302 ttsPassAsterisksCheckbox.checked = true;
303303 $('#tts_pass_asterisks').click();
304 async checkReady() {304 $('#tts_pass_asterisks').trigger('change');
305 try {305 }
306 const response = await fetch(`${this.settings.provider_endpoint}/api/ready`);306 if (this.settings.narrator_enabled) {
307 // Check if the HTTP request was successful307 ttsNarrateQuotedCheckbox.checked = true;
308 if (!response.ok) {308 ttsNarrateDialoguesCheckbox.checked = true;
309 throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`);309 $('#tts_narrate_quoted').click();
310 }310 $('#tts_narrate_quoted').trigger('change');
311 const statusText = await response.text();311 $('#tts_narrate_dialogues').click();
312 // Check if the response is 'Ready'312 $('#tts_narrate_dialogues').trigger('change');
313 if (statusText === 'Ready') {313 }
314 this.ready = true; // Set the ready flag to true314 atNarratorSelect.value = this.settings.narrator_enabled.toString();
315 console.log('TTS service is ready.');315 this.settings.narrator_enabled = this.settings.narrator_enabled.toString();
316 } else {316 }
317 this.ready = false;317 const languageSelect = document.getElementById('language_options');
318 console.log('TTS service is not ready.');318 if (languageSelect && this.settings.language) {
319 }319 languageSelect.value = this.settings.language;
320 } catch (error) {320 }
321 console.error('Error checking TTS service readiness:', error);321 if (textNotInsideSelect && this.settings.text_not_inside) {
322 this.ready = false; // Ensure ready flag is set to false in case of error322 textNotInsideSelect.value = this.settings.text_not_inside;
323 throw error; // Rethrow the error for further handling323 this.settings.at_narrator_text_not_inside = this.settings.text_not_inside;
324 }324 }
325 }325 if (generationMethodSelect && this.settings.at_generation_method) {
326326 generationMethodSelect.value = this.settings.at_generation_method;
327 //######################//327 }
328 // Get Available Voices //328 const isStreamingEnabled = this.settings.at_generation_method === 'streaming_enabled';
329 //######################//329 if (isStreamingEnabled) {
330330 if (atNarratorSelect) atNarratorSelect.disabled = true;
331 async fetchTtsVoiceObjects() {331 if (textNotInsideSelect) textNotInsideSelect.disabled = true;
332 const response = await fetch(`${this.settings.provider_endpoint}/api/voices`);332 if (narratorVoiceSelect) narratorVoiceSelect.disabled = true;
333 if (!response.ok) {333 } else {
334 const errorText = await response.text();334 if (atNarratorSelect) atNarratorSelect.disabled = false;
335 throw new Error(`HTTP ${response.status}: ${errorText}`);335 if (textNotInsideSelect) textNotInsideSelect.disabled = !this.settings.narrator_enabled;
336 }336 if (narratorVoiceSelect) narratorVoiceSelect.disabled = !this.settings.narrator_enabled;
337 const data = await response.json();337 }
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 // Check AT Server is Available //
343 preview_url: null, // Preview URL will be dynamically generated343 //##############################//
344 lang: 'en', // Default language344
345 };345 async checkReady() {
346 });346 try {
347 this.voices = voices; // Assign to the class property347 const response = await fetch(`${this.settings.provider_endpoint}/api/ready`);
348 return voices; // Also return this list348 // Check if the HTTP request was successful
349 }349 if (!response.ok) {
350350 throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`);
351 //##########################################//351 }
352 // Get Current AT Server Config & Update ST //352 const statusText = await response.text();
353 //##########################################//353 // Check if the response is 'Ready'
354354 if (statusText === 'Ready') {
355 async updateSettingsFromServer() {355 this.ready = true; // Set the ready flag to true
356 try {356 console.log('TTS service is ready.');
357 // Fetch current settings357 } else {
358 const response = await fetch(`${this.settings.provider_endpoint}/api/currentsettings`);358 this.ready = false;
359 if (!response.ok) {359 console.log('TTS service is not ready.');
360 throw new Error(`Failed to fetch current settings: ${response.statusText}`);360 }
361 }361 } catch (error) {
362 const currentSettings = await response.json();362 console.error('Error checking TTS service readiness:', error);
363 // Update internal settings363 this.ready = false; // Ensure ready flag is set to false in case of error
364 this.settings.modelsAvailable = currentSettings.models_available;364 }
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 // Get Available Voices //
369 this.settings.finetuned_model = currentSettings.finetuned_model;369 //######################//
370 // Update HTML elements370
371 this.updateModelDropdown();371 async fetchTtsVoiceObjects() {
372 this.updateCheckboxes();372 const response = await fetch(`${this.settings.provider_endpoint}/api/voices`);
373 } catch (error) {373 if (!response.ok) {
374 console.error(`Error updating settings from server: ${error}`);374 const errorText = await response.text();
375 }375 throw new Error(`HTTP ${response.status}: ${errorText}`);
376 }376 }
377377 const data = await response.json();
378 //###################################################//378 const voices = data.voices.map(filename => {
379 // Get Current AT Server Config & Update ST (Models) //379 return {
380 //###################################################//380 name: filename,
381381 voice_id: filename,
382 updateModelDropdown() {382 preview_url: null, // Preview URL will be dynamically generated
383 const modelSelect = document.getElementById('switch_model');383 lang: 'en', // Default language
384 if (modelSelect) {384 };
385 modelSelect.innerHTML = ''; // Clear existing options385 });
386 this.settings.modelsAvailable.forEach(model => {386 this.voices = voices; // Assign to the class property
387 const option = document.createElement('option');387 return voices; // Also return this list
388 option.value = model.model_name;388 }
389 option.textContent = model.model_name; // Use model_name instead of name389
390 option.selected = model.model_name === this.settings.currentModel;390 async fetchRvcVoiceObjects() {
391 modelSelect.appendChild(option);391 if (this.settings.server_version !== 'v2') {
392 });392 console.log('Skipping RVC voices fetch for V1 server');
393 }393 return [];
394 }394 }
395395
396 //#######################################################//396 console.log('Fetching RVC Voices');
397 // Get Current AT Server Config & Update ST (DS and LVR) //397 try {
398 //#######################################################//398 const response = await fetch(`${this.settings.provider_endpoint}/api/rvcvoices`);
399399 if (!response.ok) {
400 updateCheckboxes() {400 const errorText = await response.text();
401 const deepspeedCheckbox = document.getElementById('deepspeed');401 console.error('Error text:', errorText);
402 const lowVramCheckbox = document.getElementById('low_vram');402 throw new Error(`HTTP ${response.status}: ${errorText}`);
403 if (lowVramCheckbox) lowVramCheckbox.checked = this.settings.lowVramEnabled;403 }
404 if (deepspeedCheckbox) {404
405 deepspeedCheckbox.checked = this.settings.deepSpeedEnabled;405 const data = await response.json();
406 deepspeedCheckbox.disabled = !this.settings.deepspeed_available; // Disable checkbox if deepspeed is not available406 if (!data || !data.rvcvoices) {
407 }407 console.error('Invalid data format:', data);
408 }408 throw new Error('Invalid data format received from /api/rvcvoices');
409409 }
410 //###############################################################//410
411 // Get Current AT Server Config & Update ST (AT Narrator Voices) //411 const voices = data.rvcvoices.map(filename => {
412 //###############################################################//412 return {
413413 name: filename,
414 updateNarratorVoicesDropdown() {414 voice_id: filename,
415 const narratorVoiceSelect = document.getElementById('narrator_voice');415 };
416 if (narratorVoiceSelect && this.voices) {416 });
417 // Clear existing options417
418 narratorVoiceSelect.innerHTML = '';418 console.log('RVC voices:', voices);
419 // Add new options419 this.rvcVoices = voices; // Assign to the class property
420 for (let voice of this.voices) {420 this.updateRvcVoiceDropdowns(); // Update UI after fetching voices
421 const option = document.createElement('option');421 return voices; // Also return this list
422 option.value = voice.voice_id;422 } catch (error) {
423 option.textContent = voice.name;423 console.error('Error fetching RVC voices:', error);
424 narratorVoiceSelect.appendChild(option);424 this.rvcVoices = [{ name: 'Disabled', voice_id: 'Disabled' }]; // Set default on error
425 }425 throw error;
426 }426 } finally {
427 }427 // Ensure dropdowns are updated even if there was an error
428428 this.updateRvcVoiceDropdowns();
429 //######################################################//429 }
430 // Get Current AT Server Config & Update ST (Languages) //430 }
431 //######################################################//431
432432 //##########################################//
433 updateLanguageDropdown() {433 // Get Current AT Server Config & Update ST //
434 const languageSelect = document.getElementById('language_options');434 //##########################################//
435 if (languageSelect) {435
436 // Ensure default language is set (??? whatever that means)436 async updateSettingsFromServer() {
437 // this.settings.language = this.settings.language;437 try {
438438 const response = await fetch(`${this.settings.provider_endpoint}/api/currentsettings`);
439 languageSelect.innerHTML = '';439 if (!response.ok) {
440 for (let language in this.languageLabels) {440 throw new Error(`Failed to fetch current settings: ${response.statusText}`);
441 const option = document.createElement('option');441 }
442 option.value = this.languageLabels[language];442 const currentSettings = await response.json();
443 option.textContent = language;443 currentSettings.models_available.sort((a, b) => a.name.localeCompare(b.name));
444 if (this.languageLabels[language] === this.settings.language) {444
445 option.selected = true;445 this.settings.enginesAvailable = currentSettings.engines_available;
446 }446 this.settings.currentEngineLoaded = currentSettings.current_engine_loaded;
447 languageSelect.appendChild(option);447 this.settings.modelsAvailable = currentSettings.models_available;
448 }448 this.settings.currentModel = currentSettings.current_model_loaded;
449 }449 this.settings.deepspeed_capable = currentSettings.deepspeed_capable;
450 }450 this.settings.deepspeed_available = currentSettings.deepspeed_available;
451451 this.settings.deepspeed_enabled = currentSettings.deepspeed_enabled;
452 //########################################//452 this.settings.lowvram_capable = currentSettings.lowvram_capable;
453 // Start AT TTS extenstion page listeners //453 this.settings.lowvram_enabled = currentSettings.lowvram_enabled;
454 //########################################//454
455455 await this.fetchRvcVoiceObjects(); // Fetch RVC voices
456 setupEventListeners() {456
457457 this.updateModelDropdown();
458 let debounceTimeout;458 this.updateCheckboxes();
459 const debounceDelay = 500; // Milliseconds459 this.updateRvcVoiceDropdowns(); // Update the RVC voice dropdowns
460460 } catch (error) {
461 // Define the event handler function461 console.error(`Error updating settings from server: ${error}`);
462 const onModelSelectChange = async (event) => {462 }
463 console.log('Model select change event triggered'); // Debugging statement463 }
464 const selectedModel = event.target.value;464
465 console.log(`Selected model: ${selectedModel}`); // Debugging statement465 updateRvcVoiceDropdowns() {
466 // Set status to Processing466 // Handle all RVC-related elements
467 updateStatus('Processing');467 const rvcElements = document.querySelectorAll('.rvc-setting');
468 try {468 const isV2 = this.settings.server_version === 'v2';
469 const response = await fetch(`${this.settings.provider_endpoint}/api/reload?tts_method=${encodeURIComponent(selectedModel)}`, {469
470 method: 'POST',470 rvcElements.forEach(element => {
471 });471 element.style.display = isV2 ? 'block' : 'none';
472 if (!response.ok) {472 });
473 throw new Error(`HTTP Error: ${response.status}`);473
474 }474 // Update and disable/enable character voice dropdown
475 const data = await response.json();475 const rvcCharacterVoiceSelect = document.getElementById('rvc_character_voice');
476 console.log('POST response data:', data); // Debugging statement476 if (rvcCharacterVoiceSelect) {
477 // Set status to Ready if successful477 rvcCharacterVoiceSelect.disabled = !isV2;
478 updateStatus('Ready');478 if (this.rvcVoices) {
479 } catch (error) {479 rvcCharacterVoiceSelect.innerHTML = '';
480 console.error('POST request error:', error); // Debugging statement480 for (let voice of this.rvcVoices) {
481 // Set status to Error in case of failure481 const option = document.createElement('option');
482 updateStatus('Error');482 option.value = voice.voice_id;
483 }483 option.textContent = voice.name;
484484 if (voice.voice_id === this.settings.rvc_character_voice) {
485 // Handle response or error485 option.selected = true;
486 };486 }
487487 rvcCharacterVoiceSelect.appendChild(option);
488 const debouncedModelSelectChange = (event) => {488 }
489 clearTimeout(debounceTimeout);489 }
490 debounceTimeout = setTimeout(() => {490 }
491 onModelSelectChange(event);491
492 }, debounceDelay);492 // Update and disable/enable narrator voice dropdown
493 };493 const rvcNarratorVoiceSelect = document.getElementById('rvc_narrator_voice');
494494 if (rvcNarratorVoiceSelect) {
495 // Switch Model Listener495 rvcNarratorVoiceSelect.disabled = !isV2;
496 const modelSelect = document.getElementById('switch_model');496 if (this.rvcVoices) {
497 if (modelSelect) {497 rvcNarratorVoiceSelect.innerHTML = '';
498 // Remove the event listener if it was previously added498 for (let voice of this.rvcVoices) {
499 // Add the debounced event listener499 const option = document.createElement('option');
500 $(modelSelect).off('change').on('change', debouncedModelSelectChange);500 option.value = voice.voice_id;
501 }501 option.textContent = voice.name;
502502 if (voice.voice_id === this.settings.rvc_narrator_voice) {
503 // DeepSpeed Listener503 option.selected = true;
504 const deepspeedCheckbox = document.getElementById('deepspeed');504 }
505 if (deepspeedCheckbox) {505 rvcNarratorVoiceSelect.appendChild(option);
506 $(deepspeedCheckbox).off('change').on('change', async (event) => {506 }
507 const deepSpeedValue = event.target.checked ? 'True' : 'False';507 }
508 // Set status to Processing508 }
509 updateStatus('Processing');509
510 try {510 // Update pitch inputs
511 const response = await fetch(`${this.settings.provider_endpoint}/api/deepspeed?new_deepspeed_value=${deepSpeedValue}`, {511 const characterPitch = document.getElementById('rvc_character_pitch');
512 method: 'POST',512 if (characterPitch) {
513 });513 characterPitch.disabled = !isV2;
514 if (!response.ok) {514 }
515 throw new Error(`HTTP Error: ${response.status}`);515
516 }516 const narratorPitch = document.getElementById('rvc_narrator_pitch');
517 const data = await response.json();517 if (narratorPitch) {
518 console.log('POST response data:', data); // Debugging statement518 narratorPitch.disabled = !isV2;
519 // Set status to Ready if successful519 }
520 updateStatus('Ready');520 }
521 } catch (error) {521
522 console.error('POST request error:', error); // Debugging statement522 //###################################################//
523 // Set status to Error in case of failure523 // Get Current AT Server Config & Update ST (Models) //
524 updateStatus('Error');524 //###################################################//
525 }525
526 });526 updateModelDropdown() {
527 }527 const modelSelect = document.getElementById('switch_model');
528528 if (modelSelect) {
529 // Low VRAM Listener529 modelSelect.innerHTML = ''; // Clear existing options
530 const lowVramCheckbox = document.getElementById('low_vram');530 this.settings.modelsAvailable.forEach(model => {
531 if (lowVramCheckbox) {531 const option = document.createElement('option');
532 $(lowVramCheckbox).off('change').on('change', async (event) => {532 option.value = model.name;
533 const lowVramValue = event.target.checked ? 'True' : 'False';533 option.textContent = model.name; // Use model name directly
534 // Set status to Processing534 option.selected = model.name === this.settings.currentModel;
535 updateStatus('Processing');535 modelSelect.appendChild(option);
536 try {536 });
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 // Get Current AT Server Config & Update ST (DS and LVR) //
542 }542 //#######################################################//
543 const data = await response.json();543
544 console.log('POST response data:', data); // Debugging statement544 updateCheckboxes() {
545 // Set status to Ready if successful545 const deepspeedCheckbox = document.getElementById('deepspeed');
546 updateStatus('Ready');546 const lowVramCheckbox = document.getElementById('low_vram');
547 } catch (error) {547
548 console.error('POST request error:', error); // Debugging statement548 // Handle DeepSpeed checkbox
549 // Set status to Error in case of failure549 if (deepspeedCheckbox) {
550 updateStatus('Error');550 if (this.settings.deepspeed_capable) {
551 }551 // If TTS engine is capable of using DeepSpeed
552 });552 deepspeedCheckbox.disabled = !this.settings.deepspeed_available;
553 }553 this.settings.deepspeed_enabled = this.settings.deepspeed_available && this.settings.deepspeed_enabled;
554554 } else {
555 // Narrator Voice Dropdown Listener555 // If TTS engine is NOT capable of using DeepSpeed
556 const narratorVoiceSelect = document.getElementById('narrator_voice');556 deepspeedCheckbox.disabled = true;
557 if (narratorVoiceSelect) {557 this.settings.deepspeed_enabled = false;
558 $(narratorVoiceSelect).off('change').on('change', (event) => {558 }
559 this.settings.narrator_voice_gen = `${event.target.value}.wav`;559 deepspeedCheckbox.checked = this.settings.deepspeed_enabled;
560 this.onSettingsChange(); // Save the settings after change560 }
561 });561
562 }562 // Handle Low VRAM checkbox
563563 if (lowVramCheckbox) {
564 const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside');564 if (this.settings.lowvram_capable) {
565 if (textNotInsideSelect) {565 // If TTS engine is capable of low VRAM
566 $(textNotInsideSelect).off('change').on('change', (event) => {566 lowVramCheckbox.disabled = false;
567 this.settings.text_not_inside = event.target.value;567 } else {
568 this.onSettingsChange(); // Save the settings after change568 // If TTS engine is NOT capable of low VRAM
569 });569 lowVramCheckbox.disabled = true;
570 }570 this.settings.lowvram_enabled = false;
571571 }
572 // AT Narrator Dropdown Listener572 lowVramCheckbox.checked = this.settings.lowvram_enabled;
573 const atNarratorSelect = document.getElementById('at_narrator_enabled');573 }
574 const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); // Access the checkbox from index.js574 }
575 const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); // Access the checkbox from index.js575
576 const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js576
577577 //###############################################################//
578 if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) {578 // Get Current AT Server Config & Update ST (AT Narrator Voices) //
579 $(atNarratorSelect).off('change').on('change', (event) => {579 //###############################################################//
580 const isNarratorEnabled = event.target.value === 'true';580
581 this.settings.narrator_enabled = isNarratorEnabled; // Update the setting here581 updateNarratorVoicesDropdown() {
582 textNotInsideSelect.disabled = !isNarratorEnabled;582 const narratorVoiceSelect = document.getElementById('narrator_voice');
583 narratorVoiceSelect.disabled = !isNarratorEnabled;583 if (narratorVoiceSelect && this.voices) {
584584 // Clear existing options
585 // Sync the state with the checkbox in index.js585 narratorVoiceSelect.innerHTML = '';
586 if (isNarratorEnabled) {586 // Add new options
587 ttsPassAsterisksCheckbox.checked = false;587 for (let voice of this.voices) {
588 $('#tts_pass_asterisks').click(); // Simulate a click event588 const option = document.createElement('option');
589 $('#tts_pass_asterisks').trigger('change');589 option.value = voice.voice_id;
590 }590 option.textContent = voice.name;
591 if (!isNarratorEnabled) {591 narratorVoiceSelect.appendChild(option);
592 ttsPassAsterisksCheckbox.checked = true;592 }
593 $('#tts_pass_asterisks').click(); // Simulate a click event593 }
594 $('#tts_pass_asterisks').trigger('change');594 }
595 }595
596 // Uncheck and set tts_narrate_quoted to false if narrator is enabled596 //######################################################//
597 if (isNarratorEnabled) {597 // Get Current AT Server Config & Update ST (Languages) //
598 ttsNarrateQuotedCheckbox.checked = true;598 //######################################################//
599 ttsNarrateDialoguesCheckbox.checked = true;599
600 // Trigger click events instead of change events600 updateLanguageDropdown() {
601 $('#tts_narrate_quoted').click();601 const languageSelect = document.getElementById('language_options');
602 $('#tts_narrate_quoted').trigger('change');602 if (languageSelect) {
603 $('#tts_narrate_dialogues').click();603 // Ensure default language is set
604 $('#tts_narrate_dialogues').trigger('change');604 this.settings.language = this.settings.language || 'en';
605 }605
606 this.onSettingsChange(); // Save the settings after change606 languageSelect.innerHTML = '';
607 });607 for (let language in this.languageLabels) {
608 }608 const option = document.createElement('option');
609609 option.value = this.languageLabels[language];
610610 option.textContent = language;
611 // Event Listener for AT Generation Method Dropdown611 if (this.languageLabels[language] === this.settings.language) {
612 const atGenerationMethodSelect = document.getElementById('at_generation_method');612 option.selected = true;
613 const atNarratorEnabledSelect = document.getElementById('at_narrator_enabled');613 }
614 if (atGenerationMethodSelect) {614 languageSelect.appendChild(option);
615 $(atGenerationMethodSelect).off('change').on('change', (event) => {615 }
616 const selectedMethod = event.target.value;616 }
617617 }
618 if (selectedMethod === 'streaming_enabled') {618
619 // Disable and unselect AT Narrator619 //########################################//
620 atNarratorEnabledSelect.disabled = true;620 // Start AT TTS extenstion page listeners //
621 atNarratorEnabledSelect.value = 'false';621 //########################################//
622 textNotInsideSelect.disabled = true;622
623 narratorVoiceSelect.disabled = true;623 setupEventListeners() {
624 } else if (selectedMethod === 'standard_generation') {624 // Define the event handler function
625 // Enable AT Narrator625 const onModelSelectChange = async (event) => {
626 atNarratorEnabledSelect.disabled = false;626 console.log('Model select change event triggered');
627 }627 const selectedModel = event.target.value;
628 this.settings.at_generation_method = selectedMethod; // Update the setting here628 console.log(`Selected model: ${selectedModel}`);
629 this.onSettingsChange(); // Save the settings after change629 updateStatus('Processing');
630 });630 try {
631 }631 const response = await fetch(`${this.settings.provider_endpoint}/api/reload?tts_method=${encodeURIComponent(selectedModel)}`, {
632632 method: 'POST',
633 // Listener for Language Dropdown633 });
634 const languageSelect = document.getElementById('language_options');634 if (!response.ok) {
635 if (languageSelect) {635 throw new Error(`HTTP Error: ${response.status}`);
636 $(languageSelect).off('change').on('change', (event) => {636 }
637 this.settings.language = event.target.value;637 const data = await response.json();
638 this.onSettingsChange(); // Save the settings after change638 console.log('POST response data:', data);
639 });639 updateStatus('Ready');
640 }640 } catch (error) {
641641 console.error('POST request error:', error);
642 // Listener for AllTalk Endpoint Input642 updateStatus('Error');
643 const atServerInput = document.getElementById('at_server');643 }
644 if (atServerInput) {644 };
645 $(atServerInput).off('input').on('input', (event) => {645
646 this.settings.provider_endpoint = event.target.value;646 // Switch Model Listener with debounce
647 this.onSettingsChange(); // Save the settings after change647 const modelSelect = document.getElementById('switch_model');
648 });648 if (modelSelect) {
649 }649 const debouncedModelSelectChange = debounce(onModelSelectChange, 1400);
650650 modelSelect.addEventListener('change', debouncedModelSelectChange);
651 }651 }
652652
653 //#############################//653 // AllTalk Server version change listener
654 // Store ST interface settings //654 const serverVersionSelect = document.getElementById('server_version');
655 //#############################//655 if (serverVersionSelect) {
656656 serverVersionSelect.addEventListener('change', async (event) => {
657 onSettingsChange() {657 this.settings.server_version = event.target.value;
658 // Update settings based on the UI elements658 if (event.target.value === 'v2') {
659 //this.settings.provider_endpoint = $('#at_server').val();659 await this.fetchRvcVoiceObjects();
660 this.settings.language = $('#language_options').val();660 }
661 //this.settings.voiceMap = $('#voicemap').val();661 this.updateRvcVoiceDropdowns();
662 this.settings.at_generation_method = $('#at_generation_method').val();662 this.onSettingsChange();
663 this.settings.narrator_enabled = $('#at_narrator_enabled').val();663 });
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
666 // Save the updated settings666 // RVC Voice and Pitch listeners
667 saveTtsProviderSettings();667 const rvcCharacterVoiceSelect = document.getElementById('rvc_character_voice');
668 }668 if (rvcCharacterVoiceSelect) {
669669 rvcCharacterVoiceSelect.addEventListener('change', (event) => {
670 //#########################//670 this.settings.rvccharacter_voice_gen = event.target.value;
671 // ST Handle Reload button //671 this.onSettingsChange();
672 //#########################//672 });
673673 }
674 async onRefreshClick() {674
675 await this.initEndpoint();675 const rvcNarratorVoiceSelect = document.getElementById('rvc_narrator_voice');
676 // Additional actions as needed676 if (rvcNarratorVoiceSelect) {
677 }677 rvcNarratorVoiceSelect.addEventListener('change', (event) => {
678678 this.settings.rvcnarrator_voice_gen = event.target.value;
679 //##################//679 this.onSettingsChange();
680 // Preview AT Voice //680 });
681 //##################//681 }
682682
683 async previewTtsVoice(voiceName) {683 const rvcCharacterPitchSelect = document.getElementById('rvc_character_pitch');
684 try {684 if (rvcCharacterPitchSelect) {
685 // Prepare data for POST request685 rvcCharacterPitchSelect.addEventListener('change', (event) => {
686 const postData = new URLSearchParams();686 this.settings.rvc_character_pitch = event.target.value;
687 postData.append('voice', `${voiceName}.wav`);687 this.onSettingsChange();
688 // Making the POST request688 });
689 const response = await fetch(`${this.settings.provider_endpoint}/api/previewvoice/`, {689 }
690 method: 'POST',690
691 headers: {691 const rvcNarratorPitchSelect = document.getElementById('rvc_narrator_pitch');
692 'Content-Type': 'application/x-www-form-urlencoded',692 if (rvcNarratorPitchSelect) {
693 },693 rvcNarratorPitchSelect.addEventListener('change', (event) => {
694 body: postData,694 this.settings.rvc_narrator_pitch = event.target.value;
695 });695 this.onSettingsChange();
696 if (!response.ok) {696 });
697 const errorText = await response.text();697 }
698 console.error('[previewTtsVoice] Error Response Text:', errorText);698
699 throw new Error(`HTTP ${response.status}: ${errorText}`);699 // DeepSpeed Listener
700 }700 const deepspeedCheckbox = document.getElementById('deepspeed');
701 // Assuming the server returns a URL to the .wav file701 if (deepspeedCheckbox) {
702 const data = await response.json();702 const handleDeepSpeedChange = async (event) => {
703 if (data.output_file_url) {703 const deepSpeedValue = event.target.checked ? 'True' : 'False';
704 // Use an audio element to play the .wav file704 updateStatus('Processing');
705 const audioElement = new Audio(data.output_file_url);705 try {
706 audioElement.play().catch(e => console.error('Error playing audio:', e));706 const response = await fetch(`${this.settings.provider_endpoint}/api/deepspeed?new_deepspeed_value=${deepSpeedValue}`, {
707 } else {707 method: 'POST',
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 if (!response.ok) {
710 }710 throw new Error(`HTTP Error: ${response.status}`);
711711 }
712 } catch (error) {712 const data = await response.json();
713 console.error('[previewTtsVoice] Exception caught during preview generation:', error);713 console.log('POST response data:', data);
714 throw error;714 updateStatus('Ready');
715 }715 } catch (error) {
716 }716 console.error('POST request error:', error);
717717 updateStatus('Error');
718 //#####################//718 }
719 // Populate ST voices //719 };
720 //#####################//720
721721 const debouncedHandleDeepSpeedChange = debounce(handleDeepSpeedChange, 300);
722 async getVoice(voiceName, generatePreview = false) {722 deepspeedCheckbox.addEventListener('change', debouncedHandleDeepSpeedChange);
723 // Ensure this.voices is populated723 }
724 if (this.voices.length === 0) {724
725 // Fetch voice objects logic725 // Low VRAM Listener
726 }726 const lowVramCheckbox = document.getElementById('low_vram');
727 // Find the object where the name matches voiceName727 if (lowVramCheckbox) {
728 const match = this.voices.find(voice => voice.name === voiceName);728 const handleLowVramChange = async (event) => {
729 if (!match) {729 const lowVramValue = event.target.checked ? 'True' : 'False';
730 // Error handling730 updateStatus('Processing');
731 }731 try {
732 // Generate preview URL only if requested732 const response = await fetch(`${this.settings.provider_endpoint}/api/lowvramsetting?new_low_vram_value=${lowVramValue}`, {
733 if (!match.preview_url && generatePreview) {733 method: 'POST',
734 // Generate preview logic734 });
735 }735 if (!response.ok) {
736 return match; // Return the found voice object736 throw new Error(`HTTP Error: ${response.status}`);
737 }737 }
738738 const data = await response.json();
739 //##########################################//739 console.log('POST response data:', data);
740 // Generate TTS Streaming or call Standard //740 updateStatus('Ready');
741 //##########################################//741 } catch (error) {
742742 console.error('POST request error:', error);
743 async generateTts(inputText, voiceId) {743 updateStatus('Error');
744 try {744 }
745 if (this.settings.at_generation_method === 'streaming_enabled') {745 };
746 // Construct the streaming URL746
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 debouncedHandleLowVramChange = debounce(handleLowVramChange, 300);
748 console.log('Streaming URL:', streamingUrl);748 lowVramCheckbox.addEventListener('change', debouncedHandleLowVramChange);
749749 }
750 // Return the streaming URL directly750
751 return streamingUrl;751 // Other listeners without debounce since they don't need it
752 } else {752 const narratorVoiceSelect = document.getElementById('narrator_voice');
753 // For standard method753 if (narratorVoiceSelect) {
754 const outputUrl = await this.fetchTtsGeneration(inputText, voiceId);754 narratorVoiceSelect.addEventListener('change', (event) => {
755 const audioResponse = await fetch(outputUrl);755 this.settings.narrator_voice_gen = `${event.target.value}`;
756 if (!audioResponse.ok) {756 this.onSettingsChange();
757 throw new Error(`HTTP ${audioResponse.status}: Failed to fetch audio data`);757 });
758 }758 }
759 return audioResponse; // Return the fetch response directly759
760 }760 const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside');
761 } catch (error) {761 if (textNotInsideSelect) {
762 console.error('Error in generateTts:', error);762 textNotInsideSelect.addEventListener('change', (event) => {
763 throw error;763 this.settings.text_not_inside = event.target.value;
764 }764 this.onSettingsChange();
765 }765 });
766766 }
767767
768 //####################//768 // AT Narrator Dropdown Listener
769 // Generate Standard //769 const atNarratorSelect = document.getElementById('at_narrator_enabled');
770 //####################//770 const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks');
771771 const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted');
772 async fetchTtsGeneration(inputText, voiceId) {772 const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues');
773 // Prepare the request payload773
774 const requestBody = new URLSearchParams({774 if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) {
775 'text_input': inputText,775 atNarratorSelect.addEventListener('change', (event) => {
776 'text_filtering': 'standard',776 const narratorOption = event.target.value;
777 'character_voice_gen': voiceId + '.wav',777 this.settings.narrator_enabled = narratorOption;
778 'narrator_enabled': this.settings.narrator_enabled,778
779 'narrator_voice_gen': this.settings.narrator_voice_gen + '.wav',779 const isNarratorDisabled = narratorOption === 'false';
780 'text_not_inside': this.settings.at_narrator_text_not_inside,780 textNotInsideSelect.disabled = isNarratorDisabled;
781 'language': this.settings.language,781 narratorVoiceSelect.disabled = isNarratorDisabled;
782 'output_file_name': 'st_output',782
783 'output_file_timestamp': 'true',783 if (narratorOption === 'true') {
784 'autoplay': 'false',784 ttsPassAsterisksCheckbox.checked = false;
785 'autoplay_volume': '0.8',785 $('#tts_pass_asterisks').click();
786 }).toString();786 $('#tts_pass_asterisks').trigger('change');
787787 ttsNarrateQuotedCheckbox.checked = true;
788 try {788 ttsNarrateDialoguesCheckbox.checked = true;
789 const response = await doExtrasFetch(789 $('#tts_narrate_quoted').click();
790 `${this.settings.provider_endpoint}/api/tts-generate`,790 $('#tts_narrate_quoted').trigger('change');
791 {791 $('#tts_narrate_dialogues').click();
792 method: 'POST',792 $('#tts_narrate_dialogues').trigger('change');
793 headers: {793 } else if (narratorOption === 'silent') {
794 'Content-Type': 'application/x-www-form-urlencoded',794 ttsPassAsterisksCheckbox.checked = false;
795 'Cache-Control': 'no-cache',795 $('#tts_pass_asterisks').click();
796 },796 $('#tts_pass_asterisks').trigger('change');
797 body: requestBody,797 } else {
798 },798 ttsPassAsterisksCheckbox.checked = true;
799 );799 $('#tts_pass_asterisks').click();
800800 $('#tts_pass_asterisks').trigger('change');
801 if (!response.ok) {801 }
802 const errorText = await response.text();802
803 console.error('[fetchTtsGeneration] Error Response Text:', errorText);803 this.onSettingsChange();
804 // toastr.error(response.statusText, 'TTS Generation Failed');804 });
805 throw new Error(`HTTP ${response.status}: ${errorText}`);805 }
806 }806
807 const data = await response.json();807 // Event Listener for AT Generation Method Dropdown
808 const outputUrl = data.output_file_url;808 const atGenerationMethodSelect = document.getElementById('at_generation_method');
809 return outputUrl; // Return only the output_file_url809 if (atGenerationMethodSelect) {
810 } catch (error) {810 atGenerationMethodSelect.addEventListener('change', (event) => {
811 console.error('[fetchTtsGeneration] Exception caught:', error);811 const selectedMethod = event.target.value;
812 throw error; // Rethrow the error for further handling812
813 }813 if (selectedMethod === 'streaming_enabled') {
814 }814 // Disable and unselect AT Narrator
815}815 atNarratorSelect.disabled = true;
816816 atNarratorSelect.value = 'false';
817//#########################//817 textNotInsideSelect.disabled = true;
818// Update Status Messages //818 narratorVoiceSelect.disabled = true;
819//#########################//819 } else if (selectedMethod === 'standard_generation') {
820820 // Enable AT Narrator
821function updateStatus(message) {821 atNarratorSelect.disabled = false;
822 const statusElement = document.getElementById('status_info');822 }
823 if (statusElement) {823 this.settings.at_generation_method = selectedMethod;
824 statusElement.textContent = message;824 this.onSettingsChange();
825 switch (message) {825 });
826 case 'Offline':826 }
827 statusElement.style.color = 'red';827
828 break;828 // Language Dropdown Listener
829 case 'Ready':829 const languageSelect = document.getElementById('language_options');
830 statusElement.style.color = 'lightgreen';830 if (languageSelect) {
831 break;831 languageSelect.addEventListener('change', (event) => {
832 case 'Processing':832 this.settings.language = event.target.value;
833 statusElement.style.color = 'blue';833 this.onSettingsChange();
834 break;834 });
835 case 'Error':835 }
836 statusElement.style.color = 'red';836
837 break;837 // AllTalk Endpoint Input Listener
838 }838 const atServerInput = document.getElementById('at_server');
839 }839 if (atServerInput) {
840}840 atServerInput.addEventListener('input', (event) => {
841 this.settings.provider_endpoint = event.target.value;
842 this.onSettingsChange();
843 });
844 }
845 }
846
847 //#############################//
848 // Store ST interface settings //
849 //#############################//
850
851 onSettingsChange() {
852 // Update settings based on the UI elements
853 //this.settings.provider_endpoint = $('#at_server').val();
854 this.settings.language = $('#language_options').val();
855 //this.settings.voiceMap = $('#voicemap').val();
856 this.settings.at_generation_method = $('#at_generation_method').val();
857 this.settings.narrator_enabled = $('#at_narrator_enabled').val();
858 this.settings.at_narrator_text_not_inside = $('#at_narrator_text_not_inside').val();
859 this.settings.rvc_character_voice = $('#rvc_character_voice').val();
860 this.settings.rvc_narrator_voice = $('#rvc_narrator_voice').val();
861 this.settings.rvc_character_pitch = $('#rvc_character_pitch').val();
862 this.settings.rvc_narrator_pitch = $('#rvc_narrator_pitch').val();
863 this.settings.narrator_voice_gen = $('#narrator_voice').val();
864 // Save the updated settings
865 saveTtsProviderSettings();
866 }
867
868 //#########################//
869 // ST Handle Reload button //
870 //#########################//
871
872 async onRefreshClick() {
873 try {
874 updateStatus('Processing'); // Set status to Processing while refreshing
875 await this.checkReady(); // Check if the TTS provider is ready
876 await this.loadSettings(this.settings); // Reload the settings
877 await this.checkReady(); // Check if the TTS provider is ready
878 updateStatus(this.ready ? 'Ready' : 'Offline'); // Update the status based on readiness
879 } catch (error) {
880 console.error('Error during refresh:', error);
881 updateStatus('Error'); // Set status to Error in case of failure
882 }
883 }
884
885 //##################//
886 // Preview AT Voice //
887 //##################//
888
889 async previewTtsVoice(voiceName) {
890 try {
891 // Prepare data for POST request
892 const postData = new URLSearchParams();
893 postData.append('voice', `${voiceName}`);
894
895 // Add RVC parameters for V2 if applicable
896 if (this.settings.server_version === 'v2' && this.settings.rvc_character_voice !== 'Disabled') {
897 postData.append('rvccharacter_voice_gen', this.settings.rvc_character_voice);
898 postData.append('rvccharacter_pitch', this.settings.rvc_character_pitch || '0');
899 }
900
901 // Making the POST request
902 const response = await fetch(`${this.settings.provider_endpoint}/api/previewvoice/`, {
903 method: 'POST',
904 headers: {
905 'Content-Type': 'application/x-www-form-urlencoded',
906 },
907 body: postData,
908 });
909
910 if (!response.ok) {
911 const errorText = await response.text();
912 console.error('previewTtsVoice Error Response Text:', errorText);
913 throw new Error(`HTTP ${response.status}: ${errorText}`);
914 }
915
916 const data = await response.json();
917 if (data.output_file_url) {
918 // Handle V1/V2 URL differences
919 const fullUrl = this.settings.server_version === 'v1'
920 ? data.output_file_url
921 : `${this.settings.provider_endpoint}${data.output_file_url}`;
922
923 const audioElement = new Audio(fullUrl);
924 audioElement.play().catch(e => console.error('Error playing audio:', e));
925 } else {
926 console.warn('previewTtsVoice No output file URL received in the response');
927 throw new Error('No output file URL received in the response');
928 }
929 } catch (error) {
930 console.error('previewTtsVoice Exception caught during preview generation:', error);
931 throw error;
932 }
933 }
934
935 //#####################//
936 // Populate ST voices //
937 //#####################//
938
939 async getVoice(voiceName, generatePreview = false) {
940 // Ensure this.voices is populated
941 if (this.voices.length === 0) {
942 // Fetch voice objects logic
943 }
944 // Find the object where the name matches voiceName
945 const match = this.voices.find(voice => voice.name === voiceName);
946 if (!match) {
947 // Error handling
948 }
949 // Generate preview URL only if requested
950 if (!match.preview_url && generatePreview) {
951 // Generate preview logic
952 }
953 return match; // Return the found voice object
954 }
955
956 //##########################################//
957 // Generate TTS Streaming or call Standard //
958 //##########################################//
959
960 async generateTts(inputText, voiceId) {
961 try {
962 if (this.settings.at_generation_method === 'streaming_enabled') {
963 // Construct the streaming URL
964 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`;
965 console.log('Streaming URL:', streamingUrl);
966
967 // Return the streaming URL directly
968 return streamingUrl;
969 } else {
970 // For standard method
971 const outputUrl = await this.fetchTtsGeneration(inputText, voiceId);
972 const audioResponse = await fetch(outputUrl);
973 if (!audioResponse.ok) {
974 throw new Error(`HTTP ${audioResponse.status}: Failed to fetch audio data`);
975 }
976 return audioResponse; // Return the fetch response directly
977 }
978 } catch (error) {
979 console.error('Error in generateTts:', error);
980 throw error;
981 }
982 }
983
984
985 //####################//
986 // Generate Standard //
987 //####################//
988
989 async fetchTtsGeneration(inputText, voiceId) {
990 const requestBody = new URLSearchParams({
991 'text_input': inputText,
992 'text_filtering': 'standard',
993 'character_voice_gen': voiceId,
994 'narrator_enabled': this.settings.narrator_enabled,
995 'narrator_voice_gen': this.settings.narrator_voice_gen,
996 'text_not_inside': this.settings.at_narrator_text_not_inside,
997 'language': this.settings.language,
998 'output_file_name': 'st_output',
999 'output_file_timestamp': 'true',
1000 'autoplay': 'false',
1001 'autoplay_volume': '0.8',
1002 });
1003
1004 // Add RVC parameters only for V2
1005 if (this.settings.server_version === 'v2') {
1006 if (this.settings.rvc_character_voice !== 'Disabled') {
1007 requestBody.append('rvccharacter_voice_gen', this.settings.rvc_character_voice);
1008 requestBody.append('rvccharacter_pitch', this.settings.rvc_character_pitch || '0');
1009 }
1010 if (this.settings.rvc_narrator_voice !== 'Disabled') {
1011 requestBody.append('rvcnarrator_voice_gen', this.settings.rvc_narrator_voice);
1012 requestBody.append('rvcnarrator_pitch', this.settings.rvc_narrator_pitch || '0');
1013 }
1014 }
1015
1016 try {
1017 const response = await doExtrasFetch(
1018 `${this.settings.provider_endpoint}/api/tts-generate`,
1019 {
1020 method: 'POST',
1021 headers: {
1022 'Content-Type': 'application/x-www-form-urlencoded',
1023 'Cache-Control': 'no-cache',
1024 },
1025 body: requestBody,
1026 },
1027 );
1028
1029 if (!response.ok) {
1030 const errorText = await response.text();
1031 console.error('fetchTtsGeneration Error Response Text:', errorText);
1032 throw new Error(`HTTP ${response.status}: ${errorText}`);
1033 }
1034 const data = await response.json();
1035
1036 // Handle V1/V2 URL differences
1037 const outputUrl = this.settings.server_version === 'v1'
1038 ? data.output_file_url // V1 returns full URL
1039 : `${this.settings.provider_endpoint}${data.output_file_url}`; // V2 returns relative path
1040
1041 return outputUrl;
1042 } catch (error) {
1043 console.error('[fetchTtsGeneration] Exception caught:', error);
1044 throw error;
1045 }
1046 }
1047}
1048
1049//#########################//
1050// Update Status Messages //
1051//#########################//
1052
1053function updateStatus(message) {
1054 const statusElement = document.getElementById('status_info');
1055 if (statusElement) {
1056 statusElement.textContent = message;
1057 switch (message) {
1058 case 'Offline':
1059 statusElement.style.color = 'red';
1060 break;
1061 case 'Ready':
1062 statusElement.style.color = 'lightgreen';
1063 break;
1064 case 'Processing':
1065 statusElement.style.color = 'blue';
1066 break;
1067 case 'Error':
1068 statusElement.style.color = 'red';
1069 break;
1070 }
1071 }
1072}