From e6951e804a2a14ce7bede07770aea87826483f0c Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 23 Nov 2025 00:01:49 -0500 Subject: [PATCH] feat/enh: move chats in folder on delete Co-Authored-By: expruc <25387342+expruc@users.noreply.github.com> --- backend/open_webui/models/chats.py | 14 ++++++++ backend/open_webui/routers/folders.py | 13 +++++-- src/lib/apis/folders/index.ts | 7 ++-- .../chat/Placeholder/FolderTitle.svelte | 36 ++++++++++++------- .../layout/Sidebar/RecursiveFolder.svelte | 32 ++++++++++++----- 5 files changed, 76 insertions(+), 26 deletions(-) diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py index c9d36b8646..64227843a0 100644 --- a/backend/open_webui/models/chats.py +++ b/backend/open_webui/models/chats.py @@ -1142,6 +1142,20 @@ class ChatTable: except Exception: return False + def move_chats_by_user_id_and_folder_id( + self, user_id: str, folder_id: str, new_folder_id: Optional[str] + ) -> bool: + try: + with get_db() as db: + db.query(Chat).filter_by(user_id=user_id, folder_id=folder_id).update( + {"folder_id": new_folder_id} + ) + db.commit() + + return True + except Exception: + return False + def delete_shared_chats_by_user_id(self, user_id: str) -> bool: try: with get_db() as db: diff --git a/backend/open_webui/routers/folders.py b/backend/open_webui/routers/folders.py index b242b08e3a..03212bdb7c 100644 --- a/backend/open_webui/routers/folders.py +++ b/backend/open_webui/routers/folders.py @@ -258,7 +258,10 @@ async def update_folder_is_expanded_by_id( @router.delete("/{id}") async def delete_folder_by_id( - request: Request, id: str, user=Depends(get_verified_user) + request: Request, + id: str, + delete_contents: Optional[bool] = True, + user=Depends(get_verified_user), ): if Chats.count_chats_by_folder_id_and_user_id(id, user.id): chat_delete_permission = has_permission( @@ -277,8 +280,14 @@ async def delete_folder_by_id( if folder: try: folder_ids = Folders.delete_folder_by_id_and_user_id(id, user.id) + for folder_id in folder_ids: - Chats.delete_chats_by_user_id_and_folder_id(user.id, folder_id) + if delete_contents: + Chats.delete_chats_by_user_id_and_folder_id(user.id, folder_id) + else: + Chats.move_chats_by_user_id_and_folder_id( + user.id, folder_id, None + ) return True except Exception as e: diff --git a/src/lib/apis/folders/index.ts b/src/lib/apis/folders/index.ts index 0faa547141..535adbd5f6 100644 --- a/src/lib/apis/folders/index.ts +++ b/src/lib/apis/folders/index.ts @@ -239,10 +239,13 @@ export const updateFolderItemsById = async (token: string, id: string, items: Fo return res; }; -export const deleteFolderById = async (token: string, id: string) => { +export const deleteFolderById = async (token: string, id: string, deleteContents: boolean) => { let error = null; - const res = await fetch(`${WEBUI_API_BASE_URL}/folders/${id}`, { + const searchParams = new URLSearchParams(); + searchParams.append('delete_contents', deleteContents ? 'true' : 'false'); + + const res = await fetch(`${WEBUI_API_BASE_URL}/folders/${id}?${searchParams.toString()}`, { method: 'DELETE', headers: { Accept: 'application/json', diff --git a/src/lib/components/chat/Placeholder/FolderTitle.svelte b/src/lib/components/chat/Placeholder/FolderTitle.svelte index 533b627d0d..470110b7fe 100644 --- a/src/lib/components/chat/Placeholder/FolderTitle.svelte +++ b/src/lib/components/chat/Placeholder/FolderTitle.svelte @@ -31,6 +31,7 @@ let showFolderModal = false; let showDeleteConfirm = false; + let deleteFolderContents = true; const updateHandler = async ({ name, meta, data }) => { if (name === '') { @@ -98,10 +99,12 @@ }; const deleteHandler = async () => { - const res = await deleteFolderById(localStorage.token, folder.id).catch((error) => { - toast.error(`${error}`); - return null; - }); + const res = await deleteFolderById(localStorage.token, folder.id, deleteFolderContents).catch( + (error) => { + toast.error(`${error}`); + return null; + } + ); if (res) { toast.success($i18n.t('Folder deleted successfully')); @@ -141,15 +144,22 @@ deleteHandler(); }} > -
- {@html DOMPurify.sanitize( - $i18n.t( - 'This will delete {{NAME}} and all its contents.', - { - NAME: folder.name - } - ) - )} +
+ + + {$i18n.t(`Are you sure you want to delete "{{NAME}}"?`, { + NAME: folders[folderId].name + })} +
+ +
+ + +
+ {$i18n.t('Delete all contents inside this folder')} +
diff --git a/src/lib/components/layout/Sidebar/RecursiveFolder.svelte b/src/lib/components/layout/Sidebar/RecursiveFolder.svelte index 331719647d..4609f531e3 100644 --- a/src/lib/components/layout/Sidebar/RecursiveFolder.svelte +++ b/src/lib/components/layout/Sidebar/RecursiveFolder.svelte @@ -52,6 +52,8 @@ export let className = ''; + export let deleteFolderContents = true; + export let parentDragged = false; export let onDelete = (e) => {}; @@ -288,10 +290,12 @@ let showDeleteConfirm = false; const deleteHandler = async () => { - const res = await deleteFolderById(localStorage.token, folderId).catch((error) => { - toast.error(`${error}`); - return null; - }); + const res = await deleteFolderById(localStorage.token, folderId, deleteFolderContents).catch( + (error) => { + toast.error(`${error}`); + return null; + } + ); if (res) { toast.success($i18n.t('Folder deleted successfully')); @@ -430,12 +434,22 @@ deleteHandler(); }} > -
- {@html DOMPurify.sanitize( - $i18n.t('This will delete {{NAME}} and all its contents.', { +
+ + + {$i18n.t(`Are you sure you want to delete "{{NAME}}"?`, { + NAME: folders[folderId].name + })} +
+ +
+ + +
+ {$i18n.t('Delete all contents inside this folder')} +