chore: rebuild CentOS7 release package
This commit is contained in:
208
public/app.js
208
public/app.js
@@ -2,7 +2,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const ASSET_VERSION = '20260629-ccweb-prompt-dark-theme';
|
||||
const ASSET_VERSION = '20260702-visible-no-rerender';
|
||||
const WS_URL = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws`;
|
||||
const RENDER_DEBOUNCE = 100;
|
||||
const COMPOSER_SUGGESTION_DEBOUNCE = 120;
|
||||
@@ -76,6 +76,8 @@
|
||||
const OLD_SESSION_COLLAPSE_DAYS = 7;
|
||||
const OLD_SESSION_COLLAPSE_MS = OLD_SESSION_COLLAPSE_DAYS * 24 * 60 * 60 * 1000;
|
||||
const SESSION_LOAD_OVERLAY_TIMEOUT_MS = 12_000;
|
||||
const SESSION_LOAD_REQUEST_TIMEOUT_MS = 45_000;
|
||||
const SESSION_RESUME_FALLBACK_MS = 1_500;
|
||||
|
||||
const MODEL_OPTIONS = [
|
||||
{ value: 'opus', label: 'Opus', desc: '最强大,1M 上下文' },
|
||||
@@ -183,6 +185,8 @@
|
||||
let loadedHistorySessionId = null;
|
||||
let activeSessionLoad = null;
|
||||
let sessionLoadOverlayTimer = null;
|
||||
let sessionLoadRequestTimer = null;
|
||||
let sessionResumeFallbackTimer = null;
|
||||
let sidebarSwipe = null;
|
||||
let activeComposerToken = null;
|
||||
let composerSuggestionTimer = null;
|
||||
@@ -202,6 +206,7 @@
|
||||
let codexAppApprovalModal = null;
|
||||
let pendingNewSessionRequest = null;
|
||||
let pendingSessionSwitchRequest = null;
|
||||
let pendingSessionResumeRequest = null;
|
||||
let sessionSwitchRequestSeq = 0;
|
||||
let skipDeleteConfirm = localStorage.getItem('cc-web-skip-delete-confirm') === '1';
|
||||
let pendingInitialSessionLoad = false;
|
||||
@@ -4126,7 +4131,11 @@
|
||||
if (fileBrowserState && (fileBrowserState.sessionId !== snapshot.sessionId || (snapshot.cwd && fileBrowserState.rootPath !== snapshot.cwd))) {
|
||||
closeFileBrowser();
|
||||
}
|
||||
const preserveStreaming = !!(options.preserveStreaming && isGenerating && snapshot.sessionId === currentSessionId && snapshot.isRunning);
|
||||
const hasStreamingElement = !!document.getElementById('streaming-msg');
|
||||
const preserveStreaming = !!(options.preserveStreaming &&
|
||||
snapshot.sessionId === currentSessionId &&
|
||||
snapshot.isRunning &&
|
||||
(isGenerating || currentSessionRunning || hasStreamingElement));
|
||||
if (isGenerating && !preserveStreaming) {
|
||||
isGenerating = false;
|
||||
generatingSessionId = null;
|
||||
@@ -4220,6 +4229,59 @@
|
||||
sessionLoadOverlayTimer = null;
|
||||
}
|
||||
|
||||
function clearSessionLoadRequestTimer() {
|
||||
if (!sessionLoadRequestTimer) return;
|
||||
clearTimeout(sessionLoadRequestTimer);
|
||||
sessionLoadRequestTimer = null;
|
||||
}
|
||||
|
||||
function clearSessionResumeFallbackTimer() {
|
||||
if (!sessionResumeFallbackTimer) return;
|
||||
clearTimeout(sessionResumeFallbackTimer);
|
||||
sessionResumeFallbackTimer = null;
|
||||
}
|
||||
|
||||
function clearPendingSessionSwitchRequest(sessionId, requestId) {
|
||||
if (!pendingSessionSwitchRequest) return;
|
||||
if (sessionId && pendingSessionSwitchRequest.sessionId !== sessionId) return;
|
||||
if (requestId && pendingSessionSwitchRequest.requestId !== requestId) return;
|
||||
pendingSessionSwitchRequest = null;
|
||||
}
|
||||
|
||||
function clearPendingSessionResumeRequest(sessionId, requestId) {
|
||||
if (!pendingSessionResumeRequest) return;
|
||||
if (sessionId && pendingSessionResumeRequest.sessionId !== sessionId) return;
|
||||
if (requestId && pendingSessionResumeRequest.requestId !== requestId) return;
|
||||
pendingSessionResumeRequest = null;
|
||||
clearSessionResumeFallbackTimer();
|
||||
}
|
||||
|
||||
function notifySessionLoadTimeout(sessionId) {
|
||||
const meta = sessionId ? getSessionMeta(sessionId) : null;
|
||||
const title = meta?.title ? `“${meta.title}”` : '所选会话';
|
||||
appendError(`${title} 加载超时,已取消本次切换。若服务刚重启或网络恢复后,可重新点击该会话。`, {
|
||||
transient: true,
|
||||
autoDismissMs: 9000,
|
||||
preserveScroll: false,
|
||||
});
|
||||
}
|
||||
|
||||
function scheduleSessionLoadRequestTimeout(sessionId, requestId) {
|
||||
clearSessionLoadRequestTimer();
|
||||
if (!sessionId || !requestId) return;
|
||||
sessionLoadRequestTimer = setTimeout(() => {
|
||||
sessionLoadRequestTimer = null;
|
||||
if (!activeSessionLoad ||
|
||||
activeSessionLoad.sessionId !== sessionId ||
|
||||
activeSessionLoad.requestId !== requestId) {
|
||||
return;
|
||||
}
|
||||
clearPendingSessionSwitchRequest(sessionId, requestId);
|
||||
clearSessionLoading(sessionId);
|
||||
notifySessionLoadTimeout(sessionId);
|
||||
}, SESSION_LOAD_REQUEST_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
function releaseSessionLoadingOverlay({ keepActiveLoad = true, allowRetry = false } = {}) {
|
||||
clearSessionLoadOverlayTimer();
|
||||
document.body.classList.remove('session-loading-active');
|
||||
@@ -4236,10 +4298,19 @@
|
||||
|
||||
function setSessionLoading(sessionId, options = {}) {
|
||||
clearSessionLoadOverlayTimer();
|
||||
clearSessionLoadRequestTimer();
|
||||
const loading = !!sessionId;
|
||||
const blocking = options.blocking !== false;
|
||||
const requestId = loading ? (options.requestId || createSessionSwitchRequestId(sessionId)) : '';
|
||||
activeSessionLoad = loading ? { sessionId, blocking, snapshot: null, requestId, overlayReleased: false } : null;
|
||||
activeSessionLoad = loading ? {
|
||||
sessionId,
|
||||
blocking,
|
||||
snapshot: null,
|
||||
requestId,
|
||||
overlayReleased: false,
|
||||
recoverCurrent: options.recoverCurrent === true,
|
||||
} : null;
|
||||
if (loading) scheduleSessionLoadRequestTimeout(sessionId, requestId);
|
||||
const showOverlay = !!(loading && blocking);
|
||||
if (showOverlay) {
|
||||
document.body.classList.add('session-loading-active');
|
||||
@@ -4315,6 +4386,7 @@
|
||||
blocking: options.blocking !== false,
|
||||
label: options.label || '',
|
||||
requestId: options.requestId || activeSessionLoad?.requestId || createSessionSwitchRequestId(sessionId),
|
||||
recoverCurrent: options.recoverCurrent === true,
|
||||
};
|
||||
if (ws && ws.readyState === 1 && wsAuthenticated) {
|
||||
flushPendingSessionSwitch();
|
||||
@@ -4323,9 +4395,42 @@
|
||||
if (!ws || ws.readyState > 1) connect();
|
||||
}
|
||||
|
||||
function requestSessionResume(sessionId, options = {}) {
|
||||
if (!sessionId) return;
|
||||
pendingSessionResumeRequest = {
|
||||
sessionId,
|
||||
requestId: options.requestId || createSessionSwitchRequestId(sessionId),
|
||||
};
|
||||
if (ws && ws.readyState === 1 && wsAuthenticated) {
|
||||
flushPendingSessionResume();
|
||||
return;
|
||||
}
|
||||
if (!ws || ws.readyState > 1) connect();
|
||||
}
|
||||
|
||||
function scheduleSessionResumeFallback(sessionId, requestId) {
|
||||
clearSessionResumeFallbackTimer();
|
||||
if (!sessionId || !requestId) return;
|
||||
sessionResumeFallbackTimer = setTimeout(() => {
|
||||
sessionResumeFallbackTimer = null;
|
||||
if (!pendingSessionResumeRequest ||
|
||||
pendingSessionResumeRequest.sessionId !== sessionId ||
|
||||
pendingSessionResumeRequest.requestId !== requestId) {
|
||||
return;
|
||||
}
|
||||
pendingSessionResumeRequest = null;
|
||||
requestSessionLoad(sessionId, {
|
||||
blocking: false,
|
||||
label: '正在恢复运行输出…',
|
||||
requestId: createSessionSwitchRequestId(sessionId),
|
||||
recoverCurrent: true,
|
||||
});
|
||||
}, SESSION_RESUME_FALLBACK_MS);
|
||||
}
|
||||
|
||||
function flushPendingSessionSwitch() {
|
||||
if (!pendingSessionSwitchRequest) return;
|
||||
if (!ws || ws.readyState !== 1 || !wsAuthenticated) return;
|
||||
if (!pendingSessionSwitchRequest) return false;
|
||||
if (!ws || ws.readyState !== 1 || !wsAuthenticated) return false;
|
||||
const request = pendingSessionSwitchRequest;
|
||||
pendingSessionSwitchRequest = null;
|
||||
if (!activeSessionLoad) {
|
||||
@@ -4333,9 +4438,20 @@
|
||||
blocking: request.blocking,
|
||||
label: request.label || undefined,
|
||||
requestId: request.requestId,
|
||||
recoverCurrent: request.recoverCurrent === true,
|
||||
});
|
||||
}
|
||||
ws.send(JSON.stringify({ type: 'load_session', sessionId: request.sessionId, requestId: request.requestId }));
|
||||
return true;
|
||||
}
|
||||
|
||||
function flushPendingSessionResume() {
|
||||
if (!pendingSessionResumeRequest) return false;
|
||||
if (!ws || ws.readyState !== 1 || !wsAuthenticated) return false;
|
||||
const request = pendingSessionResumeRequest;
|
||||
ws.send(JSON.stringify({ type: 'resume_session', sessionId: request.sessionId, requestId: request.requestId }));
|
||||
scheduleSessionResumeFallback(request.sessionId, request.requestId);
|
||||
return true;
|
||||
}
|
||||
|
||||
function showCachedSession(sessionId) {
|
||||
@@ -4883,9 +4999,15 @@
|
||||
if (activeSessionLoad?.sessionId && !isPageUnloading) {
|
||||
pendingSessionSwitchRequest = {
|
||||
sessionId: activeSessionLoad.sessionId,
|
||||
blocking: activeSessionLoad.blocking,
|
||||
blocking: false,
|
||||
label: sessionLoadingLabel?.textContent || '',
|
||||
requestId: activeSessionLoad.requestId || createSessionSwitchRequestId(activeSessionLoad.sessionId),
|
||||
recoverCurrent: activeSessionLoad.recoverCurrent === true,
|
||||
};
|
||||
} else if (currentSessionId && (isGenerating || currentSessionRunning) && !isPageUnloading) {
|
||||
pendingSessionResumeRequest = {
|
||||
sessionId: currentSessionId,
|
||||
requestId: createSessionSwitchRequestId(currentSessionId),
|
||||
};
|
||||
}
|
||||
clearSessionLoading();
|
||||
@@ -4934,7 +5056,18 @@
|
||||
document.dispatchEvent(new CustomEvent('cc-web-auth-restored'));
|
||||
loginOverlay.hidden = true;
|
||||
app.hidden = false;
|
||||
flushPendingSessionSwitch();
|
||||
const flushedSessionSwitch = flushPendingSessionSwitch();
|
||||
const flushedSessionResume = flushedSessionSwitch ? false : flushPendingSessionResume();
|
||||
if (!flushedSessionSwitch &&
|
||||
!flushedSessionResume &&
|
||||
!pendingSessionSwitchRequest &&
|
||||
!pendingSessionResumeRequest &&
|
||||
currentSessionId &&
|
||||
(isGenerating || currentSessionRunning)) {
|
||||
requestSessionResume(currentSessionId, {
|
||||
requestId: createSessionSwitchRequestId(currentSessionId),
|
||||
});
|
||||
}
|
||||
send({ type: 'get_codex_config' });
|
||||
// Check if must change password
|
||||
if (msg.mustChangePassword) {
|
||||
@@ -4944,6 +5077,8 @@
|
||||
}
|
||||
} else {
|
||||
pendingSessionSwitchRequest = null;
|
||||
pendingSessionResumeRequest = null;
|
||||
clearSessionResumeFallbackTimer();
|
||||
clearSessionLoading();
|
||||
authToken = null;
|
||||
wsAuthenticated = false;
|
||||
@@ -5007,7 +5142,7 @@
|
||||
const canSwitchToSessionInfo = matchesActiveLoad
|
||||
|| matchesPendingNewSession
|
||||
|| msg.sessionId === currentSessionId
|
||||
|| (!currentSessionId && !activeLoad && !pendingNewSession)
|
||||
|| (!messageRequestId && !currentSessionId && !activeLoad && !pendingNewSession)
|
||||
|| (!messageRequestId && !activeLoad && !pendingNewSession);
|
||||
mergeSessionListSnapshot(snapshot);
|
||||
if (matchesActiveLoad) {
|
||||
@@ -5041,6 +5176,10 @@
|
||||
break;
|
||||
|
||||
case 'session_history_chunk':
|
||||
if (activeSessionLoad?.recoverCurrent && activeSessionLoad.sessionId === msg.sessionId) {
|
||||
if (!msg.remaining) finalizeLoadedSession(msg.sessionId);
|
||||
break;
|
||||
}
|
||||
if (msg.sessionId === currentSessionId && loadedHistorySessionId === msg.sessionId) {
|
||||
const blocking = isBlockingSessionLoad(msg.sessionId);
|
||||
if (activeSessionLoad?.sessionId === msg.sessionId && activeSessionLoad.snapshot) {
|
||||
@@ -5274,10 +5413,11 @@
|
||||
|
||||
case 'resume_generating':
|
||||
if (!isCurrentSessionEvent(msg)) break;
|
||||
clearPendingSessionResumeRequest(msg.sessionId || currentSessionId, msg.requestId);
|
||||
// Server has an active process for this session — resume streaming
|
||||
setCurrentSessionRunningState(true);
|
||||
if (!isGenerating || !document.getElementById('streaming-msg')) {
|
||||
startGenerating(msg.sessionId || currentSessionId);
|
||||
startGenerating(msg.sessionId || currentSessionId, { follow: isNearBottom() });
|
||||
} else {
|
||||
updateGenerationControls();
|
||||
toolGroupCount = 0;
|
||||
@@ -5312,8 +5452,24 @@
|
||||
}
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
case 'resume_session_result':
|
||||
if (!isCurrentSessionEvent(msg)) break;
|
||||
clearPendingSessionResumeRequest(msg.sessionId || currentSessionId, msg.requestId);
|
||||
setCurrentSessionRunningState(!!msg.isRunning);
|
||||
if (!msg.isRunning && currentSessionId && msg.sessionId === currentSessionId) {
|
||||
updateGenerationControls();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
const errorRequestId = String(msg.requestId || '');
|
||||
const matchesActiveLoadError = !!(activeSessionLoad &&
|
||||
(!msg.sessionId || msg.sessionId === activeSessionLoad.sessionId) &&
|
||||
(!errorRequestId || errorRequestId === activeSessionLoad.requestId));
|
||||
if (!matchesActiveLoadError && !isCurrentSessionEvent(msg)) break;
|
||||
if (matchesActiveLoadError) {
|
||||
clearPendingSessionSwitchRequest(activeSessionLoad.sessionId, activeSessionLoad.requestId);
|
||||
}
|
||||
if (msg.code === 'new_session_cwd_missing' && pendingNewSessionRequest?.cwd) {
|
||||
const request = pendingNewSessionRequest;
|
||||
pendingNewSessionRequest = null;
|
||||
@@ -5401,8 +5557,12 @@
|
||||
showToast(`「${msg.title}」任务完成`, msg.sessionId);
|
||||
showBrowserNotification(msg.title);
|
||||
if (msg.sessionId === currentSessionId) {
|
||||
// Reload current session to show completed response
|
||||
openSession(msg.sessionId, { forceSync: true, blocking: false });
|
||||
// 当前用户如果正在向上翻历史,不要自动整屏重绘并把滚动条拽回底部。
|
||||
if (isNearBottom()) {
|
||||
openSession(msg.sessionId, { forceSync: true, blocking: false });
|
||||
} else {
|
||||
send({ type: 'list_sessions' });
|
||||
}
|
||||
} else {
|
||||
send({ type: 'list_sessions' });
|
||||
}
|
||||
@@ -5435,9 +5595,10 @@
|
||||
}
|
||||
|
||||
// --- Generating State ---
|
||||
function startGenerating(sessionId = currentSessionId) {
|
||||
function startGenerating(sessionId = currentSessionId, options = {}) {
|
||||
const targetSessionId = sessionId || currentSessionId || null;
|
||||
if (targetSessionId && currentSessionId && targetSessionId !== currentSessionId) return false;
|
||||
const shouldFollow = options.follow !== false;
|
||||
isGenerating = true;
|
||||
generatingSessionId = targetSessionId;
|
||||
setCurrentSessionRunningState(true);
|
||||
@@ -5469,7 +5630,11 @@
|
||||
bubble.appendChild(toolsDiv);
|
||||
syncAssistantLastSectionButton(msgEl);
|
||||
messagesDiv.appendChild(msgEl);
|
||||
scrollToBottom();
|
||||
if (shouldFollow) {
|
||||
scrollToBottom();
|
||||
} else {
|
||||
updateScrollbar();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10210,20 +10375,21 @@
|
||||
rememberPw.checked = true;
|
||||
}
|
||||
|
||||
// Visibility change: re-sync state when user returns to tab (critical for mobile)
|
||||
// 页签切回只做轻量状态同步:不要因为 visible 事件整屏重载当前会话,
|
||||
// 否则 renderMessages() 会重建气泡并把滚动条拉回底部。
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState !== 'visible') return;
|
||||
if (!ws || ws.readyState > 1) {
|
||||
// WS is dead, force reconnect
|
||||
reconnectAttempts = 0;
|
||||
connect();
|
||||
} else if (ws.readyState === 1 && currentSessionId) {
|
||||
// Preserve active streaming UI when returning to foreground.
|
||||
if (isGenerating || currentSessionRunning) {
|
||||
send({ type: 'load_session', sessionId: currentSessionId });
|
||||
} else {
|
||||
beginSessionSwitch(currentSessionId, { blocking: false, force: true });
|
||||
} else if (ws.readyState === 1 && wsAuthenticated) {
|
||||
if (currentSessionId && !activeSessionLoad && (isGenerating || currentSessionRunning)) {
|
||||
requestSessionResume(currentSessionId, {
|
||||
requestId: createSessionSwitchRequestId(currentSessionId),
|
||||
});
|
||||
}
|
||||
send({ type: 'list_sessions' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user