chore: rebuild CentOS7 release package
This commit is contained in:
251
public/app.js
251
public/app.js
@@ -2,7 +2,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const ASSET_VERSION = '20260624-icon-refresh';
|
||||
const ASSET_VERSION = '20260625-branch-bubble';
|
||||
const WS_URL = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws`;
|
||||
const RENDER_DEBOUNCE = 100;
|
||||
const COMPOSER_SUGGESTION_DEBOUNCE = 120;
|
||||
@@ -11,10 +11,13 @@
|
||||
const CROSS_CONVERSATION_REPLY_COLLAPSE_STORAGE_KEY = 'cc-web-collapsed-cross-replies';
|
||||
const CROSS_CONVERSATION_REPLY_COLLAPSE_LIMIT = 500;
|
||||
const ASSISTANT_LAST_SECTION_BUTTON_CLASS = 'msg-last-section-btn';
|
||||
const ASSISTANT_BRANCH_BUTTON_CLASS = 'msg-branch-btn';
|
||||
const ASSISTANT_LAST_SECTION_FOCUS_CLASS = 'msg-last-section-focus';
|
||||
const ASSISTANT_LAST_SECTION_SCROLL_OFFSET = 72;
|
||||
const ASSISTANT_LAST_SECTION_SKIP_SELECTOR = [
|
||||
`.${ASSISTANT_LAST_SECTION_BUTTON_CLASS}`,
|
||||
`.${ASSISTANT_BRANCH_BUTTON_CLASS}`,
|
||||
'.msg-action-row',
|
||||
'.msg-tools',
|
||||
'.tool-call',
|
||||
'.tool-group',
|
||||
@@ -190,6 +193,7 @@
|
||||
const attachmentPreviewCache = new Map();
|
||||
let loginPasswordValue = ''; // store login password for force-change flow
|
||||
let currentCwd = null;
|
||||
let currentSessionMessageCount = 0;
|
||||
let currentSessionRunning = false;
|
||||
let fileBrowserState = null;
|
||||
let directoryPickerState = null;
|
||||
@@ -1406,17 +1410,74 @@
|
||||
return button;
|
||||
}
|
||||
|
||||
function getMessageActionRow(bubble) {
|
||||
if (!bubble) return null;
|
||||
let row = bubble.querySelector(':scope > .msg-action-row');
|
||||
if (!row) {
|
||||
row = document.createElement('div');
|
||||
row.className = 'msg-action-row';
|
||||
bubble.appendChild(row);
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
function syncAssistantLastSectionButton(messageEl) {
|
||||
if (!messageEl?.classList?.contains('assistant')) return;
|
||||
const bubble = messageEl.querySelector(':scope > .msg-bubble');
|
||||
if (!bubble) return;
|
||||
let button = bubble.querySelector(`:scope > .${ASSISTANT_LAST_SECTION_BUTTON_CLASS}`);
|
||||
let button = bubble.querySelector(`:scope .${ASSISTANT_LAST_SECTION_BUTTON_CLASS}`);
|
||||
const hasTarget = !!getAssistantLastSectionTarget(bubble);
|
||||
if (!button && !hasTarget) return;
|
||||
if (!button) button = createAssistantLastSectionButton();
|
||||
button.hidden = !hasTarget;
|
||||
button.disabled = !hasTarget;
|
||||
bubble.appendChild(button);
|
||||
getMessageActionRow(bubble)?.appendChild(button);
|
||||
}
|
||||
|
||||
function createAssistantBranchButton(messageIndex) {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = ASSISTANT_BRANCH_BUTTON_CLASS;
|
||||
button.title = '从这里分支新会话';
|
||||
button.setAttribute('aria-label', '从这里分支新会话');
|
||||
button.dataset.messageIndex = String(messageIndex);
|
||||
button.innerHTML = `
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M6 3v12"></path>
|
||||
<circle cx="6" cy="18" r="3"></circle>
|
||||
<circle cx="18" cy="6" r="3"></circle>
|
||||
<path d="M6 9h6a6 6 0 0 0 6-6"></path>
|
||||
<path d="M15 18h6"></path>
|
||||
<path d="M18 15v6"></path>
|
||||
</svg>
|
||||
`;
|
||||
button.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const index = Number.parseInt(button.dataset.messageIndex || '', 10);
|
||||
branchFromAssistantMessage(index);
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
function syncAssistantBranchButton(messageEl, messageIndex) {
|
||||
if (!messageEl?.classList?.contains('assistant')) return;
|
||||
if (!currentSessionId || !Number.isFinite(messageIndex) || messageIndex < 0) return;
|
||||
const bubble = messageEl.querySelector(':scope > .msg-bubble');
|
||||
if (!bubble) return;
|
||||
let button = bubble.querySelector(`:scope .${ASSISTANT_BRANCH_BUTTON_CLASS}`);
|
||||
if (!button) button = createAssistantBranchButton(messageIndex);
|
||||
button.dataset.messageIndex = String(messageIndex);
|
||||
getMessageActionRow(bubble)?.appendChild(button);
|
||||
}
|
||||
|
||||
function markSessionMessageElement(messageEl, messageIndex) {
|
||||
if (!messageEl || !Number.isFinite(messageIndex) || messageIndex < 0) return;
|
||||
messageEl.dataset.sessionMessage = 'true';
|
||||
messageEl.dataset.messageIndex = String(messageIndex);
|
||||
if (messageEl.classList.contains('assistant')) {
|
||||
syncAssistantBranchButton(messageEl, messageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function updateSessionIdBadge() {
|
||||
@@ -1534,10 +1595,17 @@
|
||||
|
||||
function normalizeSessionSnapshot(payload, options = {}) {
|
||||
const sessionId = payload.sessionId || payload.id || '';
|
||||
const messages = cloneMessages(payload.messages || []);
|
||||
const historyTotal = Number.isFinite(Number(payload.historyTotal))
|
||||
? Math.max(0, Number(payload.historyTotal))
|
||||
: messages.length;
|
||||
const historyBaseIndex = Number.isFinite(Number(payload.historyBaseIndex))
|
||||
? Math.max(0, Number(payload.historyBaseIndex))
|
||||
: Math.max(0, historyTotal - messages.length);
|
||||
return {
|
||||
sessionId,
|
||||
id: sessionId,
|
||||
messages: cloneMessages(payload.messages || []),
|
||||
messages,
|
||||
title: payload.title || '新会话',
|
||||
mode: payload.mode || 'yolo',
|
||||
model: payload.model || '',
|
||||
@@ -1558,6 +1626,8 @@
|
||||
waitingReplyCount: Number(payload.waitingReplyCount || 0),
|
||||
failedReplyCount: Number(payload.failedReplyCount || 0),
|
||||
pendingReplies: Array.isArray(payload.pendingReplies) ? deepClone(payload.pendingReplies) : [],
|
||||
historyTotal,
|
||||
historyBaseIndex,
|
||||
historyPending: !!payload.historyPending,
|
||||
complete: options.complete !== undefined ? !!options.complete : !payload.historyPending,
|
||||
};
|
||||
@@ -3332,6 +3402,7 @@
|
||||
closeFileBrowser();
|
||||
currentSessionId = null;
|
||||
loadedHistorySessionId = null;
|
||||
currentSessionMessageCount = 0;
|
||||
clearSessionLoading();
|
||||
setCurrentSessionRunningState(false);
|
||||
currentCwd = null;
|
||||
@@ -3375,6 +3446,11 @@
|
||||
}
|
||||
currentSessionId = snapshot.sessionId;
|
||||
loadedHistorySessionId = snapshot.sessionId;
|
||||
currentSessionMessageCount = Math.max(
|
||||
snapshot.historyTotal || 0,
|
||||
snapshot.historyBaseIndex + (snapshot.messages || []).length,
|
||||
(snapshot.messages || []).length,
|
||||
);
|
||||
setLastSessionForAgent(snapshot.agent, currentSessionId);
|
||||
chatTitle.textContent = snapshot.title || '新会话';
|
||||
updateSessionIdBadge();
|
||||
@@ -3393,7 +3469,10 @@
|
||||
}
|
||||
currentModel = snapshot.model || '';
|
||||
if (!preserveStreaming) {
|
||||
renderMessages(snapshot.messages || [], { immediate: !!options.immediate });
|
||||
renderMessages(snapshot.messages || [], {
|
||||
immediate: !!options.immediate,
|
||||
baseIndex: snapshot.historyBaseIndex || 0,
|
||||
});
|
||||
if (snapshot.isRunning && snapshot.sessionId === currentSessionId) {
|
||||
startGenerating(snapshot.sessionId);
|
||||
}
|
||||
@@ -4231,6 +4310,7 @@
|
||||
prependHistoryMessages(msg.messages || [], {
|
||||
preserveScroll: !blocking,
|
||||
skipScrollbar: blocking,
|
||||
baseIndex: Number.isFinite(Number(msg.historyBaseIndex)) ? Number(msg.historyBaseIndex) : 0,
|
||||
});
|
||||
if (!msg.remaining) {
|
||||
finalizeLoadedSession(msg.sessionId);
|
||||
@@ -4266,11 +4346,13 @@
|
||||
}
|
||||
}
|
||||
if (msg.sessionId === currentSessionId && msg.message) {
|
||||
const messageIndex = currentSessionMessageCount;
|
||||
currentSessionMessageCount += 1;
|
||||
collectClosedCollabAgentIds([msg.message]).forEach((id) => closedCollabAgentIds.add(id));
|
||||
const welcome = messagesDiv.querySelector('.welcome-msg');
|
||||
if (welcome) welcome.remove();
|
||||
const shouldFollow = !(currentSessionRunning || isGenerating) || isNearBottom();
|
||||
messagesDiv.appendChild(buildMsgElement(msg.message));
|
||||
messagesDiv.appendChild(buildMsgElement(msg.message, messageIndex));
|
||||
followOutputIfNeeded(shouldFollow);
|
||||
setCurrentSessionRunningState(!!getSessionMeta(currentSessionId)?.isRunning);
|
||||
}
|
||||
@@ -4494,6 +4576,10 @@
|
||||
cwd: request.cwd,
|
||||
agent: request.agent,
|
||||
mode: request.mode,
|
||||
model: request.model,
|
||||
title: request.title,
|
||||
branchSourceSessionId: request.branchSourceSessionId,
|
||||
branchMessageIndex: request.branchMessageIndex,
|
||||
createCwd: true,
|
||||
requestId: request.requestId,
|
||||
});
|
||||
@@ -4503,6 +4589,10 @@
|
||||
agent: request.agent,
|
||||
cwd: request.rawCwd || request.cwd,
|
||||
mode: request.mode,
|
||||
model: request.model,
|
||||
title: request.title,
|
||||
branchSourceSessionId: request.branchSourceSessionId,
|
||||
branchMessageIndex: request.branchMessageIndex,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -4632,6 +4722,10 @@
|
||||
|
||||
function finishGenerating(sessionId) {
|
||||
if (sessionId && currentSessionId && sessionId !== currentSessionId) return;
|
||||
const hasPersistedAssistantMessage = !!(
|
||||
pendingText
|
||||
|| (Array.isArray(window.pendingContentBlocks) && window.pendingContentBlocks.length > 0)
|
||||
);
|
||||
isGenerating = false;
|
||||
generatingSessionId = null;
|
||||
updateNoteModeUI();
|
||||
@@ -4674,6 +4768,11 @@
|
||||
}
|
||||
}
|
||||
streamEl.removeAttribute('id');
|
||||
if (hasPersistedAssistantMessage && currentSessionId) {
|
||||
const messageIndex = currentSessionMessageCount;
|
||||
currentSessionMessageCount += 1;
|
||||
markSessionMessageElement(streamEl, messageIndex);
|
||||
}
|
||||
syncAssistantLastSectionButton(streamEl);
|
||||
}
|
||||
|
||||
@@ -5769,7 +5868,7 @@
|
||||
return fallbackMessages.length > 0 ? fallbackMessages[fallbackMessages.length - 1] : null;
|
||||
}
|
||||
|
||||
function buildMsgElement(m) {
|
||||
function buildMsgElement(m, messageIndex = null) {
|
||||
const el = createMsgElement(m.role, m.content, m.attachments || [], m);
|
||||
if (m.role === 'assistant' && m.toolCalls && m.toolCalls.length > 0) {
|
||||
const bubble = el.querySelector('.msg-bubble');
|
||||
@@ -5820,12 +5919,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Number.isFinite(messageIndex)) {
|
||||
markSessionMessageElement(el, messageIndex);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
function renderMessages(messages, options = {}) {
|
||||
renderEpoch++;
|
||||
const epoch = renderEpoch;
|
||||
const baseIndex = Number.isFinite(Number(options.baseIndex)) ? Number(options.baseIndex) : 0;
|
||||
closedCollabAgentIds = collectClosedCollabAgentIds(messages);
|
||||
messagesDiv.innerHTML = '';
|
||||
clearUserMessageIndex();
|
||||
@@ -5838,7 +5941,7 @@
|
||||
}
|
||||
if (options.immediate) {
|
||||
const frag = document.createDocumentFragment();
|
||||
messages.forEach((message) => frag.appendChild(buildMsgElement(message)));
|
||||
messages.forEach((message, index) => frag.appendChild(buildMsgElement(message, baseIndex + index)));
|
||||
messagesDiv.appendChild(frag);
|
||||
updateUserOutlinePanel();
|
||||
renderPendingNotes({ scroll: false });
|
||||
@@ -5861,7 +5964,7 @@
|
||||
|
||||
// Render first batch immediately
|
||||
const frag0 = document.createDocumentFragment();
|
||||
for (let i = batches[0][0]; i < batches[0][1]; i++) frag0.appendChild(buildMsgElement(messages[i]));
|
||||
for (let i = batches[0][0]; i < batches[0][1]; i++) frag0.appendChild(buildMsgElement(messages[i], baseIndex + i));
|
||||
messagesDiv.appendChild(frag0);
|
||||
updateUserOutlinePanel();
|
||||
renderPendingNotes({ scroll: false });
|
||||
@@ -5878,7 +5981,7 @@
|
||||
const prevHeight = messagesDiv.scrollHeight;
|
||||
const prevScrollTop = messagesDiv.scrollTop;
|
||||
const frag = document.createDocumentFragment();
|
||||
for (let i = start; i < end; i++) frag.appendChild(buildMsgElement(messages[i]));
|
||||
for (let i = start; i < end; i++) frag.appendChild(buildMsgElement(messages[i], baseIndex + i));
|
||||
messagesDiv.insertBefore(frag, messagesDiv.firstChild);
|
||||
updateUserOutlinePanel();
|
||||
// Compensate scrollTop so visible area stays unchanged
|
||||
@@ -5890,13 +5993,14 @@
|
||||
|
||||
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 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) => frag.appendChild(buildMsgElement(m)));
|
||||
messages.forEach((m, index) => frag.appendChild(buildMsgElement(m, baseIndex + index)));
|
||||
if (!preserveScroll) {
|
||||
messagesDiv.insertBefore(frag, messagesDiv.firstChild);
|
||||
updateUserOutlinePanel();
|
||||
@@ -7210,6 +7314,11 @@
|
||||
const messageId = createLocalId('user');
|
||||
const element = createMsgElement('user', text, attachments, { messageId });
|
||||
messagesDiv.appendChild(element);
|
||||
if (currentSessionId) {
|
||||
const messageIndex = currentSessionMessageCount;
|
||||
currentSessionMessageCount += 1;
|
||||
markSessionMessageElement(element, messageIndex);
|
||||
}
|
||||
registerUserMessage(messageId, element, text);
|
||||
updateUserOutlinePanel();
|
||||
scrollToBottom();
|
||||
@@ -7259,6 +7368,11 @@
|
||||
} else {
|
||||
messagesDiv.appendChild(element);
|
||||
}
|
||||
if (currentSessionId) {
|
||||
const messageIndex = currentSessionMessageCount;
|
||||
currentSessionMessageCount += 1;
|
||||
markSessionMessageElement(element, messageIndex);
|
||||
}
|
||||
registerUserMessage(messageId, element, text);
|
||||
updateUserOutlinePanel();
|
||||
if (shouldFollow) {
|
||||
@@ -7886,6 +8000,31 @@
|
||||
</select>
|
||||
</div>
|
||||
<div id="codex-profile-area"></div>
|
||||
|
||||
<div class="settings-divider"></div>
|
||||
<div class="settings-section-title">容量失败重试</div>
|
||||
<div class="settings-field">
|
||||
<label>自动重试</label>
|
||||
<select class="settings-select" id="codex-retry-mode">
|
||||
<option value="limited">按次数重试</option>
|
||||
<option value="forever">一直重试</option>
|
||||
<option value="off">关闭</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="settings-retry-grid">
|
||||
<div class="settings-field">
|
||||
<label>间隔(秒)</label>
|
||||
<input type="number" id="codex-retry-interval" min="1" max="3600" step="1" inputmode="numeric">
|
||||
</div>
|
||||
<div class="settings-field" id="codex-retry-attempts-field">
|
||||
<label>重试次数</label>
|
||||
<input type="number" id="codex-retry-attempts" min="1" max="1000" step="1" inputmode="numeric">
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-inline-note" id="codex-retry-note">
|
||||
仅对没有产生文本或工具调用的 Codex 容量/过载失败生效,避免重复执行已有副作用的任务。
|
||||
</div>
|
||||
|
||||
<div class="settings-actions">
|
||||
<button class="btn-save" id="codex-save-btn">保存 Codex 配置</button>
|
||||
</div>
|
||||
@@ -7918,6 +8057,11 @@
|
||||
const closeBtn = panel.querySelector('.settings-close');
|
||||
const codexModeSelect = panel.querySelector('#codex-mode');
|
||||
const codexProfileArea = panel.querySelector('#codex-profile-area');
|
||||
const codexRetryModeSelect = panel.querySelector('#codex-retry-mode');
|
||||
const codexRetryIntervalInput = panel.querySelector('#codex-retry-interval');
|
||||
const codexRetryAttemptsInput = panel.querySelector('#codex-retry-attempts');
|
||||
const codexRetryAttemptsField = panel.querySelector('#codex-retry-attempts-field');
|
||||
const codexRetryNote = panel.querySelector('#codex-retry-note');
|
||||
const codexStatus = panel.querySelector('#codex-status');
|
||||
const codexSaveBtn = panel.querySelector('#codex-save-btn');
|
||||
|
||||
@@ -7928,6 +8072,7 @@
|
||||
let currentCodexConfig = null;
|
||||
let codexEditingProfiles = [];
|
||||
let codexActiveProfile = '';
|
||||
let codexRetryConfig = { mode: 'limited', intervalSeconds: 2, maxAttempts: 3 };
|
||||
let _onUpdateInfo = null;
|
||||
|
||||
function showCodexStatus(msg, type) {
|
||||
@@ -7935,6 +8080,42 @@
|
||||
codexStatus.className = 'settings-status ' + (type || '');
|
||||
}
|
||||
|
||||
function normalizeCodexRetryConfig(raw = {}) {
|
||||
const mode = ['off', 'limited', 'forever'].includes(raw.mode) ? raw.mode : 'limited';
|
||||
const intervalSeconds = Math.max(1, Math.min(3600, Number.parseInt(String(raw.intervalSeconds || ''), 10) || 2));
|
||||
const maxAttempts = Math.max(1, Math.min(1000, Number.parseInt(String(raw.maxAttempts || ''), 10) || 3));
|
||||
return { mode, intervalSeconds, maxAttempts };
|
||||
}
|
||||
|
||||
function syncCodexRetryInputs() {
|
||||
const mode = codexRetryModeSelect.value;
|
||||
const disabled = mode === 'off';
|
||||
codexRetryIntervalInput.disabled = disabled;
|
||||
codexRetryAttemptsInput.disabled = disabled || mode === 'forever';
|
||||
codexRetryAttemptsField.classList.toggle('settings-field-disabled', disabled || mode === 'forever');
|
||||
codexRetryNote.textContent = mode === 'off'
|
||||
? '已关闭自动重试;容量/过载失败会直接显示错误。'
|
||||
: mode === 'forever'
|
||||
? '会一直按固定间隔重试;仍只在没有文本或工具调用时触发。'
|
||||
: '按指定次数重试;仍只在没有文本或工具调用时触发,避免重复执行副作用。';
|
||||
}
|
||||
|
||||
function setCodexRetryConfig(config) {
|
||||
codexRetryConfig = normalizeCodexRetryConfig(config);
|
||||
codexRetryModeSelect.value = codexRetryConfig.mode;
|
||||
codexRetryIntervalInput.value = String(codexRetryConfig.intervalSeconds);
|
||||
codexRetryAttemptsInput.value = String(codexRetryConfig.maxAttempts);
|
||||
syncCodexRetryInputs();
|
||||
}
|
||||
|
||||
function readCodexRetryConfig() {
|
||||
return normalizeCodexRetryConfig({
|
||||
mode: codexRetryModeSelect.value,
|
||||
intervalSeconds: codexRetryIntervalInput.value,
|
||||
maxAttempts: codexRetryAttemptsInput.value,
|
||||
});
|
||||
}
|
||||
|
||||
function renderCodexProfileArea() {
|
||||
const mode = codexModeSelect.value;
|
||||
if (mode === 'local') {
|
||||
@@ -8091,10 +8272,13 @@
|
||||
codexModeSelect.value = currentCodexConfig.mode || 'local';
|
||||
codexEditingProfiles = (currentCodexConfig.profiles || []).map((profile) => ({ ...profile }));
|
||||
codexActiveProfile = currentCodexConfig.activeProfile || (codexEditingProfiles[0]?.name || '');
|
||||
setCodexRetryConfig(currentCodexConfig.retry || codexRetryConfig);
|
||||
renderCodexProfileArea();
|
||||
};
|
||||
|
||||
codexModeSelect.addEventListener('change', renderCodexProfileArea);
|
||||
codexRetryModeSelect.addEventListener('change', syncCodexRetryInputs);
|
||||
setCodexRetryConfig(codexRetryConfig);
|
||||
|
||||
codexSaveBtn.addEventListener('click', () => {
|
||||
if (codexModeSelect.value === 'custom' && codexEditingProfiles.length === 0) {
|
||||
@@ -8106,6 +8290,7 @@
|
||||
activeProfile: codexActiveProfile,
|
||||
profiles: codexEditingProfiles,
|
||||
enableSearch: false,
|
||||
retry: readCodexRetryConfig(),
|
||||
};
|
||||
send({ type: 'save_codex_config', config });
|
||||
showCodexStatus('已保存', 'success');
|
||||
@@ -8680,21 +8865,59 @@
|
||||
try { localStorage.setItem(RECENT_CWD_KEY, JSON.stringify(list)); } catch {}
|
||||
}
|
||||
|
||||
function branchFromAssistantMessage(messageIndex) {
|
||||
if (!currentSessionId) {
|
||||
appendError('当前没有可分支的会话。', { transient: true, autoDismissMs: 4000 });
|
||||
return;
|
||||
}
|
||||
if (!Number.isFinite(messageIndex) || messageIndex < 0) {
|
||||
appendError('无法定位分支消息,请刷新会话后重试。', { transient: true, autoDismissMs: 4000 });
|
||||
return;
|
||||
}
|
||||
const sourceTitle = (chatTitle.textContent || '新会话').trim() || '新会话';
|
||||
const cwd = currentCwd || getSessionEffectiveCwd(currentSessionId) || null;
|
||||
requestNewSession({
|
||||
cwd,
|
||||
rawCwd: cwd || '',
|
||||
agent: currentAgent,
|
||||
mode: currentMode,
|
||||
model: currentModel || '',
|
||||
title: `${sourceTitle} 的分支`,
|
||||
branchSourceSessionId: currentSessionId,
|
||||
branchMessageIndex: messageIndex,
|
||||
});
|
||||
}
|
||||
|
||||
function requestNewSession(options = {}) {
|
||||
const cwd = options.cwd || null;
|
||||
const rawCwd = options.rawCwd !== undefined ? options.rawCwd : (cwd || '');
|
||||
const agent = normalizeAgent(options.agent || currentAgent);
|
||||
const mode = ['default', 'plan', 'yolo'].includes(options.mode) ? options.mode : currentMode;
|
||||
const model = typeof options.model === 'string' ? options.model.trim() : '';
|
||||
const title = typeof options.title === 'string' ? options.title.trim() : '';
|
||||
const branchSourceSessionId = String(options.branchSourceSessionId || '').trim();
|
||||
const branchMessageIndex = Number.isFinite(Number(options.branchMessageIndex))
|
||||
? Number(options.branchMessageIndex)
|
||||
: null;
|
||||
const requestId = createSessionSwitchRequestId('new');
|
||||
pendingNewSessionRequest = {
|
||||
cwd,
|
||||
rawCwd,
|
||||
agent,
|
||||
mode,
|
||||
model,
|
||||
title,
|
||||
branchSourceSessionId,
|
||||
branchMessageIndex,
|
||||
requestId,
|
||||
};
|
||||
if (cwd) saveRecentCwd(cwd);
|
||||
send({ type: 'new_session', cwd, agent, mode, requestId });
|
||||
const payload = { type: 'new_session', cwd, agent, mode, requestId };
|
||||
if (model) payload.model = model;
|
||||
if (title) payload.title = title;
|
||||
if (branchSourceSessionId) payload.branchSourceSessionId = branchSourceSessionId;
|
||||
if (branchMessageIndex !== null) payload.branchMessageIndex = branchMessageIndex;
|
||||
send(payload);
|
||||
}
|
||||
|
||||
// --- New Session Modal ---
|
||||
@@ -8828,6 +9051,10 @@
|
||||
rawCwd,
|
||||
agent: targetAgent,
|
||||
mode: requestedMode,
|
||||
model: options.model || '',
|
||||
title: options.title || '',
|
||||
branchSourceSessionId: options.branchSourceSessionId || '',
|
||||
branchMessageIndex: options.branchMessageIndex,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
document.documentElement.dataset.dividerTime = dividerTime;
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="style.css?v=20260624-icon-refresh">
|
||||
<link rel="stylesheet" href="style.css?v=20260625-branch-bubble">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
||||
</head>
|
||||
<body>
|
||||
@@ -169,6 +169,6 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.1/mermaid.min.js"></script>
|
||||
<script src="app.js?v=20260624-icon-refresh"></script>
|
||||
<script src="app.js?v=20260625-branch-bubble"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2376,11 +2376,22 @@ body.session-loading-active {
|
||||
border-bottom-left-radius: 4px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.msg-last-section-btn {
|
||||
.msg-action-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.msg-action-row:empty {
|
||||
display: none;
|
||||
}
|
||||
.msg-last-section-btn,
|
||||
.msg-branch-btn {
|
||||
appearance: none;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: 8px 0 0 auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -2393,21 +2404,25 @@ body.session-loading-active {
|
||||
opacity: 0.72;
|
||||
transition: opacity 0.15s ease, background 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.15s ease;
|
||||
}
|
||||
.msg-last-section-btn[hidden] {
|
||||
.msg-last-section-btn[hidden],
|
||||
.msg-branch-btn[hidden] {
|
||||
display: none;
|
||||
}
|
||||
.msg-last-section-btn svg {
|
||||
.msg-last-section-btn svg,
|
||||
.msg-branch-btn svg {
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.msg-last-section-btn:hover {
|
||||
.msg-last-section-btn:hover,
|
||||
.msg-branch-btn:hover {
|
||||
opacity: 1;
|
||||
background: var(--bg-tertiary);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.msg-last-section-btn:focus-visible {
|
||||
.msg-last-section-btn:focus-visible,
|
||||
.msg-branch-btn:focus-visible {
|
||||
opacity: 1;
|
||||
outline: 2px solid rgba(91, 126, 161, 0.28);
|
||||
outline-offset: 2px;
|
||||
@@ -4167,6 +4182,19 @@ html[data-divider-time='hide'] .msg-bubble .agent-message-divider span {
|
||||
}
|
||||
.settings-field input:focus,
|
||||
.settings-select:focus { border-color: var(--accent); }
|
||||
.settings-field input:disabled,
|
||||
.settings-select:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.55;
|
||||
}
|
||||
.settings-field-disabled {
|
||||
opacity: 0.65;
|
||||
}
|
||||
.settings-retry-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
.settings-select {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
@@ -4219,6 +4247,7 @@ html[data-divider-time='hide'] .msg-bubble .agent-message-divider span {
|
||||
.settings-panel { width: 95%; padding: 20px 16px; }
|
||||
.settings-nav-card { padding: 13px 14px; }
|
||||
.settings-back { width: 32px; height: 32px; }
|
||||
.settings-retry-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* === Force Change Password Overlay === */
|
||||
|
||||
Reference in New Issue
Block a user