diff --git a/__init__.py b/__init__.py index 67a6071..e7adc0c 100644 --- a/__init__.py +++ b/__init__.py @@ -31,35 +31,73 @@ from .core.log_system.config import LOG_LEVEL as BACKEND_DEFAULT_LOG_LEVEL WEB_DIRECTORY = "./web" MODEL_RESOLVER_DEPENDENCY_NODE_TYPE = "ModelResolverDependency" +MODEL_RESOLVER_DEPENDENCY_NODE_DISPLAY_NAME = "Model Resolver Opener" +MODEL_RESOLVER_DEPENDENCY_NODE_CATEGORY = "Model Resolver/Workflow" +MODEL_RESOLVER_DEPENDENCY_NODE_DESCRIPTION = ( + "A passive opener node for workflows that intentionally depend on " + "Model Resolver metadata. It does not process images, models, or prompts." +) + +try: + from comfy_api.latest import ComfyExtension, io +except Exception: + ComfyExtension = None + io = None -class ModelResolverDependencyNode: - """Canvas node that declares Model Resolver and opens its workflow tools.""" +if ComfyExtension is not None and io is not None: - DESCRIPTION = ( - "A passive marker node for workflows that intentionally depend on " - "Model Resolver metadata. It does not process images, models, or prompts." - ) - RETURN_TYPES = () - FUNCTION = "noop" - CATEGORY = "Model Resolver/Workflow" + class ModelResolverDependencyNode(io.ComfyNode): + """Canvas node that declares Model Resolver and opens its workflow tools.""" - @classmethod - def INPUT_TYPES(cls): - return {"required": {}} + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id=MODEL_RESOLVER_DEPENDENCY_NODE_TYPE, + display_name=MODEL_RESOLVER_DEPENDENCY_NODE_DISPLAY_NAME, + category=MODEL_RESOLVER_DEPENDENCY_NODE_CATEGORY, + description=MODEL_RESOLVER_DEPENDENCY_NODE_DESCRIPTION, + inputs=[], + outputs=[], + ) - def noop(self): - return () + @classmethod + def execute(cls) -> io.NodeOutput: + return io.NodeOutput() + class ModelResolverNodeExtension(ComfyExtension): + async def get_node_list(self) -> list[type[io.ComfyNode]]: + return [ModelResolverDependencyNode] -NODE_CLASS_MAPPINGS = { - MODEL_RESOLVER_DEPENDENCY_NODE_TYPE: ModelResolverDependencyNode, -} -NODE_DISPLAY_NAME_MAPPINGS = { - MODEL_RESOLVER_DEPENDENCY_NODE_TYPE: "Model Resolver Opener", -} + async def comfy_entrypoint() -> ComfyExtension: + return ModelResolverNodeExtension() -__all__ = ["WEB_DIRECTORY", "NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] + __all__ = ["WEB_DIRECTORY", "comfy_entrypoint"] +else: + + class ModelResolverDependencyNode: + """Legacy fallback for ComfyUI builds without the v3 custom-node API.""" + + DESCRIPTION = MODEL_RESOLVER_DEPENDENCY_NODE_DESCRIPTION + RETURN_TYPES = () + FUNCTION = "noop" + CATEGORY = MODEL_RESOLVER_DEPENDENCY_NODE_CATEGORY + + @classmethod + def INPUT_TYPES(cls): + return {"required": {}} + + def noop(self): + return () + + NODE_CLASS_MAPPINGS = { + MODEL_RESOLVER_DEPENDENCY_NODE_TYPE: ModelResolverDependencyNode, + } + NODE_DISPLAY_NAME_MAPPINGS = { + MODEL_RESOLVER_DEPENDENCY_NODE_TYPE: MODEL_RESOLVER_DEPENDENCY_NODE_DISPLAY_NAME, + } + + __all__ = ["WEB_DIRECTORY", "NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] class JobProgressTracker: diff --git a/web/resolver.js b/web/resolver.js index ac72f3d..220ece9 100644 --- a/web/resolver.js +++ b/web/resolver.js @@ -29,7 +29,16 @@ app.registerExtension({ MODEL_RESOLVER_OPEN_DEFAULT_KEYBINDING, ], setup: modelResolver.setup, + beforeRegisterNodeDef(nodeType, nodeData) { + modelResolver.configureWorkflowDependencyMarkerNodeType(nodeType, nodeData); + }, nodeCreated(node) { modelResolver.configureWorkflowDependencyMarkerNode(node); }, + loadedGraphNode(node) { + modelResolver.configureWorkflowDependencyMarkerNode(node); + }, + afterConfigureGraph() { + modelResolver.configureWorkflowDependencyMarkerNodes(); + }, }); diff --git a/web/resolver/model_resolver.js b/web/resolver/model_resolver.js index 75d55de..fb6fc7d 100644 --- a/web/resolver/model_resolver.js +++ b/web/resolver/model_resolver.js @@ -543,6 +543,7 @@ export class ModelResolver { window.__ModelResolverWorkflowHashMetadataOwner = this; if (window.__ModelResolverWorkflowHashMetadataPatched) { this.scheduleWorkflowHashMetadataRefresh(); + this.configureWorkflowDependencyMarkerNodes(); return; } @@ -559,6 +560,7 @@ export class ModelResolver { return workflow; }; window.__ModelResolverWorkflowHashMetadataPatched = true; + this.configureWorkflowDependencyMarkerNodes(); this.scheduleWorkflowHashMetadataRefresh(); } @@ -568,8 +570,60 @@ export class ModelResolver { return workflow; } + isWorkflowDependencyMarkerNode(node) { + if (!node) return false; + const candidates = [ + node.type, + node.comfyClass, + node.comfy_class, + node.constructor?.ComfyClass, + node.constructor?.comfyClass, + node.constructor?.nodeData?.name, + ]; + return candidates.some((candidate) => candidate === WORKFLOW_DEPENDENCY_MARKER_NODE_TYPE); + } + + isWorkflowDependencyMarkerNodeType(nodeType, nodeData) { + const candidates = [ + nodeData?.name, + nodeData?.node_id, + nodeData?.class_type, + nodeType?.ComfyClass, + nodeType?.comfyClass, + nodeType?.type, + nodeType?.prototype?.comfyClass, + ]; + return candidates.some((candidate) => candidate === WORKFLOW_DEPENDENCY_MARKER_NODE_TYPE); + } + + configureWorkflowDependencyMarkerNodeType(nodeType, nodeData) { + if (!this.isWorkflowDependencyMarkerNodeType(nodeType, nodeData)) return; + const proto = nodeType?.prototype; + if (!proto || proto.__modelResolverDependencyMarkerTypePatched) return; + + const resolver = this; + const originalOnNodeCreated = proto.onNodeCreated; + proto.onNodeCreated = function(...args) { + const result = originalOnNodeCreated?.apply(this, args); + resolver.configureWorkflowDependencyMarkerNode(this); + return result; + }; + + const originalComputeSize = proto.computeSize; + proto.computeSize = function(...args) { + const size = typeof originalComputeSize === 'function' + ? originalComputeSize.apply(this, args) + : this.size; + const width = Math.max(Number(size?.[0]) || 0, 260); + const height = Math.max(Number(size?.[1]) || 0, 110); + return [width, height]; + }; + + proto.__modelResolverDependencyMarkerTypePatched = true; + } + configureWorkflowDependencyMarkerNode(node) { - if (!node || node.type !== WORKFLOW_DEPENDENCY_MARKER_NODE_TYPE) return node; + if (!this.isWorkflowDependencyMarkerNode(node)) return node; node.properties = node.properties && typeof node.properties === 'object' ? node.properties : {}; delete node.properties.cnr_id; @@ -587,6 +641,7 @@ export class ModelResolver { ); if (existingOpenButton) { existingOpenButton.callback = () => this.activateResolverButton(); + existingOpenButton.value = WORKFLOW_DEPENDENCY_MARKER_BUTTON_NAME; existingOpenButton.__modelResolverOpenButton = true; existingOpenButton.options = existingOpenButton.options && typeof existingOpenButton.options === 'object' ? existingOpenButton.options @@ -596,7 +651,7 @@ export class ModelResolver { const button = node.addWidget( "button", WORKFLOW_DEPENDENCY_MARKER_BUTTON_NAME, - null, + WORKFLOW_DEPENDENCY_MARKER_BUTTON_NAME, () => this.activateResolverButton(), { serialize: false } ); @@ -608,8 +663,15 @@ export class ModelResolver { } if (Array.isArray(node.size)) { - node.size[0] = Math.max(Number(node.size[0]) || 0, 260); - node.size[1] = Math.max(Number(node.size[1]) || 0, 90); + const size = [ + Math.max(Number(node.size[0]) || 0, 260), + Math.max(Number(node.size[1]) || 0, 110), + ]; + if (typeof node.setSize === 'function') { + node.setSize(size); + } else { + node.size = size; + } } if (!node.__modelResolverDependencySerializePatched) { @@ -624,9 +686,19 @@ export class ModelResolver { node.__modelResolverDependencySerializePatched = true; } + node.graph?.setDirtyCanvas?.(true, true); + app.canvas?.setDirty?.(true, true); return node; } + configureWorkflowDependencyMarkerNodes(graph = app?.graph) { + const nodes = graph?._nodes || graph?.nodes || []; + if (!Array.isArray(nodes)) return; + for (const node of nodes) { + this.configureWorkflowDependencyMarkerNode(node); + } + } + configureSerializedDependencyMarkerNode(node) { if (!node || node.type !== WORKFLOW_DEPENDENCY_MARKER_NODE_TYPE) return node; @@ -654,7 +726,7 @@ export class ModelResolver { findWorkflowDependencyMarkerNode(graph = app?.graph) { const nodes = graph?._nodes || graph?.nodes || []; return Array.isArray(nodes) - ? nodes.find((node) => node?.type === WORKFLOW_DEPENDENCY_MARKER_NODE_TYPE) || null + ? nodes.find((node) => this.isWorkflowDependencyMarkerNode(node)) || null : null; }