Sidebar.svelte 21.9 KB
Newer Older
1
<script lang="ts">
Timothy J. Baek's avatar
Timothy J. Baek committed
2
	import { goto } from '$app/navigation';
Timothy J. Baek's avatar
Timothy J. Baek committed
3
4
5
6
7
8
9
10
	import {
		user,
		chats,
		settings,
		showSettings,
		chatId,
		tags,
		showSidebar,
Timothy J. Baek's avatar
Timothy J. Baek committed
11
12
		mobile,
		showArchivedChats
Timothy J. Baek's avatar
Timothy J. Baek committed
13
	} from '$lib/stores';
14
	import { onMount, getContext } from 'svelte';
15
16
17

	const i18n = getContext('i18n');

Timothy J. Baek's avatar
Timothy J. Baek committed
18
19
20
	import {
		deleteChatById,
		getChatList,
Henry Holloway's avatar
Henry Holloway committed
21
		getChatById,
Timothy J. Baek's avatar
Timothy J. Baek committed
22
		getChatListByTagName,
Timothy J. Baek's avatar
Timothy J. Baek committed
23
		updateChatById,
Timothy J. Baek's avatar
Timothy J. Baek committed
24
25
		getAllChatTags,
		archiveChatById
Timothy J. Baek's avatar
Timothy J. Baek committed
26
	} from '$lib/apis/chats';
Jannik Streidl's avatar
Jannik Streidl committed
27
	import { toast } from 'svelte-sonner';
Timothy J. Baek's avatar
Timothy J. Baek committed
28
	import { fade, slide } from 'svelte/transition';
29
	import { WEBUI_BASE_URL } from '$lib/constants';
30
	import Tooltip from '../common/Tooltip.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
31
	import ChatMenu from './Sidebar/ChatMenu.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
32
	import ShareChatModal from '../chat/ShareChatModal.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
33
	import ArchiveBox from '../icons/ArchiveBox.svelte';
34
	import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
35
	import UserMenu from './Sidebar/UserMenu.svelte';
36
	import { updateUserSettings } from '$lib/apis/users';
37

Timothy J. Baek's avatar
Timothy J. Baek committed
38
	const BREAKPOINT = 768;
Timothy J. Baek's avatar
Timothy J. Baek committed
39

40
41
42
	let show = false;
	let navElement;

Timothy J. Baek's avatar
Timothy J. Baek committed
43
	let title: string = 'UI';
44
	let search = '';
45

Timothy J. Baek's avatar
Timothy J. Baek committed
46
47
	let shareChatId = null;

Timothy J. Baek's avatar
Timothy J. Baek committed
48
49
	let selectedChatId = null;

50
51
	let chatDeleteId = null;
	let chatTitleEditId = null;
52
53
	let chatTitle = '';

Timothy J. Baek's avatar
Timothy J. Baek committed
54
	let showShareChatModal = false;
55
	let showDropdown = false;
56
	let isEditing = false;
57

58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
	let filteredChatList = [];

	$: filteredChatList = $chats.filter((chat) => {
		if (search === '') {
			return true;
		} else {
			let title = chat.title.toLowerCase();
			const query = search.toLowerCase();

			let contentMatches = false;
			// Access the messages within chat.chat.messages
			if (chat.chat && chat.chat.messages && Array.isArray(chat.chat.messages)) {
				contentMatches = chat.chat.messages.some((message) => {
					// Check if message.content exists and includes the search query
					return message.content && message.content.toLowerCase().includes(query);
				});
			}

			return title.includes(query) || contentMatches;
		}
	});

Timothy J. Baek's avatar
Timothy J. Baek committed
80
81
82
83
84
85
86
	mobile;
	const onResize = () => {
		if ($showSidebar && window.innerWidth < BREAKPOINT) {
			showSidebar.set(false);
		}
	};

87
	onMount(async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
88
89
90
91
92
93
94
95
96
97
		mobile.subscribe((e) => {
			if ($showSidebar && e) {
				showSidebar.set(false);
			}

			if (!$showSidebar && !e) {
				showSidebar.set(true);
			}
		});

Timothy J. Baek's avatar
Timothy J. Baek committed
98
		showSidebar.set(window.innerWidth > BREAKPOINT);
Timothy J. Baek's avatar
Timothy J. Baek committed
99
		await chats.set(await getChatList(localStorage.token));
Timothy J. Baek's avatar
Timothy J. Baek committed
100

101
102
		let touchstart;
		let touchend;
Timothy J. Baek's avatar
Timothy J. Baek committed
103
104
105

		function checkDirection() {
			const screenWidth = window.innerWidth;
106
			const swipeDistance = Math.abs(touchend.screenX - touchstart.screenX);
Timothy J. Baek's avatar
Timothy J. Baek committed
107
			if (touchstart.clientX < 40 && swipeDistance >= screenWidth / 8) {
108
				if (touchend.screenX < touchstart.screenX) {
Timothy J. Baek's avatar
Timothy J. Baek committed
109
					showSidebar.set(false);
Timothy J. Baek's avatar
Timothy J. Baek committed
110
				}
111
				if (touchend.screenX > touchstart.screenX) {
Timothy J. Baek's avatar
Timothy J. Baek committed
112
					showSidebar.set(true);
Timothy J. Baek's avatar
Timothy J. Baek committed
113
114
115
116
				}
			}
		}

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
117
		const onTouchStart = (e) => {
118
119
			touchstart = e.changedTouches[0];
			console.log(touchstart.clientX);
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
120
		};
Timothy J. Baek's avatar
Timothy J. Baek committed
121

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
122
		const onTouchEnd = (e) => {
123
			touchend = e.changedTouches[0];
Timothy J. Baek's avatar
Timothy J. Baek committed
124
			checkDirection();
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
125
126
		};

127
128
		window.addEventListener('touchstart', onTouchStart);
		window.addEventListener('touchend', onTouchEnd);
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
129
130

		return () => {
131
132
			window.removeEventListener('touchstart', onTouchStart);
			window.removeEventListener('touchend', onTouchEnd);
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
133
		};
Timothy J. Baek's avatar
Timothy J. Baek committed
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
	});

	// Helper function to fetch and add chat content to each chat
	const enrichChatsWithContent = async (chatList) => {
		const enrichedChats = await Promise.all(
			chatList.map(async (chat) => {
				const chatDetails = await getChatById(localStorage.token, chat.id).catch((error) => null); // Handle error or non-existent chat gracefully
				if (chatDetails) {
					chat.chat = chatDetails.chat; // Assuming chatDetails.chat contains the chat content
				}
				return chat;
			})
		);

		await chats.set(enrichedChats);
	};
150
151
152
153
154
155

	const loadChat = async (id) => {
		goto(`/c/${id}`);
	};

	const editChatTitle = async (id, _title) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
156
		if (_title === '') {
Jannik Streidl's avatar
Jannik Streidl committed
157
			toast.error($i18n.t('Title cannot be an empty string.'));
Timothy J. Baek's avatar
Timothy J. Baek committed
158
159
160
161
162
163
164
165
		} else {
			title = _title;

			await updateChatById(localStorage.token, id, {
				title: _title
			});
			await chats.set(await getChatList(localStorage.token));
		}
166
167
168
	};

	const deleteChat = async (id) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
169
170
171
		const res = await deleteChatById(localStorage.token, id).catch((error) => {
			toast.error(error);
			chatDeleteId = null;
172

Timothy J. Baek's avatar
Timothy J. Baek committed
173
174
175
176
			return null;
		});

		if (res) {
Timothy J. Baek's avatar
Timothy J. Baek committed
177
178
179
180
			if ($chatId === id) {
				goto('/');
			}

Timothy J. Baek's avatar
Timothy J. Baek committed
181
182
			await chats.set(await getChatList(localStorage.token));
		}
183
	};
184
185
186

	const saveSettings = async (updated) => {
		await settings.set({ ...$settings, ...updated });
187
		await updateUserSettings(localStorage.token, { ui: $settings });
188
189
		location.href = '/';
	};
Timothy J. Baek's avatar
Timothy J. Baek committed
190
191
192
193
194

	const archiveChatHandler = async (id) => {
		await archiveChatById(localStorage.token, id);
		await chats.set(await getChatList(localStorage.token));
	};
195
196
</script>

Timothy J. Baek's avatar
Timothy J. Baek committed
197
<ShareChatModal bind:show={showShareChatModal} chatId={shareChatId} />
Timothy J. Baek's avatar
Timothy J. Baek committed
198
<ArchivedChatsModal
Timothy J. Baek's avatar
Timothy J. Baek committed
199
	bind:show={$showArchivedChats}
Timothy J. Baek's avatar
Timothy J. Baek committed
200
201
202
203
	on:change={async () => {
		await chats.set(await getChatList(localStorage.token));
	}}
/>
Timothy J. Baek's avatar
Timothy J. Baek committed
204

Timothy J. Baek's avatar
Timothy J. Baek committed
205
206
207
208
<!-- svelte-ignore a11y-no-static-element-interactions -->

{#if $showSidebar}
	<div
Timothy J. Baek's avatar
Timothy J. Baek committed
209
		class=" fixed md:hidden z-40 top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center overflow-hidden overscroll-contain"
Timothy J. Baek's avatar
Timothy J. Baek committed
210
211
212
213
214
215
		on:mousedown={() => {
			showSidebar.set(!$showSidebar);
		}}
	/>
{/if}

216
217
<div
	bind:this={navElement}
Timothy J. Baek's avatar
Timothy J. Baek committed
218
	id="sidebar"
Timothy J. Baek's avatar
Timothy J. Baek committed
219
	class="h-screen max-h-[100dvh] min-h-screen select-none {$showSidebar
Timothy J. Baek's avatar
Timothy J. Baek committed
220
		? 'md:relative w-[260px]'
Timothy J. Baek's avatar
Timothy J. Baek committed
221
		: '-translate-x-[260px] w-[0px]'} bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-200 text-sm transition fixed z-50 top-0 left-0 rounded-r-2xl
222
        "
Timothy J. Baek's avatar
Timothy J. Baek committed
223
	data-state={$showSidebar}
224
>
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
225
	<div
Timothy J. Baek's avatar
Timothy J. Baek committed
226
		class="py-2.5 my-auto flex flex-col justify-between h-screen max-h-[100dvh] w-[260px] z-50 {$showSidebar
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
227
228
229
			? ''
			: 'invisible'}"
	>
Timothy J. Baek's avatar
Timothy J. Baek committed
230
		<div class="px-2.5 flex justify-between space-x-1 text-gray-600 dark:text-gray-400">
231
			<a
Timothy J. Baek's avatar
Timothy J. Baek committed
232
				id="sidebar-new-chat-button"
Timothy J. Baek's avatar
Timothy J. Baek committed
233
				class="flex flex-1 justify-between rounded-xl px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-850 transition"
234
				href="/"
235
				draggable="false"
Timothy J. Baek's avatar
Timothy J. Baek committed
236
				on:click={async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
237
238
					selectedChatId = null;

239
					await goto('/');
240
					const newChatButton = document.getElementById('new-chat-button');
241
242
					setTimeout(() => {
						newChatButton?.click();
Timothy J. Baek's avatar
Timothy J. Baek committed
243
244
245
246

						if ($mobile) {
							showSidebar.set(false);
						}
247
					}, 0);
248
249
				}}
			>
Timothy J. Baek's avatar
Timothy J. Baek committed
250
251
				<div class="self-center mx-1.5">
					<img
Timothy J. Baek's avatar
Timothy J. Baek committed
252
						crossorigin="anonymous"
Timothy J. Baek's avatar
Timothy J. Baek committed
253
254
255
256
257
						src="{WEBUI_BASE_URL}/static/favicon.png"
						class=" size-6 -translate-x-1.5 rounded-full"
						alt="logo"
					/>
				</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
258
259
260
				<div class=" self-center font-medium text-sm text-gray-850 dark:text-white">
					{$i18n.t('New Chat')}
				</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
261
				<div class="self-center ml-auto">
262
263
264
265
					<svg
						xmlns="http://www.w3.org/2000/svg"
						viewBox="0 0 20 20"
						fill="currentColor"
Timothy J. Baek's avatar
Timothy J. Baek committed
266
						class="size-5"
267
268
269
270
271
272
273
274
275
					>
						<path
							d="M5.433 13.917l1.262-3.155A4 4 0 017.58 9.42l6.92-6.918a2.121 2.121 0 013 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 01-.65-.65z"
						/>
						<path
							d="M3.5 5.75c0-.69.56-1.25 1.25-1.25H10A.75.75 0 0010 3H4.75A2.75 2.75 0 002 5.75v9.5A2.75 2.75 0 004.75 18h9.5A2.75 2.75 0 0017 15.25V10a.75.75 0 00-1.5 0v5.25c0 .69-.56 1.25-1.25 1.25h-9.5c-.69 0-1.25-.56-1.25-1.25v-9.5z"
						/>
					</svg>
				</div>
276
			</a>
Timothy J. Baek's avatar
Timothy J. Baek committed
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300

			<button
				class=" cursor-pointer px-2 py-2 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
				on:click={() => {
					showSidebar.set(!$showSidebar);
				}}
			>
				<div class=" m-auto self-center">
					<svg
						xmlns="http://www.w3.org/2000/svg"
						fill="none"
						viewBox="0 0 24 24"
						stroke-width="2"
						stroke="currentColor"
						class="size-5"
					>
						<path
							stroke-linecap="round"
							stroke-linejoin="round"
							d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12"
						/>
					</svg>
				</div>
			</button>
301
302
		</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
303
		{#if $user?.role === 'admin'}
Timothy J. Baek's avatar
Timothy J. Baek committed
304
			<div class="px-2.5 flex justify-center text-gray-800 dark:text-gray-200">
305
				<a
Timothy J. Baek's avatar
Timothy J. Baek committed
306
					class="flex-grow flex space-x-3 rounded-xl px-2.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
Timothy J. Baek's avatar
Timothy J. Baek committed
307
					href="/workspace"
Timothy J. Baek's avatar
Timothy J. Baek committed
308
309
310
311
					on:click={() => {
						selectedChatId = null;
						chatId.set('');
					}}
312
					draggable="false"
Timothy J. Baek's avatar
Timothy J. Baek committed
313
314
315
316
317
318
				>
					<div class="self-center">
						<svg
							xmlns="http://www.w3.org/2000/svg"
							fill="none"
							viewBox="0 0 24 24"
Timothy J. Baek's avatar
Timothy J. Baek committed
319
							stroke-width="2"
Timothy J. Baek's avatar
Timothy J. Baek committed
320
							stroke="currentColor"
Timothy J. Baek's avatar
Timothy J. Baek committed
321
							class="size-[1.1rem]"
Timothy J. Baek's avatar
Timothy J. Baek committed
322
323
324
325
						>
							<path
								stroke-linecap="round"
								stroke-linejoin="round"
Timothy J. Baek's avatar
Timothy J. Baek committed
326
								d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 0 0 2.25-2.25V6a2.25 2.25 0 0 0-2.25-2.25H6A2.25 2.25 0 0 0 3.75 6v2.25A2.25 2.25 0 0 0 6 10.5Zm0 9.75h2.25A2.25 2.25 0 0 0 10.5 18v-2.25a2.25 2.25 0 0 0-2.25-2.25H6a2.25 2.25 0 0 0-2.25 2.25V18A2.25 2.25 0 0 0 6 20.25Zm9.75-9.75H18a2.25 2.25 0 0 0 2.25-2.25V6A2.25 2.25 0 0 0 18 3.75h-2.25A2.25 2.25 0 0 0 13.5 6v2.25a2.25 2.25 0 0 0 2.25 2.25Z"
Timothy J. Baek's avatar
Timothy J. Baek committed
327
328
329
							/>
						</svg>
					</div>
330

Timothy J. Baek's avatar
Timothy J. Baek committed
331
					<div class="flex self-center">
Timothy J. Baek's avatar
Timothy J. Baek committed
332
						<div class=" self-center font-medium text-sm">{$i18n.t('Workspace')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
333
					</div>
334
				</a>
Timothy J. Baek's avatar
Timothy J. Baek committed
335
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
336
		{/if}
337

338
339
		<div class="relative flex flex-col flex-1 overflow-y-auto">
			{#if !($settings.saveChatHistory ?? true)}
Timothy J. Baek's avatar
Timothy J. Baek committed
340
				<div class="absolute z-40 w-full h-full bg-gray-50/90 dark:bg-black/90 flex justify-center">
341
					<div class=" text-left px-5 py-2">
342
						<div class=" font-medium">{$i18n.t('Chat History is off for this browser.')}</div>
343
						<div class="text-xs mt-2">
Jannik Streidl's avatar
Jannik Streidl committed
344
345
346
347
348
							{$i18n.t(
								"When history is turned off, new chats on this browser won't appear in your history on any of your devices."
							)}
							<span class=" font-semibold"
								>{$i18n.t('This setting does not sync across browsers or devices.')}</span
349
350
351
352
353
							>
						</div>

						<div class="mt-3">
							<button
Timothy J. Baek's avatar
Timothy J. Baek committed
354
								class="flex justify-center items-center space-x-1.5 px-3 py-2.5 rounded-lg text-xs bg-gray-100 hover:bg-gray-200 transition text-gray-800 font-medium w-full"
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
								type="button"
								on:click={() => {
									saveSettings({
										saveChatHistory: true
									});
								}}
							>
								<svg
									xmlns="http://www.w3.org/2000/svg"
									viewBox="0 0 16 16"
									fill="currentColor"
									class="w-3 h-3"
								>
									<path
										fill-rule="evenodd"
										d="M8 1a.75.75 0 0 1 .75.75v6.5a.75.75 0 0 1-1.5 0v-6.5A.75.75 0 0 1 8 1ZM4.11 3.05a.75.75 0 0 1 0 1.06 5.5 5.5 0 1 0 7.78 0 .75.75 0 0 1 1.06-1.06 7 7 0 1 1-9.9 0 .75.75 0 0 1 1.06 0Z"
										clip-rule="evenodd"
									/>
								</svg>

Jannik Streidl's avatar
Jannik Streidl committed
375
								<div>{$i18n.t('Enable Chat History')}</div>
376
377
378
							</button>
						</div>
					</div>
379
				</div>
380
			{/if}
381

Timothy J. Baek's avatar
Timothy J. Baek committed
382
383
384
			<div class="px-2 mt-0.5 mb-2 flex justify-center space-x-2">
				<div class="flex w-full rounded-xl" id="chat-search">
					<div class="self-center pl-3 py-2 rounded-l-xl bg-transparent">
385
386
387
388
389
390
391
392
393
394
395
396
397
						<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="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
								clip-rule="evenodd"
							/>
						</svg>
					</div>
398

399
					<input
Timothy J. Baek's avatar
Timothy J. Baek committed
400
						class="w-full rounded-r-xl py-1.5 pl-2.5 pr-4 text-sm bg-transparent dark:text-gray-300 outline-none"
Jannik Streidl's avatar
Jannik Streidl committed
401
						placeholder={$i18n.t('Search')}
402
						bind:value={search}
Timothy J. Baek's avatar
Timothy J. Baek committed
403
404
405
						on:focus={() => {
							enrichChatsWithContent($chats);
						}}
406
407
408
					/>
				</div>
			</div>
409

Timothy J. Baek's avatar
Timothy J. Baek committed
410
			{#if $tags.length > 0}
Timothy J. Baek's avatar
Timothy J. Baek committed
411
				<div class="px-2.5 mb-2 flex gap-1 flex-wrap">
Timothy J. Baek's avatar
Timothy J. Baek committed
412
					<button
Timothy J. Baek's avatar
Timothy J. Baek committed
413
						class="px-2.5 text-xs font-medium bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
Timothy J. Baek's avatar
Timothy J. Baek committed
414
415
416
417
						on:click={async () => {
							await chats.set(await getChatList(localStorage.token));
						}}
					>
Timothy J. Baek's avatar
Timothy J. Baek committed
418
						{$i18n.t('all')}
Timothy J. Baek's avatar
Timothy J. Baek committed
419
420
421
					</button>
					{#each $tags as tag}
						<button
Timothy J. Baek's avatar
Timothy J. Baek committed
422
							class="px-2.5 text-xs font-medium bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
Timothy J. Baek's avatar
Timothy J. Baek committed
423
							on:click={async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
424
425
426
427
428
429
								let chatIds = await getChatListByTagName(localStorage.token, tag.name);
								if (chatIds.length === 0) {
									await tags.set(await getAllChatTags(localStorage.token));
									chatIds = await getChatList(localStorage.token);
								}
								await chats.set(chatIds);
Timothy J. Baek's avatar
Timothy J. Baek committed
430
431
432
433
434
435
436
437
							}}
						>
							{tag.name}
						</button>
					{/each}
				</div>
			{/if}

Timothy J. Baek's avatar
Timothy J. Baek committed
438
			<div class="pl-2 my-2 flex-1 flex flex-col space-y-1 overflow-y-auto scrollbar-hidden">
439
				{#each filteredChatList as chat, idx}
Timothy J. Baek's avatar
Timothy J. Baek committed
440
					{#if idx === 0 || (idx > 0 && chat.time_range !== filteredChatList[idx - 1].time_range)}
441
442
443
						<div
							class="w-full pl-2.5 text-xs text-gray-500 dark:text-gray-500 font-medium {idx === 0
								? ''
Timothy J. Baek's avatar
Timothy J. Baek committed
444
								: 'pt-5'} pb-0.5"
445
						>
Timothy J. Baek's avatar
Timothy J. Baek committed
446
							{$i18n.t(chat.time_range)}
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
							<!-- localisation keys for time_range to be recognized from the i18next parser (so they don't get automatically removed):
							{$i18n.t('Today')}
							{$i18n.t('Yesterday')}
							{$i18n.t('Previous 7 days')}
							{$i18n.t('Previous 30 days')}
							{$i18n.t('January')}
							{$i18n.t('February')}
							{$i18n.t('March')}
							{$i18n.t('April')}
							{$i18n.t('May')}
							{$i18n.t('June')}
							{$i18n.t('July')}
							{$i18n.t('August')}
							{$i18n.t('September')}
							{$i18n.t('October')}
							{$i18n.t('November')}
							{$i18n.t('December')}
							-->
465
466
467
						</div>
					{/if}

Timothy J. Baek's avatar
Timothy J. Baek committed
468
					<div class=" w-full pr-2 relative group">
Timothy J. Baek's avatar
Timothy J. Baek committed
469
470
						{#if chatTitleEditId === chat.id}
							<div
Timothy J. Baek's avatar
Timothy J. Baek committed
471
472
473
								class=" w-full flex justify-between rounded-xl px-3 py-2 {chat.id === $chatId ||
								chat.id === chatTitleEditId ||
								chat.id === chatDeleteId
Timothy J. Baek's avatar
Timothy J. Baek committed
474
									? 'bg-gray-200 dark:bg-gray-900'
Timothy J. Baek's avatar
Timothy J. Baek committed
475
									: chat.id === selectedChatId
476
477
									? 'bg-gray-100 dark:bg-gray-950'
									: 'group-hover:bg-gray-100 dark:group-hover:bg-gray-950'}  whitespace-nowrap text-ellipsis"
Timothy J. Baek's avatar
Timothy J. Baek committed
478
479
480
481
482
							>
								<input bind:value={chatTitle} class=" bg-transparent w-full outline-none mr-10" />
							</div>
						{:else}
							<a
Timothy J. Baek's avatar
Timothy J. Baek committed
483
484
485
								class=" w-full flex justify-between rounded-xl px-3 py-2 {chat.id === $chatId ||
								chat.id === chatTitleEditId ||
								chat.id === chatDeleteId
Timothy J. Baek's avatar
Timothy J. Baek committed
486
									? 'bg-gray-200 dark:bg-gray-900'
Timothy J. Baek's avatar
Timothy J. Baek committed
487
									: chat.id === selectedChatId
488
489
									? 'bg-gray-100 dark:bg-gray-950'
									: ' group-hover:bg-gray-100 dark:group-hover:bg-gray-950'}  whitespace-nowrap text-ellipsis"
Timothy J. Baek's avatar
Timothy J. Baek committed
490
								href="/c/{chat.id}"
491
								on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
492
									selectedChatId = chat.id;
Timothy J. Baek's avatar
Timothy J. Baek committed
493
									if ($mobile) {
Timothy J. Baek's avatar
Timothy J. Baek committed
494
										showSidebar.set(false);
495
496
									}
								}}
Timothy J. Baek's avatar
Timothy J. Baek committed
497
								draggable="false"
Timothy J. Baek's avatar
Timothy J. Baek committed
498
499
							>
								<div class=" flex self-center flex-1 w-full">
Timothy J. Baek's avatar
Timothy J. Baek committed
500
									<div class=" text-left self-center overflow-hidden w-full h-[20px]">
501
										{chat.title}
Timothy J. Baek's avatar
Timothy J. Baek committed
502
									</div>
503
								</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
504
505
							</a>
						{/if}
506

Timothy J. Baek's avatar
Timothy J. Baek committed
507
						<div
Timothy J. Baek's avatar
Timothy J. Baek committed
508
							class="
509

Timothy J. Baek's avatar
Timothy J. Baek committed
510
							{chat.id === $chatId || chat.id === chatTitleEditId || chat.id === chatDeleteId
Timothy J. Baek's avatar
Timothy J. Baek committed
511
								? 'from-gray-200 dark:from-gray-900'
Timothy J. Baek's avatar
Timothy J. Baek committed
512
								: chat.id === selectedChatId
513
514
								? 'from-gray-100 dark:from-gray-950'
								: 'invisible group-hover:visible from-gray-100 dark:from-gray-950'}
Timothy J. Baek's avatar
Timothy J. Baek committed
515
								absolute right-[10px] top-[10px] pr-2 pl-5 bg-gradient-to-l from-80%
516

Timothy J. Baek's avatar
Timothy J. Baek committed
517
								  to-transparent"
Timothy J. Baek's avatar
Timothy J. Baek committed
518
519
520
521
						>
							{#if chatTitleEditId === chat.id}
								<div class="flex self-center space-x-1.5 z-10">
									<button
522
										class=" self-center dark:hover:text-white transition"
Timothy J. Baek's avatar
Timothy J. Baek committed
523
524
525
526
527
528
529
530
531
532
533
										on:click={() => {
											editChatTitle(chat.id, chatTitle);
											chatTitleEditId = null;
											chatTitle = '';
										}}
									>
										<svg
											xmlns="http://www.w3.org/2000/svg"
											viewBox="0 0 20 20"
											fill="currentColor"
											class="w-4 h-4"
534
										>
Timothy J. Baek's avatar
Timothy J. Baek committed
535
536
537
538
539
540
541
542
											<path
												fill-rule="evenodd"
												d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
												clip-rule="evenodd"
											/>
										</svg>
									</button>
									<button
543
										class=" self-center dark:hover:text-white transition"
Timothy J. Baek's avatar
Timothy J. Baek committed
544
545
546
547
548
549
550
551
552
553
										on:click={() => {
											chatTitleEditId = null;
											chatTitle = '';
										}}
									>
										<svg
											xmlns="http://www.w3.org/2000/svg"
											viewBox="0 0 20 20"
											fill="currentColor"
											class="w-4 h-4"
554
										>
Timothy J. Baek's avatar
Timothy J. Baek committed
555
556
557
558
559
560
561
562
563
											<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>
							{:else if chatDeleteId === chat.id}
								<div class="flex self-center space-x-1.5 z-10">
									<button
564
										class=" self-center dark:hover:text-white transition"
Timothy J. Baek's avatar
Timothy J. Baek committed
565
566
567
568
569
570
571
572
573
										on:click={() => {
											deleteChat(chat.id);
										}}
									>
										<svg
											xmlns="http://www.w3.org/2000/svg"
											viewBox="0 0 20 20"
											fill="currentColor"
											class="w-4 h-4"
574
										>
Timothy J. Baek's avatar
Timothy J. Baek committed
575
576
577
578
579
580
581
582
											<path
												fill-rule="evenodd"
												d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
												clip-rule="evenodd"
											/>
										</svg>
									</button>
									<button
583
										class=" self-center dark:hover:text-white transition"
Timothy J. Baek's avatar
Timothy J. Baek committed
584
585
586
587
588
589
590
591
592
										on:click={() => {
											chatDeleteId = null;
										}}
									>
										<svg
											xmlns="http://www.w3.org/2000/svg"
											viewBox="0 0 20 20"
											fill="currentColor"
											class="w-4 h-4"
593
										>
Timothy J. Baek's avatar
Timothy J. Baek committed
594
595
596
597
598
599
600
											<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>
							{:else}
Timothy J. Baek's avatar
Timothy J. Baek committed
601
								<div class="flex self-center space-x-1 z-10">
Timothy J. Baek's avatar
Timothy J. Baek committed
602
									<ChatMenu
Timothy J. Baek's avatar
Timothy J. Baek committed
603
										chatId={chat.id}
Timothy J. Baek's avatar
Timothy J. Baek committed
604
605
606
607
										shareHandler={() => {
											shareChatId = selectedChatId;
											showShareChatModal = true;
										}}
Timothy J. Baek's avatar
Timothy J. Baek committed
608
609
610
										archiveChatHandler={() => {
											archiveChatHandler(chat.id);
										}}
Timothy J. Baek's avatar
Timothy J. Baek committed
611
										renameHandler={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
612
613
614
											chatTitle = chat.title;
											chatTitleEditId = chat.id;
										}}
Timothy J. Baek's avatar
Timothy J. Baek committed
615
										deleteHandler={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
616
617
											chatDeleteId = chat.id;
										}}
Timothy J. Baek's avatar
Timothy J. Baek committed
618
619
620
										onClose={() => {
											selectedChatId = null;
										}}
Timothy J. Baek's avatar
Timothy J. Baek committed
621
									>
622
										<button
Timothy J. Baek's avatar
Timothy J. Baek committed
623
											aria-label="Chat Menu"
624
											class=" self-center dark:hover:text-white transition"
625
											on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
626
												selectedChatId = chat.id;
627
											}}
628
										>
629
630
											<svg
												xmlns="http://www.w3.org/2000/svg"
Timothy J. Baek's avatar
Timothy J. Baek committed
631
632
												viewBox="0 0 16 16"
												fill="currentColor"
633
634
635
												class="w-4 h-4"
											>
												<path
Timothy J. Baek's avatar
Timothy J. Baek committed
636
													d="M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
637
638
639
												/>
											</svg>
										</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
640
									</ChatMenu>
Timothy J. Baek's avatar
Timothy J. Baek committed
641

Timothy J. Baek's avatar
Timothy J. Baek committed
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
									{#if chat.id === $chatId}
										<button
											id="delete-chat-button"
											class="hidden"
											on:click={() => {
												chatDeleteId = chat.id;
											}}
										>
											<svg
												xmlns="http://www.w3.org/2000/svg"
												viewBox="0 0 16 16"
												fill="currentColor"
												class="w-4 h-4"
											>
												<path
													d="M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
												/>
											</svg>
										</button>
									{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
662
663
664
								</div>
							{/if}
						</div>
665
666
667
					</div>
				{/each}
			</div>
668
669
		</div>

670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
		<div class="px-2.5">
			<!-- <hr class=" border-gray-900 mb-1 w-full" /> -->

			<div class="flex flex-col">
				{#if $user !== undefined}
					<UserMenu
						role={$user.role}
						on:show={(e) => {
							if (e.detail === 'archived-chat') {
								showArchivedChats.set(true);
							}
						}}
					>
						<button
							class=" flex rounded-xl py-3 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
							on:click={() => {
								showDropdown = !showDropdown;
Timothy J. Baek's avatar
Timothy J. Baek committed
687
							}}
688
						>
689
690
691
692
693
694
695
696
697
698
699
							<div class=" self-center mr-3">
								<img
									src={$user.profile_image_url}
									class=" max-w-[30px] object-cover rounded-full"
									alt="User profile"
								/>
							</div>
							<div class=" self-center font-semibold">{$user.name}</div>
						</button>
					</UserMenu>
				{/if}
700
			</div>
701
		</div>
702
703
	</div>

704
	<!-- <div
Timothy J. Baek's avatar
Timothy J. Baek committed
705
		id="sidebar-handle"
Timothy J. Baek's avatar
Timothy J. Baek committed
706
		class=" hidden md:fixed left-0 top-[50dvh] -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
707
	>
Jannik Streidl's avatar
Jannik Streidl committed
708
709
		<Tooltip
			placement="right"
Timothy J. Baek's avatar
Timothy J. Baek committed
710
			content={`${$showSidebar ? $i18n.t('Close') : $i18n.t('Open')} ${$i18n.t('sidebar')}`}
Jannik Streidl's avatar
Jannik Streidl committed
711
712
			touch={false}
		>
713
714
715
716
			<button
				id="sidebar-toggle-button"
				class=" group"
				on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
717
					showSidebar.set(!$showSidebar);
718
719
720
				}}
				><span class="" data-state="closed"
					><div
721
						class="flex h-[72px] w-8 items-center justify-center opacity-50 group-hover:opacity-100 transition"
722
723
724
					>
						<div class="flex h-6 w-6 flex-col items-center">
							<div
Timothy J. Baek's avatar
Timothy J. Baek committed
725
								class="h-3 w-1 rounded-full bg-[#0f0f0f] dark:bg-white rotate-0 translate-y-[0.15rem] {$showSidebar
726
727
728
729
									? 'group-hover:rotate-[15deg]'
									: 'group-hover:rotate-[-15deg]'}"
							/>
							<div
Timothy J. Baek's avatar
Timothy J. Baek committed
730
								class="h-3 w-1 rounded-full bg-[#0f0f0f] dark:bg-white rotate-0 translate-y-[-0.15rem] {$showSidebar
731
732
733
734
									? 'group-hover:rotate-[-15deg]'
									: 'group-hover:rotate-[15deg]'}"
							/>
						</div>
735
					</div>
736
737
738
				</span>
			</button>
		</Tooltip>
739
	</div> -->
740
</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
741
742

<style>
Timothy J. Baek's avatar
Timothy J. Baek committed
743
744
745
	.scrollbar-hidden:active::-webkit-scrollbar-thumb,
	.scrollbar-hidden:focus::-webkit-scrollbar-thumb,
	.scrollbar-hidden:hover::-webkit-scrollbar-thumb {
Timothy J. Baek's avatar
Timothy J. Baek committed
746
		visibility: visible;
Timothy J. Baek's avatar
Timothy J. Baek committed
747
	}
Timothy J. Baek's avatar
Timothy J. Baek committed
748
	.scrollbar-hidden::-webkit-scrollbar-thumb {
Timothy J. Baek's avatar
Timothy J. Baek committed
749
		visibility: hidden;
Timothy J. Baek's avatar
Timothy J. Baek committed
750
751
	}
</style>