Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
7674229e
"test/vscode:/vscode.git/clone" did not exist on "e483aa0101660c9d59e9b72ff176c1ea363d3a0a"
Commit
7674229e
authored
May 31, 2024
by
Timothy J. Baek
Browse files
feat: chat clone
parent
eb12d1e1
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
122 additions
and
23 deletions
+122
-23
backend/apps/webui/routers/chats.py
backend/apps/webui/routers/chats.py
+26
-0
src/lib/apis/chats/index.ts
src/lib/apis/chats/index.ts
+38
-0
src/lib/components/icons/DocumentDuplicate.svelte
src/lib/components/icons/DocumentDuplicate.svelte
+19
-0
src/lib/components/layout/Sidebar.svelte
src/lib/components/layout/Sidebar.svelte
+17
-1
src/lib/components/layout/Sidebar/ChatMenu.svelte
src/lib/components/layout/Sidebar/ChatMenu.svelte
+20
-8
src/lib/components/workspace/Models/ModelMenu.svelte
src/lib/components/workspace/Models/ModelMenu.svelte
+2
-14
No files found.
backend/apps/webui/routers/chats.py
View file @
7674229e
...
@@ -288,6 +288,32 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_
...
@@ -288,6 +288,32 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_
return
result
return
result
############################
# CloneChat
############################
@
router
.
get
(
"/{id}/clone"
,
response_model
=
Optional
[
ChatResponse
])
async
def
clone_chat_by_id
(
id
:
str
,
user
=
Depends
(
get_current_user
)):
chat
=
Chats
.
get_chat_by_id_and_user_id
(
id
,
user
.
id
)
if
chat
:
chat_body
=
json
.
loads
(
chat
.
chat
)
updated_chat
=
{
**
chat_body
,
"originalChatId"
:
chat
.
id
,
"branchPointMessageId"
:
chat_body
[
"history"
][
"currentId"
],
"title"
:
f
"Clone of
{
chat
.
title
}
"
,
}
chat
=
Chats
.
insert_new_chat
(
user
.
id
,
ChatForm
(
**
{
"chat"
:
updated_chat
}))
return
ChatResponse
(
**
{
**
chat
.
model_dump
(),
"chat"
:
json
.
loads
(
chat
.
chat
)})
else
:
raise
HTTPException
(
status_code
=
status
.
HTTP_401_UNAUTHORIZED
,
detail
=
ERROR_MESSAGES
.
DEFAULT
()
)
############################
############################
# ArchiveChat
# ArchiveChat
############################
############################
...
...
src/lib/apis/chats/index.ts
View file @
7674229e
...
@@ -325,6 +325,44 @@ export const getChatByShareId = async (token: string, share_id: string) => {
...
@@ -325,6 +325,44 @@ export const getChatByShareId = async (token: string, share_id: string) => {
return
res
;
return
res
;
};
};
export
const
cloneChatById
=
async
(
token
:
string
,
id
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/chats/
${
id
}
/clone`
,
{
method
:
'
GET
'
,
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
)
=>
{
export
const
shareChatById
=
async
(
token
:
string
,
id
:
string
)
=>
{
let
error
=
null
;
let
error
=
null
;
...
...
src/lib/components/icons/DocumentDuplicate.svelte
0 → 100644
View file @
7674229e
<script lang="ts">
export let className = 'w-4 h-4';
export let strokeWidth = '1.5';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
/>
</svg>
src/lib/components/layout/Sidebar.svelte
View file @
7674229e
...
@@ -22,7 +22,8 @@
...
@@ -22,7 +22,8 @@
getChatListByTagName,
getChatListByTagName,
updateChatById,
updateChatById,
getAllChatTags,
getAllChatTags,
archiveChatById
archiveChatById,
cloneChatById
} from '$lib/apis/chats';
} from '$lib/apis/chats';
import { toast } from 'svelte-sonner';
import { toast } from 'svelte-sonner';
import { fade, slide } from 'svelte/transition';
import { fade, slide } from 'svelte/transition';
...
@@ -182,6 +183,18 @@
...
@@ -182,6 +183,18 @@
}
}
};
};
const cloneChatHandler = async (id) => {
const res = await cloneChatById(localStorage.token, id).catch((error) => {
toast.error(error);
return null;
});
if (res) {
goto(`/c/${res.id}`);
await chats.set(await getChatList(localStorage.token));
}
};
const saveSettings = async (updated) => {
const saveSettings = async (updated) => {
await settings.set({ ...$settings, ...updated });
await settings.set({ ...$settings, ...updated });
await updateUserSettings(localStorage.token, { ui: $settings });
await updateUserSettings(localStorage.token, { ui: $settings });
...
@@ -601,6 +614,9 @@
...
@@ -601,6 +614,9 @@
<div class="flex self-center space-x-1 z-10">
<div class="flex self-center space-x-1 z-10">
<ChatMenu
<ChatMenu
chatId={chat.id}
chatId={chat.id}
cloneChatHandler={() => {
cloneChatHandler(chat.id);
}}
shareHandler={() => {
shareHandler={() => {
shareChatId = selectedChatId;
shareChatId = selectedChatId;
showShareChatModal = true;
showShareChatModal = true;
...
...
src/lib/components/layout/Sidebar/ChatMenu.svelte
View file @
7674229e
...
@@ -10,10 +10,12 @@
...
@@ -10,10 +10,12 @@
import Tags from '$lib/components/chat/Tags.svelte';
import Tags from '$lib/components/chat/Tags.svelte';
import Share from '$lib/components/icons/Share.svelte';
import Share from '$lib/components/icons/Share.svelte';
import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte';
const i18n = getContext('i18n');
const i18n = getContext('i18n');
export let shareHandler: Function;
export let shareHandler: Function;
export let cloneChatHandler: Function;
export let archiveChatHandler: Function;
export let archiveChatHandler: Function;
export let renameHandler: Function;
export let renameHandler: Function;
export let deleteHandler: Function;
export let deleteHandler: Function;
...
@@ -38,30 +40,30 @@
...
@@ -38,30 +40,30 @@
<div slot="content">
<div slot="content">
<DropdownMenu.Content
<DropdownMenu.Content
class="w-full max-w-[1
6
0px] rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
class="w-full max-w-[1
8
0px] rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
sideOffset={-2}
sideOffset={-2}
side="bottom"
side="bottom"
align="start"
align="start"
transition={flyAndScale}
transition={flyAndScale}
>
>
<DropdownMenu.Item
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800
rounded-md"
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
on:click={() => {
on:click={() => {
shar
eHandler();
renam
eHandler();
}}
}}
>
>
<
Share
/>
<
Pencil strokeWidth="2"
/>
<div class="flex items-center">{$i18n.t('
Shar
e')}</div>
<div class="flex items-center">{$i18n.t('
Renam
e')}</div>
</DropdownMenu.Item>
</DropdownMenu.Item>
<DropdownMenu.Item
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
on:click={() => {
on:click={() => {
rename
Handler();
cloneChat
Handler();
}}
}}
>
>
<
Pencil
strokeWidth="2" />
<
DocumentDuplicate
strokeWidth="2" />
<div class="flex items-center">{$i18n.t('
Renam
e')}</div>
<div class="flex items-center">{$i18n.t('
Clon
e')}</div>
</DropdownMenu.Item>
</DropdownMenu.Item>
<DropdownMenu.Item
<DropdownMenu.Item
...
@@ -74,6 +76,16 @@
...
@@ -74,6 +76,16 @@
<div class="flex items-center">{$i18n.t('Archive')}</div>
<div class="flex items-center">{$i18n.t('Archive')}</div>
</DropdownMenu.Item>
</DropdownMenu.Item>
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
on:click={() => {
shareHandler();
}}
>
<Share />
<div class="flex items-center">{$i18n.t('Share')}</div>
</DropdownMenu.Item>
<DropdownMenu.Item
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
on:click={() => {
on:click={() => {
...
...
src/lib/components/workspace/Models/ModelMenu.svelte
View file @
7674229e
...
@@ -10,6 +10,7 @@
...
@@ -10,6 +10,7 @@
import Tags from '$lib/components/chat/Tags.svelte';
import Tags from '$lib/components/chat/Tags.svelte';
import Share from '$lib/components/icons/Share.svelte';
import Share from '$lib/components/icons/Share.svelte';
import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte';
const i18n = getContext('i18n');
const i18n = getContext('i18n');
...
@@ -60,20 +61,7 @@
...
@@ -60,20 +61,7 @@
cloneHandler();
cloneHandler();
}}
}}
>
>
<svg
<DocumentDuplicate />
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
/>
</svg>
<div class="flex items-center">{$i18n.t('Clone')}</div>
<div class="flex items-center">{$i18n.t('Clone')}</div>
</DropdownMenu.Item>
</DropdownMenu.Item>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment