diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index dc3766cf2..b55a27b40 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -107,6 +107,47 @@ } }; + const gotoMessage = async (message, idx) => { + // Determine the correct sibling list (either parent's children or root messages) + let siblings; + if (message.parentId !== null) { + siblings = history.messages[message.parentId].childrenIds; + } else { + siblings = Object.values(history.messages) + .filter((msg) => msg.parentId === null) + .map((msg) => msg.id); + } + + // Clamp index to a valid range + idx = Math.max(0, Math.min(idx, siblings.length - 1)); + + let messageId = siblings[idx]; + + // If we're navigating to a different message + if (message.id !== messageId) { + // Drill down to the deepest child of that branch + let messageChildrenIds = history.messages[messageId].childrenIds; + while (messageChildrenIds.length !== 0) { + messageId = messageChildrenIds.at(-1); + messageChildrenIds = history.messages[messageId].childrenIds; + } + + history.currentId = messageId; + } + + await tick(); + + // Optional auto-scroll + if ($settings?.scrollOnBranchChange ?? true) { + const element = document.getElementById('messages-container'); + autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50; + + setTimeout(() => { + scrollToBottom(); + }, 100); + } + }; + const showPreviousMessage = async (message) => { if (message.parentId !== null) { let messageId = @@ -408,6 +449,7 @@ messageId={message.id} idx={messageIdx} {user} + {gotoMessage} {showPreviousMessage} {showNextMessage} {updateChat} diff --git a/src/lib/components/chat/Messages/Message.svelte b/src/lib/components/chat/Messages/Message.svelte index 9a2b7155c..9a30abfe5 100644 --- a/src/lib/components/chat/Messages/Message.svelte +++ b/src/lib/components/chat/Messages/Message.svelte @@ -20,6 +20,7 @@ export let user; + export let gotoMessage; export let showPreviousMessage; export let showNextMessage; export let updateChat; @@ -57,6 +58,7 @@ : (Object.values(history.messages) .filter((message) => message.parentId === null) .map((message) => message.id) ?? [])} + {gotoMessage} {showPreviousMessage} {showNextMessage} {editMessage} @@ -70,6 +72,7 @@ {messageId} isLastMessage={messageId === history.currentId} siblings={history.messages[history.messages[messageId].parentId]?.childrenIds ?? []} + {gotoMessage} {showPreviousMessage} {showNextMessage} {updateChat} diff --git a/src/lib/components/chat/Messages/MultiResponseMessages.svelte b/src/lib/components/chat/Messages/MultiResponseMessages.svelte index 1a8ceda79..c46be0e83 100644 --- a/src/lib/components/chat/Messages/MultiResponseMessages.svelte +++ b/src/lib/components/chat/Messages/MultiResponseMessages.svelte @@ -58,6 +58,35 @@ } } + const gotoMessage = async (modelIdx, messageIdx) => { + // Clamp messageIdx to ensure it's within valid range + groupedMessageIdsIdx[modelIdx] = Math.max( + 0, + Math.min(messageIdx, groupedMessageIds[modelIdx].messageIds.length - 1) + ); + + // Get the messageId at the specified index + let messageId = groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]]; + console.log(messageId); + + // Traverse the branch to find the deepest child message + let messageChildrenIds = history.messages[messageId].childrenIds; + while (messageChildrenIds.length !== 0) { + messageId = messageChildrenIds.at(-1); + messageChildrenIds = history.messages[messageId].childrenIds; + } + + // Update the current message ID in history + history.currentId = messageId; + + // Await UI updates + await tick(); + await updateChat(); + + // Trigger scrolling after navigation + triggerScroll(); + }; + const showPreviousMessage = async (modelIdx) => { groupedMessageIdsIdx[modelIdx] = Math.max(0, groupedMessageIdsIdx[modelIdx] - 1); @@ -224,6 +253,7 @@ messageId={_messageId} isLastMessage={true} siblings={groupedMessageIds[modelIdx].messageIds} + gotoMessage={(message, messageIdx) => gotoMessage(modelIdx, messageIdx)} showPreviousMessage={() => showPreviousMessage(modelIdx)} showNextMessage={() => showNextMessage(modelIdx)} {updateChat} diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index 1af5140dc..5b4fa1bc9 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -5,7 +5,7 @@ import { createEventDispatcher } from 'svelte'; import { onMount, tick, getContext } from 'svelte'; import type { Writable } from 'svelte/store'; - import type { i18n as i18nType } from 'i18next'; + import type { i18n as i18nType, t } from 'i18next'; const i18n = getContext>('i18n'); @@ -110,6 +110,7 @@ export let siblings; + export let gotoMessage: Function = () => {}; export let showPreviousMessage: Function; export let showNextMessage: Function; @@ -139,6 +140,8 @@ let editedContent = ''; let editTextAreaElement: HTMLTextAreaElement; + let messageIndexEdit = false; + let audioParts: Record = {}; let speaking = false; let speakingIdx: number | undefined; @@ -846,11 +849,50 @@ -
- {siblings.indexOf(message.id) + 1}/{siblings.length} -
+ {#if messageIndexEdit} +
+ { + e.target.select(); + }} + on:blur={(e) => { + gotoMessage(message, e.target.value - 1); + messageIndexEdit = false; + }} + on:keydown={(e) => { + if (e.key === 'Enter') { + gotoMessage(message, e.target.value - 1); + messageIndexEdit = false; + } + }} + class="bg-transparent font-semibold self-center dark:text-gray-100 min-w-fit outline-hidden" + />/{siblings.length} +
+ {:else} + +
{ + messageIndexEdit = true; + + await tick(); + const input = document.getElementById(`message-index-input-${message.id}`); + if (input) { + input.focus(); + input.select(); + } + }} + > + {siblings.indexOf(message.id) + 1}/{siblings.length} +
+ {/if} -
- {siblings.indexOf(message.id) + 1}/{siblings.length} -
+ {#if messageIndexEdit} +
+ { + e.target.select(); + }} + on:blur={(e) => { + gotoMessage(message, e.target.value - 1); + messageIndexEdit = false; + }} + on:keydown={(e) => { + if (e.key === 'Enter') { + gotoMessage(message, e.target.value - 1); + messageIndexEdit = false; + } + }} + class="bg-transparent font-semibold self-center dark:text-gray-100 min-w-fit outline-hidden" + />/{siblings.length} +
+ {:else} + +
{ + messageIndexEdit = true; + + await tick(); + const input = document.getElementById( + `message-index-input-${message.id}` + ); + if (input) { + input.focus(); + input.select(); + } + }} + > + {siblings.indexOf(message.id) + 1}/{siblings.length} +
+ {/if} -
- {siblings.indexOf(message.id) + 1}/{siblings.length} -
+ {#if messageIndexEdit} +
+ { + e.target.select(); + }} + on:blur={(e) => { + gotoMessage(message, e.target.value - 1); + messageIndexEdit = false; + }} + on:keydown={(e) => { + if (e.key === 'Enter') { + gotoMessage(message, e.target.value - 1); + messageIndexEdit = false; + } + }} + class="bg-transparent font-semibold self-center dark:text-gray-100 min-w-fit outline-hidden" + />/{siblings.length} +
+ {:else} + +
{ + messageIndexEdit = true; + + await tick(); + const input = document.getElementById( + `message-index-input-${message.id}` + ); + if (input) { + input.focus(); + input.select(); + } + }} + > + {siblings.indexOf(message.id) + 1}/{siblings.length} +
+ {/if}