from __future__ import annotations import json from comfy_execution.graph_utils import ExecutionBlocker class AnyType(str): def __ne__(self, other): return False ANY = AnyType("*") MISSING = object() CATEGORY = "流程分支" LEGACY_CATEGORY = "流程分支/旧版" class FlexibleOptionalInputType(dict): """Allow frontend-defined lazy inputs while keeping known internal inputs typed.""" def __init__(self, dynamic_spec, data=None): super().__init__(data or {}) self.dynamic_spec = dynamic_spec def __getitem__(self, key): return super().__getitem__(key) if dict.__contains__(self, key) else self.dynamic_spec def __contains__(self, key): return True def _blocked(message: str): return (ExecutionBlocker(f"[流程分支] {message}"),) def _compile_error(error: str): return _blocked(error) if error else None class FlowPublish: @classmethod def INPUT_TYPES(cls): return { "required": { "channel": ("STRING", { "default": "原始图像", "multiline": False, "tooltip": "结果名称。同一工作流中每个名称只能有一个发布位置。", }), }, "optional": { "value": (ANY, {"forceInput": True}), }, } RETURN_TYPES = (ANY,) RETURN_NAMES = ("值",) FUNCTION = "publish" CATEGORY = CATEGORY DESCRIPTION = "给任意类型数据一个可读名称;只有被读取时才参与执行。" @classmethod def publish(cls, channel: str, value=MISSING): if value is MISSING: return _blocked(f"发送结果“{channel.strip() or '(空名称)'}”没有输入数据。") return (value,) class FlowGet: @classmethod def INPUT_TYPES(cls): return { "required": { "channel": ("STRING", { "default": "原始图像", "multiline": False, "tooltip": "读取同名发送结果或流程阶段结果。", }), }, "optional": { "fallback": (ANY, {"forceInput": True, "lazy": True}), "source": (ANY, {"forceInput": True, "lazy": True}), "compile_error": ("STRING", {"default": "", "multiline": False}), }, } RETURN_TYPES = (ANY,) RETURN_NAMES = ("值",) FUNCTION = "get" CATEGORY = CATEGORY DESCRIPTION = "读取命名结果;找不到时可使用回退输入。" @classmethod def check_lazy_status(cls, channel: str, fallback=MISSING, source=MISSING, compile_error: str = ""): if compile_error: return [] if source is not MISSING: return ["source"] if source is None else [] if fallback is not MISSING: return ["fallback"] if fallback is None else [] return [] @classmethod def get(cls, channel: str, fallback=MISSING, source=MISSING, compile_error: str = ""): error = _compile_error(compile_error) if error: return error if source is not MISSING: return (source,) if fallback is not MISSING: return (fallback,) return _blocked(f"读取结果“{channel.strip() or '(空名称)'}”找不到发布位置,也没有连接回退输入。") class FlowPipeline: EMPTY_CONFIG = json.dumps({"version": 2, "stages": []}, ensure_ascii=False, separators=(",", ":")) @classmethod def INPUT_TYPES(cls): known_optional = { "source": (ANY, {"forceInput": True, "lazy": True}), "pipeline_result": (ANY, {"forceInput": True, "lazy": True}), "selected_value": (ANY, {"forceInput": True, "lazy": True}), "compile_error": ("STRING", {"default": "", "multiline": False}), "stage_name": ("STRING", {"default": "", "multiline": False}), "selected_name": ("STRING", {"default": "", "multiline": False}), "__stage_internal": ("BOOLEAN", {"default": False}), "__flow_generated": ("BOOLEAN", {"default": False}), } return { "required": { "input_channel": ("STRING", { "default": "原始图像", "multiline": False, "tooltip": "第一个阶段读取的结果名称。", }), "output_channel": ("STRING", { "default": "最终图像", "multiline": False, "tooltip": "整个流程完成后发布的结果名称。", }), "pipeline_config": ("STRING", { "default": cls.EMPTY_CONFIG, "multiline": False, }), }, "optional": FlexibleOptionalInputType( (ANY, {"forceInput": True, "lazy": True}), known_optional, ), } RETURN_TYPES = (ANY,) RETURN_NAMES = ("流程结果",) FUNCTION = "select" CATEGORY = CATEGORY DESCRIPTION = "可无限添加阶段和方案的惰性流程编排器。" @classmethod def check_lazy_status(cls, input_channel: str, output_channel: str, pipeline_config: str, source=MISSING, pipeline_result=MISSING, selected_value=MISSING, compile_error: str = "", **kwargs): if compile_error: return [] if kwargs.get("__stage_internal", False): if selected_value is not MISSING: return ["selected_value"] if selected_value is None else [] if source is not MISSING: return ["source"] if source is None else [] return [] if pipeline_result is not MISSING: return ["pipeline_result"] if pipeline_result is None else [] if source is not MISSING: return ["source"] if source is None else [] return [] @classmethod def select(cls, input_channel: str, output_channel: str, pipeline_config: str, source=MISSING, pipeline_result=MISSING, selected_value=MISSING, compile_error: str = "", **kwargs): error = _compile_error(compile_error) if error: return error if kwargs.get("__stage_internal", False): if selected_value is not MISSING: return (selected_value,) if source is not MISSING: return (source,) stage_name = kwargs.get("stage_name") or output_channel or "未命名阶段" return _blocked(f"阶段“{stage_name}”找不到上一阶段结果。") if pipeline_result is not MISSING: return (pipeline_result,) if source is not MISSING: return (source,) return _blocked(f"流程找不到起点结果“{input_channel.strip() or '(空名称)'}”。") class FlowStage: DEPRECATED = True CATEGORY = LEGACY_CATEGORY @classmethod def INPUT_TYPES(cls): return { "required": { "input_channel": ("STRING", { "default": "上一阶段", "multiline": False, "tooltip": "上一步成功结果的通道。", }), "output_channel": ("STRING", { "default": "本阶段", "multiline": False, "tooltip": "本阶段结果发布到这个通道。", }), "enabled": ("BOOLEAN", { "default": True, "label_on": "启用", "label_off": "旁路", }), }, "optional": { "processed": (ANY, {"forceInput": True, "lazy": True}), "source": (ANY, {"forceInput": True, "lazy": True}), "compile_error": ("STRING", {"default": "", "multiline": False}), }, } RETURN_TYPES = (ANY,) RETURN_NAMES = ("阶段结果",) FUNCTION = "select" CATEGORY = LEGACY_CATEGORY DESCRIPTION = "启用时采用处理结果,关闭或处理结果未连接时传递上一步,并发布输出通道。" @classmethod def check_lazy_status(cls, input_channel: str, output_channel: str, enabled: bool, processed=MISSING, source=MISSING, compile_error: str = ""): if compile_error: return [] if enabled and processed is not MISSING: return ["processed"] if processed is None else [] if source is not MISSING: return ["source"] if source is None else [] return [] @classmethod def select(cls, input_channel: str, output_channel: str, enabled: bool, processed=MISSING, source=MISSING, compile_error: str = ""): error = _compile_error(compile_error) if error: return error if enabled and processed is not MISSING: return (processed,) if source is not MISSING: return (source,) state = "启用但处理结果未连接" if enabled else "处于旁路状态" return _blocked( f"阶段“{input_channel} → {output_channel}”{state},但输入通道“{input_channel}”没有可用数据。" ) class FlowRoute: DEPRECATED = True CATEGORY = LEGACY_CATEGORY ROUTES = ("旁路", "方案 1", "方案 2", "方案 3") OPTION_NAMES = { "方案 1": "option_1", "方案 2": "option_2", "方案 3": "option_3", } @classmethod def INPUT_TYPES(cls): return { "required": { "input_channel": ("STRING", { "default": "上一阶段", "multiline": False, "tooltip": "旁路或方案缺失时使用的上一步通道。", }), "output_channel": ("STRING", { "default": "本阶段", "multiline": False, "tooltip": "最终选择结果发布到这个通道。", }), "route": (cls.ROUTES, {"default": "旁路"}), }, "optional": { "option_1": (ANY, {"forceInput": True, "lazy": True}), "option_2": (ANY, {"forceInput": True, "lazy": True}), "option_3": (ANY, {"forceInput": True, "lazy": True}), "source": (ANY, {"forceInput": True, "lazy": True}), "compile_error": ("STRING", {"default": "", "multiline": False}), }, } RETURN_TYPES = (ANY,) RETURN_NAMES = ("选择结果",) FUNCTION = "select" CATEGORY = LEGACY_CATEGORY DESCRIPTION = "仅执行选中的方案;旁路或所选方案未连接时返回输入通道,并发布输出通道。" @classmethod def check_lazy_status(cls, input_channel: str, output_channel: str, route: str, option_1=MISSING, option_2=MISSING, option_3=MISSING, source=MISSING, compile_error: str = ""): if compile_error: return [] option_name = cls.OPTION_NAMES.get(route) options = { "option_1": option_1, "option_2": option_2, "option_3": option_3, } option_value = options.get(option_name, MISSING) if option_name and option_value is not MISSING: return [option_name] if option_value is None else [] if source is not MISSING: return ["source"] if source is None else [] return [] @classmethod def select(cls, input_channel: str, output_channel: str, route: str, option_1=MISSING, option_2=MISSING, option_3=MISSING, source=MISSING, compile_error: str = ""): error = _compile_error(compile_error) if error: return error options = {"方案 1": option_1, "方案 2": option_2, "方案 3": option_3} selected = options.get(route, MISSING) if selected is not MISSING: return (selected,) if source is not MISSING: return (source,) return _blocked( f"多路方案“{input_channel} → {output_channel}”无法使用“{route}”," f"且输入通道“{input_channel}”没有可回退的数据。" ) class FlowIf: DEPRECATED = True CATEGORY = LEGACY_CATEGORY @classmethod def INPUT_TYPES(cls): return { "required": { "condition": ("BOOLEAN", { "default": True, "label_on": "真", "label_off": "假", }), }, "optional": { "on_true": (ANY, {"forceInput": True, "lazy": True}), "on_false": (ANY, {"forceInput": True, "lazy": True}), }, } RETURN_TYPES = (ANY,) RETURN_NAMES = ("选择结果",) FUNCTION = "select" CATEGORY = LEGACY_CATEGORY DESCRIPTION = "惰性布尔分支;只执行选中分支,选中分支未连接时自动使用另一分支。" @classmethod def check_lazy_status(cls, condition: bool, on_true=MISSING, on_false=MISSING): primary_name, primary = ("on_true", on_true) if condition else ("on_false", on_false) fallback_name, fallback = ("on_false", on_false) if condition else ("on_true", on_true) if primary is not MISSING: return [primary_name] if primary is None else [] if fallback is not MISSING: return [fallback_name] if fallback is None else [] return [] @classmethod def select(cls, condition: bool, on_true=MISSING, on_false=MISSING): primary = on_true if condition else on_false fallback = on_false if condition else on_true if primary is not MISSING: return (primary,) if fallback is not MISSING: return (fallback,) return _blocked("条件选择的两个分支都没有连接。") NODE_CLASS_MAPPINGS = { "FlowBranchPublish": FlowPublish, "FlowBranchGet": FlowGet, "FlowBranchPipeline": FlowPipeline, "FlowBranchStage": FlowStage, "FlowBranchRoute": FlowRoute, "FlowBranchIf": FlowIf, } NODE_DISPLAY_NAME_MAPPINGS = { "FlowBranchPublish": "发送结果", "FlowBranchGet": "读取结果", "FlowBranchPipeline": "流程编排器", "FlowBranchStage": "[旧版] 阶段开关", "FlowBranchRoute": "[旧版] 多路方案", "FlowBranchIf": "[旧版] 条件选择", }