chore: rebuild CentOS7 release package

This commit is contained in:
shiyue
2026-06-27 19:47:52 +08:00
parent 911dd84c35
commit cd37ecf10b
14 changed files with 3128 additions and 653 deletions

View File

@@ -2,11 +2,12 @@
(function () {
'use strict';
const ASSET_VERSION = '20260625-branch-bubble';
const ASSET_VERSION = '20260626-ccweb-prompt-compact-ui';
const WS_URL = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws`;
const RENDER_DEBOUNCE = 100;
const COMPOSER_SUGGESTION_DEBOUNCE = 120;
const DIVIDER_TIME_STORAGE_KEY = 'cc-web-show-divider-time';
const CCWEB_PROMPT_VIEW_MODE_STORAGE_KEY = 'cc-web-ccweb-prompt-view-mode';
const PROJECT_COLLAPSE_STORAGE_KEY = 'cc-web-collapsed-projects';
const CROSS_CONVERSATION_REPLY_COLLAPSE_STORAGE_KEY = 'cc-web-collapsed-cross-replies';
const CROSS_CONVERSATION_REPLY_COLLAPSE_LIMIT = 500;
@@ -210,6 +211,7 @@
let queuedMessageSeq = 0;
let queuedMessageDrainTimer = null;
let isReloadingMcp = false;
const mcpStartupToastKeys = new Map();
let sessionSearchQuery = '';
const collapsedProjectKeys = (() => {
try {
@@ -263,6 +265,8 @@
const chatCwd = $('#chat-cwd');
const userOutlineBtn = $('#user-outline-btn');
const userOutlinePanel = $('#user-outline-panel');
const ccwebPromptOutlineBtn = $('#ccweb-prompt-outline-btn');
const ccwebPromptOutlinePanel = $('#ccweb-prompt-outline-panel');
const reloadMcpBtn = $('#reload-mcp-btn');
const costDisplay = $('#cost-display');
const attachmentTray = $('#attachment-tray');
@@ -525,6 +529,7 @@
pendingNotesTray.innerHTML = '';
const notes = getCurrentNotes(false);
const queuedMessages = getCurrentQueue(false);
renderPendingCcwebPrompts({ scroll: false, updateScrollbar: false });
if ((!notes || notes.length === 0) && (!queuedMessages || queuedMessages.length === 0)) {
pendingNotesTray.hidden = true;
if (options.updateScrollbar !== false) updateScrollbar();
@@ -544,6 +549,140 @@
if (options.updateScrollbar !== false) updateScrollbar();
}
function collectCurrentPendingCcwebPrompts() {
if (!currentSessionId) return [];
const prompts = [];
const seen = new Set();
const entry = sessionCache.get(currentSessionId);
const messages = Array.isArray(entry?.snapshot?.messages) ? entry.snapshot.messages : [];
messages.forEach((message) => {
const prompt = message?.ccwebPrompt;
if (!prompt?.id || seen.has(prompt.id) || (prompt.status || 'pending') !== 'pending') return;
seen.add(prompt.id);
prompts.push(prompt);
});
messagesDiv?.querySelectorAll?.('.ccweb-prompt-card[data-status="pending"]').forEach((card) => {
const promptId = card.dataset.promptId || '';
if (!promptId || seen.has(promptId)) return;
seen.add(promptId);
prompts.push({
id: promptId,
title: card.querySelector('.ccweb-prompt-title')?.textContent || '需要用户确认',
questions: Array.from(card.querySelectorAll('.ccweb-prompt-question')).map((questionEl) => ({
id: questionEl.dataset.questionId || '',
})),
});
});
return prompts;
}
function scrollToCcwebPrompt(promptId) {
if (!promptId || !messagesDiv) return false;
const card = messagesDiv.querySelector(`.ccweb-prompt-card[data-prompt-id="${cssEscape(promptId)}"]`);
if (!card) {
showToast('未找到待提交表单', '可能在未加载的历史消息中');
return false;
}
const target = card.closest('.msg') || card;
const containerRect = messagesDiv.getBoundingClientRect();
const targetRect = target.getBoundingClientRect();
const targetTop = messagesDiv.scrollTop + targetRect.top - containerRect.top - 72;
messagesDiv.scrollTo({ top: Math.max(0, targetTop), behavior: 'smooth' });
card.classList.remove('ccweb-prompt-focus');
requestAnimationFrame(() => {
card.classList.add('ccweb-prompt-focus');
window.setTimeout(() => card.classList.remove('ccweb-prompt-focus'), 1400);
});
updateScrollbar();
return true;
}
function dismissCcwebPrompt(promptId) {
if (!promptId || !currentSessionId) return;
send({
type: 'ccweb_prompt_user_dismiss',
sessionId: currentSessionId,
promptId,
});
}
function createPendingCcwebPromptElement(prompt) {
const item = document.createElement('div');
item.className = 'pending-ccweb-prompt';
item.dataset.promptId = prompt.id || '';
const badge = document.createElement('span');
badge.className = 'pending-ccweb-prompt-badge';
badge.setAttribute('aria-label', '未提交');
const title = document.createElement('div');
title.className = 'pending-ccweb-prompt-title';
const questionCount = Array.isArray(prompt.questions) ? prompt.questions.length : 0;
title.textContent = `${prompt.title || '需要用户确认'} · ${questionCount || 1}`;
const action = document.createElement('button');
action.type = 'button';
action.className = 'pending-ccweb-prompt-action';
action.textContent = '定位';
action.addEventListener('click', () => {
closeCcwebPromptOutlinePanel();
scrollToCcwebPrompt(prompt.id);
});
const dismiss = document.createElement('button');
dismiss.type = 'button';
dismiss.className = 'pending-ccweb-prompt-dismiss';
dismiss.textContent = '忽略';
dismiss.title = '忽略并删除这个未提交表单';
dismiss.addEventListener('click', () => {
dismiss.disabled = true;
dismiss.textContent = '删除中';
dismissCcwebPrompt(prompt.id);
});
item.append(badge, title, action, dismiss);
return item;
}
function renderPendingCcwebPrompts(options = {}) {
if (!ccwebPromptOutlineBtn || !ccwebPromptOutlinePanel) return;
const prompts = collectCurrentPendingCcwebPrompts();
const anchor = ccwebPromptOutlineBtn.closest('.ccweb-prompt-outline-anchor');
if (prompts.length === 0) {
if (anchor) anchor.hidden = true;
closeCcwebPromptOutlinePanel();
delete ccwebPromptOutlineBtn.dataset.count;
ccwebPromptOutlinePanel.innerHTML = '';
if (options.updateScrollbar !== false) updateScrollbar();
return;
}
if (anchor) anchor.hidden = false;
ccwebPromptOutlineBtn.disabled = false;
ccwebPromptOutlineBtn.dataset.count = String(prompts.length);
ccwebPromptOutlinePanel.replaceChildren(...prompts.map((prompt) => createPendingCcwebPromptElement(prompt)));
if (options.updateScrollbar !== false) updateScrollbar();
}
function closeCcwebPromptOutlinePanel() {
if (!ccwebPromptOutlinePanel || !ccwebPromptOutlineBtn) return;
ccwebPromptOutlinePanel.hidden = true;
ccwebPromptOutlineBtn.setAttribute('aria-expanded', 'false');
}
function toggleCcwebPromptOutlinePanel() {
if (!ccwebPromptOutlinePanel || !ccwebPromptOutlineBtn) return;
if (ccwebPromptOutlinePanel.hidden) {
renderPendingCcwebPrompts({ scroll: false, updateScrollbar: false });
const anchor = ccwebPromptOutlineBtn.closest('.ccweb-prompt-outline-anchor');
if (anchor?.hidden || !ccwebPromptOutlinePanel.children.length) return;
closeUserOutlinePanel();
ccwebPromptOutlinePanel.hidden = false;
ccwebPromptOutlineBtn.setAttribute('aria-expanded', 'true');
} else {
closeCcwebPromptOutlinePanel();
}
}
function findPendingNote(noteId) {
const key = getCurrentNoteKey();
const notes = getNotesForKey(key, false);
@@ -1246,6 +1385,7 @@
if (!userOutlinePanel || !userOutlineBtn) return;
if (userOutlinePanel.hidden) {
updateUserOutlinePanel();
closeCcwebPromptOutlinePanel();
userOutlinePanel.hidden = false;
userOutlineBtn.setAttribute('aria-expanded', 'true');
} else {
@@ -1843,15 +1983,65 @@
reloadMcpBtn.setAttribute('aria-busy', isReloadingMcp ? 'true' : 'false');
}
function normalizeMcpStartupStatusPayload(payload) {
if (!payload || typeof payload !== 'object') return null;
if (payload.mcpStatus && typeof payload.mcpStatus === 'object') return payload.mcpStatus;
if (payload.status && typeof payload.status === 'object') return payload.status;
return payload;
}
function mcpStartupStatusToastText(status) {
const summary = normalizeMcpStartupStatusPayload(status);
if (!summary) return '已请求重载,等待状态';
const server = String(summary.server || summary.name || 'ccweb').trim() || 'ccweb';
const state = String(summary.status || 'unknown').trim().toLowerCase();
const message = String(summary.message || '').trim();
if (state === 'ready') return `${server} MCP 已启动`;
if (state === 'failed') return `${server} MCP 启动失败${message ? `${message}` : ''}`;
if (state === 'cancelled' || state === 'canceled') return `${server} MCP 启动已取消${message ? `${message}` : ''}`;
if (state === 'starting') return `${server} MCP 正在启动`;
if (state === 'pending' || state === 'unknown') return '已请求重载,等待状态';
return `${server} MCP 状态:${state}`;
}
function rememberMcpStartupStatus(sessionId, status) {
const summary = normalizeMcpStartupStatusPayload(status);
if (!sessionId || !summary) return;
updateCachedSession(sessionId, (snapshot) => {
snapshot.codexAppMcpStartupStatus = deepClone(summary);
});
}
function showMcpStartupStatusToast(status, sessionId = currentSessionId, options = {}) {
const summary = normalizeMcpStartupStatusPayload(status);
const text = mcpStartupStatusToastText(summary);
const server = String(summary?.server || summary?.name || 'ccweb').trim() || 'ccweb';
const state = String(summary?.status || 'pending').trim().toLowerCase() || 'pending';
if (state === 'ready' && !options.notifyReady) return;
if ((state === 'starting' || state === 'pending' || state === 'unknown') && !options.notifyPending) return;
const stamp = state === 'failed' || state === 'cancelled' || state === 'canceled'
? String(summary?.message || text || '')
: '';
const cacheKey = sessionId || currentSessionId || 'global';
const nextKey = `${server}|${state}|${stamp}`;
if (mcpStartupToastKeys.get(cacheKey) === nextKey) return;
mcpStartupToastKeys.set(cacheKey, nextKey);
showToast(text, sessionId);
}
async function reloadCurrentMcpServers() {
if (!currentSessionId || !isCodexAppAgent(currentAgent) || isReloadingMcp) return;
isReloadingMcp = true;
updateReloadMcpButtonUI();
try {
await fetchAuthJson(`/api/sessions/${encodeURIComponent(currentSessionId)}/reload-mcp`, {
const data = await fetchAuthJson(`/api/sessions/${encodeURIComponent(currentSessionId)}/reload-mcp`, {
method: 'POST',
});
showToast('已请求重载 MCP');
rememberMcpStartupStatus(currentSessionId, data.mcpStatus);
showMcpStartupStatusToast(data.mcpStatus || { status: 'pending' }, currentSessionId, {
notifyReady: true,
notifyPending: true,
});
} catch (err) {
showToast(err?.message || '重载 MCP 失败');
} finally {
@@ -2102,6 +2292,432 @@
panel.querySelector('input, button')?.focus();
}
function ccwebPromptStatusLabel(status) {
if (status === 'submitted') return '已提交';
if (status === 'cancelled') return '已取消';
return '待回答';
}
function ccwebPromptRecommendedOption(question) {
const options = Array.isArray(question?.options) ? question.options : [];
return options.find((option) => option?.recommended) || options[0] || null;
}
function getCcwebPromptViewMode() {
return localStorage.getItem(CCWEB_PROMPT_VIEW_MODE_STORAGE_KEY) === 'tabs' ? 'tabs' : 'cards';
}
function setCcwebPromptActiveQuestion(card, index) {
if (!card) return;
const questions = Array.from(card.querySelectorAll('.ccweb-prompt-question'));
if (questions.length === 0) return;
const activeIndex = Math.min(Math.max(Number(index) || 0, 0), questions.length - 1);
card.dataset.activeQuestionIndex = String(activeIndex);
questions.forEach((questionEl, questionIndex) => {
const isActive = questionIndex === activeIndex;
questionEl.classList.toggle('is-active', isActive);
questionEl.setAttribute('aria-hidden', isActive ? 'false' : 'true');
});
card.querySelectorAll('.ccweb-prompt-tab').forEach((tab, tabIndex) => {
const isActive = tabIndex === activeIndex;
tab.classList.toggle('is-active', isActive);
tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
tab.tabIndex = isActive ? 0 : -1;
});
const counter = card.querySelector('.ccweb-prompt-tab-counter');
if (counter) counter.textContent = `问题 ${activeIndex + 1} / ${questions.length}`;
const prev = card.querySelector('[data-ccweb-prompt-prev]');
const next = card.querySelector('[data-ccweb-prompt-next]');
if (prev) prev.disabled = activeIndex <= 0;
if (next) next.disabled = activeIndex >= questions.length - 1;
}
function setCcwebPromptViewMode(card, mode) {
if (!card) return;
const normalized = mode === 'tabs' ? 'tabs' : 'cards';
card.dataset.viewMode = normalized;
card.querySelectorAll('.ccweb-prompt-view-btn').forEach((button) => {
const isActive = button.dataset.viewMode === normalized;
button.classList.toggle('is-active', isActive);
button.setAttribute('aria-pressed', isActive ? 'true' : 'false');
});
if (normalized === 'tabs') {
setCcwebPromptActiveQuestion(card, Number(card.dataset.activeQuestionIndex || 0));
} else {
card.querySelectorAll('.ccweb-prompt-question').forEach((questionEl) => {
questionEl.setAttribute('aria-hidden', 'false');
});
}
}
function createCcwebPromptViewControls(card, questions) {
const switcher = document.createElement('div');
switcher.className = 'ccweb-prompt-view-switcher';
switcher.setAttribute('aria-label', '表单显示方式');
[
{ mode: 'cards', label: '▦', title: '卡片视图' },
{ mode: 'tabs', label: '▤', title: '页签视图' },
].forEach((item) => {
const button = document.createElement('button');
button.type = 'button';
button.className = 'ccweb-prompt-view-btn';
button.dataset.viewMode = item.mode;
button.textContent = item.label;
button.title = item.title;
button.setAttribute('aria-label', item.title);
button.addEventListener('click', () => {
localStorage.setItem(CCWEB_PROMPT_VIEW_MODE_STORAGE_KEY, item.mode);
setCcwebPromptViewMode(card, item.mode);
});
switcher.appendChild(button);
});
return switcher;
}
function createCcwebPromptTabs(card, questions) {
const controls = document.createElement('div');
controls.className = 'ccweb-prompt-view-controls';
const tabs = document.createElement('div');
tabs.className = 'ccweb-prompt-tabs';
tabs.setAttribute('role', 'tablist');
questions.forEach((question, index) => {
const tab = document.createElement('button');
tab.type = 'button';
tab.className = 'ccweb-prompt-tab';
tab.setAttribute('role', 'tab');
tab.textContent = question.title || `问题 ${index + 1}`;
tab.addEventListener('click', () => setCcwebPromptActiveQuestion(card, index));
tabs.appendChild(tab);
});
controls.append(tabs);
return controls;
}
function createCcwebPromptTabNav(card, questions) {
const nav = document.createElement('div');
nav.className = 'ccweb-prompt-tab-nav';
if (!Array.isArray(questions) || questions.length <= 1) return nav;
const prev = document.createElement('button');
prev.type = 'button';
prev.className = 'ccweb-prompt-tab-nav-btn';
prev.dataset.ccwebPromptPrev = '1';
prev.textContent = '上一个';
prev.addEventListener('click', () => setCcwebPromptActiveQuestion(card, Number(card.dataset.activeQuestionIndex || 0) - 1));
const counter = document.createElement('span');
counter.className = 'ccweb-prompt-tab-counter';
const next = document.createElement('button');
next.type = 'button';
next.className = 'ccweb-prompt-tab-nav-btn';
next.dataset.ccwebPromptNext = '1';
next.textContent = '下一个';
next.addEventListener('click', () => setCcwebPromptActiveQuestion(card, Number(card.dataset.activeQuestionIndex || 0) + 1));
nav.append(prev, counter, next);
return nav;
}
function setCcwebPromptError(card, message) {
const error = card.querySelector('.ccweb-prompt-error');
if (!error) return;
error.textContent = message || '';
error.hidden = !message;
}
function updateCcwebPromptAnswerFromSelection(questionEl, question) {
const textarea = questionEl.querySelector('.ccweb-prompt-answer');
if (!textarea) return;
const selectedIds = Array.from(questionEl.querySelectorAll('.ccweb-prompt-option.is-selected'))
.map((button) => button.dataset.optionId || '')
.filter(Boolean);
const selectedOptions = (question.options || []).filter((option) => selectedIds.includes(option.id));
const answerText = selectedOptions.map((option) => option.answerText || option.label || '').filter(Boolean).join('\n');
if (answerText) textarea.value = answerText;
}
function selectCcwebPromptOption(questionEl, question, optionId) {
const buttons = Array.from(questionEl.querySelectorAll('.ccweb-prompt-option'));
if (question.selectionMode === 'multi') {
buttons.forEach((button) => {
if (button.dataset.optionId === optionId) button.classList.toggle('is-selected');
});
} else {
buttons.forEach((button) => {
button.classList.toggle('is-selected', button.dataset.optionId === optionId);
});
}
updateCcwebPromptAnswerFromSelection(questionEl, question);
}
function createCcwebPromptQuestionElement(question, index, prompt) {
const questionEl = document.createElement('section');
questionEl.className = 'ccweb-prompt-question';
questionEl.dataset.questionId = question.id || `question_${index + 1}`;
const head = document.createElement('div');
head.className = 'ccweb-prompt-question-head';
const title = document.createElement('div');
title.className = 'ccweb-prompt-question-title';
title.textContent = question.title || `问题 ${index + 1}`;
head.appendChild(title);
if (question.required !== false && prompt.status !== 'submitted') {
const required = document.createElement('span');
required.className = 'ccweb-prompt-required';
required.textContent = '必答';
head.appendChild(required);
}
questionEl.appendChild(head);
if (question.question) {
const body = document.createElement('div');
body.className = 'ccweb-prompt-question-body';
body.textContent = question.question;
questionEl.appendChild(body);
}
if (prompt.status === 'submitted') {
const answer = prompt.answers?.[question.id] || {};
if (Array.isArray(answer.selectedOptionLabels) && answer.selectedOptionLabels.length > 0) {
const selected = document.createElement('div');
selected.className = 'ccweb-prompt-selected-readonly';
selected.textContent = `选择:${answer.selectedOptionLabels.join('')}`;
questionEl.appendChild(selected);
}
const answerText = document.createElement('div');
answerText.className = 'ccweb-prompt-answer-readonly';
answerText.textContent = answer.answerText || '(未填写答案)';
questionEl.appendChild(answerText);
return questionEl;
}
const options = Array.isArray(question.options) ? question.options : [];
if (options.length > 0 && question.selectionMode !== 'none') {
const optionList = document.createElement('div');
optionList.className = 'ccweb-prompt-options';
options.forEach((option) => {
const button = document.createElement('button');
button.type = 'button';
button.className = 'ccweb-prompt-option';
button.dataset.optionId = option.id || '';
const label = document.createElement('span');
label.className = 'ccweb-prompt-option-label';
label.textContent = option.label || option.id || '选项';
button.appendChild(label);
if (option.recommended) {
const badge = document.createElement('span');
badge.className = 'ccweb-prompt-option-badge';
badge.textContent = '推荐';
button.appendChild(badge);
}
if (option.description) {
const desc = document.createElement('span');
desc.className = 'ccweb-prompt-option-desc';
desc.textContent = option.description;
button.appendChild(desc);
}
button.addEventListener('click', () => selectCcwebPromptOption(questionEl, question, option.id));
optionList.appendChild(button);
});
questionEl.appendChild(optionList);
}
const answer = document.createElement('textarea');
answer.className = 'ccweb-prompt-answer';
answer.rows = 4;
answer.placeholder = question.answerPlaceholder || '填写你的答案...';
answer.value = question.defaultAnswer || '';
questionEl.appendChild(answer);
const recommended = ccwebPromptRecommendedOption(question);
if (recommended?.recommended) {
selectCcwebPromptOption(questionEl, question, recommended.id);
}
return questionEl;
}
function collectCcwebPromptAnswers(card, prompt) {
const answers = {};
for (const question of prompt.questions || []) {
const escapedId = cssEscape(question.id || '');
const questionEl = card.querySelector(`.ccweb-prompt-question[data-question-id="${escapedId}"]`);
if (!questionEl) continue;
const selectedOptionIds = Array.from(questionEl.querySelectorAll('.ccweb-prompt-option.is-selected'))
.map((button) => button.dataset.optionId || '')
.filter(Boolean);
const answerText = String(questionEl.querySelector('.ccweb-prompt-answer')?.value || '').trim();
if (question.required !== false && !answerText) {
return { ok: false, message: `请填写「${question.title || question.id}」的答案。` };
}
answers[question.id] = { selectedOptionIds, answerText };
}
return { ok: true, answers };
}
function createCcwebPromptElement(prompt, meta = {}) {
const card = document.createElement('section');
const promptStatus = prompt?.status || 'pending';
const questions = Array.isArray(prompt?.questions) ? prompt.questions : [];
card.className = 'ccweb-prompt-card';
card.dataset.promptId = prompt?.id || '';
card.dataset.status = promptStatus;
card.dataset.viewMode = 'cards';
const header = document.createElement('div');
header.className = 'ccweb-prompt-header';
const titleWrap = document.createElement('div');
titleWrap.className = 'ccweb-prompt-title-wrap';
const title = document.createElement('div');
title.className = 'ccweb-prompt-title';
title.textContent = prompt?.title || '需要用户确认';
titleWrap.appendChild(title);
header.appendChild(titleWrap);
const headerActions = document.createElement('div');
headerActions.className = 'ccweb-prompt-header-actions';
const status = document.createElement('span');
status.className = 'ccweb-prompt-status';
status.textContent = promptStatus === 'pending' ? '●' : ccwebPromptStatusLabel(prompt?.status || 'pending');
status.title = ccwebPromptStatusLabel(prompt?.status || 'pending');
status.setAttribute('aria-label', ccwebPromptStatusLabel(prompt?.status || 'pending'));
headerActions.appendChild(status);
if (promptStatus === 'pending' && questions.length > 1) {
headerActions.appendChild(createCcwebPromptViewControls(card, questions));
}
header.appendChild(headerActions);
card.appendChild(header);
if (prompt?.description) {
const desc = document.createElement('div');
desc.className = 'ccweb-prompt-desc';
desc.textContent = prompt.description;
card.appendChild(desc);
}
if (promptStatus === 'pending' && questions.length > 1) {
card.appendChild(createCcwebPromptTabs(card, questions));
}
const questionsWrap = document.createElement('div');
questionsWrap.className = 'ccweb-prompt-questions';
questions.forEach((question, index) => {
questionsWrap.appendChild(createCcwebPromptQuestionElement(question, index, prompt));
});
card.appendChild(questionsWrap);
const error = document.createElement('div');
error.className = 'ccweb-prompt-error';
error.hidden = true;
card.appendChild(error);
if ((prompt?.status || 'pending') === 'pending') {
const footer = document.createElement('div');
footer.className = 'ccweb-prompt-footer';
if (questions.length > 1) {
footer.appendChild(createCcwebPromptTabNav(card, questions));
}
const footerActions = document.createElement('div');
footerActions.className = 'ccweb-prompt-footer-actions';
const fillRecommended = document.createElement('button');
fillRecommended.type = 'button';
fillRecommended.className = 'ccweb-prompt-secondary';
fillRecommended.textContent = '填入推荐';
fillRecommended.addEventListener('click', () => {
questions.forEach((question) => {
const option = ccwebPromptRecommendedOption(question);
if (!option) return;
const questionEl = card.querySelector(`.ccweb-prompt-question[data-question-id="${cssEscape(question.id || '')}"]`);
if (questionEl) selectCcwebPromptOption(questionEl, question, option.id);
});
});
footerActions.appendChild(fillRecommended);
const submit = document.createElement('button');
submit.type = 'button';
submit.className = 'ccweb-prompt-submit';
submit.textContent = '提交全部';
submit.addEventListener('click', () => {
const collected = collectCcwebPromptAnswers(card, prompt);
if (!collected.ok) {
setCcwebPromptError(card, collected.message);
return;
}
setCcwebPromptError(card, '');
submit.disabled = true;
submit.textContent = '提交中';
send({
type: 'ccweb_prompt_user_response',
sessionId: meta.sessionId || currentSessionId,
promptId: prompt.id,
answers: collected.answers,
});
});
footerActions.appendChild(submit);
footer.appendChild(footerActions);
card.appendChild(footer);
}
if (promptStatus === 'pending' && questions.length > 1) {
setCcwebPromptActiveQuestion(card, 0);
setCcwebPromptViewMode(card, getCcwebPromptViewMode());
}
return card;
}
function updateCcwebPromptMessageInSnapshot(snapshot, prompt) {
if (!snapshot || !Array.isArray(snapshot.messages) || !prompt?.id) return;
for (const message of snapshot.messages) {
if (message?.ccwebPrompt?.id === prompt.id) {
message.ccwebPrompt = deepClone(prompt);
}
}
}
function removeCcwebPromptMessageFromSnapshot(snapshot, promptId) {
if (!snapshot || !Array.isArray(snapshot.messages) || !promptId) return;
snapshot.messages = snapshot.messages.filter((message) => message?.ccwebPrompt?.id !== promptId);
}
function removeCcwebPromptMessageFromDom(promptId) {
if (!promptId) return 0;
let removed = 0;
document.querySelectorAll(`.ccweb-prompt-card[data-prompt-id="${cssEscape(promptId)}"]`).forEach((card) => {
const messageEl = card.closest('.msg');
if (messageEl?.parentNode) {
messageEl.remove();
} else {
card.remove();
}
removed += 1;
});
if (removed > 0) {
updateUserOutlinePanel();
updateScrollbar();
}
return removed;
}
function applyCcwebPromptUserUpdate(msg) {
if (msg.sessionId && msg.prompt) {
updateCachedSession(msg.sessionId, (snapshot) => updateCcwebPromptMessageInSnapshot(snapshot, msg.prompt));
}
if (msg.sessionId !== currentSessionId || !msg.prompt?.id) return;
document.querySelectorAll(`.ccweb-prompt-card[data-prompt-id="${cssEscape(msg.prompt.id)}"]`).forEach((card) => {
card.replaceWith(createCcwebPromptElement(msg.prompt, { sessionId: msg.sessionId }));
});
renderPendingCcwebPrompts({ scroll: false });
}
function applyCcwebPromptUserRemove(msg) {
const promptId = msg.promptId || msg.prompt?.id || '';
if (msg.sessionId && promptId) {
updateCachedSession(msg.sessionId, (snapshot) => removeCcwebPromptMessageFromSnapshot(snapshot, promptId));
}
if (msg.sessionId !== currentSessionId || !promptId) return;
removeCcwebPromptMessageFromDom(promptId);
renderPendingCcwebPrompts({ scroll: false });
}
function closeDirectoryPicker() {
if (!directoryPickerState) return;
const { overlay, escapeHandler } = directoryPickerState;
@@ -3399,6 +4015,7 @@
function resetChatView(agent) {
setCurrentAgent(agent);
closeUserOutlinePanel();
closeCcwebPromptOutlinePanel();
closeFileBrowser();
currentSessionId = null;
loadedHistorySessionId = null;
@@ -3460,6 +4077,7 @@
setCurrentSessionRunningState(snapshot.isRunning);
setStatsDisplay(snapshot);
closeUserOutlinePanel();
closeCcwebPromptOutlinePanel();
currentCwd = snapshot.cwd || null;
updateCwdBadge();
if (snapshot.mode && MODE_LABELS[snapshot.mode]) {
@@ -3492,6 +4110,7 @@
const { preserveCurrent = true, loadLast = true } = options;
setCurrentAgent(targetAgent);
closeUserOutlinePanel();
closeCcwebPromptOutlinePanel();
renderSessionList();
const currentMeta = currentSessionId ? getSessionMeta(currentSessionId) : null;
@@ -3651,6 +4270,7 @@
send({ type: 'detach_view' });
}
closeUserOutlinePanel();
closeCcwebPromptOutlinePanel();
clearSessionLoading();
touchSessionCache(sessionId);
applySessionSnapshot(snapshot, { immediate: true, suppressUnreadToast: true });
@@ -3660,6 +4280,7 @@
function openSession(sessionId, options = {}) {
if (!sessionId) return;
closeUserOutlinePanel();
closeCcwebPromptOutlinePanel();
if (options.forceSync) {
beginSessionSwitch(sessionId, { blocking: options.blocking !== false, force: true, label: options.label });
return;
@@ -4355,6 +4976,7 @@
messagesDiv.appendChild(buildMsgElement(msg.message, messageIndex));
followOutputIfNeeded(shouldFollow);
setCurrentSessionRunningState(!!getSessionMeta(currentSessionId)?.isRunning);
renderPendingCcwebPrompts({ scroll: false });
}
renderSessionList();
break;
@@ -4499,6 +5121,19 @@
applyCcwebMcpChildAgentUpdate(msg);
break;
case 'ccweb_prompt_user_update':
applyCcwebPromptUserUpdate(msg);
break;
case 'ccweb_prompt_user_remove':
applyCcwebPromptUserRemove(msg);
break;
case 'mcp_startup_status':
rememberMcpStartupStatus(msg.sessionId, msg.mcpStatus || msg.status);
showMcpStartupStatusToast(msg.mcpStatus || msg.status, msg.sessionId);
break;
case 'mode_changed':
if (msg.mode && MODE_LABELS[msg.mode]) {
currentMode = msg.mode;
@@ -5870,6 +6505,10 @@
function buildMsgElement(m, messageIndex = null) {
const el = createMsgElement(m.role, m.content, m.attachments || [], m);
if (m.ccwebPrompt) {
const bubble = el.querySelector('.msg-bubble');
if (bubble) bubble.appendChild(createCcwebPromptElement(m.ccwebPrompt, { sessionId: currentSessionId }));
}
if (m.role === 'assistant' && m.toolCalls && m.toolCalls.length > 0) {
const bubble = el.querySelector('.msg-bubble');
const toolMount = bubble.querySelector(':scope > .cross-conversation-reply-body') || bubble;
@@ -7489,6 +8128,16 @@
});
}
if (ccwebPromptOutlineBtn && ccwebPromptOutlinePanel) {
ccwebPromptOutlineBtn.addEventListener('click', (e) => {
e.stopPropagation();
toggleCcwebPromptOutlinePanel();
});
ccwebPromptOutlinePanel.addEventListener('click', (e) => {
e.stopPropagation();
});
}
if (reloadMcpBtn) {
reloadMcpBtn.addEventListener('click', (e) => {
e.stopPropagation();
@@ -7551,6 +8200,11 @@
e.target !== userOutlineBtn) {
closeUserOutlinePanel();
}
if (ccwebPromptOutlinePanel && !ccwebPromptOutlinePanel.hidden &&
!ccwebPromptOutlinePanel.contains(e.target) &&
e.target !== ccwebPromptOutlineBtn) {
closeCcwebPromptOutlinePanel();
}
});
sendBtn.addEventListener('click', sendMessage);
if (queueSendBtn) {