diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py index 18f802afe..8e721da78 100644 --- a/backend/open_webui/models/chats.py +++ b/backend/open_webui/models/chats.py @@ -469,6 +469,8 @@ class ChatTable: def get_chat_by_share_id(self, id: str) -> Optional[ChatModel]: try: with get_db() as db: + # it is possible that the shared link was deleted. hence, + # we check if the chat is still shared by checkng if a chat with the share_id exists chat = db.query(Chat).filter_by(share_id=id).first() if chat: diff --git a/backend/open_webui/routers/chats.py b/backend/open_webui/routers/chats.py index 5e0e75e24..67d7fbb3a 100644 --- a/backend/open_webui/routers/chats.py +++ b/backend/open_webui/routers/chats.py @@ -463,6 +463,31 @@ async def clone_chat_by_id(id: str, user=Depends(get_verified_user)): ) +############################ +# CloneChatByShareId +############################ + + +@router.post("/{share_id}/clone_shared", response_model=Optional[ChatResponse]) +async def clone_chat_by_share_id(share_id: str, user=Depends(get_verified_user)): + chat = Chats.get_chat_by_share_id(share_id) + if chat: + updated_chat = { + **chat.chat, + "originalChatId": chat.id, + "branchPointMessageId": chat.chat["history"]["currentId"], + "title": f"Clone of {chat.title}", + } + + chat = Chats.insert_new_chat(user.id, ChatForm(**{"chat": updated_chat})) + return ChatResponse(**chat.model_dump()) + else: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT() + ) + + + ############################ # ArchiveChat ############################ diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index d93d21c73..d2d7e64ac 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -618,6 +618,44 @@ export const cloneChatById = async (token: string, id: string) => { return res; }; +export const cloneChatByShareId = async (token: string, share_id: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${share_id}/clone_shared`, { + 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(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + + if ('detail' in err) { + error = err.detail; + } else { + error = err; + } + + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const shareChatById = async (token: string, id: string) => { let error = null; diff --git a/src/routes/s/[id]/+page.svelte b/src/routes/s/[id]/+page.svelte index 0d4579838..b0dae7744 100644 --- a/src/routes/s/[id]/+page.svelte +++ b/src/routes/s/[id]/+page.svelte @@ -8,13 +8,14 @@ import { settings, chatId, WEBUI_NAME, models } from '$lib/stores'; import { convertMessagesToHistory, createMessagesList } from '$lib/utils'; - import { getChatByShareId } from '$lib/apis/chats'; + import { getChatByShareId, cloneChatByShareId } from '$lib/apis/chats'; import Messages from '$lib/components/chat/Messages.svelte'; import Navbar from '$lib/components/layout/Navbar.svelte'; import { getUserById } from '$lib/apis/users'; import { error } from '@sveltejs/kit'; import { getModels } from '$lib/apis'; + import { toast } from 'svelte-sonner'; const i18n = getContext('i18n'); @@ -100,6 +101,19 @@ } } }; + + const cloneSharedChat = async () => { + if (!chat) return; + + const res = await cloneChatByShareId(localStorage.token, chat.id).catch((error) => { + toast.error(error); + return null; + }); + + if (res) { + goto(`/c/${res.id}`); + } + }; @@ -121,8 +135,16 @@ {title} -
- {dayjs(chat.chat.timestamp).format($i18n.t('MMMM DD, YYYY'))} +
+
+ {dayjs(chat.chat.timestamp).format($i18n.t('MMMM DD, YYYY'))} +
+