Sidebar.svelte 23.6 KB
Newer Older
1
<script lang="ts">
Timothy J. Baek's avatar
Timothy J. Baek committed
2
3
	import { goto } from '$app/navigation';
	import { user, chats, settings, showSettings, chatId, tags, showSidebar } from '$lib/stores';
4
	import { onMount, getContext } from 'svelte';
5
6
7

	const i18n = getContext('i18n');

Timothy J. Baek's avatar
Timothy J. Baek committed
8
9
10
	import {
		deleteChatById,
		getChatList,
Henry Holloway's avatar
Henry Holloway committed
11
		getChatById,
Timothy J. Baek's avatar
Timothy J. Baek committed
12
		getChatListByTagName,
Timothy J. Baek's avatar
Timothy J. Baek committed
13
		updateChatById,
Timothy J. Baek's avatar
Timothy J. Baek committed
14
15
		getAllChatTags,
		archiveChatById
Timothy J. Baek's avatar
Timothy J. Baek committed
16
	} from '$lib/apis/chats';
Jannik Streidl's avatar
Jannik Streidl committed
17
	import { toast } from 'svelte-sonner';
Timothy J. Baek's avatar
Timothy J. Baek committed
18
	import { fade, slide } from 'svelte/transition';
19
	import { WEBUI_BASE_URL } from '$lib/constants';
20
	import Tooltip from '../common/Tooltip.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
21
	import ChatMenu from './Sidebar/ChatMenu.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
22
	import ShareChatModal from '../chat/ShareChatModal.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
23
	import ArchiveBox from '../icons/ArchiveBox.svelte';
24
	import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
25
	import UserMenu from './Sidebar/UserMenu.svelte';
26

27
	const BREAKPOINT = 1024;
Timothy J. Baek's avatar
Timothy J. Baek committed
28

29
30
31
	let show = false;
	let navElement;

Timothy J. Baek's avatar
Timothy J. Baek committed
32
	let title: string = 'UI';
33
	let search = '';
34

Timothy J. Baek's avatar
Timothy J. Baek committed
35
36
	let shareChatId = null;

Timothy J. Baek's avatar
Timothy J. Baek committed
37
38
	let selectedChatId = null;

39
40
	let chatDeleteId = null;
	let chatTitleEditId = null;
41
42
	let chatTitle = '';

43
	let showArchivedChatsModal = false;
Timothy J. Baek's avatar
Timothy J. Baek committed
44
	let showShareChatModal = false;
45
	let showDropdown = false;
46
	let isEditing = false;
47

48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
	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;
		}
	});

70
	onMount(async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
71
		showSidebar.set(window.innerWidth > BREAKPOINT);
Timothy J. Baek's avatar
Timothy J. Baek committed
72
		await chats.set(await getChatList(localStorage.token));
Timothy J. Baek's avatar
Timothy J. Baek committed
73

74
75
		let touchstart;
		let touchend;
Timothy J. Baek's avatar
Timothy J. Baek committed
76
77
78

		function checkDirection() {
			const screenWidth = window.innerWidth;
79
			const swipeDistance = Math.abs(touchend.screenX - touchstart.screenX);
Timothy J. Baek's avatar
Timothy J. Baek committed
80
			if (touchstart.clientX < 40 && swipeDistance >= screenWidth / 8) {
81
				if (touchend.screenX < touchstart.screenX) {
Timothy J. Baek's avatar
Timothy J. Baek committed
82
					showSidebar.set(false);
Timothy J. Baek's avatar
Timothy J. Baek committed
83
				}
84
				if (touchend.screenX > touchstart.screenX) {
Timothy J. Baek's avatar
Timothy J. Baek committed
85
					showSidebar.set(true);
Timothy J. Baek's avatar
Timothy J. Baek committed
86
87
88
89
				}
			}
		}

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
90
		const onTouchStart = (e) => {
91
92
			touchstart = e.changedTouches[0];
			console.log(touchstart.clientX);
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
93
		};
Timothy J. Baek's avatar
Timothy J. Baek committed
94

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
95
		const onTouchEnd = (e) => {
96
			touchend = e.changedTouches[0];
Timothy J. Baek's avatar
Timothy J. Baek committed
97
			checkDirection();
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
98
99
		};

100
		const onResize = () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
101
102
			if ($showSidebar && window.innerWidth < BREAKPOINT) {
				showSidebar.set(false);
103
			}
Timothy J. Baek's avatar
Timothy J. Baek committed
104
		};
105

106
107
		window.addEventListener('touchstart', onTouchStart);
		window.addEventListener('touchend', onTouchEnd);
108
		window.addEventListener('resize', onResize);
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
109
110

		return () => {
111
112
113
			window.removeEventListener('touchstart', onTouchStart);
			window.removeEventListener('touchend', onTouchEnd);
			window.removeEventListener('resize', onResize);
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
114
		};
Timothy J. Baek's avatar
Timothy J. Baek committed
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
	});

	// 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);
	};
131
132
133
134
135
136

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

	const editChatTitle = async (id, _title) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
137
		if (_title === '') {
Jannik Streidl's avatar
Jannik Streidl committed
138
			toast.error($i18n.t('Title cannot be an empty string.'));
Timothy J. Baek's avatar
Timothy J. Baek committed
139
140
141
142
143
144
145
146
		} else {
			title = _title;

			await updateChatById(localStorage.token, id, {
				title: _title
			});
			await chats.set(await getChatList(localStorage.token));
		}
147
148
149
	};

	const deleteChat = async (id) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
150
151
152
		const res = await deleteChatById(localStorage.token, id).catch((error) => {
			toast.error(error);
			chatDeleteId = null;
153

Timothy J. Baek's avatar
Timothy J. Baek committed
154
155
156
157
			return null;
		});

		if (res) {
Timothy J. Baek's avatar
Timothy J. Baek committed
158
159
160
161
			if ($chatId === id) {
				goto('/');
			}

Timothy J. Baek's avatar
Timothy J. Baek committed
162
163
			await chats.set(await getChatList(localStorage.token));
		}
164
	};
165
166
167
168
169
170

	const saveSettings = async (updated) => {
		await settings.set({ ...$settings, ...updated });
		localStorage.setItem('settings', JSON.stringify($settings));
		location.href = '/';
	};
Timothy J. Baek's avatar
Timothy J. Baek committed
171
172
173
174
175

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

Timothy J. Baek's avatar
Timothy J. Baek committed
178
<ShareChatModal bind:show={showShareChatModal} chatId={shareChatId} />
Timothy J. Baek's avatar
Timothy J. Baek committed
179
180
181
182
183
184
<ArchivedChatsModal
	bind:show={showArchivedChatsModal}
	on:change={async () => {
		await chats.set(await getChatList(localStorage.token));
	}}
/>
Timothy J. Baek's avatar
Timothy J. Baek committed
185

Timothy J. Baek's avatar
Timothy J. Baek committed
186
187
188
189
190
191
192
193
194
195
196
<!-- svelte-ignore a11y-no-static-element-interactions -->

{#if $showSidebar}
	<div
		class=" fixed md:hidden z-10 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"
		on:mousedown={() => {
			showSidebar.set(!$showSidebar);
		}}
	/>
{/if}

197
198
<div
	bind:this={navElement}
Timothy J. Baek's avatar
Timothy J. Baek committed
199
200
	id="sidebar"
	class="h-screen max-h-[100dvh] min-h-screen {$showSidebar
Timothy J. Baek's avatar
Timothy J. Baek committed
201
		? 'lg:relative w-[260px]'
Timothy J. Baek's avatar
Timothy J. Baek committed
202
		: '-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
203
        "
Timothy J. Baek's avatar
Timothy J. Baek committed
204
	data-state={$showSidebar}
205
>
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
206
	<div
Timothy J. Baek's avatar
Timothy J. Baek committed
207
		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
208
209
210
			? ''
			: 'invisible'}"
	>
Timothy J. Baek's avatar
Timothy J. Baek committed
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
		<div class="px-2 flex justify-between space-x-2">
			<button
				id="new-chat-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>
236
			<a
Timothy J. Baek's avatar
Timothy J. Baek committed
237
				id="sidebar-new-chat-button"
Timothy J. Baek's avatar
Timothy J. Baek committed
238
				class="flex justify-between rounded-xl px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
239
				href="/"
Timothy J. Baek's avatar
Timothy J. Baek committed
240
				on:click={async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
241
242
					selectedChatId = null;

243
					await goto('/');
244
					const newChatButton = document.getElementById('new-chat-button');
245
246
247
					setTimeout(() => {
						newChatButton?.click();
					}, 0);
248
249
250
251
252
253
254
				}}
			>
				<div class="self-center">
					<svg
						xmlns="http://www.w3.org/2000/svg"
						viewBox="0 0 20 20"
						fill="currentColor"
Timothy J. Baek's avatar
Timothy J. Baek committed
255
						class="size-5"
256
257
258
259
260
261
262
263
264
					>
						<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>
265
			</a>
266
267
		</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
268
		{#if $user?.role === 'admin'}
Timothy J. Baek's avatar
Timothy J. Baek committed
269
			<div class="px-2 flex justify-center mt-0.5">
270
				<a
Timothy J. Baek's avatar
Timothy J. Baek committed
271
					class="flex-grow flex space-x-3 rounded-xl px-3.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
272
					href="/modelfiles"
Timothy J. Baek's avatar
Timothy J. Baek committed
273
274
275
276
					on:click={() => {
						selectedChatId = null;
						chatId.set('');
					}}
Timothy J. Baek's avatar
Timothy J. Baek committed
277
278
279
280
281
282
				>
					<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
283
							stroke-width="2"
Timothy J. Baek's avatar
Timothy J. Baek committed
284
285
286
287
288
289
							stroke="currentColor"
							class="w-4 h-4"
						>
							<path
								stroke-linecap="round"
								stroke-linejoin="round"
Timothy J. Baek's avatar
Timothy J. Baek committed
290
								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
291
292
293
							/>
						</svg>
					</div>
294

Timothy J. Baek's avatar
Timothy J. Baek committed
295
					<div class="flex self-center">
296
						<div class=" self-center font-medium text-sm">{$i18n.t('Modelfiles')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
297
					</div>
298
				</a>
Timothy J. Baek's avatar
Timothy J. Baek committed
299
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
300

Timothy J. Baek's avatar
Timothy J. Baek committed
301
			<div class="px-2 flex justify-center">
302
				<a
Timothy J. Baek's avatar
Timothy J. Baek committed
303
					class="flex-grow flex space-x-3 rounded-xl px-3.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
304
					href="/prompts"
Timothy J. Baek's avatar
Timothy J. Baek committed
305
306
307
308
					on:click={() => {
						selectedChatId = null;
						chatId.set('');
					}}
Timothy J. Baek's avatar
Timothy J. Baek committed
309
310
311
312
				>
					<div class="self-center">
						<svg
							xmlns="http://www.w3.org/2000/svg"
Timothy J. Baek's avatar
Timothy J. Baek committed
313
314
							fill="none"
							viewBox="0 0 24 24"
Timothy J. Baek's avatar
Timothy J. Baek committed
315
							stroke-width="2"
Timothy J. Baek's avatar
Timothy J. Baek committed
316
							stroke="currentColor"
Timothy J. Baek's avatar
Timothy J. Baek committed
317
318
319
							class="w-4 h-4"
						>
							<path
Timothy J. Baek's avatar
Timothy J. Baek committed
320
321
322
								stroke-linecap="round"
								stroke-linejoin="round"
								d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
Timothy J. Baek's avatar
Timothy J. Baek committed
323
324
325
326
327
							/>
						</svg>
					</div>

					<div class="flex self-center">
328
						<div class=" self-center font-medium text-sm">{$i18n.t('Prompts')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
329
					</div>
330
				</a>
Timothy J. Baek's avatar
Timothy J. Baek committed
331
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
332

Timothy J. Baek's avatar
Timothy J. Baek committed
333
			<div class="px-2 flex justify-center mb-1">
334
				<a
Timothy J. Baek's avatar
Timothy J. Baek committed
335
					class="flex-grow flex space-x-3 rounded-xl px-3.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
336
					href="/documents"
Timothy J. Baek's avatar
Timothy J. Baek committed
337
338
339
340
					on:click={() => {
						selectedChatId = null;
						chatId.set('');
					}}
Timothy J. Baek's avatar
Timothy J. Baek committed
341
342
343
344
345
346
				>
					<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
347
							stroke-width="2"
Timothy J. Baek's avatar
Timothy J. Baek committed
348
349
350
351
352
353
354
355
356
357
358
359
							stroke="currentColor"
							class="w-4 h-4"
						>
							<path
								stroke-linecap="round"
								stroke-linejoin="round"
								d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
							/>
						</svg>
					</div>

					<div class="flex self-center">
360
						<div class=" self-center font-medium text-sm">{$i18n.t('Documents')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
361
					</div>
362
				</a>
Timothy J. Baek's avatar
Timothy J. Baek committed
363
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
364
		{/if}
365

366
367
		<div class="relative flex flex-col flex-1 overflow-y-auto">
			{#if !($settings.saveChatHistory ?? true)}
Timothy J. Baek's avatar
Timothy J. Baek committed
368
				<div class="absolute z-40 w-full h-full bg-gray-50/90 dark:bg-black/90 flex justify-center">
369
					<div class=" text-left px-5 py-2">
370
						<div class=" font-medium">{$i18n.t('Chat History is off for this browser.')}</div>
371
						<div class="text-xs mt-2">
Jannik Streidl's avatar
Jannik Streidl committed
372
373
374
375
376
							{$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
377
378
379
380
381
							>
						</div>

						<div class="mt-3">
							<button
Timothy J. Baek's avatar
Timothy J. Baek committed
382
								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"
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
								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
403
								<div>{$i18n.t('Enable Chat History')}</div>
404
405
406
							</button>
						</div>
					</div>
407
				</div>
408
			{/if}
409

Timothy J. Baek's avatar
Timothy J. Baek committed
410
			<div class="px-2 mt-1 mb-2 flex justify-center space-x-2">
411
				<div class="flex w-full" id="chat-search">
412
					<div class="self-center pl-3 py-2 rounded-l-xl bg-white dark:bg-gray-950">
413
414
415
416
417
418
419
420
421
422
423
424
425
						<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>
426

427
					<input
428
						class="w-full rounded-r-xl py-1.5 pl-2.5 pr-4 text-sm dark:text-gray-300 dark:bg-gray-950 outline-none"
Jannik Streidl's avatar
Jannik Streidl committed
429
						placeholder={$i18n.t('Search')}
430
						bind:value={search}
Timothy J. Baek's avatar
Timothy J. Baek committed
431
432
433
						on:focus={() => {
							enrichChatsWithContent($chats);
						}}
434
435
436
					/>
				</div>
			</div>
437

Timothy J. Baek's avatar
Timothy J. Baek committed
438
439
440
			{#if $tags.length > 0}
				<div class="px-2.5 mt-0.5 mb-2 flex gap-1 flex-wrap">
					<button
441
						class="px-2.5 text-xs font-medium bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
Timothy J. Baek's avatar
Timothy J. Baek committed
442
443
444
445
						on:click={async () => {
							await chats.set(await getChatList(localStorage.token));
						}}
					>
Timothy J. Baek's avatar
Timothy J. Baek committed
446
						{$i18n.t('all')}
Timothy J. Baek's avatar
Timothy J. Baek committed
447
448
449
					</button>
					{#each $tags as tag}
						<button
450
							class="px-2.5 text-xs font-medium bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
Timothy J. Baek's avatar
Timothy J. Baek committed
451
							on:click={async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
452
453
454
455
456
457
								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
458
459
460
461
462
463
464
465
							}}
						>
							{tag.name}
						</button>
					{/each}
				</div>
			{/if}

Timothy J. Baek's avatar
Timothy J. Baek committed
466
			<div class="pl-2 my-2 flex-1 flex flex-col space-y-1 overflow-y-auto scrollbar-none">
467
				{#each filteredChatList as chat, idx}
Timothy J. Baek's avatar
Timothy J. Baek committed
468
					{#if idx === 0 || (idx > 0 && chat.time_range !== filteredChatList[idx - 1].time_range)}
469
470
471
						<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
472
								: 'pt-5'} pb-0.5"
473
						>
Timothy J. Baek's avatar
Timothy J. Baek committed
474
							{$i18n.t(chat.time_range)}
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
							<!-- 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')}
							-->
493
494
495
						</div>
					{/if}

Timothy J. Baek's avatar
Timothy J. Baek committed
496
					<div class=" w-full pr-2 relative group">
Timothy J. Baek's avatar
Timothy J. Baek committed
497
498
						{#if chatTitleEditId === chat.id}
							<div
Timothy J. Baek's avatar
Timothy J. Baek committed
499
500
501
								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
502
									? 'bg-gray-200 dark:bg-gray-900'
Timothy J. Baek's avatar
Timothy J. Baek committed
503
									: chat.id === selectedChatId
504
505
									? '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
506
507
508
509
510
							>
								<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
511
512
513
								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
514
									? 'bg-gray-200 dark:bg-gray-900'
Timothy J. Baek's avatar
Timothy J. Baek committed
515
									: chat.id === selectedChatId
516
517
									? '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
518
								href="/c/{chat.id}"
519
								on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
520
									selectedChatId = chat.id;
521
									if (window.innerWidth < 1024) {
Timothy J. Baek's avatar
Timothy J. Baek committed
522
										showSidebar.set(false);
523
524
									}
								}}
Timothy J. Baek's avatar
Timothy J. Baek committed
525
								draggable="false"
Timothy J. Baek's avatar
Timothy J. Baek committed
526
527
							>
								<div class=" flex self-center flex-1 w-full">
Timothy J. Baek's avatar
Timothy J. Baek committed
528
									<div class=" text-left self-center overflow-hidden w-full h-[20px]">
529
										{chat.title}
Timothy J. Baek's avatar
Timothy J. Baek committed
530
									</div>
531
								</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
532
533
							</a>
						{/if}
534

Timothy J. Baek's avatar
Timothy J. Baek committed
535
						<div
Timothy J. Baek's avatar
Timothy J. Baek committed
536
							class="
537

Timothy J. Baek's avatar
Timothy J. Baek committed
538
							{chat.id === $chatId || chat.id === chatTitleEditId || chat.id === chatDeleteId
Timothy J. Baek's avatar
Timothy J. Baek committed
539
								? 'from-gray-200 dark:from-gray-900'
Timothy J. Baek's avatar
Timothy J. Baek committed
540
								: chat.id === selectedChatId
541
542
								? '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
543
								absolute right-[10px] top-[10px] pr-2 pl-5 bg-gradient-to-l from-80%
544

Timothy J. Baek's avatar
Timothy J. Baek committed
545
								  to-transparent"
Timothy J. Baek's avatar
Timothy J. Baek committed
546
547
548
549
						>
							{#if chatTitleEditId === chat.id}
								<div class="flex self-center space-x-1.5 z-10">
									<button
550
										class=" self-center dark:hover:text-white transition"
Timothy J. Baek's avatar
Timothy J. Baek committed
551
552
553
554
555
556
557
558
559
560
561
										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"
562
										>
Timothy J. Baek's avatar
Timothy J. Baek committed
563
564
565
566
567
568
569
570
											<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
571
										class=" self-center dark:hover:text-white transition"
Timothy J. Baek's avatar
Timothy J. Baek committed
572
573
574
575
576
577
578
579
580
581
										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"
582
										>
Timothy J. Baek's avatar
Timothy J. Baek committed
583
584
585
586
587
588
589
590
591
											<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
592
										class=" self-center dark:hover:text-white transition"
Timothy J. Baek's avatar
Timothy J. Baek committed
593
594
595
596
597
598
599
600
601
										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"
602
										>
Timothy J. Baek's avatar
Timothy J. Baek committed
603
604
605
606
607
608
609
610
											<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
611
										class=" self-center dark:hover:text-white transition"
Timothy J. Baek's avatar
Timothy J. Baek committed
612
613
614
615
616
617
618
619
620
										on:click={() => {
											chatDeleteId = null;
										}}
									>
										<svg
											xmlns="http://www.w3.org/2000/svg"
											viewBox="0 0 20 20"
											fill="currentColor"
											class="w-4 h-4"
621
										>
Timothy J. Baek's avatar
Timothy J. Baek committed
622
623
624
625
626
627
628
											<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
629
								<div class="flex self-center space-x-1 z-10">
Timothy J. Baek's avatar
Timothy J. Baek committed
630
									<ChatMenu
Timothy J. Baek's avatar
Timothy J. Baek committed
631
										chatId={chat.id}
Timothy J. Baek's avatar
Timothy J. Baek committed
632
633
634
635
										shareHandler={() => {
											shareChatId = selectedChatId;
											showShareChatModal = true;
										}}
Timothy J. Baek's avatar
Timothy J. Baek committed
636
										renameHandler={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
637
638
639
											chatTitle = chat.title;
											chatTitleEditId = chat.id;
										}}
Timothy J. Baek's avatar
Timothy J. Baek committed
640
										deleteHandler={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
641
642
											chatDeleteId = chat.id;
										}}
Timothy J. Baek's avatar
Timothy J. Baek committed
643
644
645
										onClose={() => {
											selectedChatId = null;
										}}
Timothy J. Baek's avatar
Timothy J. Baek committed
646
									>
647
										<button
Timothy J. Baek's avatar
Timothy J. Baek committed
648
											aria-label="Chat Menu"
649
											class=" self-center dark:hover:text-white transition"
650
											on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
651
												selectedChatId = chat.id;
652
											}}
653
										>
654
655
											<svg
												xmlns="http://www.w3.org/2000/svg"
Timothy J. Baek's avatar
Timothy J. Baek committed
656
657
												viewBox="0 0 16 16"
												fill="currentColor"
658
659
660
												class="w-4 h-4"
											>
												<path
Timothy J. Baek's avatar
Timothy J. Baek committed
661
													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"
662
663
664
												/>
											</svg>
										</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
665
									</ChatMenu>
Timothy J. Baek's avatar
Timothy J. Baek committed
666

667
									<Tooltip content={$i18n.t('Archive')}>
Timothy J. Baek's avatar
Timothy J. Baek committed
668
669
670
671
										<button
											aria-label="Archive"
											class=" self-center dark:hover:text-white transition"
											on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
672
												archiveChatHandler(chat.id);
Timothy J. Baek's avatar
Timothy J. Baek committed
673
674
675
676
677
											}}
										>
											<ArchiveBox />
										</button>
									</Tooltip>
Timothy J. Baek's avatar
Timothy J. Baek committed
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698

									{#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
699
700
701
								</div>
							{/if}
						</div>
702
703
704
					</div>
				{/each}
			</div>
705
706
707
		</div>

		<div class="px-2.5">
Timothy J. Baek's avatar
Timothy J. Baek committed
708
			<!-- <hr class=" border-gray-900 mb-1 w-full" /> -->
709
710
711

			<div class="flex flex-col">
				{#if $user !== undefined}
Timothy J. Baek's avatar
Timothy J. Baek committed
712
713
714
715
716
717
					<UserMenu
						role={$user.role}
						on:show={(e) => {
							if (e.detail === 'archived-chat') {
								showArchivedChatsModal = true;
							}
718
719
						}}
					>
Timothy J. Baek's avatar
Timothy J. Baek committed
720
721
722
723
724
						<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;
							}}
725
						>
Timothy J. Baek's avatar
Timothy J. Baek committed
726
727
728
729
730
731
							<div class=" self-center mr-3">
								<img
									src={$user.profile_image_url}
									class=" max-w-[30px] object-cover rounded-full"
									alt="User profile"
								/>
732
							</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
733
734
735
							<div class=" self-center font-semibold">{$user.name}</div>
						</button>
					</UserMenu>
736
737
738
739
740
741
				{/if}
			</div>
		</div>
	</div>

	<div
Timothy J. Baek's avatar
Timothy J. Baek committed
742
		id="sidebar-handle"
Timothy J. Baek's avatar
Timothy J. Baek committed
743
		class=" hidden md:fixed left-0 top-[50dvh] -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
744
	>
Jannik Streidl's avatar
Jannik Streidl committed
745
746
		<Tooltip
			placement="right"
Timothy J. Baek's avatar
Timothy J. Baek committed
747
			content={`${$showSidebar ? $i18n.t('Close') : $i18n.t('Open')} ${$i18n.t('sidebar')}`}
Jannik Streidl's avatar
Jannik Streidl committed
748
749
			touch={false}
		>
750
751
752
753
			<button
				id="sidebar-toggle-button"
				class=" group"
				on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
754
					showSidebar.set(!$showSidebar);
755
756
757
				}}
				><span class="" data-state="closed"
					><div
758
						class="flex h-[72px] w-8 items-center justify-center opacity-50 group-hover:opacity-100 transition"
759
760
761
					>
						<div class="flex h-6 w-6 flex-col items-center">
							<div
Timothy J. Baek's avatar
Timothy J. Baek committed
762
								class="h-3 w-1 rounded-full bg-[#0f0f0f] dark:bg-white rotate-0 translate-y-[0.15rem] {$showSidebar
763
764
765
766
									? 'group-hover:rotate-[15deg]'
									: 'group-hover:rotate-[-15deg]'}"
							/>
							<div
Timothy J. Baek's avatar
Timothy J. Baek committed
767
								class="h-3 w-1 rounded-full bg-[#0f0f0f] dark:bg-white rotate-0 translate-y-[-0.15rem] {$showSidebar
768
769
770
771
									? 'group-hover:rotate-[-15deg]'
									: 'group-hover:rotate-[15deg]'}"
							/>
						</div>
772
					</div>
773
774
775
				</span>
			</button>
		</Tooltip>
776
777
	</div>
</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
778
779

<style>
Timothy J. Baek's avatar
Timothy J. Baek committed
780
781
782
783
	.scrollbar-none:active::-webkit-scrollbar-thumb,
	.scrollbar-none:focus::-webkit-scrollbar-thumb,
	.scrollbar-none:hover::-webkit-scrollbar-thumb {
		visibility: visible;
Timothy J. Baek's avatar
Timothy J. Baek committed
784
	}
Timothy J. Baek's avatar
Timothy J. Baek committed
785
786
	.scrollbar-none::-webkit-scrollbar-thumb {
		visibility: hidden;
Timothy J. Baek's avatar
Timothy J. Baek committed
787
788
	}
</style>