SettingsModal.svelte 12.6 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
<script lang="ts">
2
	import { getContext } from 'svelte';
Jannik Streidl's avatar
Jannik Streidl committed
3
	import { toast } from 'svelte-sonner';
Timothy J. Baek's avatar
Timothy J. Baek committed
4
	import { models, settings, user } from '$lib/stores';
5

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
6
	import { getModels } from '$lib/utils';
Timothy J. Baek's avatar
Timothy J. Baek committed
7
8

	import Modal from '../common/Modal.svelte';
9
	import Account from './Settings/Account.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
10
11
12
13
	import About from './Settings/About.svelte';
	import Models from './Settings/Models.svelte';
	import General from './Settings/General.svelte';
	import Interface from './Settings/Interface.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
14
	import Audio from './Settings/Audio.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
15
	import Chats from './Settings/Chats.svelte';
16
	import Connections from './Settings/Connections.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
17
	import Images from './Settings/Images.svelte';
18

19
20
	const i18n = getContext('i18n');

Timothy J. Baek's avatar
Timothy J. Baek committed
21
	export let show = false;
22
23
24
25

	const saveSettings = async (updated) => {
		console.log(updated);
		await settings.set({ ...$settings, ...updated });
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
26
		await models.set(await getModels(localStorage.token));
27
28
		localStorage.setItem('settings', JSON.stringify($settings));
	};
29

30
	let selectedTab = 'general';
Timothy J. Baek's avatar
Timothy J. Baek committed
31
32
33
</script>

<Modal bind:show>
Timothy J. Baek's avatar
Timothy J. Baek committed
34
	<div>
Timothy J. Baek's avatar
Timothy J. Baek committed
35
		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
36
			<div class=" text-lg font-medium self-center">{$i18n.t('Settings')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
			<button
				class="self-center"
				on:click={() => {
					show = false;
				}}
			>
				<svg
					xmlns="http://www.w3.org/2000/svg"
					viewBox="0 0 20 20"
					fill="currentColor"
					class="w-5 h-5"
				>
					<path
						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
					/>
				</svg>
			</button>
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
55
		<hr class=" dark:border-gray-800" />
Timothy J. Baek's avatar
Timothy J. Baek committed
56

Timothy J. Baek's avatar
Timothy J. Baek committed
57
58
		<div class="flex flex-col md:flex-row w-full p-4 md:space-x-4">
			<div
Timothy J. Baek's avatar
Timothy J. Baek committed
59
				class="tabs flex flex-row overflow-x-auto space-x-1 md:space-x-0 md:space-y-1 md:flex-col flex-1 md:flex-none md:w-40 dark:text-gray-200 text-xs text-left mb-3 md:mb-0"
Timothy J. Baek's avatar
Timothy J. Baek committed
60
61
			>
				<button
62
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
Timothy J. Baek's avatar
Timothy J. Baek committed
63
					'general'
Timothy J. Baek's avatar
Timothy J. Baek committed
64
65
						? 'bg-gray-200 dark:bg-gray-700'
						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
Timothy J. Baek's avatar
Timothy J. Baek committed
66
					on:click={() => {
67
						selectedTab = 'general';
Timothy J. Baek's avatar
Timothy J. Baek committed
68
					}}
Timothy J. Baek's avatar
Timothy J. Baek committed
69
				>
Timothy J. Baek's avatar
Timothy J. Baek committed
70
71
72
73
74
75
76
77
78
79
80
81
82
83
					<div class=" self-center mr-2">
						<svg
							xmlns="http://www.w3.org/2000/svg"
							viewBox="0 0 20 20"
							fill="currentColor"
							class="w-4 h-4"
						>
							<path
								fill-rule="evenodd"
								d="M8.34 1.804A1 1 0 019.32 1h1.36a1 1 0 01.98.804l.295 1.473c.497.144.971.342 1.416.587l1.25-.834a1 1 0 011.262.125l.962.962a1 1 0 01.125 1.262l-.834 1.25c.245.445.443.919.587 1.416l1.473.294a1 1 0 01.804.98v1.361a1 1 0 01-.804.98l-1.473.295a6.95 6.95 0 01-.587 1.416l.834 1.25a1 1 0 01-.125 1.262l-.962.962a1 1 0 01-1.262.125l-1.25-.834a6.953 6.953 0 01-1.416.587l-.294 1.473a1 1 0 01-.98.804H9.32a1 1 0 01-.98-.804l-.295-1.473a6.957 6.957 0 01-1.416-.587l-1.25.834a1 1 0 01-1.262-.125l-.962-.962a1 1 0 01-.125-1.262l.834-1.25a6.957 6.957 0 01-.587-1.416l-1.473-.294A1 1 0 011 10.68V9.32a1 1 0 01.804-.98l1.473-.295c.144-.497.342-.971.587-1.416l-.834-1.25a1 1 0 01.125-1.262l.962-.962A1 1 0 015.38 3.03l1.25.834a6.957 6.957 0 011.416-.587l.294-1.473zM13 10a3 3 0 11-6 0 3 3 0 016 0z"
								clip-rule="evenodd"
							/>
						</svg>
					</div>
84
					<div class=" self-center">{$i18n.t('General')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
85
86
				</button>

87
88
89
				{#if $user?.role === 'admin'}
					<button
						class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
90
						'connections'
91
92
93
							? 'bg-gray-200 dark:bg-gray-700'
							: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
						on:click={() => {
94
							selectedTab = 'connections';
95
96
97
98
99
						}}
					>
						<div class=" self-center mr-2">
							<svg
								xmlns="http://www.w3.org/2000/svg"
Timothy J. Baek's avatar
Timothy J. Baek committed
100
								viewBox="0 0 16 16"
101
102
103
104
								fill="currentColor"
								class="w-4 h-4"
							>
								<path
Timothy J. Baek's avatar
Timothy J. Baek committed
105
									d="M1 9.5A3.5 3.5 0 0 0 4.5 13H12a3 3 0 0 0 .917-5.857 2.503 2.503 0 0 0-3.198-3.019 3.5 3.5 0 0 0-6.628 2.171A3.5 3.5 0 0 0 1 9.5Z"
106
107
108
								/>
							</svg>
						</div>
109
						<div class=" self-center">{$i18n.t('Connections')}</div>
110
					</button>
111

112
113
					<button
						class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
Timothy J. Baek's avatar
Timothy J. Baek committed
114
						'models'
115
116
117
							? 'bg-gray-200 dark:bg-gray-700'
							: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
						on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
118
							selectedTab = 'models';
119
120
121
122
123
						}}
					>
						<div class=" self-center mr-2">
							<svg
								xmlns="http://www.w3.org/2000/svg"
Timothy J. Baek's avatar
Timothy J. Baek committed
124
								viewBox="0 0 20 20"
125
126
127
128
								fill="currentColor"
								class="w-4 h-4"
							>
								<path
Timothy J. Baek's avatar
Timothy J. Baek committed
129
130
131
									fill-rule="evenodd"
									d="M10 1c3.866 0 7 1.79 7 4s-3.134 4-7 4-7-1.79-7-4 3.134-4 7-4zm5.694 8.13c.464-.264.91-.583 1.306-.952V10c0 2.21-3.134 4-7 4s-7-1.79-7-4V8.178c.396.37.842.688 1.306.953C5.838 10.006 7.854 10.5 10 10.5s4.162-.494 5.694-1.37zM3 13.179V15c0 2.21 3.134 4 7 4s7-1.79 7-4v-1.822c-.396.37-.842.688-1.306.953-1.532.875-3.548 1.369-5.694 1.369s-4.162-.494-5.694-1.37A7.009 7.009 0 013 13.179z"
									clip-rule="evenodd"
132
133
134
								/>
							</svg>
						</div>
135
						<div class=" self-center">{$i18n.t('Models')}</div>
136
137
					</button>
				{/if}
138

139
				<button
140
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
Timothy J. Baek's avatar
Timothy J. Baek committed
141
					'interface'
Timothy J. Baek's avatar
Timothy J. Baek committed
142
143
						? 'bg-gray-200 dark:bg-gray-700'
						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
144
					on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
145
						selectedTab = 'interface';
146
147
148
149
150
					}}
				>
					<div class=" self-center mr-2">
						<svg
							xmlns="http://www.w3.org/2000/svg"
Timothy J. Baek's avatar
Timothy J. Baek committed
151
							viewBox="0 0 16 16"
152
153
154
155
							fill="currentColor"
							class="w-4 h-4"
						>
							<path
Timothy J. Baek's avatar
Timothy J. Baek committed
156
								fill-rule="evenodd"
Timothy J. Baek's avatar
Timothy J. Baek committed
157
								d="M2 4.25A2.25 2.25 0 0 1 4.25 2h7.5A2.25 2.25 0 0 1 14 4.25v5.5A2.25 2.25 0 0 1 11.75 12h-1.312c.1.128.21.248.328.36a.75.75 0 0 1 .234.545v.345a.75.75 0 0 1-.75.75h-4.5a.75.75 0 0 1-.75-.75v-.345a.75.75 0 0 1 .234-.545c.118-.111.228-.232.328-.36H4.25A2.25 2.25 0 0 1 2 9.75v-5.5Zm2.25-.75a.75.75 0 0 0-.75.75v4.5c0 .414.336.75.75.75h7.5a.75.75 0 0 0 .75-.75v-4.5a.75.75 0 0 0-.75-.75h-7.5Z"
Timothy J. Baek's avatar
Timothy J. Baek committed
158
159
160
161
								clip-rule="evenodd"
							/>
						</svg>
					</div>
162
					<div class=" self-center">{$i18n.t('Interface')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
163
164
165
166
				</button>

				<button
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
Timothy J. Baek's avatar
Timothy J. Baek committed
167
					'audio'
Timothy J. Baek's avatar
Timothy J. Baek committed
168
169
170
						? 'bg-gray-200 dark:bg-gray-700'
						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
					on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
171
						selectedTab = 'audio';
Timothy J. Baek's avatar
Timothy J. Baek committed
172
173
174
175
176
177
178
179
180
181
182
183
184
185
					}}
				>
					<div class=" self-center mr-2">
						<svg
							xmlns="http://www.w3.org/2000/svg"
							viewBox="0 0 16 16"
							fill="currentColor"
							class="w-4 h-4"
						>
							<path
								d="M7.557 2.066A.75.75 0 0 1 8 2.75v10.5a.75.75 0 0 1-1.248.56L3.59 11H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h1.59l3.162-2.81a.75.75 0 0 1 .805-.124ZM12.95 3.05a.75.75 0 1 0-1.06 1.06 5.5 5.5 0 0 1 0 7.78.75.75 0 1 0 1.06 1.06 7 7 0 0 0 0-9.9Z"
							/>
							<path
								d="M10.828 5.172a.75.75 0 1 0-1.06 1.06 2.5 2.5 0 0 1 0 3.536.75.75 0 1 0 1.06 1.06 4 4 0 0 0 0-5.656Z"
186
187
188
							/>
						</svg>
					</div>
189
					<div class=" self-center">{$i18n.t('Audio')}</div>
190
				</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
191

Timothy J. Baek's avatar
Timothy J. Baek committed
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
				{#if $user.role === 'admin'}
					<button
						class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
						'images'
							? 'bg-gray-200 dark:bg-gray-700'
							: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
						on:click={() => {
							selectedTab = 'images';
						}}
					>
						<div class=" self-center mr-2">
							<svg
								xmlns="http://www.w3.org/2000/svg"
								viewBox="0 0 16 16"
								fill="currentColor"
								class="w-4 h-4"
							>
								<path
									fill-rule="evenodd"
									d="M2 4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4Zm10.5 5.707a.5.5 0 0 0-.146-.353l-1-1a.5.5 0 0 0-.708 0L9.354 9.646a.5.5 0 0 1-.708 0L6.354 7.354a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0-.146.353V12a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V9.707ZM12 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
									clip-rule="evenodd"
								/>
							</svg>
						</div>
216
						<div class=" self-center">{$i18n.t('Images')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
217
218
					</button>
				{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
219

220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
				<button
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
					'chats'
						? 'bg-gray-200 dark:bg-gray-700'
						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
					on:click={() => {
						selectedTab = 'chats';
					}}
				>
					<div class=" self-center mr-2">
						<svg
							xmlns="http://www.w3.org/2000/svg"
							viewBox="0 0 16 16"
							fill="currentColor"
							class="w-4 h-4"
						>
							<path
								fill-rule="evenodd"
								d="M8 2C4.262 2 1 4.57 1 8c0 1.86.98 3.486 2.455 4.566a3.472 3.472 0 0 1-.469 1.26.75.75 0 0 0 .713 1.14 6.961 6.961 0 0 0 3.06-1.06c.403.062.818.094 1.241.094 3.738 0 7-2.57 7-6s-3.262-6-7-6ZM5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm7-1a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM8 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
								clip-rule="evenodd"
							/>
						</svg>
					</div>
243
					<div class=" self-center">{$i18n.t('Chats')}</div>
244
245
				</button>

Timothy J. Baek's avatar
Timothy J. Baek committed
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
				<button
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
					'account'
						? 'bg-gray-200 dark:bg-gray-700'
						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
					on:click={() => {
						selectedTab = 'account';
					}}
				>
					<div class=" self-center mr-2">
						<svg
							xmlns="http://www.w3.org/2000/svg"
							viewBox="0 0 16 16"
							fill="currentColor"
							class="w-4 h-4"
						>
							<path
								fill-rule="evenodd"
								d="M15 8A7 7 0 1 1 1 8a7 7 0 0 1 14 0Zm-5-2a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM8 9c-1.825 0-3.422.977-4.295 2.437A5.49 5.49 0 0 0 8 13.5a5.49 5.49 0 0 0 4.294-2.063A4.997 4.997 0 0 0 8 9Z"
								clip-rule="evenodd"
							/>
						</svg>
					</div>
269
					<div class=" self-center">{$i18n.t('Account')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
270
271
				</button>

Timothy J. Baek's avatar
Timothy J. Baek committed
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
				<button
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
					'about'
						? 'bg-gray-200 dark:bg-gray-700'
						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
					on:click={() => {
						selectedTab = 'about';
					}}
				>
					<div class=" self-center mr-2">
						<svg
							xmlns="http://www.w3.org/2000/svg"
							viewBox="0 0 20 20"
							fill="currentColor"
							class="w-4 h-4"
						>
							<path
								fill-rule="evenodd"
								d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z"
								clip-rule="evenodd"
							/>
						</svg>
					</div>
295
					<div class=" self-center">{$i18n.t('About')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
296
				</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
297
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
298
			<div class="flex-1 md:min-h-[25rem]">
299
				{#if selectedTab === 'general'}
Timothy J. Baek's avatar
Timothy J. Baek committed
300
301
302
303
					<General
						{getModels}
						{saveSettings}
						on:save={() => {
304
							toast.success($i18n.t('Settings saved successfully!'));
Timothy J. Baek's avatar
Timothy J. Baek committed
305
306
						}}
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
307
				{:else if selectedTab === 'models'}
Timothy J. Baek's avatar
Timothy J. Baek committed
308
					<Models {getModels} />
309
310
				{:else if selectedTab === 'connections'}
					<Connections
Timothy J. Baek's avatar
Timothy J. Baek committed
311
312
						{getModels}
						on:save={() => {
313
							toast.success($i18n.t('Settings saved successfully!'));
314
						}}
Timothy J. Baek's avatar
Timothy J. Baek committed
315
					/>
316
				{:else if selectedTab === 'interface'}
Timothy J. Baek's avatar
Timothy J. Baek committed
317
					<Interface
Timothy J. Baek's avatar
Timothy J. Baek committed
318
						{saveSettings}
Timothy J. Baek's avatar
Timothy J. Baek committed
319
						on:save={() => {
320
							toast.success($i18n.t('Settings saved successfully!'));
321
						}}
Timothy J. Baek's avatar
Timothy J. Baek committed
322
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
323
324
				{:else if selectedTab === 'audio'}
					<Audio
Timothy J. Baek's avatar
Timothy J. Baek committed
325
326
						{saveSettings}
						on:save={() => {
327
							toast.success($i18n.t('Settings saved successfully!'));
Timothy J. Baek's avatar
Timothy J. Baek committed
328
						}}
Timothy J. Baek's avatar
Timothy J. Baek committed
329
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
330
331
332
333
				{:else if selectedTab === 'images'}
					<Images
						{saveSettings}
						on:save={() => {
334
							toast.success($i18n.t('Settings saved successfully!'));
Timothy J. Baek's avatar
Timothy J. Baek committed
335
336
						}}
					/>
337
				{:else if selectedTab === 'chats'}
Timothy J. Baek's avatar
Timothy J. Baek committed
338
					<Chats {saveSettings} />
339
				{:else if selectedTab === 'account'}
340
341
					<Account
						saveHandler={() => {
342
							toast.success($i18n.t('Settings saved successfully!'));
343
						}}
344
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
345
				{:else if selectedTab === 'about'}
Timothy J. Baek's avatar
Timothy J. Baek committed
346
					<About />
Timothy J. Baek's avatar
Timothy J. Baek committed
347
348
				{/if}
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
349
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
350
351
	</div>
</Modal>
352
353
354
355
356
357
358
359
360

<style>
	input::-webkit-outer-spin-button,
	input::-webkit-inner-spin-button {
		/* display: none; <- Crashes Chrome on hover */
		-webkit-appearance: none;
		margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
	}

361
362
363
364
365
366
367
368
369
	.tabs::-webkit-scrollbar {
		display: none; /* for Chrome, Safari and Opera */
	}

	.tabs {
		-ms-overflow-style: none; /* IE and Edge */
		scrollbar-width: none; /* Firefox */
	}

370
371
372
373
	input[type='number'] {
		-moz-appearance: textfield; /* Firefox */
	}
</style>