Files

598 lines
19 KiB
JavaScript

import { app } from "../../../scripts/app.js";
import * as lib0246 from "./utils.js";
export function monitor_value(obj, key) {
return lib0246.hijack_value(obj, key, (newValue, originalValue) => {
console.log(`${key} changed from ${lib0246.text_snip(String(originalValue), 20)} to ${lib0246.text_snip(String(newValue), 20)}`);
if (window.debug_flag) {
console.trace();
// debugger;
}
return newValue;
});
}
export function monitor_array(obj, key) {
let originalArray = obj[key];
function createLoggingArray(array) {
let loggingArray = [];
array.forEach((value, index) => {
Object.defineProperty(loggingArray, index, {
get: function () {
return value;
},
set: function (newValue) {
console.log(`${key}[${index}] changed from ${value} to ${newValue}`);
if (window.debug_flag) {
console.trace();
// debugger;
}
value = newValue;
},
enumerable: true,
configurable: true
});
});
return loggingArray;
}
let loggingArray = createLoggingArray(originalArray);
Object.defineProperty(obj, key, {
get: function () {
return loggingArray;
},
set: function (newArray) {
console.log(`${key} changed from [${originalArray}] to [${newArray}]`);
if (window.debug_flag) {
console.trace();
// debugger;
}
originalArray = newArray.slice();
loggingArray = createLoggingArray(originalArray);
},
enumerable: true,
configurable: true
});
obj.___MARK___ = true;
let flag = false;
lib0246.hijack(window, "structuredClone", (obj) => {
if (!this.mark) {
if (obj && obj.___MARK___)
flag = true;
} else if (flag) {
monitorArray(obj, key);
obj.___MARK___ = true;
flag = false;
}
});
}
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
function tree_traversal(startNode, inputAccessCallback, outputAccessCallback, isBranchCallback, rootCallback, leafCallback, branchCallback, errorCallback) {
// Helper function to find the root by tracing inputs
function findRoot(node, inputAccessCallback, visited) {
let currentNode = node;
while (true) {
if (visited.has(currentNode)) {
// A cycle is detected, so there's no root
errorCallback(currentNode);
return null;
}
visited.add(currentNode);
const parentNode = inputAccessCallback(currentNode);
if (parentNode === null)
return currentNode;
currentNode = parentNode;
}
}
// First pass to collect all nodes starting from root
function collectFromRoot(rootNode, outputAccessCallback, visited) {
const stack = [rootNode];
const collectedNodes = new Set();
while (stack.length > 0) {
const node = stack.pop();
if (!collectedNodes.has(node)) {
collectedNodes.add(node);
visited.add(node); // Mark this node as visited
// Add child nodes to the stack
stack.push(...outputAccessCallback(node));
}
}
return collectedNodes;
}
// Second pass to apply callbacks in the required order
function applyCallbacks(nodes, isBranchCallback, rootCallback, leafCallback, branchCallback, inputAccessCallback, outputAccessCallback) {
let root = null;
const leaves = [];
const branches = [];
nodes.forEach(node => {
if (inputAccessCallback(node) === null)
root = node;
else if (!isBranchCallback(node))
leaves.push(node);
else
branches.push(node);
});
// Handle edge cases
if (root === null || leaves.length === 0) {
errorCallback?.(null);
return;
}
if (nodes.size === 1) { // Single node graph edge case
errorCallback?.(root);
return;
}
// Apply callbacks and call error callback if any returns false
if (rootCallback(root) === false) {
errorCallback?.(root, 0);
return;
}
for (let i = 0; i < leaves.length; ++ i)
if (leafCallback(leaves[i]) === false) {
errorCallback?.(leaves[i], 1);
return;
}
for (let i = 0; i < branches.length; ++ i)
if (branchCallback(branches[i]) === false) {
errorCallback?.(branches[i], 2);
return;
}
}
// Set to keep track of visited nodes to detect cycles
const visited = new Set();
// Start by finding the root
const root = findRoot(startNode, inputAccessCallback, visited);
// If a cycle was detected and no root was found, return early
if (root === null)
return;
// Collect all nodes starting from the root using iterative DFS
const allNodes = collectFromRoot(root, outputAccessCallback, visited);
// Apply the callbacks in the required order
applyCallbacks(allNodes, isBranchCallback, rootCallback, leafCallback, branchCallback, inputAccessCallback, outputAccessCallback);
}
function is_reroute(node) {
// return node.prototype === LiteGraph.Nodes.RerouteNode.prototype.prototype;
return node.type === "Reroute (rgthree)" || node.type === "Reroute";
}
function reroute_process(self) {
let main_type = "",
root = null;
tree_traversal(
self,
(node) => {
if (!is_reroute(node))
return null;
if (!node.inputs)
return null;
if (node.inputs[0].link === null)
return null;
return app.graph.getNodeById(app.graph.links[node.inputs[0].link].origin_id);
},
(node) => {
const nodes = [];
if (node.outputs)
for (let i = 0; i < node.outputs.length; ++ i)
if (node.outputs[i].links !== null)
for (let j = 0; j < node.outputs[i].links.length; ++ j)
nodes.push(app.graph.getNodeById(app.graph.links[node.outputs[i].links[j]].target_id));
return nodes;
},
(node) => {
let flag = is_reroute(node);
if (flag)
node.__branch = true;
return flag;
},
(node) => {
let pin = null
scope: {
for (let i = 0; i < node.outputs.length; ++ i) {
if (node.outputs && node.outputs[i].links)
for (let j = 0; j < node.outputs[i].links.length; ++ j) {
let curr = app.graph.getNodeById(app.graph.links[node.outputs[i].links[j]].target_id);
if (curr.__branch) {
pin = node.outputs[i];
if (pin.type === "*")
return;
main_type = pin.type;
root = node;
break scope;
}
}
}
return;
}
// Check if any others connection to this pin have different types then disconnect them
for (let i = 0; i < pin.links.length; ++ i) {
let curr = app.graph.getNodeById(app.graph.links[pin.links[i]].target_id);
if (curr.__branch)
continue;
if (curr.outputs[app.graph.links[pin.links[i]].target_slot].type !== main_type)
node.disconnectOutput(pin.links[i], curr);
}
},
(node) => {
// Loop through all input pins and see which node have __branch
for (let i = 0; i < node.inputs.length; ++ i) {
if (node.inputs[i].link === null)
continue;
let curr = app.graph.getNodeById(app.graph.links[node.inputs[i].link].origin_id);
if (curr.__branch) {
const pin = node.inputs[i];
if (pin.type === "*") {
if (main_type !== "") {
pin.type = main_type;
break;
}
return false;
}
if (pin.type !== main_type) {
// Disconnect
node.disconnectInput(i);
return;
}
main_type = pin.type;
break;
}
}
},
(node) => {
delete node.__branch;
// Set all input pins and output pins to main_type
node.inputs[0].type = main_type;
node.outputs[0].type = main_type;
},
(node, code) => {
if (node === null || node)
return;
switch (code) {
case 0: {
lib0246.error_popup(`Error when processing root: ${node.getTitle()}`);
} break;
case 1: {
lib0246.error_popup(`Error when processing leaf: ${node.getTitle()}`);
} break;
case 2: {
lib0246.error_popup(`Error when processing branch: ${node.getTitle()}`);
} break;
}
}
);
}
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// Probably safe enough unless someone else attempting to reuse these. Ouch.
const patch_node_db = [
["Highway", "0246.Highway"],
["Junction", "0246.Junction"],
["JunctionBatch", "0246.JunctionBatch"],
["Loop", "0246.Loop"],
["Count", "0246.Count"],
["Hold", "0246.Hold"],
["Beautify", "0246.Beautify"],
["Random", "0246.RandomInt"],
["Stringify", "0246.Stringify"],
];
const patch_node_db_0_0_3 = [
["0246.ScriptPlan", "0246.ScriptRule"],
["0246.ScriptImbue", "0246.ScriptNode"],
];
let PATCH_SIG = [];
function patch_script(workflow) {
for (let i = 0; i < workflow.nodes.length; ++ i) {
for (let j = 0; j < patch_node_db_0_0_3.length; ++ j) {
if (workflow.nodes[i].type === patch_node_db_0_0_3[j][0]) {
console.warn(`[ComfyUI-0246] Patching node "${workflow.nodes[i].type}" to "${patch_node_db_0_0_3[j][1]}"`);
workflow.nodes[i].type = patch_node_db_0_0_3[j][1];
if (workflow.nodes[i].type === "0246.ScriptRule") {
workflow.nodes[i].outputs.shift();
workflow.nodes[i].widgets_values[0] = workflow.nodes[i].widgets_values[3];
} else if (workflow.nodes[i].type === "0246.ScriptNode")
workflow.nodes[i].outputs[2].name = "script_exec_data";
if (!PATCH_SIG.includes(2))
PATCH_SIG.push(2);
break;
} else if (workflow.nodes[i].type === patch_node_db_0_0_3[j][1])
break;
}
}
}
lib0246.hijack(app, "loadGraphData", function (workflow) {
if (!this.mark) {
PATCH_SIG.length = 0;
if (workflow) {
if (!workflow?.extra?.["0246.VERSION"]) {
for (let i = 0; i < workflow.nodes.length; ++ i) {
for (let j = 0; j < patch_node_db.length; ++ j) {
if (workflow.nodes[i].type === patch_node_db[j][0]) {
console.warn(`[ComfyUI-0246] Patching node "${workflow.nodes[i].type}" to "${patch_node_db[j][1]}"`);
workflow.nodes[i].type = patch_node_db[j][1];
break;
} else if (workflow.nodes[i].type === patch_node_db[j][1])
break;
}
if ((workflow.nodes[i].type === "0246.Highway" || workflow.nodes[i].type === "0246.HighwayBatch") && !PATCH_SIG.includes(1))
PATCH_SIG.push(1);
else if (workflow.nodes[i].type === "0246.Hub") {
console.warn(`[ComfyUI-0246] Patching node "${workflow.nodes[i].id}" with value __BATCH__ to __BATCH_PRIM__`);
if (workflow.nodes[i].widgets_values?.[0] === "__BATCH__")
workflow.nodes[i].widgets_values[0] = "__BATCH_PRIM__";
const curr_type_list = workflow.extra["0246.HUB_DATA"][workflow.nodes[i].id].sole_type;
if (workflow.nodes[i].outputs)
for (let j = 0; j < workflow.nodes[i].outputs.length; ++ j) {
const curr_name = workflow.nodes[i].outputs[j].name;
if (curr_name?.endsWith?.("__BATCH__")) {
const curr_type = curr_type_list[curr_name];
curr_type[curr_type.length - 1] = "__BATCH_PRIM__";
const new_name = curr_type.join(":");
curr_type_list[new_name] = curr_type;
delete curr_type_list[curr_name];
workflow.nodes[i].outputs[j].name = new_name;
}
}
}
}
patch_script(workflow);
}
else if (
workflow.extra["0246.VERSION"][0] === 0 &&
workflow.extra["0246.VERSION"][1] === 0 &&
workflow.extra["0246.VERSION"][2] === 3
)
patch_script(workflow);
for (let i = 0; i < workflow.nodes.length; ++ i)
if (workflow.nodes[i].type === "0246.BoxRange") {
if (workflow.nodes[i].widgets_values[0] === "ConditioningSetAreaPercentage")
workflow.nodes[i].widgets_values[0] = "%(x, y, width, height)";
} else if (workflow.nodes[i].type === "0246.ScriptNode") {
if (
workflow.nodes[i].widgets_values[2] === "pin_highway_deep" ||
workflow.nodes[i].widgets_values[2] === "pin_highway_flat"
)
workflow.nodes[i].widgets_values[2] = "pin_highway";
}
}
} else {
if (PATCH_SIG.includes(1))
for (let i = 0; i < app.graph._nodes.length; ++ i)
if (app.graph._nodes[i].type === "0246.Highway" || app.graph._nodes[i].type === "0246.HighwayBatch") {
for (let j = 0; j < app.graph._nodes[i].widgets.length; ++ j)
if (app.graph._nodes[i].widgets[j].name === "Update") {
app.graph._nodes[i].widgets[j].callback();
break;
}
}
window.setTimeout(() => {
if (PATCH_SIG.includes(2)) {
for (let i = 0; i < app.graph._nodes.length; ++ i) {
// if (app.graph._nodes[i].type === "0246.ScriptNode")
// for (let j = 0; j < app.graph._nodes[i].outputs.length; ++ j)
// app.graph._nodes[i].disconnectOutput(j);
for (let j = 0; j < patch_node_db_0_0_3.length; ++ j)
if (app.graph._nodes[i].type === patch_node_db_0_0_3[j][1])
app.graph._nodes[i].setSize([app.graph._nodes[i].size[0], app.graph._nodes[i].computeSize()[1]]);
}
lib0246.error_popup(lib0246.indent_str `
Recent update have 0246.ScriptNode (formerly 0246.ScriptImbue) have it output changed due to bad design.
Please reconnect all output to desired node manually.
Also remember to check for all 0246.ScriptRule if they have intended data.
`);
}
}, 0);
}
});
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// let error_flag = false;
// lib0246.hijack(app, "showMissingNodesError", function (nodes) {
// if (!this.mark) {
// if (!error_flag)
// for (let i = 0; i < nodes.length; ++ i)
// for (let j = 0; j < patch_node_db.length; ++ j)
// if (nodes[i] === patch_node_db[j][0]) {
// lib0246.error_popup(lib0246.indent_str `
// [ComfyUI-0246] Unfortunately I have to change node internal ID due to I'm being dumb for using generic name. Sorry for inconvenience.
// If this error message shown then that mean automatic patching failed. Please replace each nodes manually :(
// Affected node:
// - Highway -> 0246.Highway
// - Junction -> 0246.Junction
// - JunctionBatch -> 0246.JunctionBatch
// - Loop -> 0246.Loop
// - Count -> 0246.Count
// - Hold -> 0246.Hold
// - Beautify -> 0246.Beautify
// - Random -> 0246.RandomInt
// - Stringify -> 0246.Stringify
// - 0246.ScriptPlan -> 0246.ScriptRule
// - 0246.ScriptImbue -> 0246.ScriptNode
// `);
// error_flag = true;
// break;
// }
// }
// });
// Extremely hacky ways to fix issues with Reroute (rgthree). Maybe open issue about this?
app.registerExtension({
name: "0246.Fixes",
async setup(app) {
if (LiteGraph.Nodes.RerouteNode) {
// Hijack Reroute (rgthree) to do onConnectOutput and onConnectInput
// lib0246.hijack(LiteGraph.Nodes.RerouteNode.prototype, "onConnectionsChange", function (type, index, connected, link_info) {
// if (!this.mark)
// reroute_process(this.self);
// });
// Since native "Reroute" already did graph traversal, we don't need to hijack it
}
lib0246.hijack(app.graph, "serialize", function () {
// Only used when breaking changes happen
if (!this.mark)
app.graph.extra["0246.VERSION"] = [0, 0, 4];
});
window.monitor_array = monitor_array;
window.monitor_value = monitor_value;
}
});
// {
// function Test() {}
// Test.title = "Test";
// Test.category = "0246";
// Test.comfyClass = "0246.Test";
// Test.collapsable = true;
// Test.title_mode = LiteGraph.NORMAL_TITLE;
// Test.prototype.onNodeCreated = function() {
// const node = this;
// const box_widget = node.addCustomWidget(wg0246.BOX_RANGE_WIDGET("BOX_RANGE", "box_range", {
// row_count: 10,
// col_count: 10,
// }));
// node.addInput("input", "number");
// node.addOutput("output", "number");
// node.addOutput("output2", "number");
// node.addOutput("output3", "number");
// node.addOutput("output4", "number");
// wg0246.widget_flex(node, box_widget, {
// ratio: 0,
// share: 1,
// min_h: 50,
// center: true,
// });
// node.addWidget("button", "Random Value", null, () => {}, {
// serialize: false
// });
// }
// LiteGraph.registerNodeType("0246.Test", Test);
// }
// [TODO] Temporary. Probably we wants to rebuild widget list dynamically
// for (let i = 8; i < node.widgets.length; ++ i) {
// let found = false, widget_self = node.widgets[i][WIDGETS_SELF];
// if (!widget_self) continue;
// for (let id in node.hub.node_widget)
// if (
// node.hub.node_widget[id].indexOf(widget_self) > -1 ||
// node.hub.sole_widget.includes(widget_self)
// ) {
// found = true;
// break;
// }
// if (!found && widget_self.type !== "space_title")
// node.widgets.splice(i --, 1);
// }
// calc_flex(node, node.size[0]);
// console.log(node.size);
// app.canvas.setDirty(true);
// lib0246.hijack(Cloud.prototype, "onNodeCreated", function () {}, function (mode) {
// if (mode === 0b100000)
// this.self.size[0] = Math.max(this.self.size[0], 350);
// });
// TODO: HUB copy-paste broken.
// const hidden =
// node.flags?.collapsed ||
// (!!options.hideOnZoom && app.canvas.ds.scale < 0.5) ||
// widget.flex.hold_draw[3] <= 0 ||
// widget.type === "converted-widget";
// this.element.hidden = hidden;
// this.element.style.display = hidden ? "none" : "";
// if (hidden) {
// widget.options.onHide?.(widget);
// return;
// }
// computeVisibleNodes
// w.element.hidden = hidden;
// w.element.style.display = hidden ? "none" : "";
// if (hidden)
// w.options.onHide?.(w);
// Some crashes:
// https://github.com/chaojie/ComfyUI-LightGlue
// https://github.com/tusharbhutt/Endless-Nodes
// https://github.com/receyuki/comfyui-prompt-reader-node
// ♾️🌊✨ Float to String // output key is not array
// ♾️🌊✨ Integer to Number // output key is not array
// ♾️🌊✨ Integer to String // output key is not array
// LightGlue Simple // Weird ProxyObject stuff
// SD Prompt Saver // Weird ProxyObject stuff
// getAllNodeOptions(nodeDefs) {
// return [
// ...new Set(
// nodeDefs.reduce((acc, nodeDef) => {
// return [...acc, ...(this.getNodeOptions(nodeDef).some(x => typeof x !== 'string') ? (console.log(nodeDef), []) : this.getNodeOptions(nodeDef))];
// }, [])
// )
// ];
// }
// static transformOutputSpec(obj) {
// const { output, output_is_list, output_name, output_tooltips } = obj;
// if (!output?.map) console.log(obj);
// const result = (output?.map?.bind(output) ?? Array.prototype.map.bind([])).call(output ?? [], (type, index2) => {
// const typeString = Array.isArray(type) ? "COMBO" : type;
// return new ComfyOutputSpec(
// index2,
// output_name?.[index2],
// typeString,
// output_is_list?.[index2],
// Array.isArray(type) ? type : void 0,
// output_tooltips?.[index2]
// );
// });
// return new ComfyOutputsSpec(result);
// }