Interface.svelte 9.52 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
2
3
<script lang="ts">
	import { getBackendConfig } from '$lib/apis';
	import { setDefaultPromptSuggestions } from '$lib/apis/configs';
4
	import { config, models, settings, user } from '$lib/stores';
5
	import { createEventDispatcher, onMount, getContext } from 'svelte';
Jannik Streidl's avatar
Jannik Streidl committed
6
	import { toast } from 'svelte-sonner';
7
	import Tooltip from '$lib/components/common/Tooltip.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
8
9
	import { updateUserInfo } from '$lib/apis/users';
	import { getUserPosition } from '$lib/utils';
Timothy J. Baek's avatar
Timothy J. Baek committed
10
11
	const dispatch = createEventDispatcher();

12
13
	const i18n = getContext('i18n');

Timothy J. Baek's avatar
Timothy J. Baek committed
14
15
16
17
18
	export let saveSettings: Function;

	// Addons
	let titleAutoGenerate = true;
	let responseAutoCopy = false;
19
	let widescreenMode = false;
20
	let splitLargeChunks = false;
Timothy J. Baek's avatar
Timothy J. Baek committed
21
	let userLocation = false;
Timothy J. Baek's avatar
Timothy J. Baek committed
22

Timothy J. Baek's avatar
Timothy J. Baek committed
23
	// Interface
24
	let defaultModelId = '';
25
	let showUsername = false;
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
26

Timothy J. Baek's avatar
Timothy J. Baek committed
27
	let chatBubble = true;
28
	let chatDirection: 'LTR' | 'RTL' = 'LTR';
29

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
30
31
	let showEmojiInCall = false;

32
33
34
35
36
	const toggleSplitLargeChunks = async () => {
		splitLargeChunks = !splitLargeChunks;
		saveSettings({ splitLargeChunks: splitLargeChunks });
	};

37
38
39
	const togglewidescreenMode = async () => {
		widescreenMode = !widescreenMode;
		saveSettings({ widescreenMode: widescreenMode });
Timothy J. Baek's avatar
Timothy J. Baek committed
40
41
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
42
43
44
45
46
	const toggleChatBubble = async () => {
		chatBubble = !chatBubble;
		saveSettings({ chatBubble: chatBubble });
	};

47
48
49
50
51
	const toggleShowUsername = async () => {
		showUsername = !showUsername;
		saveSettings({ showUsername: showUsername });
	};

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
52
53
54
55
56
	const toggleEmojiInCall = async () => {
		showEmojiInCall = !showEmojiInCall;
		saveSettings({ showEmojiInCall: showEmojiInCall });
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
	const toggleUserLocation = async () => {
		userLocation = !userLocation;

		if (userLocation) {
			const position = await getUserPosition().catch((error) => {
				toast.error(error.message);
				return null;
			});

			if (position) {
				await updateUserInfo(localStorage.token, { location: position });
				toast.success('User location successfully retrieved.');
			} else {
				userLocation = false;
			}
		}

		saveSettings({ userLocation });
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
77
78
	const toggleTitleAutoGenerate = async () => {
		titleAutoGenerate = !titleAutoGenerate;
79
80
81
82
83
84
		saveSettings({
			title: {
				...$settings.title,
				auto: titleAutoGenerate
			}
		});
Timothy J. Baek's avatar
Timothy J. Baek committed
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
	};

	const toggleResponseAutoCopy = async () => {
		const permission = await navigator.clipboard
			.readText()
			.then(() => {
				return 'granted';
			})
			.catch(() => {
				return '';
			});

		console.log(permission);

		if (permission === 'granted') {
			responseAutoCopy = !responseAutoCopy;
			saveSettings({ responseAutoCopy: responseAutoCopy });
		} else {
			toast.error(
				'Clipboard write permission denied. Please check your browser settings to grant the necessary access.'
			);
		}
	};

109
110
	const toggleChangeChatDirection = async () => {
		chatDirection = chatDirection === 'LTR' ? 'RTL' : 'LTR';
Timothy J. Baek's avatar
Timothy J. Baek committed
111
		saveSettings({ chatDirection });
112
113
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
114
	const updateInterfaceHandler = async () => {
115
		saveSettings({
116
			models: [defaultModelId]
117
		});
Timothy J. Baek's avatar
Timothy J. Baek committed
118
119
120
	};

	onMount(async () => {
121
		titleAutoGenerate = $settings?.title?.auto ?? true;
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
122

123
124
		responseAutoCopy = $settings.responseAutoCopy ?? false;
		showUsername = $settings.showUsername ?? false;
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
125
126
127

		showEmojiInCall = $settings.showEmojiInCall ?? false;

128
		chatBubble = $settings.chatBubble ?? true;
129
		widescreenMode = $settings.widescreenMode ?? false;
130
131
		splitLargeChunks = $settings.splitLargeChunks ?? false;
		chatDirection = $settings.chatDirection ?? 'LTR';
Timothy J. Baek's avatar
Timothy J. Baek committed
132
		userLocation = $settings.userLocation ?? false;
133
134

		defaultModelId = ($settings?.models ?? ['']).at(0);
Timothy J. Baek's avatar
Timothy J. Baek committed
135
136
137
138
139
140
141
142
143
144
	});
</script>

<form
	class="flex flex-col h-full justify-between space-y-3 text-sm"
	on:submit|preventDefault={() => {
		updateInterfaceHandler();
		dispatch('save');
	}}
>
Timothy J. Baek's avatar
Timothy J. Baek committed
145
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem]">
Timothy J. Baek's avatar
Timothy J. Baek committed
146
		<div>
147
			<div class=" mb-1 text-sm font-medium">{$i18n.t('WebUI Add-ons')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
148

Timothy J. Baek's avatar
Timothy J. Baek committed
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
			<div>
				<div class=" py-0.5 flex w-full justify-between">
					<div class=" self-center text-xs font-medium">{$i18n.t('Chat Bubble UI')}</div>

					<button
						class="p-1 px-3 text-xs flex rounded transition"
						on:click={() => {
							toggleChatBubble();
						}}
						type="button"
					>
						{#if chatBubble === 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>

Timothy J. Baek's avatar
Timothy J. Baek committed
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
			<div>
				<div class=" py-0.5 flex w-full justify-between">
					<div class=" self-center text-xs font-medium">{$i18n.t('Widescreen Mode')}</div>

					<button
						class="p-1 px-3 text-xs flex rounded transition"
						on:click={() => {
							togglewidescreenMode();
						}}
						type="button"
					>
						{#if widescreenMode === 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>

Timothy J. Baek's avatar
Timothy J. Baek committed
189
190
			<div>
				<div class=" py-0.5 flex w-full justify-between">
191
					<div class=" self-center text-xs font-medium">{$i18n.t('Title Auto-Generation')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
192
193
194
195
196
197
198
199
200

					<button
						class="p-1 px-3 text-xs flex rounded transition"
						on:click={() => {
							toggleTitleAutoGenerate();
						}}
						type="button"
					>
						{#if titleAutoGenerate === true}
201
							<span class="ml-2 self-center">{$i18n.t('On')}</span>
Timothy J. Baek's avatar
Timothy J. Baek committed
202
						{:else}
203
							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
Timothy J. Baek's avatar
Timothy J. Baek committed
204
205
206
207
208
209
210
						{/if}
					</button>
				</div>
			</div>

			<div>
				<div class=" py-0.5 flex w-full justify-between">
211
212
213
					<div class=" self-center text-xs font-medium">
						{$i18n.t('Response AutoCopy to Clipboard')}
					</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
214
215
216
217
218
219
220
221
222

					<button
						class="p-1 px-3 text-xs flex rounded transition"
						on:click={() => {
							toggleResponseAutoCopy();
						}}
						type="button"
					>
						{#if responseAutoCopy === true}
223
							<span class="ml-2 self-center">{$i18n.t('On')}</span>
Timothy J. Baek's avatar
Timothy J. Baek committed
224
						{:else}
225
							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
Timothy J. Baek's avatar
Timothy J. Baek committed
226
227
228
229
						{/if}
					</button>
				</div>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
230

Timothy J. Baek's avatar
Timothy J. Baek committed
231
232
			<div>
				<div class=" py-0.5 flex w-full justify-between">
Timothy J. Baek's avatar
Timothy J. Baek committed
233
					<div class=" self-center text-xs font-medium">{$i18n.t('Allow User Location')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
234
235
236
237

					<button
						class="p-1 px-3 text-xs flex rounded transition"
						on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
238
							toggleUserLocation();
Timothy J. Baek's avatar
Timothy J. Baek committed
239
240
241
						}}
						type="button"
					>
Timothy J. Baek's avatar
Timothy J. Baek committed
242
						{#if userLocation === true}
243
							<span class="ml-2 self-center">{$i18n.t('On')}</span>
Timothy J. Baek's avatar
Timothy J. Baek committed
244
						{:else}
245
							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
Timothy J. Baek's avatar
Timothy J. Baek committed
246
247
248
249
250
						{/if}
					</button>
				</div>
			</div>

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
			<div>
				<div class=" py-0.5 flex w-full justify-between">
					<div class=" self-center text-xs font-medium">{$i18n.t('Display Emoji in Call')}</div>

					<button
						class="p-1 px-3 text-xs flex rounded transition"
						on:click={() => {
							toggleEmojiInCall();
						}}
						type="button"
					>
						{#if showEmojiInCall === 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>

271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
			{#if !$settings.chatBubble}
				<div>
					<div class=" py-0.5 flex w-full justify-between">
						<div class=" self-center text-xs font-medium">
							{$i18n.t('Display the username instead of You in the Chat')}
						</div>

						<button
							class="p-1 px-3 text-xs flex rounded transition"
							on:click={() => {
								toggleShowUsername();
							}}
							type="button"
						>
							{#if showUsername === true}
								<span class="ml-2 self-center">{$i18n.t('On')}</span>
							{:else}
								<span class="ml-2 self-center">{$i18n.t('Off')}</span>
							{/if}
						</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
291
					</div>
292
				</div>
293
			{/if}
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315

			<div>
				<div class=" py-0.5 flex w-full justify-between">
					<div class=" self-center text-xs font-medium">
						{$i18n.t('Fluidly stream large external response chunks')}
					</div>

					<button
						class="p-1 px-3 text-xs flex rounded transition"
						on:click={() => {
							toggleSplitLargeChunks();
						}}
						type="button"
					>
						{#if splitLargeChunks === 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>
Timothy J. Baek's avatar
Timothy J. Baek committed
316
317
		</div>

318
319
320
321
322
323
324
325
326
		<div>
			<div class=" py-0.5 flex w-full justify-between">
				<div class=" self-center text-xs font-medium">{$i18n.t('Chat direction')}</div>

				<button
					class="p-1 px-3 text-xs flex rounded transition"
					on:click={toggleChangeChatDirection}
					type="button"
				>
Timothy J. Baek's avatar
Timothy J. Baek committed
327
					{#if chatDirection === 'LTR'}
328
329
330
331
332
333
334
335
						<span class="ml-2 self-center">{$i18n.t('LTR')}</span>
					{:else}
						<span class="ml-2 self-center">{$i18n.t('RTL')}</span>
					{/if}
				</button>
			</div>
		</div>

336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
		<hr class=" dark:border-gray-850" />

		<div class=" space-y-1 mb-3">
			<div class="mb-2">
				<div class="flex justify-between items-center text-xs">
					<div class=" text-xs font-medium">{$i18n.t('Default Model')}</div>
				</div>
			</div>

			<div class="flex-1 mr-2">
				<select
					class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
					bind:value={defaultModelId}
					placeholder="Select a model"
				>
					<option value="" disabled selected>{$i18n.t('Select a model')}</option>
					{#each $models.filter((model) => model.id) as model}
						<option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
					{/each}
				</select>
			</div>
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
358
359
	</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
360
	<div class="flex justify-end text-sm font-medium">
Timothy J. Baek's avatar
Timothy J. Baek committed
361
		<button
Timothy J. Baek's avatar
Timothy J. Baek committed
362
			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
Timothy J. Baek's avatar
Timothy J. Baek committed
363
364
			type="submit"
		>
Jannik Streidl's avatar
Jannik Streidl committed
365
			{$i18n.t('Save')}
Timothy J. Baek's avatar
Timothy J. Baek committed
366
367
368
		</button>
	</div>
</form>