1
0
Timothy J. Baek 7 сар өмнө
parent
commit
5978e7c9a6

+ 51 - 51
src/lib/components/chat/Chat.svelte

@@ -1520,8 +1520,8 @@
 		}
 	};
 
-	const continueGeneration = async () => {
-		console.log('continueGeneration');
+	const continueResponse = async () => {
+		console.log('continueResponse');
 		const _chatId = JSON.parse(JSON.stringify($chatId));
 
 		if (history.currentId && history.messages[history.currentId].done == true) {
@@ -1552,6 +1552,53 @@
 		}
 	};
 
+	const mergeResponses = async (messageId, responses, _chatId) => {
+		console.log('mergeResponses', messageId, responses);
+		const message = history.messages[messageId];
+		const mergedResponse = {
+			status: true,
+			content: ''
+		};
+		message.merged = mergedResponse;
+		history.messages[messageId] = message;
+
+		try {
+			const [res, controller] = await generateMoACompletion(
+				localStorage.token,
+				message.model,
+				history.messages[message.parentId].content,
+				responses
+			);
+
+			if (res && res.ok && res.body) {
+				const textStream = await createOpenAITextStream(res.body, $settings.splitLargeChunks);
+				for await (const update of textStream) {
+					const { value, done, citations, error, usage } = update;
+					if (error || done) {
+						break;
+					}
+
+					if (mergedResponse.content == '' && value == '\n') {
+						continue;
+					} else {
+						mergedResponse.content += value;
+						history.messages[messageId] = message;
+					}
+
+					if (autoScroll) {
+						scrollToBottom();
+					}
+				}
+
+				await saveChatHandler(_chatId);
+			} else {
+				console.error(res);
+			}
+		} catch (e) {
+			console.error(e);
+		}
+	};
+
 	const generateChatTitle = async (userPrompt) => {
 		if ($settings?.title?.auto ?? true) {
 			const title = await generateTitle(
@@ -1684,52 +1731,6 @@
 			}
 		}
 	};
-	const mergeResponses = async (messageId, responses, _chatId) => {
-		console.log('mergeResponses', messageId, responses);
-		const message = history.messages[messageId];
-		const mergedResponse = {
-			status: true,
-			content: ''
-		};
-		message.merged = mergedResponse;
-		history.messages[messageId] = message;
-
-		try {
-			const [res, controller] = await generateMoACompletion(
-				localStorage.token,
-				message.model,
-				history.messages[message.parentId].content,
-				responses
-			);
-
-			if (res && res.ok && res.body) {
-				const textStream = await createOpenAITextStream(res.body, $settings.splitLargeChunks);
-				for await (const update of textStream) {
-					const { value, done, citations, error, usage } = update;
-					if (error || done) {
-						break;
-					}
-
-					if (mergedResponse.content == '' && value == '\n') {
-						continue;
-					} else {
-						mergedResponse.content += value;
-						history.messages[messageId] = message;
-					}
-
-					if (autoScroll) {
-						scrollToBottom();
-					}
-				}
-
-				await saveChatHandler(_chatId);
-			} else {
-				console.error(res);
-			}
-		} catch (e) {
-			console.error(e);
-		}
-	};
 </script>
 
 <svelte:head>
@@ -1827,13 +1828,12 @@
 								bind:autoScroll
 								bind:prompt
 								{selectedModels}
-								{processing}
 								{sendPrompt}
-								{continueGeneration}
+								{showMessage}
+								{continueResponse}
 								{regenerateResponse}
 								{mergeResponses}
 								{chatActionHandler}
-								{showMessage}
 								bottomPadding={files.length > 0}
 							/>
 						</div>

+ 97 - 89
src/lib/components/chat/Messages.svelte

@@ -15,7 +15,7 @@
 	export let chatId = '';
 	export let readOnly = false;
 	export let sendPrompt: Function;
-	export let continueGeneration: Function;
+	export let continueResponse: Function;
 	export let regenerateResponse: Function;
 	export let mergeResponses: Function;
 	export let chatActionHandler: Function;
@@ -23,7 +23,6 @@
 
 	export let user = $_user;
 	export let prompt;
-	export let processing = '';
 	export let bottomPadding = false;
 	export let autoScroll;
 	export let history = {};
@@ -43,7 +42,7 @@
 		element.scrollTop = element.scrollHeight;
 	};
 
-	const updateChatMessages = async () => {
+	const updateChatHistory = async () => {
 		await tick();
 		await updateChatById(localStorage.token, chatId, {
 			history: history
@@ -53,87 +52,6 @@
 		await chats.set(await getChatList(localStorage.token, $currentChatPage));
 	};
 
-	const confirmEditMessage = async (messageId, content, submit = true) => {
-		if (submit) {
-			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 {
-			history.messages[messageId].content = content;
-			await tick();
-			await updateChatById(localStorage.token, chatId, {
-				history: history
-			});
-		}
-	};
-
-	const confirmEditResponseMessage = async (messageId, content) => {
-		history.messages[messageId].originalContent = history.messages[messageId].content;
-		history.messages[messageId].content = content;
-
-		await updateChatMessages();
-	};
-
-	const saveNewResponseMessage = async (message, content) => {
-		const responseMessageId = uuidv4();
-		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 updateChatMessages();
-	};
-
-	const rateMessage = async (messageId, rating) => {
-		history.messages[messageId].annotation = {
-			...history.messages[messageId].annotation,
-			rating: rating
-		};
-
-		await updateChatMessages();
-	};
-
 	const showPreviousMessage = async (message) => {
 		if (message.parentId !== null) {
 			let messageId =
@@ -232,6 +150,87 @@
 		}
 	};
 
+	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();
+				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();
+			}
+		}
+	};
+
 	const deleteMessage = async (messageId) => {
 		const messageToDelete = history.messages[messageId];
 		const parentMessageId = messageToDelete.parentId;
@@ -267,9 +266,7 @@
 		showMessage({ id: parentMessageId });
 
 		// Update the chat
-		await updateChatById(localStorage.token, chatId, {
-			history: history
-		});
+		await updateChatHistory();
 	};
 </script>
 
@@ -315,8 +312,19 @@
 	{:else}
 		<div class="w-full pt-2">
 			{#key chatId}
-				{JSON.stringify(history)}
-				<!-- <Message {chatId} {history} messageId={history.currentId} {user}  /> -->
+				<!-- {JSON.stringify(history)} -->
+				<div class="w-full flex flex-col-reverse">
+					<Message
+						h={0}
+						{chatId}
+						{history}
+						messageId={history.currentId}
+						{user}
+						{editMessage}
+						{deleteMessage}
+						{rateMessage}
+					/>
+				</div>
 				<div class="pb-12" />
 				{#if bottomPadding}
 					<div class="  pb-6" />

+ 53 - 34
src/lib/components/chat/Messages/Message.svelte

@@ -10,6 +10,10 @@
 	import MultiResponseMessages from './MultiResponseMessages.svelte';
 	import ResponseMessage from './ResponseMessage.svelte';
 	import UserMessage from './UserMessage.svelte';
+	import { updateChatById } from '$lib/apis/chats';
+
+	// h here refers to the height of the message graph
+	export let h;
 
 	export let chatId;
 
@@ -21,19 +25,15 @@
 	export let scrollToBottom;
 	export let chatActionHandler;
 
-	export let confirmEditMessage;
-	export let confirmEditResponseMessage;
-
 	export let showPreviousMessage;
 	export let showNextMessage;
 
-	export let rateMessage;
+	export let editMessage;
 	export let deleteMessage;
-
-	export let saveNewResponseMessage;
+	export let rateMessage;
 
 	export let regenerateResponse;
-	export let continueGeneration;
+	export let continueResponse;
 
 	// MultiResponseMessages
 	export let mergeResponses;
@@ -48,44 +48,39 @@
 	};
 </script>
 
-<div class="w-full">
-	<div
-		class="flex flex-col justify-between px-5 mb-3 {($settings?.widescreenMode ?? null)
-			? 'max-w-full'
-			: 'max-w-5xl'} mx-auto rounded-lg group"
-	>
-		{#if history.messages[messageId].role === 'user'}
-			{@const message = history.messages[messageId]}
+<div
+	class="flex flex-col justify-between px-5 mb-3 w-full {($settings?.widescreenMode ?? null)
+		? 'max-w-full'
+		: 'max-w-5xl'} mx-auto rounded-lg group"
+>
+	{#if history.messages[messageId]}
+		{@const message = history.messages[messageId]}
+		{#if message.role === 'user'}
 			<UserMessage
 				{user}
-				{history}
+				{message}
+				isFirstMessage={h === 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}
-				isFirstMessage={messageIdx === 0}
+				{editMessage}
 				on:delete={() => deleteMessage(message.id)}
 				{readOnly}
 			/>
 		{:else if (history.messages[message.parentId]?.models?.length ?? 1) === 1}
 			<ResponseMessage
 				{message}
+				isLastMessage={messageId === history.currentId}
 				siblings={history.messages[message.parentId]?.childrenIds ?? []}
-				isLastMessage={messageIdx + 1 === messages.length}
-				{readOnly}
-				{updateChatMessages}
-				{confirmEditResponseMessage}
-				{saveNewResponseMessage}
 				{showPreviousMessage}
 				{showNextMessage}
+				{editMessage}
 				{rateMessage}
-				copyToClipboard={copyToClipboardWithToast}
-				{continueGeneration}
+				{continueResponse}
 				{regenerateResponse}
 				on:action={async (e) => {
 					console.log('action', e);
@@ -96,6 +91,10 @@
 						await chatActionHandler(chatId, id, message.model, message.id, event);
 					}
 				}}
+				on:update={async (e) => {
+					console.log('update', e);
+					// call updateChatHistory
+				}}
 				on:save={async (e) => {
 					console.log('save', e);
 
@@ -111,6 +110,7 @@
 						});
 					}
 				}}
+				{readOnly}
 			/>
 		{:else}
 			<MultiResponseMessages
@@ -118,16 +118,14 @@
 				{chatId}
 				messageIds={[]}
 				parentMessage={history.messages[message.parentId]}
-				isLastMessage={messageIdx + 1 === messages.length}
-				{readOnly}
-				{updateChatMessages}
-				{saveNewResponseMessage}
-				{confirmEditResponseMessage}
+				isLastMessage={messageId === history.currentId}
+				{editMessage}
 				{rateMessage}
-				copyToClipboard={copyToClipboardWithToast}
-				{continueGeneration}
+				{continueResponse}
 				{mergeResponses}
 				{regenerateResponse}
+				copyToClipboard={copyToClipboardWithToast}
+				{readOnly}
 				on:action={async (e) => {
 					console.log('action', e);
 					if (typeof e.detail === 'string') {
@@ -152,5 +150,26 @@
 				}}
 			/>
 		{/if}
-	</div>
+	{/if}
 </div>
+
+{#if history.messages[messageId]?.parentId !== null}
+	<svelte:self
+		h={h + 1}
+		{chatId}
+		{history}
+		messageId={history.messages[messageId]?.parentId}
+		{user}
+		{scrollToBottom}
+		{chatActionHandler}
+		{showPreviousMessage}
+		{showNextMessage}
+		{editMessage}
+		{deleteMessage}
+		{rateMessage}
+		{regenerateResponse}
+		{continueResponse}
+		{mergeResponses}
+		{readOnly}
+	></svelte:self>
+{/if}

+ 2 - 2
src/lib/components/chat/Messages/MultiResponseMessages.svelte

@@ -34,7 +34,7 @@
 	export let rateMessage: Function;
 
 	export let copyToClipboard: Function;
-	export let continueGeneration: Function;
+	export let continueResponse: Function;
 	export let mergeResponses: Function;
 	export let regenerateResponse: Function;
 	export let saveNewResponseMessage: Function;
@@ -193,7 +193,7 @@
 								{readOnly}
 								{rateMessage}
 								{copyToClipboard}
-								{continueGeneration}
+								{continueResponse}
 								regenerateResponse={async (message) => {
 									regenerateResponse(message);
 									await tick();

+ 17 - 15
src/lib/components/chat/Messages/ResponseMessage.svelte

@@ -13,6 +13,7 @@
 	import { synthesizeOpenAISpeech } from '$lib/apis/audio';
 	import { imageGenerations } from '$lib/apis/images';
 	import {
+		copyToClipboard as _copyToClipboard,
 		approximateToHumanReadable,
 		extractParagraphsForAudio,
 		extractSentencesForAudio,
@@ -83,16 +84,15 @@
 
 	export let readOnly = false;
 
-	export let updateChatMessages: Function;
-	export let confirmEditResponseMessage: Function;
 	export let saveNewResponseMessage: Function = () => {};
 
 	export let showPreviousMessage: Function;
 	export let showNextMessage: Function;
+
+	export let editMessage: Function;
 	export let rateMessage: Function;
 
-	export let copyToClipboard: Function;
-	export let continueGeneration: Function;
+	export let continueResponse: Function;
 	export let regenerateResponse: Function;
 
 	let model = null;
@@ -111,6 +111,13 @@
 
 	let showRateComment = false;
 
+	const copyToClipboard = async (text) => {
+		const res = await _copyToClipboard(text);
+		if (res) {
+			toast.success($i18n.t('Copying to clipboard was successful!'));
+		}
+	};
+
 	const playAudio = (idx: number) => {
 		return new Promise<void>((res) => {
 			speakingIdx = idx;
@@ -260,11 +267,7 @@
 	};
 
 	const editMessageConfirmHandler = async () => {
-		if (editedContent === '') {
-			editedContent = ' ';
-		}
-
-		confirmEditResponseMessage(message.id, editedContent);
+		editMessage(message.id, editedContent ? editedContent : '', false);
 
 		edit = false;
 		editedContent = '';
@@ -272,8 +275,8 @@
 		await tick();
 	};
 
-	const saveNewMessageHandler = async () => {
-		saveNewResponseMessage(message, editedContent);
+	const saveAsCopyHandler = async () => {
+		editMessage(message.id, editedContent ? editedContent : '');
 
 		edit = false;
 		editedContent = '';
@@ -424,7 +427,7 @@
 											id="save-new-message-button"
 											class=" px-4 py-2 bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border dark:border-gray-700 text-gray-700 dark:text-gray-200 transition rounded-3xl"
 											on:click={() => {
-												saveNewMessageHandler();
+												saveAsCopyHandler();
 											}}
 										>
 											{$i18n.t('Save As Copy')}
@@ -909,7 +912,7 @@
 													? 'visible'
 													: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
 												on:click={() => {
-													continueGeneration();
+													continueResponse();
 
 													(model?.actions ?? [])
 														.filter((action) => action?.__webui__ ?? false)
@@ -1028,8 +1031,7 @@
 							bind:show={showRateComment}
 							bind:message
 							on:submit={(e) => {
-								updateChatMessages();
-
+								dispatch('update');
 								(model?.actions ?? [])
 									.filter((action) => action?.__webui__ ?? false)
 									.forEach((action) => {

+ 23 - 12
src/lib/components/chat/Messages/UserMessage.svelte

@@ -1,18 +1,20 @@
 <script lang="ts">
 	import dayjs from 'dayjs';
-
+	import { toast } from 'svelte-sonner';
 	import { tick, createEventDispatcher, getContext } from 'svelte';
+
+	import { models, settings } from '$lib/stores';
+	import { user as _user } from '$lib/stores';
+	import {
+		copyToClipboard as _copyToClipboard,
+		processResponseContent,
+		replaceTokens
+	} from '$lib/utils';
+
 	import Name from './Name.svelte';
 	import ProfileImage from './ProfileImage.svelte';
-	import { models, settings } from '$lib/stores';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
-
-	import { user as _user } from '$lib/stores';
-	import { getFileContentById } from '$lib/apis/files';
 	import FileItem from '$lib/components/common/FileItem.svelte';
-	import { marked } from 'marked';
-	import { processResponseContent, replaceTokens } from '$lib/utils';
-	import MarkdownTokens from './Markdown/MarkdownTokens.svelte';
 	import Markdown from './Markdown.svelte';
 
 	const i18n = getContext('i18n');
@@ -23,16 +25,25 @@
 	export let message;
 	export let siblings;
 	export let isFirstMessage: boolean;
-	export let readOnly: boolean;
 
-	export let confirmEditMessage: Function;
 	export let showPreviousMessage: Function;
 	export let showNextMessage: Function;
-	export let copyToClipboard: Function;
+
+	export let editMessage: Function;
+
+	export let readOnly: boolean;
 
 	let edit = false;
 	let editedContent = '';
 	let messageEditTextAreaElement: HTMLTextAreaElement;
+
+	const copyToClipboard = async (text) => {
+		const res = await _copyToClipboard(text);
+		if (res) {
+			toast.success($i18n.t('Copying to clipboard was successful!'));
+		}
+	};
+
 	const editMessageHandler = async () => {
 		edit = true;
 		editedContent = message.content;
@@ -46,7 +57,7 @@
 	};
 
 	const editMessageConfirmHandler = async (submit = true) => {
-		confirmEditMessage(message.id, editedContent, submit);
+		editMessage(message.id, editedContent, submit);
 
 		edit = false;
 		editedContent = '';

+ 1 - 1
src/routes/s/[id]/+page.svelte

@@ -155,7 +155,7 @@
 							bind:autoScroll
 							bottomPadding={files.length > 0}
 							sendPrompt={() => {}}
-							continueGeneration={() => {}}
+							continueResponse={() => {}}
 							regenerateResponse={() => {}}
 						/>
 					</div>