open-webui/src/lib/components/chat/Messages.svelte

382 lines
11 KiB
Svelte
Raw Normal View History

2023-11-20 01:47:07 +00:00
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
2024-05-24 10:02:56 +00:00
import { chats, config, settings, user as _user, mobile } from '$lib/stores';
import { tick, getContext } from 'svelte';
2023-11-20 01:47:07 +00:00
2024-03-01 09:18:07 +00:00
import { toast } from 'svelte-sonner';
2023-12-26 20:50:52 +00:00
import { getChatList, updateChatById } from '$lib/apis/chats';
2023-11-20 01:47:07 +00:00
2023-12-30 07:03:48 +00:00
import UserMessage from './Messages/UserMessage.svelte';
import ResponseMessage from './Messages/ResponseMessage.svelte';
import Placeholder from './Messages/Placeholder.svelte';
2024-01-07 11:19:34 +00:00
import Spinner from '../common/Spinner.svelte';
2024-02-22 02:36:40 +00:00
import { imageGenerations } from '$lib/apis/images';
import { copyToClipboard, findWordIndices } from '$lib/utils';
2024-05-19 11:46:35 +00:00
import CompareMessages from './Messages/CompareMessages.svelte';
import { stringify } from 'postcss';
2023-12-30 07:03:48 +00:00
const i18n = getContext('i18n');
2023-12-26 11:28:30 +00:00
export let chatId = '';
export let readOnly = false;
2023-11-20 01:47:07 +00:00
export let sendPrompt: Function;
2024-02-15 09:23:02 +00:00
export let continueGeneration: Function;
2023-11-20 01:47:07 +00:00
export let regenerateResponse: Function;
2024-05-18 21:19:48 +00:00
export let user = $_user;
2024-04-30 21:56:06 +00:00
export let prompt;
2024-01-07 11:19:34 +00:00
export let processing = '';
2023-12-14 00:21:50 +00:00
export let bottomPadding = false;
2023-11-20 01:47:07 +00:00
export let autoScroll;
export let history = {};
export let messages = [];
2024-05-19 11:46:35 +00:00
export let selectedModels;
2023-11-20 01:47:07 +00:00
2023-12-14 00:21:50 +00:00
$: if (autoScroll && bottomPadding) {
(async () => {
await tick();
2024-02-16 00:20:46 +00:00
scrollToBottom();
2023-12-14 00:21:50 +00:00
})();
}
2024-02-16 00:20:46 +00:00
const scrollToBottom = () => {
const element = document.getElementById('messages-container');
element.scrollTop = element.scrollHeight;
};
2024-04-19 11:34:55 +00:00
const copyToClipboardWithToast = async (text) => {
const res = await copyToClipboard(text);
if (res) {
toast.success($i18n.t('Copying to clipboard was successful!'));
2023-11-20 01:47:07 +00:00
}
};
2023-12-30 07:03:48 +00:00
const confirmEditMessage = async (messageId, content) => {
let userPrompt = content;
2023-11-20 01:47:07 +00:00
let userMessageId = uuidv4();
let userMessage = {
id: userMessageId,
parentId: history.messages[messageId].parentId,
childrenIds: [],
role: 'user',
2023-12-14 00:21:50 +00:00
content: userPrompt,
2024-05-19 11:46:35 +00:00
...(history.messages[messageId].files && { files: history.messages[messageId].files }),
models: selectedModels.filter((m, mIdx) => selectedModels.indexOf(m) === mIdx)
2023-11-20 01:47:07 +00:00
};
let messageParentId = history.messages[messageId].parentId;
if (messageParentId !== null) {
history.messages[messageParentId].childrenIds = [
...history.messages[messageParentId].childrenIds,
userMessageId
];
}
history.messages[userMessageId] = userMessage;
history.currentId = userMessageId;
await tick();
2024-05-19 11:46:35 +00:00
await sendPrompt(userPrompt, userMessageId);
2023-11-20 01:47:07 +00:00
};
2024-04-08 10:08:30 +00:00
const updateChatMessages = async () => {
2023-12-30 07:25:40 +00:00
await tick();
await updateChatById(localStorage.token, chatId, {
messages: messages,
history: history
});
await chats.set(await getChatList(localStorage.token));
2023-12-11 06:47:16 +00:00
};
2024-04-08 10:08:30 +00:00
const confirmEditResponseMessage = async (messageId, content) => {
history.messages[messageId].originalContent = history.messages[messageId].content;
history.messages[messageId].content = content;
await updateChatMessages();
};
2023-12-30 07:03:48 +00:00
const rateMessage = async (messageId, rating) => {
2024-04-08 10:08:30 +00:00
history.messages[messageId].annotation = {
...history.messages[messageId].annotation,
rating: rating
};
2023-12-26 20:50:52 +00:00
2024-04-08 10:08:30 +00:00
await updateChatMessages();
2023-11-20 01:47:07 +00:00
};
const showPreviousMessage = async (message) => {
if (message.parentId !== null) {
let messageId =
history.messages[message.parentId].childrenIds[
Math.max(history.messages[message.parentId].childrenIds.indexOf(message.id) - 1, 0)
];
if (message.id !== messageId) {
let messageChildrenIds = history.messages[messageId].childrenIds;
while (messageChildrenIds.length !== 0) {
messageId = messageChildrenIds.at(-1);
messageChildrenIds = history.messages[messageId].childrenIds;
}
history.currentId = messageId;
}
} else {
let childrenIds = Object.values(history.messages)
.filter((message) => message.parentId === null)
.map((message) => message.id);
let messageId = childrenIds[Math.max(childrenIds.indexOf(message.id) - 1, 0)];
if (message.id !== messageId) {
let messageChildrenIds = history.messages[messageId].childrenIds;
while (messageChildrenIds.length !== 0) {
messageId = messageChildrenIds.at(-1);
messageChildrenIds = history.messages[messageId].childrenIds;
}
history.currentId = messageId;
}
}
await tick();
2024-02-16 00:20:46 +00:00
const element = document.getElementById('messages-container');
2024-02-16 04:10:48 +00:00
autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
2023-11-20 01:47:07 +00:00
setTimeout(() => {
2024-02-16 00:20:46 +00:00
scrollToBottom();
2023-11-20 01:47:07 +00:00
}, 100);
};
const showNextMessage = async (message) => {
if (message.parentId !== null) {
let messageId =
history.messages[message.parentId].childrenIds[
Math.min(
history.messages[message.parentId].childrenIds.indexOf(message.id) + 1,
history.messages[message.parentId].childrenIds.length - 1
)
];
if (message.id !== messageId) {
let messageChildrenIds = history.messages[messageId].childrenIds;
while (messageChildrenIds.length !== 0) {
messageId = messageChildrenIds.at(-1);
messageChildrenIds = history.messages[messageId].childrenIds;
}
history.currentId = messageId;
}
} else {
let childrenIds = Object.values(history.messages)
.filter((message) => message.parentId === null)
.map((message) => message.id);
let messageId =
childrenIds[Math.min(childrenIds.indexOf(message.id) + 1, childrenIds.length - 1)];
if (message.id !== messageId) {
let messageChildrenIds = history.messages[messageId].childrenIds;
while (messageChildrenIds.length !== 0) {
messageId = messageChildrenIds.at(-1);
messageChildrenIds = history.messages[messageId].childrenIds;
}
history.currentId = messageId;
}
}
await tick();
2024-02-16 00:20:46 +00:00
const element = document.getElementById('messages-container');
2024-02-16 04:10:48 +00:00
autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
2024-02-16 00:20:46 +00:00
2023-11-20 01:47:07 +00:00
setTimeout(() => {
2024-02-16 00:20:46 +00:00
scrollToBottom();
2023-11-20 01:47:07 +00:00
}, 100);
};
2024-02-23 01:53:58 +00:00
const messageDeleteHandler = async (messageId) => {
2024-03-06 08:55:51 +00:00
const messageToDelete = history.messages[messageId];
const messageParentId = messageToDelete.parentId;
const messageChildrenIds = messageToDelete.childrenIds ?? [];
const hasSibling = messageChildrenIds.some(
(childId) => history.messages[childId]?.childrenIds?.length > 0
2024-03-06 08:33:47 +00:00
);
2024-03-06 08:55:51 +00:00
messageChildrenIds.forEach((childId) => {
const child = history.messages[childId];
if (child && child.childrenIds) {
if (child.childrenIds.length === 0 && !hasSibling) {
// if last prompt/response pair
history.messages[messageParentId].childrenIds = [];
history.currentId = messageParentId;
} else {
child.childrenIds.forEach((grandChildId) => {
if (history.messages[grandChildId]) {
history.messages[grandChildId].parentId = messageParentId;
history.messages[messageParentId].childrenIds.push(grandChildId);
}
});
}
}
// remove response
history.messages[messageParentId].childrenIds = history.messages[
messageParentId
].childrenIds.filter((id) => id !== childId);
});
// remove prompt
history.messages[messageParentId].childrenIds = history.messages[
messageParentId
].childrenIds.filter((id) => id !== messageId);
await updateChatById(localStorage.token, chatId, {
messages: messages,
history: history
});
};
2023-11-20 01:47:07 +00:00
</script>
2024-05-02 07:07:04 +00:00
<div class="h-full flex mb-16">
2024-04-30 22:31:50 +00:00
{#if messages.length == 0}
<Placeholder
2024-05-24 10:02:56 +00:00
modelIds={selectedModels}
2024-04-30 22:31:50 +00:00
submitPrompt={async (p) => {
let text = p;
if (p.includes('{{CLIPBOARD}}')) {
const clipboardText = await navigator.clipboard.readText().catch((err) => {
toast.error($i18n.t('Failed to read clipboard contents'));
return '{{CLIPBOARD}}';
});
text = p.replaceAll('{{CLIPBOARD}}', clipboardText);
}
prompt = text;
await tick();
const chatInputElement = document.getElementById('chat-textarea');
if (chatInputElement) {
2024-04-30 22:31:50 +00:00
prompt = p;
chatInputElement.style.height = '';
chatInputElement.style.height = Math.min(chatInputElement.scrollHeight, 200) + 'px';
chatInputElement.focus();
2024-04-30 22:31:50 +00:00
const words = findWordIndices(prompt);
if (words.length > 0) {
const word = words.at(0);
chatInputElement.setSelectionRange(word?.startIndex, word.endIndex + 1);
}
2024-04-30 22:31:50 +00:00
}
await tick();
2024-04-30 22:31:50 +00:00
}}
/>
{:else}
2024-04-30 23:52:19 +00:00
<div class="w-full pt-2">
2024-04-30 22:31:50 +00:00
{#key chatId}
{#each messages as message, messageIdx}
2024-04-30 23:52:19 +00:00
<div class=" w-full {messageIdx === messages.length - 1 ? 'pb-28' : ''}">
2024-04-30 22:31:50 +00:00
<div
class="flex flex-col justify-between px-5 mb-3 {$settings?.fullScreenMode ?? null
? 'max-w-full'
2024-05-02 07:34:07 +00:00
: 'max-w-5xl'} mx-auto rounded-lg group"
2024-04-30 22:31:50 +00:00
>
{#if message.role === 'user'}
<UserMessage
on:delete={() => messageDeleteHandler(message.id)}
2024-05-18 21:19:48 +00:00
{user}
2024-04-30 22:31:50 +00:00
{readOnly}
{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={copyToClipboardWithToast}
/>
2024-05-19 11:46:35 +00:00
{:else if $mobile || (history.messages[message.parentId]?.models?.length ?? 1) === 1}
2024-05-13 21:27:58 +00:00
{#key message.id}
<ResponseMessage
{message}
siblings={history.messages[message.parentId]?.childrenIds ?? []}
isLastMessage={messageIdx + 1 === messages.length}
{readOnly}
{updateChatMessages}
{confirmEditResponseMessage}
{showPreviousMessage}
{showNextMessage}
{rateMessage}
copyToClipboard={copyToClipboardWithToast}
{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
});
}}
/>
{/key}
2024-05-19 11:46:35 +00:00
{:else}
{#key message.parentId}
<CompareMessages
bind:history
{messages}
2024-05-31 21:19:46 +00:00
{readOnly}
2024-05-19 11:46:35 +00:00
{chatId}
parentMessage={history.messages[message.parentId]}
{messageIdx}
{updateChatMessages}
{confirmEditResponseMessage}
{rateMessage}
copyToClipboard={copyToClipboardWithToast}
{continueGeneration}
{regenerateResponse}
2024-05-19 12:53:43 +00:00
on:change={async () => {
await updateChatById(localStorage.token, chatId, {
messages: messages,
history: history
});
2024-05-19 14:40:54 +00:00
if (autoScroll) {
const element = document.getElementById('messages-container');
autoScroll =
element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
setTimeout(() => {
scrollToBottom();
}, 100);
}
2024-05-19 11:46:35 +00:00
}}
/>
{/key}
2024-04-30 22:31:50 +00:00
{/if}
</div>
2024-02-16 00:20:46 +00:00
</div>
2024-04-30 22:31:50 +00:00
{/each}
{#if bottomPadding}
2024-04-30 23:52:19 +00:00
<div class=" pb-20" />
2024-04-30 22:31:50 +00:00
{/if}
{/key}
</div>
{/if}
</div>