Scaffolds the toolkit with a scalable layout (src/deepsoch_toolkit package, per-category node subfolders, shared core helpers, shipped vs user-writable preset directories) and ships four nodes: * Prompt Preset (deepsoch/prompt) — checkbox-based multi-select over a preset library, with select_all toggle and optional custom prompt; outputs as a STRING list so downstream auto-runs once per prompt. * Load Images (deepsoch/image) — upload-multi widget with native-style multi-image preview (grid + click-to-fullscreen, X close, counter cycling) mirroring ComfyUI's SaveImage UX via onDrawBackground. * Set Node / Get Node (deepsoch/util) — wireless reroutes implemented as Python stubs enhanced by JS into virtual nodes; getInputLink / getInputNode redirect graph traversal through the matching Set. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
183 lines
6.4 KiB
JavaScript
183 lines
6.4 KiB
JavaScript
// DeepSoch Toolkit — Set / Get virtual nodes (wireless reroutes).
|
|
//
|
|
// The Python side (`nodes/util/get_set.py`) registers two stub classes
|
|
// purely so ComfyUI's menu / library panel show them under the
|
|
// `deepsoch/util` category with friendly display names ("Set Node" /
|
|
// "Get Node"). All real behavior — virtual routing, type adoption,
|
|
// dropdown of available Set names — is added here.
|
|
//
|
|
// Pattern (rgthree-comfy / KJNodes style):
|
|
// * Each node instance is marked `isVirtualNode = true`, so the
|
|
// prompt builder strips it out before sending to the backend.
|
|
// * `getInputLink` / `getInputNode` on Get nodes are overridden so
|
|
// graph traversal jumps straight to whatever feeds the matching Set
|
|
// node — making the pair behave as a transparent rewire.
|
|
|
|
import { app } from "../../scripts/app.js";
|
|
|
|
const SET_TYPE = "DeepSochSetNode";
|
|
const GET_TYPE = "DeepSochGetNode";
|
|
|
|
const SET_COLOR = "#2a4d3a";
|
|
const GET_COLOR = "#2a3a4d";
|
|
|
|
function findSetNodeByName(graph, name) {
|
|
if (!graph || !name) return null;
|
|
for (const n of graph._nodes) {
|
|
if (n.type === SET_TYPE) {
|
|
const w = n.widgets?.find((w) => w.name === "name");
|
|
if (w?.value === name) return n;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function listSetNames(graph) {
|
|
if (!graph) return [""];
|
|
const names = [];
|
|
for (const n of graph._nodes) {
|
|
if (n.type === SET_TYPE) {
|
|
const v = n.widgets?.find((w) => w.name === "name")?.value;
|
|
if (v) names.push(v);
|
|
}
|
|
}
|
|
return names.length ? names : [""];
|
|
}
|
|
|
|
function refreshAllGetNodes(graph) {
|
|
if (!graph) return;
|
|
for (const n of graph._nodes) {
|
|
if (n.type === GET_TYPE && typeof n.refreshType === "function") {
|
|
n.refreshType();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----- Set node patch -----
|
|
function patchSetNode(nodeType) {
|
|
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
onNodeCreated?.apply(this, arguments);
|
|
this.isVirtualNode = true;
|
|
this.color = SET_COLOR;
|
|
this.bgcolor = "#1a1a1a";
|
|
|
|
// Refresh dependent Get nodes whenever the name changes.
|
|
const nameWidget = this.widgets?.find((w) => w.name === "name");
|
|
if (nameWidget) {
|
|
const orig = nameWidget.callback;
|
|
nameWidget.callback = (v) => {
|
|
refreshAllGetNodes(this.graph);
|
|
return orig?.(v);
|
|
};
|
|
}
|
|
};
|
|
|
|
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
|
nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info) {
|
|
onConnectionsChange?.apply(this, arguments);
|
|
// Adopt the type of whatever feeds our `value` input so downstream
|
|
// type-checks (through any matching Get) keep working.
|
|
if (type === LiteGraph.INPUT && index === 0) {
|
|
if (connected && link_info) {
|
|
const sourceNode = this.graph?._nodes_by_id?.[link_info.origin_id];
|
|
const t = sourceNode?.outputs?.[link_info.origin_slot]?.type;
|
|
if (t) {
|
|
this.inputs[0].type = t;
|
|
if (this.outputs?.[0]) this.outputs[0].type = t;
|
|
refreshAllGetNodes(this.graph);
|
|
}
|
|
} else if (!connected) {
|
|
this.inputs[0].type = "*";
|
|
if (this.outputs?.[0]) this.outputs[0].type = "*";
|
|
refreshAllGetNodes(this.graph);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// ----- Get node patch -----
|
|
function patchGetNode(nodeType) {
|
|
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
onNodeCreated?.apply(this, arguments);
|
|
this.isVirtualNode = true;
|
|
this.color = GET_COLOR;
|
|
this.bgcolor = "#1a1a1a";
|
|
|
|
// Replace the Python-declared STRING `name` widget with a combo
|
|
// populated from all Set node names currently in the graph.
|
|
const stringWidgetIdx = this.widgets?.findIndex((w) => w.name === "name");
|
|
if (stringWidgetIdx !== -1) {
|
|
const old = this.widgets[stringWidgetIdx];
|
|
const previousValue = old.value || "";
|
|
this.widgets.splice(stringWidgetIdx, 1);
|
|
const node = this;
|
|
const combo = this.addWidget(
|
|
"combo",
|
|
"name",
|
|
previousValue,
|
|
() => node.refreshType(),
|
|
{ values: () => listSetNames(node.graph) },
|
|
);
|
|
// Move it back to the original slot so layout stays predictable.
|
|
this.widgets.pop();
|
|
this.widgets.splice(stringWidgetIdx, 0, combo);
|
|
}
|
|
|
|
this.refreshType();
|
|
};
|
|
|
|
nodeType.prototype.findSetNode = function () {
|
|
const name = this.widgets?.find((w) => w.name === "name")?.value;
|
|
return findSetNodeByName(this.graph, name);
|
|
};
|
|
|
|
nodeType.prototype.refreshType = function () {
|
|
const setNode = this.findSetNode();
|
|
if (!setNode) {
|
|
if (this.outputs?.[0]) this.outputs[0].type = "*";
|
|
this.setDirtyCanvas?.(true, true);
|
|
return;
|
|
}
|
|
const t = setNode.inputs?.[0]?.type;
|
|
if (this.outputs?.[0]) {
|
|
this.outputs[0].type = t && t !== "*" ? t : "*";
|
|
}
|
|
this.setDirtyCanvas?.(true, true);
|
|
};
|
|
|
|
// Core trick: when ComfyUI walks backwards from a downstream node
|
|
// through this Get, redirect the traversal to whatever is feeding
|
|
// the matching Set node's input.
|
|
nodeType.prototype.getInputLink = function (_slot) {
|
|
const setNode = this.findSetNode();
|
|
if (!setNode) return null;
|
|
const linkId = setNode.inputs?.[0]?.link;
|
|
if (linkId == null) return null;
|
|
return this.graph.links[linkId] ?? null;
|
|
};
|
|
|
|
nodeType.prototype.getInputNode = function (_slot) {
|
|
const setNode = this.findSetNode();
|
|
if (!setNode) return null;
|
|
const linkId = setNode.inputs?.[0]?.link;
|
|
if (linkId == null) return null;
|
|
const link = this.graph.links[linkId];
|
|
if (!link) return null;
|
|
return this.graph._nodes_by_id[link.origin_id] ?? null;
|
|
};
|
|
}
|
|
|
|
app.registerExtension({
|
|
name: "deepsoch.toolkit.GetSet",
|
|
|
|
async beforeRegisterNodeDef(nodeType, nodeData) {
|
|
if (nodeData.name === SET_TYPE) {
|
|
patchSetNode(nodeType);
|
|
} else if (nodeData.name === GET_TYPE) {
|
|
patchGetNode(nodeType);
|
|
}
|
|
},
|
|
});
|