From d2da078b40578cf72901442c7a2b878dfc34cae5 Mon Sep 17 00:00:00 2001
From: wujingjing <gersonwu@qq.com>
Date: 星期五, 13 十二月 2024 12:10:00 +0800
Subject: [PATCH] feat(flow): 新增执行功能节点并优化流程画布

---
 customer_list/common/static/fonts/ywiconfont/iconfont.ttf        |    0 
 src/components/vue-flow/ui/nodes/FuncNode.vue                    |  112 ++++++++++++
 src/components/vue-flow/ui/nodes/EndNode.vue                     |   14 +
 tailwind.config.js                                               |    3 
 package-lock.json                                                |   14 +
 src/components/vue-flow/ui/nodes/OutputNode.vue                  |   14 +
 src/api/workflow/index.ts                                        |   18 +
 src/views/project/yw/systemManage/flowApp/components/Sidebar.vue |   48 ----
 src/components/vue-flow/vueFlowEnum.ts                           |   19 +
 customer_list/common/static/fonts/ywiconfont/iconfont.woff2      |    0 
 src/main.ts                                                      |    5 
 customer_list/common/static/fonts/ywiconfont/iconfont.woff       |    0 
 src/components/vue-flow/ui/nodes/AgentNode.vue                   |   14 +
 src/components/icon/index.vue                                    |   25 ++
 src/components/vue-flow/VueFlowHelper.ts                         |   24 ++
 package.json                                                     |    6 
 src/components/vue-flow/ui/nodes/ConditionNode.vue               |   23 +
 src/utils/setIconfont.ts                                         |    2 
 src/components/vue-flow/ui/VueFlowConfig.ts                      |   78 ++++++++
 src/components/vue-flow/MainCanvas.vue                           |   13 +
 src/views/project/yw/systemManage/flowApp/FlowApp.vue            |   14 +
 customer_list/common/static/fonts/ywiconfont/iconfont.css        |   14 +
 src/components/vue-flow/ui/nodes/LLMNode.vue                     |   12 +
 src/components/vue-flow/ui/nodes/StartNode.vue                   |   15 +
 24 files changed, 390 insertions(+), 97 deletions(-)

diff --git a/customer_list/common/static/fonts/ywiconfont/iconfont.css b/customer_list/common/static/fonts/ywiconfont/iconfont.css
index d046c76..7db4c1a 100644
--- a/customer_list/common/static/fonts/ywiconfont/iconfont.css
+++ b/customer_list/common/static/fonts/ywiconfont/iconfont.css
@@ -1,8 +1,8 @@
 @font-face {
   font-family: "ywifont"; /* Project id 4655417 */
-  src: url('iconfont.woff2?t=1733970308986') format('woff2'),
-       url('iconfont.woff?t=1733970308986') format('woff'),
-       url('iconfont.ttf?t=1733970308986') format('truetype');
+  src: url('iconfont.woff2?t=1734055372679') format('woff2'),
+       url('iconfont.woff?t=1734055372679') format('woff'),
+       url('iconfont.ttf?t=1734055372679') format('truetype');
 }
 
 .ywifont {
@@ -13,6 +13,14 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.ywicon-jianpan:before {
+  content: "\eac2";
+}
+
+.ywicon-llm:before {
+  content: "\e611";
+}
+
 .ywicon-jiegousheji:before {
   content: "\e63b";
 }
diff --git a/customer_list/common/static/fonts/ywiconfont/iconfont.ttf b/customer_list/common/static/fonts/ywiconfont/iconfont.ttf
index 1245eb9..b188d10 100644
--- a/customer_list/common/static/fonts/ywiconfont/iconfont.ttf
+++ b/customer_list/common/static/fonts/ywiconfont/iconfont.ttf
Binary files differ
diff --git a/customer_list/common/static/fonts/ywiconfont/iconfont.woff b/customer_list/common/static/fonts/ywiconfont/iconfont.woff
index f414a2b..768508c 100644
--- a/customer_list/common/static/fonts/ywiconfont/iconfont.woff
+++ b/customer_list/common/static/fonts/ywiconfont/iconfont.woff
Binary files differ
diff --git a/customer_list/common/static/fonts/ywiconfont/iconfont.woff2 b/customer_list/common/static/fonts/ywiconfont/iconfont.woff2
index 5760f8a..d9b4781 100644
--- a/customer_list/common/static/fonts/ywiconfont/iconfont.woff2
+++ b/customer_list/common/static/fonts/ywiconfont/iconfont.woff2
Binary files differ
diff --git a/package-lock.json b/package-lock.json
index ddcf26f..04088ef 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,6 +23,7 @@
 				"@vue-flow/background": "^1.3.2",
 				"@vue-flow/controls": "^1.1.2",
 				"@vue-flow/core": "^1.41.6",
+				"@vue-flow/minimap": "^1.5.0",
 				"@vueuse/core": "^11.0.3",
 				"@wangeditor/editor": "^5.1.23",
 				"@wangeditor/editor-for-vue": "^5.1.12",
@@ -2376,6 +2377,19 @@
 				}
 			}
 		},
+		"node_modules/@vue-flow/minimap": {
+			"version": "1.5.0",
+			"resolved": "https://registry.npmmirror.com/@vue-flow/minimap/-/minimap-1.5.0.tgz",
+			"integrity": "sha512-JhxXDF+8uTc7sgkZHDIvFpHqSl4wsK9xp8Kz5OHwNcXlgGcwqj4yad6jcc1B6bGxm+huESpNmoPotQbpMn6rVw==",
+			"dependencies": {
+				"d3-selection": "^3.0.0",
+				"d3-zoom": "^3.0.0"
+			},
+			"peerDependencies": {
+				"@vue-flow/core": "^1.23.0",
+				"vue": "^3.3.0"
+			}
+		},
 		"node_modules/@vue/compiler-core": {
 			"version": "3.4.38",
 			"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.4.38.tgz",
diff --git a/package.json b/package.json
index 1ae1441..0716b16 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
 		"@vue-flow/background": "^1.3.2",
 		"@vue-flow/controls": "^1.1.2",
 		"@vue-flow/core": "^1.41.6",
+		"@vue-flow/minimap": "^1.5.0",
 		"@vueuse/core": "^11.0.3",
 		"@wangeditor/editor": "^5.1.23",
 		"@wangeditor/editor-for-vue": "^5.1.12",
@@ -88,6 +89,7 @@
 		"@vitejs/plugin-vue": "^5.0.4",
 		"@vue/compiler-sfc": "^3.4.15",
 		"autoprefixer": "^10.4.19",
+		"code-inspector-plugin": "^0.17.7",
 		"eslint": "^8.35.0",
 		"eslint-plugin-vue": "^9.9.0",
 		"ncp": "^2.0.0",
@@ -101,9 +103,7 @@
 		"vite-plugin-compression": "^0.5.1",
 		"vite-plugin-vue-setup-extend-plus": "^0.1.0",
 		"vue-eslint-parser": "^9.1.0",
-		"yw-deploy-cli": "latest",
-		"code-inspector-plugin": "^0.17.7"
-
+		"yw-deploy-cli": "latest"
 	},
 	"browserslist": [
 		"> 1%",
diff --git a/src/api/workflow/index.ts b/src/api/workflow/index.ts
index 336c696..bcb75de 100644
--- a/src/api/workflow/index.ts
+++ b/src/api/workflow/index.ts
@@ -68,13 +68,23 @@
 		...extraData,
 	});
 
+/**
+ * @description 鑾峰彇 agent_names 鍒楄〃
+ **/
+export const get_agent_names = (extraData: ExtraConfig = {}) =>
+	request({
+		url: `/admin/workflow/get_agent_names`,
+		method: 'post',
+		...extraData,
+	});
+
 
 	/**
- * @description 娴嬭瘯宸ヤ綔娴乤gent
+ * @description 鑾峰彇 func_names 鍒楄〃
  **/
-export const get_agent_names = ( extraData: ExtraConfig = {}) =>
+export const get_flow_func_names = (extraData: ExtraConfig = {}) =>
 request({
-	url: `/admin/workflow/get_agent_names`,
+	url: `/admin/workflow/get_flow_func_names`,
 	method: 'post',
 	...extraData,
-});
+});
\ No newline at end of file
diff --git a/src/components/icon/index.vue b/src/components/icon/index.vue
new file mode 100644
index 0000000..cae8fc7
--- /dev/null
+++ b/src/components/icon/index.vue
@@ -0,0 +1,25 @@
+<template>
+	<span
+		:class="['ywifont', `ywicon-${name}`]"
+		:style="{
+			fontSize: Number(fontSize) + 'px',
+			color: color,
+		}"
+	></span>
+</template>
+
+<script setup lang="ts" name="YWIcon">
+defineProps({
+	name: {
+		type: String,
+		required: true,
+	},
+	fontSize: {
+		type: String,
+	},
+	color: {
+		type: String,
+	},
+});
+</script>
+<style scoped lang="scss"></style>
diff --git a/src/components/vue-flow/MainCanvas.vue b/src/components/vue-flow/MainCanvas.vue
index c961f12..d24f53e 100644
--- a/src/components/vue-flow/MainCanvas.vue
+++ b/src/components/vue-flow/MainCanvas.vue
@@ -1,6 +1,6 @@
 <template>
 	<div class="relative h-full w-full" id="main-canvas" @drop="handleOnDrop" @dragover="handleOnDragOver">
-		<VueFlow v-model="elements" :node-types="nodeTypes" :connection-mode="ConnectionMode.Loose">
+		<VueFlow v-model="elements" :node-types="nodeTypes" :connection-mode="ConnectionMode.Strict">
 			<template #edge-custom="customEdgeProps">
 				<CustomEdge v-bind="customEdgeProps" />
 			</template>
@@ -8,8 +8,12 @@
 			<template #node-agent="agentNodeProps">
 				<AgentNode v-bind="agentNodeProps" :agentNames="agentNames" />
 			</template>
+			<template #node-func="funcNodeProps">
+				<FuncNode v-bind="funcNodeProps" :funcNames="funcNames" />
+			</template>
 			<Controls />
 			<Background />
+			<MiniMap pannable zoomable />
 		</VueFlow>
 	</div>
 </template>
@@ -17,6 +21,8 @@
 <script setup lang="ts">
 import { Background } from '@vue-flow/background';
 import { Controls } from '@vue-flow/controls';
+import { MiniMap } from '@vue-flow/minimap';
+
 import { Dimensions, Elements, useVueFlow, VueFlow, ConnectionMode, MarkerType } from '@vue-flow/core';
 import { markRaw, nextTick, ref, watch } from 'vue';
 import { Test_data } from './testData';
@@ -28,11 +34,12 @@
 import LLMNode from './ui/nodes/LLMNode.vue';
 import OutputNode from './ui/nodes/OutputNode.vue';
 import AgentNode from './ui/nodes/AgentNode.vue';
+import FuncNode from './ui/nodes/FuncNode.vue';
 
 import CustomEdge from './ui/edges/CustomEdge.vue';
 import { onMounted } from 'vue';
 
-const props = defineProps(['flowJson', 'agentNames']);
+const props = defineProps(['flowJson', 'agentNames','funcNames']);
 
 const nodeTypes = {
 	start: markRaw(StartNode),
@@ -147,6 +154,8 @@
 @import '@vue-flow/core/dist/style.css';
 @import '@vue-flow/core/dist/theme-default.css';
 @import '@vue-flow/controls/dist/style.css';
+@import '@vue-flow/minimap/dist/style.css';
+
 
 #main-canvas {
 	--vf-handle: #2563eb;
diff --git a/src/components/vue-flow/VueFlowHelper.ts b/src/components/vue-flow/VueFlowHelper.ts
index 6eaef88..d63c9f2 100644
--- a/src/components/vue-flow/VueFlowHelper.ts
+++ b/src/components/vue-flow/VueFlowHelper.ts
@@ -40,7 +40,7 @@
 								key: 'condition',
 								label: '',
 								type: 'condition',
-								value: [ConditionHelper.getDefaultConditionGroup(),ConditionHelper.getDefaultConditionGroup(true)],
+								value: [ConditionHelper.getDefaultConditionGroup(), ConditionHelper.getDefaultConditionGroup(true)],
 							},
 						],
 					},
@@ -82,6 +82,24 @@
 								// value_label:'',
 								required: true,
 								placeholder: '浠g悊',
+							},
+						],
+					},
+				];
+				break;
+
+			case NodeType.Func:
+				data[VueFlowConstant.GROUP_PARAMS_KEY] = [
+					{
+						[VueFlowConstant.PARAMS_KEY]: [
+							{
+								key: 'func_name',
+								label: '鍑芥暟鍚嶇О',
+								type: 'func_name_select',
+								value: '',
+								// value_label:'',
+								required: true,
+								placeholder: '鍑芥暟鍚嶇О',
 							},
 						],
 					},
@@ -212,9 +230,7 @@
 			: {
 					id: VueFlowHelper.genId(),
 					operator: ConditionOperator.And,
-					conditions: [
-						ConditionHelper.getConditionItem()
-					],
+					conditions: [ConditionHelper.getConditionItem()],
 			  };
 	};
 }
diff --git a/src/components/vue-flow/ui/VueFlowConfig.ts b/src/components/vue-flow/ui/VueFlowConfig.ts
new file mode 100644
index 0000000..1c5b126
--- /dev/null
+++ b/src/components/vue-flow/ui/VueFlowConfig.ts
@@ -0,0 +1,78 @@
+import { NodeType, nodeTypeMap } from '../vueFlowEnum';
+
+export class VueFlowConfig {
+	static nodeStyleMap = new Map([
+		[
+			NodeType.Start,
+			{
+				type: NodeType.Start,
+				title: nodeTypeMap[NodeType.Start],
+				icon: 'xiangxiajiantou -rotate-90',
+				fontSize: '13',
+				color: 'white',
+				class: '!bg-primary ',
+			},
+		],
+		[
+			NodeType.End,
+			{
+				type: NodeType.End,
+				title: nodeTypeMap[NodeType.End],
+				icon: 'jieshu',
+				fontSize: '13',
+				color: 'white',
+				class: '!bg-primary ',
+			},
+		],
+		[
+			NodeType.Condition,
+			{
+				type: NodeType.Condition,
+				title: nodeTypeMap[NodeType.Condition],
+				icon: 'jiegousheji',
+				fontSize: '14',
+				class: 'bg-[#edc9e9] ',
+			},
+		],
+		[
+			NodeType.LLM,
+			{
+				type: NodeType.LLM,
+				title: nodeTypeMap[NodeType.LLM],
+				icon: 'llm',
+				fontSize: '14',
+				class: 'bg-[#d9d7ff] ',
+			},
+		],
+		[
+			NodeType.Output,
+			{
+				type: NodeType.Output,
+				title: nodeTypeMap[NodeType.Output],
+				icon: 'xiaoxi1',
+				fontSize: '14',
+				class: 'bg-[#9ce4f4] ',
+			},
+		],
+		[
+			NodeType.Agent,
+			{
+				type: NodeType.Agent,
+				title: nodeTypeMap[NodeType.Agent],
+				icon: 'wode',
+				fontSize: '14',
+				class: 'bg-[#ffd89a] ',
+			},
+		],
+		[
+			NodeType.Func,
+			{
+				type: NodeType.Func,
+				title: nodeTypeMap[NodeType.Func],
+				icon: 'kaishi',
+				fontSize: '14',
+				class: 'bg-[#bbdbff] ',
+			},
+		],
+	]);
+}
diff --git a/src/components/vue-flow/ui/nodes/AgentNode.vue b/src/components/vue-flow/ui/nodes/AgentNode.vue
index afc8fd2..e223c35 100644
--- a/src/components/vue-flow/ui/nodes/AgentNode.vue
+++ b/src/components/vue-flow/ui/nodes/AgentNode.vue
@@ -2,7 +2,7 @@
 	<div
 		class="w-max-[520px] border-2 rounded-lg border-solid border-gray-100 bg-white p-3 shadow-md relative hover:border-blue-500 group"
 	>
-		<Handle :id="targetHandleId" type="source" :position="Position.Left" />
+		<Handle :id="targetHandleId" type="target" :position="Position.Left" />
 		<div
 			class="group-hover:visible invisible flex absolute divide-y-[1.5px] divide-solid divide-gray-100 rounded-lg right-0 -top-0.5 translate-y-[-100%]"
 			style="box-shadow: 0 0 15px #dbdee6"
@@ -27,7 +27,13 @@
 		<div class="flex flex-col gap-y-2">
 			<div class="flex justify-between flex-0">
 				<div class="flex items-center gap-x-2">
-					<img src="/@/components/vue-flow/ui/assets/images/icon_Start.png" class="h-4 w-4" alt="Start icon" />
+					<YWIcon
+						:name="VueFlowConfig.nodeStyleMap.get(NodeType.Agent).icon"
+						:fontSize="VueFlowConfig.nodeStyleMap.get(NodeType.Agent).fontSize"
+						:color="VueFlowConfig.nodeStyleMap.get(NodeType.Agent).color"
+						class="rounded-lg p-1.5"
+						:class="VueFlowConfig.nodeStyleMap.get(NodeType.Agent).class"
+					/>
 					<div class="flex flex-col gap-y-1">
 						<p v-if="!titleIsEdit" class="text-xl font-bold text-gray-500" @click="titleIsEdit = true">{{ data.title }}</p>
 						<el-input v-elInputFocus="false" v-else v-model="data.title" @blur="() => (titleIsEdit = false)"></el-input>
@@ -62,9 +68,9 @@
 import { Handle, Position, useNode, useVueFlow } from '@vue-flow/core';
 import { ref } from 'vue';
 
-import type { NodeProps } from '@vue-flow/core';
 import { VueFlowHelper } from '../../VueFlowHelper';
-import { LLMNodeData, LLMNodeEvents } from './index';
+import { NodeType } from '../../vueFlowEnum';
+import { VueFlowConfig } from '../VueFlowConfig';
 import { deepClone } from '/@/utils/other';
 
 // defineProps<NodeProps<LLMNodeData, LLMNodeEvents>>();
diff --git a/src/components/vue-flow/ui/nodes/ConditionNode.vue b/src/components/vue-flow/ui/nodes/ConditionNode.vue
index aeecf58..eeb4c1d 100644
--- a/src/components/vue-flow/ui/nodes/ConditionNode.vue
+++ b/src/components/vue-flow/ui/nodes/ConditionNode.vue
@@ -27,7 +27,13 @@
 		<div class="flex flex-col gap-y-2 min-w-[400px]">
 			<div class="flex justify-between flex-0">
 				<div class="flex items-center gap-x-2">
-					<img src="/@/components/vue-flow/ui/assets/images/icon_Start.png" class="h-4 w-4" alt="Start icon" />
+					<YWIcon
+						:name="VueFlowConfig.nodeStyleMap.get(NodeType.Condition).icon"
+						:fontSize="VueFlowConfig.nodeStyleMap.get(NodeType.Condition).fontSize"
+						:color="VueFlowConfig.nodeStyleMap.get(NodeType.Condition).color"
+						class="rounded-lg p-1.5"
+						:class="VueFlowConfig.nodeStyleMap.get(NodeType.Condition).class"
+					/>
 					<div class="flex flex-col gap-y-1">
 						<p v-if="!titleIsEdit" class="text-xl font-bold text-gray-500" @click="titleIsEdit = true">{{ data.title }}</p>
 						<el-input v-elInputFocus="false" v-else v-model="data.title" @blur="() => (titleIsEdit = false)"></el-input>
@@ -60,7 +66,7 @@
 					<div class="flex flex-col gap-y-2" v-if="item.conditions">
 						<div v-for="(subItem, subIndex) in item.conditions" class="ml-5 flex-items-center gap-x-2 group/conditionItem pr-8">
 							<el-input filterable class="w-[120px] flex-0" v-model="subItem.left_value" placeholder="杈撳叆鍙橀噺"> </el-input>
-							<el-select v-model="subItem.comparison_operation" class="flex-0 w-[120px]" placeholder="閫夋嫨鏉′欢">
+							<el-select filterable v-model="subItem.comparison_operation" class="flex-0 w-[120px]" placeholder="閫夋嫨鏉′欢">
 								<el-option
 									v-for="item in Object.keys(compareOperationMap)"
 									:key="item"
@@ -114,16 +120,17 @@
 </template>
 
 <script lang="ts" setup>
-import { Handle, Position, useNode, useVueFlow } from '@vue-flow/core';
-import { ref, watchEffect } from 'vue';
-
 import type { NodeProps } from '@vue-flow/core';
-import { computed } from 'vue';
+import { Handle, Position, useNode, useVueFlow } from '@vue-flow/core';
+import { computed, ref, watchEffect } from 'vue';
 import { VueFlowConstant } from '../../VueFlowConstant';
 import { ConditionHelper, VueFlowHelper } from '../../VueFlowHelper';
-import { CompareOperation, VarType, compareOperationMap, conditionOperatorMap, varTypeMap } from '../../vueFlowEnum';
+import { CompareOperation, VarType, compareOperationMap, conditionOperatorMap } from '../../vueFlowEnum';
 import { LLMNodeData, LLMNodeEvents } from './index';
 import { deepClone } from '/@/utils/other';
+import { VueFlowConfig } from '../VueFlowConfig';
+import { NodeType } from '../../vueFlowEnum';
+
 defineProps<NodeProps<LLMNodeData, LLMNodeEvents>>();
 
 const node = useNode();
@@ -133,7 +140,7 @@
 const otherHandleId = VueFlowHelper.genId();
 
 const isShowRight = (item) => {
-	const yes =  ![CompareOperation.empty, CompareOperation.notEmpty].includes(item.comparison_operation);
+	const yes = ![CompareOperation.empty, CompareOperation.notEmpty].includes(item.comparison_operation);
 	return yes;
 };
 
diff --git a/src/components/vue-flow/ui/nodes/EndNode.vue b/src/components/vue-flow/ui/nodes/EndNode.vue
index c3ee5cb..7d5e3d5 100644
--- a/src/components/vue-flow/ui/nodes/EndNode.vue
+++ b/src/components/vue-flow/ui/nodes/EndNode.vue
@@ -28,7 +28,13 @@
 		<div class="flex flex-col gap-y-2">
 			<div class="flex justify-between flex-0">
 				<div class="flex items-center gap-x-2">
-					<img src="/@/components/vue-flow/ui/assets/images/icon_End.png" class="h-4 w-4" alt="Start icon" />
+					<YWIcon
+						:name="VueFlowConfig.nodeStyleMap.get(NodeType.End).icon"
+						:fontSize="VueFlowConfig.nodeStyleMap.get(NodeType.End).fontSize"
+						:color="VueFlowConfig.nodeStyleMap.get(NodeType.End).color"
+						class="rounded-lg p-1.5"
+						:class="VueFlowConfig.nodeStyleMap.get(NodeType.End).class"
+					/>
 					<div class="flex flex-col gap-y-1">
 						<p v-if="!titleIsEdit" class="text-xl font-bold text-gray-500" @click="titleIsEdit = true">{{ data.title }}</p>
 						<el-input v-elInputFocus="false" v-else v-model="data.title" @blur="() => (titleIsEdit = false)"></el-input>
@@ -52,11 +58,13 @@
 import { computed, ref } from 'vue';
 import { VueFlowConstant } from '../../VueFlowConstant';
 import { VueFlowHelper } from '../../VueFlowHelper';
+import { VueFlowConfig } from '../VueFlowConfig';
+import { NodeType } from '../../vueFlowEnum';
+
 import { LLMNodeData, LLMNodeEvents } from './index';
 import { deepClone } from '/@/utils/other';
-
 defineProps<NodeProps<LLMNodeData, LLMNodeEvents>>();
-
+	
 const node = useNode();
 const handleId = ref(VueFlowHelper.getHandleId(node.node, 'target'));
 
diff --git a/src/components/vue-flow/ui/nodes/FuncNode.vue b/src/components/vue-flow/ui/nodes/FuncNode.vue
new file mode 100644
index 0000000..37c0d4d
--- /dev/null
+++ b/src/components/vue-flow/ui/nodes/FuncNode.vue
@@ -0,0 +1,112 @@
+<template>
+	<div
+		class="w-max-[520px] border-2 rounded-lg border-solid border-gray-100 bg-white p-3 shadow-md relative hover:border-blue-500 group"
+	>
+		<Handle :id="targetHandleId" type="target" :position="Position.Left" />
+		<div
+			class="group-hover:visible invisible flex absolute divide-y-[1.5px] divide-solid divide-gray-100 rounded-lg right-0 -top-0.5 translate-y-[-100%]"
+			style="box-shadow: 0 0 15px #dbdee6"
+		>
+			<el-tooltip effect="dark" content="澶嶅埗" placement="top">
+				<div
+					class="flex content-center items-center border-x-0 p-1 hover:bg-gray-200 active:bg-gray-300 cursor-pointer"
+					@click="handleClickDuplicateBtn"
+				>
+					<span class="ywifont !text-[20px] mb-1 p-1.5 ywicon-copy"></span>
+				</div>
+			</el-tooltip>
+			<el-tooltip effect="dark" content="鍒犻櫎" placement="top">
+				<div
+					@click="clickDeleteBtn"
+					class="flex content-center items-center border-x-0 p-1 hover:bg-gray-200 active:bg-gray-300 hover:text-red-400 cursor-pointer"
+				>
+					<span class="ywifont !text-[20px] mb-1 p-1.5 ywicon-shanchu"></span>
+				</div>
+			</el-tooltip>
+		</div>
+		<div class="flex flex-col gap-y-2">
+			<div class="flex justify-between flex-0">
+				<div class="flex items-center gap-x-2">
+					<YWIcon
+						:name="VueFlowConfig.nodeStyleMap.get(NodeType.Agent).icon"
+						:fontSize="VueFlowConfig.nodeStyleMap.get(NodeType.Agent).fontSize"
+						:color="VueFlowConfig.nodeStyleMap.get(NodeType.Agent).color"
+						class="rounded-lg p-1.5"
+						:class="VueFlowConfig.nodeStyleMap.get(NodeType.Agent).class"
+					/>
+					<div class="flex flex-col gap-y-1">
+						<p v-if="!titleIsEdit" class="text-xl font-bold text-gray-500" @click="titleIsEdit = true">{{ data.title }}</p>
+						<el-input v-elInputFocus="false" v-else v-model="data.title" @blur="() => (titleIsEdit = false)"></el-input>
+					</div>
+				</div>
+			</div>
+
+			<div class="flex-auto gap-y-2 flex-col flex nodrag">
+				<div class="text-lg font-bold">鍑芥暟鍚嶇О</div>
+
+				<div class="min-w-[340px] flex-items-center gap-x-2">
+					<div class="flex-column gap-y-1.5">
+						<!-- <span class="text-gray-500">妯″瀷</span> -->
+						<el-select filterable placeholder="璇烽�夋嫨鍑芥暟鍚嶇О"  v-model="funcNameParams.value" @change="agentParamsValueChange">
+							<el-option v-for="item in funcNames"  :key="item.id" :value="item.id" :label="item.title"></el-option>
+						</el-select>
+					</div>
+				</div>
+			</div>
+		</div>
+		<Handle :id="sourceHandleId" type="source" :position="Position.Right" />
+	</div>
+
+	<!-- <div>
+		<span>璧峰</span>
+
+		<Handle type="source" :position="Position.Right" />
+	</div> -->
+</template>
+
+<script lang="ts" setup>
+import { Handle, Position, useNode, useVueFlow } from '@vue-flow/core';
+import { ref } from 'vue';
+
+import { VueFlowHelper } from '../../VueFlowHelper';
+import { NodeType } from '../../vueFlowEnum';
+import { VueFlowConfig } from '../VueFlowConfig';
+import { deepClone } from '/@/utils/other';
+
+// defineProps<NodeProps<LLMNodeData, LLMNodeEvents>>();
+
+const props = defineProps(['funcNames'])
+const agentParamsValueChange = () => {
+	// const foundNames = props.agentNames.find(item=>item.id == agentParams.value);
+	// agentParams.value.value_label =  foundNames?.title ??'';
+};
+const node = useNode();
+const sourceHandleId = ref(VueFlowHelper.getHandleId(node.node, 'source'));
+const targetHandleId = ref(VueFlowHelper.getHandleId(node.node, 'target'));
+
+const data = ref(node.node.data);
+
+const varList = ref(VueFlowHelper.getFieldValue(data.value, 'var_list'));
+const titleIsEdit = ref(false);
+
+const funcNameParams = ref(VueFlowHelper.getParams(VueFlowHelper.getGroupParam(data.value), 'func_name'));
+
+const { removeNodes, nodes, addNodes } = useVueFlow();
+
+function handleClickDuplicateBtn() {
+	const { type, position, data } = node.node;
+	const newNode = {
+		id: VueFlowHelper.genGeometryId(),
+		type,
+		position: {
+			x: position.x + 100,
+			y: position.y + 100,
+		},
+		data: deepClone(data),
+	};
+	addNodes(newNode);
+}
+const clickDeleteBtn = () => {
+	removeNodes(node.id);
+};
+</script>
diff --git a/src/components/vue-flow/ui/nodes/LLMNode.vue b/src/components/vue-flow/ui/nodes/LLMNode.vue
index 69eb5b6..c90e462 100644
--- a/src/components/vue-flow/ui/nodes/LLMNode.vue
+++ b/src/components/vue-flow/ui/nodes/LLMNode.vue
@@ -2,7 +2,7 @@
 	<div
 		class="w-max-[520px] border-2 rounded-lg border-solid border-gray-100 bg-white p-3 shadow-md relative hover:border-blue-500 group"
 	>
-		<Handle :id="targetHandleId" type="source" :position="Position.Left" />
+		<Handle :id="targetHandleId" type="target" :position="Position.Left" />
 		<div
 			class="group-hover:visible invisible flex absolute divide-y-[1.5px] divide-solid divide-gray-100 rounded-lg right-0 -top-0.5 translate-y-[-100%]"
 			style="box-shadow: 0 0 15px #dbdee6"
@@ -27,7 +27,13 @@
 		<div class="flex flex-col gap-y-2">
 			<div class="flex justify-between flex-0">
 				<div class="flex items-center gap-x-2">
-					<img src="/@/components/vue-flow/ui/assets/images/icon_Start.png" class="h-4 w-4" alt="Start icon" />
+					<YWIcon
+						:name="VueFlowConfig.nodeStyleMap.get(NodeType.LLM).icon"
+						:fontSize="VueFlowConfig.nodeStyleMap.get(NodeType.LLM).fontSize"
+						:color="VueFlowConfig.nodeStyleMap.get(NodeType.LLM).color"
+						class="rounded-lg p-1.5"
+						:class="VueFlowConfig.nodeStyleMap.get(NodeType.LLM).class"
+					/>
 					<div class="flex flex-col gap-y-1">
 						<p v-if="!titleIsEdit" class="text-xl font-bold text-gray-500" @click="titleIsEdit = true">{{ data.title }}</p>
 						<el-input v-else v-elInputFocus="false" v-model="data.title" @blur="() => (titleIsEdit = false)"></el-input>
@@ -85,6 +91,8 @@
 import { VueFlowHelper } from '../../VueFlowHelper';
 import { LLMNodeData, LLMNodeEvents } from './index';
 import { deepClone } from '/@/utils/other';
+import { NodeType } from '../../vueFlowEnum';
+import { VueFlowConfig } from '../VueFlowConfig';
 
 defineProps<NodeProps<LLMNodeData, LLMNodeEvents>>();
 
diff --git a/src/components/vue-flow/ui/nodes/OutputNode.vue b/src/components/vue-flow/ui/nodes/OutputNode.vue
index a62d7ec..2d45c89 100644
--- a/src/components/vue-flow/ui/nodes/OutputNode.vue
+++ b/src/components/vue-flow/ui/nodes/OutputNode.vue
@@ -2,7 +2,7 @@
 	<div
 		class="min-w-[320px] w-max-[520px] border-2 rounded-lg border-solid border-gray-100 bg-white p-3 shadow-md relative hover:border-blue-500 group"
 	>
-		<Handle :id="targetHandleId" type="source" :position="Position.Left" />
+		<Handle :id="targetHandleId" type="target" :position="Position.Left" />
 		<div
 			class="group-hover:visible invisible flex absolute divide-y-[1.5px] divide-solid divide-gray-100 rounded-lg right-0 -top-0.5 translate-y-[-100%]"
 			style="box-shadow: 0 0 15px #dbdee6"
@@ -27,7 +27,13 @@
 		<div class="flex flex-col gap-y-2">
 			<div class="flex justify-between flex-0">
 				<div class="flex items-center gap-x-2">
-					<img src="/@/components/vue-flow/ui/assets/images/icon_Start.png" class="h-4 w-4" alt="Start icon" />
+					<YWIcon
+						:name="VueFlowConfig.nodeStyleMap.get(NodeType.Output).icon"
+						:fontSize="VueFlowConfig.nodeStyleMap.get(NodeType.Output).fontSize"
+						:color="VueFlowConfig.nodeStyleMap.get(NodeType.Output).color"
+						class="rounded-lg p-1.5"
+						:class="VueFlowConfig.nodeStyleMap.get(NodeType.Output).class"
+					/>
 					<div class="flex flex-col gap-y-1">
 						<p v-if="!titleIsEdit" class="text-xl font-bold text-gray-500" @click="titleIsEdit = true">{{ data.title }}</p>
 						<el-input v-elInputFocus="false" v-else v-model="data.title" @blur="() => (titleIsEdit = false)"></el-input>
@@ -121,8 +127,8 @@
 import { InteractionType, interactionTypeMap } from '../../vueFlowEnum';
 import { LLMNodeData, LLMNodeEvents } from './index';
 import { deepClone } from '/@/utils/other';
-import { remove } from 'nprogress';
-defineProps<NodeProps<LLMNodeData, LLMNodeEvents>>();
+import { VueFlowConfig } from '../VueFlowConfig';
+import { NodeType } from '../../vueFlowEnum';defineProps<NodeProps<LLMNodeData, LLMNodeEvents>>();
 
 const node = useNode();
 const sourceHandleId = ref(VueFlowHelper.getHandleId(node.node, 'source'));
diff --git a/src/components/vue-flow/ui/nodes/StartNode.vue b/src/components/vue-flow/ui/nodes/StartNode.vue
index 582b0b5..9f2ebe2 100644
--- a/src/components/vue-flow/ui/nodes/StartNode.vue
+++ b/src/components/vue-flow/ui/nodes/StartNode.vue
@@ -26,7 +26,14 @@
 		<div class="flex flex-col gap-y-2">
 			<div class="flex justify-between flex-0">
 				<div class="flex items-center gap-x-2">
-					<img src="/@/components/vue-flow/ui/assets/images/icon_Start.png" class="h-4 w-4" alt="Start icon" />
+					<YWIcon
+						:name="VueFlowConfig.nodeStyleMap.get(NodeType.Start).icon"
+						:fontSize="VueFlowConfig.nodeStyleMap.get(NodeType.Start).fontSize"
+						:color="VueFlowConfig.nodeStyleMap.get(NodeType.Start).color"
+						class="rounded-lg p-1.5 "
+						:class="VueFlowConfig.nodeStyleMap.get(NodeType.Start).class"
+
+					/>
 					<div class="flex flex-col gap-y-1">
 						<p v-if="!titleIsEdit" class="text-xl font-bold text-gray-500" @click="titleIsEdit = true">{{ data.title }}</p>
 						<el-input v-elInputFocus="false" v-else v-model="data.title" @blur="() => (titleIsEdit = false)"></el-input>
@@ -96,17 +103,15 @@
 import type { NodeProps } from '@vue-flow/core';
 import { VueFlowConstant } from '../../VueFlowConstant';
 import { VueFlowHelper } from '../../VueFlowHelper';
-import { parameterTypeMap } from '../../vueFlowEnum';
+import { NodeType, parameterTypeMap } from '../../vueFlowEnum';
+import { VueFlowConfig } from '../VueFlowConfig';
 import { LLMNodeData, LLMNodeEvents } from './index';
 import { deepClone } from '/@/utils/other';
-
 defineProps<NodeProps<LLMNodeData, LLMNodeEvents>>();
-
 const node = useNode();
 const handleId = ref(VueFlowHelper.getHandleId(node.node, 'source'));
 
 const data = ref(node.node.data);
-
 const getVarList = () => {
 	const varList = data.value[VueFlowConstant.GROUP_PARAMS_KEY][0][VueFlowConstant.PARAMS_KEY].find(
 		(item) => item.key === 'condition'
diff --git a/src/components/vue-flow/vueFlowEnum.ts b/src/components/vue-flow/vueFlowEnum.ts
index e7c1022..a0c03c8 100644
--- a/src/components/vue-flow/vueFlowEnum.ts
+++ b/src/components/vue-flow/vueFlowEnum.ts
@@ -5,9 +5,9 @@
 }
 
 export const parameterTypeMap = {
-	[ParameterType.String]: 'string',
-	[ParameterType.Number]: 'number',
-	[ParameterType.Boolean]: 'boolean',
+	[ParameterType.String]: '瀛楃涓�',
+	[ParameterType.Number]: '鏁板瓧',
+	[ParameterType.Boolean]: '甯冨皵鍊�',
 };
 
 export const enum NodeType {
@@ -17,7 +17,8 @@
 	Condition = 'condition',
 	Knowledge = 'knowledge',
 	Output = 'output_msg',
-	Agent='agent'
+	Agent='agent',
+	Func='func',
 }
 
 export const nodeTypeMap = {
@@ -26,8 +27,9 @@
 	[NodeType.End]: '缁撴潫',
 	[NodeType.Condition]: '鏉′欢鍒ゆ柇',
 	[NodeType.Knowledge]: '鐭ヨ瘑搴�',
-	[NodeType.Output]: '杈撳嚭',
+	[NodeType.Output]: '瀵硅瘽',
 	[NodeType.Agent]: '浠g悊',
+	[NodeType.Func]: '鎵ц鍔熻兘',
 
 };
 
@@ -44,6 +46,11 @@
 	startWith = 'startWith',
 	/** @description 缁撴潫涓� */
 	endWith = 'endWith',
+
+	/** @description 闈炲紑濮嬩负 */
+	notStartWith = 'notStartWith',
+	/** @description 闈炵粨鏉熶负 */
+	notEndWith = 'notEndWith',
 
 	/** @description 绛変簬 */
 	eq = 'eq',
@@ -71,6 +78,8 @@
 	[CompareOperation.notEmpty]: '涓嶄负绌�',
 	[CompareOperation.startWith]: '寮�濮嬩负',
 	[CompareOperation.endWith]: '缁撴潫涓�',
+	[CompareOperation.notStartWith]: '闈炲紑濮嬩负',
+	[CompareOperation.notEndWith]: '闈炵粨鏉熶负',
 };
 
 export const enum VarType {
diff --git a/src/main.ts b/src/main.ts
index f2a3365..620b129 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -5,14 +5,14 @@
 import { directive } from '/@/directive/index';
 import { i18n } from '/@/i18n/index';
 import other from '/@/utils/other';
-import "@amap/amap-jsapi-types";
+import '@amap/amap-jsapi-types';
 import '/@/extend';
+import YWIcon from '/@/components/icon/index.vue';
 
 import ElementPlus from 'element-plus';
 import '/@/theme/index.scss';
 import * as ElementPlusIconsVue from '@element-plus/icons-vue';
 import { gotoRoute } from '/@/utils/route';
-
 
 const app = createApp(App);
 for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
@@ -20,6 +20,7 @@
 }
 directive(app);
 other.elSvg(app);
+app.component('YWIcon', YWIcon);
 app.use(pinia).use(router).use(ElementPlus).use(i18n).mount('#app');
 
 window.openPage = (name, siteId) => {
diff --git a/src/utils/setIconfont.ts b/src/utils/setIconfont.ts
index 0887ad2..e678257 100644
--- a/src/utils/setIconfont.ts
+++ b/src/utils/setIconfont.ts
@@ -1,7 +1,7 @@
 // 瀛椾綋鍥炬爣 url
 const cssCdnUrlList: Array<string> = [
 	'./static/fonts/iconfont/iconfont.css',
-	'./static/fonts/ywiconfont/iconfont.css?v=2112',
+	'./static/fonts/ywiconfont/iconfont.css?v=21122',
 	'./static/fonts/fontawesome/fontawesome.min.css',
 
 ];
diff --git a/src/views/project/yw/systemManage/flowApp/FlowApp.vue b/src/views/project/yw/systemManage/flowApp/FlowApp.vue
index bacc18a..4a1c654 100644
--- a/src/views/project/yw/systemManage/flowApp/FlowApp.vue
+++ b/src/views/project/yw/systemManage/flowApp/FlowApp.vue
@@ -1,11 +1,11 @@
 <template>
-	<div class="absolute bottom-0 left-0 right-0 top-0 bg-gray text-base">
+	<div class="absolute bottom-0 left-0 right-0 top-0 bg-page text-base">
 		<div class="relative flex h-full w-full flex-col">
 			<Header v-if="flowAgent" :flowAgent="flowAgent" :queryId="queryId" />
 			<main class="relative flex h-full w-full flex-1">
-				<Sidebar @dragstart="handleOnDragStart" />
+				<Sidebar class="w-52" @dragstart="handleOnDragStart" />
 				<div class="relative h-full flex-1 overflow-hidden" v-if="flowJson">
-					<MainCanvas :flowJson="flowJson" :agentNames="agentNames" />
+					<MainCanvas :flowJson="flowJson" :agentNames="agentNames" :funcNames="funcNames"/>
 				</div>
 			</main>
 		</div>
@@ -20,7 +20,7 @@
 import { computed, onMounted, ref } from 'vue';
 import Header from './components/Header.vue';
 import Sidebar from './components/Sidebar.vue';
-import { get_agent_names, get_workflow_agent_list, get_workflow_json_flow } from '/@/api/workflow';
+import { get_agent_names, get_flow_func_names, get_workflow_agent_list, get_workflow_json_flow } from '/@/api/workflow';
 import MainCanvas from '/@/components/vue-flow/MainCanvas.vue';
 import router from '/@/router';
 
@@ -62,11 +62,17 @@
 	const res = await get_agent_names();
 	agentNames.value = res.agents ?? [];
 };
+const funcNames = ref([]);
 
+const getFuncNames = async () => {
+	const res = await get_flow_func_names();
+	funcNames.value = res.funcs ?? [];
+};
 onMounted(() => {
 	if (!queryId.value) return;
 	handleGetJSON(queryId.value);
 	getFlowAgent();
 	getAgentNames();
+	getFuncNames();
 });
 </script>
diff --git a/src/views/project/yw/systemManage/flowApp/components/Sidebar.vue b/src/views/project/yw/systemManage/flowApp/components/Sidebar.vue
index e5c1ec9..bd07db3 100644
--- a/src/views/project/yw/systemManage/flowApp/components/Sidebar.vue
+++ b/src/views/project/yw/systemManage/flowApp/components/Sidebar.vue
@@ -1,16 +1,16 @@
 <template>
-	<div class="w-44 bg-white rounded-lg mt-2 py-2 px-2">
+	<div class="bg-white rounded-lg mt-2 py-2 px-2">
 		<div
-			v-for="item in sidebarList"
+			v-for="item in VueFlowConfig.nodeStyleMap.values()"
 			class="cursor-grab rounded-md bg-white py-3 px-2 hover:bg-gray-100"
 			:draggable="true"
 			@dragstart="handleOnDragStart($event, item.type)"
 		>
 			<div class="flex items-center justify-between">
-				<h3 class="flex items-center gap-x-2">
-					<img :src="item.img" class="h-4 w-4" alt="LLM icon" />
+				<span class="flex items-center gap-x-2">
+					<YWIcon :name="item.icon" :fontSize="item.fontSize" :color="item.color" class="rounded-lg p-1.5" :class="item.class" />
 					{{ item.title }}
-				</h3>
+				</span>
 				<!-- <plus-icon class="text-primary" /> -->
 			</div>
 		</div>
@@ -18,9 +18,8 @@
 </template>
 
 <script setup lang="ts">
-import llmImg from '/@/components/vue-flow/ui/assets/images/icon_LLM.png';
-import startImg from '/@/components/vue-flow/ui/assets/images/icon_Start.png';
-import endImg from '/@/components/vue-flow/ui/assets/images/icon_End.png';
+import YWIcon from '/@/components/icon/index.vue';
+import { VueFlowConfig } from '/@/components/vue-flow/ui/VueFlowConfig';
 import { NodeType, nodeTypeMap } from '/@/components/vue-flow/vueFlowEnum';
 
 const emit = defineEmits(['dragstart']);
@@ -29,38 +28,5 @@
 	emit('dragstart', e, type);
 };
 
-const sidebarList = [
-	{
-		type: NodeType.Start,
-		title: nodeTypeMap[NodeType.Start],
-		img: startImg,
-	},
-	{
-		type: NodeType.End,
-		title: nodeTypeMap[NodeType.End],
-		img: endImg,
-	},
-    {
-		type: NodeType.Condition,
-		title: nodeTypeMap[NodeType.Condition],
-		img: llmImg,
-	},
-	{
-		type: NodeType.LLM,
-		title: nodeTypeMap[NodeType.LLM],
-		img: llmImg,
-	},
-	{
-		type: NodeType.Output,
-		title: nodeTypeMap[NodeType.Output],
-		img: llmImg,
-	},
-	{
-		type: NodeType.Agent,
-		title: nodeTypeMap[NodeType.Agent],
-		img: llmImg,
-	},
-
-];
 </script>
 <style scoped lang="scss"></style>
diff --git a/tailwind.config.js b/tailwind.config.js
index 847ed5a..63336e3 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -7,7 +7,7 @@
 		'light-7': `var(--${namespace}-color-${branchName}-light-7)`,
 		'light-8': `var(--${namespace}-color-${branchName}-light-8)`,
 		'light-9': `var(--${namespace}-color-${branchName}-light-9)`,
-		main: `var(--${namespace}-color-${branchName})`,
+		DEFAULT: `var(--${namespace}-color-${branchName})`,
 		'dark-2': `var(--${namespace}-color-${branchName}-dark-2)`,
 	};
 }
@@ -36,7 +36,6 @@
 				page: `var(--${namespace}-bg-color-page)`,
 				overlay: `var(--${namespace}-bg-color-overlay)`,
 				disabled: `var(--${namespace}-disabled-bg-color)`,
-				gray: `#f4f5f8`,
 
 			},
 			textColor: {

--
Gitblit v1.9.3