From 49448e3f48a92052c39e4732eb1862e90fde11fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Thu, 11 Jun 2026 10:42:27 +0800 Subject: [PATCH] feat: Add a new node `empty-node` --- apps/application/flow/common.py | 2 +- apps/application/flow/step_node/__init__.py | 3 +- .../flow/step_node/empty_node/__init__.py | 1 + .../flow/step_node/empty_node/i_empty_node.py | 39 +++++++++++++++ .../step_node/empty_node/impl/__init__.py | 1 + .../empty_node/impl/base_empty_node.py | 37 ++++++++++++++ ui/src/assets/workflow/icon_empty.svg | 4 ++ .../execution-detail-card/index.vue | 12 +++++ ui/src/enums/application.ts | 1 + ui/src/locales/lang/en-US/workflow.ts | 6 +++ ui/src/locales/lang/zh-CN/workflow.ts | 6 +++ .../locales/lang/zh-Hant/views/application.ts | 2 +- ui/src/locales/lang/zh-Hant/workflow.ts | 8 ++- ui/src/workflow/common/NodeContainer.vue | 9 +++- ui/src/workflow/common/data.ts | 27 +++++++--- ui/src/workflow/common/validate.ts | 2 + ui/src/workflow/icons/empty-node-icon.vue | 6 +++ ui/src/workflow/nodes/empty-node/index.ts | 14 ++++++ ui/src/workflow/nodes/empty-node/index.vue | 49 +++++++++++++++++++ 19 files changed, 216 insertions(+), 13 deletions(-) create mode 100644 apps/application/flow/step_node/empty_node/__init__.py create mode 100644 apps/application/flow/step_node/empty_node/i_empty_node.py create mode 100644 apps/application/flow/step_node/empty_node/impl/__init__.py create mode 100644 apps/application/flow/step_node/empty_node/impl/base_empty_node.py create mode 100644 ui/src/assets/workflow/icon_empty.svg create mode 100644 ui/src/workflow/icons/empty-node-icon.vue create mode 100644 ui/src/workflow/nodes/empty-node/index.ts create mode 100644 ui/src/workflow/nodes/empty-node/index.vue diff --git a/apps/application/flow/common.py b/apps/application/flow/common.py index d7520cf690c..1b126435585 100644 --- a/apps/application/flow/common.py +++ b/apps/application/flow/common.py @@ -19,7 +19,7 @@ from models_provider.tools import get_model_credential from tools.models.tool import Tool -end_nodes = ['ai-chat-node', 'reply-node', 'function-node', 'function-lib-node', 'application-node', +end_nodes = ['ai-chat-node', 'reply-node', 'empty-node', 'function-node', 'function-lib-node', 'application-node', 'image-understand-node', 'speech-to-text-node', 'text-to-speech-node', 'image-generate-node', 'variable-assign-node'] diff --git a/apps/application/flow/step_node/__init__.py b/apps/application/flow/step_node/__init__.py index 4c38020771e..b6951f8966e 100644 --- a/apps/application/flow/step_node/__init__.py +++ b/apps/application/flow/step_node/__init__.py @@ -13,6 +13,7 @@ from .data_source_web_node.impl.base_data_source_web_node import BaseDataSourceWebNode from .direct_reply_node import * from .document_extract_node import * +from .empty_node import BaseEmptyNode from .form_node import * from .image_generate_step_node import * from .image_to_video_step_node import BaseImageToVideoNode @@ -44,7 +45,7 @@ from .tool_start_node import BaseToolStartStepNode node_list = [BaseStartStepNode, BaseChatNode, BaseSearchKnowledgeNode, BaseSearchDocumentNode, BaseQuestionNode, - BaseConditionNode, BaseReplyNode, + BaseConditionNode, BaseReplyNode, BaseEmptyNode, BaseToolNodeNode, BaseToolLibNodeNode, BaseRerankerNode, BaseApplicationNode, BaseDocumentExtractNode, BaseImageUnderstandNode, BaseFormNode, BaseSpeechToTextNode, BaseTextToSpeechNode, diff --git a/apps/application/flow/step_node/empty_node/__init__.py b/apps/application/flow/step_node/empty_node/__init__.py new file mode 100644 index 00000000000..8fa7a380ddc --- /dev/null +++ b/apps/application/flow/step_node/empty_node/__init__.py @@ -0,0 +1 @@ +from .impl import BaseEmptyNode diff --git a/apps/application/flow/step_node/empty_node/i_empty_node.py b/apps/application/flow/step_node/empty_node/i_empty_node.py new file mode 100644 index 00000000000..f94134ab4f5 --- /dev/null +++ b/apps/application/flow/step_node/empty_node/i_empty_node.py @@ -0,0 +1,39 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:AI Assistant + @file: i_empty_node.py + @date:2026/06/10 + @desc: 空节点接口定义 - 用于流程判断的ELSE分支占位 +""" +from typing import Type + +from rest_framework import serializers + +from application.flow.common import WorkflowMode +from application.flow.i_step_node import INode, NodeResult + + +class EmptyNodeParamsSerializer(serializers.Serializer): + """空节点参数序列化器 - 无需任何参数""" + + def is_valid(self, *, raise_exception=False): + # 空节点不需要验证任何参数,直接返回 True + return True + + +class IEmptyNode(INode): + """空节点接口""" + type = 'empty-node' + support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, + WorkflowMode.KNOWLEDGE, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP] + + def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: + return EmptyNodeParamsSerializer + + def _run(self): + return self.execute() + + def execute(self, **kwargs) -> NodeResult: + """执行空节点 - 不产生任何输出""" + return NodeResult({}, {}) diff --git a/apps/application/flow/step_node/empty_node/impl/__init__.py b/apps/application/flow/step_node/empty_node/impl/__init__.py new file mode 100644 index 00000000000..5d5a92f400f --- /dev/null +++ b/apps/application/flow/step_node/empty_node/impl/__init__.py @@ -0,0 +1 @@ +from .base_empty_node import BaseEmptyNode diff --git a/apps/application/flow/step_node/empty_node/impl/base_empty_node.py b/apps/application/flow/step_node/empty_node/impl/base_empty_node.py new file mode 100644 index 00000000000..b010d031015 --- /dev/null +++ b/apps/application/flow/step_node/empty_node/impl/base_empty_node.py @@ -0,0 +1,37 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:AI Assistant + @file: base_empty_node.py + @date:2026/06/10 + @desc: 空节点实现 - 用于流程判断的ELSE分支占位 +""" +from application.flow.i_step_node import NodeResult +from application.flow.step_node.empty_node.i_empty_node import IEmptyNode + + +class BaseEmptyNode(IEmptyNode): + """空节点实现类""" + + def save_context(self, details, workflow_manage): + """保存上下文 - 空节点无需保存任何内容""" + self.context['exception_message'] = details.get('err_message') + + def execute(self, **kwargs) -> NodeResult: + """ + 执行空节点 + 空节点不产生任何输出,仅作为流程占位符 + """ + return NodeResult({}, {}) + + def get_details(self, index: int, **kwargs): + """获取节点执行详情""" + return { + 'name': self.node.properties.get('stepName'), + "index": index, + 'run_time': self.context.get('run_time'), + 'type': self.node.type, + 'status': self.status, + 'err_message': self.err_message, + 'enableException': self.node.properties.get('enableException'), + } diff --git a/ui/src/assets/workflow/icon_empty.svg b/ui/src/assets/workflow/icon_empty.svg new file mode 100644 index 00000000000..62b802097a0 --- /dev/null +++ b/ui/src/assets/workflow/icon_empty.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ui/src/components/execution-detail-card/index.vue b/ui/src/components/execution-detail-card/index.vue index f163b40c7a3..f33042dd421 100644 --- a/ui/src/components/execution-detail-card/index.vue +++ b/ui/src/components/execution-detail-card/index.vue @@ -351,6 +351,18 @@ + + +