feat: overhaul task board and cross-conversation workflows

This commit is contained in:
shiyue
2026-08-12 09:41:36 +08:00
parent e7d28935ef
commit 86231f0d97
55 changed files with 11628 additions and 280 deletions

View File

@@ -295,6 +295,12 @@
savedSelection: null,
};
let usageDashboardRequestSeq = 0;
const taskBoardViewState = {
open: false,
savedFocus: null,
};
let taskBoardController = null;
let taskTrackingController = null;
const usageEchartInstances = new Set();
let usageChartResizeFrame = 0;
let usageChartRefreshFrame = 0;
@@ -318,6 +324,7 @@
const pendingNotesByTarget = new Map();
const queuedMessagesByTarget = new Map();
const userMessageIndex = new Map();
const codexAppSteerRecords = new Map();
const pendingSlashDraftsByRequestId = new Map();
const expandedOldSessionGroups = new Set();
document.documentElement.dataset.dividerTime = showAgentDividerTime ? 'show' : 'hide';
@@ -388,6 +395,11 @@
const usageDashboardProjectEmpty = $('#usage-dashboard-project-empty');
const usageDashboardSessionRows = $('#usage-dashboard-session-rows');
const usageDashboardSessionEmpty = $('#usage-dashboard-session-empty');
const taskBoardOpen = $('#task-board-open');
const taskBoardPanel = $('#task-board-panel');
const taskBoardClose = $('#task-board-close');
const taskBoardRoot = $('#task-board-root');
const taskTrackingControl = $('#task-tracking-control');
const sessionList = $('#session-list');
const chatHeader = chatMain?.querySelector('.chat-header') || null;
const messagesWrap = chatMain?.querySelector('.messages-wrap') || null;
@@ -1549,6 +1561,7 @@
function clearUserMessageIndex() {
userMessageIndex.clear();
codexAppSteerRecords.clear();
}
function registerUserMessage(messageId, element, content, timestamp = '') {
@@ -1562,6 +1575,73 @@
});
}
function cloneCodexAppSteerAttachments(attachments) {
return Array.isArray(attachments)
? attachments.map((attachment) => ({ ...attachment }))
: [];
}
function registerCodexAppSteerRecord(messageId, options = {}) {
const id = String(messageId || '').trim();
if (!id || !options.element) return null;
const previous = codexAppSteerRecords.get(id) || {};
const record = {
id,
element: options.element,
text: String(options.text || ''),
attachments: cloneCodexAppSteerAttachments(options.attachments),
sessionId: options.sessionId || null,
mode: options.mode || currentMode,
agent: options.agent || currentAgent,
status: options.status || previous.status || 'pending',
message: options.message || previous.message || '',
committed: previous.committed === true,
inFlight: false,
timestamp: options.timestamp || previous.timestamp || '',
};
codexAppSteerRecords.set(id, record);
return record;
}
function clearCodexAppSteerRecord(messageId) {
const id = String(messageId || '').trim();
if (!id) return;
codexAppSteerRecords.delete(id);
}
function commitCodexAppSteerMessage(record) {
if (!record || record.committed) return false;
const element = record.element;
if (!element || record.sessionId !== currentSessionId) return false;
if (element.dataset.sessionMessage === 'true') {
record.committed = true;
return false;
}
const messageIndex = currentSessionMessageCount;
currentSessionMessageCount += 1;
markSessionMessageElement(element, messageIndex);
record.committed = true;
updateUserOutlinePanel();
return true;
}
function registerCodexAppSteerSessionMessage(sessionId, message, element) {
if (!message || message.role !== 'user' || !message.codexAppSteerStatus || !element) return null;
const messageId = String(message.id || message.messageId || element.dataset.messageId || '').trim();
if (!messageId) return null;
return registerCodexAppSteerRecord(messageId, {
element,
text: message.content,
attachments: message.attachments,
sessionId,
mode: message.mode || currentMode,
agent: message.agent || currentAgent,
status: message.codexAppSteerStatus,
message: message.codexAppSteerMessage,
timestamp: message.timestamp,
});
}
function normalizeOutlineTitleHistory(history) {
if (!Array.isArray(history)) return [];
const normalized = [];
@@ -2118,6 +2198,13 @@
waitingReplyCount: Number(payload.waitingReplyCount || 0),
failedReplyCount: Number(payload.failedReplyCount || 0),
pendingReplies: Array.isArray(payload.pendingReplies) ? deepClone(payload.pendingReplies) : [],
taskTracking: payload.taskTracking ? deepClone(payload.taskTracking) : {
enabled: payload.taskTrackingEnabled === true,
statusId: 'unassigned',
version: 0,
},
taskTrackingEnabled: payload.taskTrackingEnabled === true
|| payload.taskTracking?.enabled === true,
historyTotal,
historyBaseIndex,
historyPending: !!payload.historyPending,
@@ -5287,6 +5374,84 @@
});
}
function setTaskBoardUnderlyingInert(inert) {
if (!chatMain) return;
if (inert) chatMain.setAttribute('data-task-board-inert', 'true');
else chatMain.removeAttribute('data-task-board-inert');
if (messagesWrap) messagesWrap.toggleAttribute('inert', inert);
if (inputArea) inputArea.toggleAttribute('inert', inert);
if (chatHeader) chatHeader.toggleAttribute('inert', inert);
}
function closeTaskBoard(options = {}) {
if (!taskBoardPanel || !taskBoardViewState.open) return;
taskBoardViewState.open = false;
taskBoardPanel.hidden = true;
taskBoardPanel.setAttribute('aria-hidden', 'true');
taskBoardOpen?.setAttribute('aria-expanded', 'false');
setTaskBoardUnderlyingInert(false);
requestAnimationFrame(() => {
if (options.restoreFocus !== false) {
const target = taskBoardViewState.savedFocus;
if (target?.isConnected && typeof target.focus === 'function') target.focus();
else taskBoardOpen?.focus();
}
});
}
function openTaskBoard() {
if (!taskBoardPanel || !taskBoardRoot || !window.CcwebTaskBoard) return;
if (usageDashboardState.open) closeUsageDashboard({ restoreFocus: false });
if (advancedSessionSearchState.open) closeAdvancedSessionSearch({ restoreFocus: false });
taskBoardViewState.savedFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
taskBoardViewState.open = true;
taskBoardPanel.hidden = false;
taskBoardPanel.setAttribute('aria-hidden', 'false');
taskBoardOpen?.setAttribute('aria-expanded', 'true');
setTaskBoardUnderlyingInert(true);
if (isSidebarDrawerMode()) closeSidebar();
if (!taskBoardController) {
taskBoardController = window.CcwebTaskBoard.mount(taskBoardRoot, {
send,
openSession(sessionId) {
closeTaskBoard({ restoreFocus: false });
openSession(sessionId);
},
onError(error) { showToast(error?.message || '任务看板操作失败'); },
});
} else {
taskBoardController.refresh();
}
requestAnimationFrame(() => taskBoardPanel.querySelector('button, input, select')?.focus());
}
function syncTaskTrackingControl(snapshot = null) {
if (!taskTrackingControl || !window.CcwebTaskBoard) return;
const sessionId = snapshot?.sessionId || currentSessionId || '';
const tracking = snapshot?.taskTracking || {};
if (!sessionId) {
taskTrackingControl.hidden = true;
if (taskTrackingController) taskTrackingController.setState({ sessionId: '', disabled: true });
return;
}
taskTrackingControl.hidden = false;
const state = {
sessionId,
enabled: tracking.enabled === true || snapshot?.taskTrackingEnabled === true,
version: Number.isFinite(Number(tracking.version)) ? Number(tracking.version) : null,
disabled: false,
};
if (!taskTrackingController) {
taskTrackingController = window.CcwebTaskBoard.renderTrackingControl(taskTrackingControl, {
...state,
send,
onError(error) { showToast(error?.message || '保存任务跟踪设置失败'); },
});
} else {
taskTrackingController.setState(state);
}
}
function handleUsageStatisticsResult(msg) {
const requestId = String(msg?.requestId || '');
if (!requestId || requestId !== usageDashboardState.requestId) return;
@@ -5703,6 +5868,7 @@
closeCcwebPromptOutlinePanel();
closeFileBrowser();
currentSessionId = null;
syncTaskTrackingControl(null);
loadedHistorySessionId = null;
currentSessionMessageCount = 0;
currentOutlineTitleHistory = [];
@@ -5762,6 +5928,7 @@
activeTodoCallTargets.clear();
}
currentSessionId = snapshot.sessionId;
syncTaskTrackingControl(snapshot);
loadedHistorySessionId = snapshot.sessionId;
currentOutlineTitleHistory = normalizeOutlineTitleHistory(snapshot.titleHistory);
currentSessionMessageCount = Math.max(
@@ -6130,6 +6297,7 @@
function openSession(sessionId, options = {}) {
if (!sessionId) return;
if (taskBoardViewState.open) closeTaskBoard({ restoreFocus: false });
if (usageDashboardState.open) closeUsageDashboard({ restoreFocus: false });
const meta = getSessionMeta(sessionId);
const cachedAgent = sessionCache.get(sessionId)?.snapshot?.agent;
@@ -6728,7 +6896,46 @@
return true;
}
function handleTaskBoardProtocolMessage(msg) {
const taskMessageTypes = new Set([
'task_board_result',
'task_board_event',
'task_tracking_result',
'task_status_result',
'task_status_definitions_result',
'task_archive_result',
]);
if (!taskMessageTypes.has(msg?.type)) return false;
taskBoardController?.handleMessage(msg);
taskTrackingController?.handleMessage(msg);
const task = msg.task && typeof msg.task === 'object' ? msg.task : null;
const sessionId = String(task?.sessionId || msg.sessionId || '');
if (task?.taskTracking && sessionId) {
const listItem = sessions.find((session) => session.id === sessionId);
if (listItem) {
listItem.taskTracking = deepClone(task.taskTracking);
listItem.taskTrackingEnabled = task.taskTracking.enabled === true;
}
const cached = sessionCache.get(sessionId)?.snapshot;
if (cached) {
cached.taskTracking = deepClone(task.taskTracking);
cached.taskTrackingEnabled = task.taskTracking.enabled === true;
}
if (sessionId === currentSessionId) {
syncTaskTrackingControl({
sessionId,
taskTracking: task.taskTracking,
taskTrackingEnabled: task.taskTracking.enabled === true,
});
}
}
return true;
}
function handleServerMessage(msg) {
if (handleTaskBoardProtocolMessage(msg)) return;
switch (msg.type) {
case 'auth_result':
if (msg.success) {
@@ -6928,9 +7135,10 @@
case 'session_message':
if (msg.sessionId && msg.message) {
const isCrossConversationReply = !!msg.message.crossConversation?.replyToRequestId;
const isProvisionalCodexAppSteer = !!msg.message.codexAppSteerStatus;
updateCachedSession(msg.sessionId, (snapshot) => {
snapshot.messages = Array.isArray(snapshot.messages) ? snapshot.messages : [];
snapshot.messages.push(deepClone(msg.message));
if (!isProvisionalCodexAppSteer) snapshot.messages.push(deepClone(msg.message));
snapshot.updated = msg.message.timestamp || new Date().toISOString();
if (isCrossConversationReply) {
snapshot.readyReplyCount = Math.max(0, Number(snapshot.readyReplyCount || 0) - 1);
@@ -6953,13 +7161,16 @@
}
}
if (msg.sessionId === currentSessionId && msg.message) {
const messageIndex = currentSessionMessageCount;
currentSessionMessageCount += 1;
const isProvisionalCodexAppSteer = !!msg.message.codexAppSteerStatus;
const messageIndex = isProvisionalCodexAppSteer ? null : currentSessionMessageCount;
if (!isProvisionalCodexAppSteer) 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, messageIndex));
const element = buildMsgElement(msg.message, messageIndex);
messagesDiv.appendChild(element);
registerCodexAppSteerSessionMessage(msg.sessionId, msg.message, element);
followOutputIfNeeded(shouldFollow);
setCurrentSessionRunningState(!!getSessionMeta(currentSessionId)?.isRunning);
renderPendingCcwebPrompts({ scroll: false });
@@ -7231,6 +7442,7 @@
title: request.title,
branchSourceSessionId: request.branchSourceSessionId,
branchMessageIndex: request.branchMessageIndex,
taskTrackingEnabled: request.taskTrackingEnabled === true,
createCwd: true,
requestId: request.requestId,
});
@@ -7244,6 +7456,7 @@
title: request.title,
branchSourceSessionId: request.branchSourceSessionId,
branchMessageIndex: request.branchMessageIndex,
taskTrackingEnabled: request.taskTrackingEnabled === true,
});
},
});
@@ -7490,13 +7703,81 @@
return '引导中...';
}
function retryCodexAppSteerMessage(messageId) {
const id = String(messageId || '').trim();
const record = id ? codexAppSteerRecords.get(id) : null;
if (!record || record.status !== 'failed' || record.inFlight) return false;
if (record.sessionId && currentSessionId && record.sessionId !== currentSessionId) {
appendError('该失败项不属于当前会话,无法重试。');
setCodexAppSteerStatusElement(record.element, 'failed', record.message || '插入失败');
return false;
}
if (!isGenerating || !currentSessionRunning || !isCodexAppAgent(currentAgent)) {
appendError('当前对话已结束,无法在原 turn 中重试插入。');
setCodexAppSteerStatusElement(record.element, 'failed', record.message || '插入失败');
return false;
}
if (!ws || ws.readyState !== 1) {
appendError('WebSocket 已断开,请恢复连接后重试。');
setCodexAppSteerStatusElement(record.element, 'failed', record.message || '插入失败');
return false;
}
record.inFlight = true;
setCodexAppSteerStatusElement(record.element, 'pending', '引导中...');
try {
send({
type: 'message',
text: record.text,
attachments: cloneCodexAppSteerAttachments(record.attachments),
sessionId: record.sessionId,
mode: record.mode,
agent: record.agent,
clientMessageId: id,
});
return true;
} catch (err) {
record.inFlight = false;
record.status = 'failed';
record.message = record.message || '插入失败';
setCodexAppSteerStatusElement(record.element, 'failed', record.message);
appendError(err?.message || '重试发送失败。');
return false;
}
}
function deleteCodexAppSteerMessage(messageId) {
const id = String(messageId || '').trim();
if (!id) return false;
const record = codexAppSteerRecords.get(id);
const indexed = userMessageIndex.get(id);
const element = record?.element || indexed?.element || messagesDiv.querySelector(`[data-message-id="${cssEscape(id)}"]`);
const status = record?.status || element?.querySelector('.codex-steer-status')?.dataset.status;
if (status !== 'failed') return false;
if (record?.inFlight) return false;
if (element) element.remove();
clearCodexAppSteerRecord(id);
userMessageIndex.delete(id);
updateUserOutlinePanel();
updateScrollbar();
return true;
}
function setCodexAppSteerStatusElement(element, status, message) {
if (!element) return false;
const normalized = ['pending', 'inserted', 'failed'].includes(status) ? status : 'pending';
const messageId = String(element.dataset.messageId || '').trim();
const record = messageId ? codexAppSteerRecords.get(messageId) : null;
if (record) {
record.status = normalized;
record.message = message || codexAppSteerStatusLabel(normalized);
if (normalized !== 'pending') record.inFlight = false;
}
element.classList.add('codex-steer-message');
element.classList.toggle('codex-steer-pending', normalized === 'pending');
element.classList.toggle('codex-steer-inserted', normalized === 'inserted');
element.classList.toggle('codex-steer-failed', normalized === 'failed');
element.setAttribute('aria-busy', normalized === 'pending' ? 'true' : 'false');
const bubble = element.querySelector('.msg-bubble');
if (!bubble) return false;
let statusEl = bubble.querySelector('.codex-steer-status');
@@ -7506,7 +7787,45 @@
bubble.appendChild(statusEl);
}
statusEl.dataset.status = normalized;
statusEl.setAttribute('role', 'status');
statusEl.setAttribute('aria-live', 'polite');
statusEl.textContent = message || codexAppSteerStatusLabel(normalized);
let actions = bubble.querySelector('.codex-steer-actions');
if (normalized !== 'failed') {
if (actions) actions.remove();
if (normalized === 'inserted' && record) commitCodexAppSteerMessage(record);
return true;
}
if (!actions) {
actions = document.createElement('div');
actions.className = 'codex-steer-actions';
const retryButton = document.createElement('button');
retryButton.type = 'button';
retryButton.className = 'codex-steer-action codex-steer-retry';
retryButton.textContent = '重试';
retryButton.setAttribute('aria-label', '重试插入此消息');
retryButton.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
retryCodexAppSteerMessage(messageId);
});
const deleteButton = document.createElement('button');
deleteButton.type = 'button';
deleteButton.className = 'codex-steer-action codex-steer-delete';
deleteButton.textContent = '删除';
deleteButton.setAttribute('aria-label', '删除失败插入项');
deleteButton.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
deleteCodexAppSteerMessage(messageId);
});
actions.appendChild(retryButton);
actions.appendChild(deleteButton);
bubble.appendChild(actions);
}
actions.querySelectorAll('button').forEach((button) => {
button.disabled = !!record?.inFlight;
});
return true;
}
@@ -10883,12 +11202,16 @@
} else {
messagesDiv.appendChild(element);
}
if (currentSessionId) {
const messageIndex = currentSessionMessageCount;
currentSessionMessageCount += 1;
markSessionMessageElement(element, messageIndex);
}
registerUserMessage(messageId, element, text, timestamp);
registerCodexAppSteerRecord(messageId, {
element,
text,
attachments,
sessionId: currentSessionId,
mode: currentMode,
agent: currentAgent,
timestamp,
});
updateUserOutlinePanel();
if (shouldFollow) {
scrollToBottom();
@@ -11154,6 +11477,11 @@
setUsageDashboardFeatureEnabled(false);
}
if (taskBoardOpen && taskBoardPanel && taskBoardRoot) {
taskBoardOpen.addEventListener('click', openTaskBoard);
taskBoardClose?.addEventListener('click', () => closeTaskBoard());
}
document.addEventListener('keydown', (event) => {
if ((event.ctrlKey || event.metaKey) && event.shiftKey && event.key.toLowerCase() === 'f') {
event.preventDefault();
@@ -11170,6 +11498,11 @@
closeUsageDashboard();
return;
}
if (event.key === 'Escape' && taskBoardViewState.open) {
event.preventDefault();
closeTaskBoard();
return;
}
if (event.key === 'Escape' && advancedSessionSearchState.open) {
event.preventDefault();
closeAdvancedSessionSearch();
@@ -12563,6 +12896,8 @@
const branchMessageIndex = Number.isFinite(Number(options.branchMessageIndex))
? Number(options.branchMessageIndex)
: null;
const taskTrackingEnabled = options.taskTrackingEnabled === true
|| options.taskTracking?.enabled === true;
const requestId = createSessionSwitchRequestId('new');
pendingNewSessionRequest = {
cwd,
@@ -12573,6 +12908,7 @@
title,
branchSourceSessionId,
branchMessageIndex,
taskTrackingEnabled,
requestId,
};
if (cwd) saveRecentCwd(cwd);
@@ -12581,6 +12917,7 @@
if (title) payload.title = title;
if (branchSourceSessionId) payload.branchSourceSessionId = branchSourceSessionId;
if (branchMessageIndex !== null) payload.branchMessageIndex = branchMessageIndex;
if (taskTrackingEnabled) payload.taskTrackingEnabled = true;
send(payload);
}
@@ -12717,6 +13054,7 @@
title: options.title || '',
branchSourceSessionId: options.branchSourceSessionId || '',
branchMessageIndex: options.branchMessageIndex,
taskTrackingEnabled: options.taskTrackingEnabled === true,
});
}

View File

@@ -25,6 +25,7 @@
})();
</script>
<link rel="stylesheet" href="style.css?v=20260805-usage-loading-inline">
<link rel="stylesheet" href="task-board.css?v=__CC_WEB_FRONTEND_ASSET_VERSION__">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
</head>
<body>
@@ -74,6 +75,9 @@
<path d="M5 21v-6"></path><path d="M12 21V9"></path><path d="M19 21V3"></path>
</svg>
</button>
<button id="task-board-open" class="usage-dashboard-open" type="button" title="任务看板" aria-label="打开任务看板" aria-controls="task-board-panel" aria-expanded="false">
<span aria-hidden="true"></span>
</button>
</div>
<span class="brand">CC-Web</span>
</div>
@@ -117,6 +121,14 @@
</div>
</div>
<section id="task-board-panel" class="task-board-panel" role="dialog" aria-labelledby="task-board-panel-title" aria-hidden="true" hidden>
<header class="task-board-panel__header">
<h2 id="task-board-panel-title" class="sr-only">任务看板</h2>
<button id="task-board-close" type="button" class="usage-dashboard__icon-button" title="返回聊天" aria-label="返回聊天"></button>
</header>
<div id="task-board-root" class="task-board-panel__root"></div>
</section>
<!-- Slash command menu -->
<div id="cmd-menu" class="cmd-menu" hidden></div>
@@ -330,6 +342,7 @@
</div>
<button id="reload-mcp-btn" class="reload-mcp-btn" type="button" title="重载 Codex App MCP 配置" hidden>重载 MCP</button>
<span id="cost-display" class="cost-display" hidden></span>
<div id="task-tracking-control" class="task-tracking-control" hidden></div>
</div>
<div id="attachment-tray" class="attachment-tray" hidden></div>
<div id="pending-notes-tray" class="pending-notes-tray" hidden></div>
@@ -387,6 +400,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.1/mermaid.min.js"></script>
<script src="vendor/echarts.min.js?v=5.6.0"></script>
<script>window.ccWebFrontendAssetVersion = '__CC_WEB_FRONTEND_ASSET_VERSION__';</script>
<script src="task-board.js?v=__CC_WEB_FRONTEND_ASSET_VERSION__"></script>
<script src="app.js?v=__CC_WEB_FRONTEND_ASSET_VERSION__"></script>
</body>
</html>

View File

@@ -3143,6 +3143,41 @@ html[data-theme='wasteland'] .advanced-search-panel {
.codex-steer-status[data-status='failed'] {
color: var(--danger);
}
.codex-steer-actions {
display: inline-flex;
align-items: center;
gap: 6px;
margin-top: 8px;
}
.codex-steer-action {
min-height: 24px;
padding: 3px 9px;
border-radius: 6px;
border: 1px solid var(--border-color);
background: rgba(255, 255, 255, 0.08);
color: var(--text-secondary);
font-size: 12px;
font-weight: 700;
line-height: 1.2;
cursor: pointer;
}
.codex-steer-action:hover:not(:disabled),
.codex-steer-action:focus-visible:not(:disabled) {
border-color: var(--accent);
color: var(--text-primary);
}
.codex-steer-action:disabled {
cursor: not-allowed;
opacity: 0.58;
}
.codex-steer-retry {
border-color: rgba(var(--accent-rgb, 66, 153, 225), 0.42);
color: var(--accent);
}
.codex-steer-delete {
border-color: rgba(220, 53, 69, 0.32);
color: var(--danger);
}
.msg.user.codex-steer-pending .msg-copy-btn {
background: rgba(255, 255, 255, 0.08);
border-color: var(--border-color);
@@ -4805,20 +4840,36 @@ html[data-divider-time='hide'] .msg-bubble .agent-message-divider span {
padding-right: 6px;
}
.chat-controls {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
align-items: stretch;
display: flex;
flex-wrap: wrap;
align-items: center;
}
.user-outline-anchor,
.ccweb-prompt-outline-anchor {
position: static;
}
.ccweb-prompt-outline-anchor {
width: 100%;
width: auto;
max-width: 100%;
}
.user-outline-panel,
.ccweb-prompt-outline-panel {
left: 0;
right: 0;
width: auto;
bottom: calc(100% + 6px);
max-height: min(44vh, 300px);
}
.chat-cwd {
width: 100%;
max-width: none;
justify-content: center;
}
.chat-cwd,
.mode-select,
.user-outline-btn,
.ccweb-prompt-outline-btn,
.reload-mcp-btn,
.chat-runtime-state {
width: 100%;
max-width: none;
justify-content: center;
}
@@ -4842,17 +4893,6 @@ html[data-divider-time='hide'] .msg-bubble .agent-message-divider span {
left: 0;
right: auto;
}
.user-outline-panel {
left: auto;
right: 0;
width: min(320px, calc(100vw - 20px));
max-height: min(44vh, 300px);
bottom: calc(100% + 6px);
}
.ccweb-prompt-outline-panel {
left: 0;
right: auto;
}
.msg-copy-btn {
opacity: 1;
width: 24px;

1141
public/task-board.css Normal file

File diff suppressed because it is too large Load Diff

1700
public/task-board.js Normal file

File diff suppressed because it is too large Load Diff