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 @@
}
}}
>
+