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.
This commit is contained in:
Classic298 2026-09-16 21:41:56 +02:00 • committed by GitHub
parent d9c8de9c39
commit befdd86eb1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 41 additions and 98 deletions

View file

@ -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();

View file

@ -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) {

View file

@ -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();

View file

@ -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;