/
Netrunners
/
Millenium_UI
Обзор
Документация
Войти
/
Netrunners
/
Millenium_UI
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/lib/components/chat/Messages.svelte
436 строк
11 KB
Валерий Конюхов
first_commit
08 июл 2025, 22:20
08 июл 2025, 22:20
a4e8dc9
Код
Авторство
О чём код?
<script lang="ts"> import { v4 as uuidv4 } from 'uuid'; import { chats, config, settings, user as _user, mobile, currentChatPage, temporaryChatEnabled } from '$lib/stores'; import { tick, getContext, onMount, createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher(); import { toast } from 'svelte-sonner'; import { getChatList, updateChatById } from '$lib/apis/chats'; import { copyToClipboard, findWordIndices } from '$lib/utils'; import Message from './Messages/Message.svelte'; import Loader from '../common/Loader.svelte'; import Spinner from '../common/Spinner.svelte'; import ChatPlaceholder from './ChatPlaceholder.svelte'; const i18n = getContext('i18n'); export let className = 'h-full flex pt-8'; export let chatId = ''; export let user = $_user; export let prompt; export let history = {}; export let selectedModels; export let atSelectedModel; let messages = []; export let sendPrompt: Function; export let continueResponse: Function; export let regenerateResponse: Function; export let mergeResponses: Function; export let chatActionHandler: Function; export let showMessage: Function = () => {}; export let submitMessage: Function = () => {}; export let addMessages: Function = () => {}; export let readOnly = false; export let bottomPadding = false; export let autoScroll; let messagesCount = 20; let messagesLoading = false; const loadMoreMessages = async () => { // scroll slightly down to disable continuous loading const element = document.getElementById('messages-container'); element.scrollTop = element.scrollTop + 100; messagesLoading = true; messagesCount += 20; await tick(); messagesLoading = false; }; $: if (history.currentId) { let _messages = []; let message = history.messages[history.currentId]; while (message && _messages.length <= messagesCount) { _messages.unshift({ ...message }); message = message.parentId !== null ? history.messages[message.parentId] : null; } messages = _messages; } else { messages = []; } $: if (autoScroll && bottomPadding) { (async () => { await tick(); scrollToBottom(); })(); } const scrollToBottom = () => { const element = document.getElementById('messages-container'); element.scrollTop = element.scrollHeight; }; const updateChat = async () => { if (!$temporaryChatEnabled) { history = history; await tick(); await updateChatById(localStorage.token, chatId, { history: history, messages: messages }); currentChatPage.set(1); await chats.set(await getChatList(localStorage.token, $currentChatPage)); } }; 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(); if ($settings?.scrollOnBranchChange ?? true) { const element = document.getElementById('messages-container'); autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50; setTimeout(() => { scrollToBottom(); }, 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(); if ($settings?.scrollOnBranchChange ?? true) { const element = document.getElementById('messages-container'); autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50; setTimeout(() => { scrollToBottom(); }, 100); } }; const rateMessage = async (messageId, rating) => { history.messages[messageId].annotation = { ...history.messages[messageId].annotation, rating: rating }; await updateChat(); }; 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, timestamp: Math.floor(Date.now() / 1000) // Unix epoch }; 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(history, userPrompt, userMessageId); } else { // Edit user message history.messages[messageId].content = content; await updateChat(); } } else { if (submit) { // New response message const responseMessageId = uuidv4(); const message = history.messages[messageId]; const parentId = message.parentId; const responseMessage = { ...message, id: responseMessageId, parentId: parentId, childrenIds: [], files: undefined, 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 updateChat(); } else { // Edit response message history.messages[messageId].originalContent = history.messages[messageId].content; history.messages[messageId].content = content; await updateChat(); } } }; const actionMessage = async (actionId, message, event = null) => { await chatActionHandler(chatId, actionId, message.model, message.id, event); }; const saveMessage = async (messageId, message) => { history.messages[messageId] = message; await updateChat(); }; const deleteMessage = async (messageId) => { const messageToDelete = history.messages[messageId]; const parentMessageId = messageToDelete.parentId; const childMessageIds = messageToDelete.childrenIds ?? []; // Collect all grandchildren const grandchildrenIds = childMessageIds.flatMap( (childId) => history.messages[childId]?.childrenIds ?? [] ); // Update parent's children if (parentMessageId && history.messages[parentMessageId]) { history.messages[parentMessageId].childrenIds = [ ...history.messages[parentMessageId].childrenIds.filter((id) => id !== messageId), ...grandchildrenIds ]; } // Update grandchildren's parent grandchildrenIds.forEach((grandchildId) => { if (history.messages[grandchildId]) { history.messages[grandchildId].parentId = parentMessageId; } }); // Delete the message and its children [messageId, ...childMessageIds].forEach((id) => { delete history.messages[id]; }); await tick(); showMessage({ id: parentMessageId }); // Update the chat await updateChat(); }; const triggerScroll = () => { if (autoScroll) { const element = document.getElementById('messages-container'); autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50; setTimeout(() => { scrollToBottom(); }, 100); } }; </script> <div class={className}> {#if Object.keys(history?.messages ?? {}).length == 0} <ChatPlaceholder modelIds={selectedModels} {atSelectedModel} 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 chatInputContainerElement = document.getElementById('chat-input-container'); if (chatInputContainerElement) { prompt = p; chatInputContainerElement.style.height = ''; chatInputContainerElement.style.height = Math.min(chatInputContainerElement.scrollHeight, 200) + 'px'; chatInputContainerElement.focus(); } await tick(); }} /> {:else} <div class="w-full pt-2"> {#key chatId} <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} {#each messages as message, messageIdx (message.id)} <Message {chatId} bind:history messageId={message.id} idx={messageIdx} {user} {showPreviousMessage} {showNextMessage} {updateChat} {editMessage} {deleteMessage} {rateMessage} {actionMessage} {saveMessage} {submitMessage} {regenerateResponse} {continueResponse} {mergeResponses} {addMessages} {triggerScroll} {readOnly} /> {/each} </div> <div class="pb-12" /> {#if bottomPadding} <div class=" pb-6" /> {/if} {/key} </div> {/if} </div>