refactor: isolate chat hot paths and lazy server watchers
Split static history from live tail, stabilize chat data identities, and reduce scroll-shell churn so old viewport content stops waking on stream deltas. Also defer watcher startup and health polling so idle runtime work better matches actual usage.
This commit is contained in:
@@ -23,6 +23,119 @@ export interface TurnWindowModel {
|
||||
turnCount: number;
|
||||
}
|
||||
|
||||
const getMessageSignature = (message: ChatMessageEntry | undefined): string | null => {
|
||||
if (!message) return null;
|
||||
const role = resolveMessageRole(message);
|
||||
const messageId = typeof message.info?.id === 'string' ? message.info.id : '';
|
||||
const parentId = resolveParentMessageId(message) ?? '';
|
||||
return `${messageId}::${role}::${parentId}`;
|
||||
};
|
||||
|
||||
const cloneTurnWindowModel = (model: TurnWindowModel): TurnWindowModel => ({
|
||||
turnIds: [...model.turnIds],
|
||||
turnMessageStartIndexes: [...model.turnMessageStartIndexes],
|
||||
turnIndexById: new Map(model.turnIndexById),
|
||||
messageToTurnId: new Map(model.messageToTurnId),
|
||||
messageToTurnIndex: new Map(model.messageToTurnIndex),
|
||||
turnCount: model.turnCount,
|
||||
});
|
||||
|
||||
export const updateTurnWindowModelIncremental = (
|
||||
previousModel: TurnWindowModel | null,
|
||||
previousMessages: ChatMessageEntry[] | null,
|
||||
nextMessages: ChatMessageEntry[],
|
||||
): TurnWindowModel | null => {
|
||||
if (!previousModel || !previousMessages) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (previousMessages.length === nextMessages.length) {
|
||||
let changedIndex = -1;
|
||||
for (let index = 0; index < nextMessages.length; index += 1) {
|
||||
if (previousMessages[index] === nextMessages[index]) {
|
||||
continue;
|
||||
}
|
||||
if (changedIndex !== -1) {
|
||||
return null;
|
||||
}
|
||||
changedIndex = index;
|
||||
}
|
||||
|
||||
if (changedIndex === -1) {
|
||||
return previousModel;
|
||||
}
|
||||
|
||||
if (changedIndex !== nextMessages.length - 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getMessageSignature(previousMessages[changedIndex]) === getMessageSignature(nextMessages[changedIndex])
|
||||
? previousModel
|
||||
: null;
|
||||
}
|
||||
|
||||
if (nextMessages.length !== previousMessages.length + 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let index = 0; index < previousMessages.length; index += 1) {
|
||||
if (previousMessages[index] !== nextMessages[index]) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const nextMessage = nextMessages[nextMessages.length - 1];
|
||||
if (!nextMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const role = resolveMessageRole(nextMessage);
|
||||
const messageId = nextMessage.info.id;
|
||||
const nextModel = cloneTurnWindowModel(previousModel);
|
||||
|
||||
if (role === 'user') {
|
||||
const nextTurnIndex = nextModel.turnIds.length;
|
||||
nextModel.turnIds.push(messageId);
|
||||
nextModel.turnMessageStartIndexes.push(nextMessages.length - 1);
|
||||
nextModel.turnIndexById.set(messageId, nextTurnIndex);
|
||||
nextModel.messageToTurnId.set(messageId, messageId);
|
||||
nextModel.messageToTurnIndex.set(messageId, nextTurnIndex);
|
||||
nextModel.turnCount = nextModel.turnIds.length;
|
||||
return nextModel;
|
||||
}
|
||||
|
||||
if (role !== 'assistant') {
|
||||
const currentTurnIndex = nextModel.turnIds.length - 1;
|
||||
if (currentTurnIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
const turnId = nextModel.turnIds[currentTurnIndex];
|
||||
if (!turnId) {
|
||||
return null;
|
||||
}
|
||||
nextModel.messageToTurnId.set(messageId, turnId);
|
||||
nextModel.messageToTurnIndex.set(messageId, currentTurnIndex);
|
||||
return nextModel;
|
||||
}
|
||||
|
||||
const parentId = resolveParentMessageId(nextMessage);
|
||||
const targetTurnIndex = parentId
|
||||
? nextModel.turnIndexById.get(parentId)
|
||||
: nextModel.turnIds.length - 1;
|
||||
if (typeof targetTurnIndex !== 'number' || targetTurnIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const turnId = nextModel.turnIds[targetTurnIndex];
|
||||
if (!turnId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
nextModel.messageToTurnId.set(messageId, turnId);
|
||||
nextModel.messageToTurnIndex.set(messageId, targetTurnIndex);
|
||||
return nextModel;
|
||||
};
|
||||
|
||||
export const buildTurnWindowModel = (messages: ChatMessageEntry[]): TurnWindowModel => {
|
||||
const turnIds: string[] = [];
|
||||
const turnMessageStartIndexes: number[] = [];
|
||||
|
||||
Reference in New Issue
Block a user