mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-20 06:44:46 +02:00
Add files via upload
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user