From e4101b53e209313af89fa709e0431075c0717140 Mon Sep 17 00:00:00 2001 From: wangdan-fit2cloud Date: Sun, 6 Sep 2026 15:51:46 +0800 Subject: [PATCH] feat: varable assign --- .../workflow/icon_parameter_extraction.svg | 3 + .../workflow/icon_variable-aggregation.svg | 4 + .../assets/workflow/icon_variable-assign.svg | 5 + .../workflow/icon_variable-splitting.svg | 3 + ui/src/components/COMPONENT_README.md | 30 +- .../business/model-select/index.vue | 51 ++-- ui/src/components/codemirror-editor/Json.vue | 20 +- .../items/MultiSelectConstructor.vue | 2 +- .../items/PasswordInputConstructor.vue | 3 +- .../items/RadioCardConstructor.vue | 2 +- .../items/SingleSelectConstructor.vue | 2 +- .../constructor/items/SliderConstructor.vue | 5 +- .../items/TextInputConstructor.vue | 3 +- .../items/TextareaInputConstructor.vue | 3 +- ui/src/components/mk-form-list/index.vue | 2 +- ui/src/styles/STYLE_README.md | 3 +- ui/src/styles/app.scss | 2 +- ui/src/styles/element-plus.scss | 1 + .../chat/users/dialog/QuotaSettingsDialog.vue | 2 +- .../components/LoginSetting.vue | 66 +++- .../tool-form/tool-custom/ToolFormDrawer.vue | 2 +- ui/src/workflow-canvas/WORKFLOW_README.md | 3 + ui/src/workflow-canvas/config/node-data.ts | 24 +- ui/src/workflow-canvas/core/NodeCascader.vue | 69 +++-- .../icons/parameter-extraction-node-icon.vue | 4 +- .../icons/variable-aggregation-node-icon.vue | 4 +- .../icons/variable-assign-node-icon.vue | 4 +- .../icons/variable-splitting-node-icon.vue | 4 +- .../nodes/ai-chat-node/index.vue | 9 +- .../nodes/condition-node/index.ts | 2 +- .../nodes/condition-node/index.vue | 11 +- .../nodes/image-generate-node/index.vue | 8 +- .../nodes/image-to-video-node/index.vue | 18 +- .../nodes/image-understand-node/index.vue | 11 +- .../nodes/intent-node/index.vue | 11 +- .../component/ParametersFieldDialog.vue | 8 +- .../component/ParametersFieldTable.vue | 30 +- .../nodes/parameter-extraction-node/index.ts | 10 +- .../nodes/parameter-extraction-node/index.vue | 143 +++++---- .../nodes/question-node/index.vue | 9 +- .../nodes/reply-node/index.vue | 8 +- .../search-setting/SearchSettingDialog.vue | 3 +- .../nodes/speech-to-text-node/index.vue | 11 +- .../nodes/text-to-speech-node/index.vue | 11 +- .../nodes/text-to-video-node/index.vue | 9 +- .../nodes/tool-workflow-lib-node/index.vue | 3 +- .../component/GroupFieldDialog.vue | 7 +- .../nodes/variable-aggregation-node/index.vue | 287 +++++++++--------- .../nodes/variable-assign-node/index.ts | 10 +- .../nodes/variable-assign-node/index.vue | 276 +++++++++-------- .../component/VariableFieldDialog.vue | 54 ++-- .../component/VariableFieldTable.vue | 25 +- .../nodes/variable-splitting-node/index.ts | 10 +- .../nodes/variable-splitting-node/index.vue | 62 ++-- .../nodes/video-understand-node/index.vue | 12 +- 55 files changed, 729 insertions(+), 655 deletions(-) create mode 100644 ui/src/assets/workflow/icon_parameter_extraction.svg create mode 100644 ui/src/assets/workflow/icon_variable-aggregation.svg create mode 100644 ui/src/assets/workflow/icon_variable-assign.svg create mode 100644 ui/src/assets/workflow/icon_variable-splitting.svg diff --git a/ui/src/assets/workflow/icon_parameter_extraction.svg b/ui/src/assets/workflow/icon_parameter_extraction.svg new file mode 100644 index 00000000000..40f77036a7d --- /dev/null +++ b/ui/src/assets/workflow/icon_parameter_extraction.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/src/assets/workflow/icon_variable-aggregation.svg b/ui/src/assets/workflow/icon_variable-aggregation.svg new file mode 100644 index 00000000000..f3f63b10e76 --- /dev/null +++ b/ui/src/assets/workflow/icon_variable-aggregation.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/src/assets/workflow/icon_variable-assign.svg b/ui/src/assets/workflow/icon_variable-assign.svg new file mode 100644 index 00000000000..269f075c381 --- /dev/null +++ b/ui/src/assets/workflow/icon_variable-assign.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/src/assets/workflow/icon_variable-splitting.svg b/ui/src/assets/workflow/icon_variable-splitting.svg new file mode 100644 index 00000000000..a46d5be6dee --- /dev/null +++ b/ui/src/assets/workflow/icon_variable-splitting.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/src/components/COMPONENT_README.md b/ui/src/components/COMPONENT_README.md index 2bb54378f8b..1a779c6a5d7 100644 --- a/ui/src/components/COMPONENT_README.md +++ b/ui/src/components/COMPONENT_README.md @@ -768,17 +768,39 @@ const code = defineModel({ required: true }) ### JsonInput JSON 专用输入框,通过 `v-model` 接收并回传解析后的 JSON 值,内置 JSON 语法诊断、格式化和全屏 -编辑。组件暴露 `validateRules`,可直接接入 Element Plus 表单自定义校验器;无法解析的输入不会 -覆盖最后一次有效的 `v-model` 值。 +编辑。与 `MdEditorMagnify` 一样,通过 `useFormItem()` 触发外层表单项校验,不在组件内部创建 +表单项。内容变化时触发 `change`,失焦和全屏确认后触发 `blur`;`validateEvent` 默认为 `true`, +设为 `false` 可关闭自动触发。无效输入也会触发校验,不能仅监听解析后的 `v-model`。 + +组件暴露 `format()` 和 `validateRules()`。JSON 语法规则统一由 `validateRules` 校验编辑器原始 +文本,空白或无效 JSON 提示“请输入正确的 JSON 格式”;外层 `rules` 接入该方法后,表单提交 +也会检查语法。无法解析的输入不会覆盖最后一次有效的 `v-model` 值,不要直接对已经解析的 +`value` 再调用 `JSON.parse(value)`。必填等业务规则继续放在外层表单项。 ```vue - + ``` ### MkSourceCard diff --git a/ui/src/components/business/model-select/index.vue b/ui/src/components/business/model-select/index.vue index d0747364e13..19e429c582f 100644 --- a/ui/src/components/business/model-select/index.vue +++ b/ui/src/components/business/model-select/index.vue @@ -1,4 +1,4 @@ - + + diff --git a/ui/src/workflow-canvas/nodes/question-node/index.vue b/ui/src/workflow-canvas/nodes/question-node/index.vue index 1e4f586c7a6..fd121c599e5 100644 --- a/ui/src/workflow-canvas/nodes/question-node/index.vue +++ b/ui/src/workflow-canvas/nodes/question-node/index.vue @@ -29,7 +29,6 @@ interface QuestionNodeForm { } const formRef = useTemplateRef('formRef') -const modelCascaderRef = useTemplateRef>('modelCascaderRef') const store = useWorkflowStore(apiType) const modelList = ref>([]) @@ -99,11 +98,8 @@ function validateModel(_rule: unknown, _value: unknown, callback: (error?: Error ) } -function validate() { - return Promise.all([ - formData.value.model_id_type === 'reference' ? modelCascaderRef.value?.validate() : Promise.resolve(), - formRef.value?.validate(), - ]).catch((error) => Promise.reject({ node: model, errMessage: error })) +async function validate() { + return formRef.value?.validate().catch((error) => Promise.reject({ node: model, errMessage: error })) } onMounted(() => { @@ -210,7 +206,6 @@ onMounted(() => { :value-on-clear="0" controls-position="right" align="left" - class="w-full!" :step="1" :step-strictly="true" /> diff --git a/ui/src/workflow-canvas/nodes/reply-node/index.vue b/ui/src/workflow-canvas/nodes/reply-node/index.vue index 2c42b488a39..e3c71c838fe 100644 --- a/ui/src/workflow-canvas/nodes/reply-node/index.vue +++ b/ui/src/workflow-canvas/nodes/reply-node/index.vue @@ -18,7 +18,6 @@ interface ReplyNodeForm { } const formRef = useTemplateRef('formRef') -const nodeCascaderRef = useTemplateRef>('nodeCascaderRef') const defaultForm: ReplyNodeForm = { content: '', @@ -41,11 +40,8 @@ const formData = computed({ set: (value) => (model.properties.node_data = value), }) -function validate() { - return Promise.all([ - formData.value.reply_type === 'referencing' ? nodeCascaderRef.value?.validate() : Promise.resolve(), - formRef.value?.validate(), - ]).catch((error) => Promise.reject({ node: model, errMessage: error })) +async function validate() { + return formRef.value?.validate().catch((error) => Promise.reject({ node: model, errMessage: error })) } onMounted(() => { diff --git a/ui/src/workflow-canvas/nodes/search-knowledge-node/component/search-setting/SearchSettingDialog.vue b/ui/src/workflow-canvas/nodes/search-knowledge-node/component/search-setting/SearchSettingDialog.vue index c25acfae2be..c9e5215812e 100644 --- a/ui/src/workflow-canvas/nodes/search-knowledge-node/component/search-setting/SearchSettingDialog.vue +++ b/ui/src/workflow-canvas/nodes/search-knowledge-node/component/search-setting/SearchSettingDialog.vue @@ -77,7 +77,7 @@ defineExpose({ open }) :value-on-clear="0" controls-position="right" align="left" - class="w-full!" + /> @@ -89,7 +89,6 @@ defineExpose({ open }) :value-on-clear="1" controls-position="right" align="left" - class="w-full!" /> diff --git a/ui/src/workflow-canvas/nodes/speech-to-text-node/index.vue b/ui/src/workflow-canvas/nodes/speech-to-text-node/index.vue index 5f347ad1e93..ddc027bdbde 100644 --- a/ui/src/workflow-canvas/nodes/speech-to-text-node/index.vue +++ b/ui/src/workflow-canvas/nodes/speech-to-text-node/index.vue @@ -27,8 +27,6 @@ interface SpeechToTextNodeForm { } const formRef = useTemplateRef('formRef') -const modelCascaderRef = useTemplateRef>('modelCascaderRef') -const contentCascaderRef = useTemplateRef>('contentCascaderRef') const store = useWorkflowStore(apiType) const modelList = ref>([]) @@ -91,14 +89,9 @@ function validateModel(_rule: unknown, _value: unknown, callback: (error?: Error ) } -function validate() { - return Promise.all([ - formData.value.stt_model_id_type === 'reference' ? modelCascaderRef.value?.validate() : Promise.resolve(), - contentCascaderRef.value?.validate(), - formRef.value?.validate(), - ]).catch((error) => Promise.reject({ node: model, errMessage: error })) +async function validate() { + return formRef.value?.validate().catch((error) => Promise.reject({ node: model, errMessage: error })) } - onMounted(() => { model.validate = validate store.getModelList({ model_type: 'STT' }).then((data) => { diff --git a/ui/src/workflow-canvas/nodes/text-to-speech-node/index.vue b/ui/src/workflow-canvas/nodes/text-to-speech-node/index.vue index 5637fd9d76a..42dc4db0144 100644 --- a/ui/src/workflow-canvas/nodes/text-to-speech-node/index.vue +++ b/ui/src/workflow-canvas/nodes/text-to-speech-node/index.vue @@ -26,8 +26,6 @@ interface TextToSpeechNodeForm { } const formRef = useTemplateRef('formRef') -const modelCascaderRef = useTemplateRef>('modelCascaderRef') -const contentCascaderRef = useTemplateRef>('contentCascaderRef') const store = useWorkflowStore(apiType) const modelList = ref>([]) @@ -90,14 +88,9 @@ function validateModel(_rule: unknown, _value: unknown, callback: (error?: Error ) } -function validate() { - return Promise.all([ - formData.value.tts_model_id_type === 'reference' ? modelCascaderRef.value?.validate() : Promise.resolve(), - contentCascaderRef.value?.validate(), - formRef.value?.validate(), - ]).catch((error) => Promise.reject({ node: model, errMessage: error })) +async function validate() { + return formRef.value?.validate().catch((error) => Promise.reject({ node: model, errMessage: error })) } - onMounted(() => { model.validate = validate store.getModelList({ model_type: 'TTS' }).then((data) => { diff --git a/ui/src/workflow-canvas/nodes/text-to-video-node/index.vue b/ui/src/workflow-canvas/nodes/text-to-video-node/index.vue index 1eca651616e..3b97bf82602 100644 --- a/ui/src/workflow-canvas/nodes/text-to-video-node/index.vue +++ b/ui/src/workflow-canvas/nodes/text-to-video-node/index.vue @@ -29,7 +29,6 @@ interface TextToVideoNodeForm { } const formRef = useTemplateRef('formRef') -const modelCascaderRef = useTemplateRef>('modelCascaderRef') const store = useWorkflowStore(apiType) const modelList = ref>([]) @@ -98,13 +97,9 @@ function validateModel(_rule: unknown, _value: unknown, callback: (error?: Error ) } -function validate() { - return Promise.all([ - formData.value.model_id_type === 'reference' ? modelCascaderRef.value?.validate() : Promise.resolve(), - formRef.value?.validate(), - ]).catch((error) => Promise.reject({ node: model, errMessage: error })) +async function validate() { + return formRef.value?.validate().catch((error) => Promise.reject({ node: model, errMessage: error })) } - onMounted(() => { model.validate = validate store.getModelList({ model_type: 'TTV' }).then((data) => { diff --git a/ui/src/workflow-canvas/nodes/tool-workflow-lib-node/index.vue b/ui/src/workflow-canvas/nodes/tool-workflow-lib-node/index.vue index cdeb3819959..609274642a7 100644 --- a/ui/src/workflow-canvas/nodes/tool-workflow-lib-node/index.vue +++ b/ui/src/workflow-canvas/nodes/tool-workflow-lib-node/index.vue @@ -200,7 +200,6 @@ onMounted(() => { { - \ No newline at end of file + diff --git a/ui/src/workflow-canvas/nodes/variable-aggregation-node/component/GroupFieldDialog.vue b/ui/src/workflow-canvas/nodes/variable-aggregation-node/component/GroupFieldDialog.vue index 10ae75633be..b10db80bcee 100644 --- a/ui/src/workflow-canvas/nodes/variable-aggregation-node/component/GroupFieldDialog.vue +++ b/ui/src/workflow-canvas/nodes/variable-aggregation-node/component/GroupFieldDialog.vue @@ -16,10 +16,8 @@ @@ -74,4 +72,3 @@ const submit = async (formEl: FormInstance | null) => { defineExpose({ open, close }) - diff --git a/ui/src/workflow-canvas/nodes/variable-aggregation-node/index.vue b/ui/src/workflow-canvas/nodes/variable-aggregation-node/index.vue index 8170b66dc15..9a14de16a30 100644 --- a/ui/src/workflow-canvas/nodes/variable-aggregation-node/index.vue +++ b/ui/src/workflow-canvas/nodes/variable-aggregation-node/index.vue @@ -1,100 +1,5 @@ - - +
+
+ {{ group.label }} +
+ + + + + + +
+
+ +
+
+
+ + + +
+ + + + +
+ + + +
+
+
+ + + + 添加 + +
+ + + + 添加分组 + + + + + diff --git a/ui/src/workflow-canvas/nodes/variable-assign-node/index.ts b/ui/src/workflow-canvas/nodes/variable-assign-node/index.ts index b2202f9fefa..afd0467b1d0 100644 --- a/ui/src/workflow-canvas/nodes/variable-assign-node/index.ts +++ b/ui/src/workflow-canvas/nodes/variable-assign-node/index.ts @@ -8,4 +8,12 @@ class VariableAssignNodeView extends WorkflowNodeView { } } -export default { type: WorkflowNodeType.VariableAssignNode, model: WorkflowNodeModel, view: VariableAssignNodeView } +class VariableAssignNodeModel extends WorkflowNodeModel { + override setAttributes() { + super.setAttributes() + this.width = 455 + this.properties.width = 455 + } +} + +export default { type: WorkflowNodeType.VariableAssignNode, model: VariableAssignNodeModel, view: VariableAssignNodeView } diff --git a/ui/src/workflow-canvas/nodes/variable-assign-node/index.vue b/ui/src/workflow-canvas/nodes/variable-assign-node/index.vue index 529f7a5bd27..71b8b3ed8f2 100644 --- a/ui/src/workflow-canvas/nodes/variable-assign-node/index.vue +++ b/ui/src/workflow-canvas/nodes/variable-assign-node/index.vue @@ -1,115 +1,13 @@ - - + + diff --git a/ui/src/workflow-canvas/nodes/variable-splitting-node/component/VariableFieldDialog.vue b/ui/src/workflow-canvas/nodes/variable-splitting-node/component/VariableFieldDialog.vue index 9a5fe1ffe6a..6c56e3c0d19 100644 --- a/ui/src/workflow-canvas/nodes/variable-splitting-node/component/VariableFieldDialog.vue +++ b/ui/src/workflow-canvas/nodes/variable-splitting-node/component/VariableFieldDialog.vue @@ -7,43 +7,38 @@ :close-on-click-modal="false" :close-on-press-escape="false" > - - - + + - - + - + @@ -56,10 +51,6 @@ defineOptions({ name: 'VariableSplittingFieldDialog' }) const emit = defineEmits<{ refresh: [data: { field: string; label: string; expression: string }, index?: number] }>() -const expressionTip = - '请使用 JSON Path 表达式拆分变量,例如:$.store.book ' + - '点击查看详情 ➜ pypi.org' - const fieldFormRef = useTemplateRef('fieldFormRef') const isEdit = ref(false) const currentIndex = ref(undefined) @@ -103,12 +94,3 @@ const submit = async (formEl: FormInstance | null) => { defineExpose({ open, close }) - diff --git a/ui/src/workflow-canvas/nodes/variable-splitting-node/component/VariableFieldTable.vue b/ui/src/workflow-canvas/nodes/variable-splitting-node/component/VariableFieldTable.vue index 2ab2339368f..5d1e7dc0d90 100644 --- a/ui/src/workflow-canvas/nodes/variable-splitting-node/component/VariableFieldTable.vue +++ b/ui/src/workflow-canvas/nodes/variable-splitting-node/component/VariableFieldTable.vue @@ -1,38 +1,39 @@