Move request to backend

1883095e39a210e5cb5cde0c4882bb7cd395c18a

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

5 files changed, +104 -19Showing whitespace changes
public/scripts/extensions/tts/index.js+10 -10
@@ -83,21 +83,21 @@ export function getPreviewString(lang) {
83}83}
8484
85const ttsProviders = {85const ttsProviders = {
86 ElevenLabs: ElevenLabsTtsProvider,86 AllTalk: AllTalkTtsProvider,
87 Silero: SileroTtsProvider,87 Azure: AzureTtsProvider,
88 XTTSv2: XTTSTtsProvider,
89 VITS: VITSTtsProvider,
90 GSVI: GSVITtsProvider,
91 SBVits2: SBVits2TtsProvider,
92 System: SystemTtsProvider,
93 Coqui: CoquiTtsProvider,88 Coqui: CoquiTtsProvider,
94 Edge: EdgeTtsProvider,89 Edge: EdgeTtsProvider,
90 ElevenLabs: ElevenLabsTtsProvider,
91 GSVI: GSVITtsProvider,
95 Novel: NovelTtsProvider,92 Novel: NovelTtsProvider,
96 OpenAI: OpenAITtsProvider,93 OpenAI: OpenAITtsProvider,
97 AllTalk: AllTalkTtsProvider,94 'OpenAI Compatible': OpenAICompatibleTtsProvider,
95 SBVits2: SBVits2TtsProvider,
96 Silero: SileroTtsProvider,
98 SpeechT5: SpeechT5TtsProvider,97 SpeechT5: SpeechT5TtsProvider,
99 Azure: AzureTtsProvider,98 System: SystemTtsProvider,
100 OpenAICompatible: OpenAICompatibleTtsProvider,99 VITS: VITSTtsProvider,
100 XTTSv2: XTTSTtsProvider,
101};101};
102let ttsProvider;102let ttsProvider;
103let ttsProviderName;103let ttsProviderName;
public/scripts/extensions/tts/openai-compatible.js+47 -8
@@ -1,4 +1,6 @@
1import { getRequestHeaders } from '../../../script.js';1import { getRequestHeaders } from '../../../script.js';
2import { callGenericPopup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js';
3import { findSecret, SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js';
2import { getPreviewString, saveTtsProviderSettings } from './index.js';4import { getPreviewString, saveTtsProviderSettings } from './index.js';
35
4export { OpenAICompatibleTtsProvider };6export { OpenAICompatibleTtsProvider };
@@ -21,7 +23,15 @@ class OpenAICompatibleTtsProvider {
21 get settingsHtml() {23 get settingsHtml() {
22 let html = `24 let html = `
23 <label for="openai_compatible_tts_endpoint">Provider Endpoint:</label>25 <label for="openai_compatible_tts_endpoint">Provider Endpoint:</label>
26 <div class="flex-container alignItemsCenter">
27 <div class="flex1">
24 <input id="openai_compatible_tts_endpoint" type="text" class="text_pole" maxlength="250" value="${this.defaultSettings.provider_endpoint}"/>28 <input id="openai_compatible_tts_endpoint" type="text" class="text_pole" maxlength="250" value="${this.defaultSettings.provider_endpoint}"/>
29 </div>
30 <div id="openai_compatible_tts_key" class="menu_button menu_button_icon">
31 <i class="fa-solid fa-key"></i>
32 <span>API Key</span>
33 </div>
34 </div>
25 <label for="openai_compatible_model">Model:</label>35 <label for="openai_compatible_model">Model:</label>
26 <input id="openai_compatible_model" type="text" class="text_pole" maxlength="250" value="${this.defaultSettings.model}"/>36 <input id="openai_compatible_model" type="text" class="text_pole" maxlength="250" value="${this.defaultSettings.model}"/>
27 <label for="openai_compatible_tts_voices">Available Voices (comma separated):</label>37 <label for="openai_compatible_tts_voices">Available Voices (comma separated):</label>
@@ -64,7 +74,35 @@ class OpenAICompatibleTtsProvider {
6474
65 $('#openai_compatible_tts_speed_output').text(this.settings.speed);75 $('#openai_compatible_tts_speed_output').text(this.settings.speed);
6676
67 this.refreshSession();77 $('#openai_compatible_tts_key').toggleClass('success', secret_state[SECRET_KEYS.CUSTOM_OPENAI_TTS]);
78 $('#openai_compatible_tts_key').on('click', async () => {
79 const popupText = 'OpenAI-compatible TTS API Key';
80 const savedKey = secret_state[SECRET_KEYS.CUSTOM_OPENAI_TTS] ? await findSecret(SECRET_KEYS.CUSTOM_OPENAI_TTS) : '';
81
82 const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, savedKey, {
83 customButtons: [{
84 text: 'Remove Key',
85 appendAtEnd: true,
86 result: POPUP_RESULT.CANCELLED,
87 action: async () => {
88 await writeSecret(SECRET_KEYS.CUSTOM_OPENAI_TTS, '');
89 $('#openai_compatible_tts_key').toggleClass('success', secret_state[SECRET_KEYS.CUSTOM_OPENAI_TTS]);
90 toastr.success('API Key removed');
91 await this.onRefreshClick();
92 },
93 }],
94 });
95
96 if (key == false || key == '' || key == null) {
97 return;
98 }
99
100 await writeSecret(SECRET_KEYS.CUSTOM_OPENAI_TTS, String(key));
101
102 toastr.success('API Key saved');
103 $('#openai_compatible_tts_key').toggleClass('success', secret_state[SECRET_KEYS.CUSTOM_OPENAI_TTS]);
104 await this.onRefreshClick();
105 });
68106
69 await this.checkReady();107 await this.checkReady();
70108
@@ -75,7 +113,7 @@ class OpenAICompatibleTtsProvider {
75 // Update dynamically113 // Update dynamically
76 this.settings.provider_endpoint = String($('#openai_compatible_tts_endpoint').val());114 this.settings.provider_endpoint = String($('#openai_compatible_tts_endpoint').val());
77 this.settings.model = String($('#openai_compatible_model').val());115 this.settings.model = String($('#openai_compatible_model').val());
78 this.settings.available_voices = $('#openai_compatible_tts_voices').val().split(',');116 this.settings.available_voices = String($('#openai_compatible_tts_voices').val()).split(',');
79 this.settings.speed = Number($('#openai_compatible_tts_speed').val());117 this.settings.speed = Number($('#openai_compatible_tts_speed').val());
80 $('#openai_compatible_tts_speed_output').text(this.settings.speed);118 $('#openai_compatible_tts_speed_output').text(this.settings.speed);
81 saveTtsProviderSettings();119 saveTtsProviderSettings();
@@ -132,15 +170,16 @@ class OpenAICompatibleTtsProvider {
132170
133 async fetchTtsGeneration(inputText, voiceId) {171 async fetchTtsGeneration(inputText, voiceId) {
134 console.info(`Generating new TTS for voice_id ${voiceId}`);172 console.info(`Generating new TTS for voice_id ${voiceId}`);
135 const response = await fetch(this.settings.provider_endpoint, {173 const response = await fetch('/api/openai/custom/generate-voice', {
136 method: 'POST',174 method: 'POST',
137 headers: getRequestHeaders(),175 headers: getRequestHeaders(),
138 body: JSON.stringify({176 body: JSON.stringify({
139 'model': this.settings.model,177 provider_endpoint: this.settings.provider_endpoint,
140 'input': inputText,178 model: this.settings.model,
141 'voice': voiceId,179 input: inputText,
142 'response_format': 'mp3',180 voice: voiceId,
143 'speed': this.settings.speed,181 response_format: 'mp3',
182 speed: this.settings.speed,
144 }),183 }),
145 });184 });
146185
public/scripts/secrets.js+2 -1
@@ -33,6 +33,7 @@ export const SECRET_KEYS = {
33 HUGGINGFACE: 'api_key_huggingface',33 HUGGINGFACE: 'api_key_huggingface',
34 STABILITY: 'api_key_stability',34 STABILITY: 'api_key_stability',
35 BLOCKENTROPY: 'api_key_blockentropy',35 BLOCKENTROPY: 'api_key_blockentropy',
36 CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',
36};37};
3738
38const INPUT_MAP = {39const INPUT_MAP = {
@@ -127,7 +128,7 @@ export async function writeSecret(key, value) {
127 const text = await response.text();128 const text = await response.text();
128129
129 if (text == 'ok') {130 if (text == 'ok') {
130 secret_state[key] = true;131 secret_state[key] = !!value;
131 updateSecretDisplay();132 updateSecretDisplay();
132 }133 }
133 }134 }
src/endpoints/openai.js+44 -0
@@ -282,4 +282,48 @@ router.post('/generate-image', jsonParser, async (request, response) => {
282 }282 }
283});283});
284284
285const custom = express.Router();
286
287custom.post('/generate-voice', jsonParser, async (request, response) => {
288 try {
289 const key = readSecret(request.user.directories, SECRET_KEYS.CUSTOM_OPENAI_TTS);
290 const { input, provider_endpoint, response_format, voice, speed, model } = request.body;
291
292 if (!provider_endpoint) {
293 console.log('No OpenAI-compatible TTS provider endpoint provided');
294 return response.sendStatus(400);
295 }
296
297 const result = await fetch(provider_endpoint, {
298 method: 'POST',
299 headers: {
300 'Content-Type': 'application/json',
301 Authorization: `Bearer ${key ?? ''}`,
302 },
303 body: JSON.stringify({
304 input: input ?? '',
305 response_format: response_format ?? 'mp3',
306 voice: voice ?? 'alloy',
307 speed: speed ?? 1,
308 model: model ?? 'tts-1',
309 }),
310 });
311
312 if (!result.ok) {
313 const text = await result.text();
314 console.log('OpenAI request failed', result.statusText, text);
315 return response.status(500).send(text);
316 }
317
318 const buffer = await result.arrayBuffer();
319 response.setHeader('Content-Type', 'audio/mpeg');
320 return response.send(Buffer.from(buffer));
321 } catch (error) {
322 console.error('OpenAI TTS generation failed', error);
323 response.status(500).send('Internal server error');
324 }
325});
326
327router.use('/custom', custom);
328
285module.exports = { router };329module.exports = { router };
src/endpoints/secrets.js+1 -0
@@ -45,6 +45,7 @@ const SECRET_KEYS = {
45 HUGGINGFACE: 'api_key_huggingface',45 HUGGINGFACE: 'api_key_huggingface',
46 STABILITY: 'api_key_stability',46 STABILITY: 'api_key_stability',
47 BLOCKENTROPY: 'api_key_blockentropy',47 BLOCKENTROPY: 'api_key_blockentropy',
48 CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',
48};49};
4950
50// These are the keys that are safe to expose, even if allowKeysExposure is false51// These are the keys that are safe to expose, even if allowKeysExposure is false