修复历史消息回看并重新打包

This commit is contained in:
shiyue
2026-09-11 09:29:53 +08:00
parent 6fff9023b2
commit b8fcc12229
18 changed files with 494 additions and 73 deletions

View File

@@ -237,6 +237,17 @@
let codexConfigCache = null;
let loadedHistorySessionId = null;
let activeSessionLoad = null;
let historyPageRequestSeq = 0;
let activeHistoryPageRequest = null;
let currentHistoryState = {
sessionId: null,
cursor: 0,
remaining: 0,
total: 0,
available: false,
loading: false,
error: '',
};
let sessionLoadOverlayTimer = null;
let sessionLoadRequestTimer = null;
let sessionResumeFallbackTimer = null;
@@ -436,6 +447,7 @@
const pendingNotesTray = $('#pending-notes-tray');
const imageUploadInput = $('#image-upload-input');
const attachBtn = $('#attach-btn');
const historyLoadMore = $('#history-load-more');
const messagesDiv = $('#messages');
const msgInput = $('#msg-input');
const inputWrapper = msgInput.closest('.input-wrapper');
@@ -2437,6 +2449,15 @@
|| payload.taskTracking?.enabled === true,
historyTotal,
historyBaseIndex,
historyBuffered: Number.isFinite(Number(payload.historyBuffered))
? Math.max(0, Number(payload.historyBuffered))
: messages.length,
historyCursor: Number.isFinite(Number(payload.historyCursor))
? Math.max(0, Number(payload.historyCursor))
: Math.max(0, historyBaseIndex),
historyTruncated: !!payload.historyTruncated,
historyAvailable: payload.historyAvailable !== false,
historySource: payload.historySource || 'snapshot',
historyPending: !!payload.historyPending,
complete: options.complete !== undefined ? !!options.complete : !payload.historyPending,
};
@@ -6277,6 +6298,7 @@
closeCcwebPromptOutlinePanel();
closeFileBrowser();
currentSessionId = null;
resetHistoryLoadState(null);
syncTaskTrackingControl(null);
loadedHistorySessionId = null;
currentSessionMessageCount = 0;
@@ -6336,7 +6358,9 @@
activeToolCalls.clear();
activeTodoCallTargets.clear();
}
if (currentSessionId !== snapshot.sessionId) resetHistoryLoadState(snapshot.sessionId);
currentSessionId = snapshot.sessionId;
syncHistoryLoadControl(snapshot);
syncTaskTrackingControl(snapshot);
loadedHistorySessionId = snapshot.sessionId;
currentOutlineTitleHistory = normalizeOutlineTitleHistory(snapshot.titleHistory);
@@ -6539,6 +6563,117 @@
setSessionLoading(null, { blocking: false });
}
function resetHistoryLoadState(sessionId = null) {
activeHistoryPageRequest = null;
currentHistoryState = {
sessionId,
cursor: 0,
remaining: 0,
total: 0,
available: false,
loading: false,
error: '',
};
renderHistoryLoadControl();
}
function renderHistoryLoadControl() {
if (!historyLoadMore) return;
const state = currentHistoryState;
historyLoadMore.innerHTML = '';
historyLoadMore.hidden = !state.sessionId || state.cursor <= 0;
if (historyLoadMore.hidden) return;
const copy = document.createElement('span');
copy.className = 'history-load-more-copy';
if (!state.available) {
copy.textContent = `还有 ${state.cursor} 条更早消息,但原始记录不可用,无法恢复。`;
historyLoadMore.classList.add('is-unavailable');
historyLoadMore.appendChild(copy);
return;
}
historyLoadMore.classList.remove('is-unavailable');
copy.textContent = state.error
? state.error
: `还有 ${state.cursor} 条更早消息`;
const button = document.createElement('button');
button.type = 'button';
button.className = 'history-load-more-button';
button.textContent = state.loading ? '正在加载…' : (state.error ? '重试' : '查看更早消息');
button.disabled = state.loading;
button.addEventListener('click', requestOlderHistory);
historyLoadMore.append(copy, button);
}
function syncHistoryLoadControl(payload = null) {
const sessionId = payload?.sessionId || currentSessionId;
if (!sessionId || sessionId !== currentSessionId) {
resetHistoryLoadState(sessionId || null);
return;
}
const nextCursor = Number.isFinite(Number(payload?.historyCursor))
? Math.max(0, Number(payload.historyCursor))
: currentHistoryState.cursor;
currentHistoryState = {
...currentHistoryState,
sessionId,
cursor: nextCursor,
remaining: nextCursor,
total: Number.isFinite(Number(payload?.historyTotal))
? Math.max(0, Number(payload.historyTotal))
: currentHistoryState.total,
available: payload?.historyAvailable !== undefined
? payload.historyAvailable !== false
: currentHistoryState.available,
error: '',
};
renderHistoryLoadControl();
}
function requestOlderHistory() {
const state = currentHistoryState;
if (!state.sessionId || state.sessionId !== currentSessionId || state.cursor <= 0 || !state.available) return;
if (activeHistoryPageRequest || !ws || ws.readyState !== 1 || !wsAuthenticated) return;
historyPageRequestSeq += 1;
const requestId = `history-page-${Date.now()}-${historyPageRequestSeq}`;
activeHistoryPageRequest = {
sessionId: state.sessionId,
requestId,
before: state.cursor,
};
currentHistoryState = { ...state, loading: true, error: '' };
renderHistoryLoadControl();
send({
type: 'load_history_page',
sessionId: state.sessionId,
before: state.cursor,
requestId,
});
}
function mergeHistoryChunkIntoCachedSnapshot(sessionId, messages, options = {}) {
if (!sessionId || !Array.isArray(messages) || messages.length === 0) return;
updateCachedSession(sessionId, (snapshot) => {
const existingMessages = Array.isArray(snapshot.messages) ? snapshot.messages : [];
const existingBase = Number.isFinite(Number(snapshot.historyBaseIndex))
? Number(snapshot.historyBaseIndex)
: Math.max(0, Number(snapshot.historyTotal || existingMessages.length) - existingMessages.length);
const incomingBase = Number.isFinite(Number(options.baseIndex)) ? Number(options.baseIndex) : 0;
const indexed = new Map();
existingMessages.forEach((message, index) => indexed.set(existingBase + index, message));
messages.forEach((message, index) => indexed.set(incomingBase + index, message));
const indexes = Array.from(indexed.keys()).sort((a, b) => a - b);
snapshot.messages = indexes.map((index) => indexed.get(index));
snapshot.historyBaseIndex = indexes.length > 0 ? indexes[0] : incomingBase;
snapshot.historyBuffered = snapshot.messages.length;
if (Number.isFinite(Number(options.total))) snapshot.historyTotal = Math.max(0, Number(options.total));
if (Number.isFinite(Number(options.cursor))) snapshot.historyCursor = Math.max(0, Number(options.cursor));
snapshot.historyTruncated = snapshot.historyCursor > 0;
snapshot.historyPending = false;
snapshot.complete = snapshot.historyCursor === 0;
});
}
function createSessionSwitchRequestId(sessionId) {
sessionSwitchRequestSeq += 1;
return `session-load-${Date.now()}-${sessionSwitchRequestSeq}-${String(sessionId || '').slice(0, 8)}`;
@@ -7255,6 +7390,15 @@
if (ws !== socket) return;
ws = null;
wsAuthenticated = false;
if (activeHistoryPageRequest) {
activeHistoryPageRequest = null;
currentHistoryState = {
...currentHistoryState,
loading: false,
error: '连接中断,连接恢复后可重试',
};
renderHistoryLoadControl();
}
if (activeSessionLoad?.sessionId && !isPageUnloading) {
pendingSessionSwitchRequest = {
sessionId: activeSessionLoad.sessionId,
@@ -7512,6 +7656,9 @@
const matchesActiveHistoryLoad = !!(activeSessionLoad
&& activeSessionLoad.sessionId === msg.sessionId
&& (!historyRequestId || activeSessionLoad.requestId === historyRequestId));
const matchesManualHistoryLoad = !!(typeof activeHistoryPageRequest !== 'undefined' && activeHistoryPageRequest
&& activeHistoryPageRequest.sessionId === msg.sessionId
&& activeHistoryPageRequest.requestId === historyRequestId);
const allowsLegacyHistory = !historyRequestId
&& msg.sessionId === currentSessionId
&& loadedHistorySessionId === msg.sessionId;
@@ -7523,7 +7670,7 @@
if (!msg.remaining) finalizeLoadedSession(msg.sessionId, historyRequestId || undefined);
break;
}
if ((matchesActiveHistoryLoad || allowsLegacyHistory)
if ((matchesActiveHistoryLoad || matchesManualHistoryLoad || allowsLegacyHistory)
&& msg.sessionId === currentSessionId
&& loadedHistorySessionId === msg.sessionId) {
const blocking = isBlockingSessionLoad(msg.sessionId);
@@ -7536,7 +7683,29 @@
baseIndex: Number.isFinite(Number(msg.historyBaseIndex)) ? Number(msg.historyBaseIndex) : 0,
});
scheduleAdvancedSearchJump();
if (!msg.remaining) {
if (matchesManualHistoryLoad) {
const nextCursor = Number.isFinite(Number(msg.historyCursor))
? Math.max(0, Number(msg.historyCursor))
: Math.max(0, activeHistoryPageRequest.before - (msg.messages || []).length);
mergeHistoryChunkIntoCachedSnapshot(msg.sessionId, msg.messages || [], {
baseIndex: Number.isFinite(Number(msg.historyBaseIndex)) ? Number(msg.historyBaseIndex) : 0,
cursor: nextCursor,
total: msg.historyTotal,
});
activeHistoryPageRequest = null;
currentHistoryState = {
...currentHistoryState,
cursor: nextCursor,
remaining: nextCursor,
total: Number.isFinite(Number(msg.historyTotal))
? Math.max(0, Number(msg.historyTotal))
: currentHistoryState.total,
available: msg.historyAvailable !== false,
loading: false,
error: '',
};
renderHistoryLoadControl();
} else if (!msg.remaining) {
finalizeLoadedSession(msg.sessionId, historyRequestId || undefined);
}
}
@@ -7836,6 +8005,19 @@
case 'error':
const errorRequestId = String(msg.requestId || '');
const matchesHistoryPageError = !!(typeof activeHistoryPageRequest !== 'undefined' && activeHistoryPageRequest
&& (!msg.sessionId || msg.sessionId === activeHistoryPageRequest.sessionId)
&& errorRequestId === activeHistoryPageRequest.requestId);
if (matchesHistoryPageError) {
activeHistoryPageRequest = null;
currentHistoryState = {
...currentHistoryState,
loading: false,
error: msg.message || '加载更早消息失败,请重试',
};
renderHistoryLoadControl();
break;
}
const matchesActiveLoadError = !!(activeSessionLoad &&
(!msg.sessionId || msg.sessionId === activeSessionLoad.sessionId) &&
(!errorRequestId || errorRequestId === activeSessionLoad.requestId));
@@ -10095,13 +10277,24 @@
function prependHistoryMessages(messages, options = {}) {
if (!Array.isArray(messages) || messages.length === 0) return;
const baseIndex = Number.isFinite(Number(options.baseIndex)) ? Number(options.baseIndex) : 0;
collectClosedCollabAgentIds(messages).forEach((id) => closedCollabAgentIds.add(id));
const existingIndexes = new Set(Array.from(
messagesDiv.querySelectorAll('[data-session-message="true"][data-message-index]'),
).map((element) => Number(element.dataset.messageIndex)));
const uniqueMessages = [];
messages.forEach((message, index) => {
const messageIndex = baseIndex + index;
if (existingIndexes.has(messageIndex)) return;
existingIndexes.add(messageIndex);
uniqueMessages.push({ message, index });
});
if (uniqueMessages.length === 0) return;
collectClosedCollabAgentIds(uniqueMessages.map((entry) => entry.message)).forEach((id) => closedCollabAgentIds.add(id));
const preserveScroll = options.preserveScroll !== false;
const skipScrollbar = options.skipScrollbar === true;
const welcome = messagesDiv.querySelector('.welcome-msg');
if (welcome) welcome.remove();
const frag = document.createDocumentFragment();
messages.forEach((m, index) => frag.appendChild(buildMsgElement(m, baseIndex + index)));
uniqueMessages.forEach(({ message, index }) => frag.appendChild(buildMsgElement(message, baseIndex + index)));
if (!preserveScroll) {
messagesDiv.insertBefore(frag, messagesDiv.firstChild);
updateUserOutlinePanel();