فهرست منبع

refac: deprecate messages for history

Timothy J. Baek 7 ماه پیش
والد
کامیت
fd5e8b4fcf

+ 79 - 88
src/lib/components/chat/Chat.svelte

@@ -5,6 +5,8 @@
 	import { PaneGroup, Pane, PaneResizer } from 'paneforge';
 
 	import { getContext, onDestroy, onMount, tick } from 'svelte';
+	const i18n: Writable<i18nType> = getContext('i18n');
+
 	import { goto } from '$app/navigation';
 	import { page } from '$app/stores';
 
@@ -67,11 +69,9 @@
 	import Navbar from '$lib/components/layout/Navbar.svelte';
 	import ChatControls from './ChatControls.svelte';
 	import EventConfirmDialog from '../common/ConfirmDialog.svelte';
-	import EllipsisVertical from '../icons/EllipsisVertical.svelte';
-
-	const i18n: Writable<i18nType> = getContext('i18n');
 
 	export let chatIdProp = '';
+
 	let loaded = false;
 	const eventTarget = new EventTarget();
 	let controlPane;
@@ -89,9 +89,10 @@
 	let eventConfirmationInputValue = '';
 	let eventCallback = null;
 
+	let chatIdUnsubscriber: Unsubscriber | undefined;
+
 	let selectedModels = [''];
 	let atSelectedModel: Model | undefined;
-
 	let selectedModelIds = [];
 	$: selectedModelIds = atSelectedModel !== undefined ? [atSelectedModel.id] : selectedModels;
 
@@ -102,35 +103,17 @@
 	let tags = [];
 
 	let title = '';
-	let prompt = '';
-
-	let chatFiles = [];
-	let files = [];
-	let messages = [];
 	let history = {
 		messages: {},
 		currentId: null
 	};
 
+	// Chat Input
+	let prompt = '';
+	let chatFiles = [];
+	let files = [];
 	let params = {};
 
-	let chatIdUnsubscriber: Unsubscriber | undefined;
-
-	$: if (history.currentId !== null) {
-		let _messages = [];
-		let currentMessage = history.messages[history.currentId];
-		while (currentMessage) {
-			_messages.unshift({ ...currentMessage });
-			currentMessage =
-				currentMessage.parentId !== null ? history.messages[currentMessage.parentId] : null;
-		}
-
-		// This is most likely causing the performance issue
-		messages = _messages;
-	} else {
-		messages = [];
-	}
-
 	$: if (chatIdProp) {
 		(async () => {
 			console.log(chatIdProp);
@@ -227,8 +210,6 @@
 			} else {
 				console.log('Unknown message type', data);
 			}
-
-			messages = messages;
 		}
 	};
 
@@ -310,6 +291,9 @@
 				showOverview.set(false);
 			}
 		});
+
+		const chatInput = document.getElementById('chat-textarea');
+		chatInput?.focus();
 	});
 
 	onDestroy(() => {
@@ -331,7 +315,6 @@
 		autoScroll = true;
 
 		title = '';
-		messages = [];
 		history = {
 			messages: {},
 			currentId: null
@@ -428,8 +411,8 @@
 				autoScroll = true;
 				await tick();
 
-				if (messages.length > 0) {
-					history.messages[messages.at(-1).id].done = true;
+				if (history.currentId) {
+					history.messages[history.currentId].done = true;
 				}
 				await tick();
 
@@ -448,8 +431,12 @@
 	};
 
 	const createMessagesList = (responseMessageId) => {
+		if (responseMessageId === null) {
+			return [];
+		}
+
 		const message = history.messages[responseMessageId];
-		if (message.parentId) {
+		if (message?.parentId) {
 			return [...createMessagesList(message.parentId), message];
 		} else {
 			return [message];
@@ -510,6 +497,8 @@
 	};
 
 	const chatActionHandler = async (chatId, actionId, modelId, responseMessageId, event = null) => {
+		const messages = createMessagesList(responseMessageId);
+
 		const res = await chatAction(localStorage.token, actionId, {
 			model: modelId,
 			messages: messages.map((m) => ({
@@ -575,6 +564,7 @@
 	const submitPrompt = async (userPrompt, { _raw = false } = {}) => {
 		let _responses = [];
 		console.log('submitPrompt', $chatId);
+		const messages = createMessagesList(history.currentId);
 
 		selectedModels = selectedModels.map((modelId) =>
 			$models.map((m) => m.id).includes(modelId) ? modelId : ''
@@ -668,8 +658,34 @@
 		parentId: string,
 		{ modelId = null, modelIdx = null, newChat = false } = {}
 	) => {
-		let _responses: string[] = [];
+		// Create new chat if newChat is true and first user message
+		if (
+			newChat &&
+			history.messages[history.currentId].parentId === null &&
+			history.messages[history.currentId].role === 'user'
+		) {
+			if (!$temporaryChatEnabled) {
+				chat = await createNewChat(localStorage.token, {
+					id: $chatId,
+					title: $i18n.t('New Chat'),
+					models: selectedModels,
+					system: $settings.system ?? undefined,
+					params: params,
+					history: history,
+					tags: [],
+					timestamp: Date.now()
+				});
+
+				currentChatPage.set(1);
+				await chats.set(await getChatList(localStorage.token, $currentChatPage));
+				await chatId.set(chat.id);
+			} else {
+				await chatId.set('local');
+			}
+			await tick();
+		}
 
+		let _responses: string[] = [];
 		// If modelId is provided, use it, else use selected model
 		let selectedModelIds = modelId
 			? [modelId]
@@ -714,38 +730,14 @@
 		}
 		await tick();
 
-		// Create new chat if only one message in messages
-		if (newChat && messages.length == 2) {
-			if (!$temporaryChatEnabled) {
-				chat = await createNewChat(localStorage.token, {
-					id: $chatId,
-					title: $i18n.t('New Chat'),
-					models: selectedModels,
-					system: $settings.system ?? undefined,
-					params: params,
-					messages: messages,
-					history: history,
-					tags: [],
-					timestamp: Date.now()
-				});
-
-				currentChatPage.set(1);
-				await chats.set(await getChatList(localStorage.token, $currentChatPage));
-				await chatId.set(chat.id);
-			} else {
-				await chatId.set('local');
-			}
-			await tick();
-		}
-
 		const _chatId = JSON.parse(JSON.stringify($chatId));
-
 		await Promise.all(
 			selectedModelIds.map(async (modelId, _modelIdx) => {
 				console.log('modelId', modelId);
 				const model = $models.filter((m) => m.id === modelId).at(0);
 
 				if (model) {
+					const messages = createMessagesList(parentId);
 					// If there are image files, check if model is vision capable
 					const hasImages = messages.some((message) =>
 						message.files?.some((file) => file.type === 'image')
@@ -844,7 +836,7 @@
 						}`
 					}
 				: undefined,
-			...messages
+			...createMessagesList(responseMessageId)
 		]
 			.filter((message) => message?.content?.trim())
 			.map((message) => {
@@ -895,7 +887,7 @@
 				}
 			];
 			files.push(...model.info.meta.knowledge);
-			messages = messages; // Trigger Svelte update
+			history.messages[responseMessageId] = responseMessage;
 		}
 		files.push(
 			...(userMessage?.files ?? []).filter((item) =>
@@ -969,7 +961,7 @@
 					const { value, done } = await reader.read();
 					if (done || stopResponseFlag || _chatId !== $chatId) {
 						responseMessage.done = true;
-						messages = messages;
+						history.messages[responseMessageId] = responseMessage;
 
 						if (stopResponseFlag) {
 							controller.abort('User: Stop Response');
@@ -1036,7 +1028,7 @@
 											);
 										}
 
-										messages = messages;
+										history.messages[responseMessageId] = responseMessage;
 									}
 								} else {
 									responseMessage.done = true;
@@ -1059,7 +1051,8 @@
 										eval_count: data.eval_count,
 										eval_duration: data.eval_duration
 									};
-									messages = messages;
+
+									history.messages[responseMessageId] = responseMessage;
 
 									if ($settings.notificationEnabled && !document.hasFocus()) {
 										const notification = new Notification(`${model.id}`, {
@@ -1128,7 +1121,7 @@
 				);
 			}
 
-			messages = messages;
+			history.messages[responseMessageId] = responseMessage;
 		}
 		await saveChatHandler(_chatId);
 
@@ -1161,7 +1154,8 @@
 			scrollToBottom();
 		}
 
-		if (messages.length == 2 && messages.at(1).content !== '' && selectedModels[0] === model.id) {
+		const messages = createMessagesList(responseMessageId);
+		if (messages.length == 2 && messages.at(-1).content !== '' && selectedModels[0] === model.id) {
 			window.history.replaceState(history.state, '', `/c/${_chatId}`);
 			const _title = await generateChatTitle(userPrompt);
 			await setChatTitle(_chatId, _title);
@@ -1189,7 +1183,7 @@
 				}
 			];
 			files.push(...model.info.meta.knowledge);
-			messages = messages; // Trigger Svelte update
+			history.messages[responseMessageId] = responseMessage;
 		}
 		files.push(
 			...(userMessage?.files ?? []).filter((item) =>
@@ -1240,7 +1234,7 @@
 									}`
 								}
 							: undefined,
-						...messages
+						...createMessagesList(responseMessageId)
 					]
 						.filter((message) => message?.content?.trim())
 						.map((message, idx, arr) => ({
@@ -1318,7 +1312,7 @@
 						}
 						if (done || stopResponseFlag || _chatId !== $chatId) {
 							responseMessage.done = true;
-							messages = messages;
+							history.messages[responseMessageId] = responseMessage;
 
 							if (stopResponseFlag) {
 								controller.abort('User: Stop Response');
@@ -1373,7 +1367,7 @@
 								);
 							}
 
-							messages = messages;
+							history.messages[responseMessageId] = responseMessage;
 						}
 
 						if (autoScroll) {
@@ -1414,7 +1408,7 @@
 
 		await saveChatHandler(_chatId);
 
-		messages = messages;
+		history.messages[responseMessageId] = responseMessage;
 
 		stopResponseFlag = false;
 		await tick();
@@ -1445,9 +1439,9 @@
 			scrollToBottom();
 		}
 
+		const messages = createMessagesList(responseMessageId);
 		if (messages.length == 2 && selectedModels[0] === model.id) {
 			window.history.replaceState(history.state, '', `/c/${_chatId}`);
-
 			const _title = await generateChatTitle(userPrompt);
 			await setChatTitle(_chatId, _title);
 		}
@@ -1497,7 +1491,7 @@
 			);
 		}
 
-		messages = messages;
+		history.messages[responseMessageId] = responseMessage;
 	};
 
 	const stopResponse = () => {
@@ -1508,7 +1502,7 @@
 	const regenerateResponse = async (message) => {
 		console.log('regenerateResponse');
 
-		if (messages.length != 0) {
+		if (history.currentId) {
 			let userMessage = history.messages[message.parentId];
 			let userPrompt = userMessage.content;
 
@@ -1530,7 +1524,7 @@
 		console.log('continueGeneration');
 		const _chatId = JSON.parse(JSON.stringify($chatId));
 
-		if (messages.length != 0 && messages.at(-1).done == true) {
+		if (history.currentId && history.messages[history.currentId].done == true) {
 			const responseMessage = history.messages[history.currentId];
 			responseMessage.done = false;
 			await tick();
@@ -1600,7 +1594,7 @@
 				description: $i18n.t('Generating search query')
 			}
 		];
-		messages = messages;
+		history.messages[responseMessageId] = responseMessage;
 
 		const prompt = userMessage.content;
 		let searchQuery = await generateSearchQuery(
@@ -1620,7 +1614,7 @@
 				action: 'web_search',
 				description: $i18n.t('No search query generated')
 			});
-			messages = messages;
+			history.messages[responseMessageId] = responseMessage;
 			return;
 		}
 
@@ -1629,7 +1623,7 @@
 			action: 'web_search',
 			description: $i18n.t(`Searching "{{searchQuery}}"`, { searchQuery })
 		});
-		messages = messages;
+		history.messages[responseMessageId] = responseMessage;
 
 		const results = await runWebSearch(localStorage.token, searchQuery).catch((error) => {
 			console.log(error);
@@ -1657,8 +1651,7 @@
 				type: 'web_search_results',
 				urls: results.filenames
 			});
-
-			messages = messages;
+			history.messages[responseMessageId] = responseMessage;
 		} else {
 			responseMessage.statusHistory.push({
 				done: true,
@@ -1666,7 +1659,7 @@
 				action: 'web_search',
 				description: 'No search results found'
 			});
-			messages = messages;
+			history.messages[responseMessageId] = responseMessage;
 		}
 	};
 
@@ -1680,9 +1673,8 @@
 		if ($chatId == _chatId) {
 			if (!$temporaryChatEnabled) {
 				chat = await updateChatById(localStorage.token, _chatId, {
-					messages: messages,
-					history: history,
 					models: selectedModels,
+					history: history,
 					params: params,
 					files: chatFiles
 				});
@@ -1700,7 +1692,7 @@
 			content: ''
 		};
 		message.merged = mergedResponse;
-		messages = messages;
+		history.messages[messageId] = message;
 
 		try {
 			const [res, controller] = await generateMoACompletion(
@@ -1722,7 +1714,7 @@
 						continue;
 					} else {
 						mergedResponse.content += value;
-						messages = messages;
+						history.messages[messageId] = message;
 					}
 
 					if (autoScroll) {
@@ -1788,11 +1780,11 @@
 			/>
 		{/if}
 
-		<Navbar {chat} {title} bind:selectedModels shareEnabled={messages.length > 0} {initNewChat} />
+		<Navbar {chat} {title} bind:selectedModels shareEnabled={!!history.currentId} {initNewChat} />
 
 		<PaneGroup direction="horizontal" class="w-full h-full">
 			<Pane defaultSize={50} class="h-full flex w-full relative">
-				{#if $banners.length > 0 && messages.length === 0 && !$chatId && selectedModels.length <= 1}
+				{#if $banners.length > 0 && !history.currentId && !$chatId && selectedModels.length <= 1}
 					<div class="absolute top-3 left-0 right-0 w-full z-20">
 						<div class=" flex flex-col gap-1 w-full">
 							{#each $banners.filter( (b) => (b.dismissible ? !JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]').includes(b.id) : true) ) as banner}
@@ -1834,7 +1826,6 @@
 								bind:history
 								bind:autoScroll
 								bind:prompt
-								{messages}
 								{selectedModels}
 								{processing}
 								{sendPrompt}
@@ -1850,13 +1841,13 @@
 
 					<div class="">
 						<MessageInput
+							{history}
 							bind:files
 							bind:prompt
 							bind:autoScroll
 							bind:selectedToolIds
 							bind:webSearchEnabled
 							bind:atSelectedModel
-							{messages}
 							{selectedModels}
 							availableToolIds={selectedModelIds.reduce((a, e, i, arr) => {
 								const model = $models.find((m) => m.id === e);

+ 6 - 7
src/lib/components/chat/MessageInput.svelte

@@ -61,15 +61,14 @@
 	let user = null;
 	let chatInputPlaceholder = '';
 
-	export let files = [];
+	export let history;
 
+	export let prompt = '';
+	export let files = [];
 	export let availableToolIds = [];
 	export let selectedToolIds = [];
 	export let webSearchEnabled = false;
 
-	export let prompt = '';
-	export let messages = [];
-
 	let visionCapableModels = [];
 	$: visionCapableModels = [...(atSelectedModel ? [atSelectedModel] : selectedModels)].filter(
 		(model) => $models.find((m) => m.id === model)?.info?.meta?.capabilities?.vision ?? true
@@ -272,7 +271,7 @@
 	<div class=" -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
 		<div class="flex flex-col max-w-6xl px-2.5 md:px-6 w-full">
 			<div class="relative">
-				{#if autoScroll === false && messages.length > 0}
+				{#if autoScroll === false && !history?.currentId}
 					<div
 						class=" absolute -top-12 left-0 right-0 flex justify-center z-30 pointer-events-none"
 					>
@@ -692,7 +691,7 @@
 								/>
 
 								<div class="self-end mb-2 flex space-x-1 mr-1">
-									{#if messages.length == 0 || messages.at(-1).done == true}
+									{#if !history?.currentId || history.messages[history.currentId].done == true}
 										<Tooltip content={$i18n.t('Record voice')}>
 											<button
 												id="voice-input-button"
@@ -744,7 +743,7 @@
 							</div>
 						</div>
 						<div class="flex items-end w-10">
-							{#if messages.length == 0 || messages.at(-1).done == true}
+							{#if !history.currentId || history.messages[history.currentId].done == true}
 								{#if prompt === ''}
 									<div class=" flex items-center mb-1">
 										<Tooltip content={$i18n.t('Call')}>

+ 14 - 133
src/lib/components/chat/Messages.svelte

@@ -7,10 +7,8 @@
 	import { getChatList, updateChatById } from '$lib/apis/chats';
 	import { copyToClipboard, findWordIndices } from '$lib/utils';
 
-	import UserMessage from './Messages/UserMessage.svelte';
-	import ResponseMessage from './Messages/ResponseMessage.svelte';
 	import Placeholder from './Messages/Placeholder.svelte';
-	import MultiResponseMessages from './Messages/MultiResponseMessages.svelte';
+	import Message from './Messages/Message.svelte';
 
 	const i18n = getContext('i18n');
 
@@ -45,11 +43,14 @@
 		element.scrollTop = element.scrollHeight;
 	};
 
-	const copyToClipboardWithToast = async (text) => {
-		const res = await copyToClipboard(text);
-		if (res) {
-			toast.success($i18n.t('Copying to clipboard was successful!'));
-		}
+	const updateChatMessages = async () => {
+		await tick();
+		await updateChatById(localStorage.token, chatId, {
+			history: history
+		});
+
+		currentChatPage.set(1);
+		await chats.set(await getChatList(localStorage.token, $currentChatPage));
 	};
 
 	const confirmEditMessage = async (messageId, content, submit = true) => {
@@ -85,23 +86,11 @@
 			history.messages[messageId].content = content;
 			await tick();
 			await updateChatById(localStorage.token, chatId, {
-				messages: messages,
 				history: history
 			});
 		}
 	};
 
-	const updateChatMessages = async () => {
-		await tick();
-		await updateChatById(localStorage.token, chatId, {
-			messages: messages,
-			history: history
-		});
-
-		currentChatPage.set(1);
-		await chats.set(await getChatList(localStorage.token, $currentChatPage));
-	};
-
 	const confirmEditResponseMessage = async (messageId, content) => {
 		history.messages[messageId].originalContent = history.messages[messageId].content;
 		history.messages[messageId].content = content;
@@ -243,7 +232,7 @@
 		}
 	};
 
-	const deleteMessageHandler = async (messageId) => {
+	const deleteMessage = async (messageId) => {
 		const messageToDelete = history.messages[messageId];
 		const parentMessageId = messageToDelete.parentId;
 		const childMessageIds = messageToDelete.childrenIds ?? [];
@@ -279,14 +268,13 @@
 
 		// Update the chat
 		await updateChatById(localStorage.token, chatId, {
-			messages: messages,
 			history: history
 		});
 	};
 </script>
 
 <div class="h-full flex">
-	{#if messages.length == 0}
+	{#if Object.keys(history?.messages ?? {}).length == 0}
 		<Placeholder
 			modelIds={selectedModels}
 			submitPrompt={async (p) => {
@@ -327,116 +315,9 @@
 	{:else}
 		<div class="w-full pt-2">
 			{#key chatId}
-				{#each messages as message, messageIdx (message.id)}
-					<div class=" w-full {messageIdx === messages.length - 1 ? ' pb-12' : ''}">
-						<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 message.role === 'user'}
-								<UserMessage
-									on:delete={() => deleteMessageHandler(message.id)}
-									{user}
-									{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}
-								/>
-							{:else if (history.messages[message.parentId]?.models?.length ?? 1) === 1}
-								{#key message.id}
-									<ResponseMessage
-										{message}
-										siblings={history.messages[message.parentId]?.childrenIds ?? []}
-										isLastMessage={messageIdx + 1 === messages.length}
-										{readOnly}
-										{updateChatMessages}
-										{confirmEditResponseMessage}
-										{saveNewResponseMessage}
-										{showPreviousMessage}
-										{showNextMessage}
-										{rateMessage}
-										copyToClipboard={copyToClipboardWithToast}
-										{continueGeneration}
-										{regenerateResponse}
-										on:action={async (e) => {
-											console.log('action', e);
-											if (typeof e.detail === 'string') {
-												await chatActionHandler(chatId, e.detail, message.model, message.id);
-											} else {
-												const { id, event } = e.detail;
-												await chatActionHandler(chatId, id, message.model, message.id, event);
-											}
-										}}
-										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}
-							{:else}
-								{#key message.parentId}
-									<MultiResponseMessages
-										bind:history
-										isLastMessage={messageIdx + 1 === messages.length}
-										{messages}
-										{readOnly}
-										{chatId}
-										parentMessage={history.messages[message.parentId]}
-										{messageIdx}
-										{updateChatMessages}
-										{saveNewResponseMessage}
-										{confirmEditResponseMessage}
-										{rateMessage}
-										copyToClipboard={copyToClipboardWithToast}
-										{continueGeneration}
-										{mergeResponses}
-										{regenerateResponse}
-										on:action={async (e) => {
-											console.log('action', e);
-											if (typeof e.detail === 'string') {
-												await chatActionHandler(chatId, e.detail, message.model, message.id);
-											} else {
-												const { id, event } = e.detail;
-												await chatActionHandler(chatId, id, message.model, message.id, event);
-											}
-										}}
-										on:change={async () => {
-											await updateChatById(localStorage.token, chatId, {
-												messages: messages,
-												history: history
-											});
-
-											if (autoScroll) {
-												const element = document.getElementById('messages-container');
-												autoScroll =
-													element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
-												setTimeout(() => {
-													scrollToBottom();
-												}, 100);
-											}
-										}}
-									/>
-								{/key}
-							{/if}
-						</div>
-					</div>
-				{/each}
-
+				{JSON.stringify(history)}
+				<!-- <Message {chatId} {history} messageId={history.currentId} {user}  /> -->
+				<div class="pb-12" />
 				{#if bottomPadding}
 					<div class="  pb-6" />
 				{/if}

+ 156 - 0
src/lib/components/chat/Messages/Message.svelte

@@ -0,0 +1,156 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+
+	import { tick, getContext, onMount } from 'svelte';
+	const i18n = getContext('i18n');
+
+	import { settings } from '$lib/stores';
+	import { copyToClipboard } from '$lib/utils';
+
+	import MultiResponseMessages from './MultiResponseMessages.svelte';
+	import ResponseMessage from './ResponseMessage.svelte';
+	import UserMessage from './UserMessage.svelte';
+
+	export let chatId;
+
+	export let history;
+	export let messageId;
+
+	export let user;
+
+	export let scrollToBottom;
+	export let chatActionHandler;
+
+	export let confirmEditMessage;
+	export let confirmEditResponseMessage;
+
+	export let showPreviousMessage;
+	export let showNextMessage;
+
+	export let rateMessage;
+	export let deleteMessage;
+
+	export let saveNewResponseMessage;
+
+	export let regenerateResponse;
+	export let continueGeneration;
+
+	// MultiResponseMessages
+	export let mergeResponses;
+
+	export let readOnly = false;
+
+	const copyToClipboardWithToast = async (text) => {
+		const res = await copyToClipboard(text);
+		if (res) {
+			toast.success($i18n.t('Copying to clipboard was successful!'));
+		}
+	};
+</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]}
+			<UserMessage
+				{user}
+				{history}
+				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}
+				on:delete={() => deleteMessage(message.id)}
+				{readOnly}
+			/>
+		{:else if (history.messages[message.parentId]?.models?.length ?? 1) === 1}
+			<ResponseMessage
+				{message}
+				siblings={history.messages[message.parentId]?.childrenIds ?? []}
+				isLastMessage={messageIdx + 1 === messages.length}
+				{readOnly}
+				{updateChatMessages}
+				{confirmEditResponseMessage}
+				{saveNewResponseMessage}
+				{showPreviousMessage}
+				{showNextMessage}
+				{rateMessage}
+				copyToClipboard={copyToClipboardWithToast}
+				{continueGeneration}
+				{regenerateResponse}
+				on:action={async (e) => {
+					console.log('action', e);
+					if (typeof e.detail === 'string') {
+						await chatActionHandler(chatId, e.detail, message.model, message.id);
+					} else {
+						const { id, event } = e.detail;
+						await chatActionHandler(chatId, id, message.model, message.id, event);
+					}
+				}}
+				on:save={async (e) => {
+					console.log('save', e);
+
+					const message = e.detail;
+					if (message) {
+						history.messages[message.id] = message;
+						await updateChatById(localStorage.token, chatId, {
+							history: history
+						});
+					} else {
+						await updateChatById(localStorage.token, chatId, {
+							history: history
+						});
+					}
+				}}
+			/>
+		{:else}
+			<MultiResponseMessages
+				bind:history
+				{chatId}
+				messageIds={[]}
+				parentMessage={history.messages[message.parentId]}
+				isLastMessage={messageIdx + 1 === messages.length}
+				{readOnly}
+				{updateChatMessages}
+				{saveNewResponseMessage}
+				{confirmEditResponseMessage}
+				{rateMessage}
+				copyToClipboard={copyToClipboardWithToast}
+				{continueGeneration}
+				{mergeResponses}
+				{regenerateResponse}
+				on:action={async (e) => {
+					console.log('action', e);
+					if (typeof e.detail === 'string') {
+						await chatActionHandler(chatId, e.detail, message.model, message.id);
+					} else {
+						const { id, event } = e.detail;
+						await chatActionHandler(chatId, id, message.model, message.id, event);
+					}
+				}}
+				on:change={async () => {
+					await updateChatById(localStorage.token, chatId, {
+						history: history
+					});
+
+					if (autoScroll) {
+						const element = document.getElementById('messages-container');
+						autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
+						setTimeout(() => {
+							scrollToBottom();
+						}, 100);
+					}
+				}}
+			/>
+		{/if}
+	</div>
+</div>

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

@@ -22,7 +22,6 @@
 	export let chatId;
 
 	export let history;
-	export let messages = [];
 	export let messageIdx;
 
 	export let parentMessage;
@@ -46,7 +45,9 @@
 	let groupedMessages = {};
 	let groupedMessagesIdx = {};
 
-	$: if (parentMessage) {
+	$: if (history.messages) {
+		console.log('history.messages', history.messages);
+
 		initHandler();
 	}
 
@@ -87,6 +88,7 @@
 	};
 
 	const initHandler = async () => {
+		console.log('multiresponse:initHandler');
 		await tick();
 		currentMessageId = messages[messageIdx].id;
 
@@ -146,78 +148,76 @@
 		class="flex snap-x snap-mandatory overflow-x-auto scrollbar-hidden"
 		id="responses-container-{chatId}-{parentMessage.id}"
 	>
-		{#key currentMessageId}
-			{#each Object.keys(groupedMessages) as modelIdx}
-				{#if groupedMessagesIdx[modelIdx] !== undefined && groupedMessages[modelIdx].messages.length > 0}
-					<!-- svelte-ignore a11y-no-static-element-interactions -->
-					<!-- svelte-ignore a11y-click-events-have-key-events -->
-					{@const message = groupedMessages[modelIdx].messages[groupedMessagesIdx[modelIdx]]}
-
-					<div
-						class=" snap-center w-full max-w-full m-1 border {history.messages[currentMessageId]
-							?.modelIdx == modelIdx
-							? `border-gray-100 dark:border-gray-800 border-[1.5px] ${
-									$mobile ? 'min-w-full' : 'min-w-[32rem]'
-								}`
-							: `border-gray-50 dark:border-gray-850 border-dashed ${
-									$mobile ? 'min-w-full' : 'min-w-80'
-								}`} transition-all p-5 rounded-2xl"
-						on:click={() => {
-							if (currentMessageId != message.id) {
-								currentMessageId = message.id;
-								let messageId = message.id;
-								console.log(messageId);
-								//
-								let messageChildrenIds = history.messages[messageId].childrenIds;
-								while (messageChildrenIds.length !== 0) {
-									messageId = messageChildrenIds.at(-1);
-									messageChildrenIds = history.messages[messageId].childrenIds;
-								}
-								history.currentId = messageId;
-								dispatch('change');
+		{#each Object.keys(groupedMessages) as modelIdx}
+			{#if groupedMessagesIdx[modelIdx] !== undefined && groupedMessages[modelIdx].messages.length > 0}
+				<!-- svelte-ignore a11y-no-static-element-interactions -->
+				<!-- svelte-ignore a11y-click-events-have-key-events -->
+				{@const message = groupedMessages[modelIdx].messages[groupedMessagesIdx[modelIdx]]}
+
+				<div
+					class=" snap-center w-full max-w-full m-1 border {history.messages[currentMessageId]
+						?.modelIdx == modelIdx
+						? `border-gray-100 dark:border-gray-800 border-[1.5px] ${
+								$mobile ? 'min-w-full' : 'min-w-[32rem]'
+							}`
+						: `border-gray-50 dark:border-gray-850 border-dashed ${
+								$mobile ? 'min-w-full' : 'min-w-80'
+							}`} transition-all p-5 rounded-2xl"
+					on:click={() => {
+						if (currentMessageId != message.id) {
+							currentMessageId = message.id;
+							let messageId = message.id;
+							console.log(messageId);
+							//
+							let messageChildrenIds = history.messages[messageId].childrenIds;
+							while (messageChildrenIds.length !== 0) {
+								messageId = messageChildrenIds.at(-1);
+								messageChildrenIds = history.messages[messageId].childrenIds;
 							}
-						}}
-					>
-						{#key history.currentId}
-							{#if message}
-								<ResponseMessage
-									{message}
-									siblings={groupedMessages[modelIdx].messages.map((m) => m.id)}
-									isLastMessage={true}
-									{updateChatMessages}
-									{saveNewResponseMessage}
-									{confirmEditResponseMessage}
-									showPreviousMessage={() => showPreviousMessage(modelIdx)}
-									showNextMessage={() => showNextMessage(modelIdx)}
-									{readOnly}
-									{rateMessage}
-									{copyToClipboard}
-									{continueGeneration}
-									regenerateResponse={async (message) => {
-										regenerateResponse(message);
-										await tick();
-										groupedMessagesIdx[modelIdx] = groupedMessages[modelIdx].messages.length - 1;
-									}}
-									on:action={async (e) => {
-										dispatch('action', e.detail);
-									}}
-									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}
-						{/key}
-					</div>
-				{/if}
-			{/each}
-		{/key}
+							history.currentId = messageId;
+							dispatch('change');
+						}
+					}}
+				>
+					{#key history.currentId}
+						{#if message}
+							<ResponseMessage
+								{message}
+								siblings={groupedMessages[modelIdx].messages.map((m) => m.id)}
+								isLastMessage={true}
+								{updateChatMessages}
+								{saveNewResponseMessage}
+								{confirmEditResponseMessage}
+								showPreviousMessage={() => showPreviousMessage(modelIdx)}
+								showNextMessage={() => showNextMessage(modelIdx)}
+								{readOnly}
+								{rateMessage}
+								{copyToClipboard}
+								{continueGeneration}
+								regenerateResponse={async (message) => {
+									regenerateResponse(message);
+									await tick();
+									groupedMessagesIdx[modelIdx] = groupedMessages[modelIdx].messages.length - 1;
+								}}
+								on:action={async (e) => {
+									dispatch('action', e.detail);
+								}}
+								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}
+					{/key}
+				</div>
+			{/if}
+		{/each}
 	</div>
 
 	{#if !readOnly && isLastMessage}