Pārlūkot izejas kodu

enh: archived chats modal

Timothy Jaeryang Baek 4 mēneši atpakaļ
vecāks
revīzija
7e6f1f8848

+ 21 - 8
backend/open_webui/models/chats.py

@@ -377,16 +377,29 @@ class ChatTable:
             return False
 
     def get_archived_chat_list_by_user_id(
-        self, user_id: str, skip: int = 0, limit: int = 50
+        self,
+        user_id: str,
+        filter: Optional[dict] = None,
+        skip: int = 0,
+        limit: int = 50,
     ) -> list[ChatModel]:
+
         with get_db() as db:
-            all_chats = (
-                db.query(Chat)
-                .filter_by(user_id=user_id, archived=True)
-                .order_by(Chat.updated_at.desc())
-                # .limit(limit).offset(skip)
-                .all()
-            )
+            query = db.query(Chat).filter_by(user_id=user_id, archived=True)
+
+            if filter:
+                query_key = filter.get("query")
+                if query_key:
+                    query = query.filter(Chat.title.ilike(f"%{query_key}%"))
+
+            query = query.order_by(Chat.updated_at.desc())
+
+            if skip:
+                query = query.offset(skip)
+            if limit:
+                query = query.limit(limit)
+
+            all_chats = query.all()
             return [ChatModel.model_validate(chat) for chat in all_chats]
 
     def get_chat_list_by_user_id(

+ 22 - 2
backend/open_webui/routers/chats.py

@@ -267,9 +267,29 @@ async def get_all_user_chats_in_db(user=Depends(get_admin_user)):
 
 @router.get("/archived", response_model=list[ChatTitleIdResponse])
 async def get_archived_session_user_chat_list(
-    user=Depends(get_verified_user), skip: int = 0, limit: int = 50
+    page: Optional[int] = None,
+    query: Optional[str] = None,
+    user=Depends(get_verified_user),
 ):
-    return Chats.get_archived_chat_list_by_user_id(user.id, skip, limit)
+    if page is None:
+        page = 1
+
+    limit = 60
+    skip = (page - 1) * limit
+
+    chat_list = [
+        ChatTitleIdResponse(**chat.model_dump())
+        for chat in Chats.get_archived_chat_list_by_user_id(
+            user.id,
+            {
+                "query": query if query else None,
+            },
+            skip=skip,
+            limit=limit,
+        )
+    ]
+
+    return chat_list
 
 
 ############################

+ 8 - 2
src/lib/apis/chats/index.ts

@@ -145,10 +145,16 @@ export const getChatListByUserId = async (token: string = '', userId: string) =>
 	}));
 };
 
-export const getArchivedChatList = async (token: string = '') => {
+export const getArchivedChatList = async (token: string = '', page: number = 1, query?: string) => {
 	let error = null;
 
-	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/archived`, {
+	const searchParams = new URLSearchParams();
+	searchParams.append('page', `${page}`);
+	if (query) {
+		searchParams.append('query', query);
+	}
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/archived?${searchParams.toString()}`, {
 		method: 'GET',
 		headers: {
 			Accept: 'application/json',

+ 2 - 2
src/lib/components/chat/Settings/Chats.svelte

@@ -16,7 +16,7 @@
 	import { onMount, getContext } from 'svelte';
 	import { goto } from '$app/navigation';
 	import { toast } from 'svelte-sonner';
-	import ArchivedChatsModal from '$lib/components/layout/Sidebar/ArchivedChatsModal.svelte';
+	import ArchivedChatsModal from '$lib/components/layout/ArchivedChatsModal.svelte';
 
 	const i18n = getContext('i18n');
 
@@ -105,7 +105,7 @@
 	};
 </script>
 
-<ArchivedChatsModal bind:show={showArchivedChatsModal} on:change={handleArchivedChatsChange} />
+<ArchivedChatsModal bind:show={showArchivedChatsModal} onUpdate={handleArchivedChatsChange} />
 
 <div class="flex flex-col h-full justify-between space-y-3 text-sm">
 	<div class=" space-y-2 overflow-y-scroll max-h-[28rem] lg:max-h-full">

+ 159 - 0
src/lib/components/layout/ArchivedChatsModal.svelte

@@ -0,0 +1,159 @@
+<script>
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { toast } from 'svelte-sonner';
+	import { getContext } from 'svelte';
+	import { archiveChatById, getAllArchivedChats, getArchivedChatList } from '$lib/apis/chats';
+
+	import ChatsModal from './ChatsModal.svelte';
+	import UnarchiveAllConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let show = false;
+	export let onUpdate = () => {};
+
+	let chatList = null;
+	let page = 1;
+
+	let query = '';
+
+	let allChatsLoaded = false;
+	let chatListLoading = false;
+	let searchDebounceTimeout;
+
+	let showUnarchiveAllConfirmDialog = false;
+
+	$: if (query !== null) {
+		searchHandler();
+	}
+
+	const searchHandler = async () => {
+		console.log('search', query);
+
+		if (searchDebounceTimeout) {
+			clearTimeout(searchDebounceTimeout);
+		}
+
+		page = 1;
+		chatList = null;
+
+		if (query === '') {
+			chatList = await getArchivedChatList(localStorage.token, page);
+		} else {
+			searchDebounceTimeout = setTimeout(async () => {
+				chatList = await getArchivedChatList(localStorage.token, page, query);
+			}, 500);
+		}
+
+		if ((chatList ?? []).length === 0) {
+			allChatsLoaded = true;
+		} else {
+			allChatsLoaded = false;
+		}
+	};
+
+	const loadMoreChats = async () => {
+		chatListLoading = true;
+		page += 1;
+
+		let newChatList = [];
+
+		if (query) {
+			newChatList = await getArchivedChatList(localStorage.token, page, query);
+		} else {
+			newChatList = await getArchivedChatList(localStorage.token, page);
+		}
+
+		// once the bottom of the list has been reached (no results) there is no need to continue querying
+		allChatsLoaded = newChatList.length === 0;
+
+		if (newChatList.length > 0) {
+			chatList = [...chatList, ...newChatList];
+		}
+
+		chatListLoading = false;
+	};
+
+	const exportChatsHandler = async () => {
+		const chats = await getAllArchivedChats(localStorage.token);
+		let blob = new Blob([JSON.stringify(chats)], {
+			type: 'application/json'
+		});
+		saveAs(blob, `${$i18n.t('archived-chat-export')}-${Date.now()}.json`);
+	};
+
+	const unarchiveHandler = async (chatId) => {
+		const res = await archiveChatById(localStorage.token, chatId).catch((error) => {
+			toast.error(`${error}`);
+		});
+
+		onUpdate();
+		init();
+	};
+
+	const unarchiveAllHandler = async () => {
+		const chats = await getAllArchivedChats(localStorage.token);
+		for (const chat of chats) {
+			await archiveChatById(localStorage.token, chat.id);
+		}
+
+		onUpdate();
+		init();
+	};
+
+	const init = async () => {
+		chatList = await getArchivedChatList(localStorage.token);
+	};
+
+	$: if (show) {
+		init();
+	}
+</script>
+
+<UnarchiveAllConfirmDialog
+	bind:show={showUnarchiveAllConfirmDialog}
+	message={$i18n.t('Are you sure you want to unarchive all archived chats?')}
+	confirmLabel={$i18n.t('Unarchive All')}
+	on:confirm={() => {
+		unarchiveAllHandler();
+	}}
+/>
+
+<ChatsModal
+	bind:show
+	bind:query
+	title={$i18n.t('Archived Chats')}
+	emptyPlaceholder={$i18n.t('You have no archived conversations.')}
+	{chatList}
+	{allChatsLoaded}
+	{chatListLoading}
+	onUpdate={() => {
+		init();
+	}}
+	loadHandler={loadMoreChats}
+	{unarchiveHandler}
+>
+	<div slot="footer">
+		<div class="flex flex-wrap text-sm font-medium gap-1.5 mt-2 m-1 justify-end w-full">
+			<button
+				class=" px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-100 dark:outline-gray-800 rounded-3xl"
+				on:click={() => {
+					showUnarchiveAllConfirmDialog = true;
+				}}
+			>
+				{$i18n.t('Unarchive All Archived Chats')}
+			</button>
+
+			<button
+				class="px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-100 dark:outline-gray-800 rounded-3xl"
+				on:click={() => {
+					exportChatsHandler();
+				}}
+			>
+				{$i18n.t('Export All Archived Chats')}
+			</button>
+		</div>
+	</div>
+</ChatsModal>

+ 303 - 0
src/lib/components/layout/ChatsModal.svelte

@@ -0,0 +1,303 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { getContext } from 'svelte';
+
+	import dayjs from 'dayjs';
+	import localizedFormat from 'dayjs/plugin/localizedFormat';
+
+	dayjs.extend(localizedFormat);
+
+	import { deleteChatById } from '$lib/apis/chats';
+
+	import Modal from '$lib/components/common/Modal.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Spinner from '../common/Spinner.svelte';
+	import Loader from '../common/Loader.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let show = false;
+
+	export let title = 'Chats';
+	export let emptyPlaceholder = '';
+
+	export let query = '';
+
+	export let chatList = null;
+	export let allChatsLoaded = false;
+	export let chatListLoading = false;
+
+	let selectedIdx = 0;
+
+	export let onUpdate = () => {};
+
+	export let loadHandler: null | Function = null;
+	export let unarchiveHandler: null | Function = null;
+
+	const deleteHandler = async (chatId) => {
+		const res = await deleteChatById(localStorage.token, chatId).catch((error) => {
+			toast.error(`${error}`);
+		});
+
+		onUpdate();
+	};
+</script>
+
+<Modal size="lg" bind:show>
+	<div>
+		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
+			<div class=" text-lg font-medium self-center">{title}</div>
+			<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
+						fill-rule="evenodd"
+						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"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</button>
+		</div>
+
+		<div class="flex flex-col w-full px-5 pb-4 dark:text-gray-200">
+			<div class=" flex w-full mb-1 space-x-2">
+				<div class="flex flex-1">
+					<div class=" self-center ml-1 mr-3">
+						<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>
+					<input
+						class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-hidden bg-transparent"
+						bind:value={query}
+						placeholder={$i18n.t('Search Chats')}
+					/>
+				</div>
+			</div>
+			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
+				{#if chatList}
+					<div class="w-full">
+						<div class="text-left text-sm w-full mb-3 max-h-[22rem] overflow-y-scroll">
+							{#if chatList.length === 0}
+								<div class="text-xs text-gray-500 dark:text-gray-400 text-center px-5">
+									{$i18n.t('No results found')}
+								</div>
+							{/if}
+
+							{#each chatList as chat, idx (chat.id)}
+								{#if idx === 0 || (idx > 0 && chat.time_range !== chatList[idx - 1].time_range)}
+									<div
+										class="w-full text-xs text-gray-500 dark:text-gray-500 font-medium {idx === 0
+											? ''
+											: 'pt-5'} pb-2 px-2"
+									>
+										{$i18n.t(chat.time_range)}
+										<!-- 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')}
+							-->
+									</div>
+								{/if}
+
+								<a
+									class=" w-full flex justify-between items-center rounded-lg text-sm py-2 px-3 hover:bg-gray-50 dark:hover:bg-gray-850 {selectedIdx ===
+									idx
+										? 'bg-gray-50 dark:bg-gray-850'
+										: ''}"
+									href="/c/{chat.id}"
+									draggable="false"
+									data-arrow-selected={selectedIdx === idx ? 'true' : undefined}
+									on:mouseenter={() => {
+										selectedIdx = idx;
+									}}
+									on:click={() => {
+										show = false;
+									}}
+								>
+									<div class=" flex-1">
+										<div class="text-ellipsis line-clamp-1 w-full">
+											{chat?.title}
+										</div>
+									</div>
+
+									<div class=" pl-3 shrink-0 text-gray-500 dark:text-gray-400 text-xs">
+										{dayjs(chat?.updated_at * 1000).calendar()}
+									</div>
+								</a>
+							{/each}
+
+							{#if !allChatsLoaded && loadHandler}
+								<Loader
+									on:visible={(e) => {
+										if (!chatListLoading) {
+											loadHandler();
+										}
+									}}
+								>
+									<div
+										class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2"
+									>
+										<Spinner className=" size-4" />
+										<div class=" ">Loading...</div>
+									</div>
+								</Loader>
+							{/if}
+						</div>
+
+						{#if query === ''}
+							<slot name="footer"></slot>
+						{/if}
+					</div>
+				{:else}
+					<div class="w-full h-full flex justify-center items-center min-h-20">
+						<Spinner />
+					</div>
+				{/if}
+
+				<!-- {#if chats !== null}
+					{#if chats.length > 0}
+						<div class="w-full">
+							<div class="text-left text-sm w-full mb-3 max-h-[22rem] overflow-y-scroll">
+								<div class="relative overflow-x-auto">
+									<table
+										class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto"
+									>
+										<thead
+											class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-1 border-gray-50 dark:border-gray-850"
+										>
+											<tr>
+												<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
+												<th scope="col" class="px-3 py-2 hidden md:flex">
+													{$i18n.t('Created At')}
+												</th>
+												<th scope="col" class="px-3 py-2 text-right" />
+											</tr>
+										</thead>
+										<tbody>
+											{#each chats as chat, idx}
+												<tr
+													class="bg-transparent {idx !== chats.length - 1 &&
+														'border-b'} dark:bg-gray-900 border-gray-50 dark:border-gray-850 text-xs"
+												>
+													<td class="px-3 py-1 w-2/3">
+														<a href="/c/{chat.id}" target="_blank">
+															<div class=" hover:underline line-clamp-1">
+																{chat.title}
+															</div>
+														</a>
+													</td>
+
+													<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
+														<div class="my-auto">
+															{dayjs(chat.created_at * 1000).format('LLL')}
+														</div>
+													</td>
+
+													<td class="px-3 py-1 text-right">
+														<div class="flex justify-end w-full">
+															{#if unarchiveHandler}
+																<Tooltip content={$i18n.t('Unarchive Chat')}>
+																	<button
+																		class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+																		on:click={async () => {
+																			unarchiveHandler(chat.id);
+																		}}
+																	>
+																		<svg
+																			xmlns="http://www.w3.org/2000/svg"
+																			fill="none"
+																			viewBox="0 0 24 24"
+																			stroke-width="1.5"
+																			stroke="currentColor"
+																			class="size-4"
+																		>
+																			<path
+																				stroke-linecap="round"
+																				stroke-linejoin="round"
+																				d="M9 8.25H7.5a2.25 2.25 0 0 0-2.25 2.25v9a2.25 2.25 0 0 0 2.25 2.25h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25H15m0-3-3-3m0 0-3 3m3-3V15"
+																			/>
+																		</svg>
+																	</button>
+																</Tooltip>
+															{/if}
+
+															<Tooltip content={$i18n.t('Delete Chat')}>
+																<button
+																	class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+																	on:click={async () => {
+																		deleteHandler(chat.id);
+																	}}
+																>
+																	<svg
+																		xmlns="http://www.w3.org/2000/svg"
+																		fill="none"
+																		viewBox="0 0 24 24"
+																		stroke-width="1.5"
+																		stroke="currentColor"
+																		class="w-4 h-4"
+																	>
+																		<path
+																			stroke-linecap="round"
+																			stroke-linejoin="round"
+																			d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
+																		/>
+																	</svg>
+																</button>
+															</Tooltip>
+														</div>
+													</td>
+												</tr>
+											{/each}
+										</tbody>
+									</table>
+								</div>
+							</div>
+
+							<slot name="footer"></slot>
+						</div>
+					{:else}
+						<div class="text-left text-sm w-full mb-8">
+							{emptyPlaceholder || $i18n.t('No chats found.')}
+						</div>
+					{/if}
+				{:else}
+					<div class="w-full h-full">
+						<Spinner />
+					</div>
+				{/if} -->
+			</div>
+		</div>
+	</div>
+</Modal>

+ 2 - 2
src/lib/components/layout/Sidebar.svelte

@@ -43,7 +43,7 @@
 	import { createNewFolder, getFolders, updateFolderParentIdById } from '$lib/apis/folders';
 	import { WEBUI_BASE_URL } from '$lib/constants';
 
-	import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte';
+	import ArchivedChatsModal from './ArchivedChatsModal.svelte';
 	import UserMenu from './Sidebar/UserMenu.svelte';
 	import ChatItem from './Sidebar/ChatItem.svelte';
 	import Spinner from '../common/Spinner.svelte';
@@ -395,7 +395,7 @@
 
 <ArchivedChatsModal
 	bind:show={$showArchivedChats}
-	on:change={async () => {
+	onUpdate={async () => {
 		await initChatList();
 	}}
 />

+ 0 - 255
src/lib/components/layout/Sidebar/ArchivedChatsModal.svelte

@@ -1,255 +0,0 @@
-<script lang="ts">
-	import fileSaver from 'file-saver';
-	const { saveAs } = fileSaver;
-	import { toast } from 'svelte-sonner';
-	import dayjs from 'dayjs';
-	import { getContext, createEventDispatcher } from 'svelte';
-	import localizedFormat from 'dayjs/plugin/localizedFormat';
-
-	dayjs.extend(localizedFormat);
-
-	const dispatch = createEventDispatcher();
-
-	import {
-		archiveChatById,
-		deleteChatById,
-		getAllArchivedChats,
-		getArchivedChatList
-	} from '$lib/apis/chats';
-
-	import Modal from '$lib/components/common/Modal.svelte';
-	import Tooltip from '$lib/components/common/Tooltip.svelte';
-	import UnarchiveAllConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
-	const i18n = getContext('i18n');
-
-	export let show = false;
-
-	let chats = [];
-
-	let searchValue = '';
-	let showUnarchiveAllConfirmDialog = false;
-
-	const unarchiveChatHandler = async (chatId) => {
-		const res = await archiveChatById(localStorage.token, chatId).catch((error) => {
-			toast.error(`${error}`);
-		});
-
-		chats = await getArchivedChatList(localStorage.token);
-		dispatch('change');
-	};
-
-	const deleteChatHandler = async (chatId) => {
-		const res = await deleteChatById(localStorage.token, chatId).catch((error) => {
-			toast.error(`${error}`);
-		});
-
-		chats = await getArchivedChatList(localStorage.token);
-	};
-
-	const exportChatsHandler = async () => {
-		const chats = await getAllArchivedChats(localStorage.token);
-		let blob = new Blob([JSON.stringify(chats)], {
-			type: 'application/json'
-		});
-		saveAs(blob, `${$i18n.t('archived-chat-export')}-${Date.now()}.json`);
-	};
-
-	const unarchiveAllHandler = async () => {
-		for (const chat of chats) {
-			await archiveChatById(localStorage.token, chat.id);
-		}
-		chats = await getArchivedChatList(localStorage.token);
-	};
-
-	$: if (show) {
-		(async () => {
-			chats = await getArchivedChatList(localStorage.token);
-		})();
-	}
-</script>
-
-<UnarchiveAllConfirmDialog
-	bind:show={showUnarchiveAllConfirmDialog}
-	message={$i18n.t('Are you sure you want to unarchive all archived chats?')}
-	confirmLabel={$i18n.t('Unarchive All')}
-	on:confirm={() => {
-		unarchiveAllHandler();
-	}}
-/>
-
-<Modal size="lg" bind:show>
-	<div>
-		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
-			<div class=" text-lg font-medium self-center">{$i18n.t('Archived Chats')}</div>
-			<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
-						fill-rule="evenodd"
-						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"
-						clip-rule="evenodd"
-					/>
-				</svg>
-			</button>
-		</div>
-
-		<div class="flex flex-col w-full px-5 pb-4 dark:text-gray-200">
-			<div class=" flex w-full mt-2 space-x-2">
-				<div class="flex flex-1">
-					<div class=" self-center ml-1 mr-3">
-						<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>
-					<input
-						class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-hidden bg-transparent"
-						bind:value={searchValue}
-						placeholder={$i18n.t('Search Chats')}
-					/>
-				</div>
-			</div>
-			<hr class="border-gray-100 dark:border-gray-850 my-2" />
-			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
-				{#if chats.length > 0}
-					<div class="w-full">
-						<div class="text-left text-sm w-full mb-3 max-h-[22rem] overflow-y-scroll">
-							<div class="relative overflow-x-auto">
-								<table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto">
-									<thead
-										class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 border-gray-50 dark:border-gray-850"
-									>
-										<tr>
-											<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
-											<th scope="col" class="px-3 py-2 hidden md:flex">
-												{$i18n.t('Created At')}
-											</th>
-											<th scope="col" class="px-3 py-2 text-right" />
-										</tr>
-									</thead>
-									<tbody>
-										{#each chats.filter((c) => searchValue === '' || c.title
-													.toLowerCase()
-													.includes(searchValue.toLowerCase())) as chat, idx}
-											<tr
-												class="bg-transparent {idx !== chats.length - 1 &&
-													'border-b'} dark:bg-gray-900 border-gray-50 dark:border-gray-850 text-xs"
-											>
-												<td class="px-3 py-1 w-2/3">
-													<a href="/c/{chat.id}" target="_blank">
-														<div class=" underline line-clamp-1">
-															{chat.title}
-														</div>
-													</a>
-												</td>
-
-												<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
-													<div class="my-auto">
-														{dayjs(chat.created_at * 1000).format('LLL')}
-													</div>
-												</td>
-
-												<td class="px-3 py-1 text-right">
-													<div class="flex justify-end w-full">
-														<Tooltip content={$i18n.t('Unarchive Chat')}>
-															<button
-																class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
-																on:click={async () => {
-																	unarchiveChatHandler(chat.id);
-																}}
-															>
-																<svg
-																	xmlns="http://www.w3.org/2000/svg"
-																	fill="none"
-																	viewBox="0 0 24 24"
-																	stroke-width="1.5"
-																	stroke="currentColor"
-																	class="size-4"
-																>
-																	<path
-																		stroke-linecap="round"
-																		stroke-linejoin="round"
-																		d="M9 8.25H7.5a2.25 2.25 0 0 0-2.25 2.25v9a2.25 2.25 0 0 0 2.25 2.25h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25H15m0-3-3-3m0 0-3 3m3-3V15"
-																	/>
-																</svg>
-															</button>
-														</Tooltip>
-
-														<Tooltip content={$i18n.t('Delete Chat')}>
-															<button
-																class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
-																on:click={async () => {
-																	deleteChatHandler(chat.id);
-																}}
-															>
-																<svg
-																	xmlns="http://www.w3.org/2000/svg"
-																	fill="none"
-																	viewBox="0 0 24 24"
-																	stroke-width="1.5"
-																	stroke="currentColor"
-																	class="w-4 h-4"
-																>
-																	<path
-																		stroke-linecap="round"
-																		stroke-linejoin="round"
-																		d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
-																	/>
-																</svg>
-															</button>
-														</Tooltip>
-													</div>
-												</td>
-											</tr>
-										{/each}
-									</tbody>
-								</table>
-							</div>
-						</div>
-
-						<div class="flex flex-wrap text-sm font-medium gap-1.5 mt-2 m-1 justify-end w-full">
-							<button
-								class=" px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-300 dark:outline-gray-800 rounded-3xl"
-								on:click={() => {
-									showUnarchiveAllConfirmDialog = true;
-								}}
-							>
-								{$i18n.t('Unarchive All Archived Chats')}
-							</button>
-
-							<button
-								class="px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-300 dark:outline-gray-800 rounded-3xl"
-								on:click={() => {
-									exportChatsHandler();
-								}}
-							>
-								{$i18n.t('Export All Archived Chats')}
-							</button>
-						</div>
-					</div>
-				{:else}
-					<div class="text-left text-sm w-full mb-8">
-						{$i18n.t('You have no archived conversations.')}
-					</div>
-				{/if}
-			</div>
-		</div>
-	</div>
-</Modal>