Audio.svelte 8.06 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
2
3
4
5
6
7
<script lang="ts">
	import { getAudioConfig, updateAudioConfig } from '$lib/apis/audio';
	import { user, settings, config } from '$lib/stores';
	import { createEventDispatcher, onMount, getContext } from 'svelte';
	import { toast } from 'svelte-sonner';
	import Switch from '$lib/components/common/Switch.svelte';
	import { getBackendConfig } from '$lib/apis';
8
	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
	const dispatch = createEventDispatcher();

	const i18n = getContext('i18n');

	export let saveHandler: Function;

	// Audio

	let TTS_OPENAI_API_BASE_URL = '';
	let TTS_OPENAI_API_KEY = '';
	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;

	const getOpenAIVoices = () => {
		voices = [
			{ name: 'alloy' },
			{ name: 'echo' },
			{ name: 'fable' },
			{ name: 'onyx' },
			{ name: 'nova' },
			{ name: 'shimmer' }
		];
	};

	const getOpenAIModels = () => {
		models = [{ name: 'tts-1' }, { name: 'tts-1-hd' }];
	};

	const getWebAPIVoices = () => {
		const getVoicesLoop = setInterval(async () => {
			voices = await speechSynthesis.getVoices();

			// do your loop
			if (voices.length > 0) {
				clearInterval(getVoicesLoop);
			}
		}, 100);
	};

	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,
				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
76
			toast.success($i18n.t('Audio settings updated successfully'));
Timothy J. Baek's avatar
Timothy J. Baek committed
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116

			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;

			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;
		}

		if (TTS_ENGINE === 'openai') {
			getOpenAIVoices();
			getOpenAIModels();
		} else {
			getWebAPIVoices();
		}
	});
</script>

<form
	class="flex flex-col h-full justify-between space-y-3 text-sm"
	on:submit|preventDefault={async () => {
		await updateConfigHandler();
		dispatch('save');
	}}
>
Timothy J. Baek's avatar
Timothy J. Baek committed
117
	<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
Timothy J. Baek's avatar
Timothy J. Baek committed
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
		<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
134
135
136
					</div>
				</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
137
138
139
				{#if STT_ENGINE === 'openai'}
					<div>
						<div class="mt-1 flex gap-2 mb-1">
Timothy J. Baek's avatar
Timothy J. Baek committed
140
							<input
Timothy J. Baek's avatar
Timothy J. Baek committed
141
								class="flex-1 w-full rounded-l-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
142
143
144
								placeholder={$i18n.t('API Base URL')}
								bind:value={STT_OPENAI_API_BASE_URL}
								required
Timothy J. Baek's avatar
Timothy J. Baek committed
145
146
							/>

147
							<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={STT_OPENAI_API_KEY} />
Timothy J. Baek's avatar
Timothy J. Baek committed
148
149
150
						</div>
					</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
151
152
153
154
155
156
157
158
					<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
159
									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
160
161
162
163
164
165
166
167
168
169
170
									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
171
172
			</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
			<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"
							on:change={(e) => {
								if (e.target.value === 'openai') {
									getOpenAIVoices();
									TTS_VOICE = 'alloy';
									TTS_MODEL = 'tts-1';
								} else {
									getWebAPIVoices();
									TTS_VOICE = '';
								}
							}}
						>
							<option value="">{$i18n.t('Web API')}</option>
197
							<option value="openai">{$i18n.t('OpenAI')}</option>
Timothy J. Baek's avatar
Timothy J. Baek committed
198
						</select>
Timothy J. Baek's avatar
Timothy J. Baek committed
199
200
201
					</div>
				</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
202
203
204
205
				{#if TTS_ENGINE === 'openai'}
					<div>
						<div class="mt-1 flex gap-2 mb-1">
							<input
Timothy J. Baek's avatar
Timothy J. Baek committed
206
								class="flex-1 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
207
208
209
210
								placeholder={$i18n.t('API Base URL')}
								bind:value={TTS_OPENAI_API_BASE_URL}
								required
							/>
Timothy J. Baek's avatar
Timothy J. Baek committed
211

212
							<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={TTS_OPENAI_API_KEY} />
Timothy J. Baek's avatar
Timothy J. Baek committed
213
214
						</div>
					</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
215
216
217
218
219
220
				{/if}

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

				{#if TTS_ENGINE === ''}
					<div>
Timothy J. Baek's avatar
Timothy J. Baek committed
221
222
223
						<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
224
								<select
Timothy J. Baek's avatar
Timothy J. Baek committed
225
									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
226
									bind:value={TTS_VOICE}
Timothy J. Baek's avatar
Timothy J. Baek committed
227
228
								>
									<option value="" selected={TTS_VOICE !== ''}>{$i18n.t('Default')}</option>
Timothy J. Baek's avatar
Timothy J. Baek committed
229
									{#each voices as voice}
Timothy J. Baek's avatar
Timothy J. Baek committed
230
231
232
233
234
										<option
											value={voice.voiceURI}
											class="bg-gray-100 dark:bg-gray-700"
											selected={TTS_VOICE === voice.voiceURI}>{voice.name}</option
										>
Timothy J. Baek's avatar
Timothy J. Baek committed
235
									{/each}
Timothy J. Baek's avatar
Timothy J. Baek committed
236
								</select>
Timothy J. Baek's avatar
Timothy J. Baek committed
237
238
239
							</div>
						</div>
					</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
240
241
242
243
244
245
246
247
				{:else if TTS_ENGINE === 'openai'}
					<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
248
										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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
										bind:value={TTS_VOICE}
										placeholder="Select a voice"
									/>

									<datalist id="voice-list">
										{#each voices as voice}
											<option value={voice.name} />
										{/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
										list="model-list"
Timothy J. Baek's avatar
Timothy J. Baek committed
267
										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
268
269
270
271
272
273
274
275
276
277
										bind:value={TTS_MODEL}
										placeholder="Select a model"
									/>

									<datalist id="model-list">
										{#each models as model}
											<option value={model.name} />
										{/each}
									</datalist>
								</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
278
279
280
							</div>
						</div>
					</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
281
282
				{/if}
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
283
284
285
286
287
288
289
290
291
292
293
		</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>