feat: auto-fold tool calls into single group after 5 items
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# 更新记录
|
||||
|
||||
- **v1.2.6**
|
||||
- 工具调用折叠显示:长任务中散落的工具调用块达到5个时自动折叠入唯一父节点,之后每满5个再次移入同一父节点(两级结构),输出结束后剩余散落项也一并收入;总数不超过5个则完整显示不折叠。
|
||||
- 新增编辑模板弹窗「获取上游模型列表」:通过 `/v1/models` 端点拉取可用模型,填充到四个模型输入框的下拉建议列表,支持自定义端点地址。
|
||||
- 修改密码改为按钮+弹窗模式:设置面板中密码修改从内联表单改为独立弹窗,成功后自动关闭。
|
||||
- 子弹窗关闭按钮样式适配:编辑模板和修改密码弹窗的关闭按钮统一为与主面板一致的风格。
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
let pendingText = '';
|
||||
let renderTimer = null;
|
||||
let activeToolCalls = new Map();
|
||||
let toolGroupCount = 0; // 当前 .msg-tools 直接子节点数(含已有父目录)
|
||||
let hasGrouped = false; // 本次输出是否已触发过折叠
|
||||
let cmdMenuIndex = -1;
|
||||
let currentMode = localStorage.getItem('cc-web-mode') || 'yolo';
|
||||
let currentModel = 'opus';
|
||||
@@ -353,6 +355,8 @@
|
||||
isGenerating = true;
|
||||
pendingText = '';
|
||||
activeToolCalls.clear();
|
||||
toolGroupCount = 0;
|
||||
hasGrouped = false;
|
||||
sendBtn.hidden = true;
|
||||
abortBtn.hidden = false;
|
||||
// 不禁用输入框,允许用户继续输入(但无法发送)
|
||||
@@ -388,11 +392,39 @@
|
||||
if (typing) typing.remove();
|
||||
|
||||
const streamEl = document.getElementById('streaming-msg');
|
||||
if (streamEl) streamEl.removeAttribute('id');
|
||||
if (streamEl) {
|
||||
// 若本轮出现过父目录,把末尾散落的 .tool-call 也一并收入同一父节点
|
||||
if (hasGrouped) {
|
||||
const toolsDiv = streamEl.querySelector('.msg-tools');
|
||||
if (toolsDiv) {
|
||||
const loose = Array.from(toolsDiv.children).filter(c => c.classList.contains('tool-call'));
|
||||
if (loose.length > 0) {
|
||||
let group = toolsDiv.querySelector(':scope > .tool-group');
|
||||
if (!group) {
|
||||
group = document.createElement('details');
|
||||
group.className = 'tool-group';
|
||||
const gs = document.createElement('summary');
|
||||
gs.className = 'tool-group-summary';
|
||||
group.appendChild(gs);
|
||||
const inner = document.createElement('div');
|
||||
inner.className = 'tool-group-inner';
|
||||
group.appendChild(inner);
|
||||
toolsDiv.insertBefore(group, toolsDiv.firstChild);
|
||||
}
|
||||
const inner = group.querySelector('.tool-group-inner');
|
||||
loose.forEach(c => inner.appendChild(c));
|
||||
_refreshGroupSummary(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
streamEl.removeAttribute('id');
|
||||
}
|
||||
|
||||
if (sessionId) currentSessionId = sessionId;
|
||||
pendingText = '';
|
||||
activeToolCalls.clear();
|
||||
toolGroupCount = 0;
|
||||
hasGrouped = false;
|
||||
}
|
||||
|
||||
// --- Rendering ---
|
||||
@@ -458,6 +490,8 @@
|
||||
const el = createMsgElement(m.role, m.content);
|
||||
if (m.role === 'assistant' && m.toolCalls && m.toolCalls.length > 0) {
|
||||
const bubble = el.querySelector('.msg-bubble');
|
||||
const FOLD_AT = 5;
|
||||
let grouped = false;
|
||||
for (const tc of m.toolCalls) {
|
||||
const details = document.createElement('details');
|
||||
details.className = 'tool-call';
|
||||
@@ -468,8 +502,41 @@
|
||||
details.appendChild(summary);
|
||||
const displayInput = tc.name === 'AskUserQuestion' ? tc.input : (tc.result || tc.input);
|
||||
details.appendChild(buildToolContentElement(tc.name, displayInput));
|
||||
|
||||
// 散落的 .tool-call 达到 FOLD_AT 个时,移入唯一 .tool-group
|
||||
const loose = Array.from(bubble.children).filter(c => c.classList.contains('tool-call'));
|
||||
if (loose.length >= FOLD_AT) {
|
||||
let group = bubble.querySelector(':scope > .tool-group');
|
||||
if (!group) {
|
||||
group = document.createElement('details');
|
||||
group.className = 'tool-group';
|
||||
const gs = document.createElement('summary');
|
||||
gs.className = 'tool-group-summary';
|
||||
group.appendChild(gs);
|
||||
const inner = document.createElement('div');
|
||||
inner.className = 'tool-group-inner';
|
||||
group.appendChild(inner);
|
||||
bubble.insertBefore(group, bubble.firstChild);
|
||||
grouped = true;
|
||||
}
|
||||
const inner = group.querySelector('.tool-group-inner');
|
||||
loose.forEach(c => inner.appendChild(c));
|
||||
_refreshGroupSummary(group);
|
||||
}
|
||||
bubble.appendChild(details);
|
||||
}
|
||||
// 结束时若出现过父目录,收尾散落项
|
||||
if (grouped) {
|
||||
const loose = Array.from(bubble.children).filter(c => c.classList.contains('tool-call'));
|
||||
if (loose.length > 0) {
|
||||
const group = bubble.querySelector(':scope > .tool-group');
|
||||
if (group) {
|
||||
const inner = group.querySelector('.tool-group-inner');
|
||||
loose.forEach(c => inner.appendChild(c));
|
||||
_refreshGroupSummary(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return el;
|
||||
}
|
||||
@@ -703,10 +770,40 @@
|
||||
details.appendChild(summary);
|
||||
details.appendChild(buildToolContentElement(name, input));
|
||||
|
||||
// 折叠策略:只维护唯一一个 .tool-group 父节点
|
||||
// 散落的 .tool-call 直接子节点达到5个时,将它们全部移入父节点;之后继续散落,再达5个再移入
|
||||
const FOLD_AT = 5;
|
||||
const looseBefore = Array.from(toolsDiv.children).filter(c => c.classList.contains('tool-call'));
|
||||
if (looseBefore.length >= FOLD_AT) {
|
||||
// 确保存在唯一的 .tool-group
|
||||
let group = toolsDiv.querySelector(':scope > .tool-group');
|
||||
if (!group) {
|
||||
group = document.createElement('details');
|
||||
group.className = 'tool-group';
|
||||
const gs = document.createElement('summary');
|
||||
gs.className = 'tool-group-summary';
|
||||
group.appendChild(gs);
|
||||
const inner = document.createElement('div');
|
||||
inner.className = 'tool-group-inner';
|
||||
group.appendChild(inner);
|
||||
toolsDiv.insertBefore(group, toolsDiv.firstChild);
|
||||
hasGrouped = true;
|
||||
}
|
||||
const inner = group.querySelector('.tool-group-inner');
|
||||
looseBefore.forEach(c => inner.appendChild(c));
|
||||
_refreshGroupSummary(group);
|
||||
}
|
||||
toolsDiv.appendChild(details);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
function _refreshGroupSummary(group) {
|
||||
const inner = group.querySelector('.tool-group-inner');
|
||||
const count = inner ? inner.childElementCount : 0;
|
||||
const summary = group.querySelector('.tool-group-summary');
|
||||
if (summary) summary.textContent = `展开 ${count} 个工具调用`;
|
||||
}
|
||||
|
||||
function updateToolCall(toolUseId, result) {
|
||||
const el = document.getElementById(`tool-${toolUseId}`);
|
||||
if (!el) return;
|
||||
|
||||
@@ -665,6 +665,39 @@ body {
|
||||
font-family: 'SF Mono', monospace;
|
||||
}
|
||||
|
||||
/* Tool group (auto-fold) */
|
||||
.tool-group {
|
||||
margin: 8px 0;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tool-group-summary {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
background: var(--bg-secondary);
|
||||
list-style: none;
|
||||
user-select: none;
|
||||
}
|
||||
.tool-group-summary::-webkit-details-marker { display: none; }
|
||||
.tool-group-summary::before {
|
||||
content: '▸ ';
|
||||
font-size: 11px;
|
||||
transition: transform 0.2s;
|
||||
display: inline-block;
|
||||
}
|
||||
.tool-group[open] > .tool-group-summary::before { transform: rotate(90deg); }
|
||||
.tool-group-summary:hover { background: var(--bg-tertiary); }
|
||||
.tool-group-inner {
|
||||
padding: 4px 8px;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
.tool-group-inner .tool-call {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
/* AskUserQuestion preview */
|
||||
.ask-user-question {
|
||||
padding: 10px 12px;
|
||||
|
||||
Reference in New Issue
Block a user