diff --git a/backend/open_webui/models/shared_chats.py b/backend/open_webui/models/shared_chats.py index 9132f11301..a6ceebb8b0 100644 --- a/backend/open_webui/models/shared_chats.py +++ b/backend/open_webui/models/shared_chats.py @@ -201,5 +201,15 @@ class SharedChatsTable: except Exception: return False + async def delete_all_by_user_id(self, user_id: str, db: Optional[AsyncSession] = None) -> bool: + """Delete all shared chats created by a user.""" + try: + async with get_async_db_context(db) as db: + await db.execute(delete(SharedChat).filter_by(user_id=user_id)) + await db.commit() + return True + except Exception: + return False + SharedChats = SharedChatsTable() diff --git a/backend/open_webui/routers/chats.py b/backend/open_webui/routers/chats.py index 06457b4c55..441ffd0778 100644 --- a/backend/open_webui/routers/chats.py +++ b/backend/open_webui/routers/chats.py @@ -853,10 +853,27 @@ async def unarchive_all_chats(user=Depends(get_verified_user), db: AsyncSession ############################ -# GetSharedChats +# UnshareAllChats ############################ +@router.delete('/share/all', response_model=bool) +async def unshare_all_chats(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): + # Collect chat_ids that have shares so we can clear share_id and access grants + shared_list = await SharedChats.get_by_user_id(user.id, db=db) + chat_ids = [s.chat_id for s in shared_list] + + # Delete all shared_chat rows for this user + result = await SharedChats.delete_all_by_user_id(user.id, db=db) + + # Clear share_id on the original chats and remove access grants + for chat_id in chat_ids: + await Chats.update_chat_share_id_by_id(chat_id, None, db=db) + await AccessGrants.set_access_grants('shared_chat', chat_id, [], db=db) + + return result + + @router.get('/shared', response_model=list[SharedChatResponse]) async def get_shared_session_user_chat_list( page: int | None = None, diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index 6c1f6454a3..5edab61c24 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -65,6 +65,38 @@ export const unarchiveAllChats = async (token: string) => { return res; }; +export const unshareAllChats = async (token: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/share/all`, { + method: 'DELETE', + 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(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const importChats = async (token: string, chats: object[]) => { let error = null; diff --git a/src/lib/components/layout/SharedChatsModal.svelte b/src/lib/components/layout/SharedChatsModal.svelte index 55c5d6cb51..d505578683 100644 --- a/src/lib/components/layout/SharedChatsModal.svelte +++ b/src/lib/components/layout/SharedChatsModal.svelte @@ -2,15 +2,18 @@ import type { Writable } from 'svelte/store'; import { toast } from 'svelte-sonner'; import { getContext } from 'svelte'; - import { deleteSharedChatById, getSharedChatList } from '$lib/apis/chats'; + import { unshareAllChats, deleteSharedChatById, getSharedChatList } from '$lib/apis/chats'; import ChatsModal from './ChatsModal.svelte'; + import UnshareAllConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; + import Spinner from '../common/Spinner.svelte'; const i18n: Writable = getContext('i18n'); export let show = false; export let onUpdate = () => {}; + let loading = false; let chatList: any[] | null = null; let page = 1; @@ -22,6 +25,8 @@ let chatListLoading = false; let searchDebounceTimeout: any; + let showUnshareAllConfirmDialog = false; + let filter: any = {}; $: filter = { ...(query ? { query } : {}), @@ -97,6 +102,20 @@ } }; + const unshareAllHandler = async () => { + loading = true; + try { + await unshareAllChats(localStorage.token); + toast.success($i18n.t('All shared chats have been unshared.')); + onUpdate(); + await init(); + } catch (error) { + toast.error(`${error}`); + } finally { + loading = false; + } + }; + const init = async () => { chatList = await getSharedChatList(localStorage.token); }; @@ -106,6 +125,17 @@ } + { + unshareAllHandler(); + }} +/> + +> +
+
+ +
+
+
diff --git a/src/lib/i18n/locales/en-US/translation.json b/src/lib/i18n/locales/en-US/translation.json index 34ceddec35..b41552679d 100644 --- a/src/lib/i18n/locales/en-US/translation.json +++ b/src/lib/i18n/locales/en-US/translation.json @@ -122,6 +122,7 @@ "AI": "", "All": "", "All chats have been unarchived.": "", + "All shared chats have been unshared.": "", "All day": "", "All models are now hidden": "", "All models are now visible": "", @@ -204,6 +205,7 @@ "Are you sure you want to delete this version? Child versions will be relinked to this version's parent.": "", "Are you sure you want to delete this?": "", "Are you sure you want to unarchive all archived chats?": "", + "Are you sure you want to unshare all shared chats? This will remove all share links.": "", "Arena Models": "", "Artifacts": "", "Asc": "", @@ -2175,6 +2177,8 @@ "UI Scale": "", "Unarchive All": "", "Unarchive All Archived Chats": "", + "Unshare All": "", + "Unshare All Shared Chats": "", "Unarchive Chat": "", "Underline": "", "Unknown": "",