修复历史消息回看并重新打包
This commit is contained in:
201
public/app.js
201
public/app.js
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user