<script lang="ts"> import { toast } from 'svelte-sonner'; import { createEventDispatcher, onMount, getContext } from 'svelte'; import { user, settings, config } from '$lib/stores'; import { getVoices as _getVoices } from '$lib/apis/audio'; import Switch from '$lib/components/common/Switch.svelte'; const dispatch = createEventDispatcher(); const i18n = getContext('i18n'); export let saveSettings: Function; // Audio let conversationMode = false; let speechAutoSend = false; let responseAutoPlayback = false; let nonLocalVoices = false; let STTEngine = ''; let voices = []; let voice = ''; // Audio speed control let playbackRate = 1; const speedOptions = [2, 1.75, 1.5, 1.25, 1, 0.75, 0.5]; const getVoices = async () => { if ($config.audio.tts.engine === '') { const getVoicesLoop = setInterval(async () => { voices = await speechSynthesis.getVoices(); // 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; } } }; const toggleResponseAutoPlayback = async () => { responseAutoPlayback = !responseAutoPlayback; saveSettings({ responseAutoPlayback: responseAutoPlayback }); }; const toggleSpeechAutoSend = async () => { speechAutoSend = !speechAutoSend; saveSettings({ speechAutoSend: speechAutoSend }); }; onMount(async () => { playbackRate = $settings.audio?.tts?.playbackRate ?? 1; conversationMode = $settings.conversationMode ?? false; speechAutoSend = $settings.speechAutoSend ?? false; responseAutoPlayback = $settings.responseAutoPlayback ?? false; STTEngine = $settings?.audio?.stt?.engine ?? ''; if ($settings?.audio?.tts?.defaultVoice === $config.audio.tts.voice) { voice = $settings?.audio?.tts?.voice ?? $config.audio.tts.voice ?? ''; } else { voice = $config.audio.tts.voice ?? ''; } nonLocalVoices = $settings.audio?.tts?.nonLocalVoices ?? false; await getVoices(); }); </script> <form class="flex flex-col h-full justify-between space-y-3 text-sm" on:submit|preventDefault={async () => { saveSettings({ audio: { stt: { engine: STTEngine !== '' ? STTEngine : undefined }, tts: { playbackRate: playbackRate, voice: voice !== '' ? voice : undefined, defaultVoice: $config?.audio?.tts?.voice ?? '', nonLocalVoices: $config.audio.tts.engine === '' ? nonLocalVoices : undefined } } }); dispatch('save'); }} > <div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem]"> <div> <div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div> {#if $config.audio.stt.engine !== 'web'} <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={STTEngine} placeholder="Select an engine" > <option value="">{$i18n.t('Default')}</option> <option value="web">{$i18n.t('Web API')}</option> </select> </div> </div> {/if} <div class=" py-0.5 flex w-full justify-between"> <div class=" self-center text-xs font-medium"> {$i18n.t('Instant Auto-Send After Voice Transcription')} </div> <button class="p-1 px-3 text-xs flex rounded transition" on:click={() => { toggleSpeechAutoSend(); }} type="button" > {#if speechAutoSend === true} <span class="ml-2 self-center">{$i18n.t('On')}</span> {:else} <span class="ml-2 self-center">{$i18n.t('Off')}</span> {/if} </button> </div> </div> <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('Auto-playback response')}</div> <button class="p-1 px-3 text-xs flex rounded transition" on:click={() => { toggleResponseAutoPlayback(); }} type="button" > {#if responseAutoPlayback === true} <span class="ml-2 self-center">{$i18n.t('On')}</span> {:else} <span class="ml-2 self-center">{$i18n.t('Off')}</span> {/if} </button> </div> <div class=" py-0.5 flex w-full justify-between"> <div class=" self-center text-xs font-medium">{$i18n.t('Speech Playback Speed')}</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={playbackRate} > {#each speedOptions as option} <option value={option} selected={playbackRate === option}>{option}x</option> {/each} </select> </div> </div> </div> <hr class=" dark:border-gray-850" /> {#if $config.audio.tts.engine === ''} <div> <div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div> <div class="flex w-full"> <div class="flex-1"> <select class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" bind:value={voice} > <option value="" selected={voice !== ''}>{$i18n.t('Default')}</option> {#each voices.filter((v) => nonLocalVoices || v.localService === true) as _voice} <option value={_voice.name} class="bg-gray-100 dark:bg-gray-700" selected={voice === _voice.name}>{_voice.name}</option > {/each} </select> </div> </div> <div class="flex items-center justify-between my-1.5"> <div class="text-xs"> {$i18n.t('Allow non-local voices')} </div> <div class="mt-1"> <Switch bind:state={nonLocalVoices} /> </div> </div> </div> {:else if $config.audio.tts.engine !== ''} <div> <div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set 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 dark:text-gray-300 dark:bg-gray-850 outline-none" bind:value={voice} placeholder="Select a voice" /> <datalist id="voice-list"> {#each voices as voice} <option value={voice.id}>{voice.name}</option> {/each} </datalist> </div> </div> </div> {/if} </div> <div class="flex justify-end text-sm font-medium"> <button class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full" type="submit" > {$i18n.t('Save')} </button> </div> </form>