Unverified Commit 56369fea authored by Timothy Jaeryang Baek's avatar Timothy Jaeryang Baek Committed by GitHub
Browse files

Merge branch 'dev' into feature/support_auth_by_api_key

parents a0935dec 24fb7775
...@@ -78,3 +78,7 @@ select { ...@@ -78,3 +78,7 @@ select {
/* for Chrome */ /* for Chrome */
-webkit-appearance: none; -webkit-appearance: none;
} }
.katex-mathml {
display: none;
}
...@@ -218,6 +218,102 @@ export const getChatById = async (token: string, id: string) => { ...@@ -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}/share`, {
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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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;
...@@ -128,7 +130,7 @@ ...@@ -128,7 +130,7 @@
// • auto-render specific keys, e.g.: // • auto-render specific keys, e.g.:
delimiters: [ delimiters: [
{ left: '$$', right: '$$', display: false }, { left: '$$', right: '$$', display: false },
{ left: '$', right: '$', display: false }, { left: '$ ', right: ' $', display: false },
{ left: '\\(', right: '\\)', display: false }, { left: '\\(', right: '\\)', display: false },
{ left: '\\[', right: '\\]', display: false }, { left: '\\[', right: '\\]', display: false },
{ left: '[ ', right: ' ]', display: false } { left: '[ ', right: ' ]', display: false }
...@@ -422,7 +424,7 @@ ...@@ -422,7 +424,7 @@
class=" flex justify-start space-x-1 overflow-x-auto buttons text-gray-700 dark:text-gray-500" class=" flex justify-start space-x-1 overflow-x-auto buttons text-gray-700 dark:text-gray-500"
> >
{#if siblings.length > 1} {#if siblings.length > 1}
<div class="flex self-center min-w-fit -mt-1"> <div class="flex self-center min-w-fit">
<button <button
class="self-center dark:hover:text-white hover:text-black transition" class="self-center dark:hover:text-white hover:text-black transition"
on:click={() => { on:click={() => {
...@@ -469,31 +471,33 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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;
...@@ -203,7 +204,7 @@ ...@@ -203,7 +204,7 @@
<div class=" flex justify-start space-x-1 text-gray-700 dark:text-gray-500"> <div class=" flex justify-start space-x-1 text-gray-700 dark:text-gray-500">
{#if siblings.length > 1} {#if siblings.length > 1}
<div class="flex self-center -mt-1"> <div class="flex self-center">
<button <button
class="self-center dark:hover:text-white hover:text-black transition" class="self-center dark:hover:text-white hover:text-black transition"
on:click={() => { on:click={() => {
...@@ -250,29 +251,31 @@ ...@@ -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 @@ ...@@ -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"
......
...@@ -258,6 +258,9 @@ ...@@ -258,6 +258,9 @@
console.log(error); console.log(error);
} }
} }
} else {
const error = await fileResponse?.json();
toast.error(error?.detail ?? error);
} }
if (uploaded) { if (uploaded) {
......
<script lang="ts"> <script lang="ts">
import { getContext } from 'svelte'; import { getContext, onMount } from 'svelte';
import fileSaver from 'file-saver';
const { saveAs } = fileSaver;
import { toast } from 'svelte-sonner';
import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats';
import { chatId, modelfiles } from '$lib/stores';
import { copyToClipboard } from '$lib/utils';
import Modal from '../common/Modal.svelte'; import Modal from '../common/Modal.svelte';
import Link from '../icons/Link.svelte';
let chat = null;
const i18n = getContext('i18n'); const i18n = getContext('i18n');
export let downloadChat: Function; const shareLocalChat = async () => {
export let shareChat: Function; const _chat = chat;
const sharedChat = await shareChatById(localStorage.token, $chatId);
const chatShareUrl = `${window.location.origin}/s/${sharedChat.id}`;
toast.success($i18n.t('Copied shared chat URL to clipboard!'));
copyToClipboard(chatShareUrl);
chat = await getChatById(localStorage.token, $chatId);
};
const shareChat = async () => {
const _chat = chat.chat;
console.log('share', _chat);
toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
const url = 'https://openwebui.com';
// const url = 'http://localhost:5173';
const tab = await window.open(`${url}/chats/upload`, '_blank');
window.addEventListener(
'message',
(event) => {
if (event.origin !== url) return;
if (event.data === 'loaded') {
tab.postMessage(
JSON.stringify({
chat: _chat,
modelfiles: $modelfiles.filter((modelfile) =>
_chat.models.includes(modelfile.tagName)
)
}),
'*'
);
}
},
false
);
};
const downloadChat = async () => {
const _chat = chat.chat;
console.log('download', chat);
const chatText = _chat.messages.reduce((a, message, i, arr) => {
return `${a}### ${message.role.toUpperCase()}\n${message.content}\n\n`;
}, '');
let blob = new Blob([chatText], {
type: 'text/plain'
});
saveAs(blob, `chat-${_chat.title}.txt`);
};
export let show = false; export let show = false;
</script>
<Modal bind:show size="xs"> onMount(async () => {
<div class="px-4 pt-4 pb-5 w-full flex flex-col justify-center"> chatId.subscribe(async (value) => {
<button chat = await getChatById(localStorage.token, value);
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" console.log(chat);
type="button" });
on:click={() => { });
shareChat(); </script>
show = false;
}}
>
{$i18n.t('Share to OpenWebUI Community')}
</button>
<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>
<Modal bind:show size="sm">
<div>
<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
<div class=" text-lg font-medium self-center">{$i18n.t('Share Chat')}</div>
<button <button
class=" self-center rounded-full text-xs font-medium text-gray-700 dark:text-gray-500 underline" class="self-center"
type="button"
on:click={() => { on:click={() => {
downloadChat();
show = false; show = false;
}} }}
> >
{$i18n.t('Download as a File')} <svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button> </button>
</div> </div>
<hr class=" dark:border-gray-800" />
{#if chat}
<div class="px-4 pt-4 pb-5 w-full flex flex-col justify-center">
<div class=" text-sm dark:text-gray-300 mb-1">
{#if chat.share_id}
<a href="/s/{chat.share_id}" target="_blank"
>You have shared this chat <span class=" underline">before</span>.</a
>
Click here to
<button
class="underline"
on:click={async () => {
const res = await deleteSharedChatById(localStorage.token, $chatId);
if (res) {
chat = await getChatById(localStorage.token, $chatId);
}
}}>delete this link</button
> and create a new shared link.
{:else}
Messages you send after creating your link won't be shared. Users with the URL will be
able to view the shared chat.
{/if}
</div>
<div class="flex justify-end">
<div class="flex flex-col items-end space-x-1 mt-1.5">
<div class="flex gap-1">
<button
class=" self-center px-3.5 py-2 rounded-xl text-sm font-medium bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white"
type="button"
on:click={() => {
shareChat();
show = false;
}}
>
{$i18n.t('Share to OpenWebUI Community')}
</button>
<button
class=" self-center flex items-center gap-1 px-3.5 py-2 rounded-xl text-sm font-medium bg-emerald-600 hover:bg-emerald-500 text-white"
type="button"
on:click={() => {
shareLocalChat();
show = false;
}}
>
<Link />
{#if chat.share_id}
{$i18n.t('Update and Copy Link')}
{:else}
{$i18n.t('Copy Link')}
{/if}
</button>
</div>
<div class="flex gap-1 mt-1.5">
<div class=" self-center text-gray-400 text-xs font-medium">{$i18n.t('or')}</div>
<button
class=" text-right rounded-full text-xs font-medium text-gray-700 dark:text-gray-500 underline"
type="button"
on:click={() => {
downloadChat();
show = false;
}}
>
{$i18n.t('Download as a File')}
</button>
</div>
</div>
</div>
</div>
{/if}
</div> </div>
</Modal> </Modal>
<script lang="ts"> <script lang="ts">
export let className: string = 'text-white'; export let className: string = '';
export let theme: 'blue' | 'white' | 'black' = 'white';
</script> </script>
<div class="flex justify-center text-center {className}"> <div class="flex justify-center text-center {className}">
<svg <svg class="size-5" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"
class="animate-spin -ml-1 mr-3 h-5 w-5 {theme === 'blue' ><style>
? 'text-sky-600' .spinner_ajPY {
: theme === 'white' transform-origin: center;
? 'text-white' animation: spinner_AtaB 0.75s infinite linear;
: 'text-gray-600'} " }
xmlns="http://www.w3.org/2000/svg" @keyframes spinner_AtaB {
fill="none" 100% {
viewBox="0 0 24 24" transform: rotate(360deg);
}
}
</style><path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/><path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/></svg
> >
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</div> </div>
<script lang="ts">
export let className = 'w-4 h-4';
</script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class={className}>
<path
fill-rule="evenodd"
d="M8.914 6.025a.75.75 0 0 1 1.06 0 3.5 3.5 0 0 1 0 4.95l-2 2a3.5 3.5 0 0 1-5.396-4.402.75.75 0 0 1 1.251.827 2 2 0 0 0 3.085 2.514l2-2a2 2 0 0 0 0-2.828.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
<path
fill-rule="evenodd"
d="M7.086 9.975a.75.75 0 0 1-1.06 0 3.5 3.5 0 0 1 0-4.95l2-2a3.5 3.5 0 0 1 5.396 4.402.75.75 0 0 1-1.251-.827 2 2 0 0 0-3.085-2.514l-2 2a2 2 0 0 0 0 2.828.75.75 0 0 1 0 1.06Z"
clip-rule="evenodd"
/>
</svg>
<script lang="ts"> <script lang="ts">
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import fileSaver from 'file-saver';
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';
...@@ -32,55 +30,13 @@ ...@@ -32,55 +30,13 @@
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;
const shareChat = async () => {
const chat = (await getChatById(localStorage.token, $chatId)).chat;
console.log('share', chat);
toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
const url = 'https://openwebui.com';
// const url = 'http://localhost:5173';
const tab = await window.open(`${url}/chats/upload`, '_blank');
window.addEventListener(
'message',
(event) => {
if (event.origin !== url) return;
if (event.data === 'loaded') {
tab.postMessage(
JSON.stringify({
chat: chat,
modelfiles: $modelfiles.filter((modelfile) => chat.models.includes(modelfile.tagName))
}),
'*'
);
}
},
false
);
};
const downloadChat = async () => {
const chat = (await getChatById(localStorage.token, $chatId)).chat;
console.log('download', chat);
const chatText = chat.messages.reduce((a, message, i, arr) => {
return `${a}### ${message.role.toUpperCase()}\n${message.content}\n\n`;
}, '');
let blob = new Blob([chatText], {
type: 'text/plain'
});
saveAs(blob, `chat-${chat.title}.txt`);
};
</script> </script>
<ShareChatModal bind:show={showShareChatModal} {downloadChat} {shareChat} /> <ShareChatModal bind:show={showShareChatModal} />
<!-- <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,12 +91,14 @@ ...@@ -135,12 +91,14 @@
</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">
<div class="flex self-center w-[1px] h-5 mx-2 bg-stone-700" /> <div class="flex self-center w-[1px] h-5 mx-2 bg-gray-300 dark:bg-stone-700" />
{#if !shareEnabled} {#if !shareEnabled}
<Tooltip content="Settings"> <Tooltip content="Settings">
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
"(latest)": "(nieuwste)", "(latest)": "(nieuwste)",
"{{modelName}} is thinking...": "{{modelName}} is aan het denken...", "{{modelName}} is thinking...": "{{modelName}} is aan het denken...",
"{{webUIName}} Backend Required": "{{webUIName}} Backend Verlpicht", "{{webUIName}} Backend Required": "{{webUIName}} Backend Verlpicht",
"a user": "", "a user": "een gebruiker",
"About": "Over", "About": "Over",
"Account": "Account", "Account": "Account",
"Action": "Actie", "Action": "Actie",
...@@ -35,12 +35,12 @@ ...@@ -35,12 +35,12 @@
"API Key": "API Key", "API Key": "API Key",
"API RPM": "API RPM", "API RPM": "API RPM",
"are allowed - Activate this command by typing": "zijn toegestaan - Activeer deze commando door te typen", "are allowed - Activate this command by typing": "zijn toegestaan - Activeer deze commando door te typen",
"Are you sure?": "", "Are you sure?": "Zeker weten?",
"Audio": "Audio", "Audio": "Audio",
"Auto-playback response": "Automatisch afspelen van antwoord", "Auto-playback response": "Automatisch afspelen van antwoord",
"Auto-send input after 3 sec.": "Automatisch verzenden van input na 3 sec.", "Auto-send input after 3 sec.": "Automatisch verzenden van input na 3 sec.",
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Base URL", "AUTOMATIC1111 Base URL": "AUTOMATIC1111 Base URL",
"AUTOMATIC1111 Base URL is required.": "", "AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Basis URL is verplicht",
"available!": "beschikbaar!", "available!": "beschikbaar!",
"Back": "Terug", "Back": "Terug",
"Builder Mode": "Bouwer Modus", "Builder Mode": "Bouwer Modus",
...@@ -60,8 +60,8 @@ ...@@ -60,8 +60,8 @@
"Chunk Size": "Chunk Grootte", "Chunk Size": "Chunk Grootte",
"Click here for help.": "Klik hier voor help.", "Click here for help.": "Klik hier voor help.",
"Click here to check other modelfiles.": "Klik hier om andere modelfiles te controleren.", "Click here to check other modelfiles.": "Klik hier om andere modelfiles te controleren.",
"Click here to select": "", "Click here to select": "Klik hier om te selecteren",
"Click here to select documents.": "", "Click here to select documents.": "Klik hier om documenten te selecteren",
"click here.": "click here.", "click here.": "click here.",
"Click on the user role button to change a user's role.": "Klik op de gebruikersrol knop om de rol van een gebruiker te wijzigen.", "Click on the user role button to change a user's role.": "Klik op de gebruikersrol knop om de rol van een gebruiker te wijzigen.",
"Close": "Sluiten", "Close": "Sluiten",
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
"Database": "Database", "Database": "Database",
"DD/MM/YYYY HH:mm": "YYYY/MM/DD HH:mm", "DD/MM/YYYY HH:mm": "YYYY/MM/DD HH:mm",
"Default": "Standaard", "Default": "Standaard",
"Default (Automatic1111)": "", "Default (Automatic1111)": "Standaard (Automatic1111)",
"Default (Web API)": "Standaard (Web API)", "Default (Web API)": "Standaard (Web API)",
"Default model updated": "Standaard model bijgewerkt", "Default model updated": "Standaard model bijgewerkt",
"Default Prompt Suggestions": "Standaard Prompt Suggesties", "Default Prompt Suggestions": "Standaard Prompt Suggesties",
...@@ -123,21 +123,21 @@ ...@@ -123,21 +123,21 @@
"Enable Chat History": "Schakel Chat Geschiedenis in", "Enable Chat History": "Schakel Chat Geschiedenis in",
"Enable New Sign Ups": "Schakel Nieuwe Registraties in", "Enable New Sign Ups": "Schakel Nieuwe Registraties in",
"Enabled": "Ingeschakeld", "Enabled": "Ingeschakeld",
"Enter {{role}} message here": "", "Enter {{role}} message here": "Voeg {{role}} bericht hier toe",
"Enter API Key": "", "Enter API Key": "Voeg API Key toe",
"Enter Chunk Overlap": "", "Enter Chunk Overlap": "Voeg Chunk Overlap toe",
"Enter Chunk Size": "", "Enter Chunk Size": "Voeg Chunk Size toe",
"Enter Image Size (e.g. 512x512)": "", "Enter Image Size (e.g. 512x512)": "Voeg afbeelding formaat toe (Bijv. 512x512)",
"Enter LiteLLM API Base URL (litellm_params.api_base)": "", "Enter LiteLLM API Base URL (litellm_params.api_base)": "Voeg LiteLLM API Base URL toe (litellm_params.api_base)",
"Enter LiteLLM API Key (litellm_params.api_key)": "", "Enter LiteLLM API Key (litellm_params.api_key)": "Voeg LiteLLM API Sleutel toe (litellm_params.api_key)",
"Enter LiteLLM API RPM (litellm_params.rpm)": "", "Enter LiteLLM API RPM (litellm_params.rpm)": "Voeg LiteLLM API RPM toe (litellm_params.rpm)",
"Enter LiteLLM Model (litellm_params.model)": "", "Enter LiteLLM Model (litellm_params.model)": "Voeg LiteLLM Model toe (litellm_params.model)",
"Enter Max Tokens (litellm_params.max_tokens)": "", "Enter Max Tokens (litellm_params.max_tokens)": "Voeg maximum aantal tokens toe (litellm_params.max_tokens)",
"Enter model tag (e.g. {{modelTag}})": "", "Enter model tag (e.g. {{modelTag}})": "Voeg model tag toe (Bijv. {{modelTag}})",
"Enter Number of Steps (e.g. 50)": "", "Enter Number of Steps (e.g. 50)": "Voeg aantal stappen toe (Bijv. 50)",
"Enter stop sequence": "Zet stop sequentie", "Enter stop sequence": "Zet stop sequentie",
"Enter Top K": "", "Enter Top K": "Voeg Top K toe",
"Enter URL (e.g. http://127.0.0.1:7860/)": "", "Enter URL (e.g. http://127.0.0.1:7860/)": "Zet URL (Bijv. http://127.0.0.1:7860/)",
"Enter Your Email": "Voer je Email in", "Enter Your Email": "Voer je Email in",
"Enter Your Full Name": "Voer je Volledige Naam in", "Enter Your Full Name": "Voer je Volledige Naam in",
"Enter Your Password": "Voer je Wachtwoord in", "Enter Your Password": "Voer je Wachtwoord in",
...@@ -161,7 +161,7 @@ ...@@ -161,7 +161,7 @@
"Hide Additional Params": "Verberg Extra Params", "Hide Additional Params": "Verberg Extra Params",
"How can I help you today?": "Hoe kan ik je vandaag helpen?", "How can I help you today?": "Hoe kan ik je vandaag helpen?",
"Image Generation (Experimental)": "Afbeelding Generatie (Experimenteel)", "Image Generation (Experimental)": "Afbeelding Generatie (Experimenteel)",
"Image Generation Engine": "", "Image Generation Engine": "Afbeelding Generatie Engine",
"Image Settings": "Afbeelding Instellingen", "Image Settings": "Afbeelding Instellingen",
"Images": "Afbeeldingen", "Images": "Afbeeldingen",
"Import Chats": "Importeer Chats", "Import Chats": "Importeer Chats",
...@@ -217,7 +217,7 @@ ...@@ -217,7 +217,7 @@
"Not sure what to write? Switch to": "Niet zeker wat te schrijven? Schakel over naar", "Not sure what to write? Switch to": "Niet zeker wat te schrijven? Schakel over naar",
"Off": "Uit", "Off": "Uit",
"Okay, Let's Go!": "Okay, Laten we gaan!", "Okay, Let's Go!": "Okay, Laten we gaan!",
"Ollama Base URL": "", "Ollama Base URL": "Ollama Basis URL",
"Ollama Version": "Ollama Versie", "Ollama Version": "Ollama Versie",
"On": "Aan", "On": "Aan",
"Only": "Alleen", "Only": "Alleen",
...@@ -227,11 +227,11 @@ ...@@ -227,11 +227,11 @@
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! Je gebruikt een niet-ondersteunde methode (alleen frontend). Serveer de WebUI vanuit de backend.", "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! Je gebruikt een niet-ondersteunde methode (alleen frontend). Serveer de WebUI vanuit de backend.",
"Open": "Open", "Open": "Open",
"Open AI": "Open AI", "Open AI": "Open AI",
"Open AI (Dall-E)": "", "Open AI (Dall-E)": "Open AI (Dall-E)",
"Open new chat": "Open nieuwe chat", "Open new chat": "Open nieuwe chat",
"OpenAI API": "OpenAI API", "OpenAI API": "OpenAI API",
"OpenAI API Key": "", "OpenAI API Key": "OpenAI API Sleutel",
"OpenAI API Key is required.": "", "OpenAI API Key is required.": "OpenAI API Sleutel is verplicht",
"or": "of", "or": "of",
"Parameters": "Parameters", "Parameters": "Parameters",
"Password": "Wachtwoord", "Password": "Wachtwoord",
...@@ -273,9 +273,9 @@ ...@@ -273,9 +273,9 @@
"See readme.md for instructions": "Zie readme.md voor instructies", "See readme.md for instructions": "Zie readme.md voor instructies",
"See what's new": "Zie wat er nieuw is", "See what's new": "Zie wat er nieuw is",
"Seed": "Seed", "Seed": "Seed",
"Select a mode": "", "Select a mode": "Selecteer een modus",
"Select a model": "Selecteer een model", "Select a model": "Selecteer een model",
"Select an Ollama instance": "", "Select an Ollama instance": "Selecteer een Ollama instantie",
"Send a Message": "Stuur een Bericht", "Send a Message": "Stuur een Bericht",
"Send message": "Stuur bericht", "Send message": "Stuur bericht",
"Server connection verified": "Server verbinding geverifieerd", "Server connection verified": "Server verbinding geverifieerd",
...@@ -330,7 +330,7 @@ ...@@ -330,7 +330,7 @@
"Top P": "Top P", "Top P": "Top P",
"Trouble accessing Ollama?": "Problemen met toegang tot Ollama?", "Trouble accessing Ollama?": "Problemen met toegang tot Ollama?",
"TTS Settings": "TTS instellingen", "TTS Settings": "TTS instellingen",
"Type Hugging Face Resolve (Download) URL": "", "Type Hugging Face Resolve (Download) URL": "Type Hugging Face Resolve (Download) URL",
"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! Er was een probleem met verbinden met {{provider}}.", "Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! Er was een probleem met verbinden met {{provider}}.",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Onbekend Bestandstype '{{file_type}}', maar accepteren en behandelen als platte tekst", "Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Onbekend Bestandstype '{{file_type}}', maar accepteren en behandelen als platte tekst",
"Update password": "Wijzig wachtwoord", "Update password": "Wijzig wachtwoord",
...@@ -339,7 +339,7 @@ ...@@ -339,7 +339,7 @@
"Upload Progress": "Upload Voortgang", "Upload Progress": "Upload Voortgang",
"URL Mode": "URL Modus", "URL Mode": "URL Modus",
"Use '#' in the prompt input to load and select your documents.": "Gebruik '#' in de prompt input om je documenten te laden en te selecteren.", "Use '#' in the prompt input to load and select your documents.": "Gebruik '#' in de prompt input om je documenten te laden en te selecteren.",
"Use Gravatar": "", "Use Gravatar": "Gebruik Gravatar",
"user": "user", "user": "user",
"User Permissions": "Gebruikers Rechten", "User Permissions": "Gebruikers Rechten",
"Users": "Gebruikers", "Users": "Gebruikers",
......
...@@ -48,7 +48,7 @@ ...@@ -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;
...@@ -520,11 +520,6 @@ ...@@ -520,11 +520,6 @@
const sendPromptOpenAI = async (model, userPrompt, responseMessageId, _chatId) => { const sendPromptOpenAI = async (model, userPrompt, responseMessageId, _chatId) => {
const responseMessage = history.messages[responseMessageId]; const responseMessage = history.messages[responseMessageId];
// Wait until history/message have been updated
await tick();
scrollToBottom();
const docs = messages const docs = messages
.filter((message) => message?.files ?? null) .filter((message) => message?.files ?? null)
.map((message) => .map((message) =>
...@@ -593,6 +588,11 @@ ...@@ -593,6 +588,11 @@
: `${OPENAI_API_BASE_URL}` : `${OPENAI_API_BASE_URL}`
); );
// Wait until history/message have been updated
await tick();
scrollToBottom();
if (res && res.ok) { if (res && res.ok) {
const reader = res.body const reader = res.body
.pipeThrough(new TextDecoderStream()) .pipeThrough(new TextDecoderStream())
......
...@@ -56,7 +56,7 @@ ...@@ -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;
...@@ -536,11 +536,6 @@ ...@@ -536,11 +536,6 @@
const sendPromptOpenAI = async (model, userPrompt, responseMessageId, _chatId) => { const sendPromptOpenAI = async (model, userPrompt, responseMessageId, _chatId) => {
const responseMessage = history.messages[responseMessageId]; const responseMessage = history.messages[responseMessageId];
// Wait until history/message have been updated
await tick();
scrollToBottom();
const docs = messages const docs = messages
.filter((message) => message?.files ?? null) .filter((message) => message?.files ?? null)
.map((message) => .map((message) =>
...@@ -607,6 +602,11 @@ ...@@ -607,6 +602,11 @@
: `${OPENAI_API_BASE_URL}` : `${OPENAI_API_BASE_URL}`
); );
// Wait until history/message have been updated
await tick();
scrollToBottom();
if (res && res.ok) { if (res && res.ok) {
const reader = res.body const reader = res.body
.pipeThrough(new TextDecoderStream()) .pipeThrough(new TextDecoderStream())
......
<script> <script>
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { userSignIn, userSignUp, getApiKey } from '$lib/apis/auths'; import { userSignIn, userSignUp, getApiKey } from '$lib/apis/auths';
import Spinner from '$lib/components/common/Spinner.svelte';
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants'; import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
import { WEBUI_NAME, config, user } from '$lib/stores'; import { WEBUI_NAME, config, user } from '$lib/stores';
import { onMount, getContext } from 'svelte'; import { onMount, getContext } from 'svelte';
...@@ -68,6 +69,9 @@ ...@@ -68,6 +69,9 @@
await goto('/'); await goto('/');
} }
loaded = true; loaded = true;
if ($config?.trusted_header_auth ?? false) {
await signInHandler();
}
}); });
</script> </script>
...@@ -102,100 +106,118 @@ ...@@ -102,100 +106,118 @@
</div> --> </div> -->
<div class="w-full sm:max-w-lg px-4 min-h-screen flex flex-col"> <div class="w-full sm:max-w-lg px-4 min-h-screen flex flex-col">
<div class=" my-auto pb-10 w-full"> {#if $config?.trusted_header_auth ?? false}
<form <div class=" my-auto pb-10 w-full">
class=" flex flex-col justify-center bg-white py-6 sm:py-16 px-6 sm:px-16 rounded-2xl" <div
on:submit|preventDefault={() => { class="flex items-center justify-center gap-3 text-xl sm:text-2xl text-center font-bold dark:text-gray-200"
submitHandler(); >
}} <div>
> {$i18n.t('Signing in')}
<div class=" text-xl sm:text-2xl font-bold"> {$i18n.t('to')}
{mode === 'signin' ? $i18n.t('Sign in') : $i18n.t('Sign up')} {$WEBUI_NAME}
{$i18n.t('to')} </div>
{$WEBUI_NAME}
</div>
{#if mode === 'signup'} <div>
<div class=" mt-1 text-xs font-medium text-gray-500"> <Spinner />
ⓘ {$WEBUI_NAME} </div>
{$i18n.t( </div>
'does not make any external connections, and your data stays securely on your locally hosted server.' </div>
)} {:else}
<div class=" my-auto pb-10 w-full">
<form
class=" flex flex-col justify-center bg-white py-6 sm:py-16 px-6 sm:px-16 rounded-2xl"
on:submit|preventDefault={() => {
submitHandler();
}}
>
<div class=" text-xl sm:text-2xl font-bold">
{mode === 'signin' ? $i18n.t('Sign in') : $i18n.t('Sign up')}
{$i18n.t('to')}
{$WEBUI_NAME}
</div> </div>
{/if}
<div class="flex flex-col mt-4">
{#if mode === 'signup'} {#if mode === 'signup'}
<div> <div class=" mt-1 text-xs font-medium text-gray-500">
<div class=" text-sm font-semibold text-left mb-1">{$i18n.t('Name')}</div> ⓘ {$WEBUI_NAME}
{$i18n.t(
'does not make any external connections, and your data stays securely on your locally hosted server.'
)}
</div>
{/if}
<div class="flex flex-col mt-4">
{#if mode === 'signup'}
<div>
<div class=" text-sm font-semibold text-left mb-1">{$i18n.t('Name')}</div>
<input
bind:value={name}
type="text"
class=" border px-4 py-2.5 rounded-2xl w-full text-sm"
autocomplete="name"
placeholder={$i18n.t('Enter Your Full Name')}
required
/>
</div>
<hr class=" my-3" />
{/if}
<div class="mb-2">
<div class=" text-sm font-semibold text-left mb-1">{$i18n.t('Email')}</div>
<input <input
bind:value={name} bind:value={email}
type="text" type="email"
class=" border px-4 py-2.5 rounded-2xl w-full text-sm" class=" border px-4 py-2.5 rounded-2xl w-full text-sm"
autocomplete="name" autocomplete="email"
placeholder={$i18n.t('Enter Your Full Name')} placeholder={$i18n.t('Enter Your Email')}
required required
/> />
</div> </div>
<hr class=" my-3" /> <div>
{/if} <div class=" text-sm font-semibold text-left mb-1">{$i18n.t('Password')}</div>
<input
<div class="mb-2"> bind:value={password}
<div class=" text-sm font-semibold text-left mb-1">{$i18n.t('Email')}</div> type="password"
<input class=" border px-4 py-2.5 rounded-2xl w-full text-sm"
bind:value={email} placeholder={$i18n.t('Enter Your Password')}
type="email" autocomplete="current-password"
class=" border px-4 py-2.5 rounded-2xl w-full text-sm" required
autocomplete="email" />
placeholder={$i18n.t('Enter Your Email')} </div>
required
/>
</div>
<div>
<div class=" text-sm font-semibold text-left mb-1">{$i18n.t('Password')}</div>
<input
bind:value={password}
type="password"
class=" border px-4 py-2.5 rounded-2xl w-full text-sm"
placeholder={$i18n.t('Enter Your Password')}
autocomplete="current-password"
required
/>
</div> </div>
</div>
<div class="mt-5">
<button
class=" bg-gray-900 hover:bg-gray-800 w-full rounded-full text-white font-semibold text-sm py-3 transition"
type="submit"
>
{mode === 'signin' ? $i18n.t('Sign in') : $i18n.t('Create Account')}
</button>
<div class=" mt-4 text-sm text-center">
{mode === 'signin'
? $i18n.t("Don't have an account?")
: $i18n.t('Already have an account?')}
<div class="mt-5">
<button <button
class=" font-medium underline" class=" bg-gray-900 hover:bg-gray-800 w-full rounded-full text-white font-semibold text-sm py-3 transition"
type="button" type="submit"
on:click={() => {
if (mode === 'signin') {
mode = 'signup';
} else {
mode = 'signin';
}
}}
> >
{mode === 'signin' ? $i18n.t('Sign up') : $i18n.t('Sign in')} {mode === 'signin' ? $i18n.t('Sign in') : $i18n.t('Create Account')}
</button> </button>
<div class=" mt-4 text-sm text-center">
{mode === 'signin'
? $i18n.t("Don't have an account?")
: $i18n.t('Already have an account?')}
<button
class=" font-medium underline"
type="button"
on:click={() => {
if (mode === 'signin') {
mode = 'signup';
} else {
mode = 'signin';
}
}}
>
{mode === 'signin' ? $i18n.t('Sign up') : $i18n.t('Sign in')}
</button>
</div>
</div> </div>
</div> </form>
</form> </div>
</div> {/if}
</div> </div>
</div> </div>
{/if} {/if}
......
<script lang="ts">
import { onMount, tick, getContext } from 'svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import dayjs from 'dayjs';
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;
}
}
};
</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 text-gray-700 dark:text-gray-100 bg-white dark:bg-gray-900"
>
<div class="flex flex-col flex-auto justify-center py-8">
<div class="px-3 w-full max-w-3xl mx-auto">
<div>
<div class=" text-3xl font-semibold line-clamp-1">
{title}
</div>
<div class=" mt-1 text-gray-400">
{dayjs(chat.chat.timestamp).format('MMMM D, YYYY')}
</div>
</div>
<hr class=" dark:border-gray-800 mt-6 mb-2" />
</div>
<div
class=" flex flex-col justify-center w-full flex-auto overflow-auto h-0"
id="messages-container"
>
<div class=" h-full w-full flex flex-col py-4">
<div class="py-2">
<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>
</div>
{/if}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment