mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-15 04:51:01 +02:00
Add files via upload
This commit is contained in:
@@ -3196,6 +3196,12 @@ header {
|
||||
border-color: rgba(220, 53, 69, 0.3);
|
||||
}
|
||||
|
||||
.status-chip.status-cancelled {
|
||||
background: rgba(108, 117, 125, 0.12);
|
||||
color: var(--text-secondary, #6c757d);
|
||||
border-color: rgba(108, 117, 125, 0.35);
|
||||
}
|
||||
|
||||
.status-chip.status-pending,
|
||||
.status-chip.status-unknown {
|
||||
background: rgba(255, 193, 7, 0.12);
|
||||
@@ -3203,6 +3209,18 @@ header {
|
||||
border-color: rgba(255, 193, 7, 0.3);
|
||||
}
|
||||
|
||||
.detail-abort-hint {
|
||||
font-size: 0.875rem;
|
||||
opacity: 0.88;
|
||||
margin: 0 0 10px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.detail-abort-section .btn-monitor-abort {
|
||||
border-color: rgba(253, 126, 20, 0.55);
|
||||
color: #fd7e14;
|
||||
}
|
||||
|
||||
.detail-code-card {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px dashed rgba(0, 0, 0, 0.06);
|
||||
@@ -5517,6 +5535,16 @@ header {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.monitor-status-chip.cancelled {
|
||||
background: rgba(108, 117, 125, 0.15);
|
||||
color: var(--text-muted, #6c757d);
|
||||
}
|
||||
|
||||
.monitor-execution-actions .btn-monitor-abort {
|
||||
border-color: rgba(253, 126, 20, 0.55);
|
||||
color: #fd7e14;
|
||||
}
|
||||
|
||||
.monitor-execution-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -394,6 +394,16 @@
|
||||
"tasks": {
|
||||
"title": "Task Management",
|
||||
"stopTask": "Stop task",
|
||||
"interruptModalTitle": "Interrupt current step",
|
||||
"interruptReasonLabel": "Interrupt note",
|
||||
"interruptModalHint": "Your note is saved as a user message and the agent continues in the same stream. Use \"Stop completely\" to end the task.",
|
||||
"interruptReasonPlaceholder": "e.g. Tool is too slow—skip and summarize…",
|
||||
"interruptReasonRequired": "Please enter a short note so the model can continue accordingly.",
|
||||
"interruptSubmitting": "Submitting...",
|
||||
"interruptConfirmContinue": "Interrupt & continue",
|
||||
"interruptHardStop": "Stop completely",
|
||||
"interruptModalClose": "Close",
|
||||
"userInterruptTimelineTitle": "User interrupt note (continuing)",
|
||||
"collapseDetail": "Collapse details",
|
||||
"newTask": "New task",
|
||||
"autoRefresh": "Auto refresh",
|
||||
@@ -1260,6 +1270,8 @@
|
||||
"statusCompleted": "Completed",
|
||||
"statusRunning": "Running",
|
||||
"statusFailed": "Failed",
|
||||
"statusCancelled": "Cancelled",
|
||||
"terminateExecution": "Stop",
|
||||
"loading": "Loading...",
|
||||
"noStatsData": "No statistical data",
|
||||
"noExecutions": "No execution records",
|
||||
@@ -1727,8 +1739,22 @@
|
||||
"statusRunning": "Running",
|
||||
"statusCompleted": "Completed",
|
||||
"statusFailed": "Failed",
|
||||
"statusCancelled": "Cancelled",
|
||||
"unknown": "Unknown",
|
||||
"getDetailFailed": "Failed to get details",
|
||||
"runningNoResponseYet": "No output yet; the tool may still be running. If it hangs, use \"Stop tool\" below to end this call only.",
|
||||
"abortTitle": "Execution control",
|
||||
"abortHint": "Stops only this tool call. The conversation / multi-step task continues (unlike stopping the whole task).",
|
||||
"abortBtn": "Stop tool",
|
||||
"abortConfirm": "Stop this tool call? The overall conversation or iterative task will not be cancelled.",
|
||||
"abortSuccess": "Cancellation requested; status will update when the tool returns.",
|
||||
"abortFailed": "Failed to stop tool",
|
||||
"abortNoteModalTitle": "Stop tool with a note",
|
||||
"abortNoteModalHint": "Optional: why you stopped or how the model should continue. The model sees any tool output first, then a labeled block (USER INTERRUPT NOTE — not raw tool output), then your text. Leave empty for a plain stop.",
|
||||
"abortNoteLabel": "Note (optional)",
|
||||
"abortNotePlaceholder": "e.g. Output is enough—skip waiting and continue…",
|
||||
"abortNoteSubmit": "Stop tool",
|
||||
"abortNoteClose": "Cancel",
|
||||
"execSuccessNoContent": "Execution succeeded with no displayable content.",
|
||||
"time": "Time",
|
||||
"executionId": "Execution ID",
|
||||
|
||||
@@ -383,6 +383,16 @@
|
||||
"tasks": {
|
||||
"title": "任务管理",
|
||||
"stopTask": "停止任务",
|
||||
"interruptModalTitle": "中断当前步骤",
|
||||
"interruptReasonLabel": "中断说明",
|
||||
"interruptModalHint": "填写说明后将作为一条用户消息写入对话,智能体在同一会话内继续迭代。若只需完全停止任务,请点「彻底停止」。",
|
||||
"interruptReasonPlaceholder": "例如:工具耗时过长,请先跳过并总结当前结果…",
|
||||
"interruptReasonRequired": "请填写中断说明,以便模型根据你的意图继续。",
|
||||
"interruptSubmitting": "提交中...",
|
||||
"interruptConfirmContinue": "中断并继续",
|
||||
"interruptHardStop": "彻底停止",
|
||||
"interruptModalClose": "关闭",
|
||||
"userInterruptTimelineTitle": "用户中断说明(继续迭代)",
|
||||
"collapseDetail": "收起详情",
|
||||
"newTask": "新建任务",
|
||||
"autoRefresh": "自动刷新",
|
||||
@@ -1249,6 +1259,8 @@
|
||||
"statusCompleted": "已完成",
|
||||
"statusRunning": "执行中",
|
||||
"statusFailed": "失败",
|
||||
"statusCancelled": "已终止",
|
||||
"terminateExecution": "终止",
|
||||
"loading": "加载中...",
|
||||
"noStatsData": "暂无统计数据",
|
||||
"noExecutions": "暂无执行记录",
|
||||
@@ -1716,8 +1728,22 @@
|
||||
"statusRunning": "执行中",
|
||||
"statusCompleted": "已完成",
|
||||
"statusFailed": "失败",
|
||||
"statusCancelled": "已终止",
|
||||
"unknown": "未知",
|
||||
"getDetailFailed": "获取详情失败",
|
||||
"runningNoResponseYet": "尚无返回,工具可能仍在执行。若长时间无响应,可使用下方「终止工具」结束本次调用。",
|
||||
"abortTitle": "运行控制",
|
||||
"abortHint": "仅中断当前这一次工具调用;对话与多步迭代任务会继续,不会等同于「停止任务」。",
|
||||
"abortBtn": "终止工具",
|
||||
"abortConfirm": "确定终止此次工具调用?整条对话或迭代任务不会因此停止。",
|
||||
"abortSuccess": "已发送终止请求,工具返回后状态将更新。",
|
||||
"abortFailed": "终止失败",
|
||||
"abortNoteModalTitle": "终止工具并补充说明",
|
||||
"abortNoteModalHint": "可选:说明为何终止或希望模型如何继续。提交后模型会先看到工具已输出内容(若有),再看到带「用户终止说明」标题的独立区块(中英标注,与命令行原文区分),最后是您的文字。留空则与原先仅终止一致。",
|
||||
"abortNoteLabel": "终止说明(可选)",
|
||||
"abortNotePlaceholder": "例如:输出已够判断,请停止等待并继续下一步…",
|
||||
"abortNoteSubmit": "提交终止",
|
||||
"abortNoteClose": "取消",
|
||||
"execSuccessNoContent": "执行成功,未返回可展示的文本内容。",
|
||||
"time": "时间",
|
||||
"executionId": "执行 ID",
|
||||
|
||||
@@ -306,12 +306,13 @@ async function bootstrapApp() {
|
||||
|
||||
// 通用工具函数
|
||||
function getStatusText(status) {
|
||||
const s = (status && String(status).toLowerCase()) || '';
|
||||
if (typeof window.t !== 'function') {
|
||||
const fallback = { pending: '等待中', running: '执行中', completed: '已完成', failed: '失败' };
|
||||
return fallback[status] || status;
|
||||
const fallback = { pending: '等待中', running: '执行中', completed: '已完成', failed: '失败', cancelled: '已终止' };
|
||||
return fallback[s] || status;
|
||||
}
|
||||
const keyMap = { pending: 'mcpDetailModal.statusPending', running: 'mcpDetailModal.statusRunning', completed: 'mcpDetailModal.statusCompleted', failed: 'mcpDetailModal.statusFailed' };
|
||||
const key = keyMap[status];
|
||||
const keyMap = { pending: 'mcpDetailModal.statusPending', running: 'mcpDetailModal.statusRunning', completed: 'mcpDetailModal.statusCompleted', failed: 'mcpDetailModal.statusFailed', cancelled: 'mcpDetailModal.statusCancelled' };
|
||||
const key = keyMap[s];
|
||||
return key ? window.t(key) : status;
|
||||
}
|
||||
|
||||
|
||||
+113
-1
@@ -2446,7 +2446,24 @@ async function showMCPDetail(executionId) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
responseElement.textContent = typeof window.t === 'function' ? window.t('chat.noResponseData') : '暂无响应数据';
|
||||
if (normalizedStatus === 'running') {
|
||||
responseElement.textContent = typeof window.t === 'function' ? window.t('mcpDetailModal.runningNoResponseYet') : '尚无返回,工具可能仍在执行。若长时间无响应,可在下方终止本次调用。';
|
||||
} else {
|
||||
responseElement.textContent = typeof window.t === 'function' ? window.t('chat.noResponseData') : '暂无响应数据';
|
||||
}
|
||||
}
|
||||
|
||||
const abortSection = document.getElementById('detail-abort-section');
|
||||
const abortBtn = document.getElementById('detail-abort-btn');
|
||||
if (abortSection && abortBtn) {
|
||||
if (normalizedStatus === 'running') {
|
||||
abortSection.style.display = 'block';
|
||||
abortBtn.dataset.execId = exec.id || '';
|
||||
abortBtn.textContent = typeof window.t === 'function' ? window.t('mcpDetailModal.abortBtn') : '终止工具';
|
||||
} else {
|
||||
abortSection.style.display = 'none';
|
||||
delete abortBtn.dataset.execId;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示模态框
|
||||
@@ -2464,6 +2481,101 @@ function closeMCPDetail() {
|
||||
document.getElementById('mcp-detail-modal').style.display = 'none';
|
||||
}
|
||||
|
||||
/** 从详情模态框触发:取消当前进行中的 MCP 工具调用 */
|
||||
async function abortMCPToolExecutionFromDetail() {
|
||||
const btn = document.getElementById('detail-abort-btn');
|
||||
const id = btn && btn.dataset.execId;
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
await cancelMCPToolExecution(id, { refreshDetail: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开 MCP 工具终止弹窗(说明会经服务端加上「用户终止说明」标题块后与工具输出合并给模型)
|
||||
* @param {string} executionId
|
||||
* @param {{ refreshDetail?: boolean }} [options]
|
||||
*/
|
||||
function openMcpToolAbortModal(executionId, options = {}) {
|
||||
window.__mcpToolAbortContext = { executionId: executionId, options: options || {} };
|
||||
const ta = document.getElementById('mcp-tool-abort-note');
|
||||
if (ta) {
|
||||
ta.value = '';
|
||||
}
|
||||
const m = document.getElementById('mcp-tool-abort-modal');
|
||||
if (m) {
|
||||
m.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
function closeMcpToolAbortModal() {
|
||||
window.__mcpToolAbortContext = null;
|
||||
const m = document.getElementById('mcp-tool-abort-modal');
|
||||
if (m) {
|
||||
m.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function submitMcpToolAbortModal() {
|
||||
const ctx = window.__mcpToolAbortContext;
|
||||
if (!ctx || !ctx.executionId) {
|
||||
closeMcpToolAbortModal();
|
||||
return;
|
||||
}
|
||||
const note = (document.getElementById('mcp-tool-abort-note') && document.getElementById('mcp-tool-abort-note').value || '').trim();
|
||||
const executionId = ctx.executionId;
|
||||
const options = ctx.options || {};
|
||||
closeMcpToolAbortModal();
|
||||
await cancelMCPToolExecutionSubmit(executionId, note, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交终止请求(body: { note })
|
||||
* @param {string} executionId
|
||||
* @param {string} userNote
|
||||
* @param {{ refreshDetail?: boolean }} [options]
|
||||
*/
|
||||
async function cancelMCPToolExecutionSubmit(executionId, userNote, options = {}) {
|
||||
if (!executionId) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await apiFetch(`/api/monitor/execution/${encodeURIComponent(executionId)}/cancel`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ note: userNote || '' }),
|
||||
});
|
||||
const body = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
throw new Error(body.error || body.message || res.statusText);
|
||||
}
|
||||
const okMsg = typeof window.t === 'function' ? window.t('mcpDetailModal.abortSuccess') : '已发送终止请求';
|
||||
alert(okMsg);
|
||||
if (options.refreshDetail && typeof showMCPDetail === 'function') {
|
||||
await showMCPDetail(executionId);
|
||||
}
|
||||
if (typeof refreshMonitorPanel === 'function') {
|
||||
const page = (typeof monitorState !== 'undefined' && monitorState.pagination && monitorState.pagination.page) ? monitorState.pagination.page : 1;
|
||||
await refreshMonitorPanel(page);
|
||||
}
|
||||
} catch (e) {
|
||||
const failMsg = typeof window.t === 'function' ? window.t('mcpDetailModal.abortFailed') : '终止失败';
|
||||
alert(failMsg + ': ' + (e && e.message ? e.message : String(e)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消单次 MCP 工具执行(监控页「终止」)。弹出说明框后提交;仅取消该次 tools/call,不停止整条对话/迭代任务。
|
||||
* @param {string} executionId
|
||||
* @param {{ refreshDetail?: boolean }} [options]
|
||||
*/
|
||||
async function cancelMCPToolExecution(executionId, options = {}) {
|
||||
if (!executionId) {
|
||||
return;
|
||||
}
|
||||
openMcpToolAbortModal(executionId, options);
|
||||
}
|
||||
|
||||
// 复制详情面板中的内容
|
||||
function copyDetailBlock(elementId, triggerBtn = null) {
|
||||
const target = document.getElementById(elementId);
|
||||
|
||||
+146
-24
@@ -1,4 +1,6 @@
|
||||
const progressTaskState = new Map();
|
||||
/** @type {{ progressId: string, conversationId: string } | null} */
|
||||
let userInterruptModalPending = null;
|
||||
let activeTaskInterval = null;
|
||||
const ACTIVE_TASK_REFRESH_INTERVAL = 10000; // 10秒检查一次
|
||||
const TASK_FINAL_STATUSES = new Set(['failed', 'timeout', 'cancelled', 'completed']);
|
||||
@@ -410,6 +412,128 @@ async function requestCancel(conversationId) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 用户填写说明后中断当前步骤,由后端写入对话并继续同一条流式迭代 */
|
||||
async function requestCancelWithContinue(conversationId, reason) {
|
||||
const response = await apiFetch('/api/agent-loop/cancel', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
conversationId,
|
||||
reason: reason || '',
|
||||
continueAfter: true,
|
||||
}),
|
||||
});
|
||||
const result = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || (typeof window.t === 'function' ? window.t('tasks.cancelFailed') : '取消失败'));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function openUserInterruptModal(progressId, conversationId) {
|
||||
userInterruptModalPending = { progressId, conversationId };
|
||||
const ta = document.getElementById('user-interrupt-reason');
|
||||
if (ta) {
|
||||
ta.value = '';
|
||||
}
|
||||
const m = document.getElementById('user-interrupt-modal');
|
||||
if (m) {
|
||||
m.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
function closeUserInterruptModal() {
|
||||
userInterruptModalPending = null;
|
||||
const m = document.getElementById('user-interrupt-modal');
|
||||
if (m) {
|
||||
m.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function submitUserInterruptContinue() {
|
||||
if (!userInterruptModalPending) {
|
||||
return;
|
||||
}
|
||||
const reason = (document.getElementById('user-interrupt-reason') && document.getElementById('user-interrupt-reason').value || '').trim();
|
||||
if (!reason) {
|
||||
alert(typeof window.t === 'function' ? window.t('tasks.interruptReasonRequired') : '请填写中断说明');
|
||||
return;
|
||||
}
|
||||
const { progressId, conversationId } = userInterruptModalPending;
|
||||
closeUserInterruptModal();
|
||||
const stopBtn = document.getElementById(`${progressId}-stop-btn`);
|
||||
try {
|
||||
if (stopBtn) {
|
||||
stopBtn.disabled = true;
|
||||
stopBtn.textContent = typeof window.t === 'function' ? window.t('tasks.interruptSubmitting') : '提交中...';
|
||||
}
|
||||
await requestCancelWithContinue(conversationId, reason);
|
||||
loadActiveTasks();
|
||||
} catch (error) {
|
||||
console.error('中断并继续失败:', error);
|
||||
alert((typeof window.t === 'function' ? window.t('tasks.cancelTaskFailed') : '操作失败') + ': ' + error.message);
|
||||
} finally {
|
||||
if (stopBtn) {
|
||||
stopBtn.disabled = false;
|
||||
stopBtn.textContent = typeof window.t === 'function' ? window.t('tasks.stopTask') : '停止任务';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function submitUserInterruptHardCancel() {
|
||||
if (!userInterruptModalPending) {
|
||||
return;
|
||||
}
|
||||
const { progressId } = userInterruptModalPending;
|
||||
closeUserInterruptModal();
|
||||
await performHardCancelProgressTask(progressId);
|
||||
}
|
||||
|
||||
/** 彻底停止任务(原「停止任务」行为) */
|
||||
async function performHardCancelProgressTask(progressId) {
|
||||
const state = progressTaskState.get(progressId);
|
||||
const stopBtn = document.getElementById(`${progressId}-stop-btn`);
|
||||
|
||||
if (!state || !state.conversationId) {
|
||||
if (stopBtn) {
|
||||
stopBtn.disabled = true;
|
||||
setTimeout(() => {
|
||||
stopBtn.disabled = false;
|
||||
}, 1500);
|
||||
}
|
||||
alert(typeof window.t === 'function' ? window.t('tasks.taskInfoNotSynced') : '任务信息尚未同步,请稍后再试。');
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.cancelling) {
|
||||
return;
|
||||
}
|
||||
|
||||
markProgressCancelling(progressId);
|
||||
if (stopBtn) {
|
||||
stopBtn.disabled = true;
|
||||
stopBtn.textContent = typeof window.t === 'function' ? window.t('tasks.cancelling') : '取消中...';
|
||||
}
|
||||
|
||||
try {
|
||||
await requestCancel(state.conversationId);
|
||||
loadActiveTasks();
|
||||
} catch (error) {
|
||||
console.error('取消任务失败:', error);
|
||||
alert((typeof window.t === 'function' ? window.t('tasks.cancelTaskFailed') : '取消任务失败') + ': ' + error.message);
|
||||
if (stopBtn) {
|
||||
stopBtn.disabled = false;
|
||||
stopBtn.textContent = typeof window.t === 'function' ? window.t('tasks.stopTask') : '停止任务';
|
||||
}
|
||||
const currentState = progressTaskState.get(progressId);
|
||||
if (currentState) {
|
||||
currentState.cancelling = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addProgressMessage() {
|
||||
const messagesDiv = document.getElementById('chat-messages');
|
||||
const messageDiv = document.createElement('div');
|
||||
@@ -737,7 +861,7 @@ function toggleProcessDetails(progressId, assistantMessageId) {
|
||||
}
|
||||
}
|
||||
|
||||
// 停止当前进度对应的任务
|
||||
// 停止当前进度:弹出「中断并说明 / 彻底停止」
|
||||
async function cancelProgressTask(progressId) {
|
||||
const state = progressTaskState.get(progressId);
|
||||
const stopBtn = document.getElementById(`${progressId}-stop-btn`);
|
||||
@@ -757,27 +881,7 @@ async function cancelProgressTask(progressId) {
|
||||
return;
|
||||
}
|
||||
|
||||
markProgressCancelling(progressId);
|
||||
if (stopBtn) {
|
||||
stopBtn.disabled = true;
|
||||
stopBtn.textContent = typeof window.t === 'function' ? window.t('tasks.cancelling') : '取消中...';
|
||||
}
|
||||
|
||||
try {
|
||||
await requestCancel(state.conversationId);
|
||||
loadActiveTasks();
|
||||
} catch (error) {
|
||||
console.error('取消任务失败:', error);
|
||||
alert((typeof window.t === 'function' ? window.t('tasks.cancelTaskFailed') : '取消任务失败') + ': ' + error.message);
|
||||
if (stopBtn) {
|
||||
stopBtn.disabled = false;
|
||||
stopBtn.textContent = typeof window.t === 'function' ? window.t('tasks.stopTask') : '停止任务';
|
||||
}
|
||||
const currentState = progressTaskState.get(progressId);
|
||||
if (currentState) {
|
||||
currentState.cancelling = false;
|
||||
}
|
||||
}
|
||||
openUserInterruptModal(progressId, state.conversationId);
|
||||
}
|
||||
|
||||
// 将进度消息转换为可折叠的详情组件
|
||||
@@ -1414,6 +1518,18 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
break;
|
||||
}
|
||||
|
||||
case 'user_interrupt_continue': {
|
||||
const d = event.data || {};
|
||||
const reason = (d.reason != null && String(d.reason).trim() !== '') ? String(d.reason).trim() : (event.message || '');
|
||||
const timelineTitle = typeof window.t === 'function' ? window.t('tasks.userInterruptTimelineTitle') : '用户中断说明(继续迭代)';
|
||||
addTimelineItem(timeline, 'user_interrupt', {
|
||||
title: '✋ ' + timelineTitle,
|
||||
message: reason,
|
||||
data: d,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'progress':
|
||||
const progressTitle = document.querySelector(`#${progressId} .progress-title`);
|
||||
if (progressTitle) {
|
||||
@@ -2777,7 +2893,8 @@ function renderMonitorExecutions(executions = [], statusFilter = 'all') {
|
||||
const viewDetailLabel = typeof window.t === 'function' ? window.t('mcpMonitor.viewDetail') : '查看详情';
|
||||
const deleteLabel = typeof window.t === 'function' ? window.t('mcpMonitor.delete') : '删除';
|
||||
const deleteExecTitle = typeof window.t === 'function' ? window.t('mcpMonitor.deleteExecTitle') : '删除此执行记录';
|
||||
const statusKeyMap = { pending: 'statusPending', running: 'statusRunning', completed: 'statusCompleted', failed: 'statusFailed' };
|
||||
const terminateLabel = typeof window.t === 'function' ? window.t('mcpMonitor.terminateExecution') : '终止';
|
||||
const statusKeyMap = { pending: 'statusPending', running: 'statusRunning', completed: 'statusCompleted', failed: 'statusFailed', cancelled: 'statusCancelled' };
|
||||
const locale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : undefined;
|
||||
const rows = executions
|
||||
.map(exec => {
|
||||
@@ -2788,7 +2905,11 @@ function renderMonitorExecutions(executions = [], statusFilter = 'all') {
|
||||
const startTime = exec.startTime ? (new Date(exec.startTime).toLocaleString ? new Date(exec.startTime).toLocaleString(locale || 'en-US') : String(exec.startTime)) : unknownLabel;
|
||||
const duration = formatExecutionDuration(exec.startTime, exec.endTime);
|
||||
const toolName = escapeHtml(exec.toolName || unknownToolLabel);
|
||||
const executionId = escapeHtml(exec.id || '');
|
||||
const rawExecId = exec.id || '';
|
||||
const executionId = escapeHtml(rawExecId);
|
||||
const terminateBtn = status === 'running'
|
||||
? `<button type="button" class="btn-secondary btn-monitor-abort" onclick="cancelMCPToolExecution('${rawExecId.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}')">${escapeHtml(terminateLabel)}</button>`
|
||||
: '';
|
||||
return `
|
||||
<tr>
|
||||
<td>
|
||||
@@ -2801,6 +2922,7 @@ function renderMonitorExecutions(executions = [], statusFilter = 'all') {
|
||||
<td>
|
||||
<div class="monitor-execution-actions">
|
||||
<button class="btn-secondary" onclick="showMCPDetail('${executionId}')">${escapeHtml(viewDetailLabel)}</button>
|
||||
${terminateBtn}
|
||||
<button class="btn-secondary btn-delete" onclick="deleteExecution('${executionId}')" title="${escapeHtml(deleteExecTitle)}">${escapeHtml(deleteLabel)}</button>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -1053,6 +1053,7 @@
|
||||
<option value="completed" data-i18n="mcpMonitor.statusCompleted">已完成</option>
|
||||
<option value="running" data-i18n="mcpMonitor.statusRunning">执行中</option>
|
||||
<option value="failed" data-i18n="mcpMonitor.statusFailed">失败</option>
|
||||
<option value="cancelled" data-i18n="mcpMonitor.statusCancelled">已终止</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
@@ -2449,6 +2450,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-section detail-abort-section" id="detail-abort-section" style="display: none;">
|
||||
<div class="detail-section-header">
|
||||
<h3 data-i18n="mcpDetailModal.abortTitle">运行控制</h3>
|
||||
</div>
|
||||
<p class="detail-abort-hint" data-i18n="mcpDetailModal.abortHint">仅中断当前工具调用;对话与多步任务会继续。</p>
|
||||
<button type="button" class="btn-secondary btn-monitor-abort" id="detail-abort-btn" onclick="abortMCPToolExecutionFromDetail()">终止工具</button>
|
||||
</div>
|
||||
<div class="detail-section">
|
||||
<div class="detail-section-header">
|
||||
<h3 data-i18n="mcpDetailModal.requestParams">请求参数</h3>
|
||||
@@ -2489,6 +2497,49 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户中断并说明(继续迭代) -->
|
||||
<div id="user-interrupt-modal" class="modal">
|
||||
<div class="modal-content" style="max-width: 520px;">
|
||||
<div class="modal-header">
|
||||
<h2 data-i18n="tasks.interruptModalTitle">中断当前步骤</h2>
|
||||
<span class="modal-close" onclick="closeUserInterruptModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="detail-abort-hint" data-i18n="tasks.interruptModalHint">填写说明后将写入对话并由智能体继续迭代。</p>
|
||||
<div class="form-group">
|
||||
<label for="user-interrupt-reason"><span data-i18n="tasks.interruptReasonLabel">中断说明</span></label>
|
||||
<textarea id="user-interrupt-reason" class="form-control" rows="4" data-i18n="tasks.interruptReasonPlaceholder" data-i18n-attr="placeholder" placeholder=""></textarea>
|
||||
</div>
|
||||
<div class="form-actions" style="display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end;">
|
||||
<button type="button" class="btn-secondary" onclick="closeUserInterruptModal()" data-i18n="tasks.interruptModalClose">关闭</button>
|
||||
<button type="button" class="btn-secondary btn-delete" onclick="submitUserInterruptHardCancel()" data-i18n="tasks.interruptHardStop">彻底停止</button>
|
||||
<button type="button" class="btn-primary" onclick="submitUserInterruptContinue()" data-i18n="tasks.interruptConfirmContinue">中断并继续</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MCP 工具终止:可填写给模型的说明 -->
|
||||
<div id="mcp-tool-abort-modal" class="modal">
|
||||
<div class="modal-content" style="max-width: 520px;">
|
||||
<div class="modal-header">
|
||||
<h2 data-i18n="mcpDetailModal.abortNoteModalTitle">终止工具并补充说明</h2>
|
||||
<span class="modal-close" onclick="closeMcpToolAbortModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="detail-abort-hint" data-i18n="mcpDetailModal.abortNoteModalHint">可选说明。</p>
|
||||
<div class="form-group">
|
||||
<label for="mcp-tool-abort-note"><span data-i18n="mcpDetailModal.abortNoteLabel">终止说明(可选)</span></label>
|
||||
<textarea id="mcp-tool-abort-note" class="form-control" rows="4" data-i18n="mcpDetailModal.abortNotePlaceholder" data-i18n-attr="placeholder" placeholder=""></textarea>
|
||||
</div>
|
||||
<div class="form-actions" style="display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end;">
|
||||
<button type="button" class="btn-secondary" onclick="closeMcpToolAbortModal()" data-i18n="mcpDetailModal.abortNoteClose">取消</button>
|
||||
<button type="button" class="btn-primary" onclick="submitMcpToolAbortModal()" data-i18n="mcpDetailModal.abortNoteSubmit">提交终止</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 外部MCP配置模态框 -->
|
||||
<div id="external-mcp-modal" class="modal">
|
||||
<div class="modal-content" style="max-width: 900px;">
|
||||
|
||||
Reference in New Issue
Block a user