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 @@ + + +