Browse Source

enh: multi model response tabbed display

Timothy Jaeryang Baek 2 months ago
parent
commit
8b9c5c4c1e

+ 142 - 42
src/lib/components/chat/Messages/MultiResponseMessages.svelte

@@ -3,7 +3,7 @@
 	import { onMount, tick, getContext } from 'svelte';
 	import { createEventDispatcher } from 'svelte';
 
-	import { mobile, settings } from '$lib/stores';
+	import { mobile, models, settings } from '$lib/stores';
 
 	import { generateMoACompletion } from '$lib/apis';
 	import { updateChatById } from '$lib/apis/chats';
@@ -17,6 +17,8 @@
 	import Name from './Name.svelte';
 	import Skeleton from './Skeleton.svelte';
 	import localizedFormat from 'dayjs/plugin/localizedFormat';
+	import ProfileImage from './ProfileImage.svelte';
+	import { WEBUI_BASE_URL } from '$lib/constants';
 	const i18n = getContext('i18n');
 	dayjs.extend(localizedFormat);
 
@@ -53,6 +55,8 @@
 	let groupedMessageIds = {};
 	let groupedMessageIdsIdx = {};
 
+	let selectedModelIdx = null;
+
 	let message = JSON.parse(JSON.stringify(history.messages[messageId]));
 	$: if (history.messages) {
 		if (JSON.stringify(message) !== JSON.stringify(history.messages[messageId])) {
@@ -183,11 +187,30 @@
 			}
 		}, {});
 
+		selectedModelIdx = history.messages[messageId]?.modelIdx;
+
 		console.log(groupedMessageIds, groupedMessageIdsIdx);
 
 		await tick();
 	};
 
+	const onGroupClick = async (_messageId, modelIdx) => {
+		if (messageId != _messageId) {
+			let currentMessageId = _messageId;
+			let messageChildrenIds = history.messages[currentMessageId].childrenIds;
+			while (messageChildrenIds.length !== 0) {
+				currentMessageId = messageChildrenIds.at(-1);
+				messageChildrenIds = history.messages[currentMessageId].childrenIds;
+			}
+			history.currentId = currentMessageId;
+			selectedModelIdx = modelIdx;
+
+			// await tick();
+			// await updateChat();
+			// triggerScroll();
+		}
+	};
+
 	const mergeResponsesHandler = async () => {
 		const responses = Object.keys(groupedMessageIds).map((modelIdx) => {
 			const { messageIds } = groupedMessageIds[modelIdx];
@@ -217,37 +240,58 @@
 			class="flex snap-x snap-mandatory overflow-x-auto scrollbar-hidden"
 			id="responses-container-{chatId}-{parentMessage.id}"
 		>
-			{#each Object.keys(groupedMessageIds) as modelIdx}
-				{#if groupedMessageIdsIdx[modelIdx] !== undefined && groupedMessageIds[modelIdx].messageIds.length > 0}
-					<!-- svelte-ignore a11y-no-static-element-interactions -->
-					<!-- svelte-ignore a11y-click-events-have-key-events -->
-					{@const _messageId =
-						groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]]}
-
-					<div
-						class=" snap-center w-full max-w-full m-1 border {history.messages[messageId]
-							?.modelIdx == modelIdx
-							? `bg-gray-50 dark:bg-gray-850 border-gray-100 dark:border-gray-800 border-2 ${
-									$mobile ? 'min-w-full' : 'min-w-80'
-								}`
-							: `border-gray-100 dark:border-gray-850 border-dashed ${
-									$mobile ? 'min-w-full' : 'min-w-80'
-								}`} transition-all p-5 rounded-2xl"
-						on:click={async () => {
-							if (messageId != _messageId) {
-								let currentMessageId = _messageId;
-								let messageChildrenIds = history.messages[currentMessageId].childrenIds;
-								while (messageChildrenIds.length !== 0) {
-									currentMessageId = messageChildrenIds.at(-1);
-									messageChildrenIds = history.messages[currentMessageId].childrenIds;
-								}
-								history.currentId = currentMessageId;
-								// await tick();
-								// await updateChat();
-								// triggerScroll();
-							}
-						}}
-					>
+			{#if $settings?.displayMultiModelResponsesInTabs ?? false}
+				<div class="w-full">
+					<div class=" flex w-full mb-4 border-b border-gray-200 dark:border-gray-850">
+						<div
+							class="flex gap-2 scrollbar-none overflow-x-auto w-fit text-center font-medium bg-transparent pt-1 text-sm"
+						>
+							{#each Object.keys(groupedMessageIds) as modelIdx}
+								{#if groupedMessageIdsIdx[modelIdx] !== undefined && groupedMessageIds[modelIdx].messageIds.length > 0}
+									<!-- svelte-ignore a11y-no-static-element-interactions -->
+									<!-- svelte-ignore a11y-click-events-have-key-events -->
+
+									{@const _messageId =
+										groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]]}
+
+									{@const model = $models.find((m) => m.id === history.messages[_messageId]?.model)}
+
+									<button
+										class="min-w-fit {selectedModelIdx == modelIdx
+											? ' dark:border-gray-300 '
+											: ' opacity-35 border-transparent'} pb-1.5 px-2.5 transition border-b-2"
+										on:click={async () => {
+											if (selectedModelIdx != modelIdx) {
+												selectedModelIdx = modelIdx;
+											}
+
+											onGroupClick(_messageId, modelIdx);
+										}}
+									>
+										<div class="flex items-center gap-1.5">
+											<!-- <ProfileImage
+												src={model?.info?.meta?.profile_image_url ??
+													($i18n.language === 'dg-DG'
+														? `${WEBUI_BASE_URL}/doge.png`
+														: `${WEBUI_BASE_URL}/favicon.png`)}
+												className={'size-5 assistant-message-profile-image'}
+											/> -->
+
+											<div class="-translate-y-[1px]">
+												{model ? `${model.name}` : history.messages[_messageId]?.model}
+											</div>
+										</div>
+									</button>
+								{/if}
+							{/each}
+						</div>
+					</div>
+
+					{#if selectedModelIdx !== null}
+						{@const _messageId =
+							groupedMessageIds[selectedModelIdx].messageIds[
+								groupedMessageIdsIdx[selectedModelIdx]
+							]}
 						{#key history.currentId}
 							{#if message}
 								<ResponseMessage
@@ -256,10 +300,10 @@
 									messageId={_messageId}
 									{selectedModels}
 									isLastMessage={true}
-									siblings={groupedMessageIds[modelIdx].messageIds}
-									gotoMessage={(message, messageIdx) => gotoMessage(modelIdx, messageIdx)}
-									showPreviousMessage={() => showPreviousMessage(modelIdx)}
-									showNextMessage={() => showNextMessage(modelIdx)}
+									siblings={groupedMessageIds[selectedModelIdx].messageIds}
+									gotoMessage={(message, messageIdx) => gotoMessage(selectedModelIdx, messageIdx)}
+									showPreviousMessage={() => showPreviousMessage(selectedModelIdx)}
+									showNextMessage={() => showNextMessage(selectedModelIdx)}
 									{setInputText}
 									{updateChat}
 									{editMessage}
@@ -272,17 +316,73 @@
 									regenerateResponse={async (message) => {
 										regenerateResponse(message);
 										await tick();
-										groupedMessageIdsIdx[modelIdx] =
-											groupedMessageIds[modelIdx].messageIds.length - 1;
+										groupedMessageIdsIdx[selectedModelIdx] =
+											groupedMessageIds[selectedModelIdx].messageIds.length - 1;
 									}}
 									{addMessages}
 									{readOnly}
 								/>
 							{/if}
 						{/key}
-					</div>
-				{/if}
-			{/each}
+					{/if}
+				</div>
+			{:else}
+				{#each Object.keys(groupedMessageIds) as modelIdx}
+					{#if groupedMessageIdsIdx[modelIdx] !== undefined && groupedMessageIds[modelIdx].messageIds.length > 0}
+						<!-- svelte-ignore a11y-no-static-element-interactions -->
+						<!-- svelte-ignore a11y-click-events-have-key-events -->
+						{@const _messageId =
+							groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]]}
+
+						<div
+							class=" snap-center w-full max-w-full m-1 border {history.messages[messageId]
+								?.modelIdx == modelIdx
+								? `bg-gray-50 dark:bg-gray-850 border-gray-100 dark:border-gray-800 border-2 ${
+										$mobile ? 'min-w-full' : 'min-w-80'
+									}`
+								: `border-gray-100 dark:border-gray-850 border-dashed ${
+										$mobile ? 'min-w-full' : 'min-w-80'
+									}`} transition-all p-5 rounded-2xl"
+							on:click={async () => {
+								onGroupClick(_messageId, modelIdx);
+							}}
+						>
+							{#key history.currentId}
+								{#if message}
+									<ResponseMessage
+										{chatId}
+										{history}
+										messageId={_messageId}
+										{selectedModels}
+										isLastMessage={true}
+										siblings={groupedMessageIds[modelIdx].messageIds}
+										gotoMessage={(message, messageIdx) => gotoMessage(modelIdx, messageIdx)}
+										showPreviousMessage={() => showPreviousMessage(modelIdx)}
+										showNextMessage={() => showNextMessage(modelIdx)}
+										{setInputText}
+										{updateChat}
+										{editMessage}
+										{saveMessage}
+										{rateMessage}
+										{deleteMessage}
+										{actionMessage}
+										{submitMessage}
+										{continueResponse}
+										regenerateResponse={async (message) => {
+											regenerateResponse(message);
+											await tick();
+											groupedMessageIdsIdx[modelIdx] =
+												groupedMessageIds[modelIdx].messageIds.length - 1;
+										}}
+										{addMessages}
+										{readOnly}
+									/>
+								{/if}
+							{/key}
+						</div>
+					{/if}
+				{/each}
+			{/if}
 		</div>
 
 		{#if !readOnly}
@@ -296,7 +396,7 @@
 						{#if history.messages[messageId]?.merged?.status}
 							{@const message = history.messages[messageId]?.merged}
 
-							<div class="w-full rounded-xl pl-5 pr-2 py-2">
+							<div class="w-full rounded-xl pl-5 pr-2 py-2 mt-2">
 								<Name>
 									{$i18n.t('Merged Response')}
 

+ 30 - 0
src/lib/components/chat/Settings/Interface.svelte

@@ -36,6 +36,7 @@
 	let highContrastMode = false;
 
 	let detectArtifacts = true;
+	let displayMultiModelResponsesInTabs = false;
 
 	let richTextInput = true;
 	let showFormattingToolbar = false;
@@ -155,6 +156,11 @@
 		saveSettings({ showEmojiInCall: showEmojiInCall });
 	};
 
+	const toggleDisplayMultiModelResponsesInTabs = async () => {
+		displayMultiModelResponsesInTabs = !displayMultiModelResponsesInTabs;
+		saveSettings({ displayMultiModelResponsesInTabs });
+	};
+
 	const toggleVoiceInterruption = async () => {
 		voiceInterruption = !voiceInterruption;
 		saveSettings({ voiceInterruption: voiceInterruption });
@@ -344,6 +350,7 @@
 		showEmojiInCall = $settings?.showEmojiInCall ?? false;
 		voiceInterruption = $settings?.voiceInterruption ?? false;
 
+		displayMultiModelResponsesInTabs = $settings?.displayMultiModelResponsesInTabs ?? false;
 		chatFadeStreamingText = $settings?.chatFadeStreamingText ?? true;
 
 		richTextInput = $settings?.richTextInput ?? true;
@@ -853,6 +860,29 @@
 				</div>
 			</div>
 
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div id="keep-followup-prompts-label" class=" self-center text-xs">
+						{$i18n.t('Display Multi-model Responses in Tabs')}
+					</div>
+
+					<button
+						aria-labelledby="keep-followup-prompts-label"
+						class="p-1 px-3 text-xs flex rounded-sm transition"
+						on:click={() => {
+							toggleDisplayMultiModelResponsesInTabs();
+						}}
+						type="button"
+					>
+						{#if displayMultiModelResponsesInTabs === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
 					<div id="rich-input-label" class=" self-center text-xs">