From 9602875391cb17138d3c30909c46b43e9941a795 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Sat, 22 Aug 2026 01:20:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E6=9E=81=E7=AE=80=E6=91=98?= =?UTF-8?q?=E8=A6=81=E8=A1=8C=E8=A1=A5=E5=85=A8=E5=B7=A5=E5=85=B7=E6=96=87?= =?UTF-8?q?=E6=A1=88=E3=80=81=E5=B7=A5=E4=BD=9C=E6=B5=81=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E6=96=B0=E8=AE=BE=E8=AE=A1=E3=80=81=E5=8F=AA?= =?UTF-8?q?=E8=AF=BB=E6=A8=A1=E5=BC=8F=E6=94=BE=E8=A1=8C=E8=B0=83=E7=A0=94?= =?UTF-8?q?=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MinimalBlocks: 摘要行分类覆盖全部原生工具,新增 MCP(mcp__前缀)/工作流三分/待办两分等文案;counts 改由 CATEGORY_ORDER 生成,根治新增分类漏初始化导致摘要空白的 bug - workflow.svg 换新设计(圆+扁菱形+圆弧弯+圆角方块),侧边栏/快捷菜单窗口内联图标同步,侧边栏图标尺寸修正为 18px - TOOL_ICON_MAP 增加 7 个工作流工具映射,补 submit_plan/terminate_sub_agent - tools_execution: 只读模式放行 terminal_session/snapshot、子智能体全家、list_mcp_servers、list/get_workflow_status 及全部 mcp__ 前缀工具 - EditSummaryCard: 桌面端 diff 弹窗最大高度改为 min(可用空间, 70vh, 640px) - 工作流编辑器:移除阶段块「单出线/终点」文字与顶栏激活按钮;提醒/报错改为按钮下方固定高度浮层(内部滚动+点外部关闭),不再挤占页面布局 --- core/main_terminal_parts/tools_execution.py | 18 ++++ static/icons/workflow.svg | 18 ++-- .../src/components/chat/EditSummaryCard.vue | 11 ++- static/src/components/chat/MinimalBlocks.vue | 89 ++++++++++++++++--- .../chat/quickdock/WorkflowWindow.vue | 33 +++---- .../sidebar/ConversationSidebar.vue | 16 ++-- .../workflow/WorkflowEditorView.vue | 70 +++++++++------ .../components/workflow/WorkflowStageNode.vue | 5 -- .../components/sidebar/_conversation.scss | 4 +- static/src/utils/icons.ts | 13 ++- 10 files changed, 197 insertions(+), 80 deletions(-) diff --git a/core/main_terminal_parts/tools_execution.py b/core/main_terminal_parts/tools_execution.py index 6da9ed44..9e424b22 100644 --- a/core/main_terminal_parts/tools_execution.py +++ b/core/main_terminal_parts/tools_execution.py @@ -206,6 +206,20 @@ class MainTerminalToolsExecutionMixin: "todo_get", "sleep", "ask_user", + # 以下为只读/调研性质或会话状态管理类工具,只读模式(含计划模式)放行: + # 终端会话管理与快照(terminal_input 仍禁止,防止经持久终端执行写命令) + "terminal_session", + "terminal_snapshot", + # 子智能体全生命周期(只读权限会传播给子智能体,计划模式调研主力) + "create_sub_agent", + "close_sub_agent", + "terminate_sub_agent", + "get_sub_agent_status", + # MCP 服务列举(mcp__* 工具在 evaluate_tool_permission 中按前缀放行) + "list_mcp_servers", + # 工作流只读查询(激活/推进/停用/保存等状态写操作仍禁止) + "list_workflows", + "get_workflow_status", } _APPROVAL_REQUIRED_TOOLS = { "run_command", @@ -383,6 +397,10 @@ class MainTerminalToolsExecutionMixin: # 计划模式下允许提交计划求批准 if tool_name == "submit_plan" and self._is_plan_work_mode(): return {"allowed": True, "mode": mode} + # MCP 工具(mcp__<服务>__<工具>)在只读模式下放行: + # 工具名动态生成无法静态区分读写,经用户确认全部允许(副作用风险由提示词约束) + if tool_name.startswith("mcp__"): + return {"allowed": True, "mode": mode} if tool_name not in self._READONLY_ALLOWED_TOOLS: return { "allowed": False, diff --git a/static/icons/workflow.svg b/static/icons/workflow.svg index 17b5c162..811aeff1 100644 --- a/static/icons/workflow.svg +++ b/static/icons/workflow.svg @@ -5,12 +5,20 @@ viewBox="0 0 24 24" fill="none" stroke="currentColor" - stroke-width="2" + stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" > - - - - + + + + + + + + + + + + diff --git a/static/src/components/chat/EditSummaryCard.vue b/static/src/components/chat/EditSummaryCard.vue index 214a04b2..d3c74978 100644 --- a/static/src/components/chat/EditSummaryCard.vue +++ b/static/src/components/chat/EditSummaryCard.vue @@ -66,6 +66,10 @@ const activeFile = computed(() => { const PANEL_MAX_WIDTH = 720; const VIEWPORT_MARGIN = 16; const MIN_PANEL_HEIGHT = 140; +// 桌面端弹窗最大高度上限(2026-08-22 恢复):与 CSS 基础值 min(70vh, 640px) 对齐, +// 在动态可用空间之上再取一次 min,避免高视口下弹窗占满半屏以上 +const PANEL_MAX_HEIGHT_VH_RATIO = 0.7; +const PANEL_MAX_HEIGHT_PX = 640; // 条目上方至少要有这么多可用空间才放上面,否则优先考虑下方 const PREFER_ABOVE_MIN_HEIGHT = 260; @@ -89,7 +93,8 @@ function computePanelStyle() { // 垂直:优先放条目上方(底边 = 条目上边缘 - 半个条目高,向上生长); // 上方空间不足且下方更宽裕时改放下方(顶边 = 条目下边缘 + 半个条目高,向下生长)。 - // 两个方向的高度都吃满该方向的可用空间,无固定上限。 + // 高度上限 = min(该方向可用空间, 70vh, 640px),保底 MIN_PANEL_HEIGHT。 + const maxCapHeight = Math.min(vh * PANEL_MAX_HEIGHT_VH_RATIO, PANEL_MAX_HEIGHT_PX); const rowRect = anchorEl.value.getBoundingClientRect(); const anchorAbove = rowRect.top - rowRect.height / 2; const anchorBelow = rowRect.bottom + rowRect.height / 2; @@ -111,7 +116,7 @@ function computePanelStyle() { ...base, top: 'auto', bottom: `${Math.round(vh - bottomEdge)}px`, - maxHeight: `${Math.round(bottomEdge - VIEWPORT_MARGIN)}px` + maxHeight: `${Math.round(Math.min(bottomEdge - VIEWPORT_MARGIN, maxCapHeight))}px` }; } else { const topEdge = Math.min( @@ -122,7 +127,7 @@ function computePanelStyle() { ...base, top: `${Math.round(topEdge)}px`, bottom: 'auto', - maxHeight: `${Math.round(vh - VIEWPORT_MARGIN - topEdge)}px` + maxHeight: `${Math.round(Math.min(vh - VIEWPORT_MARGIN - topEdge, maxCapHeight))}px` }; } } diff --git a/static/src/components/chat/MinimalBlocks.vue b/static/src/components/chat/MinimalBlocks.vue index c0e7695d..f5dd29c3 100644 --- a/static/src/components/chat/MinimalBlocks.vue +++ b/static/src/components/chat/MinimalBlocks.vue @@ -598,10 +598,23 @@ type ToolCategory = | 'edit' | 'search' | 'webpage' + | 'mcp' + | 'workflow_activate' + | 'workflow_advance' + | 'workflow' + | 'todo_create' + | 'todo_update' | 'memory_update' | 'memory_read' | 'conversation' | 'sub_agent' + | 'sub_agent_manage' + | 'wait' + | 'ask' + | 'plan' + | 'skill' + | 'settings' + | 'easter_egg' | 'other'; const TOOL_CATEGORY_MAP: Record = { @@ -633,6 +646,22 @@ const TOOL_CATEGORY_MAP: Record = { extract_webpage: 'webpage', save_webpage: 'webpage', + // MCP(list_mcp_servers 归入此类;mcp__ 前缀由 getToolCategory 动态判定) + list_mcp_servers: 'mcp', + + // 工作流:激活 / 推进 / 其余操作 + activate_workflow: 'workflow_activate', + report_workflow_stage: 'workflow_advance', + choose_workflow_branch: 'workflow_advance', + get_workflow_status: 'workflow', + deactivate_workflow: 'workflow', + list_workflows: 'workflow', + save_workflow: 'workflow', + + // 待办:创建 / 更新 + todo_create: 'todo_create', + todo_update_task: 'todo_update', + // 更新记忆 update_memory: 'memory_update', update_project_memory: 'memory_update', @@ -645,8 +674,19 @@ const TOOL_CATEGORY_MAP: Record = { conversation_search: 'conversation', conversation_review: 'conversation', - // 子智能体 - create_sub_agent: 'sub_agent' + // 子智能体:创建 / 管理 + create_sub_agent: 'sub_agent', + close_sub_agent: 'sub_agent_manage', + terminate_sub_agent: 'sub_agent_manage', + get_sub_agent_status: 'sub_agent_manage', + + // 其他原生工具 + sleep: 'wait', + ask_user: 'ask', + submit_plan: 'plan', + create_skill: 'skill', + manage_personalization: 'settings', + trigger_easter_egg: 'easter_egg' }; const CATEGORY_LABELS: Record string> = { @@ -655,10 +695,23 @@ const CATEGORY_LABELS: Record string> = { edit: (n) => `编辑了 ${n} 次文件`, search: (n) => `搜索了 ${n} 次`, webpage: (n) => `查看了 ${n} 次网页`, + mcp: (n) => `执行了 ${n} 次 MCP 工具`, + workflow_activate: (n) => `激活了 ${n} 个工作流`, + workflow_advance: (n) => `推进了 ${n} 次工作流进度`, + workflow: (n) => `执行了 ${n} 次工作流操作`, + todo_create: (n) => `创建了 ${n} 次待办`, + todo_update: (n) => `更新了 ${n} 次待办`, memory_update: (n) => `更新了 ${n} 次记忆`, memory_read: (n) => `查看了 ${n} 次记忆`, conversation: (n) => `回顾了 ${n} 次对话`, sub_agent: (n) => `创建了 ${n} 个子智能体`, + sub_agent_manage: (n) => `管理了 ${n} 次子智能体`, + wait: (n) => `等待了 ${n} 次`, + ask: (n) => `询问了 ${n} 次`, + plan: (n) => `提交了 ${n} 次计划`, + skill: (n) => `归档了 ${n} 次技能`, + settings: (n) => `更新了 ${n} 次个性化设置`, + easter_egg: (n) => `触发了 ${n} 次彩蛋`, other: (n) => `执行了 ${n} 次其他操作` }; @@ -668,16 +721,31 @@ const CATEGORY_ORDER: ToolCategory[] = [ 'edit', 'search', 'webpage', + 'mcp', + 'workflow_activate', + 'workflow_advance', + 'workflow', + 'todo_create', + 'todo_update', 'memory_update', 'memory_read', 'conversation', 'sub_agent', + 'sub_agent_manage', + 'wait', + 'ask', + 'plan', + 'skill', + 'settings', + 'easter_egg', 'other' ]; const getToolCategory = (action: Action): ToolCategory => { const name = action.tool?.name; if (typeof name !== 'string') return 'other'; + // MCP 工具名动态生成(mcp__<服务>__<工具>),无法穷举,按前缀归类 + if (name.startsWith('mcp__')) return 'mcp'; return TOOL_CATEGORY_MAP[name] || 'other'; }; @@ -748,18 +816,11 @@ const getCompletedSummaryText = (actions: Action[]): string => { return getToolSummaryText(toolActions[0]); } - const counts: Record = { - read: 0, - command: 0, - edit: 0, - search: 0, - webpage: 0, - memory_update: 0, - memory_read: 0, - conversation: 0, - sub_agent: 0, - other: 0 - }; + // counts 必须由 CATEGORY_ORDER 生成:手写初始化清单曾在新增分类时漏同步, + // 导致新分类计数从 undefined 累加成 NaN、被 count > 0 过滤掉,摘要行整行空白 + const counts = Object.fromEntries( + CATEGORY_ORDER.map((category) => [category, 0]) + ) as Record; toolActions.forEach((action) => { counts[getToolCategory(action)]++; diff --git a/static/src/components/chat/quickdock/WorkflowWindow.vue b/static/src/components/chat/quickdock/WorkflowWindow.vue index 07da328d..bd32b481 100644 --- a/static/src/components/chat/quickdock/WorkflowWindow.vue +++ b/static/src/components/chat/quickdock/WorkflowWindow.vue @@ -5,22 +5,23 @@ :class="{ 'qd-window-enter': windowEntering, 'qd-window-exit': windowExiting }" >
- - - - - - + + + + + + + + + {{ snapshot.name }}
diff --git a/static/src/components/sidebar/ConversationSidebar.vue b/static/src/components/sidebar/ConversationSidebar.vue index e3d5b32d..b2936579 100644 --- a/static/src/components/sidebar/ConversationSidebar.vue +++ b/static/src/components/sidebar/ConversationSidebar.vue @@ -102,7 +102,8 @@ @click="$emit('open-workflows')" > 工作流 diff --git a/static/src/components/workflow/WorkflowEditorView.vue b/static/src/components/workflow/WorkflowEditorView.vue index 8bfcf805..c961fd6b 100644 --- a/static/src/components/workflow/WorkflowEditorView.vue +++ b/static/src/components/workflow/WorkflowEditorView.vue @@ -11,15 +11,25 @@ 未保存
- +
+ + +
+
+ + {{ issue.message }} +
+
+
-
-
-
- - {{ issue.message }} -
-
{{ flashMessage }}
@@ -487,6 +487,7 @@ const dirty = ref(false); const saving = ref(false); const addNodeMenuOpen = ref(false); const addNodeSelectRef = ref(null); +const issuesAnchorRef = ref(null); const confirmingStageDelete = ref(false); let flashTimer: ReturnType | null = null; @@ -750,14 +751,13 @@ async function onSave() { } } -function onActivate() { - flash('正式版将在对话 / 菜单中激活此工作流'); -} - function onDocumentClick(event: MouseEvent) { if (addNodeMenuOpen.value && addNodeSelectRef.value && !addNodeSelectRef.value.contains(event.target as HTMLElement)) { addNodeMenuOpen.value = false; } + if (issuesOpen.value && issuesAnchorRef.value && !issuesAnchorRef.value.contains(event.target as HTMLElement)) { + issuesOpen.value = false; + } } function iconSrc(url: string) { @@ -837,13 +837,27 @@ onBeforeUnmount(() => { flex-shrink: 0; } - &__issues { - max-height: 160px; + /* 提醒/报错弹层:锚固在按钮下方,不占页面布局,固定高度 + 内部滚动 */ + &__issues-anchor { + position: relative; + } + + &__issues-pop { + position: absolute; + top: calc(100% + 6px); + right: 0; + width: 320px; + max-width: calc(100vw - 32px); + height: 240px; overflow-y: auto; padding: 8px 12px; - border-bottom: 1px solid var(--border-default); - background: var(--surface-soft); - flex-shrink: 0; + border: 1px solid var(--border-default); + border-radius: 10px; + background: var(--surface-raised); + box-shadow: var(--shadow-mid); + z-index: 30; + scrollbar-width: thin; + scrollbar-color: color-mix(in srgb, var(--text-secondary) 55%, transparent) transparent; } &__flash { diff --git a/static/src/components/workflow/WorkflowStageNode.vue b/static/src/components/workflow/WorkflowStageNode.vue index c2207f06..838b4371 100644 --- a/static/src/components/workflow/WorkflowStageNode.vue +++ b/static/src/components/workflow/WorkflowStageNode.vue @@ -34,7 +34,6 @@
{{ data.node.id }} - {{ data.isTerminal ? '终点' : '单出线' }}
@@ -166,9 +165,5 @@ function iconSrc(url: string) { overflow: hidden; text-overflow: ellipsis; } - - &__routes { - flex-shrink: 0; - } } diff --git a/static/src/styles/components/sidebar/_conversation.scss b/static/src/styles/components/sidebar/_conversation.scss index 713415f2..ba054cd2 100644 --- a/static/src/styles/components/sidebar/_conversation.scss +++ b/static/src/styles/components/sidebar/_conversation.scss @@ -180,8 +180,8 @@ } .workflow-icon svg { - width: 20px; - height: 20px; + width: 18px; + height: 18px; } .monitor-mode-btn.blocked { diff --git a/static/src/utils/icons.ts b/static/src/utils/icons.ts index 7b7278cc..89c57182 100644 --- a/static/src/utils/icons.ts +++ b/static/src/utils/icons.ts @@ -99,7 +99,18 @@ export const TOOL_ICON_MAP = Object.freeze({ trigger_easter_egg: 'sparkles', list_mcp_servers: 'mcpLogo', view_image: 'camera', - view_video: 'eye' + view_video: 'eye', + // 工作流工具统一使用 workflow 专用图标 + activate_workflow: 'workflow', + report_workflow_stage: 'workflow', + choose_workflow_branch: 'workflow', + get_workflow_status: 'workflow', + deactivate_workflow: 'workflow', + list_workflows: 'workflow', + save_workflow: 'workflow', + // 补齐原先回落到默认齿轮的原生工具 + submit_plan: 'clipboard', + terminate_sub_agent: 'bot' }); export const TOOL_CATEGORY_ICON_MAP = Object.freeze({