Files
openchamber/packages/ui/src/components/chat/lib/turns/stabilizeTurnProjection.ts
T
Bohdan Triapitsyn 605e4bf42c fix: stop stale chat renders after session switches
Roll back aggressive chat reuse paths that froze message parts, tool state, and completed assistant bodies when sessions were inactive or backgrounded.
2026-04-07 20:33:55 +03:00

79 lines
2.3 KiB
TypeScript

import { projectTurnIndexes } from './projectTurnIndexes';
import type { TurnProjectionResult, TurnRecord } from './types';
const areTurnMessagesReferenceStable = (previousTurn: TurnRecord, nextTurn: TurnRecord): boolean => {
if (previousTurn.userMessage !== nextTurn.userMessage) {
return false;
}
if (previousTurn.assistantMessages.length !== nextTurn.assistantMessages.length) {
return false;
}
for (let index = 0; index < previousTurn.assistantMessages.length; index += 1) {
if (previousTurn.assistantMessages[index] !== nextTurn.assistantMessages[index]) {
return false;
}
}
return true;
};
const buildTurnSignature = (turn: TurnRecord): string => {
const assistantIds = turn.assistantMessageIds.join(',');
return [
turn.turnId,
turn.headerMessageId ?? '',
assistantIds,
turn.summaryText ?? '',
turn.stream.isStreaming ? '1' : '0',
turn.stream.isRetrying ? '1' : '0',
turn.completedAt ?? '',
].join('|');
};
export const stabilizeTurnProjection = (
nextProjection: TurnProjectionResult,
previousProjection: TurnProjectionResult | null,
): TurnProjectionResult => {
if (!previousProjection || previousProjection.turns.length === 0 || nextProjection.turns.length === 0) {
return nextProjection;
}
const previousById = new Map(previousProjection.turns.map((turn) => [turn.turnId, turn]));
let reused = false;
const stabilizedTurns = nextProjection.turns.map((turn, index) => {
const isLastTurn = index === nextProjection.turns.length - 1;
if (isLastTurn) {
return turn;
}
const previousTurn = previousById.get(turn.turnId);
if (!previousTurn) {
return turn;
}
if (buildTurnSignature(previousTurn) !== buildTurnSignature(turn)) {
return turn;
}
if (!areTurnMessagesReferenceStable(previousTurn, turn)) {
return turn;
}
reused = true;
return previousTurn;
});
if (!reused) {
return nextProjection;
}
const projection = projectTurnIndexes(stabilizedTurns);
return {
...projection,
ungroupedMessageIds: nextProjection.ungroupedMessageIds,
};
};