Improve Model Resolver dependency marker node compatibility and rendering

- Adopt ComfyUI's ComfyExtension/comfy_entrypoint API in __init__.py when
  available, falling back to legacy NODE_CLASS_MAPPINGS/DISPLAY_NAME_MAPPINGS
  on older runtimes so the node loads across ComfyUI versions.
- Hook into beforeRegisterNodeDef, loadedGraphNode, and afterConfigureGraph
  in web/resolver.js so marker nodes are patched during registration, graph
  load, and initial render.
- Teach model_resolver.js to recognise the marker node by every known class
  property shape, patch its type on creation, enforce a minimum size with
  setSize() when present, and dirty the canvas so changes are reflected.
This commit is contained in:
Dariusz L
2026-07-06 21:59:30 +02:00
parent c40cbafc00
commit 6f9aa9cc55
3 changed files with 145 additions and 26 deletions
+59 -21
View File
@@ -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:
+9
View File
@@ -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();
},
});
+77 -5
View File
@@ -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;
}