Add files via upload

This commit is contained in:
公明
2026-05-19 16:23:21 +08:00
committed by GitHub
parent 59312d428e
commit a022baef03
4 changed files with 390 additions and 111 deletions
+21
View File
@@ -3753,6 +3753,27 @@ header {
background: rgba(21, 101, 192, 0.07);
}
.timeline-item-tool_call:has(.tool-result-section.success) {
border-left-color: var(--success-color);
background: rgba(40, 167, 69, 0.07);
}
.timeline-item-tool_call:has(.tool-result-section.error) {
border-left-color: var(--error-color);
background: rgba(220, 53, 69, 0.07);
}
.timeline-item-tool_call .tool-result-slot {
margin-top: 8px;
padding-top: 8px;
border-top: 1px dashed rgba(0, 0, 0, 0.12);
}
.timeline-item-tool_call .tool-result-section.pending .tool-result-pending {
color: var(--text-muted);
font-style: italic;
}
.timeline-item-tool_result {
border-left-color: #78909c;
background: rgba(120, 144, 156, 0.06);
+16 -3
View File
@@ -2357,6 +2357,9 @@ function renderProcessDetails(messageId, processDetails) {
detailsContainer.dataset.lazyNotLoaded = '0';
detailsContainer.dataset.loaded = '1';
processDetails = dedupeConsecutiveProcessDetailRows(processDetails);
if (typeof window.coalesceProcessDetailsToolPairs === 'function') {
processDetails = window.coalesceProcessDetailsToolPairs(processDetails);
}
// 如果没有processDetails或为空,显示空状态
if (!processDetails || processDetails.length === 0) {
// 显示空状态提示
@@ -2421,7 +2424,13 @@ function renderProcessDetails(messageId, processDetails) {
const toolName = data.toolName || (typeof window.t === 'function' ? window.t('chat.unknownTool') : '未知工具');
const index = data.index || 0;
const total = data.total || 0;
itemTitle = agPx + '🔧 ' + (typeof window.t === 'function' ? window.t('chat.callTool', { name: escapeHtml(toolName), index: index, total: total }) : '调用工具: ' + escapeHtml(toolName) + ' (' + index + '/' + total + ')');
const argsHint = typeof window.toolCallArgHint === 'function'
? window.toolCallArgHint(typeof window.parseToolCallArgsFromData === 'function' ? window.parseToolCallArgsFromData(data) : {})
: '';
const callTitle = typeof window.formatToolCallTimelineTitle === 'function'
? window.formatToolCallTimelineTitle(toolName, index, total, argsHint)
: (typeof window.t === 'function' ? window.t('chat.callTool', { name: escapeHtml(toolName), index: index, total: total }) : '调用工具: ' + escapeHtml(toolName) + ' (' + index + '/' + total + ')');
itemTitle = agPx + '🔧 ' + callTitle;
} else if (eventType === 'tool_result') {
const toolName = data.toolName || (typeof window.t === 'function' ? window.t('chat.unknownTool') : '未知工具');
const success = data.success !== false;
@@ -2451,12 +2460,16 @@ function renderProcessDetails(messageId, processDetails) {
: '⏸️ 用户中断并继续';
}
addTimelineItem(timeline, eventType, {
const timelineOpts = {
title: itemTitle,
message: detail.message || '',
data: data,
createdAt: detail.createdAt // 传递实际的事件创建时间
});
};
if (eventType === 'tool_call' && data._mergedResult) {
timelineOpts.mergedResult = data._mergedResult;
}
addTimelineItem(timeline, eventType, timelineOpts);
});
// 检查是否有错误或取消事件,如果有,确保详情默认折叠(但仍有待审批 HITL 时保持展开,由 restoreHitlInlineForConversation 处理)
+291 -64
View File
@@ -1565,7 +1565,9 @@ function handleStreamEvent(event, progressElement, progressId,
const index = toolInfo.index || 0;
const total = toolInfo.total || 0;
const toolCallId = toolInfo.toolCallId || null;
const toolCallTitle = typeof window.t === 'function' ? window.t('chat.callTool', { name: escapeHtml(toolName), index: index, total: total }) : '调用工具: ' + escapeHtml(toolName) + ' (' + index + '/' + total + ')';
const toolCallArgs = parseToolCallArgsFromData(toolInfo);
const toolCallHint = toolCallArgHint(toolCallArgs);
const toolCallTitle = formatToolCallTimelineTitle(toolName, index, total, toolCallHint);
const toolCallItemId = addTimelineItem(timeline, 'tool_call', {
title: timelineAgentBracketPrefix(toolInfo) + '🔧 ' + toolCallTitle,
message: event.message,
@@ -1593,44 +1595,33 @@ function handleStreamEvent(event, progressElement, progressId,
const key = toolResultStreamKey(progressId, toolCallId);
let state = toolResultStreamStateByKey.get(key);
const toolNameDelta = deltaInfo.toolName || (typeof window.t === 'function' ? window.t('chat.unknownTool') : '未知工具');
const deltaText = event.message || '';
if (!deltaText) break;
if (!state) {
// 首次增量:创建一个 tool_result 占位条目,后续不断更新 pre 内容
const runningLabel = typeof window.t === 'function' ? window.t('timeline.running') : '执行中...';
const title = timelineAgentBracketPrefix(deltaInfo) + '⏳ ' + (typeof window.t === 'function'
? window.t('timeline.running')
: runningLabel) + ' ' + (typeof window.t === 'function' ? window.t('chat.callTool', { name: escapeHtmlLocal(toolNameDelta), index: deltaInfo.index || 0, total: deltaInfo.total || 0 }) : toolNameDelta);
const itemId = addTimelineItem(timeline, 'tool_result', {
title: title,
message: '',
data: {
toolName: toolNameDelta,
success: true,
isError: false,
result: deltaText,
toolCallId: toolCallId,
index: deltaInfo.index,
total: deltaInfo.total,
iteration: deltaInfo.iteration,
einoAgent: deltaInfo.einoAgent,
source: deltaInfo.source
},
expanded: false
});
state = { itemId, buffer: '' };
const mapping = toolCallStatusMap.get(toolCallId);
let callItemId = mapping && mapping.itemId ? mapping.itemId : null;
if (callItemId) {
const callItem = document.getElementById(callItemId);
if (callItem) {
ensureToolCallResultSlot(callItem);
const section = callItem.querySelector('.tool-result-section');
if (section) {
section.classList.remove('pending');
section.className = 'tool-result-section success';
}
}
}
state = { itemId: callItemId, buffer: '', onCallItem: !!callItemId };
toolResultStreamStateByKey.set(key, state);
}
state.buffer += deltaText;
const item = document.getElementById(state.itemId);
const item = state.itemId ? document.getElementById(state.itemId) : null;
if (item) {
const pre = item.querySelector('pre.tool-result');
if (pre) {
pre.classList.remove('tool-result-pending');
pre.textContent = state.buffer;
}
}
@@ -1645,34 +1636,23 @@ function handleStreamEvent(event, progressElement, progressId,
const resultToolCallId = resultInfo.toolCallId || null;
const resultExecText = success ? (typeof window.t === 'function' ? window.t('chat.toolExecComplete', { name: escapeHtml(resultToolName) }) : '工具 ' + escapeHtml(resultToolName) + ' 执行完成') : (typeof window.t === 'function' ? window.t('chat.toolExecFailed', { name: escapeHtml(resultToolName) }) : '工具 ' + escapeHtml(resultToolName) + ' 执行失败');
// 若此 tool 已经流式推送过增量,则复用占位条目并更新最终结果,避免重复添加一条
if (resultToolCallId) {
const key = toolResultStreamKey(progressId, resultToolCallId);
const state = toolResultStreamStateByKey.get(key);
if (state && state.itemId) {
const item = document.getElementById(state.itemId);
if (item) {
const pre = item.querySelector('pre.tool-result');
const resultVal = resultInfo.result || resultInfo.error || '';
if (pre) pre.textContent = typeof resultVal === 'string' ? resultVal : JSON.stringify(resultVal);
const section = item.querySelector('.tool-result-section');
if (section) {
section.className = 'tool-result-section ' + (success ? 'success' : 'error');
}
const titleEl = item.querySelector('.timeline-item-title');
if (titleEl) {
if (resultInfo.einoAgent != null && String(resultInfo.einoAgent).trim() !== '') {
item.dataset.einoAgent = String(resultInfo.einoAgent).trim();
}
titleEl.textContent = timelineAgentBracketPrefix(resultInfo) + statusIcon + ' ' + resultExecText;
}
const streamState = toolResultStreamStateByKey.get(key);
if (streamState && streamState.itemId) {
const streamCallItem = document.getElementById(streamState.itemId);
if (streamCallItem) {
mergeToolResultIntoCallItem(streamCallItem, resultInfo);
}
toolResultStreamStateByKey.delete(key);
// 同时更新 tool_call 的状态
if (resultToolCallId && toolCallStatusMap.has(resultToolCallId)) {
if (toolCallStatusMap.has(resultToolCallId)) {
updateToolCallStatus(resultToolCallId, success ? 'completed' : 'failed');
toolCallStatusMap.delete(resultToolCallId);
}
break;
}
if (attachToolResultToCall(resultToolCallId, resultInfo)) {
if (toolCallStatusMap.has(resultToolCallId)) {
updateToolCallStatus(resultToolCallId, success ? 'completed' : 'failed');
toolCallStatusMap.delete(resultToolCallId);
}
@@ -2508,6 +2488,236 @@ async function attachRunningTaskEventStream(conversationId) {
window.attachRunningTaskEventStream = attachRunningTaskEventStream;
window.taskReplayProgressId = taskReplayProgressId;
/** 从工具参数提取短摘要(URL/命令等),便于同名工具批量调用时区分 */
function parseToolCallArgsFromData(data) {
if (!data) return {};
let args = data.argumentsObj;
if (args == null && data.arguments != null && String(data.arguments).trim() !== '') {
try {
args = JSON.parse(String(data.arguments));
} catch (e) {
args = { _raw: String(data.arguments) };
}
}
if (args == null || typeof args !== 'object') {
return {};
}
return args;
}
function toolCallArgHint(args) {
if (!args || typeof args !== 'object') return '';
const method = args.method != null ? String(args.method).trim().toUpperCase() : '';
const url = args.url || args.URL || args.target || args.uri;
if (url != null && String(url).trim() !== '') {
let s = String(url).trim();
if (method) s = method + ' ' + s;
return s.length > 56 ? s.slice(0, 53) + '...' : s;
}
if (method) {
return method;
}
const cmd = args.command || args.cmd || args.script;
if (cmd != null && String(cmd).trim() !== '') {
const s = String(cmd).trim();
return s.length > 48 ? s.slice(0, 45) + '...' : s;
}
return '';
}
function formatToolCallTimelineTitle(toolName, index, total, argsHint) {
const name = toolName || (typeof window.t === 'function' ? window.t('chat.unknownTool') : '未知工具');
const idx = index || 0;
const tot = total || 0;
let base;
if (typeof window.t === 'function') {
base = window.t('chat.callTool', { name: name, index: idx, total: tot });
} else {
base = '调用工具: ' + name + (tot ? ' (' + idx + '/' + tot + ')' : '');
}
const hint = (argsHint && String(argsHint).trim()) ? String(argsHint).trim() : '';
return hint ? (base + ' · ' + hint) : base;
}
function buildToolResultSectionHtml(data, opts) {
opts = opts || {};
const _t = function (k, o) {
return typeof window.t === 'function' ? window.t(k, o) : k;
};
const execResultLabel = _t('timeline.executionResult');
const execIdLabel = _t('timeline.executionId');
const waitingLabel = _t('timeline.running');
if (opts.pending) {
return (
'<div class="tool-result-section pending">' +
'<strong data-i18n="timeline.executionResult">' + escapeHtml(execResultLabel) + '</strong>' +
'<pre class="tool-result tool-result-pending">' + escapeHtml(waitingLabel) + '</pre>' +
'</div>'
);
}
const isError = data.isError || data.success === false;
const noResultText = _t('timeline.noResult');
const result = data.result != null ? data.result : (data.error != null ? data.error : noResultText);
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
const rawText = opts.rawText != null ? String(opts.rawText) : resultStr;
return (
'<div class="tool-result-section ' + (isError ? 'error' : 'success') + '">' +
'<strong data-i18n="timeline.executionResult">' + escapeHtml(execResultLabel) + '</strong>' +
'<pre class="tool-result">' + escapeHtml(rawText) + '</pre>' +
(data.executionId ? '<div class="tool-execution-id"><span data-i18n="timeline.executionId">' +
escapeHtml(execIdLabel) + '</span> <code>' + escapeHtml(String(data.executionId)) + '</code></div>' : '') +
'</div>'
);
}
function ensureToolCallResultSlot(item) {
if (!item) return null;
let section = item.querySelector('.tool-result-section');
if (section) return section;
const content = item.querySelector('.timeline-item-content');
if (!content) return null;
const wrap = document.createElement('div');
wrap.className = 'tool-details tool-result-slot';
wrap.innerHTML = buildToolResultSectionHtml({}, { pending: true });
content.appendChild(wrap);
return wrap.querySelector('.tool-result-section');
}
function mergeToolResultIntoCallItem(item, data, options) {
if (!item || !data) return false;
options = options || {};
const isError = data.isError || data.success === false;
const noResultText = typeof window.t === 'function' ? window.t('timeline.noResult') : '无结果';
const result = data.result != null ? data.result : (data.error != null ? data.error : noResultText);
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
const text = options.rawText != null ? String(options.rawText) : resultStr;
let section = item.querySelector('.tool-result-section');
if (!section) {
ensureToolCallResultSlot(item);
section = item.querySelector('.tool-result-section');
}
if (!section) return false;
section.classList.remove('pending');
section.className = 'tool-result-section ' + (isError ? 'error' : 'success');
const pre = section.querySelector('pre.tool-result');
if (pre) {
pre.classList.remove('tool-result-pending');
pre.textContent = text;
}
if (data.executionId) {
let execIdEl = section.querySelector('.tool-execution-id');
if (!execIdEl) {
const execIdLabel = typeof window.t === 'function' ? window.t('timeline.executionId') : '执行ID:';
execIdEl = document.createElement('div');
execIdEl.className = 'tool-execution-id';
execIdEl.innerHTML = '<span data-i18n="timeline.executionId">' + escapeHtml(execIdLabel) +
'</span> <code></code>';
section.appendChild(execIdEl);
}
const code = execIdEl.querySelector('code');
if (code) code.textContent = String(data.executionId);
}
item.dataset.toolResultMerged = '1';
item.dataset.toolSuccess = data.success !== false ? '1' : '0';
item.classList.remove('tool-call-running');
item.classList.add(data.success !== false ? 'tool-call-completed' : 'tool-call-failed');
return true;
}
function findToolCallItemById(root, toolCallId) {
if (!root || !toolCallId) return null;
const id = String(toolCallId).trim();
if (!id) return null;
try {
return root.querySelector('[data-tool-call-id="' + CSS.escape(id) + '"]');
} catch (e) {
return root.querySelector('[data-tool-call-id="' + id.replace(/"/g, '\\"') + '"]');
}
}
function attachToolResultToCall(toolCallId, data, options) {
if (!toolCallId || !data) return false;
const mapping = toolCallStatusMap.get(toolCallId);
let item = null;
if (mapping && mapping.itemId) {
item = document.getElementById(mapping.itemId);
}
if (!item && mapping && mapping.timeline) {
item = findToolCallItemById(mapping.timeline, toolCallId);
}
if (!item) return false;
mergeToolResultIntoCallItem(item, data, options);
return true;
}
function coalesceProcessDetailsToolPairs(details) {
if (!Array.isArray(details) || details.length === 0) return details;
const callsById = new Map();
const fifoCalls = [];
const out = [];
function absorbResult(targetDetail, resultDetail) {
const rd = resultDetail.data || {};
targetDetail.data = targetDetail.data || {};
targetDetail.data._mergedResult = Object.assign({}, rd);
if (resultDetail.createdAt) {
targetDetail.data._mergedResultAt = resultDetail.createdAt;
}
}
for (let i = 0; i < details.length; i++) {
const detail = details[i];
const et = detail.eventType || '';
const data = detail.data || {};
const id = data.toolCallId != null ? String(data.toolCallId).trim() : '';
if (et === 'tool_call') {
const copy = {
eventType: detail.eventType,
message: detail.message,
createdAt: detail.createdAt,
data: Object.assign({}, data)
};
if (id) callsById.set(id, copy);
fifoCalls.push(copy);
out.push(copy);
} else if (et === 'tool_result') {
let target = null;
if (id && callsById.has(id)) {
target = callsById.get(id);
} else {
for (let j = 0; j < fifoCalls.length; j++) {
const c = fifoCalls[j];
if (c && c.data && !c.data._mergedResult) {
target = c;
break;
}
}
}
if (target) {
absorbResult(target, detail);
continue;
}
out.push(detail);
} else {
out.push(detail);
}
}
return out;
}
window.coalesceProcessDetailsToolPairs = coalesceProcessDetailsToolPairs;
window.attachToolResultToCall = attachToolResultToCall;
window.mergeToolResultIntoCallItem = mergeToolResultIntoCallItem;
window.formatToolCallTimelineTitle = formatToolCallTimelineTitle;
window.parseToolCallArgsFromData = parseToolCallArgsFromData;
window.toolCallArgHint = toolCallArgHint;
window.buildToolResultSectionHtml = buildToolResultSectionHtml;
// 更新工具调用状态
function updateToolCallStatus(toolCallId, status) {
const mapping = toolCallStatusMap.get(toolCallId);
@@ -2574,6 +2784,16 @@ function addTimelineItem(timeline, type, options) {
if (d.toolCallId != null && String(d.toolCallId).trim() !== '') {
item.dataset.toolCallId = String(d.toolCallId).trim();
}
const callArgs = parseToolCallArgsFromData(d);
const argHint = toolCallArgHint(callArgs);
if (argHint) {
item.dataset.toolArgHint = argHint;
}
const merged = options.mergedResult || d._mergedResult;
if (merged) {
item.dataset.toolResultMerged = '1';
item.dataset.toolSuccess = merged.success !== false ? '1' : '0';
}
}
if (type === 'hitl_interrupt' && options.data && options.data.interruptId != null && String(options.data.interruptId).trim() !== '') {
item.dataset.hitlInterruptId = String(options.data.interruptId).trim();
@@ -2635,18 +2855,20 @@ function addTimelineItem(timeline, type, options) {
content += `<div class="timeline-item-content">${formatMarkdown(streamBody)}</div>`;
} else if (type === 'tool_call' && options.data) {
const data = options.data;
let args = data.argumentsObj;
if (args == null && data.arguments != null && String(data.arguments).trim() !== '') {
try {
args = JSON.parse(String(data.arguments));
} catch (e) {
args = { _raw: String(data.arguments) };
}
}
if (args == null || typeof args !== 'object') {
args = {};
}
const args = parseToolCallArgsFromData(data);
const merged = options.mergedResult || data._mergedResult;
const paramsLabel = typeof window.t === 'function' ? window.t('timeline.params') : '参数:';
let resultBlock = '';
if (merged) {
resultBlock = '<div class="tool-details tool-result-slot">' + buildToolResultSectionHtml(merged) + '</div>';
if (merged.success !== false) {
item.classList.add('tool-call-completed');
} else {
item.classList.add('tool-call-failed');
}
} else if (!options.skipPendingResult) {
resultBlock = '<div class="tool-details tool-result-slot">' + buildToolResultSectionHtml({}, { pending: true }) + '</div>';
}
content += `
<div class="timeline-item-content">
<div class="tool-details">
@@ -2654,6 +2876,7 @@ function addTimelineItem(timeline, type, options) {
<strong data-i18n="timeline.params">${escapeHtml(paramsLabel)}</strong>
<pre class="tool-args">${escapeHtml(JSON.stringify(args, null, 2))}</pre>
</div>
${resultBlock}
</div>
</div>
`;
@@ -4071,7 +4294,11 @@ function refreshProgressAndTimelineI18n() {
const name = (item.dataset.toolName != null && item.dataset.toolName !== '') ? item.dataset.toolName : _t('chat.unknownTool');
const index = parseInt(item.dataset.toolIndex, 10) || 0;
const total = parseInt(item.dataset.toolTotal, 10) || 0;
titleSpan.textContent = ap + '\uD83D\uDD27 ' + _t('chat.callTool', { name: name, index: index, total: total });
const hint = item.dataset.toolArgHint || '';
const callTitle = typeof formatToolCallTimelineTitle === 'function'
? formatToolCallTimelineTitle(name, index, total, hint)
: _t('chat.callTool', { name: name, index: index, total: total });
titleSpan.textContent = ap + '\uD83D\uDD27 ' + callTitle;
} else if (type === 'tool_result' && (item.dataset.toolName !== undefined || item.dataset.toolSuccess !== undefined)) {
const name = (item.dataset.toolName != null && item.dataset.toolName !== '') ? item.dataset.toolName : _t('chat.unknownTool');
const success = item.dataset.toolSuccess === '1';
+62 -44
View File
@@ -1666,7 +1666,13 @@ function buildWebshellTimelineItemFromDetail(detail) {
var tn = data.toolName || ((typeof window.t === 'function') ? window.t('chat.unknownTool') : '未知工具');
var idx = data.index || 0;
var total = data.total || 0;
title = ap + '🔧 ' + ((typeof window.t === 'function') ? window.t('chat.callTool', { name: tn, index: idx, total: total }) : ('调用: ' + tn + (total ? ' (' + idx + '/' + total + ')' : '')));
var wsHint = typeof window.toolCallArgHint === 'function'
? window.toolCallArgHint(typeof window.parseToolCallArgsFromData === 'function' ? window.parseToolCallArgsFromData(data) : {})
: '';
var wsCallTitle = typeof window.formatToolCallTimelineTitle === 'function'
? window.formatToolCallTimelineTitle(tn, idx, total, wsHint)
: ((typeof window.t === 'function') ? window.t('chat.callTool', { name: tn, index: idx, total: total }) : ('调用: ' + tn + (total ? ' (' + idx + '/' + total + ')' : '')));
title = ap + '🔧 ' + wsCallTitle;
} else if (eventType === 'tool_result') {
var success = data.success !== false;
var tname = data.toolName || '工具';
@@ -1695,6 +1701,9 @@ function buildWebshellTimelineItemFromDetail(detail) {
html += '<div class="webshell-ai-timeline-msg"><div class="tool-arg-section"><strong>' + escapeHtml(paramsLabel) + '</strong><pre class="tool-args">' + escapeHtml(JSON.stringify(args, null, 2)) + '</pre></div></div>';
}
} catch (e) {}
}
if (eventType === 'tool_call' && data && data._mergedResult && typeof window.buildToolResultSectionHtml === 'function') {
html += '<div class="webshell-ai-timeline-msg tool-result-slot">' + window.buildToolResultSectionHtml(data._mergedResult) + '</div>';
} else if (eventType === 'tool_result' && data) {
var isError = data.isError || data.success === false;
var noResultText = (typeof window.t === 'function') ? window.t('timeline.noResult') : '无结果';
@@ -1712,6 +1721,9 @@ function buildWebshellTimelineItemFromDetail(detail) {
// 渲染「执行过程及调用工具」折叠块(默认折叠,刷新后加载历史时保留并可展开)
function renderWebshellProcessDetailsBlock(processDetails, defaultCollapsed) {
if (!processDetails || processDetails.length === 0) return null;
if (typeof window.coalesceProcessDetailsToolPairs === 'function') {
processDetails = window.coalesceProcessDetailsToolPairs(processDetails);
}
var expandLabel = (typeof window.t === 'function') ? window.t('chat.expandDetail') : '展开详情';
var collapseLabel = (typeof window.t === 'function') ? window.t('tasks.collapseDetail') : '收起详情';
var headerLabel = (typeof window.t === 'function') ? (window.t('chat.penetrationTestDetail') || '执行过程及调用工具') : '执行过程及调用工具';
@@ -2772,7 +2784,7 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
var html = '<span class="webshell-ai-timeline-title">' + escapeHtml(title || message || '') + '</span>';
// 工具调用入参
// 工具调用入参 + 结果同卡
if (type === 'tool_call' && data) {
try {
var args = data.argumentsObj;
@@ -2783,14 +2795,20 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
args = { _raw: String(data.arguments) };
}
}
if (args && typeof args === 'object') {
var paramsLabel = (typeof window.t === 'function') ? window.t('timeline.params') : '参数:';
html += '<div class="webshell-ai-timeline-msg"><div class="tool-arg-section"><strong>' +
escapeHtml(paramsLabel) +
'</strong><pre class="tool-args">' +
escapeHtml(JSON.stringify(args, null, 2)) +
'</pre></div></div>';
if (args == null || typeof args !== 'object') {
args = {};
}
var paramsLabel = (typeof window.t === 'function') ? window.t('timeline.params') : '参数:';
var pendingResult = (typeof window.buildToolResultSectionHtml === 'function')
? window.buildToolResultSectionHtml({}, { pending: true })
: '';
html += '<div class="webshell-ai-timeline-msg"><div class="tool-arg-section"><strong>' +
escapeHtml(paramsLabel) +
'</strong><pre class="tool-args">' +
escapeHtml(JSON.stringify(args, null, 2)) +
'</pre></div>' +
(pendingResult ? '<div class="tool-result-slot">' + pendingResult + '</div>' : '') +
'</div>';
} catch (e) {
// JSON 解析失败时忽略参数详情,避免打断主流程
}
@@ -2829,6 +2847,7 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
var einoSubReplyStreams = new Map();
var wsThinkingStreams = new Map(); // streamId → { el, buf }
var wsToolResultStreams = new Map(); // toolCallId → { el, buf }
var wsToolCallItems = new Map(); // toolCallId → DOM item(参数+结果同卡)
if (inputEl) inputEl.value = '';
@@ -3035,11 +3054,16 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
var tn = _ed.toolName || '未知工具';
var idx = _ed.index || 0;
var total = _ed.total || 0;
var callTitle = wsTOr('chat.callTool', '') || ('调用工具: ' + tn + (total ? ' (' + idx + '/' + total + ')' : ''));
if (typeof window.t === 'function') {
try { callTitle = window.t('chat.callTool', { name: tn, index: idx, total: total }); } catch (e) { /* */ }
var wsHintLive = typeof window.toolCallArgHint === 'function'
? window.toolCallArgHint(typeof window.parseToolCallArgsFromData === 'function' ? window.parseToolCallArgsFromData(_ed) : {})
: '';
var callTitle = typeof window.formatToolCallTimelineTitle === 'function'
? window.formatToolCallTimelineTitle(tn, idx, total, wsHintLive)
: (wsTOr('chat.callTool', '') || ('调用工具: ' + tn + (total ? ' (' + idx + '/' + total + ')' : '')));
var callItem = appendTimelineItem('tool_call', webshellAgentPx(_ed) + '🔧 ' + callTitle, _em || '', _ed);
if (_ed.toolCallId && callItem) {
wsToolCallItems.set(_ed.toolCallId, callItem);
}
appendTimelineItem('tool_call', webshellAgentPx(_ed) + '🔧 ' + callTitle, _em || '', _ed);
if (!streamingTarget) assistantDiv.textContent = '…';
// ─── Tool result delta (streaming output) ───
@@ -3049,22 +3073,18 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
if (trdDelta) {
var trdState = wsToolResultStreams.get(trdKey);
if (!trdState) {
var trdName = _ed.toolName || '工具';
var runLabel = wsTOr('timeline.running', '执行中...');
var trdItem = document.createElement('div');
trdItem.className = 'webshell-ai-timeline-item webshell-ai-timeline-tool_result';
trdItem.innerHTML = '<span class="webshell-ai-timeline-title">' +
escapeHtml(webshellAgentPx(_ed) + '⏳ ' + runLabel + ' ' + trdName) +
'</span><div class="webshell-ai-timeline-msg"><div class="tool-result-section success">' +
'<pre class="tool-result"></pre></div></div>';
timelineContainer.appendChild(trdItem);
timelineContainer.classList.add('has-items');
trdState = { el: trdItem, buf: '' };
var callEl = wsToolCallItems.get(trdKey);
trdState = { el: callEl || null, buf: '', onCall: !!callEl };
wsToolResultStreams.set(trdKey, trdState);
}
trdState.buf += trdDelta;
var trdPre = trdState.el.querySelector('pre.tool-result');
if (trdPre) trdPre.textContent = trdState.buf;
if (trdState.el) {
var trdPre = trdState.el.querySelector('pre.tool-result');
if (trdPre) {
trdPre.classList.remove('tool-result-pending');
trdPre.textContent = trdState.buf;
}
}
}
if (!streamingTarget) assistantDiv.textContent = '…';
@@ -3072,25 +3092,23 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
} else if (_et === 'tool_result' && _ed) {
var success = _ed.success !== false;
var tname = _ed.toolName || '工具';
var titleText = wsTOr(success ? 'chat.toolExecComplete' : 'chat.toolExecFailed', '') ||
(tname + (success ? ' 执行完成' : ' 执行失败'));
if (typeof window.t === 'function') {
try { titleText = window.t(success ? 'chat.toolExecComplete' : 'chat.toolExecFailed', { name: tname }); } catch (e) { /* */ }
var merged = false;
if (_ed.toolCallId) {
var streamSt = wsToolResultStreams.get(_ed.toolCallId);
var callElRes = wsToolCallItems.get(_ed.toolCallId) || (streamSt && streamSt.el);
if (callElRes && typeof window.mergeToolResultIntoCallItem === 'function') {
window.mergeToolResultIntoCallItem(callElRes, _ed);
merged = true;
wsToolResultStreams.delete(_ed.toolCallId);
wsToolCallItems.delete(_ed.toolCallId);
}
}
// 如果有流式占位条目,更新标题
var trdExist = _ed.toolCallId ? wsToolResultStreams.get(_ed.toolCallId) : null;
if (trdExist) {
var trdTitleEl = trdExist.el.querySelector('.webshell-ai-timeline-title');
if (trdTitleEl) trdTitleEl.textContent = webshellAgentPx(_ed) + (success ? '✅ ' : '❌ ') + titleText;
// 更新结果内容
var resultText = _ed.result ? String(_ed.result) : (_em || '');
var trdPreEl = trdExist.el.querySelector('pre.tool-result');
if (trdPreEl && resultText) trdPreEl.textContent = resultText;
// 更新 section class
var trdSection = trdExist.el.querySelector('.tool-result-section');
if (trdSection) { trdSection.className = 'tool-result-section ' + (success ? 'success' : 'error'); }
wsToolResultStreams.delete(_ed.toolCallId);
} else {
if (!merged) {
var titleText = wsTOr(success ? 'chat.toolExecComplete' : 'chat.toolExecFailed', '') ||
(tname + (success ? ' 执行完成' : ' 执行失败'));
if (typeof window.t === 'function') {
try { titleText = window.t(success ? 'chat.toolExecComplete' : 'chat.toolExecFailed', { name: tname }); } catch (e) { /* */ }
}
var title = webshellAgentPx(_ed) + (success ? '✅ ' : '❌ ') + titleText;
var sub = _em || (_ed.result ? String(_ed.result).slice(0, 300) : '');
appendTimelineItem('tool_result', title, sub, _ed);