From 7e6f1f8848c45873e950c085929fe9420777cc41 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 25 May 2025 00:48:30 +0400 Subject: [PATCH] enh: archived chats modal --- backend/open_webui/models/chats.py | 29 +- backend/open_webui/routers/chats.py | 24 +- src/lib/apis/chats/index.ts | 10 +- src/lib/components/chat/Settings/Chats.svelte | 4 +- .../layout/ArchivedChatsModal.svelte | 159 +++++++++ src/lib/components/layout/ChatsModal.svelte | 303 ++++++++++++++++++ src/lib/components/layout/Sidebar.svelte | 4 +- .../layout/Sidebar/ArchivedChatsModal.svelte | 255 --------------- 8 files changed, 517 insertions(+), 271 deletions(-) create mode 100644 src/lib/components/layout/ArchivedChatsModal.svelte create mode 100644 src/lib/components/layout/ChatsModal.svelte delete mode 100644 src/lib/components/layout/Sidebar/ArchivedChatsModal.svelte diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py index 4b4f37197..9d28cc35a 100644 --- a/backend/open_webui/models/chats.py +++ b/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( diff --git a/backend/open_webui/routers/chats.py b/backend/open_webui/routers/chats.py index 6f00dd4d7..4bf06bd22 100644 --- a/backend/open_webui/routers/chats.py +++ b/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 ############################ diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index 0ff56ea23..2906f3485 100644 --- a/src/lib/apis/chats/index.ts +++ b/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', diff --git a/src/lib/components/chat/Settings/Chats.svelte b/src/lib/components/chat/Settings/Chats.svelte index 8a0ada02d..7ef0d7a56 100644 --- a/src/lib/components/chat/Settings/Chats.svelte +++ b/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 @@ }; - +
diff --git a/src/lib/components/layout/ArchivedChatsModal.svelte b/src/lib/components/layout/ArchivedChatsModal.svelte new file mode 100644 index 000000000..8e762f8de --- /dev/null +++ b/src/lib/components/layout/ArchivedChatsModal.svelte @@ -0,0 +1,159 @@ + + + { + unarchiveAllHandler(); + }} +/> + + { + init(); + }} + loadHandler={loadMoreChats} + {unarchiveHandler} +> +
+
+ + + +
+
+
diff --git a/src/lib/components/layout/ChatsModal.svelte b/src/lib/components/layout/ChatsModal.svelte new file mode 100644 index 000000000..8bdefd43e --- /dev/null +++ b/src/lib/components/layout/ChatsModal.svelte @@ -0,0 +1,303 @@ + + + +
+
+
{title}
+ +
+ +
+
+
+
+ + + +
+ +
+
+
+ {#if chatList} +
+
+ {#if chatList.length === 0} +
+ {$i18n.t('No results found')} +
+ {/if} + + {#each chatList as chat, idx (chat.id)} + {#if idx === 0 || (idx > 0 && chat.time_range !== chatList[idx - 1].time_range)} +
+ {$i18n.t(chat.time_range)} + +
+ {/if} + + { + selectedIdx = idx; + }} + on:click={() => { + show = false; + }} + > +
+
+ {chat?.title} +
+
+ +
+ {dayjs(chat?.updated_at * 1000).calendar()} +
+
+ {/each} + + {#if !allChatsLoaded && loadHandler} + { + if (!chatListLoading) { + loadHandler(); + } + }} + > +
+ +
Loading...
+
+
+ {/if} +
+ + {#if query === ''} + + {/if} +
+ {:else} +
+ +
+ {/if} + + +
+
+
+
diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index b4f31af64..34342a859 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/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 @@ { + onUpdate={async () => { await initChatList(); }} /> diff --git a/src/lib/components/layout/Sidebar/ArchivedChatsModal.svelte b/src/lib/components/layout/Sidebar/ArchivedChatsModal.svelte deleted file mode 100644 index 1fa20bcf5..000000000 --- a/src/lib/components/layout/Sidebar/ArchivedChatsModal.svelte +++ /dev/null @@ -1,255 +0,0 @@ - - - { - unarchiveAllHandler(); - }} -/> - - -
-
-
{$i18n.t('Archived Chats')}
- -
- -
-
-
-
- - - -
- -
-
-
-
- {#if chats.length > 0} -
-
-
- - - - - - - - - {#each chats.filter((c) => searchValue === '' || c.title - .toLowerCase() - .includes(searchValue.toLowerCase())) as chat, idx} - - - - - - - - {/each} - -
{$i18n.t('Name')} -
- -
- {chat.title} -
-
-
-
- - - - - - - -
-
-
-
- -
- - - -
-
- {:else} -
- {$i18n.t('You have no archived conversations.')} -
- {/if} -
-
-
-