Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ui/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Before making changes, determine which areas the task touches and read the corre
organization, and API type ownership.
- Views: `src/views/VIEW_README.md` is the source of truth for page responsibilities and
feature-local code organization.
- Workflow canvas: `src/workflow-canvas/README.md` is the source of truth for LogicFlow canvas
- Workflow canvas: `src/workflow-canvas/WORKFLOW_README.md` is the source of truth for LogicFlow canvas
responsibilities, configuration boundaries, node maintenance, and View integration.

Follow this maintenance flow:
Expand Down Expand Up @@ -76,7 +76,7 @@ ui/
│ │ ├── icons/ # Workflow node icons
│ │ ├── nodes/ # Workflow node definitions and components
│ │ ├── plugins/ # Canvas-local plugins
│ │ ├── README.md # Canvas boundaries and maintenance rules
│ │ ├── WORKFLOW_README.md # Canvas boundaries and maintenance rules
│ │ ├── types.ts # Workflow canvas protocol types
│ │ └── index.vue # MkWorkflow canvas entry
│ ├── router/ # Vue Router routes and router instance
Expand Down
2 changes: 1 addition & 1 deletion ui/src/router/admin/system/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const systemRoutes: RouteRecordRaw = {
path: 'home',
name: 'system-home',
component: () => import('@/views/system/SystemView.vue'),
meta: { title: '首页', activeIcon: 'icon_screen_outlined', icon: 'icon_screen_filled', order: 0 },
meta: { title: '首页', activeIcon: 'icon_screen_filled', icon: 'icon_screen_outlined', order: 0 },
},
{
path: 'identity',
Expand Down
8 changes: 7 additions & 1 deletion ui/src/styles/element-plus.scss
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,12 @@
/* tabs */
.el-tabs {
--el-tabs-header-height: 34px;
&.small {
--el-tabs-header-height: 28px;
.el-tabs__item {
font-size: 12px;
}
}
&__header {
margin: 0;
flex-shrink: 0;
Expand All @@ -891,7 +897,7 @@
flex: 1;
}
&__item {
padding: 2px 14px;
padding: 0 14px;
font-weight: 400;
align-items: baseline;
&.is-active {
Expand Down
2 changes: 1 addition & 1 deletion ui/src/views/VIEW_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
`src/utils`、`src/stores` 或 `src/api`。
- 所有使用 `src/workflow-canvas/` 渲染画布的路由页面统一放在 `views/workflow/`。这些页面负责
路由参数、页面头部、保存或发布等页面动作以及后续接口编排;LogicFlow 初始化、节点注册和
画布内部行为遵循 `src/workflow-canvas/README.md`,不移入 View。
画布内部行为遵循 `src/workflow-canvas/WORKFLOW_README.md`,不移入 View。
- 页面目录存在多个层级时,目录名应表达业务层级,例如
`system/identity/groups/UserGroupListView.vue`,不要创建无业务含义的分组目录。
- 页面脚本中的状态、计算属性和处理方法按照同一业务流程集中放置,并使用简短的业务备注划分
Expand Down
18 changes: 9 additions & 9 deletions ui/src/views/workflow/components/WorkflowComponentMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
import { ref } from 'vue'

import { ClickOutside as vClickOutside } from 'element-plus'
import NodeMenu from '@/workflow-canvas/component/NodeMenu.vue'
import type { WorkflowMenuNode } from '@/workflow-canvas/config/menu'
import NodeMenu from '@/workflow-canvas/node-menu/index.vue'
import type { NodeMenuItem } from '@/workflow-canvas/node-menu/types'

defineOptions({ name: 'WorkflowComponentMenu' })

const emit = defineEmits<{
dragstart: [workflowNode: WorkflowMenuNode, event: PointerEvent]
select: [workflowNode: WorkflowMenuNode]
dragstart: [node: NodeMenuItem, event: PointerEvent]
select: [node: NodeMenuItem]
}>()

const popoverVisible = ref(false)
Expand All @@ -20,13 +20,13 @@ const togglePopover = () => {
popoverVisible.value = !popoverVisible.value
}

const handleSelect = (workflowNode: WorkflowMenuNode) => {
emit('select', workflowNode)
const handleSelect = (node: NodeMenuItem) => {
emit('select', node)
popoverVisible.value = false
}

const handleDragStart = (workflowNode: WorkflowMenuNode, event: PointerEvent) => {
emit('dragstart', workflowNode, event)
const handleDragStart = (node: NodeMenuItem, event: PointerEvent) => {
emit('dragstart', node, event)
dragClosing.value = true
popoverVisible.value = false
}
Expand All @@ -39,7 +39,7 @@ const handleDragStart = (workflowNode: WorkflowMenuNode, event: PointerEvent) =>
<el-collapse-transition>
<NodeMenu
v-show="popoverVisible"
class="absolute right-0 top-[calc(100%+8px)] z-[2000]"
class="absolute -right-40 top-[calc(100%+8px)] z-[2000]"
:class="{ 'pointer-events-none': dragClosing }"
@dragstart="handleDragStart"
@select="handleSelect"
Expand Down
35 changes: 23 additions & 12 deletions ui/src/workflow-canvas/WORKFLOW_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,29 @@

```text
src/workflow-canvas/
├── component/ # 画布内可复用的控制、菜单和搜索组件
├── config/ # 节点数据、菜单、映射、常量及预留的本地化配置
├── component/ # 画布内可复用的控制和搜索组件
├── config/ # 节点数据、映射、常量及预留的本地化配置
├── core/ # 稳定的画布内核与所有节点共用的基础能力
│ ├── edge/ # 普通边、循环边及边删除按钮
│ └── node-container/ # 节点容器及其私有的条件、操作下拉组件
├── icons/ # 节点图标及图标解析工具
├── node-menu/ # 基础组件、工具和智能体节点菜单及其菜单配置
├── nodes/ # 已迁入节点的注册文件和 Vue 实现
├── plugins/ # 仅供画布使用的 LogicFlow 插件
├── index.vue # 画布入口组件 WorkflowCanvas
├── style.scss # 画布内 LogicFlow 全局样式覆盖
├── types.ts # 画布协议类型和枚举
└── README.md # 本文档
└── WORKFLOW_README.md # 本文档
```

### `core/`

`core` 是配置完成后应保持稳定的基础层。`NodeContainer.vue` 和 `NodeCascader.vue` 是节点的
`core` 是配置完成后应保持稳定的基础层。`node-container/index.vue` 和 `NodeCascader.vue` 是节点的
通用结构与交互能力,属于核心代码;连线、快捷键、公共校验、Teleport 和节点公共工具也放在
这里。只有某项行为是全部或大多数节点都必须遵守的画布规则时,才修改 `core`。新增普通节点
不应要求调整核心层。
这里。`node-container/` 内的条件和操作下拉组件是节点容器的私有实现,不作为画布可复用组件
单独引用;`edge/` 集中维护边的 LogicFlow 注册配置、模型、视图和删除按钮,普通边由画布入口
显式注册,未接入的边类型不得仅因目录调整而改变注册状态。只有某项行为是全部或大多数节点
都必须遵守的画布规则时,才修改 `core`。新增普通节点不应要求调整核心层。

节点内需要跟随画布缩放的浮层应保留 `teleported="false"`。如果浮层可能与后绘制的 SVG 锚点
重叠,使用 `core/utils.ts` 的 `createAnchorGuard()`,通过唯一 key 同步各浮层的
Expand All @@ -49,22 +54,27 @@ src/workflow-canvas/
`config` 是随业务持续维护的配置层,增加、删除或调整节点时通常会更新:

- `node-data.ts`:节点的静态定义和默认属性,只存放数据。
- `menu.ts`:不同画布模式下的菜单分组和可选节点。
- `node-mapping.ts`:节点类型到配置数据的映射、模式匹配和默认节点集合。
- `constants.ts`:仅供工作流画布使用的稳定常量。
- `locale.ts`:预留的节点文案本地化配置;国际化接入前不参与当前逻辑。

配置数据中的函数应提取到对应职责文件,不把行为逻辑混入 `node-data.ts`。当前尚未接入国际化
的文案直接使用中文,不从 `@/locales` 引入 `t`。

### `node-menu/`

`node-menu` 集中维护画布节点选择菜单。`menu.ts` 定义不同画布模式下的基础组件分组;
`BasicNodeMenu.vue` 负责基础组件搜索和列表;`ResourceNodeMenu.vue` 负责工具与智能体的文件夹、
搜索和资源列表;`index.vue` 只负责 Tabs 和事件汇总。不要另建一份基础组件节点集合。

### `nodes/`

每个已实现节点使用一个目录,目录中包含 LogicFlow 注册文件 `index.ts` 和节点视图
`index.vue`。节点注册由 `index.vue` 中的 `import.meta.glob('./nodes/**/index.ts')` 自动收集,
不要再维护一份逐项导入列表。

节点自身的表单、状态和专属校验留在节点目录;多个节点共享且属于画布基础协议的能力才上移到
`core`。节点应复用 `core/NodeContainer.vue`,需要选择上游节点字段时复用
`core`。节点应复用 `core/node-container/index.vue`,需要选择上游节点字段时复用
`core/NodeCascader.vue`。

## View 接入约定
Expand Down Expand Up @@ -98,18 +108,19 @@ src/workflow-canvas/
1. 在 `types.ts` 的 `WorkflowNodeType` 中声明稳定的 LogicFlow 节点类型值。
2. 在 `config/node-data.ts` 中添加节点静态配置。
3. 在 `config/node-mapping.ts` 中维护节点映射;需要作为初始节点时再加入默认节点集合。
4. 在 `config/menu.ts` 中加入适用画布模式的菜单分组。
4. 在 `node-menu/menu.ts` 中加入适用画布模式的菜单分组。
5. 在 `nodes/<node-type>/` 中实现注册文件和节点视图,并按需增加 `icons/` 图标。
6. 确认节点能被自动注册、从菜单添加、正确连线、校验并导出图数据。

节点协议值统一使用 `WorkflowNodeType` 等枚举或画布常量,不在判断和映射中重复书写
`ai-chat-node`、`tool-node` 等字符串字面量。
`ai-chat-node`、`tool-custom-node` 等字符串字面量。

## 当前迁移范围

- 已实现并注册:基本信息、开始、AI 对话、意图识别、文本转语音、判断器、指定回复。
- 已实现并注册:基本信息、开始、AI 对话、意图识别、文本转语音、判断器、指定回复、智能体、
自定义工具和工具库工具。
- `config/node-mapping.ts` 保留节点类型映射,以及基本信息和开始节点的默认数据集合。
- `NodeMenu` 根据当前工作流模式直接渲染 `config/menu.ts` 返回的菜单分组页面“添加组件”和节点锚点菜单均支持点击创建与拖拽到画布创建。
- `NodeMenu` 使用 Element Plus Tabs 组织基础组件、工具和智能体;基础组件直接渲染 `node-menu/menu.ts` 返回的菜单分组,工具和智能体复用 Workspace 文件夹树加载可用资源。页面“添加组件”和节点锚点菜单均支持点击创建与拖拽到画布创建。
- `ApplicationWorkflowView` 已接入详情加载、默认工作流、手动与自动保存、发布以及未保存退出确认。
- 调试、发布历史、模板中心、完整国际化、枚举补充和其他尚未迁入的节点组件后续再接入。

Expand Down
89 changes: 0 additions & 89 deletions ui/src/workflow-canvas/component/NodeMenu.vue

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<script setup lang="ts">
import type { BaseEdgeModel } from '@logicflow/core'
import { inject } from 'vue'

defineOptions({ name: 'DeleteEdgeButton' })

const getModel = inject('getModel') as () => BaseEdgeModel
const model = getModel()
const deleteEdge = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { BezierEdge, BezierEdgeModel, h } from '@logicflow/core'
import { connect, disconnect } from './teleport'
import { connect, disconnect } from '../teleport'
import DeleteEdgeButton from './DeleteEdgeButton.vue'

const DEFAULT_WIDTH = 24
const DEFAULT_HEIGHT = 24
class CustomEdge2 extends BezierEdge {
class AppEdgeView extends BezierEdge {
isMounted = false
root?: HTMLDivElement
/**
Expand Down Expand Up @@ -111,7 +111,7 @@ class CustomEdge2 extends BezierEdge {
}
}

class CustomEdgeModel2 extends BezierEdgeModel {
class AppEdgeModel extends BezierEdgeModel {
getArrowStyle() {
const arrowStyle = super.getArrowStyle()
arrowStyle.offset = 1
Expand Down Expand Up @@ -166,4 +166,4 @@ class CustomEdgeModel2 extends BezierEdgeModel {
}
}

export default { type: 'app-edge', view: CustomEdge2, model: CustomEdgeModel2 }
export default { type: 'app-edge', view: AppEdgeView, model: AppEdgeModel }
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BezierEdge, BezierEdgeModel } from '@logicflow/core'

class CustomEdgeModel2 extends BezierEdgeModel {
class LoopEdgeModel extends BezierEdgeModel {
getArrowStyle() {
const arrowStyle = super.getArrowStyle()
arrowStyle.offset = 0
Expand Down Expand Up @@ -55,4 +55,4 @@ class CustomEdgeModel2 extends BezierEdgeModel {
}
}

export default { type: 'loop-edge', view: BezierEdge, model: CustomEdgeModel2 }
export default { type: 'loop-edge', view: BezierEdge, model: LoopEdgeModel }
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const emit = defineEmits<{
'visible-change': [visible: boolean]
}>()

type NodeConditionProperties = {
interface NodeConditionProperties {
condition?: boolean | 'AND' | 'OR'
kind?: WorkflowKind
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface NodeNameForm {
title: string
}

type NodeOperateProperties = {
interface NodeOperateProperties {
disabled?: boolean
kind?: WorkflowKind
node_data?: { name?: string }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
import type { BaseNodeModel, Model } from '@logicflow/core'
import { computed, inject, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { cloneDeep, set } from 'lodash'
import { iconComponent } from '../icons/utils'
import { iconComponent } from '@/workflow-canvas/icons/utils'
import { copyText } from '@/utils/clipboard'
import { WorkflowNodeType, type ShapeItem, type WorkflowNodeField } from '@/workflow-canvas/types'
import type { WorkflowMenuNode } from '@/workflow-canvas/config/menu'
import NodeMenu from '@/workflow-canvas/component/NodeMenu.vue'
import NodeConditionDropdown from '@/workflow-canvas/component/NodeConditionDropdown.vue'
import NodeOperateDropdown from '@/workflow-canvas/component/NodeOperateDropdown.vue'
import NodeMenu from '@/workflow-canvas/node-menu/index.vue'
import type { NodeMenuItem } from '@/workflow-canvas/node-menu/types'
import NodeConditionDropdown from './NodeConditionDropdown.vue'
import NodeOperateDropdown from './NodeOperateDropdown.vue'
import { createAnchorGuard, handleNodeWheel } from '@/workflow-canvas/core/utils'

defineOptions({ name: 'NodeContainer' })

type NodeContainerProperties = {
config?: { fields?: WorkflowNodeField[]; output_title?: string }
disabled?: boolean
Expand Down Expand Up @@ -83,9 +85,9 @@ const resizeStepContainer = (nodeHeight: number) => {
}
}

function clickNodes(item: WorkflowMenuNode) {
function clickNodes(item: NodeMenuItem) {
const anchor = anchorData.value
if (!anchor) return
if (!anchor || !item.type) return

const width = Number(item.properties?.width ?? 214)
const height = Number(item.height ?? item.properties?.height ?? 0)
Expand All @@ -106,7 +108,7 @@ function clickNodes(item: WorkflowMenuNode) {
closeNodeMenu()
}

function dragNode(item: WorkflowMenuNode, event: PointerEvent) {
function dragNode(item: NodeMenuItem, event: PointerEvent) {
startDragNode?.(item, event)
nodeMenuDragClosing.value = true
closeNodeMenu()
Expand Down
Loading
Loading