From 59951a7031d50c8528764c624628886c31a6ea2c Mon Sep 17 00:00:00 2001
From: wujingjing <gersonwu@qq.com>
Date: 星期五, 13 十二月 2024 16:35:51 +0800
Subject: [PATCH] 完全对接JSON,添加描述

---
 src/components/vue-flow/ui/nodes/FuncNode.vue                   |  110 ----
 vite.config.ts                                                  |    2 
 src/components/vue-flow/ui/nodes/components/NodeBasicLayout.vue |   92 ++++
 src/components/vue-flow/ui/nodes/AgentNode.vue                  |  104 +----
 src/components/vue-flow/ui/nodes/EndNode.vue                    |   84 ---
 src/components/vue-flow/VueFlowHelper.ts                        |   49 ++
 src/components/vue-flow/ui/nodes/OutputNode.vue                 |  191 ++------
 src/components/vue-flow/ui/nodes/ConditionNode.vue              |  217 +++-------
 src/components/vue-flow/ui/nodes/components/FieldLayout.vue     |   38 +
 src/components/vue-flow/ui/nodes/LLMNode.vue                    |  129 +----
 src/components/vue-flow/ui/nodes/StartNode.vue                  |  177 ++------
 11 files changed, 443 insertions(+), 750 deletions(-)

diff --git a/src/components/vue-flow/VueFlowHelper.ts b/src/components/vue-flow/VueFlowHelper.ts
index d63c9f2..bda2965 100644
--- a/src/components/vue-flow/VueFlowHelper.ts
+++ b/src/components/vue-flow/VueFlowHelper.ts
@@ -19,6 +19,10 @@
 		};
 		switch (type) {
 			case NodeType.Start:
+				data = {
+					...data,
+					description: '宸ヤ綔娴佽繍琛岀殑璧峰鑺傜偣銆�',
+				};
 				data[VueFlowConstant.GROUP_PARAMS_KEY] = [
 					{
 						[VueFlowConstant.PARAMS_KEY]: [
@@ -32,7 +36,17 @@
 					},
 				];
 				break;
+			case NodeType.End:
+				data = {
+					...data,
+					description: '宸ヤ綔娴佽繍琛屽埌姝ょ粨鏉熴��',
+				};
+				break;
 			case NodeType.Condition:
+				data = {
+					...data,
+					description: '鏍规嵁鏉′欢琛ㄨ揪寮忔墽琛屼笉鍚岀殑鍒嗘敮銆�',
+				};
 				data[VueFlowConstant.GROUP_PARAMS_KEY] = [
 					{
 						[VueFlowConstant.PARAMS_KEY]: [
@@ -47,6 +61,10 @@
 				];
 				break;
 			case NodeType.LLM:
+				data = {
+					...data,
+					description: '璋冪敤澶фā鍨嬪洖绛旂敤鎴烽棶棰樻垨鑰呭鐞嗕换鍔°��',
+				};
 				data[VueFlowConstant.GROUP_PARAMS_KEY] = [
 					{
 						name: '妯″瀷璁剧疆',
@@ -65,7 +83,7 @@
 					},
 					{
 						name: '鎻愮ず璇�',
-						[VueFlowConstant.PARAMS_KEY]: [{ key: 'prompt', label: '鎻愮ず璇�', type: 'textarea', value: '' }],
+						[VueFlowConstant.PARAMS_KEY]: [{ key: 'prompt', label: '', type: 'textarea', value: '' }],
 					},
 				];
 				break;
@@ -73,15 +91,16 @@
 			case NodeType.Agent:
 				data[VueFlowConstant.GROUP_PARAMS_KEY] = [
 					{
+						name: '浠g悊鍚嶇О',
 						[VueFlowConstant.PARAMS_KEY]: [
 							{
 								key: 'agent',
-								label: '浠g悊',
+								label: '',
 								type: 'agent_select',
 								value: '',
 								// value_label:'',
 								required: true,
-								placeholder: '浠g悊',
+								placeholder: '浠g悊鍚嶇О',
 							},
 						],
 					},
@@ -91,10 +110,11 @@
 			case NodeType.Func:
 				data[VueFlowConstant.GROUP_PARAMS_KEY] = [
 					{
+						name: '鍑芥暟鍚嶇О',
 						[VueFlowConstant.PARAMS_KEY]: [
 							{
 								key: 'func_name',
-								label: '鍑芥暟鍚嶇О',
+								label: '',
 								type: 'func_name_select',
 								value: '',
 								// value_label:'',
@@ -106,6 +126,11 @@
 				];
 				break;
 			case NodeType.Output:
+				data = {
+					...data,
+					description:
+						'鍙悜鐢ㄦ埛鍙戦�佹秷鎭紝骞朵笖鏀寔杩涜鏇翠赴瀵岀殑浜や簰锛屼緥濡傝姹傜敤鎴锋壒鍑嗚繘琛屾煇椤规晱鎰熸搷浣溿�佸厑璁哥敤鎴峰湪妯″瀷杈撳嚭鍐呭鐨勫熀纭�涓婄洿鎺ヤ慨鏀瑰苟鎻愪氦銆�',
+				};
 				data[VueFlowConstant.GROUP_PARAMS_KEY] = [
 					{
 						[VueFlowConstant.PARAMS_KEY]: [
@@ -137,6 +162,22 @@
 		return data;
 	};
 
+	/**
+	 * 寮哄埗鍐欏叆鍒伴厤缃俊鎭腑锛岀敤浜� patch 杩囧幓鐗堟湰涓病鏈夌殑瀛楁锛屾垨瀛楁鍚嶇О宸茬粡淇敼
+	 *
+	 * 鐗堟湰绋冲畾鍚庡彲鍒犻櫎姝ゆ柟娉�
+	 * @param obj
+	 * @param key
+	 * @param val
+	 */
+	static getConfigValue = (obj: any, key: string, val: string) => {
+		const value = obj[key];
+		if (!value || value !== val) {
+			obj[key] = val;
+		}
+		return val;
+	};
+
 	static getHandleId = (node: any, handleType: HandleType, order?: number) => {
 		const orderSuffix = order == undefined ? '' : `__${order + ''}`;
 		return `${node.id}__handle-${handleType}${orderSuffix}`;
diff --git a/src/components/vue-flow/ui/nodes/AgentNode.vue b/src/components/vue-flow/ui/nodes/AgentNode.vue
index e223c35..4ff3d37 100644
--- a/src/components/vue-flow/ui/nodes/AgentNode.vue
+++ b/src/components/vue-flow/ui/nodes/AgentNode.vue
@@ -1,81 +1,38 @@
 <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"
+	<NodeBasicLayout
+		v-model:title="data.title"
+		:type="NodeType.Agent"
+		:description="VueFlowHelper.getConfigValue(data, 'description', '')"
 	>
 		<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">浠g悊鍚嶇О</div>
+		<FieldLayout :title="VueFlowHelper.getConfigValue(VueFlowHelper.getGroupParam(data), 'name', '浠g悊鍚嶇О')">
+			<el-select
+				class="w-[340px]"
+				filterable
+				:placeholder="VueFlowHelper.getConfigValue(agentParams, 'placeholder', '浠g悊鍚嶇О')"
+				v-model="agentParams.value"
+				@change="agentParamsValueChange"
+			>
+				<el-option v-for="item in agentNames" :key="item.id" :value="item.id" :label="item.title"></el-option>
+			</el-select>
+		</FieldLayout>
 
-				<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="璇烽�夋嫨浠g悊鍚嶇О"  v-model="agentParams.value" @change="agentParamsValueChange">
-							<el-option v-for="item in agentNames"  :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> -->
+	</NodeBasicLayout>
 </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';
 
+import FieldLayout from './components/FieldLayout.vue';
+import NodeBasicLayout from './components/NodeBasicLayout.vue';
+import { NodeType } from '../../vueFlowEnum';
 // defineProps<NodeProps<LLMNodeData, LLMNodeEvents>>();
 
-const props = defineProps(['agentNames'])
+const props = defineProps(['agentNames']);
 const agentParamsValueChange = () => {
 	// const foundNames = props.agentNames.find(item=>item.id == agentParams.value);
 	// agentParams.value.value_label =  foundNames?.title ??'';
@@ -86,27 +43,8 @@
 
 const data = ref(node.node.data);
 
-const varList = ref(VueFlowHelper.getFieldValue(data.value, 'var_list'));
-const titleIsEdit = ref(false);
-
 const agentParams = ref(VueFlowHelper.getParams(VueFlowHelper.getGroupParam(data.value), 'agent'));
+VueFlowHelper.getConfigValue(agentParams.value, 'label', '');
 
-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/ConditionNode.vue b/src/components/vue-flow/ui/nodes/ConditionNode.vue
index eeb4c1d..d6f82b2 100644
--- a/src/components/vue-flow/ui/nodes/ConditionNode.vue
+++ b/src/components/vue-flow/ui/nodes/ConditionNode.vue
@@ -1,143 +1,97 @@
 <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"
-	>
+	<NodeBasicLayout v-model:title="data.title" :type="NodeType.Condition" :description="VueFlowHelper.getConfigValue(data, 'description', '鏍规嵁鏉′欢琛ㄨ揪寮忔墽琛屼笉鍚岀殑鍒嗘敮銆�')">
 		<Handle :id="leftId" 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"
+		<FieldLayout
+			v-for="(item, index) in conditionGroupList"
+			:key="item.id"
+			class="relative group/conditionGroup"
+			:title="item.conditions ? '濡傛灉' : '鍚﹀垯'"
 		>
-			<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 min-w-[400px]">
-			<div class="flex justify-between flex-0">
-				<div class="flex items-center gap-x-2">
-					<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>
-					</div>
-				</div>
-			</div>
+			<template #right v-if="item.conditions">
+				<el-select v-model="item.operator" class="w-[130px]">
+					<el-option
+						v-for="item in Object.keys(conditionOperatorMap)"
+						:key="item"
+						:value="item"
+						:label="conditionOperatorMap[item]"
+					></el-option>
+				</el-select>
+				<!-- v-if="conditionGroupList.length > 1" -->
 
-			<div class="flex-auto !overflow-x-auto gap-y-2 flex-col flex nodrag">
-				<div v-for="(item, index) in conditionGroupList" :key="item.id" class="flex flex-col gap-3 relative group/conditionGroup">
-					<div class="flex-items-center justify-between">
-						<div class="text-lg font-bold">{{ item.conditions ? '濡傛灉' : '鍚﹀垯' }}</div>
-						<div class="flex-items-center gap-3" v-if="item.conditions">
-							<el-select v-model="item.operator" class="w-[130px]">
-								<el-option
-									v-for="item in Object.keys(conditionOperatorMap)"
-									:key="item"
-									:value="item"
-									:label="conditionOperatorMap[item]"
-								></el-option>
-							</el-select>
-							<!-- v-if="conditionGroupList.length > 1" -->
+				<span
+					class="ywifont ywicon-shanchu text-red-400 invisible group-hover/conditionGroup:visible cursor-pointer"
+					@click="delConditionBranch(index)"
+				></span>
+			</template>
 
-							<span
-								class="ywifont ywicon-shanchu text-red-400 invisible group-hover/conditionGroup:visible cursor-pointer"
-								@click="delConditionBranch(index)"
-							></span>
-						</div>
-					</div>
+			<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 filterable v-model="subItem.comparison_operation" class="flex-0 w-[120px]" placeholder="閫夋嫨鏉′欢">
+						<el-option
+							v-for="item in Object.keys(compareOperationMap)"
+							:key="item"
+							:value="item"
+							:label="compareOperationMap[item]"
+						></el-option>
+					</el-select>
 
-					<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 filterable v-model="subItem.comparison_operation" class="flex-0 w-[120px]" placeholder="閫夋嫨鏉′欢">
-								<el-option
-									v-for="item in Object.keys(compareOperationMap)"
-									:key="item"
-									:value="item"
-									:label="compareOperationMap[item]"
-								></el-option>
-							</el-select>
-
-							<!-- <el-select v-model="subItem.right_value_type" class="flex-0 w-[90px]" placeholder="璇烽�夋嫨">
+					<!-- <el-select v-model="subItem.right_value_type" class="flex-0 w-[90px]" placeholder="璇烽�夋嫨">
 								<el-option v-for="item in Object.keys(varTypeMap)" :key="item" :value="item" :label="varTypeMap[item]"></el-option>
 							</el-select> -->
-							<template v-if="isShowRight(subItem)">
-								<el-input
-									v-if="subItem.right_value_type === VarType.Input"
-									v-model="subItem.right_value"
-									class="w-[180px]"
-									placeholder="杈撳叆鍊�"
-								>
-								</el-input>
+					<template v-if="isShowRight(subItem)">
+						<el-input
+							v-if="subItem.right_value_type === VarType.Input"
+							v-model="subItem.right_value"
+							class="w-[180px]"
+							placeholder="杈撳叆鍊�"
+						>
+						</el-input>
 
-								<el-tree-select
-									v-else
-									filterable
-									class="w-[120px] flex-0"
-									v-model="subItem.right_value"
-									:data="treeReferOptions"
-									node-key="id"
-									:clearable="true"
-									:accordion="false"
-									:expandNode="false"
-									:check-strictly="false"
-									placeholder="閫夋嫨鍙橀噺"
-								>
-								</el-tree-select>
-							</template>
+						<el-tree-select
+							v-else
+							filterable
+							class="w-[120px] flex-0"
+							v-model="subItem.right_value"
+							:data="treeReferOptions"
+							node-key="id"
+							:clearable="true"
+							:accordion="false"
+							:expandNode="false"
+							:check-strictly="false"
+							placeholder="閫夋嫨鍙橀噺"
+						>
+						</el-tree-select>
+					</template>
 
-							<span
-								class="ywifont ywicon-shanchu text-red-400 invisible group-hover/conditionItem:visible cursor-pointer"
-								@click="delConditionItem(item, subIndex)"
-							></span>
-						</div>
-						<el-button class="w-fit mt-3" type="primary" @click="addConditionItem(item)">娣诲姞鏉′欢</el-button>
-					</div>
-					<el-button v-else @click="addConditionBranch" class="w-fit mt-3" type="success">娣诲姞鍒嗘敮</el-button>
-					<Handle class="!right-0 !-translate-y-4" :id="item.id" type="source" :position="Position.Right" />
+					<span
+						class="ywifont ywicon-shanchu text-red-400 invisible group-hover/conditionItem:visible cursor-pointer"
+						@click="delConditionItem(item, subIndex)"
+					></span>
 				</div>
+				<el-button class="w-fit mt-3" type="primary" @click="addConditionItem(item)">娣诲姞鏉′欢</el-button>
 			</div>
-		</div>
-		<!-- <Handle :id="handleId" type="source" :position="Position.Right" /> -->
-	</div>
+			<el-button v-else @click="addConditionBranch" class="w-fit mt-3" type="success">娣诲姞鍒嗘敮</el-button>
+			<Handle class="!right-0 !-translate-y-4" :id="item.id" type="source" :position="Position.Right" />
+		</FieldLayout>
+	</NodeBasicLayout>
 </template>
 
 <script lang="ts" setup>
 import type { NodeProps } from '@vue-flow/core';
 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 } from '../../vueFlowEnum';
+import { CompareOperation, NodeType, VarType, compareOperationMap, conditionOperatorMap } from '../../vueFlowEnum';
 import { LLMNodeData, LLMNodeEvents } from './index';
-import { deepClone } from '/@/utils/other';
-import { VueFlowConfig } from '../VueFlowConfig';
-import { NodeType } from '../../vueFlowEnum';
 
+import FieldLayout from './components/FieldLayout.vue';
+import NodeBasicLayout from './components/NodeBasicLayout.vue';
 defineProps<NodeProps<LLMNodeData, LLMNodeEvents>>();
-
 const node = useNode();
 const leftId = VueFlowHelper.getHandleId(node, 'target');
 const { findNode } = useVueFlow();
 const referenceOptions = ref([]);
-const otherHandleId = VueFlowHelper.genId();
 
 const isShowRight = (item) => {
 	const yes = ![CompareOperation.empty, CompareOperation.notEmpty].includes(item.comparison_operation);
@@ -203,42 +157,9 @@
 const data = ref(node.node.data);
 
 const conditionGroupList = ref(VueFlowHelper.getFieldValue(data.value, 'condition'));
-const titleIsEdit = ref(false);
 
-function handleClickAddBtn() {
-	if (!data.value[VueFlowConstant.GROUP_PARAMS_KEY]) {
-		data.value[VueFlowConstant.GROUP_PARAMS_KEY] = [];
-	}
+const { removeEdges } = useVueFlow();
 
-	data.value[VueFlowConstant.GROUP_PARAMS_KEY].push({
-		name: '',
-		description: '',
-		type: '',
-		isRequired: true,
-	});
-}
-
-function handleClickDeleteBtn(index: number) {
-	data.value[VueFlowConstant.GROUP_PARAMS_KEY].splice(index, 1);
-}
-const { removeNodes, removeEdges, 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);
-};
 const removeRelativeHandleEdge = (handleId: string) => {
 	const edges = node.connectedEdges.value.filter((item) => item.sourceHandle === handleId || item.targetHandle === handleId);
 	removeEdges(edges);
diff --git a/src/components/vue-flow/ui/nodes/EndNode.vue b/src/components/vue-flow/ui/nodes/EndNode.vue
index 7d5e3d5..0390bfb 100644
--- a/src/components/vue-flow/ui/nodes/EndNode.vue
+++ b/src/components/vue-flow/ui/nodes/EndNode.vue
@@ -1,94 +1,28 @@
 <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"
-	>
+	<div>
 		<Handle :id="handleId" 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"
+		<NodeBasicLayout
+			v-model:title="data.title"
+			:type="NodeType.End"
+			:description="VueFlowHelper.getConfigValue(data, 'description', '宸ヤ綔娴佽繍琛屽埌姝ょ粨鏉熴��')"
 		>
-			<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.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>
-					</div>
-				</div>
-			</div>
-
-			<p>宸ヤ綔娴佽繍琛屽埌姝ょ粨鏉�</p>
-		</div>
+		</NodeBasicLayout>
 	</div>
-
-	<!-- <div>
-		<Handle type="target" :position="Position.Left" />
-		<span>缁撴潫</span>
-	</div> -->
 </template>
 
 <script lang="ts" setup>
 import type { NodeProps } from '@vue-flow/core';
 import { Handle, Position, useNode, useVueFlow } from '@vue-flow/core';
-import { computed, ref } from 'vue';
-import { VueFlowConstant } from '../../VueFlowConstant';
+import { ref } from 'vue';
 import { VueFlowHelper } from '../../VueFlowHelper';
-import { VueFlowConfig } from '../VueFlowConfig';
 import { NodeType } from '../../vueFlowEnum';
 
 import { LLMNodeData, LLMNodeEvents } from './index';
-import { deepClone } from '/@/utils/other';
+import NodeBasicLayout from './components/NodeBasicLayout.vue';
 defineProps<NodeProps<LLMNodeData, LLMNodeEvents>>();
-	
+
 const node = useNode();
 const handleId = ref(VueFlowHelper.getHandleId(node.node, 'target'));
 
 const data = ref(node.node.data);
-const titleIsEdit = ref(false);
-const parameterTable = computed(() => data.value[VueFlowConstant.GROUP_PARAMS_KEY]);
-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);
-};
-
-// const { clickDeleteBtn, handleClickDuplicateBtn } = useNodeOpt(node);
 </script>
diff --git a/src/components/vue-flow/ui/nodes/FuncNode.vue b/src/components/vue-flow/ui/nodes/FuncNode.vue
index 37c0d4d..8a32672 100644
--- a/src/components/vue-flow/ui/nodes/FuncNode.vue
+++ b/src/components/vue-flow/ui/nodes/FuncNode.vue
@@ -1,112 +1,40 @@
 <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"
-	>
+	<NodeBasicLayout v-model:title="data.title"  :type="NodeType.Func" :description="VueFlowHelper.getConfigValue(data, 'description', '')">
 		<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>
+		<FieldLayout :title="VueFlowHelper.getConfigValue(VueFlowHelper.getGroupParam(data), 'name', '鍑芥暟鍚嶇О')">
+			<el-select
+				class="w-[340px]"
+				filterable
+				:placeholder="funcNameParams.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>
+		</FieldLayout>
 		<Handle :id="sourceHandleId" type="source" :position="Position.Right" />
-	</div>
-
-	<!-- <div>
-		<span>璧峰</span>
-
-		<Handle type="source" :position="Position.Right" />
-	</div> -->
+	</NodeBasicLayout>
 </template>
 
 <script lang="ts" setup>
 import { Handle, Position, useNode, useVueFlow } from '@vue-flow/core';
 import { ref } from 'vue';
+import FieldLayout from './components/FieldLayout.vue';
+import NodeBasicLayout from './components/NodeBasicLayout.vue';
+import { NodeType } from '../../vueFlowEnum';
 
 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 props = defineProps(['funcNames']);
+const agentParamsValueChange = () => {};
 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);
-};
+VueFlowHelper.getConfigValue(funcNameParams.value, 'label', '')
 </script>
diff --git a/src/components/vue-flow/ui/nodes/LLMNode.vue b/src/components/vue-flow/ui/nodes/LLMNode.vue
index c90e462..c1173ac 100644
--- a/src/components/vue-flow/ui/nodes/LLMNode.vue
+++ b/src/components/vue-flow/ui/nodes/LLMNode.vue
@@ -1,85 +1,38 @@
 <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"
+	<NodeBasicLayout
+		v-model:title="data.title"
+		:type="NodeType.LLM"
+		:description="VueFlowHelper.getConfigValue(data, 'description', '璋冪敤澶фā鍨嬪洖绛旂敤鎴烽棶棰樻垨鑰呭鐞嗕换鍔°��')"
 	>
 		<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>
+		<FieldLayout :title="VueFlowHelper.getConfigValue(modelSetting, 'name', '妯″瀷璁剧疆')">
+			<div class="w-[340px] flex-items-center gap-x-2">
+				<div class="flex-column gap-y-1.5">
+					<span class="text-gray-500">{{ VueFlowHelper.getParams(modelSetting, 'llm_model').label }}</span>
+
+					<el-select
+						v-model="VueFlowHelper.getParams(modelSetting, 'llm_model').value"
+						class="w-[150px]"
+						:placeholder="VueFlowHelper.getParams(modelSetting, 'llm_model').placeholder"
+					>
+						<el-option v-for="item in VueFlowConstant.LLM_MODEL_LIST" :key="item.value" :label="item.label" :value="item.value" />
+					</el-select>
 				</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.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>
-					</div>
+				<div class="flex-column gap-y-1.5">
+					<span class="text-gray-500">{{ VueFlowHelper.getParams(modelSetting, 'temperature').label }}</span>
+					<el-input-number
+						v-model="VueFlowHelper.getParams(modelSetting, 'temperature').value"
+						class="w-[180px]"
+						placeholder=""
+					></el-input-number>
 				</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 v-model="VueFlowHelper.getParams(modelSetting, 'llm_model').value" class="w-[150px]" placeholder="璇烽�夋嫨妯″瀷">
-							<el-option v-for="item in VueFlowConstant.LLM_MODEL_LIST" :key="item.value" :label="item.label" :value="item.value" />
-						</el-select>
-					</div>
-					<div class="flex-column gap-y-1.5">
-						<span class="text-gray-500">娓╁害</span>
-						<el-input-number
-							v-model="VueFlowHelper.getParams(modelSetting, 'temperature').value"
-							class="w-[180px]"
-							placeholder="娓╁害"
-						></el-input-number>
-					</div>
-				</div>
-			</div>
-
-			<div class="flex-auto gap-y-2 flex-col flex nodrag w-full">
-				<div class="text-lg font-bold">鎻愮ず璇�</div>
-
-				<div class="flex-column gap-y-1.5 w-full">
-					<!-- <span class="text-gray-500">妯″瀷</span> -->
-					<el-input v-model="VueFlowHelper.getParams(prompt, 'prompt').value" type="textarea" :rows="3" autocomplete="off"> </el-input>
-				</div>
-			</div>
-		</div>
+		</FieldLayout>
+		<FieldLayout :title="prompt.name">
+			<el-input v-model="VueFlowHelper.getParams(prompt, 'prompt').value" type="textarea" :rows="3" autocomplete="off"> </el-input>
+		</FieldLayout>
 		<Handle :id="sourceHandleId" type="source" :position="Position.Right" />
-	</div>
-
-	<!-- <div>
-		<span>璧峰</span>
-
-		<Handle type="source" :position="Position.Right" />
-	</div> -->
+	</NodeBasicLayout>
 </template>
 
 <script lang="ts" setup>
@@ -92,7 +45,9 @@
 import { LLMNodeData, LLMNodeEvents } from './index';
 import { deepClone } from '/@/utils/other';
 import { NodeType } from '../../vueFlowEnum';
-import { VueFlowConfig } from '../VueFlowConfig';
+
+import FieldLayout from './components/FieldLayout.vue';
+import NodeBasicLayout from './components/NodeBasicLayout.vue';
 
 defineProps<NodeProps<LLMNodeData, LLMNodeEvents>>();
 
@@ -102,28 +57,10 @@
 
 const data = ref(node.node.data);
 
-const varList = ref(VueFlowHelper.getFieldValue(data.value, 'var_list'));
-const titleIsEdit = ref(false);
-
 const modelSetting = ref(VueFlowHelper.getGroupParam(data.value, 0));
 const prompt = ref(VueFlowHelper.getGroupParam(data.value, 1));
 
-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);
-};
+
+VueFlowHelper.getConfigValue(VueFlowHelper.getParams(prompt.value, 'prompt'), 'label', '');
 </script>
diff --git a/src/components/vue-flow/ui/nodes/OutputNode.vue b/src/components/vue-flow/ui/nodes/OutputNode.vue
index 2d45c89..052ed66 100644
--- a/src/components/vue-flow/ui/nodes/OutputNode.vue
+++ b/src/components/vue-flow/ui/nodes/OutputNode.vue
@@ -1,134 +1,80 @@
 <template>
-	<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"
-	>
+	<NodeBasicLayout v-model:title="data.title" :type="NodeType.Output" :description="VueFlowHelper.getConfigValue(data, 'description', '鍙悜鐢ㄦ埛鍙戦�佹秷鎭紝骞朵笖鏀寔杩涜鏇翠赴瀵岀殑浜や簰锛屼緥濡傝姹傜敤鎴锋壒鍑嗚繘琛屾煇椤规晱鎰熸搷浣溿�佸厑璁哥敤鎴峰湪妯″瀷杈撳嚭鍐呭鐨勫熀纭�涓婄洿鎺ヤ慨鏀瑰苟鎻愪氦銆�')">
 		<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.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>
-					</div>
-				</div>
-			</div>
+		<FieldLayout level="2" :title="VueFlowHelper.getParams(outputParams, 'output_msg').label">
+			<el-input
+				v-model="VueFlowHelper.getParams(outputParams, 'output_msg').value.msg"
+				type="textarea"
+				:rows="4"
+				:placeholder="VueFlowHelper.getParams(outputParams, 'output_msg').placeholder"
+				autocomplete="off"
+			>
+			</el-input>
+		</FieldLayout>
+		<FieldLayout level="2" :title="VueFlowHelper.getParams(outputParams, 'output_result').label">
+			<el-radio-group v-model="VueFlowHelper.getParams(outputParams, 'output_result').value.type" @change="interactionTypeChange">
+				<el-radio v-for="item in Object.keys(interactionTypeMap)" :key="item" :value="item">{{ interactionTypeMap[item] }}</el-radio>
+			</el-radio-group>
+			<el-input
+				v-model="VueFlowHelper.getParams(outputParams, 'output_result').value.value"
+				type="textarea"
+				:rows="4"
+				v-if="VueFlowHelper.getParams(outputParams, 'output_result').value.type === InteractionType.Input"
+			>
+			</el-input>
 
-			<div class="flex-auto gap-y-2 flex-col flex nodrag w-full">
-				<!-- <div class="text-lg font-bold">鎻愮ず璇�</div> -->
-
-				<div class="flex-column gap-y-1.5 w-full">
-					<span class="self-start text-gray-500">{{ VueFlowHelper.getParams(outputParams, 'output_msg').label }}</span>
-					<el-input
-						v-model="VueFlowHelper.getParams(outputParams, 'output_msg').value.msg"
-						type="textarea"
-						:rows="4"
-						:placeholder="VueFlowHelper.getParams(outputParams, 'output_msg').placeholder"
-						autocomplete="off"
-					>
-					</el-input>
-				</div>
-				<div class="flex-column gap-y-1.5 w-full">
-					<span class="self-start text-gray-500">{{ VueFlowHelper.getParams(outputParams, 'output_result').label }}</span>
-
-					<el-radio-group v-model="VueFlowHelper.getParams(outputParams, 'output_result').value.type" @change="interactionTypeChange">
-						<el-radio v-for="item in Object.keys(interactionTypeMap)" :key="item" :value="item">{{
-							interactionTypeMap[item]
-						}}</el-radio>
-					</el-radio-group>
-					<el-input
-						v-model="VueFlowHelper.getParams(outputParams, 'output_result').value.value"
-						type="textarea"
-						:rows="4"
-						v-if="VueFlowHelper.getParams(outputParams, 'output_result').value.type === InteractionType.Input"
-					>
-					</el-input>
-
+			<div
+				class="self-start w-full flex-col flex items-start gap-2"
+				v-else-if="VueFlowHelper.getParams(outputParams, 'output_result').value.type === InteractionType.Select"
+			>
+				<div class="flex-column gap-2">
 					<div
-						class="self-start w-full flex-col flex items-start gap-2"
-						v-else-if="VueFlowHelper.getParams(outputParams, 'output_result').value.type === InteractionType.Select"
+						class="text-gray-400 cursor-not-allowed border border-solid rounded-lg border-gray-300 py-3 pl-3 pr-2 items-center justify-between flex group/option relative mr-6"
+						:key="item.id"
+						v-for="(item, index) in VueFlowHelper.getParams(outputParams, 'output_result').options"
 					>
-						<div class="flex-column gap-2">
-							<div
-								class="text-gray-400 cursor-not-allowed border border-solid rounded-lg border-gray-300 py-3 pl-3 pr-2 items-center justify-between flex group/option relative mr-6"
-								:key="item.id"
-								v-for="(item, index) in VueFlowHelper.getParams(outputParams, 'output_result').options"
-							>
-								<span>{{ item.label }}</span>
-								<span
-									@click="delOption(index)"
-									class="cursor-pointer group-hover/option:visible invisible ywifont ywicon-shanchu text-red-500"
-								></span>
-								<Handle class="absolute !-right-4" :id="item.id" type="source" :position="Position.Right" />
-							</div>
-						</div>
-
-						<div v-if="isEditStatus" class="flex-items-center w-full">
-							<el-input class="flex-auto" v-model="tmpEditValue"></el-input>
-							<el-button class="flex-0 ml-2" type="success" @click="confirmClick">纭畾 </el-button>
-						</div>
-						<el-button v-else type="primary" @click="addOptionClick">娣诲姞閫夐」</el-button>
+						<span>{{ item.label }}</span>
+						<span
+							@click="delOption(index)"
+							class="cursor-pointer group-hover/option:visible invisible ywifont ywicon-shanchu text-red-500"
+						></span>
+						<Handle class="absolute !-right-4" :id="item.id" type="source" :position="Position.Right" />
 					</div>
 				</div>
+
+				<div v-if="isEditStatus" class="flex-items-center w-full">
+					<el-input class="flex-auto" v-model="tmpEditValue"></el-input>
+					<el-button class="flex-0 ml-2" type="success" @click="confirmClick">纭畾 </el-button>
+				</div>
+				<el-button v-else type="primary" @click="addOptionClick">娣诲姞閫夐」</el-button>
 			</div>
-		</div>
-		<Handle
-			v-if="
-				VueFlowHelper.getParams(outputParams, 'output_result').value.type === InteractionType.Input ||
-				VueFlowHelper.getParams(outputParams, 'output_result').value.type === InteractionType.None
-			"
-			:id="sourceHandleId"
-			type="source"
-			:position="Position.Right"
-		/>
-	</div>
-
-	<!-- <div>
-		<span>璧峰</span>
-
-		<Handle type="source" :position="Position.Right" />
-	</div> -->
+			<Handle
+				v-if="
+					VueFlowHelper.getParams(outputParams, 'output_result').value.type === InteractionType.Input ||
+					VueFlowHelper.getParams(outputParams, 'output_result').value.type === InteractionType.None
+				"
+				:id="sourceHandleId"
+				type="source"
+				:position="Position.Right"
+			/>
+		</FieldLayout>
+	</NodeBasicLayout>
 </template>
 
 <script lang="ts" setup>
 import { Handle, Position, useNode, useVueFlow } from '@vue-flow/core';
 import { ref } from 'vue';
+import { NodeType } from '../../vueFlowEnum';
 
 import type { NodeProps } from '@vue-flow/core';
 import { nextTick } from 'vue';
 import { VueFlowHelper } from '../../VueFlowHelper';
 import { InteractionType, interactionTypeMap } from '../../vueFlowEnum';
 import { LLMNodeData, LLMNodeEvents } from './index';
-import { deepClone } from '/@/utils/other';
-import { VueFlowConfig } from '../VueFlowConfig';
-import { NodeType } from '../../vueFlowEnum';defineProps<NodeProps<LLMNodeData, LLMNodeEvents>>();
+
+import NodeBasicLayout from './components/NodeBasicLayout.vue';
+import FieldLayout from './components/FieldLayout.vue';
+defineProps<NodeProps<LLMNodeData, LLMNodeEvents>>();
 
 const node = useNode();
 const sourceHandleId = ref(VueFlowHelper.getHandleId(node.node, 'source'));
@@ -136,28 +82,10 @@
 
 const data = ref(node.node.data);
 
-const titleIsEdit = ref(false);
-
 const outputParams = ref(VueFlowHelper.getGroupParam(data.value, 0));
 
-const { removeNodes, nodes, addNodes, removeEdges } = useVueFlow();
+const { removeEdges } = 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);
-};
 const addOptionClick = () => {
 	isEditStatus.value = true;
 };
@@ -202,7 +130,6 @@
 		}
 	}
 	preInteractionType = type;
-
 };
 
 const isEditStatus = ref(false);
diff --git a/src/components/vue-flow/ui/nodes/StartNode.vue b/src/components/vue-flow/ui/nodes/StartNode.vue
index 9f2ebe2..462cd7d 100644
--- a/src/components/vue-flow/ui/nodes/StartNode.vue
+++ b/src/components/vue-flow/ui/nodes/StartNode.vue
@@ -1,99 +1,55 @@
 <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"
+	<NodeBasicLayout
+		v-model:title="data.title"
+		:type="NodeType.Start"
+		:description="VueFlowHelper.getConfigValue(data, 'description', '宸ヤ綔娴佽繍琛岀殑璧峰鑺傜偣銆�')"
+		maxWidth="540"
 	>
-		<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.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"
+		<FieldLayout :title="VueFlowHelper.getConfigValue(varListConfig, 'name', '娴佺▼鍙傛暟')">
+			<el-table class="flex-auto" :data="varList" border>
+				<el-table-column prop="name" width="90" label="鍙傛暟鍚�" fixed>
+					<template #default="scope">
+						<el-input v-model="scope.row.name"></el-input>
+					</template>
+				</el-table-column>
+				<el-table-column prop="type" width="120" label="绫诲瀷">
+					<template #default="scope">
+						<el-select v-model="scope.row.type">
+							<el-option
+								v-for="item in Object.keys(parameterTypeMap)"
+								:key="item"
+								:value="item"
+								:label="parameterTypeMap[item]"
+							></el-option>
+						</el-select>
+					</template>
+				</el-table-column>
+				<el-table-column prop="description" width="180" label="鎻忚堪">
+					<template #default="scope">
+						<el-input type="textarea" :rows="1" v-model="scope.row.description"></el-input>
+					</template>
+				</el-table-column>
+				<el-table-column prop="isRequired" width="56" label="蹇呭~">
+					<template #default="scope">
+						<el-checkbox v-model="scope.row.isRequired"></el-checkbox>
+					</template>
+				</el-table-column>
 
-					/>
-					<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>
-				<el-table size="small" class="flex-auto" :data="varList" border>
-					<el-table-column prop="name" width="90" label="鍙傛暟鍚�" fixed>
-						<template #default="scope">
-							<el-input v-model="scope.row.name"></el-input>
-						</template>
-					</el-table-column>
-					<el-table-column prop="type" width="120" label="绫诲瀷">
-						<template #default="scope">
-							<el-select v-model="scope.row.type">
-								<el-option
-									v-for="item in Object.keys(parameterTypeMap)"
-									:key="item"
-									:value="item"
-									:label="parameterTypeMap[item]"
-								></el-option>
-							</el-select>
-						</template>
-					</el-table-column>
-					<el-table-column prop="description" width="180" label="鎻忚堪">
-						<template #default="scope">
-							<el-input type="textarea" :rows="1" v-model="scope.row.description"></el-input>
-						</template>
-					</el-table-column>
-					<el-table-column prop="isRequired" width="56" label="蹇呭~">
-						<template #default="scope">
-							<el-checkbox v-model="scope.row.isRequired"></el-checkbox>
-						</template>
-					</el-table-column>
-
-					<el-table-column label="鎿嶄綔" width="55" fixed="right">
-						<template #default="scope">
-							<el-tooltip effect="dark" content="鍒犻櫎" placement="top">
-								<i
-									class="ywifont ywicon-shanchu !text-[17px] text-red-400 cursor-pointer"
-									@click="handleClickDeleteBtn(scope.$index)"
-								></i>
-							</el-tooltip>
-						</template>
-					</el-table-column>
-				</el-table>
-				<el-button class="w-fit" type="primary" @click="handleClickAddBtn">娣诲姞鍙傛暟</el-button>
-			</div>
-		</div>
+				<el-table-column label="鎿嶄綔" width="55" fixed="right">
+					<template #default="scope">
+						<el-tooltip effect="dark" content="鍒犻櫎" placement="top">
+							<i
+								class="ywifont ywicon-shanchu !text-[17px] text-red-400 cursor-pointer"
+								@click="handleClickDeleteBtn(scope.$index)"
+							></i>
+						</el-tooltip>
+					</template>
+				</el-table-column>
+			</el-table>
+			<el-button class="w-fit" type="primary" @click="handleClickAddBtn">娣诲姞鍙傛暟</el-button>
+		</FieldLayout>
 		<Handle :id="handleId" type="source" :position="Position.Right" />
-	</div>
-
-	<!-- <div>
-		<span>璧峰</span>
-
-		<Handle type="source" :position="Position.Right" />
-	</div> -->
+	</NodeBasicLayout>
 </template>
 
 <script lang="ts" setup>
@@ -101,26 +57,19 @@
 import { ref } from 'vue';
 
 import type { NodeProps } from '@vue-flow/core';
-import { VueFlowConstant } from '../../VueFlowConstant';
 import { VueFlowHelper } from '../../VueFlowHelper';
 import { NodeType, parameterTypeMap } from '../../vueFlowEnum';
-import { VueFlowConfig } from '../VueFlowConfig';
 import { LLMNodeData, LLMNodeEvents } from './index';
-import { deepClone } from '/@/utils/other';
+
+import NodeBasicLayout from './components/NodeBasicLayout.vue';
+import FieldLayout from './components/FieldLayout.vue';
 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'
-	).value;
-	return varList;
-};
 
 const varList = ref(VueFlowHelper.getFieldValue(data.value, 'var_list'));
-const titleIsEdit = ref(false);
 
 function handleClickAddBtn() {
 	varList.value.push({
@@ -130,26 +79,14 @@
 		isRequired: true,
 	});
 }
+const varListConfig = ref(VueFlowHelper.getGroupParam(data.value, 0));
+
+const varListParam = ref(VueFlowHelper.getParams(varListConfig.value, 'var_list'));
+
+
+VueFlowHelper.getConfigValue(varListParam.value, 'label', '');
 
 function handleClickDeleteBtn(index: number) {
 	varList.value.splice(index, 1);
 }
-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/components/FieldLayout.vue b/src/components/vue-flow/ui/nodes/components/FieldLayout.vue
new file mode 100644
index 0000000..ae4e5cc
--- /dev/null
+++ b/src/components/vue-flow/ui/nodes/components/FieldLayout.vue
@@ -0,0 +1,38 @@
+<template>
+	<div class="flex flex-col" :class="[`gap-${gapLen}`]">
+		<div class="flex-items-center justify-between">
+			<div class="text-base " :class="[level==='1' ? 'font-bold':'text-secondary']">{{ title }}</div>
+			<div class="flex-items-center gap-3" v-if="$slots.right">
+				<slot name="right"></slot>
+			</div>
+		</div>
+
+		<slot></slot>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue';
+
+type HeaderLevel = '1' | '2';
+
+const props = defineProps({
+	title: {
+		type: String,
+	},
+	/** @description 鏍囬绛夌骇 */
+	level: {
+		type: String as () => HeaderLevel,
+		default: '1',
+	},
+});
+
+const gapLen = computed(() => {
+	if (props.level === '1') {
+		return '2';
+	} else {
+		return '2';
+	}
+});
+</script>
+<style scoped lang="scss"></style>
diff --git a/src/components/vue-flow/ui/nodes/components/NodeBasicLayout.vue b/src/components/vue-flow/ui/nodes/components/NodeBasicLayout.vue
new file mode 100644
index 0000000..facc720
--- /dev/null
+++ b/src/components/vue-flow/ui/nodes/components/NodeBasicLayout.vue
@@ -0,0 +1,92 @@
+<template>
+	<div
+		class="order-2 rounded-lg border-solid border-gray-100 bg-white p-3 shadow-md relative hover:border-blue-500 group"
+		:style="{ maxWidth: `${maxWidth}px` }"
+	>
+		<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 flex-col flex-0 gap-1.5 border ">
+				<div class="flex items-center gap-x-2">
+					<YWIcon
+						:name="VueFlowConfig.nodeStyleMap.get(type).icon"
+						:fontSize="VueFlowConfig.nodeStyleMap.get(type).fontSize"
+						:color="VueFlowConfig.nodeStyleMap.get(type).color"
+						class="rounded-lg p-1.5 h-fit"
+						:class="VueFlowConfig.nodeStyleMap.get(type).class"
+					/>
+					<p v-if="!titleIsEdit" class="text-medium font-bold" @click="titleIsEdit = true">{{ title }}</p>
+					<el-input v-elInputFocus="false" v-else v-model="title" @blur="() => (titleIsEdit = false)"></el-input>
+				</div>
+				<p v-if="description" class="text-extra-small text-secondary">
+					{{ description }}
+				</p>
+			</div>
+
+			<div v-if="$slots.default" class="flex-auto !overflow-x-auto gap-y-2 flex-col flex nodrag border border-solid border-x-0 border-b-0 pt-3 border-color-default">
+				<slot></slot>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script setup lang="ts">
+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';
+import { PropType } from 'vue';
+
+const titleIsEdit = ref(false);
+
+const props = defineProps({
+	maxWidth: { type: String, default: '520' },
+	type: { type: String as PropType<NodeType>, required: true },
+	description: { type: String },
+});
+
+const title = defineModel('title', {
+	type: String,
+});
+const { removeNodes, nodes, addNodes } = useVueFlow();
+const node = useNode();
+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>
+<style scoped lang="scss"></style>
diff --git a/vite.config.ts b/vite.config.ts
index fa15255..a568226 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -45,7 +45,7 @@
 			host: '0.0.0.0',
 			port: env.VITE_PORT as unknown as number,
 			open: JSON.parse(env.VITE_OPEN),
-			hmr: true,
+			hmr: false,
 			proxy: {
 				'/gitee': {
 					target: 'https://gitee.com',

--
Gitblit v1.9.3