diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index 0fc59747..888f196b 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -396,7 +396,7 @@ "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.", + "interruptModalHint": "Same as MCP monitor \"Stop tool\": ends only the in-flight tool call; the conversation and this run continue. Optional note is merged into the tool result (bilingual USER INTERRUPT NOTE, not raw CLI). Leave empty for a plain stop. If no tool is running yet (model still thinking), wait for a tool call or use \"Stop completely\".", "interruptReasonPlaceholder": "e.g. Tool is too slow—skip and summarize…", "interruptReasonRequired": "Please enter a short note so the model can continue accordingly.", "interruptSubmitting": "Submitting...", diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index 2edc969d..508416df 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -385,7 +385,7 @@ "stopTask": "停止任务", "interruptModalTitle": "中断当前步骤", "interruptReasonLabel": "中断说明", - "interruptModalHint": "填写说明后将作为一条用户消息写入对话,智能体在同一会话内继续迭代。若只需完全停止任务,请点「彻底停止」。", + "interruptModalHint": "与 MCP 监控页「终止工具」一致:仅结束当前这一次工具调用,整条对话与本轮推理会继续;工具返回中可附带说明(中英 USER INTERRUPT NOTE 块,与命令行原文区分)。留空则等同仅终止工具。若当前没有工具在执行(模型尚在思考),请等待工具开始或改用「彻底停止」。", "interruptReasonPlaceholder": "例如:工具耗时过长,请先跳过并总结当前结果…", "interruptReasonRequired": "请填写中断说明,以便模型根据你的意图继续。", "interruptSubmitting": "提交中...", diff --git a/web/static/js/monitor.js b/web/static/js/monitor.js index a05ce992..a0f4ff8c 100644 --- a/web/static/js/monitor.js +++ b/web/static/js/monitor.js @@ -356,6 +356,23 @@ function isChatMessagesPinnedToBottom() { return scrollHeight - clientHeight - scrollTop <= CHAT_SCROLL_PIN_THRESHOLD_PX; } +/** 顶栏「停止任务」与进度条按钮对齐时,用会话 ID 反查当前页的 progress 块 ID(无则弹窗内仍可按会话取消) */ +function findProgressIdByConversationId(conversationId) { + if (!conversationId) { + return null; + } + let fallback = null; + for (const [pid, st] of progressTaskState) { + if (st && st.conversationId === conversationId) { + fallback = pid; + if (document.getElementById(pid)) { + return pid; + } + } + } + return fallback; +} + function registerProgressTask(progressId, conversationId = null) { const state = progressTaskState.get(progressId) || {}; state.conversationId = conversationId !== undefined && conversationId !== null @@ -412,7 +429,7 @@ async function requestCancel(conversationId) { return result; } -/** 用户填写说明后中断当前步骤,由后端写入对话并继续同一条流式迭代 */ +/** 与 MCP 监控一致:仅终止当前进行中的工具调用,工具返回后本轮推理继续(可选 reason 合并进工具结果) */ async function requestCancelWithContinue(conversationId, reason) { const response = await apiFetch('/api/agent-loop/cancel', { method: 'POST', @@ -433,7 +450,10 @@ async function requestCancelWithContinue(conversationId, reason) { } function openUserInterruptModal(progressId, conversationId) { - userInterruptModalPending = { progressId, conversationId }; + userInterruptModalPending = { + progressId: progressId != null && progressId !== '' ? progressId : null, + conversationId, + }; const ta = document.getElementById('user-interrupt-reason'); if (ta) { ta.value = ''; @@ -457,13 +477,9 @@ async function submitUserInterruptContinue() { 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`); + const stopBtn = progressId ? document.getElementById(`${progressId}-stop-btn`) : null; try { if (stopBtn) { stopBtn.disabled = true; @@ -486,9 +502,22 @@ async function submitUserInterruptHardCancel() { if (!userInterruptModalPending) { return; } - const { progressId } = userInterruptModalPending; + const { progressId, conversationId } = userInterruptModalPending; closeUserInterruptModal(); - await performHardCancelProgressTask(progressId); + if (progressId) { + await performHardCancelProgressTask(progressId); + return; + } + if (!conversationId) { + return; + } + try { + await requestCancel(conversationId); + loadActiveTasks(); + } catch (error) { + console.error('取消任务失败:', error); + alert((typeof window.t === 'function' ? window.t('tasks.cancelTaskFailed') : '取消任务失败') + ': ' + error.message); + } } /** 彻底停止任务(原「停止任务」行为) */ @@ -1518,18 +1547,6 @@ 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) { @@ -2533,7 +2550,7 @@ function renderActiveTasks(tasks) { if (cancelBtn) { cancelBtn.onclick = (evt) => { evt.stopPropagation(); - cancelActiveTask(task.conversationId, cancelBtn); + cancelActiveTask(task.conversationId); }; if (task.status === 'cancelling') { cancelBtn.disabled = true; @@ -2546,21 +2563,12 @@ function renderActiveTasks(tasks) { }); } -async function cancelActiveTask(conversationId, button) { - if (!conversationId) return; - const originalText = button.textContent; - button.disabled = true; - button.textContent = typeof window.t === 'function' ? window.t('tasks.cancelling') : '取消中...'; - - try { - await requestCancel(conversationId); - loadActiveTasks(); - } catch (error) { - console.error('取消任务失败:', error); - alert((typeof window.t === 'function' ? window.t('tasks.cancelTaskFailed') : '取消任务失败') + ': ' + error.message); - button.disabled = false; - button.textContent = originalText; +function cancelActiveTask(conversationId) { + if (!conversationId) { + return; } + const progressId = findProgressIdByConversationId(conversationId); + openUserInterruptModal(progressId, conversationId); } let monitorPanelFetchSeq = 0;