Selaa lähdekoodia

refactor: 重构节点渲染配置等

jiaxing.liao 5 tuntia sitten
vanhempi
commit
0275408dd4

+ 1 - 0
apps/web/components.d.ts

@@ -49,6 +49,7 @@ declare module 'vue' {
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElOption: typeof import('element-plus/es')['ElOption']
     ElPagination: typeof import('element-plus/es')['ElPagination']
+    ElPopover: typeof import('element-plus/es')['ElPopover']
     ElRadio: typeof import('element-plus/es')['ElRadio']
     ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']

+ 2 - 1
apps/web/package.json

@@ -11,6 +11,7 @@
   "dependencies": {
     "@element-plus/icons-vue": "^2.3.2",
     "@repo/nodes": "workspace:^",
+    "@vitejs/plugin-vue-jsx": "^5.1.3",
     "echarts": "^6.0.0",
     "element-plus": "^2.13.1",
     "lodash-es": "^4.17.21",
@@ -24,10 +25,10 @@
     "vue-router": "4"
   },
   "devDependencies": {
+    "@repo/api-service": "workspace:*",
     "@repo/nodes": "workspace:*",
     "@repo/ui": "workspace:*",
     "@repo/workflow": "workspace:*",
-    "@repo/api-service": "workspace:*",
     "@types/lodash-es": "^4.17.12",
     "@vitejs/plugin-vue": "^6.0.1",
     "@vue/tsconfig": "^0.8.1",

+ 75 - 0
apps/web/src/features/nodeLibary/index.vue

@@ -0,0 +1,75 @@
+<script lang="ts" setup>
+import { computed, inject } from 'vue'
+import { materialTools, type SourceType } from '@repo/nodes'
+import { Icon } from '@repo/ui'
+
+defineOptions({
+	name: 'AddMaterialsPop'
+})
+
+const emit = defineEmits<{
+	'add-node': [value: SourceType]
+}>()
+
+const vueflow = inject('vueflow')
+// const hasStart = computed(() => vueflow?.nodes?.some((node) => node.data.type === 'start'))
+// const hasEnd = computed(() => vueflow?.nodes?.some((node) => node.data.type === 'end'))
+
+const materials = computed(() => {
+	return materialTools.map((group) => {
+		return {
+			...group,
+			source: group.source.filter((item) => {
+				return item
+				// !(item.type === 'start' && !hasStart.value) && !(item.type === 'end' && !hasEnd.value)
+			})
+		}
+	})
+})
+
+const onAddNode = (value: SourceType) => {
+	emit('add-node', value)
+}
+</script>
+<template>
+	<div v-for="item in materials" :key="item.id">
+		<p class="mb-2 mt-1 text-[#676f83]">{{ item.label }}</p>
+		<ul>
+			<li
+				class="tool mb-3 flex items-center"
+				v-for="value in item.source"
+				:key="value.id"
+				@click="onAddNode(value)"
+			>
+				<Icon
+					:icon="value.icon"
+					height="16"
+					width="16"
+					class="mr-2 bg-[#6172f3] p-1 rounded"
+					color="#fff"
+				/>
+				<span>{{ value.name }}</span>
+			</li>
+		</ul>
+	</div>
+</template>
+<style lang="less" scoped>
+ul {
+	padding: 0 0 0 10px;
+
+	.tool {
+		list-style: none;
+		cursor: pointer;
+
+		span {
+			color: #354052;
+		}
+
+		&:hover {
+			span {
+				color: #6172f3;
+			}
+		}
+	}
+}
+</style>

+ 30 - 0
apps/web/src/features/toolbar/index.vue

@@ -0,0 +1,30 @@
+<template>
+	<div class="absolute top-0 right-0 p-16px flex flex-col gap-12px">
+		<el-tooltip content="节点" placement="left">
+			<el-popover placement="left-start">
+				<NodeLibary @add-node="(val) => $emit('create:node', val)" />
+				<template #reference>
+					<IconButton type="primary" class="ml-12px" icon="lucide:package-plus" square />
+				</template>
+			</el-popover>
+		</el-tooltip>
+
+		<el-tooltip content="注释" placement="left">
+			<IconButton
+				icon="lucide:file-plus-corner"
+				square
+				@click="$emit('create:node', 'stickyNote')"
+			/>
+		</el-tooltip>
+		<el-tooltip content="运行" placement="left">
+			<IconButton icon="lucide:play" icon-color="#fff" type="success" square />
+		</el-tooltip>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { IconButton } from '@repo/ui'
+import NodeLibary from '@/features/nodeLibary/index.vue'
+</script>
+
+<style scoped></style>

+ 16 - 16
apps/web/src/views/Editor.vue

@@ -58,7 +58,9 @@
 					@update:nodes:position="handleUpdateNodesPosition"
 					@update:node:attrs="handleUpdateNodeProps"
 					class="bg-#f5f5f5"
-				/>
+				>
+					<Toolbar @create:node="handleNodeCreate" />
+				</Workflow>
 				<RunWorkflow v-model:visible="runVisible" />
 				<Setter
 					:id="nodeID"
@@ -86,6 +88,7 @@ import { agent } from '@repo/api-service'
 import Setter from '@/components/setter/index.vue'
 import RunWorkflow from '@/components/RunWorkflow/index.vue'
 import EditorFooter from '@/features/editorFooter/index.vue'
+import Toolbar from '@/features/toolbar/index.vue'
 
 import { IconButton, Input } from '@repo/ui'
 
@@ -98,13 +101,9 @@ layout?.setMainStyle({
 	padding: '0px'
 })
 
-agent
-	.postGetAgentInfo({
-		id: 'b3a4aabb-a6b8-47f3-8a32-f45930f7d7b8'
-	})
-	.then((res) => {
-		console.log('在线请求结果:', res.result.nodes)
-	})
+agent.postGetAgentInfo({
+	id: 'b3a4aabb-a6b8-47f3-8a32-f45930f7d7b8'
+})
 
 const footerHeight = ref(32)
 const route = useRoute()
@@ -124,7 +123,7 @@ const workflow = ref<IWorkflow>(
 				id,
 				name: 'workflow_1',
 				created: dayjs().format('MM 月 DD 日'),
-				nodes: [startNode, endNode],
+				nodes: [],
 				edges: []
 			}
 )
@@ -154,7 +153,7 @@ watch(
 				id: newId as string,
 				name: 'workflow_1',
 				created: dayjs().format('MM 月 DD 日'),
-				nodes: [startNode, endNode],
+				nodes: [],
 				edges: []
 			}
 		}
@@ -190,7 +189,8 @@ const handleNodeCreate = (value: SourceType | string) => {
 					version: ['1.0.0'],
 					inputs: [],
 					outputs: [],
-					renderType: 'stickyNote',
+					position: { x: 600, y: 300 },
+					nodeType: 'stickyNote',
 					content: '注释内容,可以使用 **Markdown** 语法进行格式化, 双击进入编辑。',
 					width: 400,
 					height: 200,
@@ -209,18 +209,18 @@ const handleNodeCreate = (value: SourceType | string) => {
 		code: codeNode,
 		database: databaseNode
 	}
-	const nodeToAdd = nodeMap[value.type]
+	const nodeToAdd = nodeMap[value.type]?.schema
 
 	// 如果存在对应节点则添加
 	if (nodeToAdd) {
 		workflow.value?.nodes.push({
 			...nodeToAdd,
+			type: 'canvas-node',
 			data: {
-				...nodeToAdd.data,
+				...nodeToAdd,
 				id
 			},
-			zIndex: 1,
-			id: uuid()
+			id
 		})
 	}
 	console.log(workflow.value?.nodes, 'workflow.nodes')
@@ -306,7 +306,7 @@ const hangleUpdateNodeData = (id: string, data: any) => {
 const handleUpdateNodeProps = (id: string, attrs: Record<string, unknown>) => {
 	const node = workflow.value?.nodes.find((node) => node.id === id)
 	if (node) {
-		if (node.data?.renderType === 'stickyNote') {
+		if (node.data?.nodeType === 'stickyNote') {
 			Object.assign(node.data, attrs)
 		} else {
 			Object.assign(node, attrs)

+ 2 - 0
apps/web/vite.config.ts

@@ -8,11 +8,13 @@ import Components from 'unplugin-vue-components/vite'
 import IconsResolver from 'unplugin-icons/resolver'
 import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
 import monacoEditorPlugin from 'vite-plugin-monaco-editor'
+import vueJsx from '@vitejs/plugin-vue-jsx'
 
 // https://vite.dev/config/
 export default defineConfig({
 	plugins: [
 		vue(),
+		vueJsx(),
 		UnoCss(),
 		createSvgIconsPlugin({
 			// 指定存放 SVG 的文件夹

+ 111 - 14
packages/nodes/Interface.ts

@@ -1,4 +1,84 @@
-export interface INodeData {
+import type { XYPosition } from '@repo/workflow'
+import type { FormItemRule } from 'element-plus'
+import type { VNode } from 'vue'
+
+/**
+ * 渲染回调参数
+ */
+export interface RenderCallbackParams {
+	[key: string]: any
+}
+
+/**
+ * 表单项配置模型
+ */
+export interface ConfigSchema {
+	// 其他未明确指定的属性
+	[fieldName: string]: any
+	/**
+	 * 子节点列表,可选
+	 */
+	children?: ConfigSchema[]
+	/**
+	 * 表单控件属性,可选
+	 * @example { type: 'input', options: [{ label: '用户名', value: 'username' }] }
+	 */
+	componentProps?: any
+	/**
+	 * 节点字段path,可选 获取参考lodash get方法
+	 * @example 'data.username'
+	 */
+	field?: string
+	// 是否为表单输入组件,可选
+	input?: boolean
+	// 节点标签,可选
+	label?: string
+	/**
+	 * 标签配置
+	 */
+	labelConfig?: {
+		/**
+		 * 宽度,可选
+		 */
+		width?: number
+		/**
+		 * 是否显示,可选
+		 */
+		show?: boolean
+		/**
+		 * 位置,可选
+		 */
+		position?: 'top' | 'left'
+		/**
+		 * 配置提示
+		 */
+		tooltip?: string
+	}
+	// 是否无需表单项,可选
+	noFormItem?: boolean
+	// 事件绑定
+	// 表单验证规则,可选
+	rules?: FormItemRule[]
+	// 是否显示(属性编辑组件可以添加函数动态显示隐藏),可选
+	show?: ((renderCallbackParams: RenderCallbackParams) => boolean) | boolean
+	// 插槽名称(组件为插槽类型时,需要设置插槽name),可选
+	slotName?: string
+	// 插槽列表,可选
+	slots?: { [slotName: string]: ConfigSchema[] }
+	/**
+	 * 节点类型,必选
+	 * @example 'text' | 'textarea' | 'select' | 'checkbox' | 'radio' | 'switch' | 'date' | 'time' | 'datetime' | 'color' | 'file' | 'image' | 'video' | 'audio' | 'range' | 'number' | 'password' | 'hidden' | 'button' | 'reset' | 'submit' | 'html' | 'custom'
+	 */
+	valueType: string
+	// 渲染表单项
+	render?: (renderCallbackParams: RenderCallbackParams) => Element
+	// 渲染组件
+	renderItem?: (renderCallbackParams: RenderCallbackParams) => Element
+}
+
+type EndpointType = string | { type: string; label: string }
+
+export interface INodeType {
 	/**
 	 * 版本信息
 	 */
@@ -32,27 +112,44 @@ export interface INodeData {
 	/**
 	 * 输入端口列表
 	 */
-	inputs: string[]
-	/**
-	 * 输入端口名称
-	 */
-	inputNames?: string[]
+	inputs: EndpointType[] | ((params: any) => EndpointType[])
 	/**
 	 * 输出端口列表
 	 */
-	outputs: string[]
+	outputs: EndpointType[] | ((params: any) => EndpointType[])
 	/**
 	 * 输出端口名称
 	 */
-	outputNames?: string[]
-}
-
-export interface INodeType {
+	schema: {
+		// 流程id
+		appAgentId?: string
+		// 父级节点Id
+		parentId?: string
+		// 节点位置
+		position: XYPosition
+		// 节点宽度
+		width: number
+		// 节点高度
+		height: number
+		// 当前是否选中
+		selected: boolean
+		// 节点类型
+		nodeType: string
+		// 节点层级
+		zIndex: number
+		// 节点业务数据 存储配置等
+		data: Record<string, any>
+	}
 	/**
-	 * 节点数据定义
+	 * 表单配置
 	 */
-	schema: INodeData
-
+	formConfig?: ConfigSchema[]
+	/**
+	 * 节点自定义渲染
+	 */
+	render?: (renderCallbackParams: RenderCallbackParams) => Element | VNode
+	// 其他配置
+	[key: string]: any
 }
 
 /**

+ 0 - 21
packages/nodes/materials/code.ts

@@ -1,21 +0,0 @@
-/*
- * @Author: liuJie
- * @Date: 2026-01-24 19:48:30
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 22:02:44
- * @Describe: code 节点
- */
-import type { IWorkflowNode} from '@repo/workflow'
-
-export const codeNode: IWorkflowNode = {
-    id: 'code-node',
-    type: 'code-node',
-    label: '代码节点',
-    position:{x: 904, y: 378},
-    data: {
-        id: 'code-node-1',
-        description: '代码节点',
-        inputs: [],
-        outputs: []
-    }
-}

+ 33 - 0
packages/nodes/materials/code.tsx

@@ -0,0 +1,33 @@
+import { NodeConnectionTypes, type INodeType } from '../Interface'
+
+export const codeNode: INodeType = {
+	version: ['1'],
+	displayName: '代码',
+	name: 'code',
+	description: '通过代码处理数据',
+	icon: 'lucide:code',
+	iconColor: '#f9c74f',
+	inputs: [NodeConnectionTypes.main],
+	outputs: [NodeConnectionTypes.main],
+	// 业务数据
+	schema: {
+		appAgentId: '',
+		parentId: '',
+		position: {
+			x: 20,
+			y: 30
+		},
+		width: 280,
+		height: 60,
+		selected: true,
+		nodeType: 'code',
+		zIndex: 1,
+		data: {
+			// 代码配置数据
+		}
+	},
+	// 节点渲染
+	render(data) {
+		return <div></div>
+	}
+}

+ 27 - 20
packages/nodes/materials/condition.ts

@@ -1,20 +1,27 @@
-/*
- * @Author: liuJie
- * @Date: 2026-01-24 19:45:51
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 22:01:53
- * @Describe: 条件节点
- */
-import type { IWorkflowNode} from '@repo/workflow'
-export const conditionNode: IWorkflowNode = {
-    id: 'condition-node',
-    type: 'condition-node',
-    label: '条件判断',
-    position: {x: 605, y: 417},
-    data: {
-        id: 'condition-node-1',
-        description: '条件判断节点',
-        inputs: [],
-        outputs: []
-    }
-}
+import { NodeConnectionTypes, type INodeType } from '../Interface'
+
+export const conditionNode: INodeType = {
+	version: ['1'],
+	displayName: '条件判断',
+	name: 'condition',
+	description: '根据条件判断',
+	icon: 'lucide:trending-up-down',
+	iconColor: '#b33be6',
+	inputs: [],
+	outputs: [NodeConnectionTypes.main],
+	// 业务数据
+	schema: {
+		appAgentId: '',
+		parentId: '',
+		position: {
+			x: 20,
+			y: 30
+		},
+		width: 280,
+		height: 60,
+		selected: true,
+		nodeType: 'condition',
+		zIndex: 1,
+		data: {}
+	}
+}

+ 27 - 20
packages/nodes/materials/database.ts

@@ -1,20 +1,27 @@
-/*
- * @Author: liuJie
- * @Date: 2026-01-24 19:51:27
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 22:03:17
- * @Describe: 数据查询节点
- */
-import type { IWorkflowNode} from '@repo/workflow'
-export const databaseNode: IWorkflowNode = {
-    id: 'database-node',
-    type: 'database-node',
-    label: '数据查询',
-    position: {x: 835, y: 518},
-    data: {
-        id: 'database-node-1',
-        description: '数据查询节点',
-        inputs: [],
-        outputs: []
-    }
-}
+import { NodeConnectionTypes, type INodeType } from '../Interface'
+
+export const databaseNode: INodeType = {
+	version: ['1'],
+	displayName: '数据查询',
+	name: 'database',
+	description: '通过数据库查询数据',
+	icon: 'lucide:database-zap',
+	iconColor: '#64dc34',
+	inputs: [NodeConnectionTypes.main],
+	outputs: [NodeConnectionTypes.main],
+	// 业务数据
+	schema: {
+		appAgentId: '',
+		parentId: '',
+		position: {
+			x: 20,
+			y: 30
+		},
+		width: 280,
+		height: 60,
+		selected: true,
+		nodeType: 'database',
+		zIndex: 1,
+		data: {}
+	}
+}

+ 24 - 19
packages/nodes/materials/end.ts

@@ -1,22 +1,27 @@
-/*
- * @Author: liuJie
- * @Date: 2026-01-24 19:44:57
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 22:00:26
- * @Describe: 结束节点
- */
-import type { IWorkflowNode } from '@repo/workflow'
+import { NodeConnectionTypes, type INodeType } from '../Interface'
 
-export const endNode: IWorkflowNode = {
-	id: 'end-node',
-	type: 'end-node',
-	label: '结束',
-	position: { x: 850, y: 272 },
-	data: {
-		id: 'end-node',
-		label: '结束节点',
-		description: '这是一个结束节点',
-		inputs: [],
-		outputs: []
+export const endNode: INodeType = {
+	version: ['1'],
+	displayName: '结束',
+	name: 'end',
+	description: '流程结束节点',
+	icon: 'lucide:unplug',
+	iconColor: '#c49600',
+	inputs: [NodeConnectionTypes.main],
+	outputs: [],
+	// 业务数据
+	schema: {
+		appAgentId: '',
+		parentId: '',
+		position: {
+			x: 20,
+			y: 30
+		},
+		width: 280,
+		height: 60,
+		selected: true,
+		nodeType: 'end',
+		zIndex: 1,
+		data: {}
 	}
 }

+ 157 - 44
packages/nodes/materials/http.ts

@@ -1,51 +1,164 @@
-/*
- * @Author: liuJie
- * @Date: 2026-01-24 19:45:07
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 22:00:48
- * @Describe: HTTP请求节点
- */
-import type { IWorkflowNode } from '@repo/workflow'
+import { NodeConnectionTypes, type INodeType } from '../Interface'
 
-export const httpNode: IWorkflowNode = {
-	id: 'http-node',
-	type: 'http-node',
-	label: 'HTTP',
-	position: { x: 468, y: 370 },
-	data: {
-		id: 'http-node-1',
-		label: 'HTTP',
-		description: 'http请求节点',
-		inputs: [],
-		outputs: [],
-		method: 'GET',
-		url: '',
-		headers: [],
-		params: [],
-		bodyType: 'json',
-		body: '',
-		verifySSL: true,
-		timeoutConfig: {
-			connect: 8,
-			read: 6,
-			write: 1
+type HttpData = {
+	outputs: [
+		{
+			name: 'body'
+			describe: '响应内容'
+			type: 'string'
 		},
-		output: {
-			body: '',
-			status_code: 200,
-			headers: [],
-			files: []
+		{
+			name: 'status_code'
+			describe: '响应状态码'
+			type: 'number'
 		},
-		errorConfig: {
-			retry: true,
-			max_retry: 3,
-			retry_delay: 100
+		{
+			name: 'headers'
+			describe: '响应头列表 JSON'
+			type: 'object'
+		}
+	]
+	output_can_alter: false
+	variables: []
+	method: 'post'
+	ssl_verify: false
+	isInIteration: false
+	default_value: []
+	body: {
+		type: 'json'
+		data: [
+			{
+				key: ''
+				type: 'text'
+				value: '{"id":"b3a4aabb-a6b8-47f3-8a32-f45930f7d7b8"}'
+			}
+		]
+	}
+	params: []
+	title: 'HTTP 请求'
+	type: 'http-request'
+	error_strategy: 'none'
+	retry_config: {
+		max_retries: 3
+		retry_enabled: false
+		retry_interval: 100
+	}
+	url: '#{env.api_address}/api/agent/getAgentInfo'
+	authorization: {
+		type: 'none'
+		config: {
+			api_key: ''
+			header: ''
+			type: ''
+		}
+	}
+	timeout_config: {
+		max_write_timeout: 0
+		max_read_timeout: 0
+		max_connect_timeout: 0
+	}
+	heads: [
+		{
+			name: 'Authorization'
+			value: ''
+		}
+	]
+	selected: true
+	desc: ''
+	isInLoop: false
+}
+
+export const httpNode: INodeType = {
+	version: ['1'],
+	displayName: 'HTTP 请求',
+	name: 'http-request',
+	description: '通过HTTP请求获取数据',
+	icon: 'lucide:link',
+	iconColor: '#9373ee',
+	inputs: [NodeConnectionTypes.main],
+	outputs: (data: HttpData) => {
+		// todo: 判断异常处理,如果是分支,添加异常出口
+		return [NodeConnectionTypes.main]
+	},
+	// 业务数据
+	schema: {
+		appAgentId: '',
+		parentId: '',
+		position: {
+			x: 20,
+			y: 30
 		},
-		exception: 'none',
-		exceptionDefaultValue: {
-			body: '',
-			status_code: 0,
-			headers: '{}'
+		width: 280,
+		height: 60,
+		selected: true,
+		nodeType: 'http-request',
+		zIndex: 1,
+		data: {
+			outputs: [
+				{
+					name: 'body',
+					describe: '响应内容',
+					type: 'string'
+				},
+				{
+					name: 'status_code',
+					describe: '响应状态码',
+					type: 'number'
+				},
+				{
+					name: 'headers',
+					describe: '响应头列表 JSON',
+					type: 'object'
+				}
+			],
+			output_can_alter: false,
+			variables: [],
+			method: 'post',
+			ssl_verify: false,
+			isInIteration: false,
+			default_value: [],
+			body: {
+				type: 'json',
+				data: [
+					{
+						key: '',
+						type: 'text',
+						value: '{"id":"b3a4aabb-a6b8-47f3-8a32-f45930f7d7b8"}'
+					}
+				]
+			},
+			params: [],
+			title: 'HTTP 请求',
+			type: 'http-request',
+			error_strategy: 'none',
+			retry_config: {
+				max_retries: 3,
+				retry_enabled: false,
+				retry_interval: 100
+			},
+			url: '#{env.api_address}/api/agent/getAgentInfo',
+			authorization: {
+				type: 'none',
+				config: {
+					api_key: '',
+					header: '',
+					type: ''
+				}
+			},
+			timeout_config: {
+				max_write_timeout: 0,
+				max_read_timeout: 0,
+				max_connect_timeout: 0
+			},
+			heads: [
+				{
+					name: 'Authorization',
+					value: ''
+				}
+			],
+			selected: true,
+			desc: '',
+			isInLoop: false
 		}
 	}
 }

+ 27 - 14
packages/nodes/materials/index.ts

@@ -7,20 +7,33 @@
  */
 import { startNode } from './start'
 import { endNode } from './end'
-import { httpNode }from './http'
-import { conditionNode }from './condition'
-import { databaseNode }from './database'
-import { codeNode }from './code'
+import { httpNode } from './http'
+import { conditionNode } from './condition'
+import { databaseNode } from './database'
+import { codeNode } from './code'
 
-import { materialTools, type SourceType} from './toolbar'
+import { materialTools, type SourceType } from './toolbar'
+import type { INodeType } from '../Interface'
+
+const nodes = [startNode, endNode, httpNode, conditionNode, databaseNode, codeNode]
+
+const nodeMap = nodes.reduce(
+	(acc, cur) => {
+		acc[cur.name] = cur
+		return acc
+	},
+	{} as Record<string, INodeType>
+)
 
 export {
-    startNode,
-    endNode,
-    httpNode,
-    conditionNode,
-    databaseNode,
-    codeNode,
-    materialTools,
-    type SourceType,
-}
+	startNode,
+	endNode,
+	httpNode,
+	conditionNode,
+	databaseNode,
+	codeNode,
+	materialTools,
+	nodes,
+	nodeMap,
+	type SourceType
+}

+ 24 - 19
packages/nodes/materials/start.ts

@@ -1,22 +1,27 @@
-/*
- * @Author: liuJie
- * @Date: 2026-01-24 19:26:16
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 21:21:04
- * @Describe: 开始节点,
- */
-import type { IWorkflowNode } from '@repo/workflow'
+import { NodeConnectionTypes, type INodeType } from '../Interface'
 
-export const startNode: IWorkflowNode = {
-	id: 'start-node',
-	type: 'start-node',
-	label: '开始',
-	position: { x: 80, y: 272 },
-	data: {
-		id: 'start-node',
-		label: '开始节点',
-		description: '这是一个开始节点',
-		inputs: [],
-		outputs: []
+export const startNode: INodeType = {
+	version: ['1'],
+	displayName: '开始',
+	name: 'start',
+	description: '流程开始节点',
+	icon: 'lucide:play',
+	iconColor: '#409eff',
+	inputs: [],
+	outputs: [NodeConnectionTypes.main],
+	// 业务数据
+	schema: {
+		appAgentId: '',
+		parentId: '',
+		position: {
+			x: 20,
+			y: 30
+		},
+		width: 280,
+		height: 60,
+		selected: true,
+		nodeType: 'start',
+		zIndex: 1,
+		data: {}
 	}
 }

+ 1 - 1
packages/ui/components/icon-button/IconButton.vue

@@ -40,7 +40,7 @@ const iconSize = computed(() => (props.size === 'large' ? 20 : props.size === 's
 
 <style lang="less" scoped>
 .square {
-	.el-button {
+	&.el-button {
 		padding: 8px;
 		&--large {
 			padding: 12px;

+ 12 - 3
packages/workflow/src/Interface.ts

@@ -23,9 +23,18 @@ export interface CanvasElementPortWithRenderData extends CanvasConnectionPort {
 export interface IWorkflowNode extends Node {
 	data: {
 		id: string
-		inputs: CanvasConnectionPort[]
-		outputs: CanvasConnectionPort[]
-		renderType?: 'default' | 'stickyNote' | 'custom'
+		// 位置
+		position: XYPosition
+		// 宽度
+		width: number
+		// 高度
+		height: number
+		// 父级节点
+		parentId?: string
+		// 节点层级
+		zIndex?: number
+		// 节点类型
+		nodeType: string
 		// 定义节点数据
 		[key: string]: any
 	}

+ 21 - 3
packages/workflow/src/Workflow.vue

@@ -1,12 +1,19 @@
 <template>
-	<div class="w-full h-full">
-		<Canvas :id="id" :nodes="nodes" :edges="edges" :read-only="readOnly" v-bind="$attrs" />
+	<div class="w-full h-full relative">
+		<Canvas
+			ref="vueflowRef"
+			:id="id"
+			:nodes="nodes"
+			:edges="edges"
+			:read-only="readOnly"
+			v-bind="$attrs"
+		/>
 		<slot />
 	</div>
 </template>
 
 <script setup lang="ts">
-import { computed } from 'vue'
+import { ref, computed, provide } from 'vue'
 import Canvas from './components/Canvas.vue'
 import type { IWorkflow } from './Interface'
 
@@ -26,8 +33,19 @@ const props = withDefaults(
 	}
 )
 
+const vueflowRef = ref<InstanceType<typeof Canvas> | null>(null)
+
 // nodes
 const nodes = computed(() => props.workflow?.nodes || [])
 // edges
 const edges = computed(() => props.workflow?.edges || [])
+
+defineExpose({
+	getVueFlow() {
+		return vueflowRef.value?.vueFlow
+	}
+})
+
+provide('workflow', props.workflow)
+provide('vueflow', vueflowRef.value)
 </script>

+ 0 - 24
packages/workflow/src/components/Canvas.vue

@@ -275,30 +275,6 @@ defineExpose({
 			/>
 		</template>
 
-		<template #node-start-node="nodeProps">
-			<StartNode v-bind="nodeProps" :data="nodeDataById[nodeProps.id]" />
-		</template>
-
-		<template #node-end-node="nodeProps">
-			<EndNode v-bind="nodeProps" :data="nodeDataById[nodeProps.id]" />
-		</template>
-
-		<template #node-http-node="nodeProps">
-			<HttpNode v-bind="nodeProps" :data="nodeDataById[nodeProps.id]!" />
-		</template>
-
-		<template #node-code-node="nodeProps">
-			<CodeNode v-bind="nodeProps" :data="nodeDataById[nodeProps.id]!" />
-		</template>
-
-		<template #node-database-node="nodeProps">
-			<DataBaseNode v-bind="nodeProps" :data="nodeDataById[nodeProps.id]!" />
-		</template>
-
-		<template #node-condition-node="nodeProps">
-			<ConditionNode v-bind="nodeProps" :data="nodeDataById[nodeProps.id]!" />
-		</template>
-
 		<template #edge-canvas-edge="edgeProps">
 			<CanvasEdge
 				v-bind="edgeProps"

+ 0 - 103
packages/workflow/src/components/elements/control-bar/AddNode.vue

@@ -1,103 +0,0 @@
-<!--
- * @Author: liuJie
- * @Date: 2026-01-24 16:40:11
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 11:56:32
- * @Describe: 添加物料
--->
-<script lang="ts" setup>
-import { ref, computed, inject } from 'vue'
-import { Icon } from '@iconify/vue'
-import { ElPopover, ElButton } from 'element-plus'
-import { materialTools, type SourceType } from '@repo/nodes'
-
-defineOptions({
-	name: 'AddMaterialsPop'
-})
-
-const emit = defineEmits<{
-	'add-node': [value: SourceType]
-}>()
-
-const vueflow = inject('vueflow')
-const hasStart = computed(() => vueflow?.nodes?.some((node) => node.data.type === 'start'))
-const hasEnd = computed(() => vueflow?.nodes?.some((node) => node.data.type === 'end'))
-
-const materials = computed(() => {
-	return materialTools.map((group) => {
-		return {
-			...group,
-			source: group.source.filter((item) => {
-				return (
-					!(item.type === 'start' && !hasStart.value) && !(item.type === 'end' && !hasEnd.value)
-				)
-			})
-		}
-	})
-})
-
-const onAddNode = (value: SourceType) => {
-	emit('add-node', value)
-	show.value = false
-}
-const show = ref(false)
-const togglePop = () => {
-	show.value = !show.value
-}
-</script>
-<template>
-	<ElPopover
-		v-bind:visible="show"
-		trigger="click"
-		transition="el-zoom-in-top"
-		:show-after="400"
-		:hide-after="1000"
-		placement="top"
-	>
-		<div v-for="item in materials" :key="item.id">
-			<p class="mb-2 mt-1 text-[#676f83]">{{ item.label }}</p>
-			<ul>
-				<li
-					class="tool mb-3 flex items-center"
-					v-for="value in item.source"
-					:key="value.id"
-					@click="onAddNode(value)"
-				>
-					<Icon
-						:icon="value.icon"
-						height="16"
-						width="16"
-						class="mr-2 bg-[#6172f3] p-1 rounded"
-						color="#fff"
-					/>
-					<span>{{ value.name }}</span>
-				</li>
-			</ul>
-		</div>
-		<template #reference>
-			<ElButton @click="togglePop" type="primary">
-				<Icon icon="lucide:package-plus" height="16" width="16" class="mr-1" /> 新增节点
-			</ElButton>
-		</template>
-	</ElPopover>
-</template>
-<style lang="less" scoped>
-ul {
-	padding: 0 0 0 10px;
-
-	.tool {
-		list-style: none;
-		cursor: pointer;
-
-		span {
-			color: #354052;
-		}
-
-		&:hover {
-			span {
-				color: #6172f3;
-			}
-		}
-	}
-}
-</style>

+ 1 - 16
packages/workflow/src/components/elements/control-bar/CanvasControlBar.vue

@@ -2,7 +2,6 @@
 import { Controls } from '@vue-flow/controls'
 import { Icon } from '@iconify/vue'
 import { ElButton } from 'element-plus'
-import AddNode from './AddNode.vue'
 import type { SourceType } from '@repo/nodes'
 
 const emit = defineEmits<{
@@ -33,13 +32,6 @@ function onZoomToFit() {
 	emit('zoom-to-fit')
 }
 
-function onAddNode(value: SourceType | string) {
-	emit('add-node', value)
-}
-function onRun() {
-	emit('run')
-}
-
 function onToggleMinimap() {
 	emit('toggle-minimap')
 }
@@ -63,18 +55,11 @@ function onToggleMinimap() {
 			<ElButton @click="onToggleMinimap" square>
 				<Icon icon="lucide:map" height="16" width="16" />
 			</ElButton>
-			<ElButton @click="onAddNode('stickyNote')" square>
-				<Icon icon="lucide:file-plus-corner" height="16" width="16" />
-			</ElButton>
-			<AddNode @add-node="onAddNode" />
-			<ElButton @click="onRun" type="success">
-				<Icon icon="lucide:play" height="16" width="16" class="mr-1" /> 执行
-			</ElButton>
 		</div>
 	</Controls>
 </template>
 
-<style lang="less">
+<style lang="less" scoped>
 .el-button {
 	padding: 8px;
 }

+ 20 - 12
packages/workflow/src/components/elements/nodes/CanvasNode.vue

@@ -1,6 +1,11 @@
 <script setup lang="ts">
 import { computed, provide } from 'vue'
 import { Position } from '@vue-flow/core'
+import { nodeMap } from '@repo/nodes'
+
+import CanvasHandle from '../handles/CanvasHandle.vue'
+import NodeRenderer from './render-types/NodeRenderer.vue'
+
 import type { NodeProps } from '@vue-flow/core'
 import type {
 	IWorkflowNode,
@@ -8,9 +13,6 @@ import type {
 	CanvasElementPortWithRenderData
 } from '../../../Interface'
 
-import CanvasHandle from '../handles/CanvasHandle.vue'
-import NodeRenderer from './render-types/NodeRenderer.vue'
-
 type Props = NodeProps<IWorkflowNode['data']> & {
 	readOnly?: boolean
 	hovered?: boolean
@@ -50,32 +52,38 @@ const createEndpoint = (data: {
 /**
  * Inputs
  */
-const inputs = computed(() =>
-	(props.data.inputs || []).map((target, index) =>
+const inputs = computed(() => {
+	const getInputs = nodeMap[props.data.nodeType]?.inputs
+	const inputs = typeof getInputs === 'function' ? getInputs(props.data) : getInputs || []
+
+	return (inputs as CanvasConnectionPort[]).map((target, index) =>
 		createEndpoint({
 			port: target,
 			index,
-			count: props.data.inputs.length,
+			count: inputs.length,
 			offsetAxis: 'top',
 			position: Position.Left
 		})
 	)
-)
+})
 
 /**
  * Outputs
  */
-const outputs = computed(() =>
-	(props.data.outputs || []).map((source, index) =>
+const outputs = computed(() => {
+	const getOutputs = nodeMap[props.data.nodeType]?.outputs
+	const outputs = typeof getOutputs === 'function' ? getOutputs(props.data) : getOutputs || []
+
+	return (outputs as CanvasConnectionPort[]).map((target, index) =>
 		createEndpoint({
-			port: source,
+			port: target,
 			index,
-			count: props.data.outputs.length,
+			count: outputs.length,
 			offsetAxis: 'top',
 			position: Position.Right
 		})
 	)
-)
+})
 
 const onUpdate = (prop: Record<string, unknown>) => {
 	emit('update', props.id, prop)

+ 45 - 0
packages/workflow/src/components/elements/nodes/render-types/NodeDefault copy.vue

@@ -0,0 +1,45 @@
+<template>
+	<div
+		:class="nodeClass"
+		class="default-node w-96px h-96px bg-#fff box-border border-2 border-solid border-#dcdcdc rounded-8px relative"
+	>
+		<div className="w-full h-full relative flex items-center justify-center">
+			<Icon :icon="data?.icon" height="40" width="40" :color="data?.iconColor" />
+		</div>
+
+		<div className="absolute w-full bottom--24px text-12px text-center text-#333">
+			<div>{{ data?.displayName }}</div>
+			<div className="absolute w-full bottom--40px text-12px text-center text-#999 truncate">
+				{{ data?.subtitle }}
+			</div>
+		</div>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { inject, computed, type Ref } from 'vue'
+import { Icon } from '@repo/ui'
+
+import type { NodeProps } from '@vue-flow/core'
+import type { IWorkflowNode, CanvasConnectionPort } from '../../../../Interface'
+
+const node = inject<{
+	props?: NodeProps<IWorkflowNode['data']>
+	inputs?: Ref<CanvasConnectionPort[]>
+	outputs?: Ref<CanvasConnectionPort[]>
+}>('canvas-node-data')
+
+const nodeClass = computed(() => {
+	let classes: string[] = []
+	if (node?.props?.selected) {
+		classes.push('ring-6px', 'ring-#e0e2e7')
+	}
+	if (node?.inputs?.value?.length === 0) {
+		classes.push('rounded-l-36px')
+	}
+
+	return classes
+})
+
+const data = computed<IWorkflowNode['data'] | undefined>(() => node?.props?.data)
+</script>

+ 39 - 23
packages/workflow/src/components/elements/nodes/render-types/NodeDefault.vue

@@ -1,16 +1,41 @@
 <template>
-	<div
-		:class="nodeClass"
-		class="default-node w-96px h-96px bg-#fff box-border border-2 border-solid border-#dcdcdc rounded-8px relative"
-	>
-		<div className="w-full h-full relative flex items-center justify-center">
-			<Icon :icon="data?.icon" height="40" width="40" :color="data?.iconColor" />
-		</div>
+	<div class="relative min-w-[240px] transition-all duration-300 ease-out">
+		<!-- 节点主体 -->
+		<div
+			class="bg-gradient-to-br from-white to-blue-50 border-2 rounded-xl shadow-md transition-all duration-300 relative overflow-hidden"
+			:class="
+				node?.props?.selected
+					? 'border-blue-500 shadow-blue-200 shadow-lg'
+					: 'border-blue-300 hover:shadow-lg hover:shadow-blue-100'
+			"
+		>
+			<!-- 头部 -->
+			<div class="flex items-center gap-3 px-4 py-3 border-b border-blue-100">
+				<!-- 图标 -->
+				<div
+					class="relative flex-shrink-0 flex items-center justify-center w-10 h-10 rounded-lg"
+					:style="{ background: nodeType?.iconColor }"
+				>
+					<div
+						class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"
+					></div>
+					<Icon
+						:icon="nodeType?.icon ?? 'lucide:cloud'"
+						color="#ffffff"
+						class="relative z-10"
+						:size="20"
+					/>
+				</div>
 
-		<div className="absolute w-full bottom--24px text-12px text-center text-#333">
-			<div>{{ data?.displayName }}</div>
-			<div className="absolute w-full bottom--40px text-12px text-center text-#999 truncate">
-				{{ data?.subtitle }}
+				<!-- 标题 -->
+				<div class="flex-1 min-w-0">
+					<div class="text-sm font-semibold text-gray-800 truncate">
+						{{ data?.title || nodeType?.displayName || '节点标题' }}
+					</div>
+					<div v-if="node?.props?.data?.subtitle" class="text-xs text-gray-500 mt-0.5 truncate">
+						{{ data?.subtitle }}
+					</div>
+				</div>
 			</div>
 		</div>
 	</div>
@@ -19,6 +44,7 @@
 <script setup lang="ts">
 import { inject, computed, type Ref } from 'vue'
 import { Icon } from '@repo/ui'
+import { nodeMap } from '@repo/nodes'
 
 import type { NodeProps } from '@vue-flow/core'
 import type { IWorkflowNode, CanvasConnectionPort } from '../../../../Interface'
@@ -29,17 +55,7 @@ const node = inject<{
 	outputs?: Ref<CanvasConnectionPort[]>
 }>('canvas-node-data')
 
-const nodeClass = computed(() => {
-	let classes: string[] = []
-	if (node?.props?.selected) {
-		classes.push('ring-6px', 'ring-#e0e2e7')
-	}
-	if (node?.inputs?.value?.length === 0) {
-		classes.push('rounded-l-36px')
-	}
-
-	return classes
-})
-
 const data = computed<IWorkflowNode['data'] | undefined>(() => node?.props?.data)
+
+const nodeType = computed(() => nodeMap[data.value?.nodeType!])
 </script>

+ 2 - 2
packages/workflow/src/components/elements/nodes/render-types/NodeRenderer.vue

@@ -1,5 +1,5 @@
 <template>
-	<NodeStickyNote v-if="renderType === 'stickyNote'" v-bind="$attrs" />
+	<NodeStickyNote v-if="nodeType === 'stickyNote'" v-bind="$attrs" />
 	<NodeDefault v-else v-bind="$attrs"> </NodeDefault>
 </template>
 
@@ -17,5 +17,5 @@ const node = inject<{
 	outputs?: Ref<CanvasConnectionPort[]>
 }>('canvas-node-data')
 
-const renderType = computed(() => node?.props?.data?.renderType)
+const nodeType = computed(() => node?.props?.data?.nodeType)
 </script>

+ 100 - 0
pnpm-lock.yaml

@@ -142,6 +142,9 @@ importers:
       '@repo/nodes':
         specifier: workspace:^
         version: link:../../packages/nodes
+      '@vitejs/plugin-vue-jsx':
+        specifier: ^5.1.3
+        version: 5.1.3(rolldown-vite@7.2.5(@types/node@25.1.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(yaml@1.10.2))(vue@3.5.27(typescript@5.9.3))
       echarts:
         specifier: ^6.0.0
         version: 6.0.0
@@ -755,6 +758,12 @@ packages:
     peerDependencies:
       '@babel/core': ^7.0.0-0
 
+  '@babel/plugin-syntax-typescript@7.28.6':
+    resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
   '@babel/plugin-transform-arrow-functions@7.27.1':
     resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==}
     engines: {node: '>=6.9.0'}
@@ -965,6 +974,12 @@ packages:
     peerDependencies:
       '@babel/core': ^7.0.0-0
 
+  '@babel/plugin-transform-typescript@7.28.6':
+    resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
   '@babel/plugin-transform-unicode-escapes@7.27.1':
     resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==}
     engines: {node: '>=6.9.0'}
@@ -2231,6 +2246,9 @@ packages:
   '@rolldown/pluginutils@1.0.0-beta.53':
     resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==}
 
+  '@rolldown/pluginutils@1.0.0-rc.2':
+    resolution: {integrity: sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==}
+
   '@rsbuild/core@1.7.2':
     resolution: {integrity: sha512-VAFO6cM+cyg2ntxNW6g3tB2Jc5J5mpLjLluvm7VtW2uceNzyUlVv41o66Yp1t1ikxd3ljtqegViXem62JqzveA==}
     engines: {node: '>=18.12.0'}
@@ -2922,6 +2940,13 @@ packages:
     cpu: [x64]
     os: [win32]
 
+  '@vitejs/plugin-vue-jsx@5.1.3':
+    resolution: {integrity: sha512-I6Zr8cYVr5WHMW5gNOP09DNqW9rgO8RX73Wa6Czgq/0ndpTfJM4vfDChfOT1+3KtdrNqilNBtNlFwVeB02ZzGw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    peerDependencies:
+      vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
+      vue: ^3.0.0
+
   '@vitejs/plugin-vue@6.0.3':
     resolution: {integrity: sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==}
     engines: {node: ^20.19.0 || >=22.12.0}
@@ -2976,6 +3001,22 @@ packages:
       '@vue-flow/core': ^1.23.0
       vue: ^3.3.0
 
+  '@vue/babel-helper-vue-transform-on@2.0.1':
+    resolution: {integrity: sha512-uZ66EaFbnnZSYqYEyplWvn46GhZ1KuYSThdT68p+am7MgBNbQ3hphTL9L+xSIsWkdktwhPYLwPgVWqo96jDdRA==}
+
+  '@vue/babel-plugin-jsx@2.0.1':
+    resolution: {integrity: sha512-a8CaLQjD/s4PVdhrLD/zT574ZNPnZBOY+IhdtKWRB4HRZ0I2tXBi5ne7d9eCfaYwp5gU5+4KIyFTV1W1YL9xZA==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    peerDependenciesMeta:
+      '@babel/core':
+        optional: true
+
+  '@vue/babel-plugin-resolve-type@2.0.1':
+    resolution: {integrity: sha512-ybwgIuRGRRBhOU37GImDoWQoz+TlSqap65qVI6iwg/J7FfLTLmMf97TS7xQH9I7Qtr/gp161kYVdhr1ZMraSYQ==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
   '@vue/compiler-core@3.5.27':
     resolution: {integrity: sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==}
 
@@ -7522,6 +7563,11 @@ snapshots:
       '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.28.6
 
+  '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.28.6)':
+    dependencies:
+      '@babel/core': 7.28.6
+      '@babel/helper-plugin-utils': 7.28.6
+
   '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.6)':
     dependencies:
       '@babel/core': 7.28.6
@@ -7753,6 +7799,17 @@ snapshots:
       '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.28.6
 
+  '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.28.6)':
+    dependencies:
+      '@babel/core': 7.28.6
+      '@babel/helper-annotate-as-pure': 7.27.3
+      '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6)
+      '@babel/helper-plugin-utils': 7.28.6
+      '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+      '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.6)
+    transitivePeerDependencies:
+      - supports-color
+
   '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.6)':
     dependencies:
       '@babel/core': 7.28.6
@@ -9725,6 +9782,8 @@ snapshots:
 
   '@rolldown/pluginutils@1.0.0-beta.53': {}
 
+  '@rolldown/pluginutils@1.0.0-rc.2': {}
+
   '@rsbuild/core@1.7.2':
     dependencies:
       '@rspack/core': 1.7.3(@swc/helpers@0.5.18)
@@ -10636,6 +10695,18 @@ snapshots:
   '@unrs/resolver-binding-win32-x64-msvc@1.11.1':
     optional: true
 
+  '@vitejs/plugin-vue-jsx@5.1.3(rolldown-vite@7.2.5(@types/node@25.1.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(yaml@1.10.2))(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@babel/core': 7.28.6
+      '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.6)
+      '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.28.6)
+      '@rolldown/pluginutils': 1.0.0-rc.2
+      '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.28.6)
+      vite: rolldown-vite@7.2.5(@types/node@25.1.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(yaml@1.10.2)
+      vue: 3.5.27(typescript@5.9.3)
+    transitivePeerDependencies:
+      - supports-color
+
   '@vitejs/plugin-vue@6.0.3(rolldown-vite@7.2.5(@types/node@24.10.9)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(yaml@1.10.2))(vue@3.5.27(typescript@5.9.3))':
     dependencies:
       '@rolldown/pluginutils': 1.0.0-beta.53
@@ -10702,6 +10773,35 @@ snapshots:
       '@vue-flow/core': 1.48.1(vue@3.5.27(typescript@5.9.3))
       vue: 3.5.27(typescript@5.9.3)
 
+  '@vue/babel-helper-vue-transform-on@2.0.1': {}
+
+  '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.28.6)':
+    dependencies:
+      '@babel/helper-module-imports': 7.28.6(supports-color@5.5.0)
+      '@babel/helper-plugin-utils': 7.28.6
+      '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6)
+      '@babel/template': 7.28.6
+      '@babel/traverse': 7.28.6(supports-color@5.5.0)
+      '@babel/types': 7.28.6
+      '@vue/babel-helper-vue-transform-on': 2.0.1
+      '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.28.6)
+      '@vue/shared': 3.5.27
+    optionalDependencies:
+      '@babel/core': 7.28.6
+    transitivePeerDependencies:
+      - supports-color
+
+  '@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.28.6)':
+    dependencies:
+      '@babel/code-frame': 7.28.6
+      '@babel/core': 7.28.6
+      '@babel/helper-module-imports': 7.28.6(supports-color@5.5.0)
+      '@babel/helper-plugin-utils': 7.28.6
+      '@babel/parser': 7.28.6
+      '@vue/compiler-sfc': 3.5.27
+    transitivePeerDependencies:
+      - supports-color
+
   '@vue/compiler-core@3.5.27':
     dependencies:
       '@babel/parser': 7.28.6