2023-11-20 01:47:07 +00:00
|
|
|
<script lang="ts">
|
|
|
|
import { v4 as uuidv4 } from 'uuid';
|
2024-08-04 14:58:08 +00:00
|
|
|
import { chats, config, settings, user as _user, mobile, currentChatPage } from '$lib/stores';
|
2024-06-03 01:03:30 +00:00
|
|
|
import { tick, getContext, onMount } 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';
|
2024-08-18 14:47:12 +00:00
|
|
|
import { copyToClipboard, findWordIndices } from '$lib/utils';
|
2023-11-20 01:47:07 +00:00
|
|
|
|
2023-12-30 07:03:48 +00:00
|
|
|
import Placeholder from './Messages/Placeholder.svelte';
|
2024-09-22 22:55:13 +00:00
|
|
|
import Message from './Messages/Message.svelte';
|
2024-09-23 22:27:22 +00:00
|
|
|
import Loader from '../common/Loader.svelte';
|
|
|
|
import Spinner from '../common/Spinner.svelte';
|
2023-12-30 07:03:48 +00:00
|
|
|
|
2024-03-02 20:38:51 +00:00
|
|
|
const i18n = getContext('i18n');
|
|
|
|
|
2023-12-26 11:28:30 +00:00
|
|
|
export let chatId = '';
|
2024-09-23 12:24:50 +00:00
|
|
|
export let user = $_user;
|
|
|
|
|
|
|
|
export let prompt;
|
|
|
|
export let history = {};
|
|
|
|
export let selectedModels;
|
|
|
|
|
|
|
|
let messages = [];
|
|
|
|
|
2023-11-20 01:47:07 +00:00
|
|
|
export let sendPrompt: Function;
|
2024-09-22 23:36:46 +00:00
|
|
|
export let continueResponse: Function;
|
2023-11-20 01:47:07 +00:00
|
|
|
export let regenerateResponse: Function;
|
2024-08-18 18:59:59 +00:00
|
|
|
export let mergeResponses: Function;
|
2024-07-12 01:47:38 +00:00
|
|
|
export let chatActionHandler: Function;
|
2024-09-19 01:49:35 +00:00
|
|
|
export let showMessage: Function = () => {};
|
2023-11-20 01:47:07 +00:00
|
|
|
|
2024-09-23 12:24:50 +00:00
|
|
|
export let readOnly = false;
|
|
|
|
|
2023-12-14 00:21:50 +00:00
|
|
|
export let bottomPadding = false;
|
2023-11-20 01:47:07 +00:00
|
|
|
export let autoScroll;
|
|
|
|
|
2024-09-23 22:27:22 +00:00
|
|
|
let messagesCount = 20;
|
|
|
|
let messagesLoading = false;
|
|
|
|
|
|
|
|
const loadMoreMessages = async () => {
|
2024-09-23 23:05:46 +00:00
|
|
|
// scroll slightly down to disable continuous loading
|
|
|
|
const element = document.getElementById('messages-container');
|
|
|
|
element.scrollTop = element.scrollTop + 100;
|
|
|
|
|
2024-09-23 22:27:22 +00:00
|
|
|
messagesLoading = true;
|
|
|
|
messagesCount += 20;
|
2024-09-23 23:05:46 +00:00
|
|
|
|
|
|
|
await tick();
|
|
|
|
|
2024-09-23 22:27:22 +00:00
|
|
|
messagesLoading = false;
|
|
|
|
};
|
|
|
|
|
2024-09-23 12:24:50 +00:00
|
|
|
$: if (history.currentId) {
|
|
|
|
let _messages = [];
|
|
|
|
|
|
|
|
let message = history.messages[history.currentId];
|
2024-09-23 22:27:22 +00:00
|
|
|
while (message && _messages.length <= messagesCount) {
|
2024-09-23 22:57:28 +00:00
|
|
|
_messages.unshift({ ...message });
|
2024-09-23 12:24:50 +00:00
|
|
|
message = message.parentId !== null ? history.messages[message.parentId] : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
messages = _messages;
|
|
|
|
} else {
|
|
|
|
messages = [];
|
|
|
|
}
|
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-09-22 23:36:46 +00:00
|
|
|
const updateChatHistory = async () => {
|
2024-09-22 22:55:13 +00:00
|
|
|
await tick();
|
|
|
|
await updateChatById(localStorage.token, chatId, {
|
|
|
|
history: history
|
|
|
|
});
|
|
|
|
|
|
|
|
currentChatPage.set(1);
|
|
|
|
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
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-08-04 03:33:20 +00:00
|
|
|
if ($settings?.scrollOnBranchChange ?? true) {
|
2024-08-04 01:56:04 +00:00
|
|
|
const element = document.getElementById('messages-container');
|
|
|
|
autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
|
2023-11-20 01:47:07 +00:00
|
|
|
|
2024-08-04 01:56:04 +00:00
|
|
|
setTimeout(() => {
|
|
|
|
scrollToBottom();
|
|
|
|
}, 100);
|
|
|
|
}
|
2023-11-20 01:47:07 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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-08-04 03:33:20 +00:00
|
|
|
if ($settings?.scrollOnBranchChange ?? true) {
|
2024-08-04 01:56:04 +00:00
|
|
|
const element = document.getElementById('messages-container');
|
|
|
|
autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
|
2024-02-16 00:20:46 +00:00
|
|
|
|
2024-08-04 01:56:04 +00:00
|
|
|
setTimeout(() => {
|
|
|
|
scrollToBottom();
|
|
|
|
}, 100);
|
|
|
|
}
|
2023-11-20 01:47:07 +00:00
|
|
|
};
|
2024-02-20 23:12:06 +00:00
|
|
|
|
2024-09-22 23:36:46 +00:00
|
|
|
const rateMessage = async (messageId, rating) => {
|
|
|
|
history.messages[messageId].annotation = {
|
|
|
|
...history.messages[messageId].annotation,
|
|
|
|
rating: rating
|
|
|
|
};
|
|
|
|
|
|
|
|
await updateChatHistory();
|
|
|
|
};
|
|
|
|
|
|
|
|
const editMessage = async (messageId, content, submit = true) => {
|
|
|
|
if (history.messages[messageId].role === 'user') {
|
|
|
|
if (submit) {
|
|
|
|
// New user message
|
|
|
|
let userPrompt = content;
|
|
|
|
let userMessageId = uuidv4();
|
|
|
|
|
|
|
|
let userMessage = {
|
|
|
|
id: userMessageId,
|
|
|
|
parentId: history.messages[messageId].parentId,
|
|
|
|
childrenIds: [],
|
|
|
|
role: 'user',
|
|
|
|
content: userPrompt,
|
|
|
|
...(history.messages[messageId].files && { files: history.messages[messageId].files }),
|
|
|
|
models: selectedModels
|
|
|
|
};
|
|
|
|
|
|
|
|
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();
|
|
|
|
await sendPrompt(userPrompt, userMessageId);
|
|
|
|
} else {
|
|
|
|
// Edit user message
|
|
|
|
history.messages[messageId].content = content;
|
|
|
|
await updateChatHistory();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (submit) {
|
|
|
|
// New response message
|
|
|
|
const responseMessageId = uuidv4();
|
2024-09-23 21:23:08 +00:00
|
|
|
const message = history.messages[messageId];
|
2024-09-22 23:36:46 +00:00
|
|
|
const parentId = message.parentId;
|
|
|
|
|
|
|
|
const responseMessage = {
|
|
|
|
...message,
|
|
|
|
id: responseMessageId,
|
|
|
|
parentId: parentId,
|
|
|
|
childrenIds: [],
|
|
|
|
content: content,
|
|
|
|
timestamp: Math.floor(Date.now() / 1000) // Unix epoch
|
|
|
|
};
|
|
|
|
|
|
|
|
history.messages[responseMessageId] = responseMessage;
|
|
|
|
history.currentId = responseMessageId;
|
|
|
|
|
|
|
|
// Append messageId to childrenIds of parent message
|
|
|
|
if (parentId !== null) {
|
|
|
|
history.messages[parentId].childrenIds = [
|
|
|
|
...history.messages[parentId].childrenIds,
|
|
|
|
responseMessageId
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
await updateChatHistory();
|
|
|
|
} else {
|
|
|
|
// Edit response message
|
|
|
|
history.messages[messageId].originalContent = history.messages[messageId].content;
|
|
|
|
history.messages[messageId].content = content;
|
|
|
|
await updateChatHistory();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-09-22 22:55:13 +00:00
|
|
|
const deleteMessage = async (messageId) => {
|
2024-03-06 08:55:51 +00:00
|
|
|
const messageToDelete = history.messages[messageId];
|
2024-06-17 05:28:26 +00:00
|
|
|
const parentMessageId = messageToDelete.parentId;
|
|
|
|
const childMessageIds = messageToDelete.childrenIds ?? [];
|
|
|
|
|
2024-09-19 01:49:35 +00:00
|
|
|
// Collect all grandchildren
|
|
|
|
const grandchildrenIds = childMessageIds.flatMap(
|
|
|
|
(childId) => history.messages[childId]?.childrenIds ?? []
|
2024-03-06 08:33:47 +00:00
|
|
|
);
|
2024-06-17 05:28:26 +00:00
|
|
|
|
2024-09-19 01:49:35 +00:00
|
|
|
// Update parent's children
|
|
|
|
if (parentMessageId && history.messages[parentMessageId]) {
|
|
|
|
history.messages[parentMessageId].childrenIds = [
|
|
|
|
...history.messages[parentMessageId].childrenIds.filter((id) => id !== messageId),
|
|
|
|
...grandchildrenIds
|
|
|
|
];
|
|
|
|
}
|
2024-06-17 05:28:26 +00:00
|
|
|
|
2024-09-19 01:49:35 +00:00
|
|
|
// Update grandchildren's parent
|
|
|
|
grandchildrenIds.forEach((grandchildId) => {
|
|
|
|
if (history.messages[grandchildId]) {
|
|
|
|
history.messages[grandchildId].parentId = parentMessageId;
|
2024-03-06 08:55:51 +00:00
|
|
|
}
|
2024-09-19 01:49:35 +00:00
|
|
|
});
|
2024-06-17 05:28:26 +00:00
|
|
|
|
2024-09-19 01:49:35 +00:00
|
|
|
// Delete the message and its children
|
|
|
|
[messageId, ...childMessageIds].forEach((id) => {
|
|
|
|
delete history.messages[id];
|
2024-03-06 08:55:51 +00:00
|
|
|
});
|
2024-06-17 05:28:26 +00:00
|
|
|
|
|
|
|
await tick();
|
|
|
|
|
2024-09-19 01:49:35 +00:00
|
|
|
showMessage({ id: parentMessageId });
|
|
|
|
|
|
|
|
// Update the chat
|
2024-09-22 23:36:46 +00:00
|
|
|
await updateChatHistory();
|
2024-02-20 21:24:22 +00:00
|
|
|
};
|
2023-11-20 01:47:07 +00:00
|
|
|
</script>
|
|
|
|
|
2024-06-02 01:44:47 +00:00
|
|
|
<div class="h-full flex">
|
2024-09-22 22:55:13 +00:00
|
|
|
{#if Object.keys(history?.messages ?? {}).length == 0}
|
2024-04-30 22:31:50 +00:00
|
|
|
<Placeholder
|
2024-05-24 10:02:56 +00:00
|
|
|
modelIds={selectedModels}
|
2024-04-30 22:31:50 +00:00
|
|
|
submitPrompt={async (p) => {
|
2024-05-02 07:45:04 +00:00
|
|
|
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;
|
|
|
|
|
2024-05-02 07:45:04 +00:00
|
|
|
chatInputElement.style.height = '';
|
|
|
|
chatInputElement.style.height = Math.min(chatInputElement.scrollHeight, 200) + 'px';
|
|
|
|
chatInputElement.focus();
|
2024-04-30 22:31:50 +00:00
|
|
|
|
2024-05-02 07:45:04 +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
|
|
|
}
|
2024-05-02 07:45:04 +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}
|
2024-09-23 22:57:28 +00:00
|
|
|
<div class="w-full">
|
|
|
|
{#if messages.at(0)?.parentId !== null}
|
|
|
|
<Loader
|
|
|
|
on:visible={(e) => {
|
|
|
|
console.log('visible');
|
|
|
|
if (!messagesLoading) {
|
|
|
|
loadMoreMessages();
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<div class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2">
|
|
|
|
<Spinner className=" size-4" />
|
|
|
|
<div class=" ">Loading...</div>
|
|
|
|
</div>
|
|
|
|
</Loader>
|
|
|
|
{/if}
|
|
|
|
|
2024-09-23 12:24:50 +00:00
|
|
|
{#each messages as message, messageIdx (message.id)}
|
|
|
|
<Message
|
|
|
|
{chatId}
|
2024-09-23 21:20:27 +00:00
|
|
|
bind:history
|
2024-09-23 12:24:50 +00:00
|
|
|
messageId={message.id}
|
|
|
|
idx={messageIdx}
|
|
|
|
{user}
|
2024-09-23 15:40:24 +00:00
|
|
|
{showPreviousMessage}
|
|
|
|
{showNextMessage}
|
2024-09-23 12:24:50 +00:00
|
|
|
{editMessage}
|
|
|
|
{deleteMessage}
|
|
|
|
{rateMessage}
|
2024-09-23 15:40:24 +00:00
|
|
|
{regenerateResponse}
|
|
|
|
{continueResponse}
|
2024-09-23 21:20:27 +00:00
|
|
|
{mergeResponses}
|
|
|
|
{updateChatHistory}
|
|
|
|
{chatActionHandler}
|
|
|
|
{readOnly}
|
|
|
|
on:scroll={() => {
|
|
|
|
if (autoScroll) {
|
|
|
|
const element = document.getElementById('messages-container');
|
|
|
|
autoScroll =
|
|
|
|
element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
|
|
|
|
setTimeout(() => {
|
|
|
|
scrollToBottom();
|
|
|
|
}, 100);
|
|
|
|
}
|
|
|
|
}}
|
2024-09-23 12:24:50 +00:00
|
|
|
/>
|
|
|
|
{/each}
|
2024-09-22 23:36:46 +00:00
|
|
|
</div>
|
2024-09-22 22:55:13 +00:00
|
|
|
<div class="pb-12" />
|
2024-04-30 22:31:50 +00:00
|
|
|
{#if bottomPadding}
|
2024-06-18 23:11:10 +00:00
|
|
|
<div class=" pb-6" />
|
2024-04-30 22:31:50 +00:00
|
|
|
{/if}
|
|
|
|
{/key}
|
|
|
|
</div>
|
|
|
|
{/if}
|
|
|
|
</div>
|