瀏覽代碼

feat/enh: regeneration options

Timothy Jaeryang Baek 2 月之前
父節點
當前提交
92f27bb21e

+ 56 - 27
src/lib/components/chat/Chat.svelte

@@ -1484,14 +1484,23 @@
 
 		saveSessionSelectedModels();
 
-		await sendPrompt(history, userPrompt, userMessageId, { newChat: true });
+		await sendMessage(history, userMessageId, { newChat: true });
 	};
 
-	const sendPrompt = async (
+	const sendMessage = async (
 		_history,
-		prompt: string,
 		parentId: string,
-		{ modelId = null, modelIdx = null, newChat = false } = {}
+		{
+			messages = null,
+			modelId = null,
+			modelIdx = null,
+			newChat = false
+		}: {
+			messages?: any[] | null;
+			modelId?: string | null;
+			modelIdx?: number | null;
+			newChat?: boolean;
+		} = {}
 	) => {
 		if (autoScroll) {
 			scrollToBottom();
@@ -1561,9 +1570,8 @@
 				const model = $models.filter((m) => m.id === modelId).at(0);
 
 				if (model) {
-					const messages = createMessagesList(_history, parentId);
 					// If there are image files, check if model is vision capable
-					const hasImages = messages.some((message) =>
+					const hasImages = createMessagesList(_history, parentId).some((message) =>
 						message.files?.some((file) => file.type === 'image')
 					);
 
@@ -1580,7 +1588,15 @@
 					const chatEventEmitter = await getChatEventEmitter(model.id, _chatId);
 
 					scrollToBottom();
-					await sendPromptSocket(_history, model, responseMessageId, _chatId);
+					await sendMessageSocket(
+						model,
+						messages && messages.length > 0
+							? messages
+							: createMessagesList(_history, responseMessageId),
+						_history,
+						responseMessageId,
+						_chatId
+					);
 
 					if (chatEventEmitter) clearInterval(chatEventEmitter);
 				} else {
@@ -1593,12 +1609,11 @@
 		chats.set(await getChatList(localStorage.token, $currentChatPage));
 	};
 
-	const sendPromptSocket = async (_history, model, responseMessageId, _chatId) => {
-		const chatMessages = createMessagesList(history, history.currentId);
+	const sendMessageSocket = async (model, _messages, _history, responseMessageId, _chatId) => {
 		const responseMessage = _history.messages[responseMessageId];
 		const userMessage = _history.messages[responseMessage.parentId];
 
-		const chatMessageFiles = chatMessages
+		const chatMessageFiles = _messages
 			.filter((message) => message.files)
 			.flatMap((message) => message.files);
 
@@ -1652,7 +1667,7 @@
 						)}`
 					}
 				: undefined,
-			...createMessagesList(_history, responseMessageId).map((message) => ({
+			..._messages.map((message) => ({
 				...message,
 				content: processDetails(message.content)
 			}))
@@ -1900,31 +1915,39 @@
 			scrollToBottom();
 		}
 
-		await sendPrompt(history, userPrompt, userMessageId);
+		await sendMessage(history, userMessageId);
 	};
 
-	const regenerateResponse = async (message) => {
+	const regenerateResponse = async (message, suggestionPrompt = null) => {
 		console.log('regenerateResponse');
 
 		if (history.currentId) {
 			let userMessage = history.messages[message.parentId];
-			let userPrompt = userMessage.content;
 
 			if (autoScroll) {
 				scrollToBottom();
 			}
 
-			if ((userMessage?.models ?? [...selectedModels]).length == 1) {
-				// If user message has only one model selected, sendPrompt automatically selects it for regeneration
-				await sendPrompt(history, userPrompt, userMessage.id);
-			} else {
-				// If there are multiple models selected, use the model of the response message for regeneration
-				// e.g. many model chat
-				await sendPrompt(history, userPrompt, userMessage.id, {
-					modelId: message.model,
-					modelIdx: message.modelIdx
-				});
-			}
+			await sendMessage(history, userMessage.id, {
+				...(suggestionPrompt
+					? {
+							messages: [
+								...createMessagesList(history, message.id),
+								{
+									role: 'user',
+									content: suggestionPrompt
+								}
+							]
+						}
+					: {}),
+				...((userMessage?.models ?? [...selectedModels]).length > 1
+					? {
+							// If multiple models are selected, use the model from the message
+							modelId: message.model,
+							modelIdx: message.modelIdx
+						}
+					: {})
+			});
 		}
 	};
 
@@ -1942,7 +1965,13 @@
 				.at(0);
 
 			if (model) {
-				await sendPromptSocket(history, model, responseMessage.id, _chatId);
+				await sendMessageSocket(
+					model,
+					createMessagesList(history, responseMessage.id),
+					history,
+					responseMessage.id,
+					_chatId
+				);
 			}
 		}
 	};
@@ -2171,7 +2200,7 @@
 										}}
 										{selectedModels}
 										{atSelectedModel}
-										{sendPrompt}
+										{sendMessage}
 										{showMessage}
 										{submitMessage}
 										{continueResponse}

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

@@ -38,7 +38,7 @@
 
 	export let setInputText: Function = () => {};
 
-	export let sendPrompt: Function;
+	export let sendMessage: Function;
 	export let continueResponse: Function;
 	export let regenerateResponse: Function;
 	export let mergeResponses: Function;
@@ -294,7 +294,7 @@
 				history.currentId = userMessageId;
 
 				await tick();
-				await sendPrompt(history, userPrompt, userMessageId);
+				await sendMessage(history, userMessageId);
 			} else {
 				// Edit user message
 				history.messages[messageId].content = content;

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

@@ -313,8 +313,8 @@
 									{actionMessage}
 									{submitMessage}
 									{continueResponse}
-									regenerateResponse={async (message) => {
-										regenerateResponse(message);
+									regenerateResponse={async (message, prompt = null) => {
+										regenerateResponse(message, prompt);
 										await tick();
 										groupedMessageIdsIdx[selectedModelIdx] =
 											groupedMessageIds[selectedModelIdx].messageIds.length - 1;
@@ -368,8 +368,8 @@
 										{actionMessage}
 										{submitMessage}
 										{continueResponse}
-										regenerateResponse={async (message) => {
-											regenerateResponse(message);
+										regenerateResponse={async (message, prompt = null) => {
+											regenerateResponse(message, prompt);
 											await tick();
 											groupedMessageIdsIdx[modelIdx] =
 												groupedMessageIds[modelIdx].messageIds.length - 1;
@@ -428,7 +428,7 @@
 									id="merge-response-button"
 									class="{true
 										? 'visible'
-										: 'invisible group-hover:visible'} p-1 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
+										: 'invisible group-hover:visible'} p-1 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition"
 									on:click={() => {
 										mergeResponsesHandler();
 									}}

+ 64 - 40
src/lib/components/chat/Messages/ResponseMessage.svelte

@@ -51,6 +51,7 @@
 	import FollowUps from './ResponseMessage/FollowUps.svelte';
 	import { fade } from 'svelte/transition';
 	import { flyAndScale } from '$lib/utils/transitions';
+	import RegenerateMenu from './ResponseMessage/RegenerateMenu.svelte';
 
 	interface MessageType {
 		id: string;
@@ -1316,7 +1317,7 @@
 												id="continue-response-button"
 												class="{isLastMessage
 													? '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"
+													: '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"
 												on:click={() => {
 													continueResponse();
 												}}
@@ -1345,47 +1346,70 @@
 										</Tooltip>
 									{/if}
 
-									<Tooltip content={$i18n.t('Regenerate')} placement="bottom">
-										<button
-											type="button"
-											aria-label={$i18n.t('Regenerate')}
-											class="{isLastMessage
-												? '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={() => {
-												showRateComment = false;
-												regenerateResponse(message);
-
-												(model?.actions ?? []).forEach((action) => {
-													dispatch('action', {
-														id: action.id,
-														event: {
-															id: 'regenerate-response',
-															data: {
-																messageId: message.id
-															}
+									<button
+										type="button"
+										class="hidden regenerate-response-button"
+										on:click={() => {
+											showRateComment = false;
+											regenerateResponse(message);
+
+											(model?.actions ?? []).forEach((action) => {
+												dispatch('action', {
+													id: action.id,
+													event: {
+														id: 'regenerate-response',
+														data: {
+															messageId: message.id
 														}
-													});
+													}
 												});
-											}}
-										>
-											<svg
-												xmlns="http://www.w3.org/2000/svg"
-												fill="none"
-												viewBox="0 0 24 24"
-												stroke-width="2.3"
-												aria-hidden="true"
-												stroke="currentColor"
-												class="w-4 h-4"
+											});
+										}}
+									/>
+
+									<RegenerateMenu
+										onRegenerate={(prompt = null) => {
+											showRateComment = false;
+											regenerateResponse(message, prompt);
+
+											(model?.actions ?? []).forEach((action) => {
+												dispatch('action', {
+													id: action.id,
+													event: {
+														id: 'regenerate-response',
+														data: {
+															messageId: message.id
+														}
+													}
+												});
+											});
+										}}
+									>
+										<Tooltip content={$i18n.t('Regenerate')} placement="bottom">
+											<div
+												aria-label={$i18n.t('Regenerate')}
+												class="{isLastMessage
+													? '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"
 											>
-												<path
-													stroke-linecap="round"
-													stroke-linejoin="round"
-													d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
-												/>
-											</svg>
-										</button>
-									</Tooltip>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													fill="none"
+													viewBox="0 0 24 24"
+													stroke-width="2.3"
+													aria-hidden="true"
+													stroke="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														stroke-linecap="round"
+														stroke-linejoin="round"
+														d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
+													/>
+												</svg>
+											</div>
+										</Tooltip>
+									</RegenerateMenu>
 
 									{#if siblings.length > 1}
 										<Tooltip content={$i18n.t('Delete')} placement="bottom">
@@ -1395,7 +1419,7 @@
 												id="delete-response-button"
 												class="{isLastMessage
 													? '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"
+													: '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"
 												on:click={() => {
 													showDeleteConfirm = true;
 												}}

+ 126 - 0
src/lib/components/chat/Messages/ResponseMessage/RegenerateMenu.svelte

@@ -0,0 +1,126 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { getContext } from 'svelte';
+
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+	import LineSpace from '$lib/components/icons/LineSpace.svelte';
+	import LineSpaceSmaller from '$lib/components/icons/LineSpaceSmaller.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let onRegenerate: Function = (prompt = null) => {};
+	export let onClose: Function = () => {};
+
+	let show = false;
+	let inputValue = '';
+</script>
+
+<Dropdown
+	bind:show
+	on:change={(e) => {
+		if (e.detail === false) {
+			onClose();
+		}
+	}}
+	align="end"
+>
+	<slot></slot>
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-[190px] rounded-xl px-1 py-1 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
+			sideOffset={-2}
+			side="bottom"
+			align="start"
+			transition={flyAndScale}
+		>
+			<div class="py-1.5 px-2.5 flex dark:text-gray-100">
+				<input
+					type="text"
+					id="floating-message-input"
+					class="bg-transparent outline-hidden w-full flex-1 text-sm"
+					placeholder={$i18n.t('Suggest a change')}
+					bind:value={inputValue}
+					autocomplete="off"
+					on:keydown={(e) => {
+						if (e.key === 'Enter') {
+							onRegenerate(inputValue);
+							show = false;
+						}
+					}}
+				/>
+
+				<div class="ml-2 self-center flex items-center">
+					<button
+						class="{inputValue !== ''
+							? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
+							: 'text-white bg-gray-200 dark:text-gray-900 dark:bg-gray-700 disabled'} transition rounded-full p-1 self-center"
+						on:click={() => {
+							onRegenerate(inputValue);
+							show = false;
+						}}
+					>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="size-3.5"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M8 14a.75.75 0 0 1-.75-.75V4.56L4.03 7.78a.75.75 0 0 1-1.06-1.06l4.5-4.5a.75.75 0 0 1 1.06 0l4.5 4.5a.75.75 0 0 1-1.06 1.06L8.75 4.56v8.69A.75.75 0 0 1 8 14Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</button>
+				</div>
+			</div>
+			<hr class="border-gray-50 dark:border-gray-800 my-1 mx-2" />
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-1.5 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg"
+				on:click={() => {
+					onRegenerate();
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					fill="none"
+					viewBox="0 0 24 24"
+					stroke-width="2"
+					aria-hidden="true"
+					stroke="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						stroke-linecap="round"
+						stroke-linejoin="round"
+						d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
+					/>
+				</svg>
+				<div class="flex items-center">{$i18n.t('Try Again')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-1.5 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg"
+				on:click={() => {
+					onRegenerate($i18n.t('Add Details'));
+				}}
+			>
+				<LineSpace strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Add Details')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-1.5 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg"
+				on:click={() => {
+					onRegenerate($i18n.t('More Concise'));
+				}}
+			>
+				<LineSpaceSmaller strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('More Concise')}</div>
+			</DropdownMenu.Item>
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>

+ 22 - 0
src/lib/components/icons/LineSpace.svelte

@@ -0,0 +1,22 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+	><path d="M11 6H21" stroke-linecap="round" stroke-linejoin="round"></path><path
+		d="M11 12H21"
+		stroke-linecap="round"
+		stroke-linejoin="round"
+	></path><path d="M11 18H21" stroke-linecap="round" stroke-linejoin="round"></path><path
+		d="M5 19V5M5 19L3 16.5M5 19L7 16.5M5 5L3 7M5 5L7 7"
+		stroke-linecap="round"
+		stroke-linejoin="round"
+	></path></svg
+>

+ 24 - 0
src/lib/components/icons/LineSpaceSmaller.svelte

@@ -0,0 +1,24 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path d="M11 6H21" stroke-linecap="round" stroke-linejoin="round"></path><path
+		d="M11 12H21"
+		stroke-linecap="round"
+		stroke-linejoin="round"
+	></path>
+	<path d="M11 18H21" stroke-linecap="round" stroke-linejoin="round"></path><path
+		d="M5 19V5M5 10L3 8M5 10L7 8M5 14L3 16M5 14L7 16"
+		stroke-linecap="round"
+		stroke-linejoin="round"
+	></path>
+</svg>

+ 1 - 1
src/lib/components/layout/SearchModal.svelte

@@ -352,7 +352,7 @@
 							bind:history
 							bind:messages
 							autoScroll={true}
-							sendPrompt={() => {}}
+							sendMessage={() => {}}
 							continueResponse={() => {}}
 							regenerateResponse={() => {}}
 						/>

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

@@ -186,7 +186,7 @@
 							bind:messages
 							bind:autoScroll
 							bottomPadding={files.length > 0}
-							sendPrompt={() => {}}
+							sendMessage={() => {}}
 							continueResponse={() => {}}
 							regenerateResponse={() => {}}
 						/>