Voice.svelte 3.26 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
2
3
4
5
6
7
<script lang="ts">
	import { createEventDispatcher, onMount } from 'svelte';
	const dispatch = createEventDispatcher();

	export let saveSettings: Function;

	// Voice
Timothy J. Baek's avatar
Timothy J. Baek committed
8
	let engines = ['', 'openai'];
Timothy J. Baek's avatar
Timothy J. Baek committed
9
	let engine = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
10
11
12

	let voices = [];
	let speaker = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
13

Timothy J. Baek's avatar
Timothy J. Baek committed
14
15
16
17
18
19
20
21
22
23
	const getOpenAIVoices = () => {
		voices = [
			{ name: 'alloy' },
			{ name: 'echo' },
			{ name: 'fable' },
			{ name: 'onyx' },
			{ name: 'nova' },
			{ name: 'shimmer' }
		];
	};
Timothy J. Baek's avatar
Timothy J. Baek committed
24

Timothy J. Baek's avatar
Timothy J. Baek committed
25
	const getWebAPIVoices = () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
26
		const getVoicesLoop = setInterval(async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
27
			voices = await speechSynthesis.getVoices();
Timothy J. Baek's avatar
Timothy J. Baek committed
28
29

			// do your loop
Timothy J. Baek's avatar
Timothy J. Baek committed
30
			if (voices.length > 0) {
Timothy J. Baek's avatar
Timothy J. Baek committed
31
32
33
				clearInterval(getVoicesLoop);
			}
		}, 100);
Timothy J. Baek's avatar
Timothy J. Baek committed
34
35
36
37
38
39
40
41
42
43
44
45
46
	};

	onMount(async () => {
		let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');

		engine = settings?.speech?.engine ?? '';
		speaker = settings?.speech?.speaker ?? '';

		if (engine === 'openai') {
			getOpenAIVoices();
		} else {
			getWebAPIVoices();
		}
Timothy J. Baek's avatar
Timothy J. Baek committed
47
48
49
50
51
52
53
	});
</script>

<form
	class="flex flex-col h-full justify-between space-y-3 text-sm"
	on:submit|preventDefault={() => {
		saveSettings({
Timothy J. Baek's avatar
Timothy J. Baek committed
54
55
56
57
			speech: {
				engine: engine !== '' ? engine : undefined,
				speaker: speaker !== '' ? speaker : undefined
			}
Timothy J. Baek's avatar
Timothy J. Baek committed
58
59
60
61
62
		});
		dispatch('save');
	}}
>
	<div class=" space-y-3">
Timothy J. Baek's avatar
Timothy J. Baek committed
63
64
65
66
67
		<div class=" py-0.5 flex w-full justify-between">
			<div class=" self-center text-sm font-medium">Speech Engine</div>
			<div class="flex items-center relative">
				<select
					class="w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
Timothy J. Baek's avatar
Timothy J. Baek committed
68
					bind:value={engine}
Timothy J. Baek's avatar
Timothy J. Baek committed
69
70
					placeholder="Select a mode"
					on:change={(e) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
71
72
73
74
75
76
77
						if (e.target.value === 'openai') {
							getOpenAIVoices();
							speaker = 'alloy';
						} else {
							getWebAPIVoices();
							speaker = '';
						}
Timothy J. Baek's avatar
Timothy J. Baek committed
78
79
80
81
82
83
84
85
86
87
					}}
				>
					<option value="">Default (Web API)</option>
					<option value="openai">Open AI</option>
				</select>
			</div>
		</div>

		<hr class=" dark:border-gray-700" />

Timothy J. Baek's avatar
Timothy J. Baek committed
88
		{#if engine === ''}
Timothy J. Baek's avatar
Timothy J. Baek committed
89
			<div>
Timothy J. Baek's avatar
Timothy J. Baek committed
90
				<div class=" mb-2.5 text-sm font-medium">Set Voice</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
91
92
93
94
				<div class="flex w-full">
					<div class="flex-1">
						<select
							class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
Timothy J. Baek's avatar
Timothy J. Baek committed
95
							bind:value={speaker}
Timothy J. Baek's avatar
Timothy J. Baek committed
96
97
98
							placeholder="Select a voice"
						>
							<option value="" selected>Default</option>
Timothy J. Baek's avatar
Timothy J. Baek committed
99
							{#each voices.filter((v) => v.localService === true) as voice}
Timothy J. Baek's avatar
Timothy J. Baek committed
100
101
102
103
104
105
106
								<option value={voice.name} class="bg-gray-100 dark:bg-gray-700">{voice.name}</option
								>
							{/each}
						</select>
					</div>
				</div>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
		{:else if engine === 'openai'}
			<div>
				<div class=" mb-2.5 text-sm font-medium">Set Voice</div>
				<div class="flex w-full">
					<div class="flex-1">
						<select
							class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
							bind:value={speaker}
							placeholder="Select a voice"
						>
							{#each voices as voice}
								<option value={voice.name} class="bg-gray-100 dark:bg-gray-700">{voice.name}</option
								>
							{/each}
						</select>
					</div>
				</div>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
125
		{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
126
127
128
129
130
131
132
133
134
135
136
	</div>

	<div class="flex justify-end pt-3 text-sm font-medium">
		<button
			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
			type="submit"
		>
			Save
		</button>
	</div>
</form>