From befdd86eb1703e17b90ea7905b8a031e00524ce0 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Wed, 16 Sep 2026 21:41:56 +0200 Subject: [PATCH] refac: share one guarded helper for chat branch descent (#30070) The branch navigation handlers each carried their own copy of the loop that walks a message's childrenIds down to the deepest child, eleven copies in total across the single-response view, the multi-response view and Chat.svelte. They now call one getDeepestChildId helper in the frontend utils, which tracks the ids it has already visited and stops when one repeats, the same way the message list build already does. The helper also absorbs the "start from the last root message" fallback that two of the call sites repeated inline, so every site is now a single call. --- src/lib/components/chat/Chat.svelte | 17 +----- src/lib/components/chat/Messages.svelte | 58 +++---------------- .../Messages/MultiResponseMessages.svelte | 41 +++---------- src/lib/utils/index.ts | 23 ++++++++ 4 files changed, 41 insertions(+), 98 deletions(-) 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;