diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index 23d6b21c..c91db344 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -178,7 +178,6 @@ "taskCancelled": "Task cancelled", "unknownTool": "Unknown tool", "einoAgentReplyTitle": "Sub-agent reply", - "einoRecoveryTitle": "🔄 Invalid tool JSON · run {{n}}/{{max}} (hint appended)", "einoStreamErrorTitle": "⚠️ Eino stream interrupted ({{agent}})", "einoStreamErrorMessage": "Streaming read failed; the system will retry or terminate according to policy.", "iterationLimitReachedTitle": "⛔ Iteration limit reached", diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index be506859..2da9a2e8 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -178,7 +178,6 @@ "taskCancelled": "任务已取消", "unknownTool": "未知工具", "einoAgentReplyTitle": "子代理回复", - "einoRecoveryTitle": "🔄 工具参数无效 · 第 {{n}}/{{max}} 轮(已追加提示)", "einoStreamErrorTitle": "⚠️ Eino 流式中断({{agent}})", "einoStreamErrorMessage": "流式读取异常,系统将按策略重试或结束。", "iterationLimitReachedTitle": "⛔ 达到迭代上限", diff --git a/web/static/js/chat.js b/web/static/js/chat.js index 3797d91c..89408057 100644 --- a/web/static/js/chat.js +++ b/web/static/js/chat.js @@ -2226,10 +2226,6 @@ function renderProcessDetails(messageId, processDetails) { itemTitle = agPx + execLine; } else if (eventType === 'eino_agent_reply') { itemTitle = agPx + '💬 ' + (typeof window.t === 'function' ? window.t('chat.einoAgentReplyTitle') : '子代理回复'); - } else if (eventType === 'eino_recovery') { - const ri = data.runIndex != null ? data.runIndex : (data.einoRetry != null ? data.einoRetry + 1 : 1); - const mx = data.maxRuns != null ? data.maxRuns : 3; - itemTitle = (typeof window.t === 'function' ? window.t('chat.einoRecoveryTitle', { n: ri, max: mx }) : ('🔄 第 ' + ri + '/' + mx + ' 轮(已追加提示)')); } else if (eventType === 'knowledge_retrieval') { itemTitle = '📚 ' + (typeof window.t === 'function' ? window.t('chat.knowledgeRetrieval') : '知识检索'); } else if (eventType === 'error') { diff --git a/web/static/js/monitor.js b/web/static/js/monitor.js index 5d3e102b..61081bff 100644 --- a/web/static/js/monitor.js +++ b/web/static/js/monitor.js @@ -1133,24 +1133,6 @@ function handleStreamEvent(event, progressElement, progressId, }); break; - case 'eino_recovery': { - const d = event.data || {}; - const runIdx = d.runIndex != null ? d.runIndex : (d.einoRetry != null ? d.einoRetry + 1 : 1); - const maxRuns = d.maxRuns != null ? d.maxRuns : 3; - const title = typeof window.t === 'function' - ? window.t('chat.einoRecoveryTitle', { n: runIdx, max: maxRuns }) - : ('🔄 工具参数无效 · 第 ' + runIdx + '/' + maxRuns + ' 轮(已追加提示)'); - addTimelineItem(timeline, 'eino_recovery', { - title: title, - message: event.message || '', - data: event.data - }); - // If the backend triggers a recovery run, any "running" tool_call items in this progress - // should be closed to avoid being stuck forever. - finalizeOutstandingToolCallsForProgress(progressId, 'failed'); - break; - } - case 'eino_stream_error': { const d = event.data || {}; const agent = d.einoAgent ? String(d.einoAgent) : ''; @@ -2190,15 +2172,6 @@ function addTimelineItem(timeline, type, options) { if (type === 'progress' && options.message) { item.dataset.progressMessage = options.message; } - if (type === 'eino_recovery' && options.data) { - const d = options.data; - if (d.runIndex != null) { - item.dataset.recoveryRunIndex = String(d.runIndex); - } - if (d.maxRuns != null) { - item.dataset.recoveryMaxRuns = String(d.maxRuns); - } - } if (type === 'tool_calls_detected' && options.data && options.data.count != null) { item.dataset.toolCallsCount = String(options.data.count); } @@ -2309,12 +2282,6 @@ function addTimelineItem(timeline, type, options) { `; - } else if (type === 'eino_recovery' && options.message) { - content += ` -
- ${escapeHtml(options.message).replace(/\n/g, '
')} -
- `; } else if (type === 'cancelled') { const taskCancelledLabel = typeof window.t === 'function' ? window.t('chat.taskCancelled') : '任务已取消'; content += ` @@ -3197,10 +3164,6 @@ function refreshProgressAndTimelineI18n() { titleSpan.textContent = ap + icon + (success ? _t('chat.toolExecComplete', { name: name }) : _t('chat.toolExecFailed', { name: name })); } else if (type === 'eino_agent_reply') { titleSpan.textContent = ap + '\uD83D\uDCAC ' + _t('chat.einoAgentReplyTitle'); - } else if (type === 'eino_recovery' && item.dataset.recoveryRunIndex) { - const n = parseInt(item.dataset.recoveryRunIndex, 10) || 1; - const mx = parseInt(item.dataset.recoveryMaxRuns, 10) || 3; - titleSpan.textContent = _t('chat.einoRecoveryTitle', { n: n, max: mx }); } else if (type === 'cancelled') { titleSpan.textContent = '\u26D4 ' + _t('chat.taskCancelled'); } else if (type === 'progress' && item.dataset.progressMessage !== undefined) { diff --git a/web/static/js/vulnerability.js b/web/static/js/vulnerability.js index 59590192..1628c447 100644 --- a/web/static/js/vulnerability.js +++ b/web/static/js/vulnerability.js @@ -10,6 +10,9 @@ let currentVulnerabilityId = null; let vulnerabilityFilters = { id: '', conversation_id: '', + task_id: '', + conversation_tag: '', + task_tag: '', severity: '', status: '' }; @@ -106,6 +109,15 @@ async function loadVulnerabilities(page = null) { if (vulnerabilityFilters.conversation_id) { params.append('conversation_id', vulnerabilityFilters.conversation_id); } + if (vulnerabilityFilters.task_id) { + params.append('task_id', vulnerabilityFilters.task_id); + } + if (vulnerabilityFilters.conversation_tag) { + params.append('conversation_tag', vulnerabilityFilters.conversation_tag); + } + if (vulnerabilityFilters.task_tag) { + params.append('task_tag', vulnerabilityFilters.task_tag); + } if (vulnerabilityFilters.severity) { params.append('severity', vulnerabilityFilters.severity); } @@ -241,6 +253,10 @@ function renderVulnerabilities(vulnerabilities) { ${vuln.type ? `
类型: ${escapeHtml(vuln.type)}
` : ''} ${vuln.target ? `
目标: ${escapeHtml(vuln.target)}
` : ''}
会话ID: ${escapeHtml(vuln.conversation_id)}
+ ${vuln.task_id ? `
任务ID: ${escapeHtml(vuln.task_id)}
` : ''} + ${vuln.task_queue_id ? `
任务队列ID: ${escapeHtml(vuln.task_queue_id)}
` : ''} + ${vuln.conversation_tag ? `
对话标签: ${escapeHtml(vuln.conversation_tag)}
` : ''} + ${vuln.task_tag ? `
任务标签: ${escapeHtml(vuln.task_tag)}
` : ''} ${vuln.proof ? `
证明:
${escapeHtml(vuln.proof)}
` : ''} ${vuln.impact ? `
影响: ${escapeHtml(vuln.impact)}
` : ''} @@ -338,6 +354,8 @@ function showAddVulnerabilityModal() { // 清空表单 document.getElementById('vulnerability-conversation-id').value = ''; + document.getElementById('vulnerability-conversation-tag').value = ''; + document.getElementById('vulnerability-task-tag').value = ''; document.getElementById('vulnerability-title').value = ''; document.getElementById('vulnerability-description').value = ''; document.getElementById('vulnerability-severity').value = ''; @@ -363,6 +381,8 @@ async function editVulnerability(id) { // 填充表单 document.getElementById('vulnerability-conversation-id').value = vuln.conversation_id || ''; + document.getElementById('vulnerability-conversation-tag').value = vuln.conversation_tag || ''; + document.getElementById('vulnerability-task-tag').value = vuln.task_tag || ''; document.getElementById('vulnerability-title').value = vuln.title || ''; document.getElementById('vulnerability-description').value = vuln.description || ''; document.getElementById('vulnerability-severity').value = vuln.severity || ''; @@ -393,6 +413,8 @@ async function saveVulnerability() { const data = { conversation_id: conversationId, + conversation_tag: document.getElementById('vulnerability-conversation-tag').value.trim(), + task_tag: document.getElementById('vulnerability-task-tag').value.trim(), title: title, description: document.getElementById('vulnerability-description').value.trim(), severity: severity, @@ -472,6 +494,9 @@ function closeVulnerabilityModal() { function filterVulnerabilities() { vulnerabilityFilters.id = document.getElementById('vulnerability-id-filter').value.trim(); vulnerabilityFilters.conversation_id = document.getElementById('vulnerability-conversation-filter').value.trim(); + vulnerabilityFilters.task_id = document.getElementById('vulnerability-task-filter').value.trim(); + vulnerabilityFilters.conversation_tag = document.getElementById('vulnerability-conversation-tag-filter').value.trim(); + vulnerabilityFilters.task_tag = document.getElementById('vulnerability-task-tag-filter').value.trim(); vulnerabilityFilters.severity = document.getElementById('vulnerability-severity-filter').value; vulnerabilityFilters.status = document.getElementById('vulnerability-status-filter').value; @@ -486,12 +511,18 @@ function filterVulnerabilities() { function clearVulnerabilityFilters() { document.getElementById('vulnerability-id-filter').value = ''; document.getElementById('vulnerability-conversation-filter').value = ''; + document.getElementById('vulnerability-task-filter').value = ''; + document.getElementById('vulnerability-conversation-tag-filter').value = ''; + document.getElementById('vulnerability-task-tag-filter').value = ''; document.getElementById('vulnerability-severity-filter').value = ''; document.getElementById('vulnerability-status-filter').value = ''; vulnerabilityFilters = { id: '', conversation_id: '', + task_id: '', + conversation_tag: '', + task_tag: '', severity: '', status: '' }; @@ -565,6 +596,18 @@ function formatVulnerabilityAsMarkdown(vuln) { markdown += `- **目标**: ${vuln.target}\n`; } markdown += `- **会话ID**: \`${vuln.conversation_id}\`\n`; + if (vuln.task_id) { + markdown += `- **任务ID**: \`${vuln.task_id}\`\n`; + } + if (vuln.task_queue_id) { + markdown += `- **任务队列ID**: \`${vuln.task_queue_id}\`\n`; + } + if (vuln.conversation_tag) { + markdown += `- **对话标签**: ${vuln.conversation_tag}\n`; + } + if (vuln.task_tag) { + markdown += `- **任务标签**: ${vuln.task_tag}\n`; + } markdown += `- **创建时间**: ${createdDate}\n`; markdown += `- **更新时间**: ${updatedDate}\n\n`; @@ -587,6 +630,58 @@ function formatVulnerabilityAsMarkdown(vuln) { return markdown; } +function buildVulnerabilityFilterParams() { + const params = new URLSearchParams(); + const keys = ['id', 'conversation_id', 'task_id', 'conversation_tag', 'task_tag', 'severity', 'status']; + keys.forEach((k) => { + if (vulnerabilityFilters[k]) { + params.append(k, vulnerabilityFilters[k]); + } + }); + return params; +} + +function triggerTextDownload(fileName, content) { + const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); +} + +async function exportVulnerabilityReports() { + try { + const params = buildVulnerabilityFilterParams(); + params.set('mode', 'summary'); + params.set('group_by', 'conversation'); + const response = await apiFetch(`/api/vulnerabilities/export?${params.toString()}`); + if (!response.ok) { + const error = await response.json().catch(() => ({ error: '导出失败' })); + throw new Error(error.error || '导出失败'); + } + const data = await response.json(); + const files = Array.isArray(data.files) ? data.files : []; + if (!files.length) { + alert('当前筛选条件下无可导出漏洞'); + return; + } + files.forEach((file, idx) => { + setTimeout(() => triggerTextDownload(file.filename || `vulnerability-export-${idx + 1}.md`, file.content || ''), idx * 120); + }); + if (files.length > 1) { + alert(`已开始下载 ${files.length} 份报告`); + } + } catch (error) { + console.error('导出漏洞报告失败:', error); + alert('导出漏洞报告失败: ' + error.message); + } +} + + // 下载漏洞为Markdown格式 async function downloadVulnerabilityAsMarkdown(id, event) { try { diff --git a/web/static/js/webshell.js b/web/static/js/webshell.js index eef8dc13..3d3ba16b 100644 --- a/web/static/js/webshell.js +++ b/web/static/js/webshell.js @@ -2881,17 +2881,6 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) { } else if (_et === 'warning') { appendTimelineItem('warning', '⚠️ ' + (_em || ''), '', _ed); - // ─── Eino recovery ─── - } else if (_et === 'eino_recovery') { - var runIdx = _ed.runIndex != null ? _ed.runIndex : (_ed.einoRetry != null ? _ed.einoRetry + 1 : 1); - var maxRuns = _ed.maxRuns != null ? _ed.maxRuns : 3; - var recTitle = wsTOr('chat.einoRecoveryTitle', '') || - ('🔄 工具参数无效 · 第 ' + runIdx + '/' + maxRuns + ' 轮(已追加提示)'); - if (typeof window.t === 'function') { - try { recTitle = window.t('chat.einoRecoveryTitle', { n: runIdx, max: maxRuns }); } catch (e) { /* */ } - } - appendTimelineItem('eino_recovery', recTitle, _em, _ed); - // ─── Tool calls ─── } else if (_et === 'tool_calls_detected' && _ed) { var count = _ed.count || 0; diff --git a/web/templates/index.html b/web/templates/index.html index 66f96e7a..cacc3193 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -1097,6 +1097,18 @@ 会话ID + + +