forked from open-webui/open-webui
feat: add frontend support for locally sharing chats
This commit is contained in:
parent
bfbfdae1c5
commit
196f91d68c
9 changed files with 433 additions and 106 deletions
|
@ -218,6 +218,102 @@ export const getChatById = async (token: string, id: string) => {
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getChatByShareId = async (token: string, share_id: string) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/share/${share_id}`, {
|
||||||
|
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;
|
||||||
|
|
||||||
|
console.log(err);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const shareChatById = async (token: string, id: string) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/share`, {
|
||||||
|
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;
|
||||||
|
|
||||||
|
console.log(err);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteSharedChatById = async (token: string, id: string) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}`, {
|
||||||
|
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;
|
||||||
|
|
||||||
|
console.log(err);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
export const updateChatById = async (token: string, id: string, chat: object) => {
|
export const updateChatById = async (token: string, id: string, chat: object) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
export let chatId = '';
|
export let chatId = '';
|
||||||
|
export let readOnly = false;
|
||||||
export let sendPrompt: Function;
|
export let sendPrompt: Function;
|
||||||
export let continueGeneration: Function;
|
export let continueGeneration: Function;
|
||||||
export let regenerateResponse: Function;
|
export let regenerateResponse: Function;
|
||||||
|
@ -317,6 +318,7 @@
|
||||||
<UserMessage
|
<UserMessage
|
||||||
on:delete={() => messageDeleteHandler(message.id)}
|
on:delete={() => messageDeleteHandler(message.id)}
|
||||||
user={$user}
|
user={$user}
|
||||||
|
{readOnly}
|
||||||
{message}
|
{message}
|
||||||
isFirstMessage={messageIdx === 0}
|
isFirstMessage={messageIdx === 0}
|
||||||
siblings={message.parentId !== null
|
siblings={message.parentId !== null
|
||||||
|
@ -335,6 +337,7 @@
|
||||||
modelfiles={selectedModelfiles}
|
modelfiles={selectedModelfiles}
|
||||||
siblings={history.messages[message.parentId]?.childrenIds ?? []}
|
siblings={history.messages[message.parentId]?.childrenIds ?? []}
|
||||||
isLastMessage={messageIdx + 1 === messages.length}
|
isLastMessage={messageIdx + 1 === messages.length}
|
||||||
|
{readOnly}
|
||||||
{confirmEditResponseMessage}
|
{confirmEditResponseMessage}
|
||||||
{showPreviousMessage}
|
{showPreviousMessage}
|
||||||
{showNextMessage}
|
{showNextMessage}
|
||||||
|
|
|
@ -33,6 +33,8 @@
|
||||||
|
|
||||||
export let isLastMessage = true;
|
export let isLastMessage = true;
|
||||||
|
|
||||||
|
export let readOnly = false;
|
||||||
|
|
||||||
export let confirmEditResponseMessage: Function;
|
export let confirmEditResponseMessage: Function;
|
||||||
export let showPreviousMessage: Function;
|
export let showPreviousMessage: Function;
|
||||||
export let showNextMessage: Function;
|
export let showNextMessage: Function;
|
||||||
|
@ -469,31 +471,33 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Tooltip content="Edit" placement="bottom">
|
{#if !readOnly}
|
||||||
<button
|
<Tooltip content="Edit" placement="bottom">
|
||||||
class="{isLastMessage
|
<button
|
||||||
? 'visible'
|
class="{isLastMessage
|
||||||
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
|
? 'visible'
|
||||||
on:click={() => {
|
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
|
||||||
editMessageHandler();
|
on:click={() => {
|
||||||
}}
|
editMessageHandler();
|
||||||
>
|
}}
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
stroke-linecap="round"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
stroke-linejoin="round"
|
fill="none"
|
||||||
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
|
viewBox="0 0 24 24"
|
||||||
/>
|
stroke-width="2"
|
||||||
</svg>
|
stroke="currentColor"
|
||||||
</button>
|
class="w-4 h-4"
|
||||||
</Tooltip>
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<Tooltip content="Copy" placement="bottom">
|
<Tooltip content="Copy" placement="bottom">
|
||||||
<button
|
<button
|
||||||
|
@ -521,59 +525,61 @@
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip content="Good Response" placement="bottom">
|
{#if !readOnly}
|
||||||
<button
|
<Tooltip content="Good Response" placement="bottom">
|
||||||
class="{isLastMessage
|
<button
|
||||||
? 'visible'
|
class="{isLastMessage
|
||||||
: 'invisible group-hover:visible'} p-1 rounded {message.rating === 1
|
? 'visible'
|
||||||
? 'bg-gray-100 dark:bg-gray-800'
|
: 'invisible group-hover:visible'} p-1 rounded {message.rating === 1
|
||||||
: ''} dark:hover:text-white hover:text-black transition"
|
? 'bg-gray-100 dark:bg-gray-800'
|
||||||
on:click={() => {
|
: ''} dark:hover:text-white hover:text-black transition"
|
||||||
rateMessage(message.id, 1);
|
on:click={() => {
|
||||||
}}
|
rateMessage(message.id, 1);
|
||||||
>
|
}}
|
||||||
<svg
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-width="2"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
class="w-4 h-4"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
><path
|
|
||||||
d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"
|
|
||||||
/></svg
|
|
||||||
>
|
>
|
||||||
</button>
|
<svg
|
||||||
</Tooltip>
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="w-4 h-4"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
><path
|
||||||
|
d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"
|
||||||
|
/></svg
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip content="Bad Response" placement="bottom">
|
<Tooltip content="Bad Response" placement="bottom">
|
||||||
<button
|
<button
|
||||||
class="{isLastMessage
|
class="{isLastMessage
|
||||||
? 'visible'
|
? 'visible'
|
||||||
: 'invisible group-hover:visible'} p-1 rounded {message.rating === -1
|
: 'invisible group-hover:visible'} p-1 rounded {message.rating === -1
|
||||||
? 'bg-gray-100 dark:bg-gray-800'
|
? 'bg-gray-100 dark:bg-gray-800'
|
||||||
: ''} dark:hover:text-white hover:text-black transition"
|
: ''} dark:hover:text-white hover:text-black transition"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
rateMessage(message.id, -1);
|
rateMessage(message.id, -1);
|
||||||
}}
|
}}
|
||||||
>
|
|
||||||
<svg
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-width="2"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
class="w-4 h-4"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
><path
|
|
||||||
d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"
|
|
||||||
/></svg
|
|
||||||
>
|
>
|
||||||
</button>
|
<svg
|
||||||
</Tooltip>
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="w-4 h-4"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
><path
|
||||||
|
d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"
|
||||||
|
/></svg
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<Tooltip content="Read Aloud" placement="bottom">
|
<Tooltip content="Read Aloud" placement="bottom">
|
||||||
<button
|
<button
|
||||||
|
@ -656,7 +662,7 @@
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{#if $config.images}
|
{#if $config.images && !readOnly}
|
||||||
<Tooltip content="Generate Image" placement="bottom">
|
<Tooltip content="Generate Image" placement="bottom">
|
||||||
<button
|
<button
|
||||||
class="{isLastMessage
|
class="{isLastMessage
|
||||||
|
@ -752,7 +758,7 @@
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if isLastMessage}
|
{#if isLastMessage && !readOnly}
|
||||||
<Tooltip content="Continue Response" placement="bottom">
|
<Tooltip content="Continue Response" placement="bottom">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
export let message;
|
export let message;
|
||||||
export let siblings;
|
export let siblings;
|
||||||
export let isFirstMessage: boolean;
|
export let isFirstMessage: boolean;
|
||||||
|
export let readOnly: boolean;
|
||||||
|
|
||||||
export let confirmEditMessage: Function;
|
export let confirmEditMessage: Function;
|
||||||
export let showPreviousMessage: Function;
|
export let showPreviousMessage: Function;
|
||||||
|
@ -250,29 +251,31 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Tooltip content="Edit" placement="bottom">
|
{#if !readOnly}
|
||||||
<button
|
<Tooltip content="Edit" placement="bottom">
|
||||||
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button"
|
<button
|
||||||
on:click={() => {
|
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button"
|
||||||
editMessageHandler();
|
on:click={() => {
|
||||||
}}
|
editMessageHandler();
|
||||||
>
|
}}
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
stroke-linecap="round"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
stroke-linejoin="round"
|
fill="none"
|
||||||
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
|
viewBox="0 0 24 24"
|
||||||
/>
|
stroke-width="2"
|
||||||
</svg>
|
stroke="currentColor"
|
||||||
</button>
|
class="w-4 h-4"
|
||||||
</Tooltip>
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<Tooltip content="Copy" placement="bottom">
|
<Tooltip content="Copy" placement="bottom">
|
||||||
<button
|
<button
|
||||||
|
@ -298,7 +301,7 @@
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{#if !isFirstMessage}
|
{#if !isFirstMessage && !readOnly}
|
||||||
<Tooltip content="Delete" placement="bottom">
|
<Tooltip content="Delete" placement="bottom">
|
||||||
<button
|
<button
|
||||||
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
|
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
export let downloadChat: Function;
|
export let downloadChat: Function;
|
||||||
export let shareChat: Function;
|
export let shareChat: Function;
|
||||||
|
export let shareLocalChat: Function;
|
||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
</script>
|
</script>
|
||||||
|
@ -23,6 +24,17 @@
|
||||||
{$i18n.t('Share to OpenWebUI Community')}
|
{$i18n.t('Share to OpenWebUI Community')}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class=" self-center px-8 py-1.5 w-full rounded-full text-sm font-medium bg-blue-600 hover:bg-blue-500 text-white mt-1.5"
|
||||||
|
type="button"
|
||||||
|
on:click={() => {
|
||||||
|
shareLocalChat();
|
||||||
|
show = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$i18n.t('Create local share link')}
|
||||||
|
</button>
|
||||||
|
|
||||||
<div class="flex justify-center space-x-1 mt-1.5">
|
<div class="flex justify-center space-x-1 mt-1.5">
|
||||||
<div class=" self-center text-gray-400 text-xs font-medium">{$i18n.t('or')}</div>
|
<div class=" self-center text-gray-400 text-xs font-medium">{$i18n.t('or')}</div>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
const { saveAs } = fileSaver;
|
const { saveAs } = fileSaver;
|
||||||
|
|
||||||
import { Separator } from 'bits-ui';
|
import { Separator } from 'bits-ui';
|
||||||
import { getChatById } from '$lib/apis/chats';
|
import { getChatById, shareChatById } from '$lib/apis/chats';
|
||||||
import { WEBUI_NAME, chatId, modelfiles, settings, showSettings } from '$lib/stores';
|
import { WEBUI_NAME, chatId, modelfiles, settings, showSettings } from '$lib/stores';
|
||||||
|
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
|
@ -19,6 +19,7 @@
|
||||||
import ChevronUpDown from '../icons/ChevronUpDown.svelte';
|
import ChevronUpDown from '../icons/ChevronUpDown.svelte';
|
||||||
import Menu from './Navbar/Menu.svelte';
|
import Menu from './Navbar/Menu.svelte';
|
||||||
import TagChatModal from '../chat/TagChatModal.svelte';
|
import TagChatModal from '../chat/TagChatModal.svelte';
|
||||||
|
import { copyToClipboard } from '$lib/utils';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@
|
||||||
export let addTag: Function;
|
export let addTag: Function;
|
||||||
export let deleteTag: Function;
|
export let deleteTag: Function;
|
||||||
|
|
||||||
export let showModelSelector = false;
|
export let showModelSelector = true;
|
||||||
|
|
||||||
let showShareChatModal = false;
|
let showShareChatModal = false;
|
||||||
let showTagChatModal = false;
|
let showTagChatModal = false;
|
||||||
|
@ -64,6 +65,23 @@
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const shareLocalChat = async () => {
|
||||||
|
const chat = await getChatById(localStorage.token, $chatId);
|
||||||
|
console.log('shareLocal', chat);
|
||||||
|
if (chat.share_id) {
|
||||||
|
const shareUrl = `${window.location.origin}/s/${chat.share_id}`;
|
||||||
|
toast.info(
|
||||||
|
$i18n.t('Chat is already shared at {{shareUrl}}, copied to clipboard', { shareUrl })
|
||||||
|
);
|
||||||
|
copyToClipboard(shareUrl);
|
||||||
|
} else {
|
||||||
|
const sharedChat = await shareChatById(localStorage.token, $chatId);
|
||||||
|
const shareUrl = `${window.location.origin}/s/${sharedChat.id}`;
|
||||||
|
toast.info($i18n.t('Chat is now shared at {{shareUrl}}, copied to clipboard', { shareUrl }));
|
||||||
|
copyToClipboard(shareUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const downloadChat = async () => {
|
const downloadChat = async () => {
|
||||||
const chat = (await getChatById(localStorage.token, $chatId)).chat;
|
const chat = (await getChatById(localStorage.token, $chatId)).chat;
|
||||||
console.log('download', chat);
|
console.log('download', chat);
|
||||||
|
@ -80,7 +98,7 @@
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ShareChatModal bind:show={showShareChatModal} {downloadChat} {shareChat} />
|
<ShareChatModal bind:show={showShareChatModal} {downloadChat} {shareChat} {shareLocalChat} />
|
||||||
<!-- <TagChatModal bind:show={showTagChatModal} {tags} {deleteTag} {addTag} /> -->
|
<!-- <TagChatModal bind:show={showTagChatModal} {tags} {deleteTag} {addTag} /> -->
|
||||||
<nav id="nav" class=" sticky py-2.5 top-0 flex flex-row justify-center z-30">
|
<nav id="nav" class=" sticky py-2.5 top-0 flex flex-row justify-center z-30">
|
||||||
<div
|
<div
|
||||||
|
@ -135,8 +153,10 @@
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
<div class="flex items-center w-full max-w-full">
|
<div class="flex items-center w-full max-w-full">
|
||||||
<div class="w-full flex-1 overflow-hidden max-w-full">
|
<div class="flex-1 overflow-hidden max-w-full">
|
||||||
<ModelSelector bind:selectedModels />
|
{#if showModelSelector}
|
||||||
|
<ModelSelector bind:selectedModels />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="self-start flex flex-none items-center">
|
<div class="self-start flex flex-none items-center">
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
let messagesContainerElement: HTMLDivElement;
|
let messagesContainerElement: HTMLDivElement;
|
||||||
let currentRequestId = null;
|
let currentRequestId = null;
|
||||||
|
|
||||||
let showModelSelector = false;
|
let showModelSelector = true;
|
||||||
let selectedModels = [''];
|
let selectedModels = [''];
|
||||||
|
|
||||||
let selectedModelfile = null;
|
let selectedModelfile = null;
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
let currentRequestId = null;
|
let currentRequestId = null;
|
||||||
|
|
||||||
// let chatId = $page.params.id;
|
// let chatId = $page.params.id;
|
||||||
let showModelSelector = false;
|
let showModelSelector = true;
|
||||||
let selectedModels = [''];
|
let selectedModels = [''];
|
||||||
let selectedModelfile = null;
|
let selectedModelfile = null;
|
||||||
|
|
||||||
|
|
187
src/routes/(app)/s/[id]/+page.svelte
Normal file
187
src/routes/(app)/s/[id]/+page.svelte
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount, tick, getContext } from 'svelte';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
import { modelfiles, settings, chatId, WEBUI_NAME } from '$lib/stores';
|
||||||
|
import { convertMessagesToHistory } from '$lib/utils';
|
||||||
|
|
||||||
|
import { getChatByShareId } from '$lib/apis/chats';
|
||||||
|
|
||||||
|
import Messages from '$lib/components/chat/Messages.svelte';
|
||||||
|
import Navbar from '$lib/components/layout/Navbar.svelte';
|
||||||
|
|
||||||
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
|
let loaded = false;
|
||||||
|
|
||||||
|
let autoScroll = true;
|
||||||
|
let processing = '';
|
||||||
|
let messagesContainerElement: HTMLDivElement;
|
||||||
|
|
||||||
|
// let chatId = $page.params.id;
|
||||||
|
let showModelSelector = false;
|
||||||
|
let selectedModels = [''];
|
||||||
|
|
||||||
|
let selectedModelfiles = {};
|
||||||
|
$: selectedModelfiles = selectedModels.reduce((a, tagName, i, arr) => {
|
||||||
|
const modelfile =
|
||||||
|
$modelfiles.filter((modelfile) => modelfile.tagName === tagName)?.at(0) ?? undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...a,
|
||||||
|
...(modelfile && { [tagName]: modelfile })
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
let chat = null;
|
||||||
|
|
||||||
|
let title = '';
|
||||||
|
let files = [];
|
||||||
|
|
||||||
|
let messages = [];
|
||||||
|
let history = {
|
||||||
|
messages: {},
|
||||||
|
currentId: null
|
||||||
|
};
|
||||||
|
|
||||||
|
$: if (history.currentId !== null) {
|
||||||
|
let _messages = [];
|
||||||
|
|
||||||
|
let currentMessage = history.messages[history.currentId];
|
||||||
|
while (currentMessage !== null) {
|
||||||
|
_messages.unshift({ ...currentMessage });
|
||||||
|
currentMessage =
|
||||||
|
currentMessage.parentId !== null ? history.messages[currentMessage.parentId] : null;
|
||||||
|
}
|
||||||
|
messages = _messages;
|
||||||
|
} else {
|
||||||
|
messages = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if ($page.params.id) {
|
||||||
|
(async () => {
|
||||||
|
if (await loadSharedChat()) {
|
||||||
|
await tick();
|
||||||
|
loaded = true;
|
||||||
|
|
||||||
|
window.setTimeout(() => scrollToBottom(), 0);
|
||||||
|
const chatInput = document.getElementById('chat-textarea');
|
||||||
|
chatInput?.focus();
|
||||||
|
} else {
|
||||||
|
await goto('/');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
// Web functions
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
const loadSharedChat = async () => {
|
||||||
|
await chatId.set($page.params.id);
|
||||||
|
chat = await getChatByShareId(localStorage.token, $chatId).catch(async (error) => {
|
||||||
|
await goto('/');
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (chat) {
|
||||||
|
const chatContent = chat.chat;
|
||||||
|
|
||||||
|
if (chatContent) {
|
||||||
|
console.log(chatContent);
|
||||||
|
|
||||||
|
selectedModels =
|
||||||
|
(chatContent?.models ?? undefined) !== undefined
|
||||||
|
? chatContent.models
|
||||||
|
: [chatContent.models ?? ''];
|
||||||
|
history =
|
||||||
|
(chatContent?.history ?? undefined) !== undefined
|
||||||
|
? chatContent.history
|
||||||
|
: convertMessagesToHistory(chatContent.messages);
|
||||||
|
title = chatContent.title;
|
||||||
|
|
||||||
|
let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
||||||
|
await settings.set({
|
||||||
|
..._settings,
|
||||||
|
system: chatContent.system ?? _settings.system,
|
||||||
|
options: chatContent.options ?? _settings.options
|
||||||
|
});
|
||||||
|
autoScroll = true;
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
if (messages.length > 0) {
|
||||||
|
history.messages[messages.at(-1).id].done = true;
|
||||||
|
}
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
if (messagesContainerElement) {
|
||||||
|
messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
if (!($settings.saveChatHistory ?? true)) {
|
||||||
|
await goto('/');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>
|
||||||
|
{title
|
||||||
|
? `${title.length > 30 ? `${title.slice(0, 30)}...` : title} | ${$WEBUI_NAME}`
|
||||||
|
: `${$WEBUI_NAME}`}
|
||||||
|
</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
{#if loaded}
|
||||||
|
<div class="min-h-screen max-h-screen w-full flex flex-col">
|
||||||
|
<Navbar
|
||||||
|
{title}
|
||||||
|
bind:selectedModels
|
||||||
|
bind:showModelSelector
|
||||||
|
shareEnabled={false}
|
||||||
|
initNewChat={async () => {
|
||||||
|
goto('/');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div class="flex flex-col flex-auto">
|
||||||
|
<div
|
||||||
|
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
|
||||||
|
id="messages-container"
|
||||||
|
bind:this={messagesContainerElement}
|
||||||
|
on:scroll={(e) => {
|
||||||
|
autoScroll =
|
||||||
|
messagesContainerElement.scrollHeight - messagesContainerElement.scrollTop <=
|
||||||
|
messagesContainerElement.clientHeight + 5;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" h-full w-full flex flex-col py-4">
|
||||||
|
<Messages
|
||||||
|
chatId={$chatId}
|
||||||
|
readOnly={true}
|
||||||
|
{selectedModels}
|
||||||
|
{selectedModelfiles}
|
||||||
|
{processing}
|
||||||
|
bind:history
|
||||||
|
bind:messages
|
||||||
|
bind:autoScroll
|
||||||
|
bottomPadding={files.length > 0}
|
||||||
|
sendPrompt={() => {}}
|
||||||
|
continueGeneration={() => {}}
|
||||||
|
regenerateResponse={() => {}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
Loading…
Reference in a new issue