2026-03-12 23:45:45 +02:00
|
|
|
import { projectTurnIndexes } from './projectTurnIndexes';
|
|
|
|
|
import type { TurnProjectionResult, TurnRecord } from './types';
|
|
|
|
|
|
2026-04-07 20:33:01 +03:00
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-12 23:45:45 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 20:33:01 +03:00
|
|
|
if (!areTurnMessagesReferenceStable(previousTurn, turn)) {
|
|
|
|
|
return turn;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-12 23:45:45 +02:00
|
|
|
reused = true;
|
|
|
|
|
return previousTurn;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!reused) {
|
|
|
|
|
return nextProjection;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const projection = projectTurnIndexes(stabilizedTurns);
|
|
|
|
|
return {
|
|
|
|
|
...projection,
|
|
|
|
|
ungroupedMessageIds: nextProjection.ungroupedMessageIds,
|
|
|
|
|
};
|
|
|
|
|
};
|