diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 73aa79a570..a5df89c3a1 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -57,6 +57,7 @@ copyToClipboard, getMessageContentParts, createMessagesList, + getDeepestChildId, sanitizeHistory, getPromptVariables, processDetails, @@ -1119,21 +1120,7 @@ const _chatId = JSON.parse(JSON.stringify($chatId)); let _messageId = JSON.parse(JSON.stringify(message.id)); - let messageChildrenIds = []; - if (_messageId === null) { - messageChildrenIds = Object.keys(history.messages).filter( - (id) => history.messages[id].parentId === null - ); - } else { - messageChildrenIds = history.messages[_messageId].childrenIds; - } - - while (messageChildrenIds.length !== 0) { - _messageId = messageChildrenIds.at(-1); - messageChildrenIds = history.messages[_messageId].childrenIds; - } - - history.currentId = _messageId; + history.currentId = getDeepestChildId(history, _messageId); await tick(); diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index dce9b72798..30ac1a10a3 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -7,7 +7,7 @@ import { toast } from 'svelte-sonner'; import { deleteChatMessageById, updateChatById } from '$lib/apis/chats'; - import { copyToClipboard, extractCurlyBraceWords } from '$lib/utils'; + import { copyToClipboard, extractCurlyBraceWords, getDeepestChildId } from '$lib/utils'; import Message from './Messages/Message.svelte'; import Loader from '../common/Loader.svelte'; @@ -214,14 +214,7 @@ // 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; + history.currentId = getDeepestChildId(history, messageId); } await tick(); @@ -247,14 +240,7 @@ ]; if (message.id !== messageId) { - let messageChildrenIds = history.messages[messageId].childrenIds; - - while (messageChildrenIds.length !== 0) { - messageId = messageChildrenIds.at(-1); - messageChildrenIds = history.messages[messageId].childrenIds; - } - - history.currentId = messageId; + history.currentId = getDeepestChildId(history, messageId); } } else { let childrenIds = Object.values(history.messages) @@ -263,14 +249,7 @@ let messageId = childrenIds[Math.max(childrenIds.indexOf(message.id) - 1, 0)]; if (message.id !== messageId) { - let messageChildrenIds = history.messages[messageId].childrenIds; - - while (messageChildrenIds.length !== 0) { - messageId = messageChildrenIds.at(-1); - messageChildrenIds = history.messages[messageId].childrenIds; - } - - history.currentId = messageId; + history.currentId = getDeepestChildId(history, messageId); } } @@ -299,14 +278,7 @@ ]; if (message.id !== messageId) { - let messageChildrenIds = history.messages[messageId].childrenIds; - - while (messageChildrenIds.length !== 0) { - messageId = messageChildrenIds.at(-1); - messageChildrenIds = history.messages[messageId].childrenIds; - } - - history.currentId = messageId; + history.currentId = getDeepestChildId(history, messageId); } } else { let childrenIds = Object.values(history.messages) @@ -316,14 +288,7 @@ childrenIds[Math.min(childrenIds.indexOf(message.id) + 1, childrenIds.length - 1)]; if (message.id !== messageId) { - let messageChildrenIds = history.messages[messageId].childrenIds; - - while (messageChildrenIds.length !== 0) { - messageId = messageChildrenIds.at(-1); - messageChildrenIds = history.messages[messageId].childrenIds; - } - - history.currentId = messageId; + history.currentId = getDeepestChildId(history, messageId); } } @@ -480,16 +445,7 @@ delete history.messages[id]; }); - let nextMessageId = parentMessageId; - let nextChildrenIds = - nextMessageId === null - ? Object.keys(history.messages).filter((id) => history.messages[id].parentId === null) - : (history.messages[nextMessageId]?.childrenIds ?? []); - while (nextChildrenIds.length > 0) { - nextMessageId = nextChildrenIds.at(-1); - nextChildrenIds = history.messages[nextMessageId]?.childrenIds ?? []; - } - history.currentId = nextMessageId; + history.currentId = getDeepestChildId(history, parentMessageId); history = history; if (!$temporaryChatEnabled) { diff --git a/src/lib/components/chat/Messages/MultiResponseMessages.svelte b/src/lib/components/chat/Messages/MultiResponseMessages.svelte index d01ba315af..5977b432fe 100644 --- a/src/lib/components/chat/Messages/MultiResponseMessages.svelte +++ b/src/lib/components/chat/Messages/MultiResponseMessages.svelte @@ -19,7 +19,11 @@ import ProfileImage from './ProfileImage.svelte'; import { WEBUI_BASE_URL } from '$lib/constants'; import equal from 'fast-deep-equal'; - import { formatMessageTimestamp, formatMessageTimestampFull } from '$lib/utils'; + import { + formatMessageTimestamp, + formatMessageTimestampFull, + getDeepestChildId + } from '$lib/utils'; const i18n = getContext('i18n'); export let chatId; @@ -88,15 +92,8 @@ 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; + history.currentId = getDeepestChildId(history, messageId); // Await UI updates await tick(); @@ -112,14 +109,7 @@ let messageId = groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]]; 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; + history.currentId = getDeepestChildId(history, messageId); await tick(); await updateChat(); @@ -135,14 +125,7 @@ let messageId = groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]]; 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; + history.currentId = getDeepestChildId(history, messageId); await tick(); await updateChat(); @@ -209,13 +192,7 @@ 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; + history.currentId = getDeepestChildId(history, _messageId); selectedModelIdx = modelIdx; // await tick(); diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 7073503aa9..f85bea4b08 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -1433,6 +1433,29 @@ export const createMessagesList = (history, messageId) => { return list.reverse(); }; +export const getDeepestChildId = (history, messageId) => { + let deepestId = messageId; + const visitedMessageIds = new Set([deepestId]); + let childrenIds = + deepestId === null + ? Object.keys(history.messages).filter((id) => history.messages[id].parentId === null) + : (history.messages[deepestId]?.childrenIds ?? []); + + while (childrenIds.length !== 0) { + const childId = childrenIds.at(-1); + if (visitedMessageIds.has(childId)) { + console.warn('Circular dependency detected in message history', childId); + break; + } + + visitedMessageIds.add(childId); + deepestId = childId; + childrenIds = history.messages[deepestId]?.childrenIds ?? []; + } + + return deepestId; +}; + const toTokenCount = (value: unknown) => { const parsed = Number(value || 0); return Number.isFinite(parsed) ? Math.trunc(parsed) : 0;