249 lines
8.7 KiB
Python
249 lines
8.7 KiB
Python
"""
|
|
条件分支节点
|
|
"""
|
|
import logging
|
|
import operator
|
|
from typing import Any, Dict
|
|
|
|
from ..base import BaseNode, NodeContext, NodeResult
|
|
from ..registry import NodeRegistry
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@NodeRegistry.register
|
|
class ConditionNode(BaseNode):
|
|
"""
|
|
条件分支节点
|
|
|
|
根据条件判断选择不同的分支
|
|
"""
|
|
|
|
node_type = 'condition'
|
|
node_name = '条件分支'
|
|
node_category = 'logic'
|
|
node_icon = 'git-branch'
|
|
node_description = '根据条件判断选择不同的执行分支'
|
|
|
|
supports_branches = True
|
|
|
|
inputs = [
|
|
{
|
|
'name': 'value',
|
|
'type': 'any',
|
|
'description': '要判断的值',
|
|
},
|
|
]
|
|
|
|
outputs = [
|
|
{
|
|
'name': 'branch',
|
|
'type': 'string',
|
|
'description': '选中的分支',
|
|
},
|
|
]
|
|
|
|
# 支持的操作符
|
|
OPERATORS = {
|
|
'eq': operator.eq, # 等于
|
|
'ne': operator.ne, # 不等于
|
|
'gt': operator.gt, # 大于
|
|
'gte': operator.ge, # 大于等于
|
|
'lt': operator.lt, # 小于
|
|
'lte': operator.le, # 小于等于
|
|
'contains': lambda a, b: b in str(a), # 包含
|
|
'not_contains': lambda a, b: b not in str(a), # 不包含
|
|
'starts_with': lambda a, b: str(a).startswith(str(b)), # 开头是
|
|
'ends_with': lambda a, b: str(a).endswith(str(b)), # 结尾是
|
|
'is_empty': lambda a, _: not a, # 为空
|
|
'is_not_empty': lambda a, _: bool(a), # 不为空
|
|
}
|
|
|
|
def execute(self, context: NodeContext) -> NodeResult:
|
|
"""执行条件判断"""
|
|
try:
|
|
branches = self.config.get('branches', [])
|
|
|
|
# 遍历所有 IF 分支
|
|
for branch in branches:
|
|
branch_id = branch.get('id')
|
|
conditions = branch.get('conditions', [])
|
|
|
|
# 评估该分支的所有条件
|
|
if self._evaluate_branch(conditions, context):
|
|
return NodeResult(
|
|
success=True,
|
|
output=branch_id,
|
|
next_node_id=branch_id,
|
|
metadata={'matched_branch': branch_id},
|
|
)
|
|
|
|
# 没有匹配的条件,走 ELSE 分支
|
|
return NodeResult(
|
|
success=True,
|
|
output='else',
|
|
next_node_id='else',
|
|
metadata={'matched_branch': 'else'},
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.exception(f'条件节点执行失败: {e}')
|
|
return NodeResult(
|
|
success=False,
|
|
error=str(e),
|
|
)
|
|
|
|
def _evaluate_branch(self, conditions: list, context: NodeContext) -> bool:
|
|
"""评估分支的一组条件 (AND 关系)"""
|
|
if not conditions:
|
|
return True # 无条件默认为真
|
|
|
|
for condition in conditions:
|
|
if not self._evaluate_condition(condition, context):
|
|
return False
|
|
return True
|
|
|
|
def _resolve_variable(self, variable: str, context: NodeContext) -> Any:
|
|
"""
|
|
解析变量引用
|
|
|
|
支持格式:
|
|
- {{variable_name}} - 直接变量引用
|
|
- {{NodeID.key}} - 节点输出引用
|
|
- 普通字符串 - 直接返回
|
|
"""
|
|
if not isinstance(variable, str):
|
|
return variable
|
|
|
|
# 检查是否是 {{...}} 格式
|
|
if not (variable.startswith('{{') and variable.endswith('}}')):
|
|
return variable
|
|
|
|
content = variable[2:-2].strip()
|
|
|
|
logger.info(f'解析变量: {variable}, content={content}, 上下文变量keys={list(context.variables.keys())}')
|
|
|
|
# 尝试解析 NodeID.key 格式
|
|
if '.' in content:
|
|
parts = content.split('.', 1)
|
|
node_id = parts[0]
|
|
var_key = parts[1]
|
|
|
|
logger.info(f'解析节点变量: node_id={node_id}, var_key={var_key}')
|
|
|
|
# 先尝试从节点输出命名空间获取
|
|
node_outputs = context.get_variable(f'_node_{node_id}')
|
|
logger.info(f'节点输出 _node_{node_id}: {node_outputs}')
|
|
if isinstance(node_outputs, dict) and var_key in node_outputs:
|
|
return node_outputs[var_key]
|
|
|
|
# 回退:尝试直接从变量获取
|
|
node_data = context.get_variable(node_id)
|
|
logger.info(f'直接变量 {node_id}: {node_data}')
|
|
if isinstance(node_data, dict) and var_key in node_data:
|
|
return node_data[var_key]
|
|
|
|
# 直接变量引用
|
|
value = context.get_variable(content)
|
|
if value is not None:
|
|
return value
|
|
|
|
return None
|
|
|
|
def _evaluate_condition(self, condition: Dict, context: NodeContext) -> bool:
|
|
"""评估单个条件"""
|
|
variable = condition.get('variable', '')
|
|
op_name = condition.get('operator', 'equals')
|
|
value = condition.get('value', '')
|
|
|
|
# 解析变量和值
|
|
left_val = self._resolve_variable(variable, context)
|
|
right_val = self._resolve_variable(value, context)
|
|
|
|
logger.info(f'条件评估: variable={variable}, left_val={left_val}, op={op_name}, right_val={right_val}')
|
|
|
|
# 映射操作符
|
|
op_mapping = {
|
|
'equals': 'eq',
|
|
'not_equals': 'ne',
|
|
}
|
|
op_key = op_mapping.get(op_name, op_name)
|
|
op_func = self.OPERATORS.get(op_key, operator.eq)
|
|
|
|
try:
|
|
# 特殊处理空值判断,不需要右值
|
|
if op_key in ['is_empty', 'is_not_empty']:
|
|
return op_func(left_val, None)
|
|
|
|
# 布尔值比较:将字符串 "true"/"false" 转换为布尔值
|
|
if isinstance(left_val, bool) and isinstance(right_val, str):
|
|
if right_val.lower() in ['true', '1', 'yes']:
|
|
right_val = True
|
|
elif right_val.lower() in ['false', '0', 'no']:
|
|
right_val = False
|
|
elif isinstance(right_val, bool) and isinstance(left_val, str):
|
|
if left_val.lower() in ['true', '1', 'yes']:
|
|
left_val = True
|
|
elif left_val.lower() in ['false', '0', 'no']:
|
|
left_val = False
|
|
|
|
# 尝试转换类型以进行比较 (如数字)
|
|
if isinstance(left_val, (int, float)) and isinstance(right_val, str):
|
|
try:
|
|
if '.' in right_val:
|
|
right_val = float(right_val)
|
|
else:
|
|
right_val = int(right_val)
|
|
except:
|
|
pass
|
|
|
|
# 字符串比较忽略类型差异
|
|
if op_key in ['contains', 'not_contains', 'starts_with', 'ends_with']:
|
|
return op_func(str(left_val), str(right_val))
|
|
|
|
return op_func(left_val, right_val)
|
|
except Exception as e:
|
|
logger.warning(f'条件评估失败: {e}')
|
|
return False
|
|
|
|
@classmethod
|
|
def get_config_schema(cls) -> Dict[str, Any]:
|
|
"""获取配置 Schema"""
|
|
return {
|
|
'type': 'object',
|
|
'properties': {
|
|
'conditions': {
|
|
'type': 'array',
|
|
'title': '条件列表',
|
|
'items': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'variable': {
|
|
'type': 'string',
|
|
'title': '变量名',
|
|
},
|
|
'operator': {
|
|
'type': 'string',
|
|
'title': '操作符',
|
|
'enum': list(cls.OPERATORS.keys()),
|
|
'default': 'eq',
|
|
},
|
|
'value': {
|
|
'type': 'string',
|
|
'title': '比较值',
|
|
},
|
|
'branch_id': {
|
|
'type': 'string',
|
|
'title': '分支 ID',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'default_branch': {
|
|
'type': 'string',
|
|
'title': '默认分支',
|
|
'description': '没有条件匹配时执行的分支',
|
|
},
|
|
},
|
|
}
|