Audio.svelte 10 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
2
<script lang="ts">
	import { toast } from 'svelte-sonner';
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
3
4
5
	import { createEventDispatcher, onMount, getContext } from 'svelte';
	const dispatch = createEventDispatcher();

Timothy J. Baek's avatar
Timothy J. Baek committed
6
	import { getBackendConfig } from '$lib/apis';
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
7
8
9
10
11
12
13
14
	import {
		getAudioConfig,
		updateAudioConfig,
		getModels as _getModels,
		getVoices as _getVoices
	} from '$lib/apis/audio';
	import { user, settings, config } from '$lib/stores';

15
	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
16
17
18
19
20
21
22
23
24

	const i18n = getContext('i18n');

	export let saveHandler: Function;

	// Audio

	let TTS_OPENAI_API_BASE_URL = '';
	let TTS_OPENAI_API_KEY = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
25
	let TTS_API_KEY = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
26
27
28
29
30
31
32
33
34
35
36
37
38
	let TTS_ENGINE = '';
	let TTS_MODEL = '';
	let TTS_VOICE = '';

	let STT_OPENAI_API_BASE_URL = '';
	let STT_OPENAI_API_KEY = '';
	let STT_ENGINE = '';
	let STT_MODEL = '';

	let voices = [];
	let models = [];
	let nonLocalVoices = false;

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
39
40
41
42
43
44
45
	const getModels = async () => {
		if (TTS_ENGINE === '') {
			models = [];
		} else {
			const res = await _getModels(localStorage.token).catch((e) => {
				toast.error(e);
			});
Timothy J. Baek's avatar
Timothy J. Baek committed
46

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
47
48
49
50
51
			if (res) {
				console.log(res);
				models = res.models;
			}
		}
Timothy J. Baek's avatar
Timothy J. Baek committed
52
53
	};

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
54
55
56
57
	const getVoices = async () => {
		if (TTS_ENGINE === '') {
			const getVoicesLoop = setInterval(async () => {
				voices = await speechSynthesis.getVoices();
Timothy J. Baek's avatar
Timothy J. Baek committed
58

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
59
60
61
62
63
64
65
66
67
68
69
70
71
				// do your loop
				if (voices.length > 0) {
					clearInterval(getVoicesLoop);
				}
			}, 100);
		} else {
			const res = await _getVoices(localStorage.token).catch((e) => {
				toast.error(e);
			});

			if (res) {
				console.log(res);
				voices = res.voices;
Timothy J. Baek's avatar
Timothy J. Baek committed
72
			}
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
73
		}
Timothy J. Baek's avatar
Timothy J. Baek committed
74
75
76
77
78
79
80
	};

	const updateConfigHandler = async () => {
		const res = await updateAudioConfig(localStorage.token, {
			tts: {
				OPENAI_API_BASE_URL: TTS_OPENAI_API_BASE_URL,
				OPENAI_API_KEY: TTS_OPENAI_API_KEY,
Timothy J. Baek's avatar
Timothy J. Baek committed
81
				API_KEY: TTS_API_KEY,
Timothy J. Baek's avatar
Timothy J. Baek committed
82
83
84
85
86
87
88
89
90
91
92
93
94
				ENGINE: TTS_ENGINE,
				MODEL: TTS_MODEL,
				VOICE: TTS_VOICE
			},
			stt: {
				OPENAI_API_BASE_URL: STT_OPENAI_API_BASE_URL,
				OPENAI_API_KEY: STT_OPENAI_API_KEY,
				ENGINE: STT_ENGINE,
				MODEL: STT_MODEL
			}
		});

		if (res) {
SimonOriginal's avatar
SimonOriginal committed
95
			toast.success($i18n.t('Audio settings updated successfully'));
Timothy J. Baek's avatar
Timothy J. Baek committed
96
97
98
99
100
101
102
103
104
105
106
107

			config.set(await getBackendConfig());
		}
	};

	onMount(async () => {
		const res = await getAudioConfig(localStorage.token);

		if (res) {
			console.log(res);
			TTS_OPENAI_API_BASE_URL = res.tts.OPENAI_API_BASE_URL;
			TTS_OPENAI_API_KEY = res.tts.OPENAI_API_KEY;
Timothy J. Baek's avatar
Timothy J. Baek committed
108
			TTS_API_KEY = res.tts.API_KEY;
Timothy J. Baek's avatar
Timothy J. Baek committed
109
110
111
112
113
114
115
116
117
118
119
120

			TTS_ENGINE = res.tts.ENGINE;
			TTS_MODEL = res.tts.MODEL;
			TTS_VOICE = res.tts.VOICE;

			STT_OPENAI_API_BASE_URL = res.stt.OPENAI_API_BASE_URL;
			STT_OPENAI_API_KEY = res.stt.OPENAI_API_KEY;

			STT_ENGINE = res.stt.ENGINE;
			STT_MODEL = res.stt.MODEL;
		}

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
121
122
		await getVoices();
		await getModels();
Timothy J. Baek's avatar
Timothy J. Baek committed
123
124
125
126
127
128
129
130
131
	});
</script>

<form
	class="flex flex-col h-full justify-between space-y-3 text-sm"
	on:submit|preventDefault={async () => {
		await updateConfigHandler();
		dispatch('save');
	}}
Justin Hayes's avatar
name  
Justin Hayes committed
132
>
Timothy J. Baek's avatar
Timothy J. Baek committed
133
	<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
Timothy J. Baek's avatar
Timothy J. Baek committed
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
		<div class="flex flex-col gap-3">
			<div>
				<div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>

				<div class=" py-0.5 flex w-full justify-between">
					<div class=" self-center text-xs font-medium">{$i18n.t('Speech-to-Text Engine')}</div>
					<div class="flex items-center relative">
						<select
							class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
							bind:value={STT_ENGINE}
							placeholder="Select an engine"
						>
							<option value="">{$i18n.t('Whisper (Local)')}</option>
							<option value="openai">OpenAI</option>
							<option value="web">{$i18n.t('Web API')}</option>
						</select>
Timothy J. Baek's avatar
Timothy J. Baek committed
150
151
152
					</div>
				</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
153
154
155
				{#if STT_ENGINE === 'openai'}
					<div>
						<div class="mt-1 flex gap-2 mb-1">
Timothy J. Baek's avatar
Timothy J. Baek committed
156
							<input
Timothy J. Baek's avatar
Timothy J. Baek committed
157
								class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
Timothy J. Baek's avatar
Timothy J. Baek committed
158
159
160
								placeholder={$i18n.t('API Base URL')}
								bind:value={STT_OPENAI_API_BASE_URL}
								required
Timothy J. Baek's avatar
Timothy J. Baek committed
161
162
							/>

163
							<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={STT_OPENAI_API_KEY} />
Timothy J. Baek's avatar
Timothy J. Baek committed
164
165
166
						</div>
					</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
167
168
169
170
171
172
173
174
					<hr class=" dark:border-gray-850 my-2" />

					<div>
						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('STT Model')}</div>
						<div class="flex w-full">
							<div class="flex-1">
								<input
									list="model-list"
Timothy J. Baek's avatar
Timothy J. Baek committed
175
									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
Timothy J. Baek's avatar
Timothy J. Baek committed
176
177
178
179
180
181
182
183
184
185
186
									bind:value={STT_MODEL}
									placeholder="Select a model"
								/>

								<datalist id="model-list">
									<option value="whisper-1" />
								</datalist>
							</div>
						</div>
					</div>
				{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
187
188
			</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
189
190
191
192
193
194
195
196
197
198
199
200
			<hr class=" dark:border-gray-800" />

			<div>
				<div class=" mb-1 text-sm font-medium">{$i18n.t('TTS Settings')}</div>

				<div class=" py-0.5 flex w-full justify-between">
					<div class=" self-center text-xs font-medium">{$i18n.t('Text-to-Speech Engine')}</div>
					<div class="flex items-center relative">
						<select
							class=" dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
							bind:value={TTS_ENGINE}
							placeholder="Select a mode"
201
							on:change={async (e) => {
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
202
203
204
205
								await updateConfigHandler();
								await getVoices();
								await getModels();

Timothy J. Baek's avatar
Timothy J. Baek committed
206
207
208
209
210
								if (e.target.value === 'openai') {
									TTS_VOICE = 'alloy';
									TTS_MODEL = 'tts-1';
								} else {
									TTS_VOICE = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
211
									TTS_MODEL = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
212
213
214
215
								}
							}}
						>
							<option value="">{$i18n.t('Web API')}</option>
216
							<option value="openai">{$i18n.t('OpenAI')}</option>
Justin Hayes's avatar
name  
Justin Hayes committed
217
							<option value="elevenlabs">{$i18n.t('ElevenLabs')}</option>
Timothy J. Baek's avatar
Timothy J. Baek committed
218
						</select>
Timothy J. Baek's avatar
Timothy J. Baek committed
219
220
221
					</div>
				</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
222
223
224
225
				{#if TTS_ENGINE === 'openai'}
					<div>
						<div class="mt-1 flex gap-2 mb-1">
							<input
Timothy J. Baek's avatar
Timothy J. Baek committed
226
								class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
Timothy J. Baek's avatar
Timothy J. Baek committed
227
228
229
230
								placeholder={$i18n.t('API Base URL')}
								bind:value={TTS_OPENAI_API_BASE_URL}
								required
							/>
Timothy J. Baek's avatar
Timothy J. Baek committed
231

232
							<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={TTS_OPENAI_API_KEY} />
Timothy J. Baek's avatar
Timothy J. Baek committed
233
234
						</div>
					</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
235
236
237
238
239
240
241
242
243
244
245
				{:else if TTS_ENGINE === 'elevenlabs'}
					<div>
						<div class="mt-1 flex gap-2 mb-1">
							<input
								class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
								placeholder={$i18n.t('API Key')}
								bind:value={TTS_API_KEY}
								required
							/>
						</div>
					</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
246
247
248
249
				{/if}

				<hr class=" dark:border-gray-850 my-2" />

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
250
				{#if TTS_ENGINE === ''}
Timothy J. Baek's avatar
Timothy J. Baek committed
251
					<div>
Timothy J. Baek's avatar
Timothy J. Baek committed
252
253
254
						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Voice')}</div>
						<div class="flex w-full">
							<div class="flex-1">
Timothy J. Baek's avatar
Timothy J. Baek committed
255
								<select
Timothy J. Baek's avatar
Timothy J. Baek committed
256
									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
Timothy J. Baek's avatar
Timothy J. Baek committed
257
									bind:value={TTS_VOICE}
Timothy J. Baek's avatar
Timothy J. Baek committed
258
259
								>
									<option value="" selected={TTS_VOICE !== ''}>{$i18n.t('Default')}</option>
Timothy J. Baek's avatar
Timothy J. Baek committed
260
									{#each voices as voice}
Timothy J. Baek's avatar
Timothy J. Baek committed
261
										<option
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
262
											value={voice.voiceURI}
Timothy J. Baek's avatar
Timothy J. Baek committed
263
											class="bg-gray-100 dark:bg-gray-700"
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
264
											selected={TTS_VOICE === voice.voiceURI}>{voice.name}</option
Timothy J. Baek's avatar
Timothy J. Baek committed
265
										>
Timothy J. Baek's avatar
Timothy J. Baek committed
266
									{/each}
Timothy J. Baek's avatar
Timothy J. Baek committed
267
								</select>
Timothy J. Baek's avatar
Timothy J. Baek committed
268
269
270
							</div>
						</div>
					</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
271
				{:else if TTS_ENGINE === 'openai'}
Timothy J. Baek's avatar
Timothy J. Baek committed
272
273
274
275
276
277
278
279
280
281
282
283
284
285
					<div class=" flex gap-2">
						<div class="w-full">
							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Voice')}</div>
							<div class="flex w-full">
								<div class="flex-1">
									<input
										list="voice-list"
										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
										bind:value={TTS_VOICE}
										placeholder="Select a voice"
									/>

									<datalist id="voice-list">
										{#each voices as voice}
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
286
											<option value={voice.id}>{voice.name}</option>
Timothy J. Baek's avatar
Timothy J. Baek committed
287
288
289
290
291
292
293
294
295
296
										{/each}
									</datalist>
								</div>
							</div>
						</div>
						<div class="w-full">
							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Model')}</div>
							<div class="flex w-full">
								<div class="flex-1">
									<input
Timothy J. Baek's avatar
fix  
Timothy J. Baek committed
297
										list="tts-model-list"
Timothy J. Baek's avatar
Timothy J. Baek committed
298
299
300
301
302
										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
										bind:value={TTS_MODEL}
										placeholder="Select a model"
									/>

Timothy J. Baek's avatar
fix  
Timothy J. Baek committed
303
									<datalist id="tts-model-list">
Timothy J. Baek's avatar
Timothy J. Baek committed
304
										{#each models as model}
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
305
											<option value={model.id} />
Timothy J. Baek's avatar
Timothy J. Baek committed
306
307
308
309
310
311
312
										{/each}
									</datalist>
								</div>
							</div>
						</div>
					</div>
				{:else if TTS_ENGINE === 'elevenlabs'}
Timothy J. Baek's avatar
Timothy J. Baek committed
313
314
315
316
317
318
319
					<div class=" flex gap-2">
						<div class="w-full">
							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Voice')}</div>
							<div class="flex w-full">
								<div class="flex-1">
									<input
										list="voice-list"
Timothy J. Baek's avatar
Timothy J. Baek committed
320
										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
Timothy J. Baek's avatar
Timothy J. Baek committed
321
322
323
324
325
326
										bind:value={TTS_VOICE}
										placeholder="Select a voice"
									/>

									<datalist id="voice-list">
										{#each voices as voice}
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
327
											<option value={voice.id}>{voice.name}</option>
Timothy J. Baek's avatar
Timothy J. Baek committed
328
329
330
331
332
333
334
335
336
337
										{/each}
									</datalist>
								</div>
							</div>
						</div>
						<div class="w-full">
							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Model')}</div>
							<div class="flex w-full">
								<div class="flex-1">
									<input
Timothy J. Baek's avatar
fix  
Timothy J. Baek committed
338
										list="tts-model-list"
Timothy J. Baek's avatar
Timothy J. Baek committed
339
										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
Timothy J. Baek's avatar
Timothy J. Baek committed
340
341
342
343
										bind:value={TTS_MODEL}
										placeholder="Select a model"
									/>

Timothy J. Baek's avatar
fix  
Timothy J. Baek committed
344
									<datalist id="tts-model-list">
Timothy J. Baek's avatar
Timothy J. Baek committed
345
										{#each models as model}
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
346
											<option value={model.id} />
Timothy J. Baek's avatar
Timothy J. Baek committed
347
348
349
										{/each}
									</datalist>
								</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
350
351
352
							</div>
						</div>
					</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
353
354
				{/if}
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
355
356
357
358
359
360
361
362
363
364
365
		</div>
	</div>
	<div class="flex justify-end text-sm font-medium">
		<button
			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
			type="submit"
		>
			{$i18n.t('Save')}
		</button>
	</div>
</form>