forked from open-webui/open-webui
Merge branch 'main' into litellm
This commit is contained in:
commit
ee22e641ff
39 changed files with 887 additions and 166 deletions
|
@ -222,6 +222,34 @@
|
|||
scrollToBottom();
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// TODO: change delete behaviour
|
||||
// const deleteMessageAndDescendants = async (messageId: string) => {
|
||||
// if (history.messages[messageId]) {
|
||||
// history.messages[messageId].deleted = true;
|
||||
|
||||
// for (const childId of history.messages[messageId].childrenIds) {
|
||||
// await deleteMessageAndDescendants(childId);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
// const triggerDeleteMessageRecursive = async (messageId: string) => {
|
||||
// await deleteMessageAndDescendants(messageId);
|
||||
// await updateChatById(localStorage.token, chatId, { history });
|
||||
// await chats.set(await getChatList(localStorage.token));
|
||||
// };
|
||||
|
||||
const messageDeleteHandler = async (messageId) => {
|
||||
if (history.messages[messageId]) {
|
||||
history.messages[messageId].deleted = true;
|
||||
|
||||
for (const childId of history.messages[messageId].childrenIds) {
|
||||
history.messages[childId].deleted = true;
|
||||
}
|
||||
}
|
||||
await updateChatById(localStorage.token, chatId, { history });
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if messages.length == 0}
|
||||
|
@ -230,99 +258,103 @@
|
|||
<div class=" pb-10">
|
||||
{#key chatId}
|
||||
{#each messages as message, messageIdx}
|
||||
<div class=" w-full">
|
||||
<div
|
||||
class="flex flex-col justify-between px-5 mb-3 {$settings?.fullScreenMode ?? null
|
||||
? 'max-w-full'
|
||||
: 'max-w-3xl'} mx-auto rounded-lg group"
|
||||
>
|
||||
{#if message.role === 'user'}
|
||||
<UserMessage
|
||||
user={$user}
|
||||
{message}
|
||||
siblings={message.parentId !== null
|
||||
? history.messages[message.parentId]?.childrenIds ?? []
|
||||
: Object.values(history.messages)
|
||||
.filter((message) => message.parentId === null)
|
||||
.map((message) => message.id) ?? []}
|
||||
{confirmEditMessage}
|
||||
{showPreviousMessage}
|
||||
{showNextMessage}
|
||||
{copyToClipboard}
|
||||
/>
|
||||
{#if !message.deleted}
|
||||
<div class=" w-full">
|
||||
<div
|
||||
class="flex flex-col justify-between px-5 mb-3 {$settings?.fullScreenMode ?? null
|
||||
? 'max-w-full'
|
||||
: 'max-w-3xl'} mx-auto rounded-lg group"
|
||||
>
|
||||
{#if message.role === 'user'}
|
||||
<UserMessage
|
||||
on:delete={() => messageDeleteHandler(message.id)}
|
||||
user={$user}
|
||||
{message}
|
||||
isFirstMessage={messageIdx === 0}
|
||||
siblings={message.parentId !== null
|
||||
? history.messages[message.parentId]?.childrenIds ?? []
|
||||
: Object.values(history.messages)
|
||||
.filter((message) => message.parentId === null)
|
||||
.map((message) => message.id) ?? []}
|
||||
{confirmEditMessage}
|
||||
{showPreviousMessage}
|
||||
{showNextMessage}
|
||||
{copyToClipboard}
|
||||
/>
|
||||
|
||||
{#if messages.length - 1 === messageIdx && processing !== ''}
|
||||
<div class="flex my-2.5 ml-12 items-center w-fit space-x-2.5">
|
||||
<div class=" dark:text-blue-100">
|
||||
<svg
|
||||
class=" w-4 h-4 translate-y-[0.5px]"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><style>
|
||||
.spinner_qM83 {
|
||||
animation: spinner_8HQG 1.05s infinite;
|
||||
}
|
||||
.spinner_oXPr {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.spinner_ZTLf {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
@keyframes spinner_8HQG {
|
||||
0%,
|
||||
57.14% {
|
||||
animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
|
||||
transform: translate(0);
|
||||
{#if messages.length - 1 === messageIdx && processing !== ''}
|
||||
<div class="flex my-2.5 ml-12 items-center w-fit space-x-2.5">
|
||||
<div class=" dark:text-blue-100">
|
||||
<svg
|
||||
class=" w-4 h-4 translate-y-[0.5px]"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><style>
|
||||
.spinner_qM83 {
|
||||
animation: spinner_8HQG 1.05s infinite;
|
||||
}
|
||||
28.57% {
|
||||
animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
|
||||
transform: translateY(-6px);
|
||||
.spinner_oXPr {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
100% {
|
||||
transform: translate(0);
|
||||
.spinner_ZTLf {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
}
|
||||
</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
|
||||
class="spinner_qM83 spinner_oXPr"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="2.5"
|
||||
/><circle class="spinner_qM83 spinner_ZTLf" cx="20" cy="12" r="2.5" /></svg
|
||||
>
|
||||
@keyframes spinner_8HQG {
|
||||
0%,
|
||||
57.14% {
|
||||
animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
|
||||
transform: translate(0);
|
||||
}
|
||||
28.57% {
|
||||
animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
|
||||
transform: translateY(-6px);
|
||||
}
|
||||
100% {
|
||||
transform: translate(0);
|
||||
}
|
||||
}
|
||||
</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
|
||||
class="spinner_qM83 spinner_oXPr"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="2.5"
|
||||
/><circle class="spinner_qM83 spinner_ZTLf" cx="20" cy="12" r="2.5" /></svg
|
||||
>
|
||||
</div>
|
||||
<div class=" text-sm font-medium">
|
||||
{processing}
|
||||
</div>
|
||||
</div>
|
||||
<div class=" text-sm font-medium">
|
||||
{processing}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<ResponseMessage
|
||||
{message}
|
||||
modelfiles={selectedModelfiles}
|
||||
siblings={history.messages[message.parentId]?.childrenIds ?? []}
|
||||
isLastMessage={messageIdx + 1 === messages.length}
|
||||
{confirmEditResponseMessage}
|
||||
{showPreviousMessage}
|
||||
{showNextMessage}
|
||||
{rateMessage}
|
||||
{copyToClipboard}
|
||||
{continueGeneration}
|
||||
{regenerateResponse}
|
||||
on:save={async (e) => {
|
||||
console.log('save', e);
|
||||
|
||||
const message = e.detail;
|
||||
history.messages[message.id] = message;
|
||||
await updateChatById(localStorage.token, chatId, {
|
||||
messages: messages,
|
||||
history: history
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<ResponseMessage
|
||||
{message}
|
||||
modelfiles={selectedModelfiles}
|
||||
siblings={history.messages[message.parentId]?.childrenIds ?? []}
|
||||
isLastMessage={messageIdx + 1 === messages.length}
|
||||
{confirmEditResponseMessage}
|
||||
{showPreviousMessage}
|
||||
{showNextMessage}
|
||||
{rateMessage}
|
||||
{copyToClipboard}
|
||||
{continueGeneration}
|
||||
{regenerateResponse}
|
||||
on:save={async (e) => {
|
||||
console.log('save', e);
|
||||
|
||||
const message = e.detail;
|
||||
history.messages[message.id] = message;
|
||||
await updateChatById(localStorage.token, chatId, {
|
||||
messages: messages,
|
||||
history: history
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
{#if bottomPadding}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let models = [];
|
||||
|
@ -27,14 +28,16 @@
|
|||
>
|
||||
{#if model in modelfiles}
|
||||
<img
|
||||
src={modelfiles[model]?.imageUrl ?? './favicon.png'}
|
||||
src={modelfiles[model]?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png`}
|
||||
alt="modelfile"
|
||||
class=" w-14 rounded-full border-[1px] border-gray-200 dark:border-none"
|
||||
draggable="false"
|
||||
/>
|
||||
{:else}
|
||||
<img
|
||||
src={models.length === 1 ? '/favicon.png' : '/favicon.png'}
|
||||
src={models.length === 1
|
||||
? `${WEBUI_BASE_URL}/static/favicon.png`
|
||||
: `${WEBUI_BASE_URL}/static/favicon.png`}
|
||||
class=" w-14 rounded-full border-[1px] border-gray-200 dark:border-none"
|
||||
alt="logo"
|
||||
draggable="false"
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
import ProfileImage from './ProfileImage.svelte';
|
||||
import Skeleton from './Skeleton.svelte';
|
||||
import CodeBlock from './CodeBlock.svelte';
|
||||
import Image from '$lib/components/common/Image.svelte';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
|
||||
export let modelfiles = [];
|
||||
export let message;
|
||||
|
@ -46,7 +48,6 @@
|
|||
let speakingIdx = null;
|
||||
|
||||
let loadingSpeech = false;
|
||||
|
||||
let generatingImage = false;
|
||||
|
||||
$: tokens = marked.lexer(message.content);
|
||||
|
@ -298,7 +299,9 @@
|
|||
|
||||
{#key message.id}
|
||||
<div class=" flex w-full message-{message.id}">
|
||||
<ProfileImage src={modelfiles[message.model]?.imageUrl ?? '/favicon.png'} />
|
||||
<ProfileImage
|
||||
src={modelfiles[message.model]?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png`}
|
||||
/>
|
||||
|
||||
<div class="w-full overflow-hidden">
|
||||
<Name>
|
||||
|
@ -323,7 +326,7 @@
|
|||
{#each message.files as file}
|
||||
<div>
|
||||
{#if file.type === 'image'}
|
||||
<img src={file.url} alt="input" class=" max-h-96 rounded-lg" draggable="false" />
|
||||
<Image src={file.url} />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
<script lang="ts">
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { tick } from 'svelte';
|
||||
import { tick, createEventDispatcher } from 'svelte';
|
||||
import Name from './Name.svelte';
|
||||
import ProfileImage from './ProfileImage.svelte';
|
||||
import { modelfiles, settings } from '$lib/stores';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let user;
|
||||
export let message;
|
||||
export let siblings;
|
||||
export let isFirstMessage: boolean;
|
||||
|
||||
export let confirmEditMessage: Function;
|
||||
export let showPreviousMessage: Function;
|
||||
|
@ -42,6 +45,10 @@
|
|||
edit = false;
|
||||
editedContent = '';
|
||||
};
|
||||
|
||||
const deleteMessageHandler = async () => {
|
||||
dispatch('delete', message.id);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class=" flex w-full">
|
||||
|
@ -189,11 +196,11 @@
|
|||
<div class="w-full">
|
||||
<pre id="user-message">{message.content}</pre>
|
||||
|
||||
<div class=" flex justify-start space-x-1">
|
||||
<div class=" flex justify-start space-x-1 text-gray-700 dark:text-gray-500">
|
||||
{#if siblings.length > 1}
|
||||
<div class="flex self-center">
|
||||
<button
|
||||
class="self-center"
|
||||
class="self-center dark:hover:text-white hover:text-black transition"
|
||||
on:click={() => {
|
||||
showPreviousMessage(message);
|
||||
}}
|
||||
|
@ -212,12 +219,12 @@
|
|||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="text-xs font-bold self-center">
|
||||
<div class="text-xs font-bold self-center dark:text-gray-100">
|
||||
{siblings.indexOf(message.id) + 1} / {siblings.length}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="self-center"
|
||||
class="self-center dark:hover:text-white hover:text-black transition"
|
||||
on:click={() => {
|
||||
showNextMessage(message);
|
||||
}}
|
||||
|
@ -239,7 +246,7 @@
|
|||
{/if}
|
||||
|
||||
<button
|
||||
class="invisible group-hover:visible p-1 rounded dark:hover:text-white transition edit-user-message-button"
|
||||
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button"
|
||||
on:click={() => {
|
||||
editMessageHandler();
|
||||
}}
|
||||
|
@ -261,7 +268,7 @@
|
|||
</button>
|
||||
|
||||
<button
|
||||
class="invisible group-hover:visible p-1 rounded dark:hover:text-white transition"
|
||||
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
|
||||
on:click={() => {
|
||||
copyToClipboard(message.content);
|
||||
}}
|
||||
|
@ -281,6 +288,30 @@
|
|||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{#if !isFirstMessage}
|
||||
<button
|
||||
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
|
||||
on:click={() => {
|
||||
deleteMessageHandler();
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
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="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { getOllamaVersion } from '$lib/apis/ollama';
|
||||
import { WEBUI_NAME, WEB_UI_VERSION } from '$lib/constants';
|
||||
import { config } from '$lib/stores';
|
||||
import { WEBUI_VERSION } from '$lib/constants';
|
||||
import { WEBUI_NAME, config, showChangelog } from '$lib/stores';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let ollamaVersion = '';
|
||||
|
@ -15,10 +15,25 @@
|
|||
<div class="flex flex-col h-full justify-between space-y-3 text-sm mb-6">
|
||||
<div class=" space-y-3">
|
||||
<div>
|
||||
<div class=" mb-2.5 text-sm font-medium">{WEBUI_NAME} Version</div>
|
||||
<div class=" mb-2.5 text-sm font-medium flex space-x-2 items-center">
|
||||
<div>
|
||||
{$WEBUI_NAME} Version
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 text-xs text-gray-700 dark:text-gray-200">
|
||||
{WEB_UI_VERSION}
|
||||
<div class="flex-1 text-xs text-gray-700 dark:text-gray-200 flex space-x-1.5 items-center">
|
||||
<div>
|
||||
v{WEBUI_VERSION}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class=" underline flex items-center space-x-1 text-xs text-gray-500 dark:text-gray-500"
|
||||
on:click={() => {
|
||||
showChangelog.set(true);
|
||||
}}
|
||||
>
|
||||
<div>See what's new</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,9 +8,11 @@
|
|||
getDefaultDiffusionModel,
|
||||
getDiffusionModels,
|
||||
getImageGenerationEnabledStatus,
|
||||
getImageSize,
|
||||
toggleImageGenerationEnabledStatus,
|
||||
updateAUTOMATIC1111Url,
|
||||
updateDefaultDiffusionModel
|
||||
updateDefaultDiffusionModel,
|
||||
updateImageSize
|
||||
} from '$lib/apis/images';
|
||||
import { getBackendConfig } from '$lib/apis';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
@ -25,6 +27,8 @@
|
|||
let selectedModel = '';
|
||||
let models = [];
|
||||
|
||||
let imageSize = '';
|
||||
|
||||
const getModels = async () => {
|
||||
models = await getDiffusionModels(localStorage.token).catch((error) => {
|
||||
toast.error(error);
|
||||
|
@ -53,7 +57,6 @@
|
|||
AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleImageGeneration = async () => {
|
||||
if (AUTOMATIC1111_BASE_URL) {
|
||||
enableImageGeneration = await toggleImageGenerationEnabledStatus(localStorage.token).catch(
|
||||
|
@ -79,6 +82,7 @@
|
|||
AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token);
|
||||
|
||||
if (enableImageGeneration && AUTOMATIC1111_BASE_URL) {
|
||||
imageSize = await getImageSize(localStorage.token);
|
||||
getModels();
|
||||
}
|
||||
}
|
||||
|
@ -89,13 +93,17 @@
|
|||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||
on:submit|preventDefault={async () => {
|
||||
loading = true;
|
||||
const res = await updateDefaultDiffusionModel(localStorage.token, selectedModel);
|
||||
await updateDefaultDiffusionModel(localStorage.token, selectedModel);
|
||||
await updateImageSize(localStorage.token, imageSize).catch((error) => {
|
||||
toast.error(error);
|
||||
return null;
|
||||
});
|
||||
|
||||
dispatch('save');
|
||||
loading = false;
|
||||
}}
|
||||
>
|
||||
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
|
||||
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[21rem]">
|
||||
<div>
|
||||
<div class=" mb-1 text-sm font-medium">Image Settings</div>
|
||||
|
||||
|
@ -169,7 +177,7 @@
|
|||
<hr class=" dark:border-gray-700" />
|
||||
|
||||
<div>
|
||||
<div class=" mb-2.5 text-sm font-medium">Set default model</div>
|
||||
<div class=" mb-2.5 text-sm font-medium">Set Default Model</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<select
|
||||
|
@ -189,6 +197,19 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" mb-2.5 text-sm font-medium">Set Image Size</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<input
|
||||
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
|
||||
placeholder="Enter Image Size (e.g. 512x512)"
|
||||
bind:value={imageSize}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
import toast from 'svelte-french-toast';
|
||||
|
||||
import { createModel, deleteModel, pullModel } from '$lib/apis/ollama';
|
||||
import { WEBUI_API_BASE_URL, WEBUI_NAME } from '$lib/constants';
|
||||
import { models, user } from '$lib/stores';
|
||||
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
||||
import { WEBUI_NAME, models, user } from '$lib/stores';
|
||||
import { splitStream } from '$lib/utils';
|
||||
|
||||
export let getModels: Function;
|
||||
|
@ -59,9 +59,9 @@
|
|||
} else {
|
||||
toast.success(`Model '${modelName}' has been successfully downloaded.`);
|
||||
|
||||
const notification = new Notification(WEBUI_NAME, {
|
||||
const notification = new Notification($WEBUI_NAME, {
|
||||
body: `Model '${modelName}' has been successfully downloaded.`,
|
||||
icon: '/favicon.png'
|
||||
icon: `${WEBUI_BASE_URL}/static/favicon.png`
|
||||
});
|
||||
|
||||
models.set(await getModels());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue