From e5a08d52208e8b1ed07ff94906e27d174146b1ca Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 26 Jul 2026 23:54:16 -0400 Subject: [PATCH] refac --- backend/open_webui/models/chats.py | 15 ++++++ backend/open_webui/routers/chats.py | 8 ++++ src/lib/apis/chats/index.ts | 28 +++++++++++ src/lib/components/layout/Sidebar.svelte | 47 ++++++++++++++++++- .../components/layout/Sidebar/Section.svelte | 2 + src/lib/stores/chatList.ts | 7 +++ 6 files changed, 106 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py index d753fe3319..56d0523f84 100644 --- a/backend/open_webui/models/chats.py +++ b/backend/open_webui/models/chats.py @@ -694,6 +694,21 @@ class ChatTable: await session.commit() return result.rowcount or 0 + async def mark_root_chats_read_by_user_id(self, user_id: str, db: AsyncSession | None = None) -> int: + async with get_async_db_context(db) as session: + result = await session.execute( + update(Chat) + .where( + Chat.user_id == user_id, + Chat.folder_id.is_(None), + Chat.archived == False, + Chat.meta['internal'].as_boolean().is_not(True), + ) + .values(last_read_at=Chat.updated_at) + ) + await session.commit() + return result.rowcount or 0 + async def update_chat_title_by_id(self, id: str, title: str) -> ChatModel | None: try: async with get_async_db_context() as session: diff --git a/backend/open_webui/routers/chats.py b/backend/open_webui/routers/chats.py index 206c2db974..ffda93d9ba 100644 --- a/backend/open_webui/routers/chats.py +++ b/backend/open_webui/routers/chats.py @@ -255,6 +255,14 @@ async def get_session_user_chat_list( raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()) +@router.post('/read') +async def mark_root_chats_read_by_user_id( + user=Depends(get_verified_user), + db: AsyncSession = Depends(get_async_session), +): + return {'updated_count': await Chats.mark_root_chats_read_by_user_id(user.id, db=db)} + + ############################ # GetChatUsageStats # EXPERIMENTAL: may be removed in future releases diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index 66b1a3e9ea..b55df4b72d 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -913,6 +913,34 @@ export const markChatUnreadById = async (token: string, id: string) => { return res; }; +export const markChatsRead = async (token: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/read`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = 'detail' in err ? err.detail : err; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const cloneChatById = async (token: string, id: string, title?: string) => { let error = null; diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 0e40652585..6883097b15 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -31,6 +31,7 @@ loadNextChatListPage, refreshChatList, registerFolderRefreshHandler, + setAllChatsRead, setChatActive, setChatReadAt } from '$lib/stores/chatList'; @@ -47,7 +48,8 @@ updateChatFolderIdById, importChats, deleteAllChats, - getChatListBySearchText + getChatListBySearchText, + markChatsRead } from '$lib/apis/chats'; import { createNewFolder, @@ -86,6 +88,10 @@ import WorkspaceIcon from './Sidebar/icons/Workspace.svelte'; import { slide } from 'svelte/transition'; import HotkeyHint from '../common/HotkeyHint.svelte'; + import Dropdown from '../common/Dropdown.svelte'; + import DropdownMenu from '../common/DropdownMenu.svelte'; + import CheckIcon from '../icons/Check.svelte'; + import MoreHorizontalIcon from './Sidebar/icons/MoreHorizontal.svelte'; const BREAKPOINT = 768; const DEFAULT_PINNED_ITEMS = ['notes', 'workspace']; @@ -121,6 +127,7 @@ let showChannels = false; let showFolders = false; let showSharedFolders = false; + let showChatsMenu = false; let folders = {}; let folderRegistry: Record< @@ -440,6 +447,17 @@ } }; + const markAllChatsReadHandler = async () => { + const res = await markChatsRead(localStorage.token).catch((error) => { + toast.error(`${error}`); + return null; + }); + if (!res) return; + + showChatsMenu = false; + setAllChatsRead(); + }; + const importChatHandler = async (items, pinned = false, folderId = null) => { if (!canImportChats) { toast.error($i18n.t('Access prohibited')); @@ -1434,6 +1452,33 @@ } }} > + + + + + + +
+ + + +
+
+
+ {#if $pinnedChats.length > 0}
diff --git a/src/lib/components/layout/Sidebar/Section.svelte b/src/lib/components/layout/Sidebar/Section.svelte index 9e0557dbe4..cf55bd8ceb 100644 --- a/src/lib/components/layout/Sidebar/Section.svelte +++ b/src/lib/components/layout/Sidebar/Section.svelte @@ -143,6 +143,8 @@ + + {#if onAdd}