Add files via upload

This commit is contained in:
公明
2026-04-28 01:02:30 +08:00
committed by GitHub
parent 3aeb8c3474
commit 1532426b4f
7 changed files with 116 additions and 54 deletions
-1
View File
@@ -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",
-1
View File
@@ -178,7 +178,6 @@
"taskCancelled": "任务已取消",
"unknownTool": "未知工具",
"einoAgentReplyTitle": "子代理回复",
"einoRecoveryTitle": "🔄 工具参数无效 · 第 {{n}}/{{max}} 轮(已追加提示)",
"einoStreamErrorTitle": "⚠️ Eino 流式中断({{agent}}",
"einoStreamErrorMessage": "流式读取异常,系统将按策略重试或结束。",
"iterationLimitReachedTitle": "⛔ 达到迭代上限",
-4
View File
@@ -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') {
-37
View File
@@ -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) {
</div>
</div>
`;
} else if (type === 'eino_recovery' && options.message) {
content += `
<div class="timeline-item-content timeline-eino-recovery">
${escapeHtml(options.message).replace(/\n/g, '<br>')}
</div>
`;
} 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) {
+95
View File
@@ -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 ? `<div class="detail-item"><strong>类型:</strong> ${escapeHtml(vuln.type)}</div>` : ''}
${vuln.target ? `<div class="detail-item"><strong>目标:</strong> ${escapeHtml(vuln.target)}</div>` : ''}
<div class="detail-item"><strong>会话ID:</strong> <code>${escapeHtml(vuln.conversation_id)}</code></div>
${vuln.task_id ? `<div class="detail-item"><strong>任务ID:</strong> <code>${escapeHtml(vuln.task_id)}</code></div>` : ''}
${vuln.task_queue_id ? `<div class="detail-item"><strong>任务队列ID:</strong> <code>${escapeHtml(vuln.task_queue_id)}</code></div>` : ''}
${vuln.conversation_tag ? `<div class="detail-item"><strong>对话标签:</strong> ${escapeHtml(vuln.conversation_tag)}</div>` : ''}
${vuln.task_tag ? `<div class="detail-item"><strong>任务标签:</strong> ${escapeHtml(vuln.task_tag)}</div>` : ''}
</div>
${vuln.proof ? `<div class="vulnerability-proof"><strong>证明:</strong><pre>${escapeHtml(vuln.proof)}</pre></div>` : ''}
${vuln.impact ? `<div class="vulnerability-impact"><strong>影响:</strong> ${escapeHtml(vuln.impact)}</div>` : ''}
@@ -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 {
-11
View File
@@ -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;
+21
View File
@@ -1097,6 +1097,18 @@
<span data-i18n="vulnerabilityPage.conversationId">会话ID</span>
<input type="text" id="vulnerability-conversation-filter" data-i18n="vulnerabilityPage.filterConversation" data-i18n-attr="placeholder" placeholder="筛选特定会话" />
</label>
<label>
<span>任务ID/队列ID</span>
<input type="text" id="vulnerability-task-filter" placeholder="筛选任务ID或队列ID" />
</label>
<label>
<span>对话标签</span>
<input type="text" id="vulnerability-conversation-tag-filter" placeholder="筛选对话标签" />
</label>
<label>
<span>任务标签</span>
<input type="text" id="vulnerability-task-tag-filter" placeholder="筛选任务标签" />
</label>
<label>
<span data-i18n="vulnerabilityPage.severity">严重程度</span>
<select id="vulnerability-severity-filter">
@@ -1120,6 +1132,7 @@
</label>
<button class="btn-secondary" onclick="filterVulnerabilities()" data-i18n="vulnerabilityPage.filter">筛选</button>
<button class="btn-secondary" onclick="clearVulnerabilityFilters()" data-i18n="vulnerabilityPage.clear">清除</button>
<button class="btn-primary" onclick="exportVulnerabilityReports()">导出报告</button>
</div>
</div>
@@ -2599,6 +2612,14 @@
<label for="vulnerability-conversation-id"><span data-i18n="vulnerabilityModal.conversationId">会话ID</span> <span style="color: red;">*</span></label>
<input type="text" id="vulnerability-conversation-id" data-i18n="vulnerabilityModal.conversationIdPlaceholder" data-i18n-attr="placeholder" placeholder="输入会话ID" required />
</div>
<div class="form-group">
<label for="vulnerability-conversation-tag">对话标签</label>
<input type="text" id="vulnerability-conversation-tag" placeholder="如:红队演练A、客户A周报" />
</div>
<div class="form-group">
<label for="vulnerability-task-tag">任务标签</label>
<input type="text" id="vulnerability-task-tag" placeholder="如:批量扫描Q2、专项复测" />
</div>
<div class="form-group">
<label for="vulnerability-title"><span data-i18n="vulnerabilityModal.title">标题</span> <span style="color: red;">*</span></label>
<input type="text" id="vulnerability-title" data-i18n="vulnerabilityModal.titlePlaceholder" data-i18n-attr="placeholder" placeholder="漏洞标题" required />