This commit is contained in:
Timothy Jaeryang Baek
2026-05-19 20:12:00 +04:00
parent 359590ca9d
commit d310a0777c
2 changed files with 79 additions and 23 deletions
+75
View File
@@ -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