wujingjing
2025-02-18 67e50265b5cdae1c28f39b924ccb277857f4acea
一些必填
已修改13个文件
已添加1个文件
952 ■■■■■ 文件已修改
src/components/input/codeEditor/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/vue-flow/MainCanvas.vue 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/vue-flow/VueFlowHelper.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/vue-flow/ui/nodes/AgentNode.vue 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/vue-flow/ui/nodes/CodeNode.vue 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/vue-flow/ui/nodes/ConditionNode.vue 248 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/vue-flow/ui/nodes/LLMNode.vue 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/vue-flow/ui/nodes/OutputNode.vue 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/vue-flow/ui/nodes/PythonCodeNode.vue 89 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/vue-flow/ui/nodes/StartNode.vue 88 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/vue-flow/ui/nodes/TextResourceNode.vue 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/vue-flow/ui/nodes/utils.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/yw/systemManage/flowApp/FlowApp.vue 65 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/yw/systemManage/flowApp/components/Header.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/input/codeEditor/index.vue
@@ -24,7 +24,7 @@
                
                </el-tooltip> -->
        <YWIcon
            class="absolute right-1 top-1 cursor-pointer"
            class="absolute right-4 top-1 cursor-pointer"
            :class="[defaultLanguage !== 'text' ? '!text-gray-300' : '!text-gray-500']"
            name="pingmufangda"
            @click="fullEditCodeClick"
@@ -48,7 +48,7 @@
</template>
<script setup lang="ts" name="CodeEditor">
import { computed, onMounted, ref, type PropType } from 'vue';
import { computed, ref, type PropType } from 'vue';
import { Codemirror } from 'vue-codemirror';
import TextEditDialog from '../../dialog/TextEditDialog/index.vue';
import type { TextType } from './types';
src/components/vue-flow/MainCanvas.vue
@@ -2,13 +2,13 @@
    <div class="relative h-full w-full" id="main-canvas" @drop="handleOnDrop" @dragover="handleOnDragOver">
        <VueFlow v-model="elements" :node-types="nodeTypes" :connection-mode="ConnectionMode.Strict">
            <template #node-start="startNodeProps">
                <StartNode ref="nodeRef" v-bind="startNodeProps" :isViewMode="isViewMode" />
                <StartNode ref="nodeRef" v-bind="startNodeProps" :isViewMode="isViewMode" @register="(ref) => registerNodeRef(startNodeProps.id, ref)" />
            </template>
            <template #node-condition="conditionNodeProps">
                <ConditionNode ref="nodeRef" v-bind="conditionNodeProps" :isViewMode="isViewMode" />
                <ConditionNode ref="nodeRef" v-bind="conditionNodeProps" :isViewMode="isViewMode" @register="(ref) => registerNodeRef(conditionNodeProps.id, ref)" />
            </template>
            <template #node-output_msg="outputNodeProps">
                <OutputNode ref="nodeRef" v-bind="outputNodeProps" :isViewMode="isViewMode" />
                <OutputNode ref="nodeRef" v-bind="outputNodeProps" :isViewMode="isViewMode" @register="(ref) => registerNodeRef(outputNodeProps.id, ref)" />
            </template>
            <template #node-end="endNodeProps">
@@ -20,20 +20,20 @@
            </template>
            <template #node-agent="agentNodeProps">
                <AgentNode ref="nodeRef" v-bind="agentNodeProps" :agentNames="agentNames" :isViewMode="isViewMode" />
                <AgentNode ref="nodeRef" v-bind="agentNodeProps" :agentNames="agentNames" :isViewMode="isViewMode" @register="(ref) => registerNodeRef(agentNodeProps.id, ref)" />
            </template>
            <template #node-func="funcNodeProps">
                <FuncNode ref="nodeRef" v-bind="funcNodeProps" :funcNames="funcNames" :isViewMode="isViewMode" />
            </template>
            <template #node-code="codeNodeProps">
                <CodeNode ref="nodeRef" v-bind="codeNodeProps" :isViewMode="isViewMode" />
                <CodeNode ref="nodeRef" v-bind="codeNodeProps" :isViewMode="isViewMode" @register="(ref) => registerNodeRef(codeNodeProps.id, ref)" />
            </template>
            <template #node-python_code="pythonCodeNodeProps">
                <PythonCodeNode ref="nodeRef" v-bind="pythonCodeNodeProps" :isViewMode="isViewMode" />
                <PythonCodeNode ref="nodeRef" v-bind="pythonCodeNodeProps" :isViewMode="isViewMode" @register="(ref) => registerNodeRef(pythonCodeNodeProps.id, ref)" />
            </template>
            <template #node-text_resource="textResourceNodeProps">
                <TextResourceNode ref="nodeRef" v-bind="textResourceNodeProps" :isViewMode="isViewMode" />
                <TextResourceNode ref="nodeRef" v-bind="textResourceNodeProps" :isViewMode="isViewMode" @register="(ref) => registerNodeRef(textResourceNodeProps.id, ref)" />
            </template>
            <template #node-analysis="analysisNodeProps">
@@ -41,7 +41,12 @@
            </template>
            <template #node-LLM="llmNodeProps">
                <LLMNode ref="nodeRef" v-bind="llmNodeProps" :llmInfoList="llmInfoList" :isViewMode="isViewMode" />
                <LLMNode
                    v-bind="llmNodeProps"
                    :llmInfoList="llmInfoList"
                    :isViewMode="isViewMode"
                    @register="(ref) => registerNodeRef(llmNodeProps.id, ref)"
                />
            </template>
            <Controls :showInteractive="false" />
            <Background />
@@ -190,11 +195,24 @@
    }
}
const nodeRef = ref();
const validateForm = () => {
    // nodeRef.value.validateForm();
    console.log('🚀 ~ nodeRef.value:', nodeRef.value);
const nodeRefs = ref(new Map());
const registerNodeRef = (nodeId: string, ref: any) => {
    nodeRefs.value.set(nodeId, ref);
};
const validateForm = async () => {
    const validPromises = [];
    for (const nodeRef of nodeRefs.value.values()) {
        if (nodeRef?.validateForm) {
            validPromises.push(nodeRef.validateForm());
        }
    }
    const results = await Promise.all(validPromises);
    return results;
};
defineExpose({
    validateForm,
});
src/components/vue-flow/VueFlowHelper.ts
@@ -144,7 +144,7 @@
                                    key: 'code',
                                    type: 'code',
                                    required: true,
                                    value: 'const main = (arg1, arg2) =>{\n  return {\n    result1: arg1,\n    result2: arg2\n  }\n}',
                                    value: "ar recs = args.querySQL(\n    \" SELECT * FROM guangming_data.tc_hzl_v4\"\n  + \" WHERE otime >={start_time}::timestamp + '-1 months' AND otime < {end_time}::timestamp + '1 days'\", {\n  start_time : args.getArg(\"start_time\"),\n  end_time : args.getArg(\"end_time\")\n});\n\nvar resultText = \"时间,水厂名,耗电量,出口流量\\n\";\nfor(var recIndex in recs)\n{\n   var rec = recs[recIndex];\n   resultText += rec[\"OTIME\"] + \",\" + rec[\"FACT_NAME\"] + \",\" + rec[\"VPOWER\"] + \",\" + rec[\"VFLOW\"] + \"\\n\";\n}\nargs.setArg(\"RECORD_LIST\", resultText);\n\nargs.sendTableToClent({\n  title : \"流量\",\n  columns : [\n    {\"title\":\"时间\", \"name\":\"OTIME\"},\n    {\"title\":\"水厂\", \"name\":\"FACT_NAME\"},\n    {\"title\":\"流量\", \"name\":\"VFLOW\"}\n  ],\n\n  recs     : recs\n});\n\nargs.sendChartToClient({\n  title : \"电量\",\n  col_time : \"OTIME\",\n  col_name : \"FACT_NAME\",\n  col_value: \"VPOWER\",\n  recs     : recs\n});\nargs.sendKnowledgeToClient({\n  result : args.getArg(\"num1\") + \" + \" + args.getArg(\"num2\") + \"=\" + args.getArg(\"V\")\n});",
                                    language: ['text', 'javascript'],
                                    defaultLanguage: 'javascript',
                                },
@@ -203,7 +203,7 @@
                                    key: 'code',
                                    type: 'code',
                                    required: true,
                                    value: 'const main = (arg1, arg2) =>{\n  return {\n    result1: arg1,\n    result2: arg2\n  }\n}',
                                    value: `from lib.system import *\n\nargJson = readArgJson()\nrestult = argJson['num1'] + argJson['num2']\nwriteResultText(str(restult))`,
                                    language: ['python'],
                                    defaultLanguage: 'python',
                                },
src/components/vue-flow/ui/nodes/AgentNode.vue
@@ -6,32 +6,36 @@
        :isViewMode="isViewMode"
    >
        <Handle :id="targetHandleId" type="target" :position="Position.Left" />
        <FieldLayout :title="VueFlowHelper.getConfigValue(VueFlowHelper.getGroupParam(data), 'name', '代理名称')">
            <el-select
                class="w-full"
                filterable
                :disabled="isViewMode"
                :placeholder="VueFlowHelper.getConfigValue(agentParams, 'placeholder', '代理名称')"
                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>
        <el-form ref="formRef" :model="data" :rules="formRules" label-position="right" label-width="60px" :show-message="false">
            <FieldLayout :title="VueFlowHelper.getConfigValue(VueFlowHelper.getGroupParam(data), 'name', '代理名称')">
                <el-form-item prop="group_params.0.params.0.value" labelWidth="0">
                    <el-select
                        class="w-full"
                        filterable
                        :disabled="isViewMode"
                        :placeholder="VueFlowHelper.getConfigValue(agentParams, 'placeholder', '代理名称')"
                        v-model="agentParams.value"
                        clearable
                        @change="agentParamsValueChange"
                    >
                        <el-option v-for="item in agentNames" :key="item.id" :value="item.id" :label="item.title"></el-option>
                    </el-select>
                </el-form-item>
            </FieldLayout>
        </el-form>
        <Handle :id="sourceHandleId" type="source" :position="Position.Right" />
    </NodeBasicLayout>
</template>
<script lang="ts" setup>
import { Handle, Position, useNode } from '@vue-flow/core';
import { ref } from 'vue';
import { onMounted, ref } from 'vue';
import { VueFlowHelper } from '../../VueFlowHelper';
import { NodeType } from '../../vueFlowEnum';
import FieldLayout from './components/FieldLayout.vue';
import NodeBasicLayout from './components/NodeBasicLayout.vue';
import { validateForm } from './utils';
// defineProps<NodeProps<LLMNodeData, LLMNodeEvents>>();
const props = defineProps({
@@ -41,6 +45,11 @@
        default: false,
    },
});
const emit = defineEmits<{
    (e: 'register', data: { validateForm: () => Promise<{ isValid: boolean; invalidFields?: any }> }): void;
}>();
const agentParamsValueChange = () => {
    // const foundNames = props.agentNames.find(item=>item.id == agentParams.value);
    // agentParams.value.value_label =  foundNames?.title ??'';
@@ -53,4 +62,15 @@
const agentParams = ref(VueFlowHelper.getParams(VueFlowHelper.getGroupParam(data.value), 'agent'));
VueFlowHelper.getConfigValue(agentParams.value, 'label', '');
const formRef = ref();
const formRules = ref({
    'group_params.0.params.0.value': [{ required: true, message: '请选择代理名称', trigger: 'change' }],
});
// æ³¨å†ŒèŠ‚ç‚¹å®žä¾‹
onMounted(() => {
    emit('register', {
        validateForm: validateForm(formRef) as any,
    });
});
</script>
src/components/vue-flow/ui/nodes/CodeNode.vue
@@ -1,42 +1,48 @@
<template>
    <NodeBasicLayout
        v-model:title="data.title"
        style="width: 320px"
        :type="NodeType.Code"
        :showOffset="false"
        :description="data.description"
        :isViewMode="isViewMode"
    >
        <Handle :id="targetHandleId" type="target" :position="Position.Left" />
        <FieldLayout :title="codeInput.name">
            <div class="flex flex-col gap-y-2">
                <div :key="subIndex" v-for="(subItem, subIndex) in codeInput.params[0].value" class="flex-items-center gap-x-2 pr-8">
                    <el-input filterable class="w-[120px] flex-0" v-model="subItem.key" placeholder="参数名" :readonly="isViewMode"></el-input>
        <el-form ref="formRef" :model="data" :rules="formRules" label-position="right" label-width="60px" :show-message="false">
            <!-- <FieldLayout :title="codeInput.name">
                <div class="flex flex-col gap-y-2">
                    <div :key="subIndex" v-for="(subItem, subIndex) in codeInput.params[0].value" class="flex-items-center gap-x-2 pr-8">
                        <el-input filterable class="w-[120px] flex-0" v-model="subItem.key" placeholder="参数名" :readonly="isViewMode"></el-input>
                    <el-input filterable class="w-[120px] flex-0" v-model="subItem.value" placeholder="值" :readonly="isViewMode"></el-input>
                    <span
                        v-if="!isViewMode"
                        class="ywifont ywicon-shanchu text-red-400 cursor-pointer"
                        @click="delInputVarItem(subIndex)"
                    ></span>
                        <el-input filterable class="w-[120px] flex-0" v-model="subItem.value" placeholder="值" :readonly="isViewMode"></el-input>
                        <span
                            v-if="!isViewMode"
                            class="ywifont ywicon-shanchu text-red-400 cursor-pointer"
                            @click="delInputVarItem(subIndex)"
                        ></span>
                    </div>
                    <el-button v-if="!isViewMode" class="w-fit mt-3" type="primary" @click="addInputVarItem">添加新的入参</el-button>
                </div>
                <el-button v-if="!isViewMode" class="w-fit mt-3" type="primary" @click="addInputVarItem">添加新的入参</el-button>
            </div>
        </FieldLayout>
        <FieldLayout :title="codeStr.name">
            <CodeEditor
                :title="codeStr.name"
                :language="codeLanguage"
                v-model:defaultLanguage="codeStr.params[0].defaultLanguage"
                v-model:editValue="codeStr.params[0].value"
                :disabled="isViewMode"
            />
            <template #right>
                <el-select :disabled="isViewMode" size="small" class="w-[100px]" v-model="codeStr.params[0].defaultLanguage">
                    <el-option v-for="item in codeLanguage" :key="item" :value="item" :label="textTypeMap[item]"></el-option>
                </el-select>
            </template>
        </FieldLayout>
        <FieldLayout :title="codeOutput.name">
            </FieldLayout> -->
            <FieldLayout :title="codeStr.name" required>
                <el-form-item prop="group_params.1.params.0.value" labelWidth="0">
                    <CodeEditor
                        class="w-full"
                        :title="codeStr.name"
                        :language="codeLanguage"
                        v-model:defaultLanguage="codeStr.params[0].defaultLanguage"
                        v-model:editValue="codeStr.params[0].value"
                        :disabled="isViewMode"
                    />
                </el-form-item>
                <template #right>
                    <el-select :disabled="isViewMode" size="small" class="w-[100px]" v-model="codeStr.params[0].defaultLanguage">
                        <el-option v-for="item in codeLanguage" :key="item" :value="item" :label="textTypeMap[item]"></el-option>
                    </el-select>
                </template>
            </FieldLayout>
            <!-- <FieldLayout :title="codeOutput.name">
            <div class="flex flex-col gap-y-2">
                <div :key="subIndex" v-for="(subItem, subIndex) in codeOutput.params[0].value" class="flex-items-center gap-x-2 pr-8">
                    <el-input :readonly="isViewMode" filterable class="w-[120px] flex-0" v-model="subItem.key" placeholder="参数名"> </el-input>
@@ -57,7 +63,8 @@
                </div>
                <el-button v-if="!isViewMode" class="w-fit mt-3" type="primary" @click="addOutputVarItem">添加新的出参</el-button>
            </div>
        </FieldLayout>
        </FieldLayout> -->
        </el-form>
        <Handle :id="sourceHandleId" type="source" :position="Position.Right" />
    </NodeBasicLayout>
</template>
@@ -65,15 +72,16 @@
<script lang="ts" setup>
import type { NodeProps } from '@vue-flow/core';
import { Handle, Position, useNode } from '@vue-flow/core';
import { ref } from 'vue';
import { onMounted, ref } from 'vue';
import { VueFlowHelper } from '../../VueFlowHelper';
import { NodeType, ParameterType, parameterTypeMap } from '../../vueFlowEnum';
import { NodeType, ParameterType } from '../../vueFlowEnum';
import CodeEditor from '/@/components/input/codeEditor/index.vue';
// import CodeEditDialog from './components/CodeEditDlg.vue';
import FieldLayout from './components/FieldLayout.vue';
import NodeBasicLayout from './components/NodeBasicLayout.vue';
import type { LLMNodeData, LLMNodeEvents } from './index';
import { textTypeMap } from '/@/components/input/codeEditor/types';
import { validateForm } from './utils';
defineProps<
    NodeProps<LLMNodeData, LLMNodeEvents> & {
        isViewMode?: boolean;
@@ -88,7 +96,9 @@
const codeOutput = ref(VueFlowHelper.getGroupParam(data.value, 2));
const codeLanguage = ref(VueFlowHelper.getConfigValue(codeStr.value.params[0], 'language', ['text', 'javascript']));
const emit = defineEmits<{
    (e: 'register', data: { validateForm: () => Promise<{ isValid: boolean; invalidFields?: any }> }): void;
}>();
// defaultLanguage ä¸å­˜åœ¨ï¼Œè®¾ç½®é»˜è®¤å€¼
!codeStr.value.params[0].defaultLanguage && (codeStr.value.params[0].defaultLanguage = 'javascript');
@@ -142,4 +152,16 @@
    }
    codeOutput.value.params[0].value.push(getOutputEmptyItem());
};
const formRef = ref();
const formRules = ref({
    'group_params.1.params.0.value': [{ required: true, message: '请输入执行代码', trigger: 'blur' }],
});
// æ³¨å†ŒèŠ‚ç‚¹å®žä¾‹
onMounted(() => {
    emit('register', {
        validateForm: validateForm(formRef) as any,
    });
});
</script>
src/components/vue-flow/ui/nodes/ConditionNode.vue
@@ -6,106 +6,119 @@
        :isViewMode="isViewMode"
    >
        <Handle :id="leftId" type="target" :position="Position.Left" />
        <FieldLayout
            v-for="(item, index) in conditionGroupList"
            :key="item.id"
            class="relative group/conditionGroup"
            :title="item.conditions ? '如果' : '否则'"
        >
            <template #right v-if="item.conditions">
                <el-select v-model="item.operator" class="w-[130px]" :disabled="isViewMode">
                    <el-option
                        v-for="item in Object.keys(conditionOperatorMap)"
                        :key="item"
                        :value="item"
                        :label="conditionOperatorMap[item]"
                    ></el-option>
                </el-select>
                <span
                    v-if="!isViewMode"
                    class="ywifont ywicon-shanchu text-red-400 invisible group-hover/conditionGroup:visible cursor-pointer"
                    @click="delConditionBranch(index)"
                ></span>
            </template>
            <div class="flex flex-col gap-y-2" v-if="item.conditions">
                <div
                    v-for="(subItem, subIndex) in item.conditions"
                    :key="subIndex"
                    class="ml-5 flex-items-center gap-x-2 group/conditionItem pr-8"
                >
                    <el-input :readonly="isViewMode" filterable class="w-[120px] flex-0" v-model="subItem.left_value" placeholder="输入变量">
                    </el-input>
                    <el-select
                        :disabled="isViewMode"
                        filterable
                        v-model="subItem.comparison_operation"
                        class="flex-0 w-[120px]"
                        placeholder="选择条件"
                    >
        <el-form ref="formRef" :model="data" :rules="formRules" label-width="100px" :show-message="false">
            <FieldLayout
                v-for="(item, index) in conditionGroupList"
                :key="item.id"
                class="relative group/conditionGroup"
                :title="item.conditions ? '如果' : '否则'"
            >
                <template #right v-if="item.conditions">
                    <el-select v-model="item.operator" class="w-[130px]" :disabled="isViewMode">
                        <el-option
                            v-for="operation in Object.keys(compareOperationMap)"
                            :key="operation"
                            :value="operation"
                            :label="compareOperationMap[operation]"
                            v-for="item in Object.keys(conditionOperatorMap)"
                            :key="item"
                            :value="item"
                            :label="conditionOperatorMap[item]"
                        ></el-option>
                    </el-select>
                    <!-- <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
                            :readonly="isViewMode"
                            v-if="subItem.right_value_type === VarType.Input"
                            v-model="subItem.right_value"
                            class="w-[180px]"
                            placeholder="输入值"
                        >
                        </el-input>
                        <el-tree-select
                            v-else
                            :disabled="isViewMode"
                            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
                        v-if="!isViewMode"
                        class="ywifont ywicon-shanchu text-red-400 invisible group-hover/conditionItem:visible cursor-pointer"
                        @click="delConditionItem(item, subIndex)"
                        class="ywifont ywicon-shanchu text-red-400 invisible group-hover/conditionGroup:visible cursor-pointer"
                        @click="delConditionBranch(index)"
                    ></span>
                </template>
                <div class="flex flex-col gap-y-2" v-if="item.conditions">
                    <div
                        v-for="(subItem, subIndex) in item.conditions"
                        :key="subIndex"
                        class="ml-5 flex-items-center gap-x-2 group/conditionItem pr-8"
                    >
                        <el-form-item
                            class="w-[120px] flex-0 !mb-0"
                            labelWidth="0"
                            :prop="`group_params.0.params.0.value.${index}.conditions.${subIndex}.left_value`"
                        >
                            <el-input :readonly="isViewMode" filterable v-model="subItem.left_value" placeholder="输入变量"> </el-input>
                        </el-form-item>
                        <el-form-item
                            class="flex-0 w-[120px] !mb-0"
                            labelWidth="0"
                            :prop="`group_params.0.params.0.value.${index}.conditions.${subIndex}.comparison_operation`"
                        >
                            <el-select :disabled="isViewMode" filterable v-model="subItem.comparison_operation" placeholder="选择条件">
                                <el-option
                                    v-for="operation in Object.keys(compareOperationMap)"
                                    :key="operation"
                                    :value="operation"
                                    :label="compareOperationMap[operation]"
                                ></el-option>
                            </el-select>
                        </el-form-item>
                        <template v-if="isShowRight(subItem)">
                            <el-form-item
                                class="w-[180px] !mb-0"
                                labelWidth="0"
                                :prop="`group_params.0.params.0.value.${index}.conditions.${subIndex}.right_value`"
                            >
                                <el-input
                                    :readonly="isViewMode"
                                    v-if="subItem.right_value_type === VarType.Input"
                                    v-model="subItem.right_value"
                                    placeholder="输入值"
                                >
                                </el-input>
                                <el-tree-select
                                    v-else
                                    :disabled="isViewMode"
                                    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>
                            </el-form-item>
                        </template>
                        <span
                            v-if="!isViewMode"
                            class="ywifont ywicon-shanchu text-red-400 invisible group-hover/conditionItem:visible cursor-pointer"
                            @click="delConditionItem(item, subIndex)"
                        ></span>
                    </div>
                    <el-button v-if="!isViewMode" class="w-fit mt-3" type="primary" @click="addConditionItem(item)">添加条件</el-button>
                </div>
                <el-button v-if="!isViewMode" class="w-fit mt-3" type="primary" @click="addConditionItem(item)">添加条件</el-button>
            </div>
            <div v-else-if="isViewMode" class="h-[24px]"></div>
            <el-button v-else-if="!isViewMode" @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>
                <div v-else-if="isViewMode" class="h-[24px]"></div>
                <el-button v-else-if="!isViewMode" @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>
        </el-form>
    </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 { computed, onMounted, ref, watch, watchEffect } from 'vue';
import { ConditionHelper, VueFlowHelper } from '../../VueFlowHelper';
import { CompareOperation, NodeType, VarType, compareOperationMap, conditionOperatorMap } from '../../vueFlowEnum';
import type { LLMNodeData, LLMNodeEvents } from './index';
import FieldLayout from './components/FieldLayout.vue';
import NodeBasicLayout from './components/NodeBasicLayout.vue';
import { validateForm } from './utils';
const emit = defineEmits<{
    (e: 'register', data: { validateForm: () => Promise<{ isValid: boolean; invalidFields?: any }> }): void;
}>();
defineProps<
    NodeProps<LLMNodeData, LLMNodeEvents> & {
        isViewMode?: boolean;
@@ -178,7 +191,6 @@
});
const data = ref(node.node.data);
const conditionGroupList = ref(VueFlowHelper.getFieldValue(data.value, 'condition'));
const { removeEdges } = useVueFlow();
@@ -187,28 +199,98 @@
    const edges = node.connectedEdges.value.filter((item) => item.sourceHandle === handleId || item.targetHandle === handleId);
    removeEdges(edges);
};
const formRef = ref();
// æ·»åŠ è¡¨å•éªŒè¯è§„åˆ™
const formRules = ref({});
// æ›´æ–°éªŒè¯è§„则的方法
const updateFormRules = () => {
    const rules = {};
    // éåŽ†æ‰€æœ‰æ¡ä»¶ç»„
    conditionGroupList.value.forEach((group, index) => {
        if (group.conditions) {
            // éåŽ†æ¯ä¸ªæ¡ä»¶ç»„ä¸­çš„æ¡ä»¶
            group.conditions.forEach((_, subIndex) => {
                // å·¦å€¼éªŒè¯è§„则
                rules[`group_params.0.params.0.value.${index}.conditions.${subIndex}.left_value`] = [
                    { required: true, message: '请输入变量名', trigger: 'blur' },
                ];
                // æ¯”较操作符验证规则
                rules[`group_params.0.params.0.value.${index}.conditions.${subIndex}.comparison_operation`] = [
                    { required: true, message: '请选择比较条件', trigger: 'change' },
                ];
                // å³å€¼éªŒè¯è§„则 (仅当需要右值时验证)
                rules[`group_params.0.params.0.value.${index}.conditions.${subIndex}.right_value`] = [
                    {
                        required: true,
                        message: '请输入比较值',
                        trigger: 'blur',
                        validator: (rule, value, callback) => {
                            const condition = group.conditions[subIndex];
                            if (isShowRight(condition) && !value) {
                                callback(new Error('请输入比较值'));
                            } else {
                                callback();
                            }
                        },
                    },
                ];
            });
        }
    });
    formRules.value = rules;
};
const addConditionBranch = () => {
    const conditionGroup = ConditionHelper.getDefaultConditionGroup();
    // ä¸èƒ½åˆ é™¤å…¶ä»–分支
    conditionGroupList.value.splice(-1, 0, conditionGroup);
    updateFormRules();
};
const delConditionBranch = (index) => {
    const group = conditionGroupList.value[index];
    conditionGroupList.value.splice(index, 1);
    removeRelativeHandleEdge(group.id);
    if (conditionGroupList.value.length === 0) {
        addConditionBranch();
    }
    updateFormRules();
};
const addConditionItem = (group) => {
    const conditionGroup = ConditionHelper.getConditionItem();
    group.conditions.push(conditionGroup);
    updateFormRules();
};
const delConditionItem = (group, index) => {
    group.conditions.splice(index, 1);
    updateFormRules();
};
// è¡¨å•验证方法
// æ³¨å†ŒèŠ‚ç‚¹å®žä¾‹
onMounted(() => {
    updateFormRules();
    emit('register', {
        validateForm: validateForm(formRef) as any,
    });
});
// ç›‘听条件列表变化更新规则
watch(
    conditionGroupList,
    () => {
        updateFormRules();
    },
    { deep: true }
);
</script>
src/components/vue-flow/ui/nodes/LLMNode.vue
@@ -6,7 +6,7 @@
        :isViewMode="isViewMode"
    >
        <Handle :id="targetHandleId" type="target" :position="Position.Left" />
        <el-form :model="data" :rules="formRules" label-position="right" label-width="60px" status-icon>
        <el-form ref="formRef" :model="data" :rules="formRules" label-position="right" label-width="60px" :show-message="false">
            <FieldLayout :title="VueFlowHelper.getConfigValue(modelSetting, 'name', '模型设置')">
                <div class="w-full flex-items-center gap-x-2">
                    <div class="flex-column gap-y-1.5">
@@ -47,16 +47,18 @@
                    </div>
                </div>
            </FieldLayout>
            <FieldLayout :title="prompt.name">
                <CodeEditor
                    :title="prompt.name"
                    class="nowheel"
                    v-model:editValue="VueFlowHelper.getParams(prompt, 'prompt').value"
                    :maxHeight="180"
                    :disabled="isViewMode"
                />
            <FieldLayout :title="prompt.name" required>
                <el-form-item labelWidth="0" prop="group_params.1.params.0.value">
                    <CodeEditor
                        :title="prompt.name"
                        class="nowheel w-full"
                        v-model:editValue="VueFlowHelper.getParams(prompt, 'prompt').value"
                        :maxHeight="180"
                        :disabled="isViewMode"
                    />
                </el-form-item>
            </FieldLayout>
            <FieldLayout :title="outputParam.name" >
            <FieldLayout :title="outputParam.name" required>
                <el-form-item labelWidth="0" prop="group_params.2.params.0.value">
                    <el-input
                        class="w-full flex-0"
@@ -75,13 +77,15 @@
<script setup lang="ts">
import type { NodeProps } from '@vue-flow/core';
import { Handle, Position, useNode } from '@vue-flow/core';
import { ref } from 'vue';
import { onMounted, ref } from 'vue';
import { VueFlowHelper } from '../../VueFlowHelper';
import { NodeType } from '../../vueFlowEnum';
import FieldLayout from './components/FieldLayout.vue';
import NodeBasicLayout from './components/NodeBasicLayout.vue';
import type { LLMNodeData, LLMNodeEvents } from './index';
import CodeEditor from '/@/components/input/codeEditor/index.vue';
import { invalid } from 'moment';
import { validateForm } from './utils';
defineProps<
    NodeProps<LLMNodeData, LLMNodeEvents> & {
@@ -95,7 +99,6 @@
const targetHandleId = ref(VueFlowHelper.getHandleId(node.node, 'target'));
const data = ref(node.node.data);
console.log('🚀 ~ data.value:', data.value);
const modelSetting = ref(VueFlowHelper.getGroupParam(data.value, 0));
const prompt = ref(VueFlowHelper.getGroupParam(data.value, 1));
const outputParam = ref(VueFlowHelper.getGroupParam(data.value, 2));
@@ -103,19 +106,20 @@
const formRules = ref({
    'group_params.0.params.0.value': [{ required: true, message: '请选择模型', trigger: 'change' }],
    // 'group_params.0.params.1.value': [{ required: true, message: '请输入温度', trigger: 'blur' }],
    // 'group_params.2.params.0.value': [{ required: true, message: '请输入输出参数名', trigger: 'blur' }],
    'group_params.1.params.0.value': [{ required: true, message: '请输入提示词', trigger: 'blur' }],
    'group_params.2.params.0.value': [{ required: true, message: '请输入输出参数名', trigger: 'blur' }],
});
const emit = defineEmits(['register']);
const formRef = ref();
const validateForm = async () => {
    const valid = await formRef.value.validate();
    if (valid) {
        console.log('submit!');
    }
};
defineExpose({
    validateForm,
// è¡¨å•验证方法
// æ³¨å†ŒèŠ‚ç‚¹å®žä¾‹
onMounted(() => {
    emit('register', {
        validateForm: validateForm(formRef),
    });
});
</script>
src/components/vue-flow/ui/nodes/OutputNode.vue
@@ -6,77 +6,81 @@
        :isViewMode="isViewMode"
    >
        <Handle :id="targetHandleId" type="target" :position="Position.Left" />
        <FieldLayout level="2" :title="VueFlowHelper.getParams(outputParams, 'output_msg').label">
            <CodeEditor
                :title="VueFlowHelper.getParams(outputParams, 'output_msg').label"
                v-model:editValue="VueFlowHelper.getParams(outputParams, 'output_msg').value.msg"
                :maxHeight="180"
                :disabled="isViewMode"
                :placeholder="VueFlowHelper.getParams(outputParams, 'output_msg').placeholder"
            />
        </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"
                :disabled="isViewMode"
                class="flex-items-center justify-between interaction-radio-group"
            >
                <el-radio v-for="item in Object.keys(interactionTypeMap)" :key="item" :value="item">
                    {{ interactionTypeMap[item] }}
                </el-radio>
            </el-radio-group>
        <el-form ref="formRef" :model="data" :rules="formRules" label-position="right" label-width="60px" :show-message="false">
            <FieldLayout required level="2" :title="VueFlowHelper.getParams(outputParams, 'output_msg').label">
                <el-form-item labelWidth="0" prop="group_params.0.params.0.value.msg">
                    <CodeEditor
                        class="w-full"
                        :title="VueFlowHelper.getParams(outputParams, 'output_msg').label"
                        v-model:editValue="VueFlowHelper.getParams(outputParams, 'output_msg').value.msg"
                        :maxHeight="180"
                        :disabled="isViewMode"
                        :placeholder="VueFlowHelper.getParams(outputParams, 'output_msg').placeholder"
                    />
                </el-form-item>
            </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"
                    :disabled="isViewMode"
                    class="flex-items-center justify-between interaction-radio-group"
                >
                    <el-radio v-for="item in Object.keys(interactionTypeMap)" :key="item" :value="item">
                        {{ interactionTypeMap[item] }}
                    </el-radio>
                </el-radio-group>
            <CodeEditor
                title="输入型交互"
                :disabled="isViewMode"
                v-if="VueFlowHelper.getParams(outputParams, 'output_result').value.type === InteractionType.Input"
                v-model:editValue="VueFlowHelper.getParams(outputParams, 'output_result').value.value"
                :maxHeight="150"
            />
                <CodeEditor
                    title="输入型交互"
                    :disabled="isViewMode"
                    v-if="VueFlowHelper.getParams(outputParams, 'output_result').value.type === InteractionType.Input"
                    v-model:editValue="VueFlowHelper.getParams(outputParams, 'output_result').value.value"
                    :maxHeight="150"
                />
            <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="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
                    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="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>
                <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 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>
                <el-button v-else type="primary" @click="addOptionClick">添加选项</el-button>
            </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>
                <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>
        </el-form>
    </NodeBasicLayout>
</template>
<script lang="ts" setup>
import { Handle, Position, useNode, useVueFlow } from '@vue-flow/core';
import { ref } from 'vue';
import { onMounted, ref } from 'vue';
import { NodeType } from '../../vueFlowEnum';
import type { NodeProps } from '@vue-flow/core';
@@ -87,19 +91,21 @@
import NodeBasicLayout from './components/NodeBasicLayout.vue';
import type { LLMNodeData, LLMNodeEvents } from './index';
import CodeEditor from '/@/components/input/codeEditor/index.vue';
import { validateForm } from './utils';
defineProps<
    NodeProps<LLMNodeData, LLMNodeEvents> & {
        isViewMode?: boolean;
    }
>();
const emit = defineEmits<{
    (e: 'register', data: { validateForm: () => Promise<{ isValid: boolean; invalidFields?: any }> }): void;
}>();
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 outputParams = ref(VueFlowHelper.getGroupParam(data.value, 0));
const { removeEdges } = useVueFlow();
@@ -152,6 +158,17 @@
const isEditStatus = ref(false);
const tmpEditValue = ref('');
const formRef = ref();
const formRules = ref({
    'group_params.0.params.0.value.msg': [{ required: true, message: '请输入消息内容', trigger: 'blur' }],
});
// æ³¨å†ŒèŠ‚ç‚¹å®žä¾‹
onMounted(() => {
    emit('register', {
        validateForm: validateForm(formRef) as any,
    });
});
</script>
<style scoped lang="scss">
.interaction-radio-group {
src/components/vue-flow/ui/nodes/PythonCodeNode.vue
@@ -2,35 +2,58 @@
    <NodeBasicLayout
        v-model:title="data.title"
        :type="NodeType.PythonCode"
        :style="{ width: '320px' }"
        :showOffset="false"
        :description="data.description"
        :isViewMode="isViewMode"
    >
        <Handle :id="targetHandleId" type="target" :position="Position.Left" />
        <FieldLayout :title="codeInput.name">
            <!-- codeInput.params[0].value -->
            <el-input filterable class="w-[120px] flex-0" v-model="codeInput.params[0].value.value" placeholder="参数名" :readonly="isViewMode"></el-input>
        </FieldLayout>
        <el-form ref="formRef" :model="data" :rules="formRules" label-position="right" label-width="60px" :show-message="false">
            <FieldLayout :title="codeInput.name">
                <!-- codeInput.params[0].value -->
                <el-form-item prop="group_params.0.params.0.value.value" labelWidth="0">
                    <el-input
                        filterable
                        class="w-[120px] flex-0"
                        v-model="codeInput.params[0].value.value"
                        placeholder="参数名"
                        :readonly="isViewMode"
                    ></el-input>
                </el-form-item>
            </FieldLayout>
        <FieldLayout :title="codeOutput.name">
            <!-- codeOutput.params[0].value -->
            <el-input filterable class="w-[120px] flex-0" v-model="codeOutput.params[0].value.value" placeholder="参数名" :readonly="isViewMode"></el-input>
        </FieldLayout>
            <FieldLayout :title="codeOutput.name" required>
                <!-- codeOutput.params[0].value -->
                <el-form-item prop="group_params.1.params.0.value.value" labelWidth="0">
                    <el-input
                        filterable
                        class="w-[120px] flex-0"
                        v-model="codeOutput.params[0].value.value"
                        placeholder="参数名"
                        :readonly="isViewMode"
                    ></el-input>
                </el-form-item>
            </FieldLayout>
        <FieldLayout :title="codeStr.name">
            <CodeEditor
                :title="codeStr.name"
                :language="codeLanguage"
                v-model:defaultLanguage="codeStr.params[0].defaultLanguage"
                v-model:editValue="codeStr.params[0].value"
                :disabled="isViewMode"
            />
            <template #right>
                <el-select :disabled="isViewMode" size="small" class="w-[100px]" v-model="codeStr.params[0].defaultLanguage">
                    <el-option v-for="item in codeLanguage" :key="item" :value="item" :label="textTypeMap[item]"></el-option>
                </el-select>
            </template>
        </FieldLayout>
            <FieldLayout :title="codeStr.name">
                <el-form-item prop="group_params.2.params.0.value" labelWidth="0">
                    <CodeEditor
                        class="w-full"
                        :title="codeStr.name"
                        :language="codeLanguage"
                        v-model:defaultLanguage="codeStr.params[0].defaultLanguage"
                        v-model:editValue="codeStr.params[0].value"
                        :disabled="isViewMode"
                    />
                </el-form-item>
                <template #right>
                    <el-select :disabled="isViewMode" size="small" class="w-[100px]" v-model="codeStr.params[0].defaultLanguage">
                        <el-option v-for="item in codeLanguage" :key="item" :value="item" :label="textTypeMap[item]"></el-option>
                    </el-select>
                </template>
            </FieldLayout>
        </el-form>
        <Handle :id="sourceHandleId" type="source" :position="Position.Right" />
    </NodeBasicLayout>
</template>
@@ -38,20 +61,24 @@
<script lang="ts" setup>
import type { NodeProps } from '@vue-flow/core';
import { Handle, Position, useNode } from '@vue-flow/core';
import { ref } from 'vue';
import { onMounted, ref } from 'vue';
import { VueFlowHelper } from '../../VueFlowHelper';
import { NodeType, ParameterType, parameterTypeMap } from '../../vueFlowEnum';
import { NodeType, ParameterType } from '../../vueFlowEnum';
import CodeEditor from '/@/components/input/codeEditor/index.vue';
// import CodeEditDialog from './components/CodeEditDlg.vue';
import FieldLayout from './components/FieldLayout.vue';
import NodeBasicLayout from './components/NodeBasicLayout.vue';
import type { LLMNodeData, LLMNodeEvents } from './index';
import { validateForm } from './utils';
import { textTypeMap } from '/@/components/input/codeEditor/types';
defineProps<
    NodeProps<LLMNodeData, LLMNodeEvents> & {
        isViewMode?: boolean;
    }
>();
const emit = defineEmits<{
    (e: 'register', data: { validateForm: () => Promise<{ isValid: boolean; invalidFields?: any }> }): void;
}>();
const node = useNode();
const targetHandleId = ref(VueFlowHelper.getHandleId(node.node, 'target'));
const sourceHandleId = ref(VueFlowHelper.getHandleId(node.node, 'source'));
@@ -115,4 +142,18 @@
    }
    codeOutput.value.params[0].value.push(getOutputEmptyItem());
};
const formRef = ref();
const formRules = ref({
    // 'group_params.0.params.0.value.value': [{ required: true, message: '请输入入参', trigger: 'blur' }],
    'group_params.1.params.0.value.value': [{ required: true, message: '请输入出参', trigger: 'blur' }],
    'group_params.2.params.0.value': [{ required: true, message: '请输入执行代码', trigger: 'blur' }],
});
// æ³¨å†ŒèŠ‚ç‚¹å®žä¾‹
onMounted(() => {
    emit('register', {
        validateForm: validateForm(formRef) as any,
    });
});
</script>
src/components/vue-flow/ui/nodes/StartNode.vue
@@ -5,37 +5,43 @@
        :description="VueFlowHelper.getConfigValue(data, 'description', '工作流运行的起始节点。')"
        :isViewMode="isViewMode"
    >
        <FieldLayout :title="VueFlowHelper.getConfigValue(varListConfig, 'name', '流程参数')">
            <div v-for="(item, index) in varList" :key="index" class="group/varItem">
                <FieldLayout level="2" title="参数名">
                    <template #right v-if="!isViewMode">
                        <span
                            class="ywifont ywicon-shanchu text-red-400 invisible group-hover/varItem:visible cursor-pointer"
                            @click="handleClickDeleteBtn(index)"
                        ></span>
                    </template>
                    <el-input v-model="item.name" :readonly="isViewMode"></el-input>
                </FieldLayout>
                <FieldLayout level="2" title="描述">
                    <el-input
                        class="nowheel"
                        type="textarea"
                        :autosize="{ minRows: 2, maxRows: 6 }"
                        v-model="item.description"
                        :readonly="isViewMode"
                    ></el-input>
                </FieldLayout>
            </div>
        <el-form ref="formRef" :model="data" :rules="formRules" label-position="right" label-width="60px" :show-message="false">
            <FieldLayout :title="VueFlowHelper.getConfigValue(varListConfig, 'name', '流程参数')">
                <div v-for="(item, index) in varList" :key="index" class="group/varItem">
                    <FieldLayout level="2" title="参数名" required>
                        <template #right v-if="!isViewMode">
                            <span
                                class="ywifont ywicon-shanchu text-red-400 invisible group-hover/varItem:visible cursor-pointer"
                                @click="handleClickDeleteBtn(index)"
                            ></span>
                        </template>
                        <el-form-item labelWidth="0" :prop="`group_params.0.params.0.value.${index}.name`">
                            <el-input v-model="item.name" :readonly="isViewMode"></el-input>
                        </el-form-item>
                    </FieldLayout>
                    <FieldLayout level="2" title="描述" required>
                        <el-form-item labelWidth="0" :prop="`group_params.0.params.0.value.${index}.description`">
                            <el-input
                                class="nowheel"
                                type="textarea"
                                :autosize="{ minRows: 2, maxRows: 6 }"
                                v-model="item.description"
                                :readonly="isViewMode"
                            ></el-input>
                        </el-form-item>
                    </FieldLayout>
                </div>
            <el-button v-if="!isViewMode" class="w-fit" type="primary" @click="handleClickAddBtn">添加参数</el-button>
        </FieldLayout>
                <el-button v-if="!isViewMode" class="w-fit" type="primary" @click="handleClickAddBtn">添加参数</el-button>
            </FieldLayout>
        </el-form>
        <Handle :id="handleId" type="source" :position="Position.Right" />
    </NodeBasicLayout>
</template>
<script lang="ts" setup>
import { Handle, Position, useNode } from '@vue-flow/core';
import { ref } from 'vue';
import { onMounted, ref } from 'vue';
import type { NodeProps } from '@vue-flow/core';
import { VueFlowHelper } from '../../VueFlowHelper';
@@ -44,6 +50,10 @@
import FieldLayout from './components/FieldLayout.vue';
import NodeBasicLayout from './components/NodeBasicLayout.vue';
import { validateForm } from './utils';
const emit = defineEmits(['register']);
defineProps<
    NodeProps<LLMNodeData, LLMNodeEvents> & {
        isViewMode?: boolean;
@@ -53,7 +63,6 @@
const handleId = ref(VueFlowHelper.getHandleId(node.node, 'source'));
const data = ref(node.node.data);
const varList = ref(VueFlowHelper.getFieldValue(data.value, 'var_list'));
function handleClickAddBtn() {
@@ -63,6 +72,7 @@
        type: '',
        isRequired: true,
    });
    updateFormRules();
}
const varListConfig = ref(VueFlowHelper.getGroupParam(data.value, 0));
@@ -72,5 +82,33 @@
function handleClickDeleteBtn(index: number) {
    varList.value.splice(index, 1);
    updateFormRules();
}
const formRules = ref({
    ...varList.value.reduce((rules, _, index) => {
        rules[`group_params.0.params.0.value.${index}.name`] = [{ required: true, message: '请输入参数名', trigger: 'blur' }];
        rules[`group_params.0.params.0.value.${index}.description`] = [{ required: true, message: '请输入参数描述', trigger: 'blur' }];
        return rules;
    }, {}),
});
const formRef = ref();
const updateFormRules = () => {
    formRules.value = {
        ...varList.value.reduce((rules, _, index) => {
            rules[`group_params.0.params.0.value.${index}.name`] = [{ required: true, message: '请输入参数名', trigger: 'blur' }];
            rules[`group_params.0.params.0.value.${index}.description`] = [{ required: true, message: '请输入参数描述', trigger: 'blur' }];
            return rules;
        }, {}),
    };
};
// æ³¨å†ŒèŠ‚ç‚¹å®žä¾‹
onMounted(() => {
    updateFormRules();
    emit('register', {
        validateForm: validateForm(formRef),
    });
});
</script>
src/components/vue-flow/ui/nodes/TextResourceNode.vue
@@ -7,25 +7,31 @@
        :isViewMode="isViewMode"
    >
        <Handle :id="targetHandleId" type="target" :position="Position.Left" />
        <FieldLayout :title="codeInput.name">
            <el-input class="w-full flex-0" v-model="codeInput.params[0].value" placeholder="参数名" :readonly="isViewMode"> </el-input>
        </FieldLayout>
        <FieldLayout :title="codeStr.name">
            <CodeEditor
                :disabled="isViewMode"
                :title="codeStr.name"
                :language="codeLanguage"
                v-model:defaultLanguage="codeStr.params[0].defaultLanguage"
                v-model:editValue="codeStr.params[0].value"
                :readonly="isViewMode"
            />
            <template #right>
                <el-select v-if="!isViewMode" size="small" class="w-[100px]" v-model="codeStr.params[0].defaultLanguage">
                    <el-option v-for="item in codeLanguage" :key="item" :value="item" :label="textTypeMap[item]"></el-option>
                </el-select>
            </template>
        </FieldLayout>
        <el-form ref="formRef" :model="data" :rules="formRules" label-position="right" label-width="60px" :show-message="false">
            <FieldLayout :title="codeInput.name" required>
                <el-form-item prop="group_params.0.params.0.value" labelWidth="0">
                    <el-input class="w-full flex-0" v-model="codeInput.params[0].value" placeholder="参数名" :readonly="isViewMode"> </el-input>
                </el-form-item>
            </FieldLayout>
            <FieldLayout :title="codeStr.name" required>
                <el-form-item prop="group_params.1.params.0.value" labelWidth="0">
                    <CodeEditor
                        class="w-full"
                        :disabled="isViewMode"
                        :title="codeStr.name"
                        :language="codeLanguage"
                        v-model:defaultLanguage="codeStr.params[0].defaultLanguage"
                        v-model:editValue="codeStr.params[0].value"
                        :readonly="isViewMode"
                    />
                </el-form-item>
                <template #right>
                    <el-select v-if="!isViewMode" size="small" class="w-[100px]" v-model="codeStr.params[0].defaultLanguage">
                        <el-option v-for="item in codeLanguage" :key="item" :value="item" :label="textTypeMap[item]"></el-option>
                    </el-select>
                </template>
            </FieldLayout>
        </el-form>
        <Handle :id="sourceHandleId" type="source" :position="Position.Right" />
    </NodeBasicLayout>
</template>
@@ -35,7 +41,7 @@
import { vscodeDark } from '@uiw/codemirror-theme-vscode';
import type { NodeProps } from '@vue-flow/core';
import { Handle, Position, useNode } from '@vue-flow/core';
import { ref } from 'vue';
import { onMounted, ref } from 'vue';
import { VueFlowHelper } from '../../VueFlowHelper';
import { NodeType, ParameterType } from '../../vueFlowEnum';
import FieldLayout from './components/FieldLayout.vue';
@@ -43,12 +49,16 @@
import type { LLMNodeData, LLMNodeEvents } from './index';
import CodeEditor from '/@/components/input/codeEditor/index.vue';
import { textTypeMap } from '/@/components/input/codeEditor/types';
import { validateForm } from './utils';
defineProps<
    NodeProps<LLMNodeData, LLMNodeEvents> & {
        isViewMode?: boolean;
    }
>();
const emit = defineEmits<{
    (e: 'register', data: { validateForm: () => Promise<{ isValid: boolean; invalidFields?: any }> }): void;
}>();
const node = useNode();
const targetHandleId = ref(VueFlowHelper.getHandleId(node.node, 'target'));
const sourceHandleId = ref(VueFlowHelper.getHandleId(node.node, 'source'));
@@ -92,4 +102,17 @@
    }
    codeInput.value.params[0].value.push(getInputEmptyItem());
};
const formRef = ref();
const formRules = ref({
    'group_params.0.params.0.value': [{ required: true, message: '请输入参数名', trigger: 'blur' }],
    'group_params.1.params.0.value': [{ required: true, message: '请输入参数值', trigger: 'blur' }],
});
// æ³¨å†ŒèŠ‚ç‚¹å®žä¾‹
onMounted(() => {
    emit('register', {
        validateForm: validateForm(formRef) as any,
    });
});
</script>
src/components/vue-flow/ui/nodes/utils.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
export const validateForm = (formRef: any) => {
    return () =>
        new Promise((resolve, reject) => {
            if (!formRef.value) resolve({ isValid: true });
            try {
                formRef.value.validate((isValid, invalidFields) => {
                    resolve({ isValid, invalidFields });
                });
            } catch (error) {
                resolve({ isValid: false });
            }
        });
};
src/views/project/yw/systemManage/flowApp/FlowApp.vue
@@ -1,7 +1,7 @@
<template>
    <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 :isViewMode="isViewMode" v-if="flowAgent" :flowAgent="flowAgent" :queryId="queryId" @saveClick="validateForm" />
            <Header :isViewMode="isViewMode" v-if="flowAgent" :flowAgent="flowAgent" :queryId="queryId" @saveClick="saveClick" />
            <main class="relative flex h-full w-full flex-1">
                <Sidebar v-if="!isViewMode" class="w-52" @dragstart="handleOnDragStart" />
                <div class="relative h-full flex-1 overflow-hidden" v-if="flowJson">
@@ -24,18 +24,21 @@
import { useVueFlow } from '@vue-flow/core';
import { useClipboard } from '@vueuse/core';
// import MainCanvas from '@/components/vue-flow/MainCanvas.vue';
import { computed, onMounted, ref } from 'vue';
import { ElMessage } from 'element-plus';
import { computed, h, onMounted, ref } from 'vue';
import Header from './components/Header.vue';
import Sidebar from './components/Sidebar.vue';
import {
    get_agent_names,
    get_flow_func_names,
    get_llm_info_list,
    get_workflow_agent_list,
    get_workflow_json_flow,
    get_llm_info_list,
    update_workflow_json_flow,
} from '/@/api/workflow';
import MainCanvas from '/@/components/vue-flow/MainCanvas.vue';
import router from '/@/router';
import { useCompRef } from '/@/utils/types';
const props = defineProps<{
    isViewMode: {
        type: Boolean;
@@ -111,11 +114,59 @@
    llmInfoList.value = logicTree;
};
const mainCanvasRef = ref();
const validateForm = () => {
    mainCanvasRef.value.validateForm();
};
const mainCanvasRef = useCompRef(MainCanvas);
const saveClick = async (jsonStr: string) => {
    const results = await mainCanvasRef.value.validateForm();
    const isValid = results.every((item) => item.isValid);
    if (!isValid) {
        const messageSet = new Set();
        const invalidFields = results.filter((item) => !item.isValid);
        for (const item of invalidFields) {
            for (const fieldArray of Object.values(item.invalidFields) as any[]) {
                for (const field of fieldArray) {
                    field.message && messageSet.add(field.message);
                }
            }
        }
        // ä½¿ç”¨ h æ–¹æ³•创建带换行的消息内容
        ElMessage({
            type: 'error',
            message: h(
                'div',
                {
                    style: {
                        color: '#f56c6c', // Element Plus é»˜è®¤é”™è¯¯æ¶ˆæ¯çš„红色
                        fontSize: '14px',
                        lineHeight: '21px',
                    },
                },
                [
                    ...Array.from(messageSet).slice(0, 5).map((msg) =>
                        h(
                            'div',
                            {
                                style: {
                                    marginBottom: '4px',
                                },
                            },
                            msg as any
                        )
                    ),
                ]
            ),
            duration: 3000,
        });
        // ElMessage.error('cesadfsadf')
        return;
    }
    const res = await update_workflow_json_flow({
        agent_id: queryId.value,
        json_flow: jsonStr,
    });
    ElMessage.success('保存成功!');
};
onMounted(() => {
    if (!queryId.value) return;
src/views/project/yw/systemManage/flowApp/components/Header.vue
@@ -79,12 +79,8 @@
    const obj = toObject();
    const jsonStr = obj ? JSONFormat(obj) : '';
    const res = await update_workflow_json_flow({
        agent_id: props.queryId,
        json_flow: jsonStr,
    });
    ElMessage.success('保存成功!');
    emit('saveClick');
    emit('saveClick',jsonStr);
};
onChange((file) => {