refac
This commit is contained in:
@@ -58,6 +58,7 @@
|
||||
copyToClipboard,
|
||||
getMessageContentParts,
|
||||
createMessagesList,
|
||||
sanitizeHistory,
|
||||
getPromptVariables,
|
||||
processDetails,
|
||||
removeAllDetails,
|
||||
@@ -1379,29 +1380,9 @@
|
||||
? chatContent.history
|
||||
: convertMessagesToHistory(chatContent.messages);
|
||||
|
||||
// Sanitize history: repair orphaned references from failed regenerations (#24424)
|
||||
for (const message of Object.values(history.messages)) {
|
||||
if (message.childrenIds) {
|
||||
message.childrenIds = message.childrenIds.filter(
|
||||
(childId) => history.messages[childId]
|
||||
);
|
||||
}
|
||||
}
|
||||
if (history.currentId && !history.messages[history.currentId]) {
|
||||
const messageIds = Object.keys(history.messages);
|
||||
let lastMessageId = null;
|
||||
for (const messageId of messageIds) {
|
||||
const message = history.messages[messageId];
|
||||
if (
|
||||
(message.childrenIds ?? []).length === 0 &&
|
||||
(!lastMessageId ||
|
||||
(message.timestamp ?? 0) > (history.messages[lastMessageId].timestamp ?? 0))
|
||||
) {
|
||||
lastMessageId = messageId;
|
||||
}
|
||||
}
|
||||
history.currentId = lastMessageId ?? messageIds[0] ?? null;
|
||||
}
|
||||
// Sanitize history: repair orphaned references and structurally-malformed
|
||||
// nodes from failed regenerations (#24424, #24157, #20474)
|
||||
sanitizeHistory(history);
|
||||
|
||||
chatTitle.set(chatContent.title);
|
||||
|
||||
|
||||
@@ -225,6 +225,81 @@ export const convertMessagesToHistory = (messages) => {
|
||||
return history;
|
||||
};
|
||||
|
||||
// Repair structurally-malformed history nodes from failed regenerations.
|
||||
// A lost assistant placeholder may exist under its map key with only
|
||||
// completion fields (content/done/error), missing id/role/parentId.
|
||||
// Reconstruct graph fields so already-corrupted chats recover on open.
|
||||
export const sanitizeHistory = (history) => {
|
||||
if (!history?.messages || typeof history.messages !== 'object') return;
|
||||
|
||||
// Purge entries that aren't usable objects
|
||||
for (const [id, message] of Object.entries(history.messages)) {
|
||||
if (!message || typeof message !== 'object') {
|
||||
delete history.messages[id];
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure every surviving node has its canonical id and a childrenIds array
|
||||
for (const [id, message] of Object.entries(history.messages)) {
|
||||
if (message.id !== id) message.id = id;
|
||||
if (!Array.isArray(message.childrenIds)) message.childrenIds = [];
|
||||
}
|
||||
|
||||
// Build reverse lookup: parent, indexed by child id
|
||||
const parentByChildId = {};
|
||||
for (const [id, message] of Object.entries(history.messages)) {
|
||||
for (const childId of message.childrenIds) {
|
||||
parentByChildId[childId] = id;
|
||||
}
|
||||
}
|
||||
|
||||
// Reconstruct missing parentId and role
|
||||
for (const [id, message] of Object.entries(history.messages)) {
|
||||
// Well-formed: has role and explicit parentId (null is valid for root)
|
||||
if (message.role && message.parentId !== undefined) continue;
|
||||
|
||||
if (message.parentId === undefined) {
|
||||
message.parentId = parentByChildId[id] ?? null;
|
||||
}
|
||||
|
||||
if (!message.role) {
|
||||
const parent = message.parentId ? history.messages[message.parentId] : null;
|
||||
message.role =
|
||||
parent?.role === 'user'
|
||||
? 'assistant'
|
||||
: parent?.role === 'assistant'
|
||||
? 'user'
|
||||
: message.model || message.usage || message.done !== undefined
|
||||
? 'assistant'
|
||||
: 'user';
|
||||
}
|
||||
}
|
||||
|
||||
// Prune childrenIds referencing deleted/missing nodes
|
||||
for (const message of Object.values(history.messages)) {
|
||||
message.childrenIds = message.childrenIds.filter(
|
||||
(childId) => history.messages[childId]
|
||||
);
|
||||
}
|
||||
|
||||
// Recover currentId if it points to a missing or incomplete node
|
||||
const currentMessage = history.messages?.[history.currentId];
|
||||
if (!currentMessage?.id || !currentMessage?.role) {
|
||||
let latestLeafId = null;
|
||||
let latestTimestamp = -1;
|
||||
for (const [id, message] of Object.entries(history.messages)) {
|
||||
if (
|
||||
message.childrenIds.length === 0 &&
|
||||
(message.timestamp ?? 0) > latestTimestamp
|
||||
) {
|
||||
latestLeafId = id;
|
||||
latestTimestamp = message.timestamp ?? 0;
|
||||
}
|
||||
}
|
||||
history.currentId = latestLeafId ?? Object.keys(history.messages)[0] ?? null;
|
||||
}
|
||||
};
|
||||
|
||||
export const getGravatarURL = (email) => {
|
||||
// Trim leading and trailing whitespace from
|
||||
// an email address and force all characters
|
||||
|
||||
Reference in New Issue
Block a user