diff --git a/ui/src/router/admin/index.ts b/ui/src/router/admin/index.ts index 3ce08ade5e6..a535a91ff26 100644 --- a/ui/src/router/admin/index.ts +++ b/ui/src/router/admin/index.ts @@ -14,6 +14,8 @@ const router = createRouter({ history: createWebHistory(ADMIN_BASE_PATH), routes: [ { path: '/', redirect: { name: 'workspace-home', params: { workspaceId: 'default' } } }, + // TODO(demo): 执行详情样式预览,验收后删除 + { path: '/details-demo', name: 'details-demo', component: () => import('@/views/details-demo/index.vue'), meta: { title: '执行详情 Demo' } }, ...loginRoutes, ...workflowRoutes, workspaceRoutes, @@ -24,7 +26,7 @@ const router = createRouter({ router.beforeEach(async (to, from) => { if (to.path !== from.path) NProgress.start() - const notAuthRouteNameList = ['login', 'forgot-password', 'not-found'] + const notAuthRouteNameList = ['login', 'forgot-password', 'not-found', 'details-demo'] const { auth, user } = useStore() if (!notAuthRouteNameList.includes(to.name ? to.name.toString() : '')) { diff --git a/ui/src/views/details-demo/index.vue b/ui/src/views/details-demo/index.vue new file mode 100644 index 00000000000..f80a80f192e --- /dev/null +++ b/ui/src/views/details-demo/index.vue @@ -0,0 +1,82 @@ + + + + + + + 执行详情样式 Demo + + application + knowledge + + + + + + diff --git a/ui/src/workflow-canvas/WORKFLOW_README.md b/ui/src/workflow-canvas/WORKFLOW_README.md index cd5dbe36dfa..d5c174f360c 100644 --- a/ui/src/workflow-canvas/WORKFLOW_README.md +++ b/ui/src/workflow-canvas/WORKFLOW_README.md @@ -22,6 +22,7 @@ src/workflow-canvas/ ├── component/ # 画布内可复用的控制和搜索组件 ├── config/ # 节点数据、映射、常量及预留的本地化配置 +├── details/ # 执行详情:顶层分发入口与公共卡壳,节点内容由 nodes/*/details/ 提供 ├── core/ # 稳定的画布内核与所有节点共用的基础能力 │ ├── edge/ # 普通边、循环边及边删除按钮 │ └── node-container/ # 节点容器、锚点按钮及私有的条件、操作下拉组件 @@ -225,6 +226,40 @@ MkFormList 的排序、增删均以 `cloneDeep` 回写;MkTable 保留普通行 带 ID 的新行使用 `default-item` 工厂函数,每次添加生成独立 ID。条件分支等保留锚点 ID、 固定末尾分支的特殊规则不能直接替换成普通列表移动。 +### 执行详情(`details/`) + +节点执行详情按三层组织,与画布节点注册同源,通过节点类型分发渲染: + +- `details/index.vue`(`ExecutionDetailContent`)是顶层分发层:接收 `detail` 数组和 `workflowMode`, + 按 `index` 升序,逐项按节点类型渲染对应节点的详情组件。类型到详情组件的映射来自 + `import.meta.glob('../nodes/*/index.ts')` 收集的默认导出中的 `details` 字段,与画布节点注册同一份 + 清单,不按目录名推断,也不再单独维护映射表。 +- `details/DetailContainer.vue` 是纯布局卡壳(架子),不认识任何具体节点类型:提供 `#header` 具名 + 插槽(透出折叠 `show`)、折叠体默认插槽承载节点内容,以及 `showContentOnError` 决定失败时是否仍 + 展示内容(默认失败只显示错误日志块)。 +- `details/BaseHeader.vue` 是公共头部:折叠箭头、节点图标、名称、耗时和状态图标;是否显示 tokens 由 + 节点通过 `show-tokens` 布尔控制,tokens 数值由 `BaseHeader` 从 `data` 自行计算,容器不参与该判断。 +- 详情载荷类型 `ExecutionNodeDetail` 在 `details/types.ts`,以开放索引签名承载各节点动态字段,仅显式 + 声明外层卡片通用字段;分发和模式统一使用 `WorkflowMode` 枚举,不自造字符串联合类型。 + +每个节点的详情入口统一为 `nodes//details/index.vue`,并在该节点 `index.ts` 的默认导出中通过 +`details` 字段注册(LogicFlow 注册忽略该额外字段): + +- 内容不随工作流模式变化的节点(如循环),`details/index.vue` 直接就是详情内容。 +- 内容随模式不同的节点(如 AI 对话),`details/index.vue` 按 `workflowMode` 用映射分发到同目录下的 + `application.vue`、`knowledge.vue` 等模式文件,其它模式兜底到应用视图;各模式文件各自完整、自包含, + 共用部分不强行抽取。 + +详情内的灰底标题块直接使用内联 Tailwind(`overflow-hidden rounded-md bg-N100` + `h5.px-3.py-2` + +`border-t border-dashed px-3 py-2 text-N900`),不引入独立的区块组件。只读 Markdown 回答复用全局 +`MdPreview`,并在节点内通过 `:deep()` 覆盖其固定高度与背景以融入灰底。 + +循环节点的详情是唯一的递归点:它维护循环设置与轮次选择,选中某轮后把该轮的子节点数组交回顶层 +`ExecutionDetailContent` 渲染(排序与类型分发由顶层负责),并对外层卡壳传 `show-content-on-error` +以便整体失败时仍能展开已执行的子节点。递归由数据驱动、逐层向下收敛(一个循环的轮次数据不含它 +自身),执行详情是有限树因而必然终止;`details/index.vue` 与节点详情之间的循环 import 仅在渲染期 +使用、不在模块求值期调用,属正常的组件递归引用。 + ## View 接入约定 - 所有画布路由页面均放在 `src/views/workflow/`,并以 `XxxWorkflowView.vue` 命名,例如 @@ -260,7 +295,9 @@ MkFormList 的排序、增删均以 `cloneDeep` 回写;MkTable 保留普通行 3. 在 `config/node-mapping.ts` 中维护节点映射;需要作为初始节点时再加入默认节点集合。 4. 在 `node-menu/menu.ts` 中加入适用画布模式的菜单分组。 5. 在 `nodes//` 中实现注册文件和节点视图,并按需增加 `icons/` 图标。 -6. 确认节点能被自动注册、从菜单添加、正确连线、校验并导出图数据。 +6. 如需执行详情,在 `nodes//details/index.vue` 中实现,并在 `index.ts` 默认导出中通过 + `details` 字段注册;内容随模式变化时在该目录下按 `workflowMode` 分发到模式文件。 +7. 确认节点能被自动注册、从菜单添加、正确连线、校验并导出图数据。 节点协议值统一使用 `WorkflowNodeType` 等枚举或画布常量,不在判断和映射中重复书写 `ai-chat-node`、`tool-custom-node` 等字符串字面量。 diff --git a/ui/src/workflow-canvas/details/BaseHeader.vue b/ui/src/workflow-canvas/details/BaseHeader.vue new file mode 100644 index 00000000000..276dc4aad22 --- /dev/null +++ b/ui/src/workflow-canvas/details/BaseHeader.vue @@ -0,0 +1,45 @@ + + + + + + + + + + {{ data.name }} + + + {{ tokenCount }} tokens + {{ data?.run_time?.toFixed(2) || '0.00' }} s + + + + + + + + + + + + diff --git a/ui/src/workflow-canvas/details/DetailContainer.vue b/ui/src/workflow-canvas/details/DetailContainer.vue new file mode 100644 index 00000000000..4e1045cc195 --- /dev/null +++ b/ui/src/workflow-canvas/details/DetailContainer.vue @@ -0,0 +1,38 @@ + + + + + + + + + + + + 错误日志 + {{ data.err_message || '-' }} + + + + + diff --git a/ui/src/workflow-canvas/details/index.vue b/ui/src/workflow-canvas/details/index.vue new file mode 100644 index 00000000000..47e12c53261 --- /dev/null +++ b/ui/src/workflow-canvas/details/index.vue @@ -0,0 +1,55 @@ + + + + + + + + + diff --git a/ui/src/workflow-canvas/details/types.ts b/ui/src/workflow-canvas/details/types.ts new file mode 100644 index 00000000000..36564124e11 --- /dev/null +++ b/ui/src/workflow-canvas/details/types.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { WorkflowNodeType } from '@/workflow-canvas/types' + +/** + * 节点执行详情的运行时载荷。字段随节点类型(含循环、工具工作流的嵌套子节点)差异极大, + * 由后端按节点动态返回,因此以开放索引签名承载动态字段,仅显式声明外层卡片通用的字段。 + * 各节点的 details.vue 直接读取本类型所需字段,待接口契约稳定后再逐节点收敛为精确类型。 + */ +export interface ExecutionNodeDetail { + [key: string]: any + answer_tokens?: number + err_message?: string + message_tokens?: number + name?: string + run_time?: number + status?: number + type?: WorkflowNodeType +} diff --git a/ui/src/workflow-canvas/nodes/ai-chat-node/details/application.vue b/ui/src/workflow-canvas/nodes/ai-chat-node/details/application.vue new file mode 100644 index 00000000000..4d273a6999f --- /dev/null +++ b/ui/src/workflow-canvas/nodes/ai-chat-node/details/application.vue @@ -0,0 +1,102 @@ + + + + + + + + + + + 系统提示词 + {{ data.system || '-' }} + + + + + 历史记录 + + + + {{ history.role }}: + {{ history.content }} + + + - + + + + + + 本次对话 + + + + + + {{ item.text }} + {{ item }} + + + + {{ data.question || '-' }} + + + + + + + 思考过程 + + {{ data.reasoning_content || '-' }} + + + + + + AI 回答 + + + - + + + + + + diff --git a/ui/src/workflow-canvas/nodes/ai-chat-node/details/index.vue b/ui/src/workflow-canvas/nodes/ai-chat-node/details/index.vue new file mode 100644 index 00000000000..04350d6baf5 --- /dev/null +++ b/ui/src/workflow-canvas/nodes/ai-chat-node/details/index.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/ui/src/workflow-canvas/nodes/ai-chat-node/details/knowledge.vue b/ui/src/workflow-canvas/nodes/ai-chat-node/details/knowledge.vue new file mode 100644 index 00000000000..599dc3026c3 --- /dev/null +++ b/ui/src/workflow-canvas/nodes/ai-chat-node/details/knowledge.vue @@ -0,0 +1,88 @@ + + + + + + + + + + + 系统提示词 + {{ data.system || '-' }} + + + + + 用户提示词 + + + + + + {{ item.text }} + {{ item }} + + + + {{ data.question || '-' }} + + + + + + + 思考过程 + + {{ data.reasoning_content || '-' }} + + + + + + AI 回答 + + + - + + + + + + diff --git a/ui/src/workflow-canvas/nodes/ai-chat-node/index.ts b/ui/src/workflow-canvas/nodes/ai-chat-node/index.ts index 3970bf68978..63ad7e2d14e 100644 --- a/ui/src/workflow-canvas/nodes/ai-chat-node/index.ts +++ b/ui/src/workflow-canvas/nodes/ai-chat-node/index.ts @@ -1,4 +1,5 @@ import AiChatNode from './index.vue' +import AiChatNodeDetail from './details/index.vue' import { WorkflowNodeModel, WorkflowNodeView } from '@/workflow-canvas/core/workflow-node' import { WorkflowNodeType } from '@/workflow-canvas/types.ts' @@ -8,4 +9,4 @@ class AiChatNodeView extends WorkflowNodeView { } } -export default { type: WorkflowNodeType.AiChat, model: WorkflowNodeModel, view: AiChatNodeView } +export default { type: WorkflowNodeType.AiChat, model: WorkflowNodeModel, view: AiChatNodeView, details: AiChatNodeDetail } diff --git a/ui/src/workflow-canvas/nodes/loop-node/details/index.vue b/ui/src/workflow-canvas/nodes/loop-node/details/index.vue new file mode 100644 index 00000000000..e982650b6ac --- /dev/null +++ b/ui/src/workflow-canvas/nodes/loop-node/details/index.vue @@ -0,0 +1,66 @@ + + + + + + + + + + + + 循环设置 + + 循环类型:{{ data.loop_type || '-' }} + + 循环数组: + {{ data.loop_type === 'NUMBER' ? data.number : loopKeys.join(', ') || '-' }} + + + + + + + 循环详情 + + + + + 第 {{ Number(key) + 1 }} 轮 + + + + + - + + + + diff --git a/ui/src/workflow-canvas/nodes/loop-node/index.ts b/ui/src/workflow-canvas/nodes/loop-node/index.ts index 5dcb664435b..8b5ee6f07fc 100644 --- a/ui/src/workflow-canvas/nodes/loop-node/index.ts +++ b/ui/src/workflow-canvas/nodes/loop-node/index.ts @@ -1,4 +1,5 @@ import LoopNodeVue from './index.vue' +import LoopNodeDetail from './details/index.vue' import { WorkflowNodeModel, WorkflowNodeView } from '@/workflow-canvas/core/workflow-node' import { WorkflowNodeType } from '@/workflow-canvas/types' import type { Model } from '@logicflow/core' @@ -25,4 +26,5 @@ export default { type: WorkflowNodeType.LoopNode, model: LoopNodeModel, view: LoopNodeView, + details: LoopNodeDetail, }
+ {{ history.role }}: + {{ history.content }} +
循环类型:{{ data.loop_type || '-' }}
+ 循环数组: + {{ data.loop_type === 'NUMBER' ? data.number : loopKeys.join(', ') || '-' }} +