Add custom nodes, Civitai loras (LFS), and vast.ai setup script
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled

Includes 30 custom nodes committed directly, 7 Civitai-exclusive
loras stored via Git LFS, and a setup script that installs all
dependencies and downloads HuggingFace-hosted models on vast.ai.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-09 00:55:26 +00:00
parent 2b70ab9ad0
commit f09734b0ee
2274 changed files with 748556 additions and 3 deletions

View File

@@ -0,0 +1,69 @@
import { app } from "../../scripts/app.js";
import { IoDirection, addConnectionLayoutSupport, followConnectionUntilType } from "./utils.js";
import { RgthreeBaseServerNode } from "./base_node.js";
import { NodeTypesString } from "./constants.js";
import { removeUnusedInputsFromEnd } from "./utils_inputs_outputs.js";
import { debounce } from "../../rgthree/common/shared_utils.js";
class RgthreeAnySwitch extends RgthreeBaseServerNode {
constructor(title = RgthreeAnySwitch.title) {
super(title);
this.stabilizeBound = this.stabilize.bind(this);
this.nodeType = null;
this.addAnyInput(5);
}
onConnectionsChange(type, slotIndex, isConnected, linkInfo, ioSlot) {
var _a;
(_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.call(this, type, slotIndex, isConnected, linkInfo, ioSlot);
this.scheduleStabilize();
}
onConnectionsChainChange() {
this.scheduleStabilize();
}
scheduleStabilize(ms = 64) {
return debounce(this.stabilizeBound, ms);
}
addAnyInput(num = 1) {
for (let i = 0; i < num; i++) {
this.addInput(`any_${String(this.inputs.length + 1).padStart(2, "0")}`, (this.nodeType || "*"));
}
}
stabilize() {
removeUnusedInputsFromEnd(this, 4);
this.addAnyInput();
let connectedType = followConnectionUntilType(this, IoDirection.INPUT, undefined, true);
if (!connectedType) {
connectedType = followConnectionUntilType(this, IoDirection.OUTPUT, undefined, true);
}
this.nodeType = (connectedType === null || connectedType === void 0 ? void 0 : connectedType.type) || "*";
for (const input of this.inputs) {
input.type = this.nodeType;
}
for (const output of this.outputs) {
output.type = this.nodeType;
output.label =
output.type === "RGTHREE_CONTEXT"
? "CONTEXT"
: Array.isArray(this.nodeType) || this.nodeType.includes(",")
? (connectedType === null || connectedType === void 0 ? void 0 : connectedType.label) || String(this.nodeType)
: String(this.nodeType);
}
}
static setUp(comfyClass, nodeData) {
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, RgthreeAnySwitch);
addConnectionLayoutSupport(RgthreeAnySwitch, app, [
["Left", "Right"],
["Right", "Left"],
]);
}
}
RgthreeAnySwitch.title = NodeTypesString.ANY_SWITCH;
RgthreeAnySwitch.type = NodeTypesString.ANY_SWITCH;
RgthreeAnySwitch.comfyClass = NodeTypesString.ANY_SWITCH;
app.registerExtension({
name: "rgthree.AnySwitch",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "Any Switch (rgthree)") {
RgthreeAnySwitch.setUp(nodeType, nodeData);
}
},
});

View File

@@ -0,0 +1,208 @@
import { app } from "../../scripts/app.js";
import { RgthreeBaseVirtualNode } from "./base_node.js";
import { rgthree } from "./rgthree.js";
import { PassThroughFollowing, addConnectionLayoutSupport, addMenuItem, getConnectedInputNodes, getConnectedInputNodesAndFilterPassThroughs, getConnectedOutputNodes, getConnectedOutputNodesAndFilterPassThroughs, } from "./utils.js";
export class BaseAnyInputConnectedNode extends RgthreeBaseVirtualNode {
constructor(title = BaseAnyInputConnectedNode.title) {
super(title);
this.isVirtualNode = true;
this.inputsPassThroughFollowing = PassThroughFollowing.NONE;
this.debouncerTempWidth = 0;
this.schedulePromise = null;
}
onConstructed() {
this.addInput("", "*");
return super.onConstructed();
}
clone() {
const cloned = super.clone();
if (!rgthree.canvasCurrentlyCopyingToClipboardWithMultipleNodes) {
while (cloned.inputs.length > 1) {
cloned.removeInput(cloned.inputs.length - 1);
}
if (cloned.inputs[0]) {
cloned.inputs[0].label = "";
}
}
return cloned;
}
scheduleStabilizeWidgets(ms = 100) {
if (!this.schedulePromise) {
this.schedulePromise = new Promise((resolve) => {
setTimeout(() => {
this.schedulePromise = null;
this.doStablization();
resolve();
}, ms);
});
}
return this.schedulePromise;
}
stabilizeInputsOutputs() {
var _a;
let changed = false;
const hasEmptyInput = !((_a = this.inputs[this.inputs.length - 1]) === null || _a === void 0 ? void 0 : _a.link);
if (!hasEmptyInput) {
this.addInput("", "*");
changed = true;
}
for (let index = this.inputs.length - 2; index >= 0; index--) {
const input = this.inputs[index];
if (!input.link) {
this.removeInput(index);
changed = true;
}
else {
const node = getConnectedInputNodesAndFilterPassThroughs(this, this, index, this.inputsPassThroughFollowing)[0];
const newName = (node === null || node === void 0 ? void 0 : node.title) || "";
if (input.name !== newName) {
input.name = (node === null || node === void 0 ? void 0 : node.title) || "";
changed = true;
}
}
}
return changed;
}
doStablization() {
if (!this.graph) {
return;
}
let dirty = false;
this._tempWidth = this.size[0];
dirty = this.stabilizeInputsOutputs();
const linkedNodes = getConnectedInputNodesAndFilterPassThroughs(this);
dirty = this.handleLinkedNodesStabilization(linkedNodes) || dirty;
if (dirty) {
this.graph.setDirtyCanvas(true, true);
}
this.scheduleStabilizeWidgets(500);
}
handleLinkedNodesStabilization(linkedNodes) {
linkedNodes;
throw new Error("handleLinkedNodesStabilization should be overridden.");
}
onConnectionsChainChange() {
this.scheduleStabilizeWidgets();
}
onConnectionsChange(type, index, connected, linkInfo, ioSlot) {
super.onConnectionsChange &&
super.onConnectionsChange(type, index, connected, linkInfo, ioSlot);
if (!linkInfo)
return;
const connectedNodes = getConnectedOutputNodesAndFilterPassThroughs(this);
for (const node of connectedNodes) {
if (node.onConnectionsChainChange) {
node.onConnectionsChainChange();
}
}
this.scheduleStabilizeWidgets();
}
removeInput(slot) {
this._tempWidth = this.size[0];
return super.removeInput(slot);
}
addInput(name, type, extra_info) {
this._tempWidth = this.size[0];
return super.addInput(name, type, extra_info);
}
addWidget(type, name, value, callback, options) {
this._tempWidth = this.size[0];
return super.addWidget(type, name, value, callback, options);
}
removeWidget(widget) {
this._tempWidth = this.size[0];
super.removeWidget(widget);
}
computeSize(out) {
var _a, _b;
let size = super.computeSize(out);
if (this._tempWidth) {
size[0] = this._tempWidth;
this.debouncerTempWidth && clearTimeout(this.debouncerTempWidth);
this.debouncerTempWidth = setTimeout(() => {
this._tempWidth = null;
}, 32);
}
if (this.properties["collapse_connections"]) {
const rows = Math.max(((_a = this.inputs) === null || _a === void 0 ? void 0 : _a.length) || 0, ((_b = this.outputs) === null || _b === void 0 ? void 0 : _b.length) || 0, 1) - 1;
size[1] = size[1] - rows * LiteGraph.NODE_SLOT_HEIGHT;
}
setTimeout(() => {
var _a;
(_a = this.graph) === null || _a === void 0 ? void 0 : _a.setDirtyCanvas(true, true);
}, 16);
return size;
}
onConnectOutput(outputIndex, inputType, inputSlot, inputNode, inputIndex) {
let canConnect = true;
if (super.onConnectOutput) {
canConnect = super.onConnectOutput(outputIndex, inputType, inputSlot, inputNode, inputIndex);
}
if (canConnect) {
const nodes = getConnectedInputNodes(this);
if (nodes.includes(inputNode)) {
alert(`Whoa, whoa, whoa. You've just tried to create a connection that loops back on itself, ` +
`a situation that could create a time paradox, the results of which could cause a ` +
`chain reaction that would unravel the very fabric of the space time continuum, ` +
`and destroy the entire universe!`);
canConnect = false;
}
}
return canConnect;
}
onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex) {
let canConnect = true;
if (super.onConnectInput) {
canConnect = super.onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex);
}
if (canConnect) {
const nodes = getConnectedOutputNodes(this);
if (nodes.includes(outputNode)) {
alert(`Whoa, whoa, whoa. You've just tried to create a connection that loops back on itself, ` +
`a situation that could create a time paradox, the results of which could cause a ` +
`chain reaction that would unravel the very fabric of the space time continuum, ` +
`and destroy the entire universe!`);
canConnect = false;
}
}
return canConnect;
}
connectByTypeOutput(slot, sourceNode, sourceSlotType, optsIn) {
const lastInput = this.inputs[this.inputs.length - 1];
if (!(lastInput === null || lastInput === void 0 ? void 0 : lastInput.link) && (lastInput === null || lastInput === void 0 ? void 0 : lastInput.type) === "*") {
var sourceSlot = sourceNode.findOutputSlotByType(sourceSlotType, false, true);
return sourceNode.connect(sourceSlot, this, slot);
}
return super.connectByTypeOutput(slot, sourceNode, sourceSlotType, optsIn);
}
static setUp() {
super.setUp();
addConnectionLayoutSupport(this, app, [
["Left", "Right"],
["Right", "Left"],
]);
addMenuItem(this, app, {
name: (node) => { var _a; return `${((_a = node.properties) === null || _a === void 0 ? void 0 : _a["collapse_connections"]) ? "Show" : "Collapse"} Connections`; },
property: "collapse_connections",
prepareValue: (_value, node) => { var _a; return !((_a = node.properties) === null || _a === void 0 ? void 0 : _a["collapse_connections"]); },
callback: (_node) => {
var _a;
(_a = app.canvas.getCurrentGraph()) === null || _a === void 0 ? void 0 : _a.setDirtyCanvas(true, true);
},
});
}
}
const oldLGraphNodeConnectByType = LGraphNode.prototype.connectByType;
LGraphNode.prototype.connectByType = function connectByType(slot, targetNode, targetSlotType, optsIn) {
if (targetNode.inputs) {
for (const [index, input] of targetNode.inputs.entries()) {
if (!input.link && input.type === "*") {
this.connect(slot, targetNode, index);
return null;
}
}
}
return ((oldLGraphNodeConnectByType &&
oldLGraphNodeConnectByType.call(this, slot, targetNode, targetSlotType, optsIn)) ||
null);
};

View File

@@ -0,0 +1,316 @@
import { app } from "../../scripts/app.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js";
import { LogLevel, rgthree } from "./rgthree.js";
import { addHelpMenuItem } from "./utils.js";
import { RgthreeHelpDialog } from "../../rgthree/common/dialog.js";
import { importIndividualNodesInnerOnDragDrop, importIndividualNodesInnerOnDragOver, } from "./feature_import_individual_nodes.js";
import { defineProperty, moveArrayItem } from "../../rgthree/common/shared_utils.js";
export class RgthreeBaseNode extends LGraphNode {
constructor(title = RgthreeBaseNode.title, skipOnConstructedCall = true) {
super(title);
this.comfyClass = "__NEED_COMFY_CLASS__";
this.nickname = "rgthree";
this.isVirtualNode = false;
this.isDropEnabled = false;
this.removed = false;
this.configuring = false;
this._tempWidth = 0;
this.__constructed__ = false;
this.helpDialog = null;
if (title == "__NEED_CLASS_TITLE__") {
throw new Error("RgthreeBaseNode needs overrides.");
}
this.widgets = this.widgets || [];
this.properties = this.properties || {};
setTimeout(() => {
if (this.comfyClass == "__NEED_COMFY_CLASS__") {
throw new Error("RgthreeBaseNode needs a comfy class override.");
}
if (this.constructor.type == "__NEED_CLASS_TYPE__") {
throw new Error("RgthreeBaseNode needs overrides.");
}
this.checkAndRunOnConstructed();
});
defineProperty(this, "mode", {
get: () => {
return this.rgthree_mode;
},
set: (mode) => {
if (this.rgthree_mode != mode) {
const oldMode = this.rgthree_mode;
this.rgthree_mode = mode;
this.onModeChange(oldMode, mode);
}
},
});
}
checkAndRunOnConstructed() {
var _a;
if (!this.__constructed__) {
this.onConstructed();
const [n, v] = rgthree.logger.logParts(LogLevel.DEV, `[RgthreeBaseNode] Child class did not call onConstructed for "${this.type}.`);
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
}
return this.__constructed__;
}
onDragOver(e) {
if (!this.isDropEnabled)
return false;
return importIndividualNodesInnerOnDragOver(this, e);
}
async onDragDrop(e) {
if (!this.isDropEnabled)
return false;
return importIndividualNodesInnerOnDragDrop(this, e);
}
onConstructed() {
var _a;
if (this.__constructed__)
return false;
this.type = (_a = this.type) !== null && _a !== void 0 ? _a : undefined;
this.__constructed__ = true;
rgthree.invokeExtensionsAsync("nodeCreated", this);
return this.__constructed__;
}
configure(info) {
this.configuring = true;
super.configure(info);
for (const w of this.widgets || []) {
w.last_y = w.last_y || 0;
}
this.configuring = false;
}
clone() {
const cloned = super.clone();
if ((cloned === null || cloned === void 0 ? void 0 : cloned.properties) && !!window.structuredClone) {
cloned.properties = structuredClone(cloned.properties);
}
cloned.graph = this.graph;
return cloned;
}
onModeChange(from, to) {
}
async handleAction(action) {
action;
}
removeWidget(widget) {
var _a;
if (typeof widget === "number") {
widget = this.widgets[widget];
}
if (!widget)
return;
const canUseComfyUIRemoveWidget = false;
if (canUseComfyUIRemoveWidget && typeof super.removeWidget === 'function') {
super.removeWidget(widget);
}
else {
const index = this.widgets.indexOf(widget);
if (index > -1) {
this.widgets.splice(index, 1);
}
(_a = widget.onRemove) === null || _a === void 0 ? void 0 : _a.call(widget);
}
}
replaceWidget(widgetOrSlot, newWidget) {
let index = null;
if (widgetOrSlot) {
index = typeof widgetOrSlot === "number" ? widgetOrSlot : this.widgets.indexOf(widgetOrSlot);
this.removeWidget(this.widgets[index]);
}
index = index != null ? index : this.widgets.length - 1;
if (this.widgets.includes(newWidget)) {
moveArrayItem(this.widgets, newWidget, index);
}
else {
this.widgets.splice(index, 0, newWidget);
}
}
defaultGetSlotMenuOptions(slot) {
var _a, _b;
const menu_info = [];
if ((_b = (_a = slot === null || slot === void 0 ? void 0 : slot.output) === null || _a === void 0 ? void 0 : _a.links) === null || _b === void 0 ? void 0 : _b.length) {
menu_info.push({ content: "Disconnect Links", slot });
}
let inputOrOutput = slot.input || slot.output;
if (inputOrOutput) {
if (inputOrOutput.removable) {
menu_info.push(inputOrOutput.locked ? { content: "Cannot remove" } : { content: "Remove Slot", slot });
}
if (!inputOrOutput.nameLocked) {
menu_info.push({ content: "Rename Slot", slot });
}
}
return menu_info;
}
onRemoved() {
var _a;
(_a = super.onRemoved) === null || _a === void 0 ? void 0 : _a.call(this);
this.removed = true;
}
static setUp(...args) {
}
getHelp() {
return "";
}
showHelp() {
const help = this.getHelp() || this.constructor.help;
if (help) {
this.helpDialog = new RgthreeHelpDialog(this, help).show();
this.helpDialog.addEventListener("close", (e) => {
this.helpDialog = null;
});
}
}
onKeyDown(event) {
KEY_EVENT_SERVICE.handleKeyDownOrUp(event);
if (event.key == "?" && !this.helpDialog) {
this.showHelp();
}
}
onKeyUp(event) {
KEY_EVENT_SERVICE.handleKeyDownOrUp(event);
}
getExtraMenuOptions(canvas, options) {
var _a, _b, _c, _d, _e, _f;
if (super.getExtraMenuOptions) {
(_a = super.getExtraMenuOptions) === null || _a === void 0 ? void 0 : _a.apply(this, [canvas, options]);
}
else if ((_c = (_b = this.constructor.nodeType) === null || _b === void 0 ? void 0 : _b.prototype) === null || _c === void 0 ? void 0 : _c.getExtraMenuOptions) {
(_f = (_e = (_d = this.constructor.nodeType) === null || _d === void 0 ? void 0 : _d.prototype) === null || _e === void 0 ? void 0 : _e.getExtraMenuOptions) === null || _f === void 0 ? void 0 : _f.apply(this, [canvas, options]);
}
const help = this.getHelp() || this.constructor.help;
if (help) {
addHelpMenuItem(this, help, options);
}
return [];
}
}
RgthreeBaseNode.exposedActions = [];
RgthreeBaseNode.title = "__NEED_CLASS_TITLE__";
RgthreeBaseNode.type = "__NEED_CLASS_TYPE__";
RgthreeBaseNode.category = "rgthree";
RgthreeBaseNode._category = "rgthree";
export class RgthreeBaseVirtualNode extends RgthreeBaseNode {
constructor(title = RgthreeBaseNode.title) {
super(title, false);
this.isVirtualNode = true;
}
static setUp() {
if (!this.type) {
throw new Error(`Missing type for RgthreeBaseVirtualNode: ${this.title}`);
}
LiteGraph.registerNodeType(this.type, this);
if (this._category) {
this.category = this._category;
}
}
}
export class RgthreeBaseServerNode extends RgthreeBaseNode {
constructor(title) {
super(title, true);
this.isDropEnabled = true;
this.serialize_widgets = true;
this.setupFromServerNodeData();
this.onConstructed();
}
getWidgets() {
return ComfyWidgets;
}
async setupFromServerNodeData() {
var _a, _b, _c, _d, _e;
const nodeData = this.constructor.nodeData;
if (!nodeData) {
throw Error("No node data");
}
this.comfyClass = nodeData.name;
let inputs = nodeData["input"]["required"];
if (nodeData["input"]["optional"] != undefined) {
inputs = Object.assign({}, inputs, nodeData["input"]["optional"]);
}
const WIDGETS = this.getWidgets();
const config = {
minWidth: 1,
minHeight: 1,
widget: null,
};
for (const inputName in inputs) {
const inputData = inputs[inputName];
const type = inputData[0];
if ((_a = inputData[1]) === null || _a === void 0 ? void 0 : _a.forceInput) {
this.addInput(inputName, type);
}
else {
let widgetCreated = true;
if (Array.isArray(type)) {
Object.assign(config, WIDGETS.COMBO(this, inputName, inputData, app) || {});
}
else if (`${type}:${inputName}` in WIDGETS) {
Object.assign(config, WIDGETS[`${type}:${inputName}`](this, inputName, inputData, app) || {});
}
else if (type in WIDGETS) {
Object.assign(config, WIDGETS[type](this, inputName, inputData, app) || {});
}
else {
this.addInput(inputName, type);
widgetCreated = false;
}
if (widgetCreated && ((_b = inputData[1]) === null || _b === void 0 ? void 0 : _b.forceInput) && (config === null || config === void 0 ? void 0 : config.widget)) {
if (!config.widget.options)
config.widget.options = {};
config.widget.options.forceInput = inputData[1].forceInput;
}
if (widgetCreated && ((_c = inputData[1]) === null || _c === void 0 ? void 0 : _c.defaultInput) && (config === null || config === void 0 ? void 0 : config.widget)) {
if (!config.widget.options)
config.widget.options = {};
config.widget.options.defaultInput = inputData[1].defaultInput;
}
}
}
for (const o in nodeData["output"]) {
let output = nodeData["output"][o];
if (output instanceof Array)
output = "COMBO";
const outputName = nodeData["output_name"][o] || output;
const outputShape = nodeData["output_is_list"][o]
? LiteGraph.GRID_SHAPE
: LiteGraph.CIRCLE_SHAPE;
this.addOutput(outputName, output, { shape: outputShape });
}
const s = this.computeSize();
s[0] = Math.max((_d = config.minWidth) !== null && _d !== void 0 ? _d : 1, s[0] * 1.5);
s[1] = Math.max((_e = config.minHeight) !== null && _e !== void 0 ? _e : 1, s[1]);
this.size = s;
this.serialize_widgets = true;
}
static registerForOverride(comfyClass, nodeData, rgthreeClass) {
if (OVERRIDDEN_SERVER_NODES.has(comfyClass)) {
throw Error(`Already have a class to override ${comfyClass.type || comfyClass.name || comfyClass.title}`);
}
OVERRIDDEN_SERVER_NODES.set(comfyClass, rgthreeClass);
if (!rgthreeClass.__registeredForOverride__) {
rgthreeClass.__registeredForOverride__ = true;
rgthreeClass.nodeType = comfyClass;
rgthreeClass.nodeData = nodeData;
rgthreeClass.onRegisteredForOverride(comfyClass, rgthreeClass);
}
}
static onRegisteredForOverride(comfyClass, rgthreeClass) {
}
}
RgthreeBaseServerNode.nodeType = null;
RgthreeBaseServerNode.nodeData = null;
RgthreeBaseServerNode.__registeredForOverride__ = false;
const OVERRIDDEN_SERVER_NODES = new Map();
const oldregisterNodeType = LiteGraph.registerNodeType;
LiteGraph.registerNodeType = async function (nodeId, baseClass) {
var _a;
const clazz = OVERRIDDEN_SERVER_NODES.get(baseClass) || baseClass;
if (clazz !== baseClass) {
const classLabel = clazz.type || clazz.name || clazz.title;
const [n, v] = rgthree.logger.logParts(LogLevel.DEBUG, `${nodeId}: replacing default ComfyNode implementation with custom ${classLabel} class.`);
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
}
return oldregisterNodeType.call(LiteGraph, nodeId, clazz);
};

View File

@@ -0,0 +1,51 @@
import { rgthree } from "./rgthree.js";
import { BaseAnyInputConnectedNode } from "./base_any_input_connected_node.js";
import { PassThroughFollowing, getConnectedInputNodes, getConnectedInputNodesAndFilterPassThroughs, shouldPassThrough, } from "./utils.js";
export class BaseCollectorNode extends BaseAnyInputConnectedNode {
constructor(title) {
super(title);
this.inputsPassThroughFollowing = PassThroughFollowing.REROUTE_ONLY;
this.logger = rgthree.newLogSession("[BaseCollectorNode]");
}
clone() {
const cloned = super.clone();
return cloned;
}
handleLinkedNodesStabilization(linkedNodes) {
return false;
}
onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex) {
var _a, _b, _c, _d;
let canConnect = super.onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex);
if (canConnect) {
const allConnectedNodes = getConnectedInputNodes(this);
const nodesAlreadyInSlot = getConnectedInputNodes(this, undefined, inputIndex);
if (allConnectedNodes.includes(outputNode)) {
const [n, v] = this.logger.debugParts(`${outputNode.title} is already connected to ${this.title}.`);
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
if (nodesAlreadyInSlot.includes(outputNode)) {
const [n, v] = this.logger.debugParts(`... but letting it slide since it's for the same slot.`);
(_b = console[n]) === null || _b === void 0 ? void 0 : _b.call(console, ...v);
}
else {
canConnect = false;
}
}
if (canConnect && shouldPassThrough(outputNode, PassThroughFollowing.REROUTE_ONLY)) {
const connectedNode = getConnectedInputNodesAndFilterPassThroughs(outputNode, undefined, undefined, PassThroughFollowing.REROUTE_ONLY)[0];
if (connectedNode && allConnectedNodes.includes(connectedNode)) {
const [n, v] = this.logger.debugParts(`${connectedNode.title} is already connected to ${this.title}.`);
(_c = console[n]) === null || _c === void 0 ? void 0 : _c.call(console, ...v);
if (nodesAlreadyInSlot.includes(connectedNode)) {
const [n, v] = this.logger.debugParts(`... but letting it slide since it's for the same slot.`);
(_d = console[n]) === null || _d === void 0 ? void 0 : _d.call(console, ...v);
}
else {
canConnect = false;
}
}
}
}
return canConnect;
}
}

View File

@@ -0,0 +1,93 @@
import { BaseAnyInputConnectedNode } from "./base_any_input_connected_node.js";
import { changeModeOfNodes, PassThroughFollowing } from "./utils.js";
import { wait } from "../../rgthree/common/shared_utils.js";
export class BaseNodeModeChanger extends BaseAnyInputConnectedNode {
constructor(title) {
super(title);
this.inputsPassThroughFollowing = PassThroughFollowing.ALL;
this.isVirtualNode = true;
this.modeOn = -1;
this.modeOff = -1;
this.properties["toggleRestriction"] = "default";
}
onConstructed() {
wait(10).then(() => {
if (this.modeOn < 0 || this.modeOff < 0) {
throw new Error("modeOn and modeOff must be overridden.");
}
});
this.addOutput("OPT_CONNECTION", "*");
return super.onConstructed();
}
handleLinkedNodesStabilization(linkedNodes) {
let changed = false;
for (const [index, node] of linkedNodes.entries()) {
let widget = this.widgets && this.widgets[index];
if (!widget) {
this._tempWidth = this.size[0];
widget = this.addWidget("toggle", "", false, "", { on: "yes", off: "no" });
changed = true;
}
if (node) {
changed = this.setWidget(widget, node) || changed;
}
}
if (this.widgets && this.widgets.length > linkedNodes.length) {
this.widgets.length = linkedNodes.length;
changed = true;
}
return changed;
}
setWidget(widget, linkedNode, forceValue) {
let changed = false;
const value = forceValue == null ? linkedNode.mode === this.modeOn : forceValue;
let name = `Enable ${linkedNode.title}`;
if (widget.name !== name) {
widget.name = `Enable ${linkedNode.title}`;
widget.options = { on: "yes", off: "no" };
widget.value = value;
widget.doModeChange = (forceValue, skipOtherNodeCheck) => {
var _a, _b, _c;
let newValue = forceValue == null ? linkedNode.mode === this.modeOff : forceValue;
if (skipOtherNodeCheck !== true) {
if (newValue && ((_b = (_a = this.properties) === null || _a === void 0 ? void 0 : _a["toggleRestriction"]) === null || _b === void 0 ? void 0 : _b.includes(" one"))) {
for (const widget of this.widgets) {
widget.doModeChange(false, true);
}
}
else if (!newValue && ((_c = this.properties) === null || _c === void 0 ? void 0 : _c["toggleRestriction"]) === "always one") {
newValue = this.widgets.every((w) => !w.value || w === widget);
}
}
changeModeOfNodes(linkedNode, (newValue ? this.modeOn : this.modeOff));
widget.value = newValue;
};
widget.callback = () => {
widget.doModeChange();
};
changed = true;
}
if (forceValue != null) {
const newMode = (forceValue ? this.modeOn : this.modeOff);
if (linkedNode.mode !== newMode) {
changeModeOfNodes(linkedNode, newMode);
changed = true;
}
}
return changed;
}
forceWidgetOff(widget, skipOtherNodeCheck) {
widget.doModeChange(false, skipOtherNodeCheck);
}
forceWidgetOn(widget, skipOtherNodeCheck) {
widget.doModeChange(true, skipOtherNodeCheck);
}
forceWidgetToggle(widget, skipOtherNodeCheck) {
widget.doModeChange(!widget.value, skipOtherNodeCheck);
}
}
BaseNodeModeChanger.collapsible = false;
BaseNodeModeChanger["@toggleRestriction"] = {
type: "combo",
values: ["default", "max one", "always one"],
};

View File

@@ -0,0 +1,252 @@
import { api } from "../../scripts/api.js";
import { wait } from "../../rgthree/common/shared_utils.js";
import { rgthree } from "./rgthree.js";
export class PowerPrompt {
constructor(node, nodeData) {
this.combos = {};
this.combosValues = {};
this.configuring = false;
this.node = node;
this.node.properties = this.node.properties || {};
this.node.properties["combos_filter"] = "";
this.nodeData = nodeData;
this.isSimple = this.nodeData.name.includes("Simple");
this.promptEl = node.widgets[0].inputEl;
this.addAndHandleKeyboardLoraEditWeight();
this.patchNodeRefresh();
const oldConfigure = this.node.configure;
this.node.configure = (info) => {
this.configuring = true;
oldConfigure === null || oldConfigure === void 0 ? void 0 : oldConfigure.apply(this.node, [info]);
this.configuring = false;
};
const oldOnConnectionsChange = this.node.onConnectionsChange;
this.node.onConnectionsChange = (type, slotIndex, isConnected, link_info, _ioSlot) => {
oldOnConnectionsChange === null || oldOnConnectionsChange === void 0 ? void 0 : oldOnConnectionsChange.apply(this.node, [type, slotIndex, isConnected, link_info, _ioSlot]);
this.onNodeConnectionsChange(type, slotIndex, isConnected, link_info, _ioSlot);
};
const oldOnConnectInput = this.node.onConnectInput;
this.node.onConnectInput = (inputIndex, outputType, outputSlot, outputNode, outputIndex) => {
let canConnect = true;
if (oldOnConnectInput) {
canConnect = oldOnConnectInput.apply(this.node, [
inputIndex,
outputType,
outputSlot,
outputNode,
outputIndex,
]);
}
return (this.configuring ||
!!rgthree.loadingApiJson ||
(canConnect && !this.node.inputs[inputIndex].disabled));
};
const oldOnConnectOutput = this.node.onConnectOutput;
this.node.onConnectOutput = (outputIndex, inputType, inputSlot, inputNode, inputIndex) => {
let canConnect = true;
if (oldOnConnectOutput) {
canConnect = oldOnConnectOutput === null || oldOnConnectOutput === void 0 ? void 0 : oldOnConnectOutput.apply(this.node, [
outputIndex,
inputType,
inputSlot,
inputNode,
inputIndex,
]);
}
return (this.configuring ||
!!rgthree.loadingApiJson ||
(canConnect && !this.node.outputs[outputIndex].disabled));
};
const onPropertyChanged = this.node.onPropertyChanged;
this.node.onPropertyChanged = (property, value, prevValue) => {
const v = onPropertyChanged && onPropertyChanged.call(this.node, property, value, prevValue);
if (property === "combos_filter") {
this.refreshCombos(this.nodeData);
}
return v !== null && v !== void 0 ? v : true;
};
for (let i = this.node.widgets.length - 1; i >= 0; i--) {
if (this.shouldRemoveServerWidget(this.node.widgets[i])) {
this.node.widgets.splice(i, 1);
}
}
this.refreshCombos(nodeData);
setTimeout(() => {
this.stabilizeInputsOutputs();
}, 32);
}
onNodeConnectionsChange(_type, _slotIndex, _isConnected, _linkInfo, _ioSlot) {
this.stabilizeInputsOutputs();
}
stabilizeInputsOutputs() {
if (this.configuring || rgthree.loadingApiJson) {
return;
}
const clipLinked = this.node.inputs.some((i) => i.name.includes("clip") && !!i.link);
const modelLinked = this.node.inputs.some((i) => i.name.includes("model") && !!i.link);
for (const output of this.node.outputs) {
const type = output.type.toLowerCase();
if (type.includes("model")) {
output.disabled = !modelLinked;
}
else if (type.includes("conditioning")) {
output.disabled = !clipLinked;
}
else if (type.includes("clip")) {
output.disabled = !clipLinked;
}
else if (type.includes("string")) {
output.color_off = "#7F7";
output.color_on = "#7F7";
}
if (output.disabled) {
}
}
}
onFreshNodeDefs(event) {
this.refreshCombos(event.detail[this.nodeData.name]);
}
shouldRemoveServerWidget(widget) {
var _a, _b, _c, _d;
return (((_a = widget.name) === null || _a === void 0 ? void 0 : _a.startsWith("insert_")) ||
((_b = widget.name) === null || _b === void 0 ? void 0 : _b.startsWith("target_")) ||
((_c = widget.name) === null || _c === void 0 ? void 0 : _c.startsWith("crop_")) ||
((_d = widget.name) === null || _d === void 0 ? void 0 : _d.startsWith("values_")));
}
refreshCombos(nodeData) {
var _a, _b, _c;
this.nodeData = nodeData;
let filter = null;
if ((_a = this.node.properties["combos_filter"]) === null || _a === void 0 ? void 0 : _a.trim()) {
try {
filter = new RegExp(this.node.properties["combos_filter"].trim(), "i");
}
catch (e) {
console.error(`Could not parse "${filter}" for Regular Expression`, e);
filter = null;
}
}
let data = Object.assign({}, ((_b = this.nodeData.input) === null || _b === void 0 ? void 0 : _b.optional) || {}, ((_c = this.nodeData.input) === null || _c === void 0 ? void 0 : _c.hidden) || {});
for (const [key, value] of Object.entries(data)) {
if (Array.isArray(value[0])) {
let values = value[0];
if (key.startsWith("insert")) {
values = filter
? values.filter((v, i) => i < 1 || (i == 1 && v.match(/^disable\s[a-z]/i)) || (filter === null || filter === void 0 ? void 0 : filter.test(v)))
: values;
const shouldShow = values.length > 2 || (values.length > 1 && !values[1].match(/^disable\s[a-z]/i));
if (shouldShow) {
if (!this.combos[key]) {
this.combos[key] = this.node.addWidget("combo", key, values[0], (selected) => {
if (selected !== values[0] && !selected.match(/^disable\s[a-z]/i)) {
wait().then(() => {
if (key.includes("embedding")) {
this.insertSelectionText(`embedding:${selected}`);
}
else if (key.includes("saved")) {
this.insertSelectionText(this.combosValues[`values_${key}`][values.indexOf(selected)]);
}
else if (key.includes("lora")) {
this.insertSelectionText(`<lora:${selected}:1.0>`);
}
this.combos[key].value = values[0];
});
}
}, {
values,
serialize: true,
});
this.combos[key].oldComputeSize = this.combos[key].computeSize;
let node = this.node;
this.combos[key].computeSize = function (width) {
var _a, _b;
const size = ((_b = (_a = this).oldComputeSize) === null || _b === void 0 ? void 0 : _b.call(_a, width)) || [
width,
LiteGraph.NODE_WIDGET_HEIGHT,
];
if (this === node.widgets[node.widgets.length - 1]) {
size[1] += 10;
}
return size;
};
}
this.combos[key].options.values = values;
this.combos[key].value = values[0];
}
else if (!shouldShow && this.combos[key]) {
this.node.widgets.splice(this.node.widgets.indexOf(this.combos[key]), 1);
delete this.combos[key];
}
}
else if (key.startsWith("values")) {
this.combosValues[key] = values;
}
}
}
}
insertSelectionText(text) {
if (!this.promptEl) {
console.error("Asked to insert text, but no textbox found.");
return;
}
let prompt = this.promptEl.value;
let first = prompt.substring(0, this.promptEl.selectionEnd).replace(/ +$/, "");
first = first + (["\n"].includes(first[first.length - 1]) ? "" : first.length ? " " : "");
let second = prompt.substring(this.promptEl.selectionEnd).replace(/^ +/, "");
second = (["\n"].includes(second[0]) ? "" : second.length ? " " : "") + second;
this.promptEl.value = first + text + second;
this.promptEl.focus();
this.promptEl.selectionStart = first.length;
this.promptEl.selectionEnd = first.length + text.length;
}
addAndHandleKeyboardLoraEditWeight() {
this.promptEl.addEventListener("keydown", (event) => {
var _a, _b;
if (!(event.key === "ArrowUp" || event.key === "ArrowDown"))
return;
if (!event.ctrlKey && !event.metaKey)
return;
const delta = event.shiftKey ? 0.01 : 0.1;
let start = this.promptEl.selectionStart;
let end = this.promptEl.selectionEnd;
let fullText = this.promptEl.value;
let selectedText = fullText.substring(start, end);
if (!selectedText) {
const stopOn = "<>()\r\n\t";
if (fullText[start] == ">") {
start -= 2;
end -= 2;
}
if (fullText[end - 1] == "<") {
start += 2;
end += 2;
}
while (!stopOn.includes(fullText[start]) && start > 0) {
start--;
}
while (!stopOn.includes(fullText[end - 1]) && end < fullText.length) {
end++;
}
selectedText = fullText.substring(start, end);
}
if (!selectedText.startsWith("<lora:") || !selectedText.endsWith(">")) {
return;
}
let weight = (_b = Number((_a = selectedText.match(/:(-?\d*(\.\d*)?)>$/)) === null || _a === void 0 ? void 0 : _a[1])) !== null && _b !== void 0 ? _b : 1;
weight += event.key === "ArrowUp" ? delta : -delta;
const updatedText = selectedText.replace(/(:-?\d*(\.\d*)?)?>$/, `:${weight.toFixed(2)}>`);
this.promptEl.setRangeText(updatedText, start, end, "select");
event.preventDefault();
event.stopPropagation();
});
}
patchNodeRefresh() {
this.boundOnFreshNodeDefs = this.onFreshNodeDefs.bind(this);
api.addEventListener("fresh-node-defs", this.boundOnFreshNodeDefs);
const oldNodeRemoved = this.node.onRemoved;
this.node.onRemoved = () => {
oldNodeRemoved === null || oldNodeRemoved === void 0 ? void 0 : oldNodeRemoved.call(this.node);
api.removeEventListener("fresh-node-defs", this.boundOnFreshNodeDefs);
};
}
}

View File

@@ -0,0 +1,105 @@
import { app } from "../../scripts/app.js";
import { RgthreeBaseVirtualNode } from "./base_node.js";
import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js";
import { SERVICE as BOOKMARKS_SERVICE } from "./services/bookmarks_services.js";
import { NodeTypesString } from "./constants.js";
import { getClosestOrSelf, query } from "../../rgthree/common/utils_dom.js";
import { wait } from "../../rgthree/common/shared_utils.js";
import { findFromNodeForSubgraph } from "./utils.js";
export class Bookmark extends RgthreeBaseVirtualNode {
get _collapsed_width() {
return this.___collapsed_width;
}
set _collapsed_width(width) {
const canvas = app.canvas;
const ctx = canvas.canvas.getContext("2d");
const oldFont = ctx.font;
ctx.font = canvas.title_text_font;
this.___collapsed_width = 40 + ctx.measureText(this.title).width;
ctx.font = oldFont;
}
constructor(title = Bookmark.title) {
super(title);
this.comfyClass = NodeTypesString.BOOKMARK;
this.___collapsed_width = 0;
this.isVirtualNode = true;
this.serialize_widgets = true;
const nextShortcutChar = BOOKMARKS_SERVICE.getNextShortcut();
this.addWidget("text", "shortcut_key", nextShortcutChar, (value, ...args) => {
value = value.trim()[0] || "1";
}, {
y: 8,
});
this.addWidget("number", "zoom", 1, (value) => { }, {
y: 8 + LiteGraph.NODE_WIDGET_HEIGHT + 4,
max: 2,
min: 0.5,
precision: 2,
});
this.keypressBound = this.onKeypress.bind(this);
this.title = "🔖";
this.onConstructed();
}
get shortcutKey() {
var _a, _b, _c;
return (_c = (_b = (_a = this.widgets[0]) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.toLocaleLowerCase()) !== null && _c !== void 0 ? _c : "";
}
onAdded(graph) {
KEY_EVENT_SERVICE.addEventListener("keydown", this.keypressBound);
}
onRemoved() {
KEY_EVENT_SERVICE.removeEventListener("keydown", this.keypressBound);
}
onKeypress(event) {
const originalEvent = event.detail.originalEvent;
const target = originalEvent.target;
if (getClosestOrSelf(target, 'input,textarea,[contenteditable="true"]')) {
return;
}
if (KEY_EVENT_SERVICE.areOnlyKeysDown(this.widgets[0].value, true)) {
this.canvasToBookmark();
originalEvent.preventDefault();
originalEvent.stopPropagation();
}
}
onMouseDown(event, pos, graphCanvas) {
var _a;
const input = query(".graphdialog > input.value");
if (input && input.value === ((_a = this.widgets[0]) === null || _a === void 0 ? void 0 : _a.value)) {
input.addEventListener("keydown", (e) => {
KEY_EVENT_SERVICE.handleKeyDownOrUp(e);
e.preventDefault();
e.stopPropagation();
input.value = Object.keys(KEY_EVENT_SERVICE.downKeys).join(" + ");
});
}
return false;
}
async canvasToBookmark() {
var _a, _b;
const canvas = app.canvas;
if (this.graph !== app.canvas.getCurrentGraph()) {
const subgraph = this.graph;
const fromNode = findFromNodeForSubgraph(subgraph.id);
canvas.openSubgraph(subgraph, fromNode);
await wait(16);
}
if ((_a = canvas === null || canvas === void 0 ? void 0 : canvas.ds) === null || _a === void 0 ? void 0 : _a.offset) {
canvas.ds.offset[0] = -this.pos[0] + 16;
canvas.ds.offset[1] = -this.pos[1] + 40;
}
if (((_b = canvas === null || canvas === void 0 ? void 0 : canvas.ds) === null || _b === void 0 ? void 0 : _b.scale) != null) {
canvas.ds.scale = Number(this.widgets[1].value || 1);
}
canvas.setDirty(true, true);
}
}
Bookmark.type = NodeTypesString.BOOKMARK;
Bookmark.title = NodeTypesString.BOOKMARK;
Bookmark.slot_start_y = -20;
app.registerExtension({
name: "rgthree.Bookmark",
registerCustomNodes() {
Bookmark.setUp();
},
});

View File

@@ -0,0 +1,45 @@
import { app } from "../../scripts/app.js";
import { BaseNodeModeChanger } from "./base_node_mode_changer.js";
import { NodeTypesString } from "./constants.js";
const MODE_BYPASS = 4;
const MODE_ALWAYS = 0;
class BypasserNode extends BaseNodeModeChanger {
constructor(title = BypasserNode.title) {
super(title);
this.comfyClass = NodeTypesString.FAST_BYPASSER;
this.modeOn = MODE_ALWAYS;
this.modeOff = MODE_BYPASS;
this.onConstructed();
}
async handleAction(action) {
if (action === "Bypass all") {
for (const widget of this.widgets || []) {
this.forceWidgetOff(widget, true);
}
}
else if (action === "Enable all") {
for (const widget of this.widgets || []) {
this.forceWidgetOn(widget, true);
}
}
else if (action === "Toggle all") {
for (const widget of this.widgets || []) {
this.forceWidgetToggle(widget, true);
}
}
}
}
BypasserNode.exposedActions = ["Bypass all", "Enable all", "Toggle all"];
BypasserNode.type = NodeTypesString.FAST_BYPASSER;
BypasserNode.title = NodeTypesString.FAST_BYPASSER;
app.registerExtension({
name: "rgthree.Bypasser",
registerCustomNodes() {
BypasserNode.setUp();
},
loadedGraphNode(node) {
if (node.type == BypasserNode.title) {
node._tempWidth = node.size[0];
}
},
});

View File

@@ -0,0 +1,197 @@
import { app } from "../../scripts/app.js";
import { iconGear, iconStarFilled, logoRgthreeAsync } from "../../rgthree/common/media/svgs.js";
import { $el, empty } from "../../rgthree/common/utils_dom.js";
import { SERVICE as BOOKMARKS_SERVICE } from "./services/bookmarks_services.js";
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
import { RgthreeConfigDialog } from "./config.js";
import { wait } from "../../rgthree/common/shared_utils.js";
let rgthreeButtonGroup = null;
function addRgthreeTopBarButtons() {
var _a, _b, _c;
if (!CONFIG_SERVICE.getFeatureValue("comfy_top_bar_menu.enabled")) {
if ((_a = rgthreeButtonGroup === null || rgthreeButtonGroup === void 0 ? void 0 : rgthreeButtonGroup.element) === null || _a === void 0 ? void 0 : _a.parentElement) {
rgthreeButtonGroup.element.parentElement.removeChild(rgthreeButtonGroup.element);
}
return;
}
else if (rgthreeButtonGroup) {
(_b = app.menu) === null || _b === void 0 ? void 0 : _b.settingsGroup.element.before(rgthreeButtonGroup.element);
return;
}
const buttons = [];
const rgthreeButton = new RgthreeComfyButton({
icon: "<svg></svg>",
tooltip: "rgthree-comfy",
primary: true,
enabled: true,
classList: "comfyui-button comfyui-menu-mobile-collapse primary",
});
buttons.push(rgthreeButton);
logoRgthreeAsync().then((t) => {
rgthreeButton.setIcon(t);
});
rgthreeButton.withPopup(new RgthreeComfyPopup({ target: rgthreeButton.element }, $el("menu.rgthree-menu.rgthree-top-menu", {
children: [
$el("li", {
child: $el("button.rgthree-button-reset", {
html: iconGear + "Settings (rgthree-comfy)",
onclick: () => new RgthreeConfigDialog().show(),
}),
}),
$el("li", {
child: $el("button.rgthree-button-reset", {
html: iconStarFilled + "Star on Github",
onclick: () => window.open("https://github.com/rgthree/rgthree-comfy", "_blank"),
}),
}),
],
})), "click");
if (CONFIG_SERVICE.getFeatureValue("comfy_top_bar_menu.button_bookmarks.enabled")) {
const bookmarksListEl = $el("menu.rgthree-menu.rgthree-top-menu");
bookmarksListEl.appendChild($el("li.rgthree-message", {
child: $el("span", { text: "No bookmarks in current workflow." }),
}));
const bookmarksButton = new RgthreeComfyButton({
icon: "bookmark",
tooltip: "Workflow Bookmarks (rgthree-comfy)",
});
const bookmarksPopup = new RgthreeComfyPopup({ target: bookmarksButton.element, modal: false }, bookmarksListEl);
bookmarksPopup.onOpen(() => {
const bookmarks = BOOKMARKS_SERVICE.getCurrentBookmarks();
empty(bookmarksListEl);
if (bookmarks.length) {
for (const b of bookmarks) {
bookmarksListEl.appendChild($el("li", {
child: $el("button.rgthree-button-reset", {
text: `[${b.shortcutKey}] ${b.title}`,
onclick: () => {
b.canvasToBookmark();
},
}),
}));
}
}
else {
bookmarksListEl.appendChild($el("li.rgthree-message", {
child: $el("span", { text: "No bookmarks in current workflow." }),
}));
}
});
bookmarksButton.withPopup(bookmarksPopup, "hover");
buttons.push(bookmarksButton);
}
rgthreeButtonGroup = new RgthreeComfyButtonGroup(...buttons);
(_c = app.menu) === null || _c === void 0 ? void 0 : _c.settingsGroup.element.before(rgthreeButtonGroup.element);
}
app.registerExtension({
name: "rgthree.TopMenu",
async setup() {
addRgthreeTopBarButtons();
CONFIG_SERVICE.addEventListener("config-change", ((e) => {
var _a, _b;
if ((_b = (_a = e.detail) === null || _a === void 0 ? void 0 : _a.key) === null || _b === void 0 ? void 0 : _b.includes("features.comfy_top_bar_menu")) {
addRgthreeTopBarButtons();
}
}));
},
});
class RgthreeComfyButtonGroup {
constructor(...buttons) {
this.element = $el("div.rgthree-comfybar-top-button-group");
this.buttons = buttons;
this.update();
}
insert(button, index) {
this.buttons.splice(index, 0, button);
this.update();
}
append(button) {
this.buttons.push(button);
this.update();
}
remove(indexOrButton) {
if (typeof indexOrButton !== "number") {
indexOrButton = this.buttons.indexOf(indexOrButton);
}
if (indexOrButton > -1) {
const btn = this.buttons.splice(indexOrButton, 1);
this.update();
return btn;
}
return null;
}
update() {
this.element.replaceChildren(...this.buttons.map((b) => { var _a; return (_a = b["element"]) !== null && _a !== void 0 ? _a : b; }));
}
}
class RgthreeComfyButton {
constructor(opts) {
this.element = $el("button.rgthree-comfybar-top-button.rgthree-button-reset.rgthree-button");
this.iconElement = $el("span.rgthree-button-icon");
opts.icon && this.setIcon(opts.icon);
opts.tooltip && this.element.setAttribute("title", opts.tooltip);
opts.primary && this.element.classList.add("-primary");
}
setIcon(iconOrMarkup) {
const markup = iconOrMarkup.startsWith("<")
? iconOrMarkup
: `<i class="mdi mdi-${iconOrMarkup}"></i>`;
this.iconElement.innerHTML = markup;
if (!this.iconElement.parentElement) {
this.element.appendChild(this.iconElement);
}
}
withPopup(popup, trigger) {
if (trigger === "click") {
this.element.addEventListener("click", () => {
popup.open();
});
}
if (trigger === "hover") {
this.element.addEventListener("pointerenter", () => {
popup.open();
});
}
}
}
class RgthreeComfyPopup {
constructor(opts, element) {
this.onOpenFn = null;
this.onWindowClickBound = this.onWindowClick.bind(this);
this.element = element;
this.opts = opts;
opts.target && (this.target = opts.target);
opts.modal && this.element.classList.add("-modal");
}
async open() {
if (!this.target) {
throw new Error("No target for RgthreeComfyPopup");
}
if (this.onOpenFn) {
await this.onOpenFn();
}
await wait(16);
const rect = this.target.getBoundingClientRect();
this.element.setAttribute("state", "measuring");
document.body.appendChild(this.element);
this.element.style.position = "fixed";
this.element.style.left = `${rect.left}px`;
this.element.style.top = `${rect.top + rect.height}px`;
this.element.setAttribute("state", "open");
if (this.opts.modal) {
document.body.classList.add("rgthree-modal-menu-open");
}
window.addEventListener("click", this.onWindowClickBound);
}
close() {
this.element.remove();
document.body.classList.remove("rgthree-modal-menu-open");
window.removeEventListener("click", this.onWindowClickBound);
}
onOpen(fn) {
this.onOpenFn = fn;
}
onWindowClick() {
this.close();
}
}

View File

@@ -0,0 +1,369 @@
import { app } from "../../scripts/app.js";
import { RgthreeDialog } from "../../rgthree/common/dialog.js";
import { createElement as $el, queryAll as $$ } from "../../rgthree/common/utils_dom.js";
import { checkmark, logoRgthree } from "../../rgthree/common/media/svgs.js";
import { rgthree } from "./rgthree.js";
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
var ConfigType;
(function (ConfigType) {
ConfigType[ConfigType["UNKNOWN"] = 0] = "UNKNOWN";
ConfigType[ConfigType["BOOLEAN"] = 1] = "BOOLEAN";
ConfigType[ConfigType["STRING"] = 2] = "STRING";
ConfigType[ConfigType["NUMBER"] = 3] = "NUMBER";
ConfigType[ConfigType["ARRAY"] = 4] = "ARRAY";
})(ConfigType || (ConfigType = {}));
var ConfigInputType;
(function (ConfigInputType) {
ConfigInputType[ConfigInputType["UNKNOWN"] = 0] = "UNKNOWN";
ConfigInputType[ConfigInputType["CHECKLIST"] = 1] = "CHECKLIST";
})(ConfigInputType || (ConfigInputType = {}));
const TYPE_TO_STRING = {
[ConfigType.UNKNOWN]: "unknown",
[ConfigType.BOOLEAN]: "boolean",
[ConfigType.STRING]: "string",
[ConfigType.NUMBER]: "number",
[ConfigType.ARRAY]: "array",
};
const CONFIGURABLE = {
features: [
{
key: "features.progress_bar.enabled",
type: ConfigType.BOOLEAN,
label: "Prompt Progress Bar",
description: `Shows a minimal progress bar for nodes and steps at the top of the app.`,
subconfig: [
{
key: "features.progress_bar.height",
type: ConfigType.NUMBER,
label: "Height of the bar",
},
{
key: "features.progress_bar.position",
type: ConfigType.STRING,
label: "Position at top or bottom of window",
options: ["top", "bottom"],
},
],
},
{
key: "features.import_individual_nodes.enabled",
type: ConfigType.BOOLEAN,
label: "Import Individual Nodes Widgets",
description: "Dragging & Dropping a similar image/JSON workflow onto (most) current workflow nodes" +
"will allow you to import that workflow's node's widgets when it has the same " +
"id and type. This is useful when you have several images and you'd like to import just " +
"one part of a previous iteration, like a seed, or prompt.",
},
],
menus: [
{
key: "features.comfy_top_bar_menu.enabled",
type: ConfigType.BOOLEAN,
label: "Enable Top Bar Menu",
description: "Have quick access from ComfyUI's new top bar to rgthree-comfy bookmarks, settings " +
"(and more to come).",
},
{
key: "features.menu_queue_selected_nodes",
type: ConfigType.BOOLEAN,
label: "Show 'Queue Selected Output Nodes'",
description: "Will show a menu item in the right-click context menus to queue (only) the selected " +
"output nodes.",
},
{
key: "features.menu_auto_nest.subdirs",
type: ConfigType.BOOLEAN,
label: "Auto Nest Subdirectories in Menus",
description: "When a large, flat list of values contain sub-directories, auto nest them. (Like, for " +
"a large list of checkpoints).",
subconfig: [
{
key: "features.menu_auto_nest.threshold",
type: ConfigType.NUMBER,
label: "Number of items needed to trigger nesting.",
},
],
},
{
key: "features.menu_bookmarks.enabled",
type: ConfigType.BOOLEAN,
label: "Show Bookmarks in context menu",
description: "Will list bookmarks in the rgthree-comfy right-click context menu.",
},
],
groups: [
{
key: "features.group_header_fast_toggle.enabled",
type: ConfigType.BOOLEAN,
label: "Show fast toggles in Group Headers",
description: "Show quick toggles in Groups' Headers to quickly mute, bypass or queue.",
subconfig: [
{
key: "features.group_header_fast_toggle.toggles",
type: ConfigType.ARRAY,
label: "Which toggles to show.",
inputType: ConfigInputType.CHECKLIST,
options: [
{ value: "queue", label: "queue" },
{ value: "bypass", label: "bypass" },
{ value: "mute", label: "mute" },
],
},
{
key: "features.group_header_fast_toggle.show",
type: ConfigType.STRING,
label: "When to show them.",
options: [
{ value: "hover", label: "on hover" },
{ value: "always", label: "always" },
],
},
],
},
],
advanced: [
{
key: "features.show_alerts_for_corrupt_workflows",
type: ConfigType.BOOLEAN,
label: "Detect Corrupt Workflows",
description: "Will show a message at the top of the screen when loading a workflow that has " +
"corrupt linking data.",
},
{
key: "log_level",
type: ConfigType.STRING,
label: "Log level for browser dev console.",
description: "Further down the list, the more verbose logs to the console will be. For instance, " +
"selecting 'IMPORTANT' means only important message will be logged to the browser " +
"console, while selecting 'WARN' will log all messages at or higher than WARN, including " +
"'ERROR' and 'IMPORTANT' etc.",
options: ["IMPORTANT", "ERROR", "WARN", "INFO", "DEBUG", "DEV"],
isDevOnly: true,
onSave: function (value) {
rgthree.setLogLevel(value);
},
},
{
key: "features.invoke_extensions_async.node_created",
type: ConfigType.BOOLEAN,
label: "Allow other extensions to call nodeCreated on rgthree-nodes.",
isDevOnly: true,
description: "Do not disable unless you are having trouble (and then file an issue at rgthree-comfy)." +
"Prior to Apr 2024 it was not possible for other extensions to invoke their nodeCreated " +
"event on some rgthree-comfy nodes. Now it's possible and this option is only here in " +
"for easy if something is wrong.",
},
],
};
function fieldrow(item) {
var _a;
const initialValue = CONFIG_SERVICE.getConfigValue(item.key);
const container = $el(`div.fieldrow.-type-${TYPE_TO_STRING[item.type]}`, {
dataset: {
name: item.key,
initial: initialValue,
type: item.type,
},
});
$el(`label[for="${item.key}"]`, {
children: [
$el(`span[text="${item.label}"]`),
item.description ? $el("small", { html: item.description }) : null,
],
parent: container,
});
let input;
if ((_a = item.options) === null || _a === void 0 ? void 0 : _a.length) {
if (item.inputType === ConfigInputType.CHECKLIST) {
const initialValueList = initialValue || [];
input = $el(`fieldset.rgthree-checklist-group[id="${item.key}"]`, {
parent: container,
children: item.options.map((o) => {
const label = o.label || String(o);
const value = o.value || o;
const id = `${item.key}_${value}`;
return $el(`span.rgthree-checklist-item`, {
children: [
$el(`input[type="checkbox"][value="${value}"]`, {
id,
checked: initialValueList.includes(value),
}),
$el(`label`, {
for: id,
text: label,
})
]
});
}),
});
}
else {
input = $el(`select[id="${item.key}"]`, {
parent: container,
children: item.options.map((o) => {
const label = o.label || String(o);
const value = o.value || o;
const valueSerialized = JSON.stringify({ value: value });
return $el(`option[value="${valueSerialized}"]`, {
text: label,
selected: valueSerialized === JSON.stringify({ value: initialValue }),
});
}),
});
}
}
else if (item.type === ConfigType.BOOLEAN) {
container.classList.toggle("-checked", !!initialValue);
input = $el(`input[type="checkbox"][id="${item.key}"]`, {
parent: container,
checked: initialValue,
});
}
else {
input = $el(`input[id="${item.key}"]`, {
parent: container,
value: initialValue,
});
}
$el("div.fieldrow-value", { children: [input], parent: container });
return container;
}
export class RgthreeConfigDialog extends RgthreeDialog {
constructor() {
const content = $el("div");
content.appendChild(RgthreeConfigDialog.buildFieldset(CONFIGURABLE["features"], "Features"));
content.appendChild(RgthreeConfigDialog.buildFieldset(CONFIGURABLE["menus"], "Menus"));
content.appendChild(RgthreeConfigDialog.buildFieldset(CONFIGURABLE["groups"], "Groups"));
content.appendChild(RgthreeConfigDialog.buildFieldset(CONFIGURABLE["advanced"], "Advanced"));
content.addEventListener("input", (e) => {
const changed = this.getChangedFormData();
$$(".save-button", this.element)[0].disabled =
!Object.keys(changed).length;
});
content.addEventListener("change", (e) => {
const changed = this.getChangedFormData();
$$(".save-button", this.element)[0].disabled =
!Object.keys(changed).length;
});
const dialogOptions = {
class: "-iconed -settings",
title: logoRgthree + `<h2>Settings - rgthree-comfy</h2>`,
content,
onBeforeClose: () => {
const changed = this.getChangedFormData();
if (Object.keys(changed).length) {
return confirm("Looks like there are unsaved changes. Are you sure you want close?");
}
return true;
},
buttons: [
{
label: "Save",
disabled: true,
className: "rgthree-button save-button -blue",
callback: async (e) => {
var _a, _b;
const changed = this.getChangedFormData();
if (!Object.keys(changed).length) {
this.close();
return;
}
const success = await CONFIG_SERVICE.setConfigValues(changed);
if (success) {
for (const key of Object.keys(changed)) {
(_b = (_a = Object.values(CONFIGURABLE)
.flat()
.find((f) => f.key === key)) === null || _a === void 0 ? void 0 : _a.onSave) === null || _b === void 0 ? void 0 : _b.call(_a, changed[key]);
}
this.close();
rgthree.showMessage({
id: "config-success",
message: `${checkmark} Successfully saved rgthree-comfy settings!`,
timeout: 4000,
});
$$(".save-button", this.element)[0].disabled = true;
}
else {
alert("There was an error saving rgthree-comfy configuration.");
}
},
},
],
};
super(dialogOptions);
}
static buildFieldset(datas, label) {
const fieldset = $el(`fieldset`, { children: [$el(`legend[text="${label}"]`)] });
for (const data of datas) {
if (data.isDevOnly && !rgthree.isDevMode()) {
continue;
}
const container = $el("div.formrow");
container.appendChild(fieldrow(data));
if (data.subconfig) {
for (const subfeature of data.subconfig) {
container.appendChild(fieldrow(subfeature));
}
}
fieldset.appendChild(container);
}
return fieldset;
}
getChangedFormData() {
return $$("[data-name]", this.contentElement).reduce((acc, el) => {
const name = el.dataset["name"];
const type = el.dataset["type"];
const initialValue = CONFIG_SERVICE.getConfigValue(name);
let currentValueEl = $$("fieldset.rgthree-checklist-group, input, textarea, select", el)[0];
let currentValue = null;
if (type === String(ConfigType.BOOLEAN)) {
currentValue = currentValueEl.checked;
el.classList.toggle("-checked", currentValue);
}
else {
currentValue = currentValueEl === null || currentValueEl === void 0 ? void 0 : currentValueEl.value;
if (currentValueEl.nodeName === "SELECT") {
currentValue = JSON.parse(currentValue).value;
}
else if (currentValueEl.classList.contains('rgthree-checklist-group')) {
currentValue = [];
for (const check of $$('input[type="checkbox"]', currentValueEl)) {
if (check.checked) {
currentValue.push(check.value);
}
}
}
else if (type === String(ConfigType.NUMBER)) {
currentValue = Number(currentValue) || initialValue;
}
}
if (JSON.stringify(currentValue) !== JSON.stringify(initialValue)) {
acc[name] = currentValue;
}
return acc;
}, {});
}
}
app.ui.settings.addSetting({
id: "rgthree.config",
defaultValue: null,
name: "Open rgthree-comfy config",
type: () => {
return $el("tr.rgthree-comfyui-settings-row", {
children: [
$el("td", {
child: `<div>${logoRgthree} [rgthree-comfy] configuration / settings</div>`,
}),
$el("td", {
child: $el('button.rgthree-button.-blue[text="rgthree-comfy settings"]', {
events: {
click: (e) => {
new RgthreeConfigDialog().show();
},
},
}),
}),
],
});
},
});

View File

@@ -0,0 +1,62 @@
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
export function addRgthree(str) {
return str + " (rgthree)";
}
export function stripRgthree(str) {
return str.replace(/\s*\(rgthree\)$/, "");
}
export const NodeTypesString = {
ANY_SWITCH: addRgthree("Any Switch"),
CONTEXT: addRgthree("Context"),
CONTEXT_BIG: addRgthree("Context Big"),
CONTEXT_SWITCH: addRgthree("Context Switch"),
CONTEXT_SWITCH_BIG: addRgthree("Context Switch Big"),
CONTEXT_MERGE: addRgthree("Context Merge"),
CONTEXT_MERGE_BIG: addRgthree("Context Merge Big"),
DYNAMIC_CONTEXT: addRgthree("Dynamic Context"),
DYNAMIC_CONTEXT_SWITCH: addRgthree("Dynamic Context Switch"),
DISPLAY_ANY: addRgthree("Display Any"),
IMAGE_OR_LATENT_SIZE: addRgthree("Image or Latent Size"),
NODE_MODE_RELAY: addRgthree("Mute / Bypass Relay"),
NODE_MODE_REPEATER: addRgthree("Mute / Bypass Repeater"),
FAST_MUTER: addRgthree("Fast Muter"),
FAST_BYPASSER: addRgthree("Fast Bypasser"),
FAST_GROUPS_MUTER: addRgthree("Fast Groups Muter"),
FAST_GROUPS_BYPASSER: addRgthree("Fast Groups Bypasser"),
FAST_ACTIONS_BUTTON: addRgthree("Fast Actions Button"),
LABEL: addRgthree("Label"),
POWER_PRIMITIVE: addRgthree("Power Primitive"),
POWER_PROMPT: addRgthree("Power Prompt"),
POWER_PROMPT_SIMPLE: addRgthree("Power Prompt - Simple"),
POWER_PUTER: addRgthree("Power Puter"),
POWER_CONDUCTOR: addRgthree("Power Conductor"),
SDXL_EMPTY_LATENT_IMAGE: addRgthree("SDXL Empty Latent Image"),
SDXL_POWER_PROMPT_POSITIVE: addRgthree("SDXL Power Prompt - Positive"),
SDXL_POWER_PROMPT_NEGATIVE: addRgthree("SDXL Power Prompt - Simple / Negative"),
POWER_LORA_LOADER: addRgthree("Power Lora Loader"),
KSAMPLER_CONFIG: addRgthree("KSampler Config"),
NODE_COLLECTOR: addRgthree("Node Collector"),
REROUTE: addRgthree("Reroute"),
RANDOM_UNMUTER: addRgthree("Random Unmuter"),
SEED: addRgthree("Seed"),
BOOKMARK: addRgthree("Bookmark"),
IMAGE_COMPARER: addRgthree("Image Comparer"),
IMAGE_INSET_CROP: addRgthree("Image Inset Crop"),
};
const UNRELEASED_KEYS = {
[NodeTypesString.DYNAMIC_CONTEXT]: "dynamic_context",
[NodeTypesString.DYNAMIC_CONTEXT_SWITCH]: "dynamic_context",
[NodeTypesString.POWER_CONDUCTOR]: "power_conductor",
};
export function getNodeTypeStrings() {
const unreleasedKeys = Object.keys(UNRELEASED_KEYS);
return Object.values(NodeTypesString)
.map((i) => stripRgthree(i))
.filter((i) => {
if (unreleasedKeys.includes(i)) {
return !!CONFIG_SERVICE.getConfigValue(`unreleased.${UNRELEASED_KEYS[i]}.enabled`);
}
return true;
})
.sort();
}

View File

@@ -0,0 +1,322 @@
import { app } from "../../scripts/app.js";
import { IoDirection, addConnectionLayoutSupport, addMenuItem, matchLocalSlotsToServer, replaceNode, } from "./utils.js";
import { RgthreeBaseServerNode } from "./base_node.js";
import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js";
import { debounce, wait } from "../../rgthree/common/shared_utils.js";
import { removeUnusedInputsFromEnd } from "./utils_inputs_outputs.js";
import { NodeTypesString } from "./constants.js";
function findMatchingIndexByTypeOrName(otherNode, otherSlot, ctxSlots) {
const otherNodeType = (otherNode.type || "").toUpperCase();
const otherNodeName = (otherNode.title || "").toUpperCase();
let otherSlotType = otherSlot.type;
if (Array.isArray(otherSlotType) || otherSlotType.includes(",")) {
otherSlotType = "COMBO";
}
const otherSlotName = otherSlot.name.toUpperCase().replace("OPT_", "").replace("_NAME", "");
let ctxSlotIndex = -1;
if (["CONDITIONING", "INT", "STRING", "FLOAT", "COMBO"].includes(otherSlotType)) {
ctxSlotIndex = ctxSlots.findIndex((ctxSlot) => {
const ctxSlotName = ctxSlot.name.toUpperCase().replace("OPT_", "").replace("_NAME", "");
let ctxSlotType = ctxSlot.type;
if (Array.isArray(ctxSlotType) || ctxSlotType.includes(",")) {
ctxSlotType = "COMBO";
}
if (ctxSlotType !== otherSlotType) {
return false;
}
if (ctxSlotName === otherSlotName ||
(ctxSlotName === "SEED" && otherSlotName.includes("SEED")) ||
(ctxSlotName === "STEP_REFINER" && otherSlotName.includes("AT_STEP")) ||
(ctxSlotName === "STEP_REFINER" && otherSlotName.includes("REFINER_STEP"))) {
return true;
}
if ((otherNodeType.includes("POSITIVE") || otherNodeName.includes("POSITIVE")) &&
((ctxSlotName === "POSITIVE" && otherSlotType === "CONDITIONING") ||
(ctxSlotName === "TEXT_POS_G" && otherSlotName.includes("TEXT_G")) ||
(ctxSlotName === "TEXT_POS_L" && otherSlotName.includes("TEXT_L")))) {
return true;
}
if ((otherNodeType.includes("NEGATIVE") || otherNodeName.includes("NEGATIVE")) &&
((ctxSlotName === "NEGATIVE" && otherSlotType === "CONDITIONING") ||
(ctxSlotName === "TEXT_NEG_G" && otherSlotName.includes("TEXT_G")) ||
(ctxSlotName === "TEXT_NEG_L" && otherSlotName.includes("TEXT_L")))) {
return true;
}
return false;
});
}
else {
ctxSlotIndex = ctxSlots.map((s) => s.type).indexOf(otherSlotType);
}
return ctxSlotIndex;
}
export class BaseContextNode extends RgthreeBaseServerNode {
constructor(title) {
super(title);
this.___collapsed_width = 0;
}
get _collapsed_width() {
return this.___collapsed_width;
}
set _collapsed_width(width) {
const canvas = app.canvas;
const ctx = canvas.canvas.getContext("2d");
const oldFont = ctx.font;
ctx.font = canvas.title_text_font;
let title = this.title.trim();
this.___collapsed_width = 30 + (title ? 10 + ctx.measureText(title).width : 0);
ctx.font = oldFont;
}
connectByType(slot, targetNode, targetSlotType, optsIn) {
var _a;
let canConnect = (_a = super.connectByType) === null || _a === void 0 ? void 0 : _a.call(this, slot, targetNode, targetSlotType, optsIn);
if (!super.connectByType) {
canConnect = LGraphNode.prototype.connectByType.call(this, slot, targetNode, targetSlotType, optsIn);
}
if (!canConnect && slot === 0) {
const ctrlKey = KEY_EVENT_SERVICE.ctrlKey;
for (const [index, input] of (targetNode.inputs || []).entries()) {
if (input.link && !ctrlKey) {
continue;
}
const thisOutputSlot = findMatchingIndexByTypeOrName(targetNode, input, this.outputs);
if (thisOutputSlot > -1) {
this.connect(thisOutputSlot, targetNode, index);
}
}
}
return null;
}
connectByTypeOutput(slot, sourceNode, sourceSlotType, optsIn) {
var _a, _b;
let canConnect = (_a = super.connectByTypeOutput) === null || _a === void 0 ? void 0 : _a.call(this, slot, sourceNode, sourceSlotType, optsIn);
if (!super.connectByType) {
canConnect = LGraphNode.prototype.connectByTypeOutput.call(this, slot, sourceNode, sourceSlotType, optsIn);
}
if (!canConnect && slot === 0) {
const ctrlKey = KEY_EVENT_SERVICE.ctrlKey;
for (const [index, output] of (sourceNode.outputs || []).entries()) {
if (((_b = output.links) === null || _b === void 0 ? void 0 : _b.length) && !ctrlKey) {
continue;
}
const thisInputSlot = findMatchingIndexByTypeOrName(sourceNode, output, this.inputs);
if (thisInputSlot > -1) {
sourceNode.connect(index, this, thisInputSlot);
}
}
}
return null;
}
static setUp(comfyClass, nodeData, ctxClass) {
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, ctxClass);
wait(500).then(() => {
LiteGraph.slot_types_default_out["RGTHREE_CONTEXT"] =
LiteGraph.slot_types_default_out["RGTHREE_CONTEXT"] || [];
LiteGraph.slot_types_default_out["RGTHREE_CONTEXT"].push(comfyClass.comfyClass);
});
}
static onRegisteredForOverride(comfyClass, ctxClass) {
addConnectionLayoutSupport(ctxClass, app, [
["Left", "Right"],
["Right", "Left"],
]);
setTimeout(() => {
ctxClass.category = comfyClass.category;
});
}
}
class ContextNode extends BaseContextNode {
constructor(title = ContextNode.title) {
super(title);
}
static setUp(comfyClass, nodeData) {
BaseContextNode.setUp(comfyClass, nodeData, ContextNode);
}
static onRegisteredForOverride(comfyClass, ctxClass) {
BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
addMenuItem(ContextNode, app, {
name: "Convert To Context Big",
callback: (node) => {
replaceNode(node, ContextBigNode.type);
},
});
}
}
ContextNode.title = NodeTypesString.CONTEXT;
ContextNode.type = NodeTypesString.CONTEXT;
ContextNode.comfyClass = NodeTypesString.CONTEXT;
class ContextBigNode extends BaseContextNode {
constructor(title = ContextBigNode.title) {
super(title);
}
static setUp(comfyClass, nodeData) {
BaseContextNode.setUp(comfyClass, nodeData, ContextBigNode);
}
static onRegisteredForOverride(comfyClass, ctxClass) {
BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
addMenuItem(ContextBigNode, app, {
name: "Convert To Context (Original)",
callback: (node) => {
replaceNode(node, ContextNode.type);
},
});
}
}
ContextBigNode.title = NodeTypesString.CONTEXT_BIG;
ContextBigNode.type = NodeTypesString.CONTEXT_BIG;
ContextBigNode.comfyClass = NodeTypesString.CONTEXT_BIG;
class BaseContextMultiCtxInputNode extends BaseContextNode {
constructor(title) {
super(title);
this.stabilizeBound = this.stabilize.bind(this);
this.addContextInput(5);
}
addContextInput(num = 1) {
for (let i = 0; i < num; i++) {
this.addInput(`ctx_${String(this.inputs.length + 1).padStart(2, "0")}`, "RGTHREE_CONTEXT");
}
}
onConnectionsChange(type, slotIndex, isConnected, link, ioSlot) {
var _a;
(_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.apply(this, [...arguments]);
if (type === LiteGraph.INPUT) {
this.scheduleStabilize();
}
}
scheduleStabilize(ms = 64) {
return debounce(this.stabilizeBound, 64);
}
stabilize() {
removeUnusedInputsFromEnd(this, 4);
this.addContextInput();
}
}
class ContextSwitchNode extends BaseContextMultiCtxInputNode {
constructor(title = ContextSwitchNode.title) {
super(title);
}
static setUp(comfyClass, nodeData) {
BaseContextNode.setUp(comfyClass, nodeData, ContextSwitchNode);
}
static onRegisteredForOverride(comfyClass, ctxClass) {
BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
addMenuItem(ContextSwitchNode, app, {
name: "Convert To Context Switch Big",
callback: (node) => {
replaceNode(node, ContextSwitchBigNode.type);
},
});
}
}
ContextSwitchNode.title = NodeTypesString.CONTEXT_SWITCH;
ContextSwitchNode.type = NodeTypesString.CONTEXT_SWITCH;
ContextSwitchNode.comfyClass = NodeTypesString.CONTEXT_SWITCH;
class ContextSwitchBigNode extends BaseContextMultiCtxInputNode {
constructor(title = ContextSwitchBigNode.title) {
super(title);
}
static setUp(comfyClass, nodeData) {
BaseContextNode.setUp(comfyClass, nodeData, ContextSwitchBigNode);
}
static onRegisteredForOverride(comfyClass, ctxClass) {
BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
addMenuItem(ContextSwitchBigNode, app, {
name: "Convert To Context Switch",
callback: (node) => {
replaceNode(node, ContextSwitchNode.type);
},
});
}
}
ContextSwitchBigNode.title = NodeTypesString.CONTEXT_SWITCH_BIG;
ContextSwitchBigNode.type = NodeTypesString.CONTEXT_SWITCH_BIG;
ContextSwitchBigNode.comfyClass = NodeTypesString.CONTEXT_SWITCH_BIG;
class ContextMergeNode extends BaseContextMultiCtxInputNode {
constructor(title = ContextMergeNode.title) {
super(title);
}
static setUp(comfyClass, nodeData) {
BaseContextNode.setUp(comfyClass, nodeData, ContextMergeNode);
}
static onRegisteredForOverride(comfyClass, ctxClass) {
BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
addMenuItem(ContextMergeNode, app, {
name: "Convert To Context Merge Big",
callback: (node) => {
replaceNode(node, ContextMergeBigNode.type);
},
});
}
}
ContextMergeNode.title = NodeTypesString.CONTEXT_MERGE;
ContextMergeNode.type = NodeTypesString.CONTEXT_MERGE;
ContextMergeNode.comfyClass = NodeTypesString.CONTEXT_MERGE;
class ContextMergeBigNode extends BaseContextMultiCtxInputNode {
constructor(title = ContextMergeBigNode.title) {
super(title);
}
static setUp(comfyClass, nodeData) {
BaseContextNode.setUp(comfyClass, nodeData, ContextMergeBigNode);
}
static onRegisteredForOverride(comfyClass, ctxClass) {
BaseContextNode.onRegisteredForOverride(comfyClass, ctxClass);
addMenuItem(ContextMergeBigNode, app, {
name: "Convert To Context Switch",
callback: (node) => {
replaceNode(node, ContextMergeNode.type);
},
});
}
}
ContextMergeBigNode.title = NodeTypesString.CONTEXT_MERGE_BIG;
ContextMergeBigNode.type = NodeTypesString.CONTEXT_MERGE_BIG;
ContextMergeBigNode.comfyClass = NodeTypesString.CONTEXT_MERGE_BIG;
const contextNodes = [
ContextNode,
ContextBigNode,
ContextSwitchNode,
ContextSwitchBigNode,
ContextMergeNode,
ContextMergeBigNode,
];
const contextTypeToServerDef = {};
function fixBadConfigs(node) {
const wrongName = node.outputs.find((o, i) => o.name === "CLIP_HEIGTH");
if (wrongName) {
wrongName.name = "CLIP_HEIGHT";
}
}
app.registerExtension({
name: "rgthree.Context",
async beforeRegisterNodeDef(nodeType, nodeData) {
for (const ctxClass of contextNodes) {
if (nodeData.name === ctxClass.type) {
contextTypeToServerDef[ctxClass.type] = nodeData;
ctxClass.setUp(nodeType, nodeData);
break;
}
}
},
async nodeCreated(node) {
const type = node.type || node.constructor.type;
const serverDef = type && contextTypeToServerDef[type];
if (serverDef) {
fixBadConfigs(node);
matchLocalSlotsToServer(node, IoDirection.OUTPUT, serverDef);
if (!type.includes("Switch") && !type.includes("Merge")) {
matchLocalSlotsToServer(node, IoDirection.INPUT, serverDef);
}
}
},
async loadedGraphNode(node) {
const type = node.type || node.constructor.type;
const serverDef = type && contextTypeToServerDef[type];
if (serverDef) {
fixBadConfigs(node);
matchLocalSlotsToServer(node, IoDirection.OUTPUT, serverDef);
if (!type.includes("Switch") && !type.includes("Merge")) {
matchLocalSlotsToServer(node, IoDirection.INPUT, serverDef);
}
}
},
});

View File

@@ -0,0 +1,292 @@
import { RgthreeDialog } from "../../rgthree/common/dialog.js";
import { createElement as $el, empty, appendChildren, getClosestOrSelf, query, queryAll, setAttributes, } from "../../rgthree/common/utils_dom.js";
import { logoCivitai, link, pencilColored, diskColored, dotdotdot, } from "../../rgthree/common/media/svgs.js";
import { CHECKPOINT_INFO_SERVICE, LORA_INFO_SERVICE } from "../../rgthree/common/model_info_service.js";
import { rgthree } from "./rgthree.js";
import { MenuButton } from "../../rgthree/common/menu.js";
import { generateId, injectCss } from "../../rgthree/common/shared_utils.js";
class RgthreeInfoDialog extends RgthreeDialog {
constructor(file) {
const dialogOptions = {
class: "rgthree-info-dialog",
title: `<h2>Loading...</h2>`,
content: "<center>Loading..</center>",
onBeforeClose: () => {
return true;
},
};
super(dialogOptions);
this.modifiedModelData = false;
this.modelInfo = null;
this.init(file);
}
async init(file) {
var _a, _b;
const cssPromise = injectCss("rgthree/common/css/dialog_model_info.css");
this.modelInfo = await this.getModelInfo(file);
await cssPromise;
this.setContent(this.getInfoContent());
this.setTitle(((_a = this.modelInfo) === null || _a === void 0 ? void 0 : _a["name"]) || ((_b = this.modelInfo) === null || _b === void 0 ? void 0 : _b["file"]) || "Unknown");
this.attachEvents();
}
getCloseEventDetail() {
const detail = {
dirty: this.modifiedModelData,
};
return { detail };
}
attachEvents() {
this.contentElement.addEventListener("click", async (e) => {
const target = getClosestOrSelf(e.target, "[data-action]");
const action = target === null || target === void 0 ? void 0 : target.getAttribute("data-action");
if (!target || !action) {
return;
}
await this.handleEventAction(action, target, e);
});
}
async handleEventAction(action, target, e) {
var _a, _b;
const info = this.modelInfo;
if (!(info === null || info === void 0 ? void 0 : info.file)) {
return;
}
if (action === "fetch-civitai") {
this.modelInfo = await this.refreshModelInfo(info.file);
this.setContent(this.getInfoContent());
this.setTitle(((_a = this.modelInfo) === null || _a === void 0 ? void 0 : _a["name"]) || ((_b = this.modelInfo) === null || _b === void 0 ? void 0 : _b["file"]) || "Unknown");
}
else if (action === "copy-trained-words") {
const selected = queryAll(".-rgthree-is-selected", target.closest("tr"));
const text = selected.map((el) => el.getAttribute("data-word")).join(", ");
await navigator.clipboard.writeText(text);
rgthree.showMessage({
id: "copy-trained-words-" + generateId(4),
type: "success",
message: `Successfully copied ${selected.length} key word${selected.length === 1 ? "" : "s"}.`,
timeout: 4000,
});
}
else if (action === "toggle-trained-word") {
target === null || target === void 0 ? void 0 : target.classList.toggle("-rgthree-is-selected");
const tr = target.closest("tr");
if (tr) {
const span = query("td:first-child > *", tr);
let small = query("small", span);
if (!small) {
small = $el("small", { parent: span });
}
const num = queryAll(".-rgthree-is-selected", tr).length;
small.innerHTML = num
? `${num} selected | <span role="button" data-action="copy-trained-words">Copy</span>`
: "";
}
}
else if (action === "edit-row") {
const tr = target.closest("tr");
const td = query("td:nth-child(2)", tr);
const input = td.querySelector("input,textarea");
if (!input) {
const fieldName = tr.dataset["fieldName"];
tr.classList.add("-rgthree-editing");
const isTextarea = fieldName === "userNote";
const input = $el(`${isTextarea ? "textarea" : 'input[type="text"]'}`, {
value: td.textContent,
});
input.addEventListener("keydown", (e) => {
if (!isTextarea && e.key === "Enter") {
const modified = saveEditableRow(info, tr, true);
this.modifiedModelData = this.modifiedModelData || modified;
e.stopPropagation();
e.preventDefault();
}
else if (e.key === "Escape") {
const modified = saveEditableRow(info, tr, false);
this.modifiedModelData = this.modifiedModelData || modified;
e.stopPropagation();
e.preventDefault();
}
});
appendChildren(empty(td), [input]);
input.focus();
}
else if (target.nodeName.toLowerCase() === "button") {
const modified = saveEditableRow(info, tr, true);
this.modifiedModelData = this.modifiedModelData || modified;
}
e === null || e === void 0 ? void 0 : e.preventDefault();
e === null || e === void 0 ? void 0 : e.stopPropagation();
}
}
getInfoContent() {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
const info = this.modelInfo || {};
const civitaiLink = (_a = info.links) === null || _a === void 0 ? void 0 : _a.find((i) => i.includes("civitai.com/models"));
const html = `
<ul class="rgthree-info-area">
<li title="Type" class="rgthree-info-tag -type -type-${(info.type || "").toLowerCase()}"><span>${info.type || ""}</span></li>
<li title="Base Model" class="rgthree-info-tag -basemodel -basemodel-${(info.baseModel || "").toLowerCase()}"><span>${info.baseModel || ""}</span></li>
<li class="rgthree-info-menu" stub="menu"></li>
${""}
</ul>
<table class="rgthree-info-table">
${infoTableRow("File", info.file || "")}
${infoTableRow("Hash (sha256)", info.sha256 || "")}
${civitaiLink
? infoTableRow("Civitai", `<a href="${civitaiLink}" target="_blank">${logoCivitai}View on Civitai</a>`)
: ((_c = (_b = info.raw) === null || _b === void 0 ? void 0 : _b.civitai) === null || _c === void 0 ? void 0 : _c.error) === "Model not found"
? infoTableRow("Civitai", '<i>Model not found</i> <span class="-help" title="The model was not found on civitai with the sha256 hash. It\'s possible the model was removed, re-uploaded, or was never on civitai to begin with."></span>')
: ((_e = (_d = info.raw) === null || _d === void 0 ? void 0 : _d.civitai) === null || _e === void 0 ? void 0 : _e.error)
? infoTableRow("Civitai", (_g = (_f = info.raw) === null || _f === void 0 ? void 0 : _f.civitai) === null || _g === void 0 ? void 0 : _g.error)
: !((_h = info.raw) === null || _h === void 0 ? void 0 : _h.civitai)
? infoTableRow("Civitai", `<button class="rgthree-button" data-action="fetch-civitai">Fetch info from civitai</button>`)
: ""}
${infoTableRow("Name", info.name || ((_k = (_j = info.raw) === null || _j === void 0 ? void 0 : _j.metadata) === null || _k === void 0 ? void 0 : _k.ss_output_name) || "", "The name for display.", "name")}
${!info.baseModelFile && !info.baseModelFile
? ""
: infoTableRow("Base Model", (info.baseModel || "") + (info.baseModelFile ? ` (${info.baseModelFile})` : ""))}
${!((_l = info.trainedWords) === null || _l === void 0 ? void 0 : _l.length)
? ""
: infoTableRow("Trained Words", (_m = getTrainedWordsMarkup(info.trainedWords)) !== null && _m !== void 0 ? _m : "", "Trained words from the metadata and/or civitai. Click to select for copy.")}
${!((_p = (_o = info.raw) === null || _o === void 0 ? void 0 : _o.metadata) === null || _p === void 0 ? void 0 : _p.ss_clip_skip) || ((_r = (_q = info.raw) === null || _q === void 0 ? void 0 : _q.metadata) === null || _r === void 0 ? void 0 : _r.ss_clip_skip) == "None"
? ""
: infoTableRow("Clip Skip", (_t = (_s = info.raw) === null || _s === void 0 ? void 0 : _s.metadata) === null || _t === void 0 ? void 0 : _t.ss_clip_skip)}
${infoTableRow("Strength Min", (_u = info.strengthMin) !== null && _u !== void 0 ? _u : "", "The recommended minimum strength, In the Power Lora Loader node, strength will signal when it is below this threshold.", "strengthMin")}
${infoTableRow("Strength Max", (_v = info.strengthMax) !== null && _v !== void 0 ? _v : "", "The recommended maximum strength. In the Power Lora Loader node, strength will signal when it is above this threshold.", "strengthMax")}
${""}
${infoTableRow("Additional Notes", (_w = info.userNote) !== null && _w !== void 0 ? _w : "", "Additional notes you'd like to keep and reference in the info dialog.", "userNote")}
</table>
<ul class="rgthree-info-images">${(_y = (_x = info.images) === null || _x === void 0 ? void 0 : _x.map((img) => `
<li>
<figure>${img.type === 'video'
? `<video src="${img.url}" autoplay loop></video>`
: `<img src="${img.url}" />`}
<figcaption><!--
-->${imgInfoField("", img.civitaiUrl
? `<a href="${img.civitaiUrl}" target="_blank">civitai${link}</a>`
: undefined)}<!--
-->${imgInfoField("seed", img.seed)}<!--
-->${imgInfoField("steps", img.steps)}<!--
-->${imgInfoField("cfg", img.cfg)}<!--
-->${imgInfoField("sampler", img.sampler)}<!--
-->${imgInfoField("model", img.model)}<!--
-->${imgInfoField("positive", img.positive)}<!--
-->${imgInfoField("negative", img.negative)}<!--
--><!--${""}--></figcaption>
</figure>
</li>`).join("")) !== null && _y !== void 0 ? _y : ""}</ul>
`;
const div = $el("div", { html });
if (rgthree.isDevMode()) {
setAttributes(query('[stub="menu"]', div), {
children: [
new MenuButton({
icon: dotdotdot,
options: [
{ label: "More Actions", type: "title" },
{
label: "Open API JSON",
callback: async (e) => {
var _a;
if ((_a = this.modelInfo) === null || _a === void 0 ? void 0 : _a.file) {
window.open(`rgthree/api/loras/info?file=${encodeURIComponent(this.modelInfo.file)}`);
}
},
},
{
label: "Clear all local info",
callback: async (e) => {
var _a, _b, _c;
if ((_a = this.modelInfo) === null || _a === void 0 ? void 0 : _a.file) {
this.modelInfo = await LORA_INFO_SERVICE.clearFetchedInfo(this.modelInfo.file);
this.setContent(this.getInfoContent());
this.setTitle(((_b = this.modelInfo) === null || _b === void 0 ? void 0 : _b["name"]) || ((_c = this.modelInfo) === null || _c === void 0 ? void 0 : _c["file"]) || "Unknown");
}
},
},
],
}),
],
});
}
return div;
}
}
export class RgthreeLoraInfoDialog extends RgthreeInfoDialog {
async getModelInfo(file) {
return LORA_INFO_SERVICE.getInfo(file, false, false);
}
async refreshModelInfo(file) {
return LORA_INFO_SERVICE.refreshInfo(file);
}
async clearModelInfo(file) {
return LORA_INFO_SERVICE.clearFetchedInfo(file);
}
}
export class RgthreeCheckpointInfoDialog extends RgthreeInfoDialog {
async getModelInfo(file) {
return CHECKPOINT_INFO_SERVICE.getInfo(file, false, false);
}
async refreshModelInfo(file) {
return CHECKPOINT_INFO_SERVICE.refreshInfo(file);
}
async clearModelInfo(file) {
return CHECKPOINT_INFO_SERVICE.clearFetchedInfo(file);
}
}
function infoTableRow(name, value, help = "", editableFieldName = "") {
return `
<tr class="${editableFieldName ? "editable" : ""}" ${editableFieldName ? `data-field-name="${editableFieldName}"` : ""}>
<td><span>${name} ${help ? `<span class="-help" title="${help}"></span>` : ""}<span></td>
<td ${editableFieldName ? "" : 'colspan="2"'}>${String(value).startsWith("<") ? value : `<span>${value}<span>`}</td>
${editableFieldName
? `<td style="width: 24px;"><button class="rgthree-button-reset rgthree-button-edit" data-action="edit-row">${pencilColored}${diskColored}</button></td>`
: ""}
</tr>`;
}
function getTrainedWordsMarkup(words) {
let markup = `<ul class="rgthree-info-trained-words-list">`;
for (const wordData of words || []) {
markup += `<li title="${wordData.word}" data-word="${wordData.word}" class="rgthree-info-trained-words-list-item" data-action="toggle-trained-word">
<span>${wordData.word}</span>
${wordData.civitai ? logoCivitai : ""}
${wordData.count != null ? `<small>${wordData.count}</small>` : ""}
</li>`;
}
markup += `</ul>`;
return markup;
}
function saveEditableRow(info, tr, saving = true) {
var _a;
const fieldName = tr.dataset["fieldName"];
const input = query("input,textarea", tr);
let newValue = (_a = info[fieldName]) !== null && _a !== void 0 ? _a : "";
let modified = false;
if (saving) {
newValue = input.value;
if (fieldName.startsWith("strength")) {
if (Number.isNaN(Number(newValue))) {
alert(`You must enter a number into the ${fieldName} field.`);
return false;
}
newValue = (Math.round(Number(newValue) * 100) / 100).toFixed(2);
}
LORA_INFO_SERVICE.savePartialInfo(info.file, { [fieldName]: newValue });
modified = true;
}
tr.classList.remove("-rgthree-editing");
const td = query("td:nth-child(2)", tr);
appendChildren(empty(td), [$el("span", { text: newValue })]);
return modified;
}
function imgInfoField(label, value) {
return value != null ? `<span>${label ? `<label>${label} </label>` : ""}${value}</span>` : "";
}

View File

@@ -0,0 +1,35 @@
import { app } from "../../scripts/app.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
import { addConnectionLayoutSupport } from "./utils.js";
import { rgthree } from "./rgthree.js";
let hasShownAlertForUpdatingInt = false;
app.registerExtension({
name: "rgthree.DisplayAny",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "Display Any (rgthree)" || nodeData.name === "Display Int (rgthree)") {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
onNodeCreated ? onNodeCreated.apply(this, []) : undefined;
this.showValueWidget = ComfyWidgets["STRING"](this, "output", ["STRING", { multiline: true }], app).widget;
this.showValueWidget.inputEl.readOnly = true;
this.showValueWidget.serializeValue = async (node, index) => {
const n = rgthree.getNodeFromInitialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff(node);
if (n) {
n.widgets_values[index] = "";
}
else {
console.warn("No serialized node found in workflow. May be attributed to " +
"https://github.com/comfyanonymous/ComfyUI/issues/2193");
}
return "";
};
};
addConnectionLayoutSupport(nodeType, app, [["Left"], ["Right"]]);
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
onExecuted === null || onExecuted === void 0 ? void 0 : onExecuted.apply(this, [message]);
this.showValueWidget.value = message.text[0];
};
}
},
});

View File

@@ -0,0 +1,253 @@
import { app } from "../../scripts/app.js";
import { IoDirection, followConnectionUntilType, getConnectedInputInfosAndFilterPassThroughs, } from "./utils.js";
import { rgthree } from "./rgthree.js";
import { SERVICE as CONTEXT_SERVICE, InputMutationOperation, } from "./services/context_service.js";
import { NodeTypesString } from "./constants.js";
import { removeUnusedInputsFromEnd } from "./utils_inputs_outputs.js";
import { DynamicContextNodeBase } from "./dynamic_context_base.js";
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
const OWNED_PREFIX = "+";
const REGEX_OWNED_PREFIX = /^\+\s*/;
const REGEX_EMPTY_INPUT = /^\+\s*$/;
export class DynamicContextNode extends DynamicContextNodeBase {
constructor(title = DynamicContextNode.title) {
super(title);
}
onNodeCreated() {
this.addInput("base_ctx", "RGTHREE_DYNAMIC_CONTEXT");
this.ensureOneRemainingNewInputSlot();
super.onNodeCreated();
}
onConnectionsChange(type, slotIndex, isConnected, link, ioSlot) {
var _a;
(_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.call(this, type, slotIndex, isConnected, link, ioSlot);
if (this.configuring) {
return;
}
if (type === LiteGraph.INPUT) {
if (isConnected) {
this.handleInputConnected(slotIndex);
}
else {
this.handleInputDisconnected(slotIndex);
}
}
}
onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex) {
var _a;
let canConnect = true;
if (super.onConnectInput) {
canConnect = super.onConnectInput.apply(this, [...arguments]);
}
if (canConnect &&
outputNode instanceof DynamicContextNode &&
outputIndex === 0 &&
inputIndex !== 0) {
const [n, v] = rgthree.logger.warnParts("Currently, you can only connect a context node in the first slot.");
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
canConnect = false;
}
return canConnect;
}
handleInputConnected(slotIndex) {
const ioSlot = this.inputs[slotIndex];
const connectedIndexes = [];
if (slotIndex === 0) {
let baseNodeInfos = getConnectedInputInfosAndFilterPassThroughs(this, this, 0);
const baseNodes = baseNodeInfos.map((n) => n.node);
const baseNodesDynamicCtx = baseNodes[0];
if (baseNodesDynamicCtx === null || baseNodesDynamicCtx === void 0 ? void 0 : baseNodesDynamicCtx.provideInputsData) {
const inputsData = CONTEXT_SERVICE.getDynamicContextInputsData(baseNodesDynamicCtx);
console.log("inputsData", inputsData);
for (const input of baseNodesDynamicCtx.provideInputsData()) {
if (input.name === "base_ctx" || input.name === "+") {
continue;
}
this.addContextInput(input.name, input.type, input.index);
this.stabilizeNames();
}
}
}
else if (this.isInputSlotForNewInput(slotIndex)) {
this.handleNewInputConnected(slotIndex);
}
}
isInputSlotForNewInput(slotIndex) {
const ioSlot = this.inputs[slotIndex];
return ioSlot && ioSlot.name === "+" && ioSlot.type === "*";
}
handleNewInputConnected(slotIndex) {
if (!this.isInputSlotForNewInput(slotIndex)) {
throw new Error('Expected the incoming slot index to be the "new input" input.');
}
const ioSlot = this.inputs[slotIndex];
let cxn = null;
if (ioSlot.link != null) {
cxn = followConnectionUntilType(this, IoDirection.INPUT, slotIndex, true);
}
if ((cxn === null || cxn === void 0 ? void 0 : cxn.type) && (cxn === null || cxn === void 0 ? void 0 : cxn.name)) {
let name = this.addOwnedPrefix(this.getNextUniqueNameForThisNode(cxn.name));
if (name.match(/^\+\s*[A-Z_]+(\.\d+)?$/)) {
name = name.toLowerCase();
}
ioSlot.name = name;
ioSlot.type = cxn.type;
ioSlot.removable = true;
while (!this.outputs[slotIndex]) {
this.addOutput("*", "*");
}
this.outputs[slotIndex].type = cxn.type;
this.outputs[slotIndex].name = this.stripOwnedPrefix(name).toLocaleUpperCase();
if (cxn.type === "COMBO" || cxn.type.includes(",") || Array.isArray(cxn.type)) {
this.outputs[slotIndex].widget = true;
}
this.inputsMutated({
operation: InputMutationOperation.ADDED,
node: this,
slotIndex,
slot: ioSlot,
});
this.stabilizeNames();
this.ensureOneRemainingNewInputSlot();
}
}
handleInputDisconnected(slotIndex) {
var _a, _b;
const inputs = this.getContextInputsList();
if (slotIndex === 0) {
for (let index = inputs.length - 1; index > 0; index--) {
if (index === 0 || index === inputs.length - 1) {
continue;
}
const input = inputs[index];
if (!this.isOwnedInput(input.name)) {
if (input.link || ((_b = (_a = this.outputs[index]) === null || _a === void 0 ? void 0 : _a.links) === null || _b === void 0 ? void 0 : _b.length)) {
this.renameContextInput(index, input.name, true);
}
else {
this.removeContextInput(index);
}
}
}
this.setSize(this.computeSize());
this.setDirtyCanvas(true, true);
}
}
ensureOneRemainingNewInputSlot() {
removeUnusedInputsFromEnd(this, 1, REGEX_EMPTY_INPUT);
this.addInput(OWNED_PREFIX, "*");
}
getNextUniqueNameForThisNode(desiredName) {
const inputs = this.getContextInputsList();
const allExistingKeys = inputs.map((i) => this.stripOwnedPrefix(i.name).toLocaleUpperCase());
desiredName = this.stripOwnedPrefix(desiredName);
let newName = desiredName;
let n = 0;
while (allExistingKeys.includes(newName.toLocaleUpperCase())) {
newName = `${desiredName}.${++n}`;
}
return newName;
}
removeInput(slotIndex) {
const slot = this.inputs[slotIndex];
super.removeInput(slotIndex);
if (this.outputs[slotIndex]) {
this.removeOutput(slotIndex);
}
this.inputsMutated({ operation: InputMutationOperation.REMOVED, node: this, slotIndex, slot });
this.stabilizeNames();
}
stabilizeNames() {
const inputs = this.getContextInputsList();
const names = [];
for (const [index, input] of inputs.entries()) {
if (index === 0 || index === inputs.length - 1) {
continue;
}
input.label = undefined;
this.outputs[index].label = undefined;
let origName = this.stripOwnedPrefix(input.name).replace(/\.\d+$/, "");
let name = input.name;
if (!this.isOwnedInput(name)) {
names.push(name.toLocaleUpperCase());
}
else {
let n = 0;
name = this.addOwnedPrefix(origName);
while (names.includes(this.stripOwnedPrefix(name).toLocaleUpperCase())) {
name = `${this.addOwnedPrefix(origName)}.${++n}`;
}
names.push(this.stripOwnedPrefix(name).toLocaleUpperCase());
if (input.name !== name) {
this.renameContextInput(index, name);
}
}
}
}
getSlotMenuOptions(slot) {
const editable = this.isOwnedInput(slot.input.name) && this.type !== "*";
return [
{
content: "✏️ Rename Input",
disabled: !editable,
callback: () => {
var dialog = app.canvas.createDialog("<span class='name'>Name</span><input autofocus type='text'/><button>OK</button>", {});
var dialogInput = dialog.querySelector("input");
if (dialogInput) {
dialogInput.value = this.stripOwnedPrefix(slot.input.name || "");
}
var inner = () => {
this.handleContextMenuRenameInputDialog(slot.slot, dialogInput.value);
dialog.close();
};
dialog.querySelector("button").addEventListener("click", inner);
dialogInput.addEventListener("keydown", (e) => {
var _a;
dialog.is_modified = true;
if (e.keyCode == 27) {
dialog.close();
}
else if (e.keyCode == 13) {
inner();
}
else if (e.keyCode != 13 && ((_a = e.target) === null || _a === void 0 ? void 0 : _a.localName) != "textarea") {
return;
}
e.preventDefault();
e.stopPropagation();
});
dialogInput.focus();
},
},
{
content: "🗑️ Delete Input",
disabled: !editable,
callback: () => {
this.removeInput(slot.slot);
},
},
];
}
handleContextMenuRenameInputDialog(slotIndex, value) {
app.graph.beforeChange();
this.renameContextInput(slotIndex, value);
this.stabilizeNames();
this.setDirtyCanvas(true, true);
app.graph.afterChange();
}
}
DynamicContextNode.title = NodeTypesString.DYNAMIC_CONTEXT;
DynamicContextNode.type = NodeTypesString.DYNAMIC_CONTEXT;
DynamicContextNode.comfyClass = NodeTypesString.DYNAMIC_CONTEXT;
const contextDynamicNodes = [DynamicContextNode];
app.registerExtension({
name: "rgthree.DynamicContext",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (!CONFIG_SERVICE.getConfigValue("unreleased.dynamic_context.enabled")) {
return;
}
if (nodeData.name === DynamicContextNode.type) {
DynamicContextNode.setUp(nodeType, nodeData);
}
},
});

View File

@@ -0,0 +1,192 @@
import { app } from "../../scripts/app.js";
import { BaseContextNode } from "./context.js";
import { RgthreeBaseServerNode } from "./base_node.js";
import { moveArrayItem, wait } from "../../rgthree/common/shared_utils.js";
import { RgthreeInvisibleWidget } from "./utils_widgets.js";
import { getContextOutputName, InputMutationOperation, } from "./services/context_service.js";
import { SERVICE as CONTEXT_SERVICE } from "./services/context_service.js";
const OWNED_PREFIX = "+";
const REGEX_OWNED_PREFIX = /^\+\s*/;
const REGEX_EMPTY_INPUT = /^\+\s*$/;
export class DynamicContextNodeBase extends BaseContextNode {
constructor() {
super(...arguments);
this.hasShadowInputs = false;
}
getContextInputsList() {
return this.inputs;
}
provideInputsData() {
const inputs = this.getContextInputsList();
return inputs
.map((input, index) => ({
name: this.stripOwnedPrefix(input.name),
type: String(input.type),
index,
}))
.filter((i) => i.type !== "*");
}
addOwnedPrefix(name) {
return `+ ${this.stripOwnedPrefix(name)}`;
}
isOwnedInput(inputOrName) {
const name = typeof inputOrName == "string" ? inputOrName : (inputOrName === null || inputOrName === void 0 ? void 0 : inputOrName.name) || "";
return REGEX_OWNED_PREFIX.test(name);
}
stripOwnedPrefix(name) {
return name.replace(REGEX_OWNED_PREFIX, "");
}
handleUpstreamMutation(mutation) {
console.log(`[node ${this.id}] handleUpstreamMutation`, mutation);
if (mutation.operation === InputMutationOperation.ADDED) {
const slot = mutation.slot;
if (!slot) {
throw new Error("Cannot have an ADDED mutation without a provided slot data.");
}
this.addContextInput(this.stripOwnedPrefix(slot.name), slot.type, mutation.slotIndex);
return;
}
if (mutation.operation === InputMutationOperation.REMOVED) {
const slot = mutation.slot;
if (!slot) {
throw new Error("Cannot have an REMOVED mutation without a provided slot data.");
}
this.removeContextInput(mutation.slotIndex);
return;
}
if (mutation.operation === InputMutationOperation.RENAMED) {
const slot = mutation.slot;
if (!slot) {
throw new Error("Cannot have an RENAMED mutation without a provided slot data.");
}
this.renameContextInput(mutation.slotIndex, slot.name);
return;
}
}
clone() {
const cloned = super.clone();
while (cloned.inputs.length > 1) {
cloned.removeInput(cloned.inputs.length - 1);
}
while (cloned.widgets.length > 1) {
cloned.removeWidget(cloned.widgets.length - 1);
}
while (cloned.outputs.length > 1) {
cloned.removeOutput(cloned.outputs.length - 1);
}
return cloned;
}
onNodeCreated() {
const node = this;
this.addCustomWidget(new RgthreeInvisibleWidget("output_keys", "RGTHREE_DYNAMIC_CONTEXT_OUTPUTS", "", () => {
return (node.outputs || [])
.map((o, i) => i > 0 && o.name)
.filter((n) => n !== false)
.join(",");
}));
}
addContextInput(name, type, slot = -1) {
const inputs = this.getContextInputsList();
if (this.hasShadowInputs) {
inputs.push({ name, type, link: null, boundingRect: null });
}
else {
this.addInput(name, type);
}
if (slot > -1) {
moveArrayItem(inputs, inputs.length - 1, slot);
}
else {
slot = inputs.length - 1;
}
if (type !== "*") {
const output = this.addOutput(getContextOutputName(name), type);
if (type === "COMBO" || String(type).includes(",") || Array.isArray(type)) {
output.widget = true;
}
if (slot > -1) {
moveArrayItem(this.outputs, this.outputs.length - 1, slot);
}
}
this.fixInputsOutputsLinkSlots();
this.inputsMutated({
operation: InputMutationOperation.ADDED,
node: this,
slotIndex: slot,
slot: inputs[slot],
});
}
removeContextInput(slotIndex) {
if (this.hasShadowInputs) {
const inputs = this.getContextInputsList();
const input = inputs.splice(slotIndex, 1)[0];
if (this.outputs[slotIndex]) {
this.removeOutput(slotIndex);
}
}
else {
this.removeInput(slotIndex);
}
}
renameContextInput(index, newName, forceOwnBool = null) {
const inputs = this.getContextInputsList();
const input = inputs[index];
const oldName = input.name;
newName = this.stripOwnedPrefix(newName.trim() || this.getSlotDefaultInputLabel(index));
if (forceOwnBool === true || (this.isOwnedInput(oldName) && forceOwnBool !== false)) {
newName = this.addOwnedPrefix(newName);
}
if (oldName !== newName) {
input.name = newName;
input.removable = this.isOwnedInput(newName);
this.outputs[index].name = getContextOutputName(inputs[index].name);
this.inputsMutated({
node: this,
operation: InputMutationOperation.RENAMED,
slotIndex: index,
slot: input,
});
}
}
getSlotDefaultInputLabel(slotIndex) {
const inputs = this.getContextInputsList();
const input = inputs[slotIndex];
let defaultLabel = this.stripOwnedPrefix(input.name).toLowerCase();
return defaultLabel.toLocaleLowerCase();
}
inputsMutated(mutation) {
CONTEXT_SERVICE.onInputChanges(this, mutation);
}
fixInputsOutputsLinkSlots() {
if (!this.hasShadowInputs) {
const inputs = this.getContextInputsList();
for (let index = inputs.length - 1; index > 0; index--) {
const input = inputs[index];
if ((input === null || input === void 0 ? void 0 : input.link) != null) {
app.graph.links[input.link].target_slot = index;
}
}
}
const outputs = this.outputs;
for (let index = outputs.length - 1; index > 0; index--) {
const output = outputs[index];
if (output) {
output.nameLocked = true;
for (const link of output.links || []) {
app.graph.links[link].origin_slot = index;
}
}
}
}
static setUp(comfyClass, nodeData) {
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, this);
wait(500).then(() => {
LiteGraph.slot_types_default_out["RGTHREE_DYNAMIC_CONTEXT"] =
LiteGraph.slot_types_default_out["RGTHREE_DYNAMIC_CONTEXT"] || [];
const comfyClassStr = comfyClass.comfyClass;
if (comfyClassStr) {
LiteGraph.slot_types_default_out["RGTHREE_DYNAMIC_CONTEXT"].push(comfyClassStr);
}
});
}
}

View File

@@ -0,0 +1,147 @@
import { app } from "../../scripts/app.js";
import { DynamicContextNodeBase } from "./dynamic_context_base.js";
import { NodeTypesString } from "./constants.js";
import { SERVICE as CONTEXT_SERVICE, getContextOutputName, } from "./services/context_service.js";
import { getConnectedInputNodesAndFilterPassThroughs } from "./utils.js";
import { debounce, moveArrayItem } from "../../rgthree/common/shared_utils.js";
import { measureText } from "./utils_canvas.js";
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
class DynamicContextSwitchNode extends DynamicContextNodeBase {
constructor(title = DynamicContextSwitchNode.title) {
super(title);
this.hasShadowInputs = true;
this.lastInputsList = [];
this.shadowInputs = [
{ name: "base_ctx", type: "RGTHREE_DYNAMIC_CONTEXT", link: null, count: 0, boundingRect: null },
];
}
getContextInputsList() {
return this.shadowInputs;
}
handleUpstreamMutation(mutation) {
this.scheduleHardRefresh();
}
onConnectionsChange(type, slotIndex, isConnected, link, inputOrOutput) {
var _a;
(_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.call(this, type, slotIndex, isConnected, link, inputOrOutput);
if (this.configuring) {
return;
}
if (type === LiteGraph.INPUT) {
this.scheduleHardRefresh();
}
}
scheduleHardRefresh(ms = 64) {
return debounce(() => {
this.refreshInputsAndOutputs();
}, ms);
}
onNodeCreated() {
this.addInput("ctx_1", "RGTHREE_DYNAMIC_CONTEXT");
this.addInput("ctx_2", "RGTHREE_DYNAMIC_CONTEXT");
this.addInput("ctx_3", "RGTHREE_DYNAMIC_CONTEXT");
this.addInput("ctx_4", "RGTHREE_DYNAMIC_CONTEXT");
this.addInput("ctx_5", "RGTHREE_DYNAMIC_CONTEXT");
super.onNodeCreated();
}
addContextInput(name, type, slot) { }
refreshInputsAndOutputs() {
var _a;
const inputs = [
{ name: "base_ctx", type: "RGTHREE_DYNAMIC_CONTEXT", link: null, count: 0, boundingRect: null },
];
let numConnected = 0;
for (let i = 0; i < this.inputs.length; i++) {
const childCtxs = getConnectedInputNodesAndFilterPassThroughs(this, this, i);
if (childCtxs.length > 1) {
throw new Error("How is there more than one input?");
}
const ctx = childCtxs[0];
if (!ctx)
continue;
numConnected++;
const slotsData = CONTEXT_SERVICE.getDynamicContextInputsData(ctx);
console.log(slotsData);
for (const slotData of slotsData) {
const found = inputs.find((n) => getContextOutputName(slotData.name) === getContextOutputName(n.name));
if (found) {
found.count += 1;
continue;
}
inputs.push({
name: slotData.name,
type: slotData.type,
link: null,
count: 1,
boundingRect: null,
});
}
}
this.shadowInputs = inputs;
let i = 0;
for (i; i < this.shadowInputs.length; i++) {
const data = this.shadowInputs[i];
let existing = this.outputs.find((o) => getContextOutputName(o.name) === getContextOutputName(data.name));
if (!existing) {
existing = this.addOutput(getContextOutputName(data.name), data.type);
}
moveArrayItem(this.outputs, existing, i);
delete existing.rgthree_status;
if (data.count !== numConnected) {
existing.rgthree_status = "WARN";
}
}
while (this.outputs[i]) {
const output = this.outputs[i];
if ((_a = output === null || output === void 0 ? void 0 : output.links) === null || _a === void 0 ? void 0 : _a.length) {
output.rgthree_status = "ERROR";
i++;
}
else {
this.removeOutput(i);
}
}
this.fixInputsOutputsLinkSlots();
}
onDrawForeground(ctx, canvas) {
var _a, _b;
const low_quality = ((_b = (_a = canvas === null || canvas === void 0 ? void 0 : canvas.ds) === null || _a === void 0 ? void 0 : _a.scale) !== null && _b !== void 0 ? _b : 1) < 0.6;
if (low_quality || this.size[0] <= 10) {
return;
}
let y = LiteGraph.NODE_SLOT_HEIGHT - 1;
const w = this.size[0];
ctx.save();
ctx.font = "normal " + LiteGraph.NODE_SUBTEXT_SIZE + "px Arial";
ctx.textAlign = "right";
for (const output of this.outputs) {
if (!output.rgthree_status) {
y += LiteGraph.NODE_SLOT_HEIGHT;
continue;
}
const x = w - 20 - measureText(ctx, output.name);
if (output.rgthree_status === "ERROR") {
ctx.fillText("🛑", x, y);
}
else if (output.rgthree_status === "WARN") {
ctx.fillText("⚠️", x, y);
}
y += LiteGraph.NODE_SLOT_HEIGHT;
}
ctx.restore();
}
}
DynamicContextSwitchNode.title = NodeTypesString.DYNAMIC_CONTEXT_SWITCH;
DynamicContextSwitchNode.type = NodeTypesString.DYNAMIC_CONTEXT_SWITCH;
DynamicContextSwitchNode.comfyClass = NodeTypesString.DYNAMIC_CONTEXT_SWITCH;
app.registerExtension({
name: "rgthree.DynamicContextSwitch",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (!CONFIG_SERVICE.getConfigValue("unreleased.dynamic_context.enabled")) {
return;
}
if (nodeData.name === DynamicContextSwitchNode.type) {
DynamicContextSwitchNode.setUp(nodeType, nodeData);
}
},
});

View File

@@ -0,0 +1,276 @@
import { app } from "../../scripts/app.js";
import { BaseAnyInputConnectedNode } from "./base_any_input_connected_node.js";
import { NodeTypesString } from "./constants.js";
import { addMenuItem, changeModeOfNodes } from "./utils.js";
import { rgthree } from "./rgthree.js";
const MODE_ALWAYS = 0;
const MODE_MUTE = 2;
const MODE_BYPASS = 4;
class FastActionsButton extends BaseAnyInputConnectedNode {
constructor(title) {
super(title);
this.comfyClass = NodeTypesString.FAST_ACTIONS_BUTTON;
this.logger = rgthree.newLogSession("[FastActionsButton]");
this.isVirtualNode = true;
this.serialize_widgets = true;
this.widgetToData = new Map();
this.nodeIdtoFunctionCache = new Map();
this.executingFromShortcut = false;
this.properties["buttonText"] = "🎬 Action!";
this.properties["shortcutModifier"] = "alt";
this.properties["shortcutKey"] = "";
this.buttonWidget = this.addWidget("button", this.properties["buttonText"], "", () => {
this.executeConnectedNodes();
}, { serialize: false });
this.keypressBound = this.onKeypress.bind(this);
this.keyupBound = this.onKeyup.bind(this);
this.onConstructed();
}
configure(info) {
super.configure(info);
setTimeout(() => {
if (info.widgets_values) {
for (let [index, value] of info.widgets_values.entries()) {
if (index > 0) {
if (typeof value === "string" && value.startsWith("comfy_action:")) {
value = value.replace("comfy_action:", "");
this.addComfyActionWidget(index, value);
}
if (this.widgets[index]) {
this.widgets[index].value = value;
}
}
}
}
}, 100);
}
clone() {
const cloned = super.clone();
cloned.properties["buttonText"] = "🎬 Action!";
cloned.properties["shortcutKey"] = "";
return cloned;
}
onAdded(graph) {
window.addEventListener("keydown", this.keypressBound);
window.addEventListener("keyup", this.keyupBound);
}
onRemoved() {
window.removeEventListener("keydown", this.keypressBound);
window.removeEventListener("keyup", this.keyupBound);
}
async onKeypress(event) {
const target = event.target;
if (this.executingFromShortcut ||
target.localName == "input" ||
target.localName == "textarea") {
return;
}
if (this.properties["shortcutKey"].trim() &&
this.properties["shortcutKey"].toLowerCase() === event.key.toLowerCase()) {
const shortcutModifier = this.properties["shortcutModifier"];
let good = shortcutModifier === "ctrl" && event.ctrlKey;
good = good || (shortcutModifier === "alt" && event.altKey);
good = good || (shortcutModifier === "shift" && event.shiftKey);
good = good || (shortcutModifier === "meta" && event.metaKey);
if (good) {
setTimeout(() => {
this.executeConnectedNodes();
}, 20);
this.executingFromShortcut = true;
event.preventDefault();
event.stopImmediatePropagation();
app.canvas.dirty_canvas = true;
return false;
}
}
return;
}
onKeyup(event) {
const target = event.target;
if (target.localName == "input" || target.localName == "textarea") {
return;
}
this.executingFromShortcut = false;
}
onPropertyChanged(property, value, prevValue) {
var _a, _b;
if (property == "buttonText" && typeof value === "string") {
this.buttonWidget.name = value;
}
if (property == "shortcutKey" && typeof value === "string") {
this.properties["shortcutKey"] = (_b = (_a = value.trim()[0]) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : "";
}
return true;
}
handleLinkedNodesStabilization(linkedNodes) {
var _a, _b, _c, _d, _e, _f, _g, _h;
let changed = false;
for (const [widget, data] of this.widgetToData.entries()) {
if (!data.node) {
continue;
}
if (!linkedNodes.includes(data.node)) {
const index = this.widgets.indexOf(widget);
if (index > -1) {
this.widgetToData.delete(widget);
this.removeWidget(widget);
changed = true;
}
else {
const [m, a] = this.logger.debugParts("Connected widget is not in widgets... weird.");
(_a = console[m]) === null || _a === void 0 ? void 0 : _a.call(console, ...a);
}
}
}
const badNodes = [];
let indexOffset = 1;
for (const [index, node] of linkedNodes.entries()) {
if (!node) {
const [m, a] = this.logger.debugParts("linkedNode provided that does not exist. ");
(_b = console[m]) === null || _b === void 0 ? void 0 : _b.call(console, ...a);
badNodes.push(node);
continue;
}
let widgetAtSlot = this.widgets[index + indexOffset];
if (widgetAtSlot && ((_c = this.widgetToData.get(widgetAtSlot)) === null || _c === void 0 ? void 0 : _c.comfy)) {
indexOffset++;
widgetAtSlot = this.widgets[index + indexOffset];
}
if (!widgetAtSlot || ((_e = (_d = this.widgetToData.get(widgetAtSlot)) === null || _d === void 0 ? void 0 : _d.node) === null || _e === void 0 ? void 0 : _e.id) !== node.id) {
let widget = null;
for (let i = index + indexOffset; i < this.widgets.length; i++) {
if (((_g = (_f = this.widgetToData.get(this.widgets[i])) === null || _f === void 0 ? void 0 : _f.node) === null || _g === void 0 ? void 0 : _g.id) === node.id) {
widget = this.widgets.splice(i, 1)[0];
this.widgets.splice(index + indexOffset, 0, widget);
changed = true;
break;
}
}
if (!widget) {
const exposedActions = node.constructor.exposedActions || [];
widget = this.addWidget("combo", node.title, "None", "", {
values: ["None", "Mute", "Bypass", "Enable", ...exposedActions],
});
widget.serializeValue = async (_node, _index) => {
return widget === null || widget === void 0 ? void 0 : widget.value;
};
this.widgetToData.set(widget, { node });
changed = true;
}
}
}
for (let i = this.widgets.length - 1; i > linkedNodes.length + indexOffset - 1; i--) {
const widgetAtSlot = this.widgets[i];
if (widgetAtSlot && ((_h = this.widgetToData.get(widgetAtSlot)) === null || _h === void 0 ? void 0 : _h.comfy)) {
continue;
}
this.removeWidget(widgetAtSlot);
changed = true;
}
return changed;
}
removeWidget(widget) {
widget = typeof widget === "number" ? this.widgets[widget] : widget;
if (widget && this.widgetToData.has(widget)) {
this.widgetToData.delete(widget);
}
super.removeWidget(widget);
}
async executeConnectedNodes() {
var _a, _b;
for (const widget of this.widgets) {
if (widget == this.buttonWidget) {
continue;
}
const action = widget.value;
const { comfy, node } = (_a = this.widgetToData.get(widget)) !== null && _a !== void 0 ? _a : {};
if (comfy) {
if (action === "Queue Prompt") {
await comfy.queuePrompt(0);
}
continue;
}
if (node) {
if (action === "Mute") {
changeModeOfNodes(node, MODE_MUTE);
}
else if (action === "Bypass") {
changeModeOfNodes(node, MODE_BYPASS);
}
else if (action === "Enable") {
changeModeOfNodes(node, MODE_ALWAYS);
}
if (node.handleAction) {
if (typeof action !== "string") {
throw new Error("Fast Actions Button action should be a string: " + action);
}
await node.handleAction(action);
}
(_b = this.graph) === null || _b === void 0 ? void 0 : _b.change();
continue;
}
console.warn("Fast Actions Button has a widget without correct data.");
}
}
addComfyActionWidget(slot, value) {
let widget = this.addWidget("combo", "Comfy Action", "None", () => {
if (String(widget.value).startsWith("MOVE ")) {
this.widgets.push(this.widgets.splice(this.widgets.indexOf(widget), 1)[0]);
widget.value = String(widget.rgthree_lastValue);
}
else if (String(widget.value).startsWith("REMOVE ")) {
this.removeWidget(widget);
}
widget.rgthree_lastValue = widget.value;
}, {
values: ["None", "Queue Prompt", "REMOVE Comfy Action", "MOVE to end"],
});
widget.rgthree_lastValue = value;
widget.serializeValue = async (_node, _index) => {
return `comfy_app:${widget === null || widget === void 0 ? void 0 : widget.value}`;
};
this.widgetToData.set(widget, { comfy: app });
if (slot != null) {
this.widgets.splice(slot, 0, this.widgets.splice(this.widgets.indexOf(widget), 1)[0]);
}
return widget;
}
onSerialize(serialised) {
var _a, _b;
(_a = super.onSerialize) === null || _a === void 0 ? void 0 : _a.call(this, serialised);
for (let [index, value] of (serialised.widgets_values || []).entries()) {
if (((_b = this.widgets[index]) === null || _b === void 0 ? void 0 : _b.name) === "Comfy Action") {
serialised.widgets_values[index] = `comfy_action:${value}`;
}
}
}
static setUp() {
super.setUp();
addMenuItem(this, app, {
name: " Append a Comfy Action",
callback: (nodeArg) => {
nodeArg.addComfyActionWidget();
},
});
}
}
FastActionsButton.type = NodeTypesString.FAST_ACTIONS_BUTTON;
FastActionsButton.title = NodeTypesString.FAST_ACTIONS_BUTTON;
FastActionsButton["@buttonText"] = { type: "string" };
FastActionsButton["@shortcutModifier"] = {
type: "combo",
values: ["ctrl", "alt", "shift"],
};
FastActionsButton["@shortcutKey"] = { type: "string" };
FastActionsButton.collapsible = false;
app.registerExtension({
name: "rgthree.FastActionsButton",
registerCustomNodes() {
FastActionsButton.setUp();
},
loadedGraphNode(node) {
if (node.type == FastActionsButton.title) {
node._tempWidth = node.size[0];
}
},
});

View File

@@ -0,0 +1,27 @@
import { app } from "../../scripts/app.js";
import { NodeTypesString } from "./constants.js";
import { BaseFastGroupsModeChanger } from "./fast_groups_muter.js";
export class FastGroupsBypasser extends BaseFastGroupsModeChanger {
constructor(title = FastGroupsBypasser.title) {
super(title);
this.comfyClass = NodeTypesString.FAST_GROUPS_BYPASSER;
this.helpActions = "bypass and enable";
this.modeOn = LiteGraph.ALWAYS;
this.modeOff = 4;
this.onConstructed();
}
}
FastGroupsBypasser.type = NodeTypesString.FAST_GROUPS_BYPASSER;
FastGroupsBypasser.title = NodeTypesString.FAST_GROUPS_BYPASSER;
FastGroupsBypasser.exposedActions = ["Bypass all", "Enable all", "Toggle all"];
app.registerExtension({
name: "rgthree.FastGroupsBypasser",
registerCustomNodes() {
FastGroupsBypasser.setUp();
},
loadedGraphNode(node) {
if (node.type == FastGroupsBypasser.title) {
node.tempSize = [...node.size];
}
},
});

View File

@@ -0,0 +1,437 @@
import { app } from "../../scripts/app.js";
import { RgthreeBaseVirtualNode } from "./base_node.js";
import { NodeTypesString } from "./constants.js";
import { SERVICE as FAST_GROUPS_SERVICE } from "./services/fast_groups_service.js";
import { drawNodeWidget, fitString } from "./utils_canvas.js";
import { RgthreeBaseWidget } from "./utils_widgets.js";
import { changeModeOfNodes, getGroupNodes } from "./utils.js";
const PROPERTY_SORT = "sort";
const PROPERTY_SORT_CUSTOM_ALPHA = "customSortAlphabet";
const PROPERTY_MATCH_COLORS = "matchColors";
const PROPERTY_MATCH_TITLE = "matchTitle";
const PROPERTY_SHOW_NAV = "showNav";
const PROPERTY_SHOW_ALL_GRAPHS = "showAllGraphs";
const PROPERTY_RESTRICTION = "toggleRestriction";
export class BaseFastGroupsModeChanger extends RgthreeBaseVirtualNode {
constructor(title = FastGroupsMuter.title) {
super(title);
this.modeOn = LiteGraph.ALWAYS;
this.modeOff = LiteGraph.NEVER;
this.debouncerTempWidth = 0;
this.tempSize = null;
this.serialize_widgets = false;
this.helpActions = "mute and unmute";
this.properties[PROPERTY_MATCH_COLORS] = "";
this.properties[PROPERTY_MATCH_TITLE] = "";
this.properties[PROPERTY_SHOW_NAV] = true;
this.properties[PROPERTY_SHOW_ALL_GRAPHS] = true;
this.properties[PROPERTY_SORT] = "position";
this.properties[PROPERTY_SORT_CUSTOM_ALPHA] = "";
this.properties[PROPERTY_RESTRICTION] = "default";
}
onConstructed() {
this.addOutput("OPT_CONNECTION", "*");
return super.onConstructed();
}
onAdded(graph) {
FAST_GROUPS_SERVICE.addFastGroupNode(this);
}
onRemoved() {
FAST_GROUPS_SERVICE.removeFastGroupNode(this);
}
refreshWidgets() {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
const canvas = app.canvas;
let sort = ((_a = this.properties) === null || _a === void 0 ? void 0 : _a[PROPERTY_SORT]) || "position";
let customAlphabet = null;
if (sort === "custom alphabet") {
const customAlphaStr = (_c = (_b = this.properties) === null || _b === void 0 ? void 0 : _b[PROPERTY_SORT_CUSTOM_ALPHA]) === null || _c === void 0 ? void 0 : _c.replace(/\n/g, "");
if (customAlphaStr && customAlphaStr.trim()) {
customAlphabet = customAlphaStr.includes(",")
? customAlphaStr.toLocaleLowerCase().split(",")
: customAlphaStr.toLocaleLowerCase().trim().split("");
}
if (!(customAlphabet === null || customAlphabet === void 0 ? void 0 : customAlphabet.length)) {
sort = "alphanumeric";
customAlphabet = null;
}
}
const groups = [...FAST_GROUPS_SERVICE.getGroups(sort)];
if (customAlphabet === null || customAlphabet === void 0 ? void 0 : customAlphabet.length) {
groups.sort((a, b) => {
let aIndex = -1;
let bIndex = -1;
for (const [index, alpha] of customAlphabet.entries()) {
aIndex =
aIndex < 0 ? (a.title.toLocaleLowerCase().startsWith(alpha) ? index : -1) : aIndex;
bIndex =
bIndex < 0 ? (b.title.toLocaleLowerCase().startsWith(alpha) ? index : -1) : bIndex;
if (aIndex > -1 && bIndex > -1) {
break;
}
}
if (aIndex > -1 && bIndex > -1) {
const ret = aIndex - bIndex;
if (ret === 0) {
return a.title.localeCompare(b.title);
}
return ret;
}
else if (aIndex > -1) {
return -1;
}
else if (bIndex > -1) {
return 1;
}
return a.title.localeCompare(b.title);
});
}
let filterColors = (((_e = (_d = this.properties) === null || _d === void 0 ? void 0 : _d[PROPERTY_MATCH_COLORS]) === null || _e === void 0 ? void 0 : _e.split(",")) || []).filter((c) => c.trim());
if (filterColors.length) {
filterColors = filterColors.map((color) => {
color = color.trim().toLocaleLowerCase();
if (LGraphCanvas.node_colors[color]) {
color = LGraphCanvas.node_colors[color].groupcolor;
}
color = color.replace("#", "").toLocaleLowerCase();
if (color.length === 3) {
color = color.replace(/(.)(.)(.)/, "$1$1$2$2$3$3");
}
return `#${color}`;
});
}
let index = 0;
for (const group of groups) {
if (filterColors.length) {
let groupColor = (_f = group.color) === null || _f === void 0 ? void 0 : _f.replace("#", "").trim().toLocaleLowerCase();
if (!groupColor) {
continue;
}
if (groupColor.length === 3) {
groupColor = groupColor.replace(/(.)(.)(.)/, "$1$1$2$2$3$3");
}
groupColor = `#${groupColor}`;
if (!filterColors.includes(groupColor)) {
continue;
}
}
if ((_h = (_g = this.properties) === null || _g === void 0 ? void 0 : _g[PROPERTY_MATCH_TITLE]) === null || _h === void 0 ? void 0 : _h.trim()) {
try {
if (!new RegExp(this.properties[PROPERTY_MATCH_TITLE], "i").exec(group.title)) {
continue;
}
}
catch (e) {
console.error(e);
continue;
}
}
const showAllGraphs = (_j = this.properties) === null || _j === void 0 ? void 0 : _j[PROPERTY_SHOW_ALL_GRAPHS];
if (!showAllGraphs && group.graph !== app.canvas.getCurrentGraph()) {
continue;
}
let isDirty = false;
const widgetLabel = `Enable ${group.title}`;
let widget = this.widgets.find((w) => w.label === widgetLabel);
if (!widget) {
this.tempSize = [...this.size];
widget = this.addCustomWidget(new FastGroupsToggleRowWidget(group, this));
this.setSize(this.computeSize());
isDirty = true;
}
if (widget.label != widgetLabel) {
widget.label = widgetLabel;
isDirty = true;
}
if (group.rgthree_hasAnyActiveNode != null &&
widget.toggled != group.rgthree_hasAnyActiveNode) {
widget.toggled = group.rgthree_hasAnyActiveNode;
isDirty = true;
}
if (this.widgets[index] !== widget) {
const oldIndex = this.widgets.findIndex((w) => w === widget);
this.widgets.splice(index, 0, this.widgets.splice(oldIndex, 1)[0]);
isDirty = true;
}
if (isDirty) {
this.setDirtyCanvas(true, false);
}
index++;
}
while ((this.widgets || [])[index]) {
this.removeWidget(index++);
}
}
computeSize(out) {
let size = super.computeSize(out);
if (this.tempSize) {
size[0] = Math.max(this.tempSize[0], size[0]);
size[1] = Math.max(this.tempSize[1], size[1]);
this.debouncerTempWidth && clearTimeout(this.debouncerTempWidth);
this.debouncerTempWidth = setTimeout(() => {
this.tempSize = null;
}, 32);
}
setTimeout(() => {
var _a;
(_a = this.graph) === null || _a === void 0 ? void 0 : _a.setDirtyCanvas(true, true);
}, 16);
return size;
}
async handleAction(action) {
var _a, _b, _c, _d, _e;
if (action === "Mute all" || action === "Bypass all") {
const alwaysOne = ((_a = this.properties) === null || _a === void 0 ? void 0 : _a[PROPERTY_RESTRICTION]) === "always one";
for (const [index, widget] of this.widgets.entries()) {
widget === null || widget === void 0 ? void 0 : widget.doModeChange(alwaysOne && !index ? true : false, true);
}
}
else if (action === "Enable all") {
const onlyOne = (_b = this.properties) === null || _b === void 0 ? void 0 : _b[PROPERTY_RESTRICTION].includes(" one");
for (const [index, widget] of this.widgets.entries()) {
widget === null || widget === void 0 ? void 0 : widget.doModeChange(onlyOne && index > 0 ? false : true, true);
}
}
else if (action === "Toggle all") {
const onlyOne = (_c = this.properties) === null || _c === void 0 ? void 0 : _c[PROPERTY_RESTRICTION].includes(" one");
let foundOne = false;
for (const [index, widget] of this.widgets.entries()) {
let newValue = onlyOne && foundOne ? false : !widget.value;
foundOne = foundOne || newValue;
widget === null || widget === void 0 ? void 0 : widget.doModeChange(newValue, true);
}
if (!foundOne && ((_d = this.properties) === null || _d === void 0 ? void 0 : _d[PROPERTY_RESTRICTION]) === "always one") {
(_e = this.widgets[this.widgets.length - 1]) === null || _e === void 0 ? void 0 : _e.doModeChange(true, true);
}
}
}
getHelp() {
return `
<p>The ${this.type.replace("(rgthree)", "")} is an input-less node that automatically collects all groups in your current
workflow and allows you to quickly ${this.helpActions} all nodes within the group.</p>
<ul>
<li>
<p>
<strong>Properties.</strong> You can change the following properties (by right-clicking
on the node, and select "Properties" or "Properties Panel" from the menu):
</p>
<ul>
<li><p>
<code>${PROPERTY_MATCH_COLORS}</code> - Only add groups that match the provided
colors. Can be ComfyUI colors (red, pale_blue) or hex codes (#a4d399). Multiple can be
added, comma delimited.
</p></li>
<li><p>
<code>${PROPERTY_MATCH_TITLE}</code> - Filter the list of toggles by title match
(string match, or regular expression).
</p></li>
<li><p>
<code>${PROPERTY_SHOW_NAV}</code> - Add / remove a quick navigation arrow to take you
to the group. <i>(default: true)</i>
</p></li>
<li><p>
<code>${PROPERTY_SHOW_ALL_GRAPHS}</code> - Show groups from all [sub]graphs in the
workflow. <i>(default: true)</i>
</p></li>
<li><p>
<code>${PROPERTY_SORT}</code> - Sort the toggles' order by "alphanumeric", graph
"position", or "custom alphabet". <i>(default: "position")</i>
</p></li>
<li>
<p>
<code>${PROPERTY_SORT_CUSTOM_ALPHA}</code> - When the
<code>${PROPERTY_SORT}</code> property is "custom alphabet" you can define the
alphabet to use here, which will match the <i>beginning</i> of each group name and
sort against it. If group titles do not match any custom alphabet entry, then they
will be put after groups that do, ordered alphanumerically.
</p>
<p>
This can be a list of single characters, like "zyxw..." or comma delimited strings
for more control, like "sdxl,pro,sd,n,p".
</p>
<p>
Note, when two group title match the same custom alphabet entry, the <i>normal
alphanumeric alphabet</i> breaks the tie. For instance, a custom alphabet of
"e,s,d" will order groups names like "SDXL, SEGS, Detailer" eventhough the custom
alphabet has an "e" before "d" (where one may expect "SE" to be before "SD").
</p>
<p>
To have "SEGS" appear before "SDXL" you can use longer strings. For instance, the
custom alphabet value of "se,s,f" would work here.
</p>
</li>
<li><p>
<code>${PROPERTY_RESTRICTION}</code> - Optionally, attempt to restrict the number of
widgets that can be enabled to a maximum of one, or always one.
</p>
<p><em><strong>Note:</strong> If using "max one" or "always one" then this is only
enforced when clicking a toggle on this node; if nodes within groups are changed
outside of the initial toggle click, then these restriction will not be enforced, and
could result in a state where more than one toggle is enabled. This could also happen
if nodes are overlapped with multiple groups.
</p></li>
</ul>
</li>
</ul>`;
}
}
BaseFastGroupsModeChanger.type = NodeTypesString.FAST_GROUPS_MUTER;
BaseFastGroupsModeChanger.title = NodeTypesString.FAST_GROUPS_MUTER;
BaseFastGroupsModeChanger.exposedActions = ["Mute all", "Enable all", "Toggle all"];
BaseFastGroupsModeChanger["@matchColors"] = { type: "string" };
BaseFastGroupsModeChanger["@matchTitle"] = { type: "string" };
BaseFastGroupsModeChanger["@showNav"] = { type: "boolean" };
BaseFastGroupsModeChanger["@showAllGraphs"] = { type: "boolean" };
BaseFastGroupsModeChanger["@sort"] = {
type: "combo",
values: ["position", "alphanumeric", "custom alphabet"],
};
BaseFastGroupsModeChanger["@customSortAlphabet"] = { type: "string" };
BaseFastGroupsModeChanger["@toggleRestriction"] = {
type: "combo",
values: ["default", "max one", "always one"],
};
export class FastGroupsMuter extends BaseFastGroupsModeChanger {
constructor(title = FastGroupsMuter.title) {
super(title);
this.comfyClass = NodeTypesString.FAST_GROUPS_MUTER;
this.helpActions = "mute and unmute";
this.modeOn = LiteGraph.ALWAYS;
this.modeOff = LiteGraph.NEVER;
this.onConstructed();
}
}
FastGroupsMuter.type = NodeTypesString.FAST_GROUPS_MUTER;
FastGroupsMuter.title = NodeTypesString.FAST_GROUPS_MUTER;
FastGroupsMuter.exposedActions = ["Bypass all", "Enable all", "Toggle all"];
class FastGroupsToggleRowWidget extends RgthreeBaseWidget {
constructor(group, node) {
super("RGTHREE_TOGGLE_AND_NAV");
this.value = { toggled: false };
this.options = { on: "yes", off: "no" };
this.type = "custom";
this.label = "";
this.group = group;
this.node = node;
}
doModeChange(force, skipOtherNodeCheck) {
var _a, _b, _c, _d;
this.group.recomputeInsideNodes();
const hasAnyActiveNodes = getGroupNodes(this.group).some((n) => n.mode === LiteGraph.ALWAYS);
let newValue = force != null ? force : !hasAnyActiveNodes;
if (skipOtherNodeCheck !== true) {
if (newValue && ((_b = (_a = this.node.properties) === null || _a === void 0 ? void 0 : _a[PROPERTY_RESTRICTION]) === null || _b === void 0 ? void 0 : _b.includes(" one"))) {
for (const widget of this.node.widgets) {
if (widget instanceof FastGroupsToggleRowWidget) {
widget.doModeChange(false, true);
}
}
}
else if (!newValue && ((_c = this.node.properties) === null || _c === void 0 ? void 0 : _c[PROPERTY_RESTRICTION]) === "always one") {
newValue = this.node.widgets.every((w) => !w.value || w === this);
}
}
changeModeOfNodes(getGroupNodes(this.group), (newValue ? this.node.modeOn : this.node.modeOff));
this.group.rgthree_hasAnyActiveNode = newValue;
this.toggled = newValue;
(_d = this.group.graph) === null || _d === void 0 ? void 0 : _d.setDirtyCanvas(true, false);
}
get toggled() {
return this.value.toggled;
}
set toggled(value) {
this.value.toggled = value;
}
toggle(value) {
value = value == null ? !this.toggled : value;
if (value !== this.toggled) {
this.value.toggled = value;
this.doModeChange();
}
}
draw(ctx, node, width, posY, height) {
var _a;
const widgetData = drawNodeWidget(ctx, { size: [width, height], pos: [15, posY] });
const showNav = ((_a = node.properties) === null || _a === void 0 ? void 0 : _a[PROPERTY_SHOW_NAV]) !== false;
let currentX = widgetData.width - widgetData.margin;
if (!widgetData.lowQuality && showNav) {
currentX -= 7;
const midY = widgetData.posY + widgetData.height * 0.5;
ctx.fillStyle = ctx.strokeStyle = "#89A";
ctx.lineJoin = "round";
ctx.lineCap = "round";
const arrow = new Path2D(`M${currentX} ${midY} l -7 6 v -3 h -7 v -6 h 7 v -3 z`);
ctx.fill(arrow);
ctx.stroke(arrow);
currentX -= 14;
currentX -= 7;
ctx.strokeStyle = widgetData.colorOutline;
ctx.stroke(new Path2D(`M ${currentX} ${widgetData.posY} v ${widgetData.height}`));
}
else if (widgetData.lowQuality && showNav) {
currentX -= 28;
}
currentX -= 7;
ctx.fillStyle = this.toggled ? "#89A" : "#333";
ctx.beginPath();
const toggleRadius = height * 0.36;
ctx.arc(currentX - toggleRadius, posY + height * 0.5, toggleRadius, 0, Math.PI * 2);
ctx.fill();
currentX -= toggleRadius * 2;
if (!widgetData.lowQuality) {
currentX -= 4;
ctx.textAlign = "right";
ctx.fillStyle = this.toggled ? widgetData.colorText : widgetData.colorTextSecondary;
const label = this.label;
const toggleLabelOn = this.options.on || "true";
const toggleLabelOff = this.options.off || "false";
ctx.fillText(this.toggled ? toggleLabelOn : toggleLabelOff, currentX, posY + height * 0.7);
currentX -= Math.max(ctx.measureText(toggleLabelOn).width, ctx.measureText(toggleLabelOff).width);
currentX -= 7;
ctx.textAlign = "left";
let maxLabelWidth = widgetData.width - widgetData.margin - 10 - (widgetData.width - currentX);
if (label != null) {
ctx.fillText(fitString(ctx, label, maxLabelWidth), widgetData.margin + 10, posY + height * 0.7);
}
}
}
serializeValue(node, index) {
return this.value;
}
mouse(event, pos, node) {
var _a, _b, _c;
if (event.type == "pointerdown") {
if (((_a = node.properties) === null || _a === void 0 ? void 0 : _a[PROPERTY_SHOW_NAV]) !== false && pos[0] >= node.size[0] - 15 - 28 - 1) {
const canvas = app.canvas;
const lowQuality = (((_b = canvas.ds) === null || _b === void 0 ? void 0 : _b.scale) || 1) <= 0.5;
if (!lowQuality) {
canvas.centerOnNode(this.group);
const zoomCurrent = ((_c = canvas.ds) === null || _c === void 0 ? void 0 : _c.scale) || 1;
const zoomX = canvas.canvas.width / this.group._size[0] - 0.02;
const zoomY = canvas.canvas.height / this.group._size[1] - 0.02;
canvas.setZoom(Math.min(zoomCurrent, zoomX, zoomY), [
canvas.canvas.width / 2,
canvas.canvas.height / 2,
]);
canvas.setDirty(true, true);
}
}
else {
this.toggle();
}
}
return true;
}
}
app.registerExtension({
name: "rgthree.FastGroupsMuter",
registerCustomNodes() {
FastGroupsMuter.setUp();
},
loadedGraphNode(node) {
if (node.type == FastGroupsMuter.title) {
node.tempSize = [...node.size];
}
},
});

View File

@@ -0,0 +1,220 @@
import { app } from "../../scripts/app.js";
import { rgthree } from "./rgthree.js";
import { changeModeOfNodes, getGroupNodes, getOutputNodes } from "./utils.js";
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
const BTN_SIZE = 20;
const BTN_MARGIN = [6, 6];
const BTN_SPACING = 8;
const BTN_GRID = BTN_SIZE / 8;
const TOGGLE_TO_MODE = new Map([
["MUTE", LiteGraph.NEVER],
["BYPASS", 4],
]);
function getToggles() {
return [...CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.toggles", [])].reverse();
}
function clickedOnToggleButton(e, group) {
const toggles = getToggles();
const pos = group.pos;
const size = group.size;
for (let i = 0; i < toggles.length; i++) {
const toggle = toggles[i];
if (LiteGraph.isInsideRectangle(e.canvasX, e.canvasY, pos[0] + size[0] - (BTN_SIZE + BTN_MARGIN[0]) * (i + 1), pos[1] + BTN_MARGIN[1], BTN_SIZE, BTN_SIZE)) {
return toggle;
}
}
return null;
}
app.registerExtension({
name: "rgthree.GroupHeaderToggles",
async setup() {
setInterval(() => {
if (CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.enabled") &&
CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.show") !== "always") {
app.canvas.setDirty(true, true);
}
}, 250);
rgthree.addEventListener("on-process-mouse-down", ((e) => {
if (!CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.enabled"))
return;
const canvas = app.canvas;
if (canvas.selected_group) {
const originalEvent = e.detail.originalEvent;
const group = canvas.selected_group;
const clickedOnToggle = clickedOnToggleButton(originalEvent, group) || "";
const toggleAction = clickedOnToggle === null || clickedOnToggle === void 0 ? void 0 : clickedOnToggle.toLocaleUpperCase();
if (toggleAction) {
console.log(toggleAction);
const nodes = getGroupNodes(group);
if (toggleAction === "QUEUE") {
const outputNodes = getOutputNodes(nodes);
if (!(outputNodes === null || outputNodes === void 0 ? void 0 : outputNodes.length)) {
rgthree.showMessage({
id: "no-output-in-group",
type: "warn",
timeout: 4000,
message: "No output nodes for group!",
});
}
else {
rgthree.queueOutputNodes(outputNodes);
}
}
else {
const toggleMode = TOGGLE_TO_MODE.get(toggleAction);
if (toggleMode) {
group.recomputeInsideNodes();
const hasAnyActiveNodes = nodes.some((n) => n.mode === LiteGraph.ALWAYS);
const isAllMuted = !hasAnyActiveNodes && nodes.every((n) => n.mode === LiteGraph.NEVER);
const isAllBypassed = !hasAnyActiveNodes && !isAllMuted && nodes.every((n) => n.mode === 4);
let newMode = LiteGraph.ALWAYS;
if (toggleMode === LiteGraph.NEVER) {
newMode = isAllMuted ? LiteGraph.ALWAYS : LiteGraph.NEVER;
}
else {
newMode = isAllBypassed ? LiteGraph.ALWAYS : 4;
}
changeModeOfNodes(nodes, newMode);
}
}
canvas.selected_group = null;
canvas.dragging_canvas = false;
}
}
}));
const drawGroups = LGraphCanvas.prototype.drawGroups;
LGraphCanvas.prototype.drawGroups = function (canvasEl, ctx) {
drawGroups.apply(this, [...arguments]);
if (!CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.enabled") ||
!rgthree.lastCanvasMouseEvent) {
return;
}
const graph = app.canvas.graph;
let groups;
if (CONFIG_SERVICE.getFeatureValue("group_header_fast_toggle.show") !== "always") {
const hoverGroup = graph.getGroupOnPos(rgthree.lastCanvasMouseEvent.canvasX, rgthree.lastCanvasMouseEvent.canvasY);
groups = hoverGroup ? [hoverGroup] : [];
}
else {
groups = graph._groups || [];
}
if (!groups.length) {
return;
}
const toggles = getToggles();
ctx.save();
for (const group of groups || []) {
const nodes = getGroupNodes(group);
let anyActive = false;
let allMuted = !!nodes.length;
let allBypassed = allMuted;
for (const node of nodes) {
if (!(node instanceof LGraphNode))
continue;
anyActive = anyActive || node.mode === LiteGraph.ALWAYS;
allMuted = allMuted && node.mode === LiteGraph.NEVER;
allBypassed = allBypassed && node.mode === 4;
if (anyActive || (!allMuted && !allBypassed)) {
break;
}
}
for (let i = 0; i < toggles.length; i++) {
const toggle = toggles[i];
const pos = group._pos;
const size = group._size;
ctx.fillStyle = ctx.strokeStyle = group.color || "#335";
const x = pos[0] + size[0] - BTN_MARGIN[0] - BTN_SIZE - (BTN_SPACING + BTN_SIZE) * i;
const y = pos[1] + BTN_MARGIN[1];
const midX = x + BTN_SIZE / 2;
const midY = y + BTN_SIZE / 2;
if (toggle === "queue") {
const outputNodes = getOutputNodes(nodes);
const oldGlobalAlpha = ctx.globalAlpha;
if (!(outputNodes === null || outputNodes === void 0 ? void 0 : outputNodes.length)) {
ctx.globalAlpha = 0.5;
}
ctx.lineJoin = "round";
ctx.lineCap = "round";
const arrowSizeX = BTN_SIZE * 0.6;
const arrowSizeY = BTN_SIZE * 0.7;
const arrow = new Path2D(`M ${x + arrowSizeX / 2} ${midY} l 0 -${arrowSizeY / 2} l ${arrowSizeX} ${arrowSizeY / 2} l -${arrowSizeX} ${arrowSizeY / 2} z`);
ctx.stroke(arrow);
if (outputNodes === null || outputNodes === void 0 ? void 0 : outputNodes.length) {
ctx.fill(arrow);
}
ctx.globalAlpha = oldGlobalAlpha;
}
else {
const on = toggle === "bypass" ? allBypassed : allMuted;
ctx.beginPath();
ctx.lineJoin = "round";
ctx.rect(x, y, BTN_SIZE, BTN_SIZE);
ctx.lineWidth = 2;
if (toggle === "mute") {
ctx.lineJoin = "round";
ctx.lineCap = "round";
if (on) {
ctx.stroke(new Path2D(`
${eyeFrame(midX, midY)}
${eyeLashes(midX, midY)}
`));
}
else {
const radius = BTN_GRID * 1.5;
ctx.fill(new Path2D(`
${eyeFrame(midX, midY)}
${eyeFrame(midX, midY, -1)}
${circlePath(midX, midY, radius)}
${circlePath(midX + BTN_GRID / 2, midY - BTN_GRID / 2, BTN_GRID * 0.375)}
`), "evenodd");
ctx.stroke(new Path2D(`${eyeFrame(midX, midY)} ${eyeFrame(midX, midY, -1)}`));
ctx.globalAlpha = this.editor_alpha * 0.5;
ctx.stroke(new Path2D(`${eyeLashes(midX, midY)} ${eyeLashes(midX, midY, -1)}`));
ctx.globalAlpha = this.editor_alpha;
}
}
else {
const lineChanges = on
? `a ${BTN_GRID * 3}, ${BTN_GRID * 3} 0 1, 1 ${BTN_GRID * 3 * 2},0
l ${BTN_GRID * 2.0} 0`
: `l ${BTN_GRID * 8} 0`;
ctx.stroke(new Path2D(`
M ${x} ${midY}
${lineChanges}
M ${x + BTN_SIZE} ${midY} l -2 2
M ${x + BTN_SIZE} ${midY} l -2 -2
`));
ctx.fill(new Path2D(`${circlePath(x + BTN_GRID * 3, midY, BTN_GRID * 1.8)}`));
}
}
}
}
ctx.restore();
};
},
});
function eyeFrame(midX, midY, yFlip = 1) {
return `
M ${midX - BTN_SIZE / 2} ${midY}
c ${BTN_GRID * 1.5} ${yFlip * BTN_GRID * 2.5}, ${BTN_GRID * (8 - 1.5)} ${yFlip * BTN_GRID * 2.5}, ${BTN_GRID * 8} 0
`;
}
function eyeLashes(midX, midY, yFlip = 1) {
return `
M ${midX - BTN_GRID * 3.46} ${midY + yFlip * BTN_GRID * 0.9} l -1.15 ${1.25 * yFlip}
M ${midX - BTN_GRID * 2.38} ${midY + yFlip * BTN_GRID * 1.6} l -0.90 ${1.5 * yFlip}
M ${midX - BTN_GRID * 1.15} ${midY + yFlip * BTN_GRID * 1.95} l -0.50 ${1.75 * yFlip}
M ${midX + BTN_GRID * 0.0} ${midY + yFlip * BTN_GRID * 2.0} l 0.00 ${2.0 * yFlip}
M ${midX + BTN_GRID * 1.15} ${midY + yFlip * BTN_GRID * 1.95} l 0.50 ${1.75 * yFlip}
M ${midX + BTN_GRID * 2.38} ${midY + yFlip * BTN_GRID * 1.6} l 0.90 ${1.5 * yFlip}
M ${midX + BTN_GRID * 3.46} ${midY + yFlip * BTN_GRID * 0.9} l 1.15 ${1.25 * yFlip}
`;
}
function circlePath(cx, cy, radius) {
return `
M ${cx} ${cy}
m ${radius}, 0
a ${radius},${radius} 0 1, 1 -${radius * 2},0
a ${radius},${radius} 0 1, 1 ${radius * 2},0
`;
}

View File

@@ -0,0 +1,66 @@
import { app } from "../../scripts/app.js";
import { tryToGetWorkflowDataFromEvent } from "../../rgthree/common/utils_workflow.js";
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
import { NodeTypesString } from "./constants.js";
app.registerExtension({
name: "rgthree.ImportIndividualNodes",
async beforeRegisterNodeDef(nodeType, nodeData) {
const onDragOver = nodeType.prototype.onDragOver;
nodeType.prototype.onDragOver = function (e) {
var _a;
let handled = (_a = onDragOver === null || onDragOver === void 0 ? void 0 : onDragOver.apply) === null || _a === void 0 ? void 0 : _a.call(onDragOver, this, [...arguments]);
if (handled != null) {
return handled;
}
return importIndividualNodesInnerOnDragOver(this, e);
};
const onDragDrop = nodeType.prototype.onDragDrop;
nodeType.prototype.onDragDrop = async function (e) {
var _a;
const alreadyHandled = await ((_a = onDragDrop === null || onDragDrop === void 0 ? void 0 : onDragDrop.apply) === null || _a === void 0 ? void 0 : _a.call(onDragDrop, this, [...arguments]));
if (alreadyHandled) {
return alreadyHandled;
}
return importIndividualNodesInnerOnDragDrop(this, e);
};
},
});
export function importIndividualNodesInnerOnDragOver(node, e) {
var _a;
return ((((_a = node.widgets) === null || _a === void 0 ? void 0 : _a.length) && !!CONFIG_SERVICE.getFeatureValue("import_individual_nodes.enabled")) ||
false);
}
export async function importIndividualNodesInnerOnDragDrop(node, e) {
var _a, _b;
if (!((_a = node.widgets) === null || _a === void 0 ? void 0 : _a.length) || !CONFIG_SERVICE.getFeatureValue("import_individual_nodes.enabled")) {
return false;
}
const dynamicWidgetLengthNodes = [NodeTypesString.POWER_LORA_LOADER];
let handled = false;
const { workflow, prompt } = await tryToGetWorkflowDataFromEvent(e);
const exact = ((workflow === null || workflow === void 0 ? void 0 : workflow.nodes) || []).find((n) => {
var _a, _b;
return n.id === node.id &&
n.type === node.type &&
(dynamicWidgetLengthNodes.includes(node.type) ||
((_a = n.widgets_values) === null || _a === void 0 ? void 0 : _a.length) === ((_b = node.widgets_values) === null || _b === void 0 ? void 0 : _b.length));
});
if (!exact) {
handled = !confirm("[rgthree-comfy] Could not find a matching node (same id & type) in the dropped workflow." +
" Would you like to continue with the default drop behaviour instead?");
}
else if (!((_b = exact.widgets_values) === null || _b === void 0 ? void 0 : _b.length)) {
handled = !confirm("[rgthree-comfy] Matching node found (same id & type) but there's no widgets to set." +
" Would you like to continue with the default drop behaviour instead?");
}
else if (confirm("[rgthree-comfy] Found a matching node (same id & type) in the dropped workflow." +
" Would you like to set the widget values?")) {
node.configure({
title: node.title,
widgets_values: [...((exact === null || exact === void 0 ? void 0 : exact.widgets_values) || [])],
mode: exact.mode,
});
handled = true;
}
return handled;
}

View File

@@ -0,0 +1,365 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { RgthreeBaseServerNode } from "./base_node.js";
import { NodeTypesString } from "./constants.js";
import { addConnectionLayoutSupport } from "./utils.js";
import { RgthreeBaseWidget } from "./utils_widgets.js";
import { measureText } from "./utils_canvas.js";
function imageDataToUrl(data) {
return api.apiURL(`/view?filename=${encodeURIComponent(data.filename)}&type=${data.type}&subfolder=${data.subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`);
}
export class RgthreeImageComparer extends RgthreeBaseServerNode {
constructor(title = RgthreeImageComparer.title) {
super(title);
this.imageIndex = 0;
this.imgs = [];
this.serialize_widgets = true;
this.isPointerDown = false;
this.isPointerOver = false;
this.pointerOverPos = [0, 0];
this.canvasWidget = null;
this.properties["comparer_mode"] = "Slide";
}
onExecuted(output) {
var _a;
(_a = super.onExecuted) === null || _a === void 0 ? void 0 : _a.call(this, output);
if ("images" in output) {
this.canvasWidget.value = {
images: (output.images || []).map((d, i) => {
return {
name: i === 0 ? "A" : "B",
selected: true,
url: imageDataToUrl(d),
};
}),
};
}
else {
output.a_images = output.a_images || [];
output.b_images = output.b_images || [];
const imagesToChoose = [];
const multiple = output.a_images.length + output.b_images.length > 2;
for (const [i, d] of output.a_images.entries()) {
imagesToChoose.push({
name: output.a_images.length > 1 || multiple ? `A${i + 1}` : "A",
selected: i === 0,
url: imageDataToUrl(d),
});
}
for (const [i, d] of output.b_images.entries()) {
imagesToChoose.push({
name: output.b_images.length > 1 || multiple ? `B${i + 1}` : "B",
selected: i === 0,
url: imageDataToUrl(d),
});
}
this.canvasWidget.value = { images: imagesToChoose };
}
}
onSerialize(serialised) {
var _a;
super.onSerialize && super.onSerialize(serialised);
for (let [index, widget_value] of (serialised.widgets_values || []).entries()) {
if (((_a = this.widgets[index]) === null || _a === void 0 ? void 0 : _a.name) === "rgthree_comparer") {
serialised.widgets_values[index] = this.widgets[index].value.images.map((d) => {
d = { ...d };
delete d.img;
return d;
});
}
}
}
onNodeCreated() {
this.canvasWidget = this.addCustomWidget(new RgthreeImageComparerWidget("rgthree_comparer", this));
this.setSize(this.computeSize());
this.setDirtyCanvas(true, true);
}
setIsPointerDown(down = this.isPointerDown) {
const newIsDown = down && !!app.canvas.pointer_is_down;
if (this.isPointerDown !== newIsDown) {
this.isPointerDown = newIsDown;
this.setDirtyCanvas(true, false);
}
this.imageIndex = this.isPointerDown ? 1 : 0;
if (this.isPointerDown) {
requestAnimationFrame(() => {
this.setIsPointerDown();
});
}
}
onMouseDown(event, pos, canvas) {
var _a;
(_a = super.onMouseDown) === null || _a === void 0 ? void 0 : _a.call(this, event, pos, canvas);
this.setIsPointerDown(true);
return false;
}
onMouseEnter(event) {
var _a;
(_a = super.onMouseEnter) === null || _a === void 0 ? void 0 : _a.call(this, event);
this.setIsPointerDown(!!app.canvas.pointer_is_down);
this.isPointerOver = true;
}
onMouseLeave(event) {
var _a;
(_a = super.onMouseLeave) === null || _a === void 0 ? void 0 : _a.call(this, event);
this.setIsPointerDown(false);
this.isPointerOver = false;
}
onMouseMove(event, pos, canvas) {
var _a;
(_a = super.onMouseMove) === null || _a === void 0 ? void 0 : _a.call(this, event, pos, canvas);
this.pointerOverPos = [...pos];
this.imageIndex = this.pointerOverPos[0] > this.size[0] / 2 ? 1 : 0;
}
getHelp() {
return `
<p>
The ${this.type.replace("(rgthree)", "")} node compares two images on top of each other.
</p>
<ul>
<li>
<p>
<strong>Notes</strong>
</p>
<ul>
<li><p>
The right-click menu may show image options (Open Image, Save Image, etc.) which will
correspond to the first image (image_a) if clicked on the left-half of the node, or
the second image if on the right half of the node.
</p></li>
</ul>
</li>
<li>
<p>
<strong>Inputs</strong>
</p>
<ul>
<li><p>
<code>image_a</code> <i>Optional.</i> The first image to use to compare.
image_a.
</p></li>
<li><p>
<code>image_b</code> <i>Optional.</i> The second image to use to compare.
</p></li>
<li><p>
<b>Note</b> <code>image_a</code> and <code>image_b</code> work best when a single
image is provided. However, if each/either are a batch, you can choose which item
from each batch are chosen to be compared. If either <code>image_a</code> or
<code>image_b</code> are not provided, the node will choose the first two from the
provided input if it's a batch, otherwise only show the single image (just as
Preview Image would).
</p></li>
</ul>
</li>
<li>
<p>
<strong>Properties.</strong> You can change the following properties (by right-clicking
on the node, and select "Properties" or "Properties Panel" from the menu):
</p>
<ul>
<li><p>
<code>comparer_mode</code> - Choose between "Slide" and "Click". Defaults to "Slide".
</p></li>
</ul>
</li>
</ul>`;
}
static setUp(comfyClass, nodeData) {
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, RgthreeImageComparer);
}
static onRegisteredForOverride(comfyClass) {
addConnectionLayoutSupport(RgthreeImageComparer, app, [
["Left", "Right"],
["Right", "Left"],
]);
setTimeout(() => {
RgthreeImageComparer.category = comfyClass.category;
});
}
}
RgthreeImageComparer.title = NodeTypesString.IMAGE_COMPARER;
RgthreeImageComparer.type = NodeTypesString.IMAGE_COMPARER;
RgthreeImageComparer.comfyClass = NodeTypesString.IMAGE_COMPARER;
RgthreeImageComparer["@comparer_mode"] = {
type: "combo",
values: ["Slide", "Click"],
};
class RgthreeImageComparerWidget extends RgthreeBaseWidget {
constructor(name, node) {
super(name);
this.type = "custom";
this.hitAreas = {};
this.selected = [];
this._value = { images: [] };
this.node = node;
}
set value(v) {
let cleanedVal;
if (Array.isArray(v)) {
cleanedVal = v.map((d, i) => {
if (!d || typeof d === "string") {
d = { url: d, name: i == 0 ? "A" : "B", selected: true };
}
return d;
});
}
else {
cleanedVal = v.images || [];
}
if (cleanedVal.length > 2) {
const hasAAndB = cleanedVal.some((i) => i.name.startsWith("A")) &&
cleanedVal.some((i) => i.name.startsWith("B"));
if (!hasAAndB) {
cleanedVal = [cleanedVal[0], cleanedVal[1]];
}
}
let selected = cleanedVal.filter((d) => d.selected);
if (!selected.length && cleanedVal.length) {
cleanedVal[0].selected = true;
}
selected = cleanedVal.filter((d) => d.selected);
if (selected.length === 1 && cleanedVal.length > 1) {
cleanedVal.find((d) => !d.selected).selected = true;
}
this._value.images = cleanedVal;
selected = cleanedVal.filter((d) => d.selected);
this.setSelected(selected);
}
get value() {
return this._value;
}
setSelected(selected) {
this._value.images.forEach((d) => (d.selected = false));
this.node.imgs.length = 0;
for (const sel of selected) {
if (!sel.img) {
sel.img = new Image();
sel.img.src = sel.url;
this.node.imgs.push(sel.img);
}
sel.selected = true;
}
this.selected = selected;
}
draw(ctx, node, width, y) {
var _a;
this.hitAreas = {};
if (this.value.images.length > 2) {
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.font = `14px Arial`;
const drawData = [];
const spacing = 5;
let x = 0;
for (const img of this.value.images) {
const width = measureText(ctx, img.name);
drawData.push({
img,
text: img.name,
x,
width: measureText(ctx, img.name),
});
x += width + spacing;
}
x = (node.size[0] - (x - spacing)) / 2;
for (const d of drawData) {
ctx.fillStyle = d.img.selected ? "rgba(180, 180, 180, 1)" : "rgba(180, 180, 180, 0.5)";
ctx.fillText(d.text, x, y);
this.hitAreas[d.text] = {
bounds: [x, y, d.width, 14],
data: d.img,
onDown: this.onSelectionDown,
};
x += d.width + spacing;
}
y += 20;
}
if (((_a = node.properties) === null || _a === void 0 ? void 0 : _a["comparer_mode"]) === "Click") {
this.drawImage(ctx, this.selected[this.node.isPointerDown ? 1 : 0], y);
}
else {
this.drawImage(ctx, this.selected[0], y);
if (node.isPointerOver) {
this.drawImage(ctx, this.selected[1], y, this.node.pointerOverPos[0]);
}
}
}
onSelectionDown(event, pos, node, bounds) {
const selected = [...this.selected];
if (bounds === null || bounds === void 0 ? void 0 : bounds.data.name.startsWith("A")) {
selected[0] = bounds.data;
}
else if (bounds === null || bounds === void 0 ? void 0 : bounds.data.name.startsWith("B")) {
selected[1] = bounds.data;
}
this.setSelected(selected);
}
drawImage(ctx, image, y, cropX) {
var _a, _b;
if (!((_a = image === null || image === void 0 ? void 0 : image.img) === null || _a === void 0 ? void 0 : _a.naturalWidth) || !((_b = image === null || image === void 0 ? void 0 : image.img) === null || _b === void 0 ? void 0 : _b.naturalHeight)) {
return;
}
let [nodeWidth, nodeHeight] = this.node.size;
const imageAspect = (image === null || image === void 0 ? void 0 : image.img.naturalWidth) / (image === null || image === void 0 ? void 0 : image.img.naturalHeight);
let height = nodeHeight - y;
const widgetAspect = nodeWidth / height;
let targetWidth, targetHeight;
let offsetX = 0;
if (imageAspect > widgetAspect) {
targetWidth = nodeWidth;
targetHeight = nodeWidth / imageAspect;
}
else {
targetHeight = height;
targetWidth = height * imageAspect;
offsetX = (nodeWidth - targetWidth) / 2;
}
const widthMultiplier = (image === null || image === void 0 ? void 0 : image.img.naturalWidth) / targetWidth;
const sourceX = 0;
const sourceY = 0;
const sourceWidth = cropX != null ? (cropX - offsetX) * widthMultiplier : image === null || image === void 0 ? void 0 : image.img.naturalWidth;
const sourceHeight = image === null || image === void 0 ? void 0 : image.img.naturalHeight;
const destX = (nodeWidth - targetWidth) / 2;
const destY = y + (height - targetHeight) / 2;
const destWidth = cropX != null ? cropX - offsetX : targetWidth;
const destHeight = targetHeight;
ctx.save();
ctx.beginPath();
let globalCompositeOperation = ctx.globalCompositeOperation;
if (cropX) {
ctx.rect(destX, destY, destWidth, destHeight);
ctx.clip();
}
ctx.drawImage(image === null || image === void 0 ? void 0 : image.img, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight);
if (cropX != null && cropX >= (nodeWidth - targetWidth) / 2 && cropX <= targetWidth + offsetX) {
ctx.beginPath();
ctx.moveTo(cropX, destY);
ctx.lineTo(cropX, destY + destHeight);
ctx.globalCompositeOperation = "difference";
ctx.strokeStyle = "rgba(255,255,255, 1)";
ctx.stroke();
}
ctx.globalCompositeOperation = globalCompositeOperation;
ctx.restore();
}
computeSize(width) {
return [width, 20];
}
serializeValue(node, index) {
const v = [];
for (const data of this._value.images) {
const d = { ...data };
delete d.img;
v.push(d);
}
return { images: v };
}
}
app.registerExtension({
name: "rgthree.ImageComparer",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name === RgthreeImageComparer.type) {
RgthreeImageComparer.setUp(nodeType, nodeData);
}
},
});

View File

@@ -0,0 +1,59 @@
import { app } from "../../scripts/app.js";
import { RgthreeBaseServerNode } from "./base_node.js";
import { NodeTypesString } from "./constants.js";
class ImageInsetCrop extends RgthreeBaseServerNode {
constructor(title = ImageInsetCrop.title) {
super(title);
}
onAdded(graph) {
const measurementWidget = this.widgets[0];
let callback = measurementWidget.callback;
measurementWidget.callback = (...args) => {
this.setWidgetStep();
callback && callback.apply(measurementWidget, [...args]);
};
this.setWidgetStep();
}
configure(info) {
super.configure(info);
this.setWidgetStep();
}
setWidgetStep() {
const measurementWidget = this.widgets[0];
for (let i = 1; i <= 4; i++) {
if (measurementWidget.value === "Pixels") {
this.widgets[i].options.step = 80;
this.widgets[i].options.max = ImageInsetCrop.maxResolution;
}
else {
this.widgets[i].options.step = 10;
this.widgets[i].options.max = 99;
}
}
}
async handleAction(action) {
if (action === "Reset Crop") {
for (const widget of this.widgets) {
if (["left", "right", "top", "bottom"].includes(widget.name)) {
widget.value = 0;
}
}
}
}
static setUp(comfyClass, nodeData) {
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, ImageInsetCrop);
}
}
ImageInsetCrop.title = NodeTypesString.IMAGE_INSET_CROP;
ImageInsetCrop.type = NodeTypesString.IMAGE_INSET_CROP;
ImageInsetCrop.comfyClass = NodeTypesString.IMAGE_INSET_CROP;
ImageInsetCrop.exposedActions = ["Reset Crop"];
ImageInsetCrop.maxResolution = 8192;
app.registerExtension({
name: "rgthree.ImageInsetCrop",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name === NodeTypesString.IMAGE_INSET_CROP) {
ImageInsetCrop.setUp(nodeType, nodeData);
}
},
});

View File

@@ -0,0 +1,35 @@
import { app } from "../../scripts/app.js";
import { RgthreeBaseServerNode } from "./base_node.js";
import { NodeTypesString } from "./constants.js";
class RgthreeImageOrLatentSize extends RgthreeBaseServerNode {
static setUp(comfyClass, nodeData) {
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, NODE_CLASS);
}
constructor(title = NODE_CLASS.title) {
super(title);
}
onNodeCreated() {
var _a;
(_a = super.onNodeCreated) === null || _a === void 0 ? void 0 : _a.call(this);
this.addInput("input", ["IMAGE", "LATENT", "MASK"]);
}
configure(info) {
var _a;
super.configure(info);
if ((_a = this.inputs) === null || _a === void 0 ? void 0 : _a.length) {
this.inputs[0].type = ["IMAGE", "LATENT", "MASK"];
}
}
}
RgthreeImageOrLatentSize.title = NodeTypesString.IMAGE_OR_LATENT_SIZE;
RgthreeImageOrLatentSize.type = NodeTypesString.IMAGE_OR_LATENT_SIZE;
RgthreeImageOrLatentSize.comfyClass = NodeTypesString.IMAGE_OR_LATENT_SIZE;
const NODE_CLASS = RgthreeImageOrLatentSize;
app.registerExtension({
name: "rgthree.ImageOrLatentSize",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name === NODE_CLASS.type) {
NODE_CLASS.setUp(nodeType, nodeData);
}
},
});

View File

@@ -0,0 +1,163 @@
import { app } from "../../scripts/app.js";
import { RgthreeBaseVirtualNode } from "./base_node.js";
import { NodeTypesString } from "./constants.js";
import { rgthree } from "./rgthree.js";
export class Label extends RgthreeBaseVirtualNode {
constructor(title = Label.title) {
super(title);
this.comfyClass = NodeTypesString.LABEL;
this.resizable = false;
this.properties["fontSize"] = 12;
this.properties["fontFamily"] = "Arial";
this.properties["fontColor"] = "#ffffff";
this.properties["textAlign"] = "left";
this.properties["backgroundColor"] = "transparent";
this.properties["padding"] = 0;
this.properties["borderRadius"] = 0;
this.properties["angle"] = 0;
this.color = "#fff0";
this.bgcolor = "#fff0";
this.onConstructed();
}
draw(ctx) {
var _a, _b, _c, _d;
this.flags = this.flags || {};
this.flags.allow_interaction = !this.flags.pinned;
ctx.save();
this.color = "#fff0";
this.bgcolor = "#fff0";
const fontColor = this.properties["fontColor"] || "#ffffff";
const backgroundColor = this.properties["backgroundColor"] || "";
ctx.font = `${Math.max(this.properties["fontSize"] || 0, 1)}px ${(_a = this.properties["fontFamily"]) !== null && _a !== void 0 ? _a : "Arial"}`;
const padding = (_b = Number(this.properties["padding"])) !== null && _b !== void 0 ? _b : 0;
const processedTitle = ((_c = this.title) !== null && _c !== void 0 ? _c : "").replace(/\\n/g, "\n").replace(/\n*$/, "");
const lines = processedTitle.split("\n");
const maxWidth = Math.max(...lines.map((s) => ctx.measureText(s).width));
this.size[0] = maxWidth + padding * 2;
this.size[1] = this.properties["fontSize"] * lines.length + padding * 2;
const angleDeg = parseInt(String((_d = this.properties["angle"]) !== null && _d !== void 0 ? _d : 0)) || 0;
if (angleDeg) {
const cx = this.size[0] / 2;
const cy = this.size[1] / 2;
ctx.translate(cx, cy);
ctx.rotate((angleDeg * Math.PI) / 180);
ctx.translate(-cx, -cy);
}
if (backgroundColor) {
ctx.beginPath();
const borderRadius = Number(this.properties["borderRadius"]) || 0;
ctx.roundRect(0, 0, this.size[0], this.size[1], [borderRadius]);
ctx.fillStyle = backgroundColor;
ctx.fill();
}
ctx.textAlign = "left";
let textX = padding;
if (this.properties["textAlign"] === "center") {
ctx.textAlign = "center";
textX = this.size[0] / 2;
}
else if (this.properties["textAlign"] === "right") {
ctx.textAlign = "right";
textX = this.size[0] - padding;
}
ctx.textBaseline = "top";
ctx.fillStyle = fontColor;
let currentY = padding;
for (let i = 0; i < lines.length; i++) {
ctx.fillText(lines[i] || " ", textX, currentY);
currentY += this.properties["fontSize"];
}
ctx.restore();
}
onDblClick(event, pos, canvas) {
LGraphCanvas.active_canvas.showShowNodePanel(this);
}
onShowCustomPanelInfo(panel) {
var _a, _b;
(_a = panel.querySelector('div.property[data-property="Mode"]')) === null || _a === void 0 ? void 0 : _a.remove();
(_b = panel.querySelector('div.property[data-property="Color"]')) === null || _b === void 0 ? void 0 : _b.remove();
}
inResizeCorner(x, y) {
return this.resizable;
}
getHelp() {
return `
<p>
The rgthree-comfy ${this.type.replace("(rgthree)", "")} node allows you to add a floating
label to your workflow.
</p>
<p>
The text shown is the "Title" of the node and you can adjust the the font size, font family,
font color, text alignment as well as a background color, padding, and background border
radius from the node's properties. You can double-click the node to open the properties
panel.
<p>
<ul>
<li>
<p>
<strong>Pro tip #1:</strong> You can add multiline text from the properties panel
<i>(because ComfyUI let's you shift + enter there, only)</i>.
</p>
</li>
<li>
<p>
<strong>Pro tip #2:</strong> You can use ComfyUI's native "pin" option in the
right-click menu to make the label stick to the workflow and clicks to "go through".
You can right-click at any time to unpin.
</p>
</li>
<li>
<p>
<strong>Pro tip #3:</strong> Color values are hexidecimal strings, like "#FFFFFF" for
white, or "#660000" for dark red. You can supply a 7th & 8th value (or 5th if using
shorthand) to create a transluscent color. For instance, "#FFFFFF88" is semi-transparent
white.
</p>
</li>
</ul>`;
}
}
Label.type = NodeTypesString.LABEL;
Label.title = NodeTypesString.LABEL;
Label.title_mode = LiteGraph.NO_TITLE;
Label.collapsable = false;
Label["@fontSize"] = { type: "number" };
Label["@fontFamily"] = { type: "string" };
Label["@fontColor"] = { type: "string" };
Label["@textAlign"] = { type: "combo", values: ["left", "center", "right"] };
Label["@backgroundColor"] = { type: "string" };
Label["@padding"] = { type: "number" };
Label["@borderRadius"] = { type: "number" };
Label["@angle"] = { type: "number" };
const oldDrawNode = LGraphCanvas.prototype.drawNode;
LGraphCanvas.prototype.drawNode = function (node, ctx) {
if (node.constructor === Label.prototype.constructor) {
node.bgcolor = "transparent";
node.color = "transparent";
const v = oldDrawNode.apply(this, arguments);
node.draw(ctx);
return v;
}
const v = oldDrawNode.apply(this, arguments);
return v;
};
const oldGetNodeOnPos = LGraph.prototype.getNodeOnPos;
LGraph.prototype.getNodeOnPos = function (x, y, nodes_list) {
var _a, _b;
if (nodes_list &&
rgthree.processingMouseDown &&
((_a = rgthree.lastCanvasMouseEvent) === null || _a === void 0 ? void 0 : _a.type.includes("down")) &&
((_b = rgthree.lastCanvasMouseEvent) === null || _b === void 0 ? void 0 : _b.which) === 1) {
let isDoubleClick = LiteGraph.getTime() - LGraphCanvas.active_canvas.last_mouseclick < 300;
if (!isDoubleClick) {
nodes_list = [...nodes_list].filter((n) => { var _a; return !(n instanceof Label) || !((_a = n.flags) === null || _a === void 0 ? void 0 : _a.pinned); });
}
}
return oldGetNodeOnPos.apply(this, [x, y, nodes_list]);
};
app.registerExtension({
name: "rgthree.Label",
registerCustomNodes() {
Label.setUp();
},
});

View File

@@ -0,0 +1,113 @@
import { app } from "../../scripts/app.js";
import { rgthree } from "./rgthree.js";
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
const SPECIAL_ENTRIES = [/^(CHOOSE|NONE|DISABLE|OPEN)(\s|$)/i, /^\p{Extended_Pictographic}/gu];
app.registerExtension({
name: "rgthree.ContextMenuAutoNest",
async setup() {
const logger = rgthree.newLogSession("[ContextMenuAutoNest]");
const existingContextMenu = LiteGraph.ContextMenu;
LiteGraph.ContextMenu = function (values, options) {
var _a, _b, _c, _d, _e, _f, _g, _h;
const threshold = CONFIG_SERVICE.getConfigValue("features.menu_auto_nest.threshold", 20);
const enabled = CONFIG_SERVICE.getConfigValue("features.menu_auto_nest.subdirs", false);
let incompatible = !enabled || !!((_a = options === null || options === void 0 ? void 0 : options.extra) === null || _a === void 0 ? void 0 : _a.rgthree_doNotNest);
if (!incompatible) {
if (values.length <= threshold) {
incompatible = `Skipping context menu auto nesting b/c threshold is not met (${threshold})`;
}
if (!((_c = (_b = options.parentMenu) === null || _b === void 0 ? void 0 : _b.options) === null || _c === void 0 ? void 0 : _c.rgthree_originalCallback)) {
if (!(options === null || options === void 0 ? void 0 : options.callback)) {
incompatible = `Skipping context menu auto nesting b/c a callback was expected.`;
}
else if (values.some((i) => typeof i !== "string")) {
incompatible = `Skipping context menu auto nesting b/c not all values were strings.`;
}
}
}
if (incompatible) {
if (enabled) {
const [n, v] = logger.infoParts("Skipping context menu auto nesting for incompatible menu.");
(_d = console[n]) === null || _d === void 0 ? void 0 : _d.call(console, ...v);
}
return existingContextMenu.apply(this, [...arguments]);
}
const folders = {};
const specialOps = [];
const folderless = [];
for (const value of values) {
if (!value) {
folderless.push(value);
continue;
}
const newValue = typeof value === "string" ? { content: value } : Object.assign({}, value);
newValue.rgthree_originalValue = value.rgthree_originalValue || value;
const valueContent = newValue.content || "";
const splitBy = valueContent.indexOf("/") > -1 ? "/" : "\\";
const valueSplit = valueContent.split(splitBy);
if (valueSplit.length > 1) {
const key = valueSplit.shift();
newValue.content = valueSplit.join(splitBy);
folders[key] = folders[key] || [];
folders[key].push(newValue);
}
else if (SPECIAL_ENTRIES.some((r) => r.test(valueContent))) {
specialOps.push(newValue);
}
else {
folderless.push(newValue);
}
}
const foldersCount = Object.values(folders).length;
if (foldersCount > 0) {
options.rgthree_originalCallback =
options.rgthree_originalCallback ||
((_f = (_e = options.parentMenu) === null || _e === void 0 ? void 0 : _e.options) === null || _f === void 0 ? void 0 : _f.rgthree_originalCallback) ||
options.callback;
const oldCallback = options === null || options === void 0 ? void 0 : options.rgthree_originalCallback;
options.callback = undefined;
const newCallback = (item, options, event, parentMenu, node) => {
oldCallback === null || oldCallback === void 0 ? void 0 : oldCallback(item === null || item === void 0 ? void 0 : item.rgthree_originalValue, options, event, undefined, node);
};
const [n, v] = logger.infoParts(`Nested folders found (${foldersCount}).`);
(_g = console[n]) === null || _g === void 0 ? void 0 : _g.call(console, ...v);
const newValues = [];
for (const [folderName, folderValues] of Object.entries(folders)) {
newValues.push({
content: `📁 ${folderName}`,
has_submenu: true,
callback: () => {
},
submenu: {
options: folderValues.map((value) => {
value.callback = newCallback;
return value;
}),
},
});
}
values = [].concat(specialOps.map((f) => {
if (typeof f === "string") {
f = { content: f };
}
f.callback = newCallback;
return f;
}), newValues, folderless.map((f) => {
if (typeof f === "string") {
f = { content: f };
}
f.callback = newCallback;
return f;
}));
}
if (options.scale == null) {
options.scale = Math.max(((_h = app.canvas.ds) === null || _h === void 0 ? void 0 : _h.scale) || 1, 1);
}
const oldCtrResponse = existingContextMenu.call(this, values, options);
if (oldCtrResponse === null || oldCtrResponse === void 0 ? void 0 : oldCtrResponse.constructor) {
oldCtrResponse.constructor = LiteGraph.ContextMenu;
}
return this;
};
},
});

View File

@@ -0,0 +1,62 @@
import { app } from "../../scripts/app.js";
const clipboardSupportedPromise = new Promise(async (resolve) => {
try {
const result = await navigator.permissions.query({ name: "clipboard-write" });
resolve(result.state === "granted");
return;
}
catch (e) {
try {
if (!navigator.clipboard.write) {
throw new Error();
}
new ClipboardItem({ "image/png": new Blob([], { type: "image/png" }) });
resolve(true);
return;
}
catch (e) {
resolve(false);
}
}
});
app.registerExtension({
name: "rgthree.CopyImageToClipboard",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name.toLowerCase().includes("image")) {
if (await clipboardSupportedPromise) {
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function (canvas, options) {
var _a, _b;
options = (_a = getExtraMenuOptions === null || getExtraMenuOptions === void 0 ? void 0 : getExtraMenuOptions.call(this, canvas, options)) !== null && _a !== void 0 ? _a : options;
if ((_b = this.imgs) === null || _b === void 0 ? void 0 : _b.length) {
let img = this.imgs[this.imageIndex || 0] || this.imgs[this.overIndex || 0] || this.imgs[0];
const foundIdx = options.findIndex((option) => { var _a; return (_a = option === null || option === void 0 ? void 0 : option.content) === null || _a === void 0 ? void 0 : _a.includes("Copy Image"); });
if (img && foundIdx === -1) {
const menuItem = {
content: "Copy Image (rgthree)",
callback: () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight);
canvas.toBlob((blob) => {
navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
});
},
};
let idx = options.findIndex((option) => { var _a; return (_a = option === null || option === void 0 ? void 0 : option.content) === null || _a === void 0 ? void 0 : _a.includes("Open Image"); }) + 1;
if (idx != null) {
options.splice(idx, 0, menuItem);
}
else {
options.unshift(menuItem);
}
}
}
return [];
};
}
}
},
});

View File

@@ -0,0 +1,65 @@
import { app } from "../../scripts/app.js";
import { rgthree } from "./rgthree.js";
import { getGroupNodes, getOutputNodes } from "./utils.js";
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
function showQueueNodesMenuIfOutputNodesAreSelected(existingOptions) {
if (CONFIG_SERVICE.getConfigValue("features.menu_queue_selected_nodes") === false) {
return;
}
const outputNodes = getOutputNodes(Object.values(app.canvas.selected_nodes));
const menuItem = {
content: `Queue Selected Output Nodes (rgthree) &nbsp;`,
className: "rgthree-contextmenu-item",
callback: () => {
rgthree.queueOutputNodes(outputNodes);
},
disabled: !outputNodes.length,
};
let idx = existingOptions.findIndex((o) => (o === null || o === void 0 ? void 0 : o.content) === "Outputs") + 1;
idx = idx || existingOptions.findIndex((o) => (o === null || o === void 0 ? void 0 : o.content) === "Align") + 1;
idx = idx || 3;
existingOptions.splice(idx, 0, menuItem);
}
function showQueueGroupNodesMenuIfGroupIsSelected(existingOptions) {
if (CONFIG_SERVICE.getConfigValue("features.menu_queue_selected_nodes") === false) {
return;
}
const group = rgthree.lastCanvasMouseEvent &&
(app.canvas.getCurrentGraph() || app.graph).getGroupOnPos(rgthree.lastCanvasMouseEvent.canvasX, rgthree.lastCanvasMouseEvent.canvasY);
const outputNodes = (group && getOutputNodes(getGroupNodes(group))) || null;
const menuItem = {
content: `Queue Group Output Nodes (rgthree) &nbsp;`,
className: "rgthree-contextmenu-item",
callback: () => {
outputNodes && rgthree.queueOutputNodes(outputNodes);
},
disabled: !(outputNodes === null || outputNodes === void 0 ? void 0 : outputNodes.length),
};
let idx = existingOptions.findIndex((o) => { var _a; return (_a = o === null || o === void 0 ? void 0 : o.content) === null || _a === void 0 ? void 0 : _a.startsWith("Queue Selected "); }) + 1;
idx = idx || existingOptions.findIndex((o) => (o === null || o === void 0 ? void 0 : o.content) === "Outputs") + 1;
idx = idx || existingOptions.findIndex((o) => (o === null || o === void 0 ? void 0 : o.content) === "Align") + 1;
idx = idx || 3;
existingOptions.splice(idx, 0, menuItem);
}
app.registerExtension({
name: "rgthree.QueueNode",
async beforeRegisterNodeDef(nodeType, nodeData) {
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function (canvas, options) {
var _a;
const extraOptions = (_a = getExtraMenuOptions === null || getExtraMenuOptions === void 0 ? void 0 : getExtraMenuOptions.call(this, canvas, options)) !== null && _a !== void 0 ? _a : [];
showQueueNodesMenuIfOutputNodesAreSelected(options);
showQueueGroupNodesMenuIfGroupIsSelected(options);
return extraOptions;
};
},
async setup() {
const getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args) {
const options = getCanvasMenuOptions.apply(this, [...args]);
showQueueNodesMenuIfOutputNodesAreSelected(options);
showQueueGroupNodesMenuIfGroupIsSelected(options);
return options;
};
},
});

View File

@@ -0,0 +1,45 @@
import { app } from "../../scripts/app.js";
import { BaseNodeModeChanger } from "./base_node_mode_changer.js";
import { NodeTypesString } from "./constants.js";
const MODE_MUTE = 2;
const MODE_ALWAYS = 0;
class MuterNode extends BaseNodeModeChanger {
constructor(title = MuterNode.title) {
super(title);
this.comfyClass = NodeTypesString.FAST_MUTER;
this.modeOn = MODE_ALWAYS;
this.modeOff = MODE_MUTE;
this.onConstructed();
}
async handleAction(action) {
if (action === "Mute all") {
for (const widget of this.widgets) {
this.forceWidgetOff(widget, true);
}
}
else if (action === "Enable all") {
for (const widget of this.widgets) {
this.forceWidgetOn(widget, true);
}
}
else if (action === "Toggle all") {
for (const widget of this.widgets) {
this.forceWidgetToggle(widget, true);
}
}
}
}
MuterNode.exposedActions = ["Mute all", "Enable all", "Toggle all"];
MuterNode.type = NodeTypesString.FAST_MUTER;
MuterNode.title = NodeTypesString.FAST_MUTER;
app.registerExtension({
name: "rgthree.Muter",
registerCustomNodes() {
MuterNode.setUp();
},
loadedGraphNode(node) {
if (node.type == MuterNode.title) {
node._tempWidth = node.size[0];
}
},
});

View File

@@ -0,0 +1,113 @@
import { app } from "../../scripts/app.js";
import { addConnectionLayoutSupport } from "./utils.js";
import { wait } from "../../rgthree/common/shared_utils.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
import { BaseCollectorNode } from "./base_node_collector.js";
import { NodeTypesString } from "./constants.js";
class CollectorNode extends BaseCollectorNode {
constructor(title = CollectorNode.title) {
super(title);
this.comfyClass = NodeTypesString.NODE_COLLECTOR;
this.onConstructed();
}
onConstructed() {
this.addOutput("Output", "*");
return super.onConstructed();
}
}
CollectorNode.type = NodeTypesString.NODE_COLLECTOR;
CollectorNode.title = NodeTypesString.NODE_COLLECTOR;
class CombinerNode extends CollectorNode {
constructor(title = CombinerNode.title) {
super(title);
const note = ComfyWidgets["STRING"](this, "last_seed", ["STRING", { multiline: true }], app).widget;
note.inputEl.value =
'The Node Combiner has been renamed to Node Collector. You can right-click and select "Update to Node Collector" to attempt to automatically update.';
note.inputEl.readOnly = true;
note.inputEl.style.backgroundColor = "#332222";
note.inputEl.style.fontWeight = "bold";
note.inputEl.style.fontStyle = "italic";
note.inputEl.style.opacity = "0.8";
this.getExtraMenuOptions = (canvas, options) => {
options.splice(options.length - 1, 0, {
content: "‼️ Update to Node Collector",
callback: (_value, _options, _event, _parentMenu, _node) => {
updateCombinerToCollector(this);
},
});
return [];
};
}
configure(info) {
super.configure(info);
if (this.title != CombinerNode.title && !this.title.startsWith("‼️")) {
this.title = "‼️ " + this.title;
}
}
}
CombinerNode.legacyType = "Node Combiner (rgthree)";
CombinerNode.title = "‼️ Node Combiner [DEPRECATED]";
async function updateCombinerToCollector(node) {
if (node.type === CombinerNode.legacyType) {
const newNode = new CollectorNode();
if (node.title != CombinerNode.title) {
newNode.title = node.title.replace("‼️ ", "");
}
newNode.pos = [...node.pos];
newNode.size = [...node.size];
newNode.properties = { ...node.properties };
const links = [];
const graph = (node.graph || app.graph);
for (const [index, output] of node.outputs.entries()) {
for (const linkId of output.links || []) {
const link = graph.links[linkId];
if (!link)
continue;
const targetNode = graph.getNodeById(link.target_id);
links.push({ node: newNode, slot: index, targetNode, targetSlot: link.target_slot });
}
}
for (const [index, input] of node.inputs.entries()) {
const linkId = input.link;
if (linkId) {
const link = graph.links[linkId];
const originNode = graph.getNodeById(link.origin_id);
links.push({
node: originNode,
slot: link.origin_slot,
targetNode: newNode,
targetSlot: index,
});
}
}
graph.add(newNode);
await wait();
for (const link of links) {
link.node.connect(link.slot, link.targetNode, link.targetSlot);
}
await wait();
graph.remove(node);
}
}
app.registerExtension({
name: "rgthree.NodeCollector",
registerCustomNodes() {
addConnectionLayoutSupport(CollectorNode, app, [
["Left", "Right"],
["Right", "Left"],
]);
LiteGraph.registerNodeType(CollectorNode.title, CollectorNode);
CollectorNode.category = CollectorNode._category;
},
});
app.registerExtension({
name: "rgthree.NodeCombiner",
registerCustomNodes() {
addConnectionLayoutSupport(CombinerNode, app, [
["Left", "Right"],
["Right", "Left"],
]);
LiteGraph.registerNodeType(CombinerNode.legacyType, CombinerNode);
CombinerNode.category = CombinerNode._category;
},
});

View File

@@ -0,0 +1,205 @@
import { app } from "../../scripts/app.js";
import { PassThroughFollowing, addConnectionLayoutSupport, changeModeOfNodes, getConnectedInputNodesAndFilterPassThroughs, getConnectedOutputNodesAndFilterPassThroughs, } from "./utils.js";
import { wait } from "../../rgthree/common/shared_utils.js";
import { BaseCollectorNode } from "./base_node_collector.js";
import { NodeTypesString, stripRgthree } from "./constants.js";
import { fitString } from "./utils_canvas.js";
import { rgthree } from "./rgthree.js";
const MODE_ALWAYS = 0;
const MODE_MUTE = 2;
const MODE_BYPASS = 4;
const MODE_REPEATS = [MODE_MUTE, MODE_BYPASS];
const MODE_NOTHING = -99;
const MODE_TO_OPTION = new Map([
[MODE_ALWAYS, "ACTIVE"],
[MODE_MUTE, "MUTE"],
[MODE_BYPASS, "BYPASS"],
[MODE_NOTHING, "NOTHING"],
]);
const OPTION_TO_MODE = new Map([
["ACTIVE", MODE_ALWAYS],
["MUTE", MODE_MUTE],
["BYPASS", MODE_BYPASS],
["NOTHING", MODE_NOTHING],
]);
const MODE_TO_PROPERTY = new Map([
[MODE_MUTE, "on_muted_inputs"],
[MODE_BYPASS, "on_bypassed_inputs"],
[MODE_ALWAYS, "on_any_active_inputs"],
]);
const logger = rgthree.newLogSession("[NodeModeRelay]");
class NodeModeRelay extends BaseCollectorNode {
constructor(title) {
super(title);
this.inputsPassThroughFollowing = PassThroughFollowing.ALL;
this.comfyClass = NodeTypesString.NODE_MODE_RELAY;
this.properties["on_muted_inputs"] = "MUTE";
this.properties["on_bypassed_inputs"] = "BYPASS";
this.properties["on_any_active_inputs"] = "ACTIVE";
this.onConstructed();
}
onConstructed() {
this.addOutput("REPEATER", "_NODE_REPEATER_", {
color_on: "#Fc0",
color_off: "#a80",
shape: LiteGraph.ARROW_SHAPE,
});
setTimeout(() => {
this.stabilize();
}, 500);
return super.onConstructed();
}
onModeChange(from, to) {
var _a;
super.onModeChange(from, to);
if (this.inputs.length <= 1 && !this.isInputConnected(0) && this.isAnyOutputConnected()) {
const [n, v] = logger.infoParts(`Mode change without any inputs; relaying our mode.`);
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
this.dispatchModeToRepeater(to);
}
}
onDrawForeground(ctx, canvas) {
var _a;
if ((_a = this.flags) === null || _a === void 0 ? void 0 : _a.collapsed) {
return;
}
if (this.properties["on_muted_inputs"] !== "MUTE" ||
this.properties["on_bypassed_inputs"] !== "BYPASS" ||
this.properties["on_any_active_inputs"] != "ACTIVE") {
let margin = 15;
ctx.textAlign = "left";
let label = `*(MUTE > ${this.properties["on_muted_inputs"]}, `;
label += `BYPASS > ${this.properties["on_bypassed_inputs"]}, `;
label += `ACTIVE > ${this.properties["on_any_active_inputs"]})`;
ctx.fillStyle = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR;
const oldFont = ctx.font;
ctx.font = "italic " + (LiteGraph.NODE_SUBTEXT_SIZE - 2) + "px Arial";
ctx.fillText(fitString(ctx, label, this.size[0] - 20), 15, this.size[1] - 6);
ctx.font = oldFont;
}
}
computeSize(out) {
let size = super.computeSize(out);
if (this.properties["on_muted_inputs"] !== "MUTE" ||
this.properties["on_bypassed_inputs"] !== "BYPASS" ||
this.properties["on_any_active_inputs"] != "ACTIVE") {
size[1] += 17;
}
return size;
}
onConnectOutput(outputIndex, inputType, inputSlot, inputNode, inputIndex) {
var _a, _b;
let canConnect = (_a = super.onConnectOutput) === null || _a === void 0 ? void 0 : _a.call(this, outputIndex, inputType, inputSlot, inputNode, inputIndex);
let nextNode = (_b = getConnectedOutputNodesAndFilterPassThroughs(this, inputNode)[0]) !== null && _b !== void 0 ? _b : inputNode;
return canConnect && nextNode.type === NodeTypesString.NODE_MODE_REPEATER;
}
onConnectionsChange(type, slotIndex, isConnected, link_info, ioSlot) {
super.onConnectionsChange(type, slotIndex, isConnected, link_info, ioSlot);
setTimeout(() => {
this.stabilize();
}, 500);
}
stabilize() {
if (!this.graph || !this.isAnyOutputConnected() || !this.isInputConnected(0)) {
return;
}
const inputNodes = getConnectedInputNodesAndFilterPassThroughs(this, this, -1, this.inputsPassThroughFollowing);
let mode = undefined;
for (const inputNode of inputNodes) {
if (mode === undefined) {
mode = inputNode.mode;
}
else if (mode === inputNode.mode && MODE_REPEATS.includes(mode)) {
continue;
}
else if (inputNode.mode === MODE_ALWAYS || mode === MODE_ALWAYS) {
mode = MODE_ALWAYS;
}
else {
mode = undefined;
}
}
this.dispatchModeToRepeater(mode);
setTimeout(() => {
this.stabilize();
}, 500);
}
dispatchModeToRepeater(mode) {
var _a, _b;
if (mode != null) {
const propertyVal = (_a = this.properties) === null || _a === void 0 ? void 0 : _a[MODE_TO_PROPERTY.get(mode) || ""];
const newMode = OPTION_TO_MODE.get(propertyVal);
mode = (newMode !== null ? newMode : mode);
if (mode !== null && mode !== MODE_NOTHING) {
if ((_b = this.outputs) === null || _b === void 0 ? void 0 : _b.length) {
const outputNodes = getConnectedOutputNodesAndFilterPassThroughs(this);
for (const outputNode of outputNodes) {
changeModeOfNodes(outputNode, mode);
wait(16).then(() => {
outputNode.setDirtyCanvas(true, true);
});
}
}
}
}
}
getHelp() {
return `
<p>
This node will relay its input nodes' modes (Mute, Bypass, or Active) to a connected
${stripRgthree(NodeTypesString.NODE_MODE_REPEATER)} (which would then repeat that mode
change to all of its inputs).
</p>
<ul>
<li><p>
When all connected input nodes are muted, the relay will set a connected repeater to
mute (by default).
</p></li>
<li><p>
When all connected input nodes are bypassed, the relay will set a connected repeater to
bypass (by default).
</p></li>
<li><p>
When any connected input nodes are active, the relay will set a connected repeater to
active (by default).
</p></li>
<li><p>
If no inputs are connected, the relay will set a connected repeater to its mode <i>when
its own mode is changed</i>. <b>Note</b>, if any inputs are connected, then the above
will occur and the Relay's mode does not matter.
</p></li>
</ul>
<p>
Note, you can change which signals get sent on the above in the <code>Properties</code>.
For instance, you could configure an inverse relay which will send a MUTE when any of its
inputs are active (instead of sending an ACTIVE signal), and send an ACTIVE signal when all
of its inputs are muted (instead of sending a MUTE signal), etc.
</p>
`;
}
}
NodeModeRelay.type = NodeTypesString.NODE_MODE_RELAY;
NodeModeRelay.title = NodeTypesString.NODE_MODE_RELAY;
NodeModeRelay["@on_muted_inputs"] = {
type: "combo",
values: ["MUTE", "ACTIVE", "BYPASS", "NOTHING"],
};
NodeModeRelay["@on_bypassed_inputs"] = {
type: "combo",
values: ["BYPASS", "ACTIVE", "MUTE", "NOTHING"],
};
NodeModeRelay["@on_any_active_inputs"] = {
type: "combo",
values: ["BYPASS", "ACTIVE", "MUTE", "NOTHING"],
};
app.registerExtension({
name: "rgthree.NodeModeRepeaterHelper",
registerCustomNodes() {
addConnectionLayoutSupport(NodeModeRelay, app, [
["Left", "Right"],
["Right", "Left"],
]);
LiteGraph.registerNodeType(NodeModeRelay.type, NodeModeRelay);
NodeModeRelay.category = NodeModeRelay._category;
},
});

View File

@@ -0,0 +1,148 @@
import { app } from "../../scripts/app.js";
import { BaseCollectorNode } from "./base_node_collector.js";
import { NodeTypesString, stripRgthree } from "./constants.js";
import { PassThroughFollowing, addConnectionLayoutSupport, changeModeOfNodes, getConnectedInputNodesAndFilterPassThroughs, getConnectedOutputNodesAndFilterPassThroughs, getGroupNodes, } from "./utils.js";
class NodeModeRepeater extends BaseCollectorNode {
constructor(title) {
super(title);
this.inputsPassThroughFollowing = PassThroughFollowing.ALL;
this.comfyClass = NodeTypesString.NODE_MODE_REPEATER;
this.hasRelayInput = false;
this.hasTogglerOutput = false;
this.onConstructed();
}
onConstructed() {
this.addOutput("OPT_CONNECTION", "*", {
color_on: "#Fc0",
color_off: "#a80",
});
return super.onConstructed();
}
onConnectOutput(outputIndex, inputType, inputSlot, inputNode, inputIndex) {
let canConnect = !this.hasRelayInput;
canConnect =
canConnect && super.onConnectOutput(outputIndex, inputType, inputSlot, inputNode, inputIndex);
let nextNode = getConnectedOutputNodesAndFilterPassThroughs(this, inputNode)[0] || inputNode;
return (canConnect &&
[
NodeTypesString.FAST_MUTER,
NodeTypesString.FAST_BYPASSER,
NodeTypesString.NODE_COLLECTOR,
NodeTypesString.FAST_ACTIONS_BUTTON,
NodeTypesString.REROUTE,
NodeTypesString.RANDOM_UNMUTER,
].includes(nextNode.type || ""));
}
onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex) {
var _a;
let canConnect = (_a = super.onConnectInput) === null || _a === void 0 ? void 0 : _a.call(this, inputIndex, outputType, outputSlot, outputNode, outputIndex);
let nextNode = getConnectedOutputNodesAndFilterPassThroughs(this, outputNode)[0] || outputNode;
const isNextNodeRelay = nextNode.type === NodeTypesString.NODE_MODE_RELAY;
return canConnect && (!isNextNodeRelay || !this.hasTogglerOutput);
}
onConnectionsChange(type, slotIndex, isConnected, linkInfo, ioSlot) {
super.onConnectionsChange(type, slotIndex, isConnected, linkInfo, ioSlot);
let hasTogglerOutput = false;
let hasRelayInput = false;
const outputNodes = getConnectedOutputNodesAndFilterPassThroughs(this);
for (const outputNode of outputNodes) {
if ((outputNode === null || outputNode === void 0 ? void 0 : outputNode.type) === NodeTypesString.FAST_MUTER ||
(outputNode === null || outputNode === void 0 ? void 0 : outputNode.type) === NodeTypesString.FAST_BYPASSER) {
hasTogglerOutput = true;
break;
}
}
const inputNodes = getConnectedInputNodesAndFilterPassThroughs(this);
for (const [index, inputNode] of inputNodes.entries()) {
if ((inputNode === null || inputNode === void 0 ? void 0 : inputNode.type) === NodeTypesString.NODE_MODE_RELAY) {
if (hasTogglerOutput) {
console.log(`Can't be connected to a Relay if also output to a toggler.`);
this.disconnectInput(index);
}
else {
hasRelayInput = true;
if (this.inputs[index]) {
this.inputs[index].color_on = "#FC0";
this.inputs[index].color_off = "#a80";
}
}
}
else {
changeModeOfNodes(inputNode, this.mode);
}
}
this.hasTogglerOutput = hasTogglerOutput;
this.hasRelayInput = hasRelayInput;
if (this.hasRelayInput) {
if (this.outputs[0]) {
this.disconnectOutput(0);
this.removeOutput(0);
}
}
else if (!this.outputs[0]) {
this.addOutput("OPT_CONNECTION", "*", {
color_on: "#Fc0",
color_off: "#a80",
});
}
}
onModeChange(from, to) {
var _a, _b;
super.onModeChange(from, to);
const linkedNodes = getConnectedInputNodesAndFilterPassThroughs(this).filter((node) => node.type !== NodeTypesString.NODE_MODE_RELAY);
if (linkedNodes.length) {
for (const node of linkedNodes) {
if (node.type !== NodeTypesString.NODE_MODE_RELAY) {
changeModeOfNodes(node, to);
}
}
}
else if ((_b = (_a = this.graph) === null || _a === void 0 ? void 0 : _a._groups) === null || _b === void 0 ? void 0 : _b.length) {
for (const group of this.graph._groups) {
group.recomputeInsideNodes();
const groupNodes = getGroupNodes(group);
if (groupNodes === null || groupNodes === void 0 ? void 0 : groupNodes.includes(this)) {
for (const node of groupNodes) {
if (node !== this) {
changeModeOfNodes(node, to);
}
}
}
}
}
}
getHelp() {
return `
<p>
When this node's mode (Mute, Bypass, Active) changes, it will "repeat" that mode to all
connected input nodes, or, if there are no connected nodes AND it is overlapping a group,
"repeat" it's mode to all nodes in that group.
</p>
<ul>
<li><p>
Optionally, connect this mode's output to a ${stripRgthree(NodeTypesString.FAST_MUTER)}
or ${stripRgthree(NodeTypesString.FAST_BYPASSER)} for a single toggle to quickly
mute/bypass all its connected nodes.
</p></li>
<li><p>
Optionally, connect a ${stripRgthree(NodeTypesString.NODE_MODE_RELAY)} to this nodes
inputs to have it automatically toggle its mode. If connected, this will always take
precedence (and disconnect any connected fast togglers).
</p></li>
</ul>
`;
}
}
NodeModeRepeater.type = NodeTypesString.NODE_MODE_REPEATER;
NodeModeRepeater.title = NodeTypesString.NODE_MODE_REPEATER;
app.registerExtension({
name: "rgthree.NodeModeRepeater",
registerCustomNodes() {
addConnectionLayoutSupport(NodeModeRepeater, app, [
["Left", "Right"],
["Right", "Left"],
]);
LiteGraph.registerNodeType(NodeModeRepeater.type, NodeModeRepeater);
NodeModeRepeater.category = NodeModeRepeater._category;
},
});

View File

@@ -0,0 +1,149 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _ComfyNodeWrapper_id, _ComfyWidgetWrapper_widget;
import { app } from "../../scripts/app.js";
import { Exposed, execute, PyTuple } from "../../rgthree/common/py_parser.js";
import { RgthreeBaseVirtualNode } from "./base_node.js";
import { RgthreeBetterButtonWidget } from "./utils_widgets.js";
import { NodeTypesString } from "./constants.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
import { changeModeOfNodes, getNodeById } from "./utils.js";
const BUILT_INS = {
node: {
fn: (query) => {
if (typeof query === "number" || /^\d+(\.\d+)?/.exec(query)) {
return new ComfyNodeWrapper(Number(query));
}
return null;
},
},
};
class RgthreePowerConductor extends RgthreeBaseVirtualNode {
constructor(title = RgthreePowerConductor.title) {
super(title);
this.comfyClass = NodeTypesString.POWER_CONDUCTOR;
this.serialize_widgets = true;
this.codeWidget = ComfyWidgets.STRING(this, "", ["STRING", { multiline: true }], app).widget;
this.addCustomWidget(this.codeWidget);
(this.buttonWidget = new RgthreeBetterButtonWidget("Run", (...args) => {
this.execute();
})),
this.addCustomWidget(this.buttonWidget);
this.onConstructed();
}
execute() {
execute(this.codeWidget.value, {}, BUILT_INS);
}
}
RgthreePowerConductor.title = NodeTypesString.POWER_CONDUCTOR;
RgthreePowerConductor.type = NodeTypesString.POWER_CONDUCTOR;
const NODE_CLASS = RgthreePowerConductor;
class ComfyNodeWrapper {
constructor(id) {
_ComfyNodeWrapper_id.set(this, void 0);
__classPrivateFieldSet(this, _ComfyNodeWrapper_id, id, "f");
}
getNode() {
return getNodeById(__classPrivateFieldGet(this, _ComfyNodeWrapper_id, "f"));
}
get id() {
return this.getNode().id;
}
get title() {
return this.getNode().title;
}
set title(value) {
this.getNode().title = value;
}
get widgets() {
var _a;
return new PyTuple((_a = this.getNode().widgets) === null || _a === void 0 ? void 0 : _a.map((w) => new ComfyWidgetWrapper(w)));
}
get mode() {
return this.getNode().mode;
}
mute() {
changeModeOfNodes(this.getNode(), 2);
}
bypass() {
changeModeOfNodes(this.getNode(), 4);
}
enable() {
changeModeOfNodes(this.getNode(), 0);
}
}
_ComfyNodeWrapper_id = new WeakMap();
__decorate([
Exposed
], ComfyNodeWrapper.prototype, "id", null);
__decorate([
Exposed
], ComfyNodeWrapper.prototype, "title", null);
__decorate([
Exposed
], ComfyNodeWrapper.prototype, "widgets", null);
__decorate([
Exposed
], ComfyNodeWrapper.prototype, "mode", null);
__decorate([
Exposed
], ComfyNodeWrapper.prototype, "mute", null);
__decorate([
Exposed
], ComfyNodeWrapper.prototype, "bypass", null);
__decorate([
Exposed
], ComfyNodeWrapper.prototype, "enable", null);
class ComfyWidgetWrapper {
constructor(widget) {
_ComfyWidgetWrapper_widget.set(this, void 0);
__classPrivateFieldSet(this, _ComfyWidgetWrapper_widget, widget, "f");
}
get value() {
return __classPrivateFieldGet(this, _ComfyWidgetWrapper_widget, "f").value;
}
get label() {
return __classPrivateFieldGet(this, _ComfyWidgetWrapper_widget, "f").label;
}
toggle(value) {
if (typeof __classPrivateFieldGet(this, _ComfyWidgetWrapper_widget, "f")["toggle"] === "function") {
__classPrivateFieldGet(this, _ComfyWidgetWrapper_widget, "f")["toggle"](value);
}
else {
}
}
}
_ComfyWidgetWrapper_widget = new WeakMap();
__decorate([
Exposed
], ComfyWidgetWrapper.prototype, "value", null);
__decorate([
Exposed
], ComfyWidgetWrapper.prototype, "label", null);
__decorate([
Exposed
], ComfyWidgetWrapper.prototype, "toggle", null);
app.registerExtension({
name: "rgthree.PowerConductor",
registerCustomNodes() {
if (CONFIG_SERVICE.getConfigValue("unreleased.power_conductor.enabled")) {
NODE_CLASS.setUp();
}
},
});

View File

@@ -0,0 +1,592 @@
var _a;
import { app } from "../../scripts/app.js";
import { RgthreeBaseServerNode } from "./base_node.js";
import { rgthree } from "./rgthree.js";
import { addConnectionLayoutSupport } from "./utils.js";
import { NodeTypesString } from "./constants.js";
import { drawInfoIcon, drawNumberWidgetPart, drawRoundedRectangle, drawTogglePart, fitString, isLowQuality, } from "./utils_canvas.js";
import { RgthreeBaseWidget, RgthreeBetterButtonWidget, RgthreeDividerWidget, } from "./utils_widgets.js";
import { rgthreeApi } from "../../rgthree/common/rgthree_api.js";
import { showLoraChooser } from "./utils_menu.js";
import { moveArrayItem, removeArrayItem } from "../../rgthree/common/shared_utils.js";
import { RgthreeLoraInfoDialog } from "./dialog_info.js";
import { LORA_INFO_SERVICE } from "../../rgthree/common/model_info_service.js";
const PROP_LABEL_SHOW_STRENGTHS = "Show Strengths";
const PROP_LABEL_SHOW_STRENGTHS_STATIC = `@${PROP_LABEL_SHOW_STRENGTHS}`;
const PROP_VALUE_SHOW_STRENGTHS_SINGLE = "Single Strength";
const PROP_VALUE_SHOW_STRENGTHS_SEPARATE = "Separate Model & Clip";
class RgthreePowerLoraLoader extends RgthreeBaseServerNode {
constructor(title = NODE_CLASS.title) {
super(title);
this.serialize_widgets = true;
this.logger = rgthree.newLogSession(`[Power Lora Stack]`);
this.loraWidgetsCounter = 0;
this.widgetButtonSpacer = null;
this.properties[PROP_LABEL_SHOW_STRENGTHS] = PROP_VALUE_SHOW_STRENGTHS_SINGLE;
rgthreeApi.getLoras();
if (rgthree.loadingApiJson) {
const fullApiJson = rgthree.loadingApiJson;
setTimeout(() => {
this.configureFromApiJson(fullApiJson);
}, 16);
}
}
configureFromApiJson(fullApiJson) {
var _b, _c;
if (this.id == null) {
const [n, v] = this.logger.errorParts("Cannot load from API JSON without node id.");
(_b = console[n]) === null || _b === void 0 ? void 0 : _b.call(console, ...v);
return;
}
const nodeData = fullApiJson[this.id] || fullApiJson[String(this.id)] || fullApiJson[Number(this.id)];
if (nodeData == null) {
const [n, v] = this.logger.errorParts(`No node found in API JSON for node id ${this.id}.`);
(_c = console[n]) === null || _c === void 0 ? void 0 : _c.call(console, ...v);
return;
}
this.configure({
widgets_values: Object.values(nodeData.inputs).filter((input) => typeof (input === null || input === void 0 ? void 0 : input["lora"]) === "string"),
});
}
configure(info) {
var _b;
while ((_b = this.widgets) === null || _b === void 0 ? void 0 : _b.length)
this.removeWidget(0);
this.widgetButtonSpacer = null;
if (info.id != null) {
super.configure(info);
}
this._tempWidth = this.size[0];
this._tempHeight = this.size[1];
for (const widgetValue of info.widgets_values || []) {
if ((widgetValue === null || widgetValue === void 0 ? void 0 : widgetValue.lora) !== undefined) {
const widget = this.addNewLoraWidget();
widget.value = { ...widgetValue };
}
}
this.addNonLoraWidgets();
this.size[0] = this._tempWidth;
this.size[1] = Math.max(this._tempHeight, this.computeSize()[1]);
}
onNodeCreated() {
var _b;
(_b = super.onNodeCreated) === null || _b === void 0 ? void 0 : _b.call(this);
this.addNonLoraWidgets();
const computed = this.computeSize();
this.size = this.size || [0, 0];
this.size[0] = Math.max(this.size[0], computed[0]);
this.size[1] = Math.max(this.size[1], computed[1]);
this.setDirtyCanvas(true, true);
}
addNewLoraWidget(lora) {
this.loraWidgetsCounter++;
const widget = this.addCustomWidget(new PowerLoraLoaderWidget("lora_" + this.loraWidgetsCounter));
if (lora)
widget.setLora(lora);
if (this.widgetButtonSpacer) {
moveArrayItem(this.widgets, widget, this.widgets.indexOf(this.widgetButtonSpacer));
}
return widget;
}
addNonLoraWidgets() {
moveArrayItem(this.widgets, this.addCustomWidget(new RgthreeDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 })), 0);
moveArrayItem(this.widgets, this.addCustomWidget(new PowerLoraLoaderHeaderWidget()), 1);
this.widgetButtonSpacer = this.addCustomWidget(new RgthreeDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }));
this.addCustomWidget(new RgthreeBetterButtonWidget(" Add Lora", (event, pos, node) => {
rgthreeApi.getLoras().then((lorasDetails) => {
const loras = lorasDetails.map((l) => l.file);
showLoraChooser(event, (value) => {
var _b;
if (typeof value === "string") {
if (value.includes("Power Lora Chooser")) {
}
else if (value !== "NONE") {
this.addNewLoraWidget(value);
const computed = this.computeSize();
const tempHeight = (_b = this._tempHeight) !== null && _b !== void 0 ? _b : 15;
this.size[1] = Math.max(tempHeight, computed[1]);
this.setDirtyCanvas(true, true);
}
}
}, null, [...loras]);
});
return true;
}));
}
getSlotInPosition(canvasX, canvasY) {
var _b;
const slot = super.getSlotInPosition(canvasX, canvasY);
if (!slot) {
let lastWidget = null;
for (const widget of this.widgets) {
if (!widget.last_y)
return;
if (canvasY > this.pos[1] + widget.last_y) {
lastWidget = widget;
continue;
}
break;
}
if ((_b = lastWidget === null || lastWidget === void 0 ? void 0 : lastWidget.name) === null || _b === void 0 ? void 0 : _b.startsWith("lora_")) {
return { widget: lastWidget, output: { type: "LORA WIDGET" } };
}
}
return slot;
}
getSlotMenuOptions(slot) {
var _b, _c, _d, _e, _f, _g;
if ((_c = (_b = slot === null || slot === void 0 ? void 0 : slot.widget) === null || _b === void 0 ? void 0 : _b.name) === null || _c === void 0 ? void 0 : _c.startsWith("lora_")) {
const widget = slot.widget;
const index = this.widgets.indexOf(widget);
const canMoveUp = !!((_e = (_d = this.widgets[index - 1]) === null || _d === void 0 ? void 0 : _d.name) === null || _e === void 0 ? void 0 : _e.startsWith("lora_"));
const canMoveDown = !!((_g = (_f = this.widgets[index + 1]) === null || _f === void 0 ? void 0 : _f.name) === null || _g === void 0 ? void 0 : _g.startsWith("lora_"));
const menuItems = [
{
content: ` Show Info`,
callback: () => {
widget.showLoraInfoDialog();
},
},
null,
{
content: `${widget.value.on ? "⚫" : "🟢"} Toggle ${widget.value.on ? "Off" : "On"}`,
callback: () => {
widget.value.on = !widget.value.on;
},
},
{
content: `⬆️ Move Up`,
disabled: !canMoveUp,
callback: () => {
moveArrayItem(this.widgets, widget, index - 1);
},
},
{
content: `⬇️ Move Down`,
disabled: !canMoveDown,
callback: () => {
moveArrayItem(this.widgets, widget, index + 1);
},
},
{
content: `🗑️ Remove`,
callback: () => {
removeArrayItem(this.widgets, widget);
},
},
];
new LiteGraph.ContextMenu(menuItems, {
title: "LORA WIDGET",
event: rgthree.lastCanvasMouseEvent,
});
return undefined;
}
return this.defaultGetSlotMenuOptions(slot);
}
refreshComboInNode(defs) {
rgthreeApi.getLoras(true);
}
hasLoraWidgets() {
var _b;
return !!((_b = this.widgets) === null || _b === void 0 ? void 0 : _b.find((w) => { var _b; return (_b = w.name) === null || _b === void 0 ? void 0 : _b.startsWith("lora_"); }));
}
allLorasState() {
var _b, _c, _d;
let allOn = true;
let allOff = true;
for (const widget of this.widgets) {
if ((_b = widget.name) === null || _b === void 0 ? void 0 : _b.startsWith("lora_")) {
const on = (_c = widget.value) === null || _c === void 0 ? void 0 : _c.on;
allOn = allOn && on === true;
allOff = allOff && on === false;
if (!allOn && !allOff) {
return null;
}
}
}
return allOn && ((_d = this.widgets) === null || _d === void 0 ? void 0 : _d.length) ? true : false;
}
toggleAllLoras() {
var _b, _c;
const allOn = this.allLorasState();
const toggledTo = !allOn ? true : false;
for (const widget of this.widgets) {
if (((_b = widget.name) === null || _b === void 0 ? void 0 : _b.startsWith("lora_")) && ((_c = widget.value) === null || _c === void 0 ? void 0 : _c.on) != null) {
widget.value.on = toggledTo;
}
}
}
static setUp(comfyClass, nodeData) {
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, NODE_CLASS);
}
static onRegisteredForOverride(comfyClass, ctxClass) {
addConnectionLayoutSupport(NODE_CLASS, app, [
["Left", "Right"],
["Right", "Left"],
]);
setTimeout(() => {
NODE_CLASS.category = comfyClass.category;
});
}
getHelp() {
return `
<p>
The ${this.type.replace("(rgthree)", "")} is a powerful node that condenses 100s of pixels
of functionality in a single, dynamic node that allows you to add loras, change strengths,
and quickly toggle on/off all without taking up half your screen.
</p>
<ul>
<li><p>
Add as many Lora's as you would like by clicking the "+ Add Lora" button.
There's no real limit!
</p></li>
<li><p>
Right-click on a Lora widget for special options to move the lora up or down
(no image affect, only presentational), toggle it on/off, or delete the row all together.
</p></li>
<li>
<p>
<strong>Properties.</strong> You can change the following properties (by right-clicking
on the node, and select "Properties" or "Properties Panel" from the menu):
</p>
<ul>
<li><p>
<code>${PROP_LABEL_SHOW_STRENGTHS}</code> - Change between showing a single, simple
strength (which will be used for both model and clip), or a more advanced view with
both model and clip strengths being modifiable.
</p></li>
</ul>
</li>
</ul>`;
}
}
_a = PROP_LABEL_SHOW_STRENGTHS_STATIC;
RgthreePowerLoraLoader.title = NodeTypesString.POWER_LORA_LOADER;
RgthreePowerLoraLoader.type = NodeTypesString.POWER_LORA_LOADER;
RgthreePowerLoraLoader.comfyClass = NodeTypesString.POWER_LORA_LOADER;
RgthreePowerLoraLoader[_a] = {
type: "combo",
values: [PROP_VALUE_SHOW_STRENGTHS_SINGLE, PROP_VALUE_SHOW_STRENGTHS_SEPARATE],
};
class PowerLoraLoaderHeaderWidget extends RgthreeBaseWidget {
constructor(name = "PowerLoraLoaderHeaderWidget") {
super(name);
this.value = { type: "PowerLoraLoaderHeaderWidget" };
this.type = "custom";
this.hitAreas = {
toggle: { bounds: [0, 0], onDown: this.onToggleDown },
};
this.showModelAndClip = null;
}
draw(ctx, node, w, posY, height) {
if (!node.hasLoraWidgets()) {
return;
}
this.showModelAndClip =
node.properties[PROP_LABEL_SHOW_STRENGTHS] === PROP_VALUE_SHOW_STRENGTHS_SEPARATE;
const margin = 10;
const innerMargin = margin * 0.33;
const lowQuality = isLowQuality();
const allLoraState = node.allLorasState();
posY += 2;
const midY = posY + height * 0.5;
let posX = 10;
ctx.save();
this.hitAreas.toggle.bounds = drawTogglePart(ctx, { posX, posY, height, value: allLoraState });
if (!lowQuality) {
posX += this.hitAreas.toggle.bounds[1] + innerMargin;
ctx.globalAlpha = app.canvas.editor_alpha * 0.55;
ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;
ctx.textAlign = "left";
ctx.textBaseline = "middle";
ctx.fillText("Toggle All", posX, midY);
let rposX = node.size[0] - margin - innerMargin - innerMargin;
ctx.textAlign = "center";
ctx.fillText(this.showModelAndClip ? "Clip" : "Strength", rposX - drawNumberWidgetPart.WIDTH_TOTAL / 2, midY);
if (this.showModelAndClip) {
rposX = rposX - drawNumberWidgetPart.WIDTH_TOTAL - innerMargin * 2;
ctx.fillText("Model", rposX - drawNumberWidgetPart.WIDTH_TOTAL / 2, midY);
}
}
ctx.restore();
}
onToggleDown(event, pos, node) {
node.toggleAllLoras();
this.cancelMouseDown();
return true;
}
}
const DEFAULT_LORA_WIDGET_DATA = {
on: true,
lora: null,
strength: 1,
strengthTwo: null,
};
class PowerLoraLoaderWidget extends RgthreeBaseWidget {
constructor(name) {
super(name);
this.type = "custom";
this.haveMouseMovedStrength = false;
this.loraInfoPromise = null;
this.loraInfo = null;
this.showModelAndClip = null;
this.hitAreas = {
toggle: { bounds: [0, 0], onDown: this.onToggleDown },
lora: { bounds: [0, 0], onClick: this.onLoraClick },
strengthDec: { bounds: [0, 0], onClick: this.onStrengthDecDown },
strengthVal: { bounds: [0, 0], onClick: this.onStrengthValUp },
strengthInc: { bounds: [0, 0], onClick: this.onStrengthIncDown },
strengthAny: { bounds: [0, 0], onMove: this.onStrengthAnyMove },
strengthTwoDec: { bounds: [0, 0], onClick: this.onStrengthTwoDecDown },
strengthTwoVal: { bounds: [0, 0], onClick: this.onStrengthTwoValUp },
strengthTwoInc: { bounds: [0, 0], onClick: this.onStrengthTwoIncDown },
strengthTwoAny: { bounds: [0, 0], onMove: this.onStrengthTwoAnyMove },
};
this._value = {
on: true,
lora: null,
strength: 1,
strengthTwo: null,
};
}
set value(v) {
this._value = v;
if (typeof this._value !== "object") {
this._value = { ...DEFAULT_LORA_WIDGET_DATA };
if (this.showModelAndClip) {
this._value.strengthTwo = this._value.strength;
}
}
this.getLoraInfo();
}
get value() {
return this._value;
}
setLora(lora) {
this._value.lora = lora;
this.getLoraInfo();
}
draw(ctx, node, w, posY, height) {
var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
let currentShowModelAndClip = node.properties[PROP_LABEL_SHOW_STRENGTHS] === PROP_VALUE_SHOW_STRENGTHS_SEPARATE;
if (this.showModelAndClip !== currentShowModelAndClip) {
let oldShowModelAndClip = this.showModelAndClip;
this.showModelAndClip = currentShowModelAndClip;
if (this.showModelAndClip) {
if (oldShowModelAndClip != null) {
this.value.strengthTwo = (_b = this.value.strength) !== null && _b !== void 0 ? _b : 1;
}
}
else {
this.value.strengthTwo = null;
this.hitAreas.strengthTwoDec.bounds = [0, -1];
this.hitAreas.strengthTwoVal.bounds = [0, -1];
this.hitAreas.strengthTwoInc.bounds = [0, -1];
this.hitAreas.strengthTwoAny.bounds = [0, -1];
}
}
ctx.save();
const margin = 10;
const innerMargin = margin * 0.33;
const lowQuality = isLowQuality();
const midY = posY + height * 0.5;
let posX = margin;
drawRoundedRectangle(ctx, { pos: [posX, posY], size: [node.size[0] - margin * 2, height] });
this.hitAreas.toggle.bounds = drawTogglePart(ctx, { posX, posY, height, value: this.value.on });
posX += this.hitAreas.toggle.bounds[1] + innerMargin;
if (lowQuality) {
ctx.restore();
return;
}
if (!this.value.on) {
ctx.globalAlpha = app.canvas.editor_alpha * 0.4;
}
ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;
let rposX = node.size[0] - margin - innerMargin - innerMargin;
const strengthValue = this.showModelAndClip
? ((_c = this.value.strengthTwo) !== null && _c !== void 0 ? _c : 1)
: ((_d = this.value.strength) !== null && _d !== void 0 ? _d : 1);
let textColor = undefined;
if (((_e = this.loraInfo) === null || _e === void 0 ? void 0 : _e.strengthMax) != null && strengthValue > ((_f = this.loraInfo) === null || _f === void 0 ? void 0 : _f.strengthMax)) {
textColor = "#c66";
}
else if (((_g = this.loraInfo) === null || _g === void 0 ? void 0 : _g.strengthMin) != null && strengthValue < ((_h = this.loraInfo) === null || _h === void 0 ? void 0 : _h.strengthMin)) {
textColor = "#c66";
}
const [leftArrow, text, rightArrow] = drawNumberWidgetPart(ctx, {
posX: node.size[0] - margin - innerMargin - innerMargin,
posY,
height,
value: strengthValue,
direction: -1,
textColor,
});
this.hitAreas.strengthDec.bounds = leftArrow;
this.hitAreas.strengthVal.bounds = text;
this.hitAreas.strengthInc.bounds = rightArrow;
this.hitAreas.strengthAny.bounds = [leftArrow[0], rightArrow[0] + rightArrow[1] - leftArrow[0]];
rposX = leftArrow[0] - innerMargin;
if (this.showModelAndClip) {
rposX -= innerMargin;
this.hitAreas.strengthTwoDec.bounds = this.hitAreas.strengthDec.bounds;
this.hitAreas.strengthTwoVal.bounds = this.hitAreas.strengthVal.bounds;
this.hitAreas.strengthTwoInc.bounds = this.hitAreas.strengthInc.bounds;
this.hitAreas.strengthTwoAny.bounds = this.hitAreas.strengthAny.bounds;
let textColor = undefined;
if (((_j = this.loraInfo) === null || _j === void 0 ? void 0 : _j.strengthMax) != null && this.value.strength > ((_k = this.loraInfo) === null || _k === void 0 ? void 0 : _k.strengthMax)) {
textColor = "#c66";
}
else if (((_l = this.loraInfo) === null || _l === void 0 ? void 0 : _l.strengthMin) != null &&
this.value.strength < ((_m = this.loraInfo) === null || _m === void 0 ? void 0 : _m.strengthMin)) {
textColor = "#c66";
}
const [leftArrow, text, rightArrow] = drawNumberWidgetPart(ctx, {
posX: rposX,
posY,
height,
value: (_o = this.value.strength) !== null && _o !== void 0 ? _o : 1,
direction: -1,
textColor,
});
this.hitAreas.strengthDec.bounds = leftArrow;
this.hitAreas.strengthVal.bounds = text;
this.hitAreas.strengthInc.bounds = rightArrow;
this.hitAreas.strengthAny.bounds = [
leftArrow[0],
rightArrow[0] + rightArrow[1] - leftArrow[0],
];
rposX = leftArrow[0] - innerMargin;
}
const infoIconSize = height * 0.66;
const infoWidth = infoIconSize + innerMargin + innerMargin;
if (this.hitAreas["info"]) {
rposX -= innerMargin;
drawInfoIcon(ctx, rposX - infoIconSize, posY + (height - infoIconSize) / 2, infoIconSize);
this.hitAreas.info.bounds = [rposX - infoIconSize, infoWidth];
rposX = rposX - infoIconSize - innerMargin;
}
const loraWidth = rposX - posX;
ctx.textAlign = "left";
ctx.textBaseline = "middle";
const loraLabel = String(((_p = this.value) === null || _p === void 0 ? void 0 : _p.lora) || "None");
ctx.fillText(fitString(ctx, loraLabel, loraWidth), posX, midY);
this.hitAreas.lora.bounds = [posX, loraWidth];
posX += loraWidth + innerMargin;
ctx.globalAlpha = app.canvas.editor_alpha;
ctx.restore();
}
serializeValue(node, index) {
var _b;
const v = { ...this.value };
if (!this.showModelAndClip) {
delete v.strengthTwo;
}
else {
this.value.strengthTwo = (_b = this.value.strengthTwo) !== null && _b !== void 0 ? _b : 1;
v.strengthTwo = this.value.strengthTwo;
}
return v;
}
onToggleDown(event, pos, node) {
this.value.on = !this.value.on;
this.cancelMouseDown();
return true;
}
onInfoDown(event, pos, node) {
this.showLoraInfoDialog();
}
onLoraClick(event, pos, node) {
showLoraChooser(event, (value) => {
if (typeof value === "string") {
this.value.lora = value;
this.loraInfo = null;
this.getLoraInfo();
}
node.setDirtyCanvas(true, true);
});
this.cancelMouseDown();
}
onStrengthDecDown(event, pos, node) {
this.stepStrength(-1, false);
}
onStrengthIncDown(event, pos, node) {
this.stepStrength(1, false);
}
onStrengthTwoDecDown(event, pos, node) {
this.stepStrength(-1, true);
}
onStrengthTwoIncDown(event, pos, node) {
this.stepStrength(1, true);
}
onStrengthAnyMove(event, pos, node) {
this.doOnStrengthAnyMove(event, false);
}
onStrengthTwoAnyMove(event, pos, node) {
this.doOnStrengthAnyMove(event, true);
}
doOnStrengthAnyMove(event, isTwo = false) {
var _b;
if (event.deltaX) {
let prop = isTwo ? "strengthTwo" : "strength";
this.haveMouseMovedStrength = true;
this.value[prop] = ((_b = this.value[prop]) !== null && _b !== void 0 ? _b : 1) + event.deltaX * 0.05;
}
}
onStrengthValUp(event, pos, node) {
this.doOnStrengthValUp(event, false);
}
onStrengthTwoValUp(event, pos, node) {
this.doOnStrengthValUp(event, true);
}
doOnStrengthValUp(event, isTwo = false) {
if (this.haveMouseMovedStrength)
return;
let prop = isTwo ? "strengthTwo" : "strength";
const canvas = app.canvas;
canvas.prompt("Value", this.value[prop], (v) => (this.value[prop] = Number(v)), event);
}
onMouseUp(event, pos, node) {
super.onMouseUp(event, pos, node);
this.haveMouseMovedStrength = false;
}
showLoraInfoDialog() {
if (!this.value.lora || this.value.lora === "None") {
return;
}
const infoDialog = new RgthreeLoraInfoDialog(this.value.lora).show();
infoDialog.addEventListener("close", ((e) => {
if (e.detail.dirty) {
this.getLoraInfo(true);
}
}));
}
stepStrength(direction, isTwo = false) {
var _b;
let step = 0.05;
let prop = isTwo ? "strengthTwo" : "strength";
let strength = ((_b = this.value[prop]) !== null && _b !== void 0 ? _b : 1) + step * direction;
this.value[prop] = Math.round(strength * 100) / 100;
}
getLoraInfo(force = false) {
if (!this.loraInfoPromise || force == true) {
let promise;
if (this.value.lora && this.value.lora != "None") {
promise = LORA_INFO_SERVICE.getInfo(this.value.lora, force, true);
}
else {
promise = Promise.resolve(null);
}
this.loraInfoPromise = promise.then((v) => (this.loraInfo = v));
}
return this.loraInfoPromise;
}
}
const NODE_CLASS = RgthreePowerLoraLoader;
app.registerExtension({
name: "rgthree.PowerLoraLoader",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name === NODE_CLASS.type) {
NODE_CLASS.setUp(nodeType, nodeData);
}
},
});

View File

@@ -0,0 +1,175 @@
import { app } from "../../scripts/app.js";
import { RgthreeBaseServerNode } from "./base_node.js";
import { NodeTypesString } from "./constants.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
import { moveArrayItem } from "../../rgthree/common/shared_utils.js";
const PROPERTY_HIDE_TYPE_SELECTOR = "hideTypeSelector";
const PRIMITIVES = {
STRING: "STRING",
INT: "INT",
FLOAT: "FLOAT",
BOOLEAN: "BOOLEAN",
};
class RgthreePowerPrimitive extends RgthreeBaseServerNode {
constructor(title = NODE_CLASS.title) {
super(title);
this.typeState = '';
this.properties[PROPERTY_HIDE_TYPE_SELECTOR] = false;
}
static setUp(comfyClass, nodeData) {
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, NODE_CLASS);
}
onNodeCreated() {
var _a;
(_a = super.onNodeCreated) === null || _a === void 0 ? void 0 : _a.call(this);
this.addInitialWidgets();
}
configure(info) {
super.configure(info);
if (this.outputTypeWidget.value === 'BOOL') {
this.outputTypeWidget.value = 'BOOLEAN';
}
setTimeout(() => {
this.setTypedData();
});
}
getExtraMenuOptions(canvas, options) {
const that = this;
super.getExtraMenuOptions(canvas, options);
const isHidden = !!this.properties[PROPERTY_HIDE_TYPE_SELECTOR];
const menuItems = [
{
content: `${isHidden ? "Show" : "Hide"} Type Selector Widget`,
callback: (...args) => {
this.setProperty(PROPERTY_HIDE_TYPE_SELECTOR, !this.properties[PROPERTY_HIDE_TYPE_SELECTOR]);
},
},
{
content: `Set type`,
submenu: {
options: Object.keys(PRIMITIVES),
callback(value, ...args) {
that.outputTypeWidget.value = value;
that.setTypedData();
},
},
},
];
options.splice(0, 0, ...menuItems, null);
return [];
}
addInitialWidgets() {
if (!this.outputTypeWidget) {
this.outputTypeWidget = this.addWidget("combo", "type", "STRING", (...args) => {
this.setTypedData();
}, {
values: Object.keys(PRIMITIVES),
});
this.outputTypeWidget.hidden = this.properties[PROPERTY_HIDE_TYPE_SELECTOR];
}
this.setTypedData();
}
setTypedData() {
var _a, _b, _c, _d, _e;
const name = "value";
const type = this.outputTypeWidget.value;
const linked = !!((_b = (_a = this.inputs) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.link);
const newTypeState = `${type}|${linked}`;
if (this.typeState == newTypeState)
return;
this.typeState = newTypeState;
let value = (_d = (_c = this.valueWidget) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : null;
let newWidget = null;
if (linked) {
newWidget = ComfyWidgets["STRING"](this, name, ["STRING"], app).widget;
newWidget.value = "";
}
else if (type == "STRING") {
newWidget = ComfyWidgets["STRING"](this, name, ["STRING", { multiline: true }], app).widget;
newWidget.value = value ? "" : String(value);
}
else if (type === "INT" || type === "FLOAT") {
const isFloat = type === "FLOAT";
newWidget = this.addWidget("number", name, value !== null && value !== void 0 ? value : 1, undefined, {
precision: isFloat ? 1 : 0,
step2: isFloat ? 0.1 : 0,
});
value = Number(value);
value = value == null || isNaN(value) ? 0 : value;
newWidget.value = value;
}
else if (type === "BOOLEAN") {
newWidget = this.addWidget("toggle", name, !!(value !== null && value !== void 0 ? value : true), undefined, {
on: "true",
off: "false",
});
if (typeof value === "string") {
value = !["false", "null", "None", "", "0"].includes(value.toLowerCase());
}
newWidget.value = !!value;
}
if (newWidget == null) {
throw new Error(`Unsupported type "${type}".`);
}
if (this.valueWidget) {
this.replaceWidget(this.valueWidget, newWidget);
}
else {
if (!this.widgets.includes(newWidget)) {
this.addCustomWidget(newWidget);
}
moveArrayItem(this.widgets, newWidget, 1);
}
this.valueWidget = newWidget;
if (!((_e = this.inputs) === null || _e === void 0 ? void 0 : _e.length)) {
this.addInput("value", "*", { widget: this.valueWidget });
}
else {
this.inputs[0].widget = this.valueWidget;
}
const output = this.outputs[0];
const outputLabel = output.label === "*" || output.label === output.type ? null : output.label;
output.type = type;
output.label = outputLabel || output.type;
}
onConnectionsChange(type, index, isConnected, link_info, inputOrOutput) {
var _a;
(_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.apply(this, [...arguments]);
if (this.inputs.includes(inputOrOutput)) {
this.setTypedData();
}
}
onPropertyChanged(name, value, prev_value) {
if (name === PROPERTY_HIDE_TYPE_SELECTOR) {
if (!this.outputTypeWidget) {
return true;
}
this.outputTypeWidget.hidden = this.properties[PROPERTY_HIDE_TYPE_SELECTOR];
if (this.outputTypeWidget.hidden) {
this.outputTypeWidget.computeLayoutSize = () => ({
minHeight: 0,
minWidth: 0,
maxHeight: 0,
maxWidth: 0,
});
}
else {
this.outputTypeWidget.computeLayoutSize = undefined;
}
}
return true;
}
}
RgthreePowerPrimitive.title = NodeTypesString.POWER_PRIMITIVE;
RgthreePowerPrimitive.type = NodeTypesString.POWER_PRIMITIVE;
RgthreePowerPrimitive.comfyClass = NodeTypesString.POWER_PRIMITIVE;
RgthreePowerPrimitive["@hideTypeSelector"] = { type: "boolean" };
const NODE_CLASS = RgthreePowerPrimitive;
app.registerExtension({
name: "rgthree.PowerPrimitive",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name === NODE_CLASS.type) {
NODE_CLASS.setUp(nodeType, nodeData);
}
},
});

View File

@@ -0,0 +1,42 @@
import { app } from "../../scripts/app.js";
import { addConnectionLayoutSupport } from "./utils.js";
import { PowerPrompt } from "./base_power_prompt.js";
import { NodeTypesString } from "./constants.js";
let nodeData = null;
app.registerExtension({
name: "rgthree.PowerPrompt",
async beforeRegisterNodeDef(nodeType, passedNodeData) {
if (passedNodeData.name.includes("Power Prompt") && passedNodeData.name.includes("rgthree")) {
nodeData = passedNodeData;
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
onNodeCreated ? onNodeCreated.apply(this, []) : undefined;
this.powerPrompt = new PowerPrompt(this, passedNodeData);
};
addConnectionLayoutSupport(nodeType, app, [
["Left", "Right"],
["Right", "Left"],
]);
}
},
async loadedGraphNode(node) {
if (node.type === NodeTypesString.POWER_PROMPT) {
setTimeout(() => {
if (node.outputs[0].type === "STRING") {
if (node.outputs[0].links) {
node.outputs[3].links = node.outputs[3].links || [];
for (const link of node.outputs[0].links) {
node.outputs[3].links.push(link);
(node.graph || app.graph).links[link].origin_slot = 3;
}
node.outputs[0].links = null;
}
node.outputs[0].type = nodeData.output[0];
node.outputs[0].name = nodeData.output_name[0] || node.outputs[0].type;
node.outputs[0].color_on = undefined;
node.outputs[0].color_off = undefined;
}
}, 50);
}
},
});

View File

@@ -0,0 +1,314 @@
import { app } from "../../scripts/app.js";
import { RgthreeBaseServerNode } from "./base_node.js";
import { NodeTypesString } from "./constants.js";
import { removeUnusedInputsFromEnd } from "./utils_inputs_outputs.js";
import { debounce } from "../../rgthree/common/shared_utils.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
import { RgthreeBaseWidget } from "./utils_widgets.js";
import { drawPlusIcon, drawRoundedRectangle, drawWidgetButton, isLowQuality, measureText, } from "./utils_canvas.js";
import { rgthree } from "./rgthree.js";
const ALPHABET = "abcdefghijklmnopqrstuv".split("");
const OUTPUT_TYPES = ["STRING", "INT", "FLOAT", "BOOLEAN", "*"];
class RgthreePowerPuter extends RgthreeBaseServerNode {
constructor(title = NODE_CLASS.title) {
super(title);
this.stabilizeBound = this.stabilize.bind(this);
this.addAnyInput(2);
this.addInitialWidgets();
}
static setUp(comfyClass, nodeData) {
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, NODE_CLASS);
}
onConnectionsChange(...args) {
var _a;
(_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.apply(this, [...arguments]);
this.scheduleStabilize();
}
scheduleStabilize(ms = 64) {
return debounce(this.stabilizeBound, ms);
}
stabilize() {
removeUnusedInputsFromEnd(this, 1);
this.addAnyInput();
this.setOutputs();
}
addInitialWidgets() {
if (!this.outputTypeWidget) {
this.outputTypeWidget = this.addCustomWidget(new OutputsWidget("outputs", this));
this.expressionWidget = ComfyWidgets["STRING"](this, "code", ["STRING", { multiline: true }], app).widget;
}
}
addAnyInput(num = 1) {
for (let i = 0; i < num; i++) {
this.addInput(ALPHABET[this.inputs.length], "*");
}
}
setOutputs() {
const desiredOutputs = this.outputTypeWidget.value.outputs;
for (let i = 0; i < Math.max(this.outputs.length, desiredOutputs.length); i++) {
const desired = desiredOutputs[i];
let output = this.outputs[i];
if (!desired && output) {
this.disconnectOutput(i);
this.removeOutput(i);
continue;
}
output = output || this.addOutput("", "");
const outputLabel = output.label === "*" || output.label === output.type ? null : output.label;
output.type = String(desired);
output.label = outputLabel || output.type;
}
}
getHelp() {
return `
<p>
The ${this.type.replace("(rgthree)", "")} is a powerful and versatile node that opens the
door for a wide range of utility by offering mult-line code parsing for output. This node
can be used for simple string concatenation, or math operations; to an image dimension or a
node's widgets with advanced list comprehension.
If you want to output something in your workflow, this is the node to do it.
</p>
<ul>
<li><p>
Evaluate almost any kind of input and more, and choose your output from INT, FLOAT,
STRING, or BOOLEAN.
</p></li>
<li><p>
Connect some nodes and do simply math operations like <code>a + b</code> or
<code>ceil(1 / 2)</code>.
</p></li>
<li><p>
Or do more advanced things, like input an image, and get the width like
<code>a.shape[2]</code>.
</p></li>
<li><p>
Even more powerful, you can target nodes in the prompt that's sent to the backend. For
instance; if you have a Power Lora Loader node at id #5, and want to get a comma-delimited
list of the enabled loras, you could enter
<code>', '.join([v.lora for v in node(5).inputs.values() if 'lora' in v and v.on])</code>.
</p></li>
<li><p>
See more at the <a target="_blank"
href="https://github.com/rgthree/rgthree-comfy/wiki/Node:-Power-Puter">rgthree-comfy
wiki</a>.
</p></li>
</ul>`;
}
}
RgthreePowerPuter.title = NodeTypesString.POWER_PUTER;
RgthreePowerPuter.type = NodeTypesString.POWER_PUTER;
RgthreePowerPuter.comfyClass = NodeTypesString.POWER_PUTER;
const NODE_CLASS = RgthreePowerPuter;
const OUTPUTS_WIDGET_CHIP_HEIGHT = LiteGraph.NODE_WIDGET_HEIGHT - 4;
const OUTPUTS_WIDGET_CHIP_SPACE = 4;
const OUTPUTS_WIDGET_CHIP_ARROW_WIDTH = 5.5;
const OUTPUTS_WIDGET_CHIP_ARROW_HEIGHT = 4;
class OutputsWidget extends RgthreeBaseWidget {
constructor(name, node) {
super(name);
this.type = "custom";
this._value = { outputs: ["STRING"] };
this.rows = 1;
this.neededHeight = LiteGraph.NODE_WIDGET_HEIGHT + 8;
this.hitAreas = {
add: { bounds: [0, 0], onClick: this.onAddChipDown },
output0: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 0 } },
output1: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 1 } },
output2: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 2 } },
output3: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 3 } },
output4: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 4 } },
output5: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 5 } },
output6: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 6 } },
output7: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 7 } },
output8: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 8 } },
output9: { bounds: [0, 0], onClick: this.onOutputChipDown, data: { index: 9 } },
};
this.node = node;
}
set value(v) {
let outputs = typeof v === "string" ? [v] : [...v.outputs];
outputs = outputs.map((o) => (o === "BOOL" ? "BOOLEAN" : o));
this._value.outputs = outputs;
}
get value() {
return this._value;
}
onAddChipDown(event, pos, node, bounds) {
new LiteGraph.ContextMenu(OUTPUT_TYPES, {
event: event,
title: "Add an output",
className: "rgthree-dark",
callback: (value) => {
if (isLowQuality())
return;
if (typeof value === "string" && OUTPUT_TYPES.includes(value)) {
this._value.outputs.push(value);
this.node.scheduleStabilize();
}
},
});
this.cancelMouseDown();
return true;
}
onOutputChipDown(event, pos, node, bounds) {
const options = [...OUTPUT_TYPES];
if (this.value.outputs.length > 1) {
options.push(null, "🗑️ Delete");
}
new LiteGraph.ContextMenu(options, {
event: event,
title: `Edit output #${bounds.data.index + 1}`,
className: "rgthree-dark",
callback: (value) => {
var _a, _b;
const index = bounds.data.index;
if (typeof value !== "string" || value === this._value.outputs[index] || isLowQuality()) {
return;
}
const output = this.node.outputs[index];
if (value.toLocaleLowerCase().includes("delete")) {
if ((_a = output.links) === null || _a === void 0 ? void 0 : _a.length) {
rgthree.showMessage({
id: "puter-remove-linked-output",
type: "warn",
message: "[Power Puter] Removed and disconnected output from that was connected!",
timeout: 3000,
});
this.node.disconnectOutput(index);
}
this.node.removeOutput(index);
this._value.outputs.splice(index, 1);
this.node.scheduleStabilize();
return;
}
if (((_b = output.links) === null || _b === void 0 ? void 0 : _b.length) && value !== "*") {
rgthree.showMessage({
id: "puter-remove-linked-output",
type: "warn",
message: "[Power Puter] Changing output type of linked output! You should check for" +
" compatibility.",
timeout: 3000,
});
}
this._value.outputs[index] = value;
this.node.scheduleStabilize();
},
});
this.cancelMouseDown();
return true;
}
computeLayoutSize(node) {
this.neededHeight =
OUTPUTS_WIDGET_CHIP_SPACE +
(OUTPUTS_WIDGET_CHIP_HEIGHT + OUTPUTS_WIDGET_CHIP_SPACE) * this.rows;
return {
minHeight: this.neededHeight,
maxHeight: this.neededHeight,
minWidth: 0,
};
}
draw(ctx, node, w, posY, height) {
var _a, _b;
ctx.save();
height = this.neededHeight;
const margin = 10;
const innerMargin = margin * 0.33;
const width = node.size[0] - margin * 2;
let borderRadius = LiteGraph.NODE_WIDGET_HEIGHT * 0.5;
let midY = posY + height * 0.5;
let posX = margin;
let rposX = node.size[0] - margin;
drawRoundedRectangle(ctx, { pos: [posX, posY], size: [width, height], borderRadius });
posX += innerMargin * 2;
rposX -= innerMargin * 2;
if (isLowQuality()) {
ctx.restore();
return;
}
ctx.fillStyle = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR;
ctx.textAlign = "left";
ctx.textBaseline = "middle";
ctx.fillText("outputs", posX, midY);
posX += measureText(ctx, "outputs") + innerMargin * 2;
ctx.stroke(new Path2D(`M ${posX} ${posY} v ${height}`));
posX += 1 + innerMargin * 2;
const inititalPosX = posX;
posY += OUTPUTS_WIDGET_CHIP_SPACE;
height = OUTPUTS_WIDGET_CHIP_HEIGHT;
borderRadius = height * 0.5;
midY = posY + height / 2;
ctx.textAlign = "center";
ctx.lineJoin = ctx.lineCap = "round";
ctx.fillStyle = ctx.strokeStyle = LiteGraph.WIDGET_TEXT_COLOR;
let rows = 1;
const values = (_b = (_a = this.value) === null || _a === void 0 ? void 0 : _a.outputs) !== null && _b !== void 0 ? _b : [];
const fontSize = ctx.font.match(/(\d+)px/);
if (fontSize === null || fontSize === void 0 ? void 0 : fontSize[1]) {
ctx.font = ctx.font.replace(fontSize[1], `${Number(fontSize[1]) - 2}`);
}
let i = 0;
for (i; i < values.length; i++) {
const hitArea = this.hitAreas[`output${i}`];
const isClicking = !!hitArea.wasMouseClickedAndIsOver;
hitArea.data.index = i;
const text = values[i];
const textWidth = measureText(ctx, text) + innerMargin * 2;
const width = textWidth + OUTPUTS_WIDGET_CHIP_ARROW_WIDTH + innerMargin * 5;
if (posX + width >= rposX) {
posX = inititalPosX;
posY = posY + height + 4;
midY = posY + height / 2;
rows++;
}
drawWidgetButton(ctx, { pos: [posX, posY], size: [width, height], borderRadius }, null, isClicking);
const startX = posX;
posX += innerMargin * 2;
const newMidY = midY + (isClicking ? 1 : 0);
ctx.fillText(text, posX + textWidth / 2, newMidY);
posX += textWidth + innerMargin;
const arrow = new Path2D(`M${posX} ${newMidY - OUTPUTS_WIDGET_CHIP_ARROW_HEIGHT / 2}
h${OUTPUTS_WIDGET_CHIP_ARROW_WIDTH}
l-${OUTPUTS_WIDGET_CHIP_ARROW_WIDTH / 2} ${OUTPUTS_WIDGET_CHIP_ARROW_HEIGHT} z`);
ctx.fill(arrow);
ctx.stroke(arrow);
posX += OUTPUTS_WIDGET_CHIP_ARROW_WIDTH + innerMargin * 2;
hitArea.bounds = [startX, posY, width, height];
posX += OUTPUTS_WIDGET_CHIP_SPACE;
}
for (i; i < 9; i++) {
const hitArea = this.hitAreas[`output${i}`];
if (hitArea.bounds[0] > 0) {
hitArea.bounds = [0, 0, 0, 0];
}
}
const addHitArea = this.hitAreas["add"];
if (this.value.outputs.length < 10) {
const isClicking = !!addHitArea.wasMouseClickedAndIsOver;
const plusSize = 10;
let plusWidth = innerMargin * 2 + plusSize + innerMargin * 2;
if (posX + plusWidth >= rposX) {
posX = inititalPosX;
posY = posY + height + 4;
midY = posY + height / 2;
rows++;
}
drawWidgetButton(ctx, { size: [plusWidth, height], pos: [posX, posY], borderRadius }, null, isClicking);
drawPlusIcon(ctx, posX + innerMargin * 2, midY + (isClicking ? 1 : 0), plusSize);
addHitArea.bounds = [posX, posY, plusWidth, height];
}
else {
addHitArea.bounds = [0, 0, 0, 0];
}
this.rows = rows;
ctx.restore();
}
}
app.registerExtension({
name: "rgthree.PowerPuter",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name === NODE_CLASS.type) {
NODE_CLASS.setUp(nodeType, nodeData);
}
},
});

View File

@@ -0,0 +1,102 @@
import { app } from "../../scripts/app.js";
import { BaseAnyInputConnectedNode } from "./base_any_input_connected_node.js";
import { NodeTypesString } from "./constants.js";
import { rgthree } from "./rgthree.js";
import { changeModeOfNodes, getConnectedInputNodesAndFilterPassThroughs } from "./utils.js";
const MODE_MUTE = 2;
const MODE_ALWAYS = 0;
class RandomUnmuterNode extends BaseAnyInputConnectedNode {
constructor(title = RandomUnmuterNode.title) {
super(title);
this.comfyClass = NodeTypesString.RANDOM_UNMUTER;
this.modeOn = MODE_ALWAYS;
this.modeOff = MODE_MUTE;
this.tempEnabledNode = null;
this.processingQueue = false;
this.onQueueBound = this.onQueue.bind(this);
this.onQueueEndBound = this.onQueueEnd.bind(this);
this.onGraphtoPromptBound = this.onGraphtoPrompt.bind(this);
this.onGraphtoPromptEndBound = this.onGraphtoPromptEnd.bind(this);
rgthree.addEventListener("queue", this.onQueueBound);
rgthree.addEventListener("queue-end", this.onQueueEndBound);
rgthree.addEventListener("graph-to-prompt", this.onGraphtoPromptBound);
rgthree.addEventListener("graph-to-prompt-end", this.onGraphtoPromptEndBound);
this.onConstructed();
}
onRemoved() {
rgthree.removeEventListener("queue", this.onQueueBound);
rgthree.removeEventListener("queue-end", this.onQueueEndBound);
rgthree.removeEventListener("graph-to-prompt", this.onGraphtoPromptBound);
rgthree.removeEventListener("graph-to-prompt-end", this.onGraphtoPromptEndBound);
}
onQueue(event) {
this.processingQueue = true;
}
onQueueEnd(event) {
this.processingQueue = false;
}
onGraphtoPrompt(event) {
if (!this.processingQueue) {
return;
}
this.tempEnabledNode = null;
const linkedNodes = getConnectedInputNodesAndFilterPassThroughs(this);
let allMuted = true;
if (linkedNodes.length) {
for (const node of linkedNodes) {
if (node.mode !== this.modeOff) {
allMuted = false;
break;
}
}
if (allMuted) {
this.tempEnabledNode = linkedNodes[Math.floor(Math.random() * linkedNodes.length)] || null;
if (this.tempEnabledNode) {
changeModeOfNodes(this.tempEnabledNode, this.modeOn);
}
}
}
}
onGraphtoPromptEnd(event) {
if (this.tempEnabledNode) {
changeModeOfNodes(this.tempEnabledNode, this.modeOff);
this.tempEnabledNode = null;
}
}
handleLinkedNodesStabilization(linkedNodes) {
return false;
}
getHelp() {
return `
<p>
Use this node to unmute on of its inputs randomly when the graph is queued (and, immediately
mute it back).
</p>
<ul>
<li><p>
NOTE: All input nodes MUST be muted to start; if not this node will not randomly unmute
another. (This is powerful, as the generated image can be dragged in and the chosen input
will already by unmuted and work w/o any further action.)
</p></li>
<li><p>
TIP: Connect a Repeater's output to this nodes input and place that Repeater on a group
without any other inputs, and it will mute/unmute the entire group.
</p></li>
</ul>
`;
}
}
RandomUnmuterNode.exposedActions = ["Mute all", "Enable all"];
RandomUnmuterNode.type = NodeTypesString.RANDOM_UNMUTER;
RandomUnmuterNode.title = RandomUnmuterNode.type;
app.registerExtension({
name: "rgthree.RandomUnmuter",
registerCustomNodes() {
RandomUnmuterNode.setUp();
},
loadedGraphNode(node) {
if (node.type == RandomUnmuterNode.title) {
node._tempWidth = node.size[0];
}
},
});

View File

@@ -0,0 +1,981 @@
var _a, _b;
import { app } from "../../scripts/app.js";
import { rgthreeConfig } from "../../rgthree/config.js";
import { rgthree } from "./rgthree.js";
import { IoDirection, LAYOUT_CLOCKWISE, LAYOUT_LABEL_OPPOSITES, LAYOUT_LABEL_TO_DATA, addConnectionLayoutSupport, addMenuItem, getSlotLinks, isValidConnection, setConnectionsLayout, waitForCanvas, } from "./utils.js";
import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js";
import { wait } from "../../rgthree/common/shared_utils.js";
import { RgthreeBaseVirtualNode } from "./base_node.js";
import { NodeTypesString } from "./constants.js";
import { rgthreeApi } from "../../rgthree/common/rgthree_api.js";
import { getWidgetConfig, mergeIfValid, setWidgetConfig } from "./utils_deprecated_comfyui.js";
const CONFIG_REROUTE = ((_a = rgthreeConfig === null || rgthreeConfig === void 0 ? void 0 : rgthreeConfig["nodes"]) === null || _a === void 0 ? void 0 : _a["reroute"]) || {};
const CONFIG_FAST_REROUTE = CONFIG_REROUTE["fast_reroute"];
const CONFIG_FAST_REROUTE_ENABLED = (_b = CONFIG_FAST_REROUTE["enabled"]) !== null && _b !== void 0 ? _b : false;
const CONFIG_KEY_CREATE_WHILE_LINKING = CONFIG_FAST_REROUTE["key_create_while_dragging_link"];
const CONFIG_KEY_ROTATE = CONFIG_FAST_REROUTE["key_rotate"];
const CONFIG_KEY_RESIZE = CONFIG_FAST_REROUTE["key_resize"];
const CONFIG_KEY_MOVE = CONFIG_FAST_REROUTE["key_move"];
const CONFIG_KEY_CXN_INPUT = CONFIG_FAST_REROUTE["key_connections_input"];
const CONFIG_KEY_CXN_OUTPUT = CONFIG_FAST_REROUTE["key_connections_output"];
let configWidth = Math.max(Math.round((Number(CONFIG_REROUTE["default_width"]) || 40) / 10) * 10, 10);
let configHeight = Math.max(Math.round((Number(CONFIG_REROUTE["default_height"]) || 30) / 10) * 10, 10);
while (configWidth * configHeight < 400) {
configWidth += 10;
configHeight += 10;
}
const configDefaultSize = [configWidth, configHeight];
const configResizable = !!CONFIG_REROUTE["default_resizable"];
let configLayout = CONFIG_REROUTE["default_layout"];
if (!Array.isArray(configLayout)) {
configLayout = ["Left", "Right"];
}
if (!LAYOUT_LABEL_TO_DATA[configLayout[0]]) {
configLayout[0] = "Left";
}
if (!LAYOUT_LABEL_TO_DATA[configLayout[1]] || configLayout[0] == configLayout[1]) {
configLayout[1] = LAYOUT_LABEL_OPPOSITES[configLayout[0]];
}
class RerouteService {
constructor() {
this.isFastLinking = false;
this.handledNewRerouteKeypress = false;
this.connectingData = null;
this.fastReroutesHistory = [];
this.handleLinkingKeydownBound = this.handleLinkingKeydown.bind(this);
this.handleLinkingKeyupBound = this.handleLinkingKeyup.bind(this);
if (CONFIG_FAST_REROUTE_ENABLED && (CONFIG_KEY_CREATE_WHILE_LINKING === null || CONFIG_KEY_CREATE_WHILE_LINKING === void 0 ? void 0 : CONFIG_KEY_CREATE_WHILE_LINKING.trim())) {
this.onCanvasSetUpListenerForLinking();
}
}
async onCanvasSetUpListenerForLinking() {
const canvas = await waitForCanvas();
const canvasProperty = true ? "connecting_links" : "connecting_node";
canvas[`_${canvasProperty}`];
const thisService = this;
Object.defineProperty(canvas, canvasProperty, {
get: function () {
return this[`_${canvasProperty}`];
},
set: function (value) {
var _a;
const isValNull = !value || !(value === null || value === void 0 ? void 0 : value.length);
const isPropNull = !this[`_${canvasProperty}`] || !((_a = this[`_${canvasProperty}`]) === null || _a === void 0 ? void 0 : _a.length);
const isStartingLinking = !isValNull && isPropNull;
const isStoppingLinking = !isPropNull && isValNull;
this[`_${canvasProperty}`] = value;
if (isStartingLinking) {
thisService.startingLinking();
}
if (isStoppingLinking) {
thisService.stoppingLinking();
thisService.connectingData = null;
}
},
});
}
startingLinking() {
this.isFastLinking = true;
KEY_EVENT_SERVICE.addEventListener("keydown", this.handleLinkingKeydownBound);
KEY_EVENT_SERVICE.addEventListener("keyup", this.handleLinkingKeyupBound);
}
stoppingLinking() {
this.isFastLinking = false;
this.fastReroutesHistory = [];
KEY_EVENT_SERVICE.removeEventListener("keydown", this.handleLinkingKeydownBound);
KEY_EVENT_SERVICE.removeEventListener("keyup", this.handleLinkingKeyupBound);
}
handleLinkingKeydown(event) {
if (!this.handledNewRerouteKeypress &&
KEY_EVENT_SERVICE.areOnlyKeysDown(CONFIG_KEY_CREATE_WHILE_LINKING)) {
this.handledNewRerouteKeypress = true;
this.insertNewRerouteWhileLinking();
}
}
handleLinkingKeyup(event) {
if (this.handledNewRerouteKeypress &&
!KEY_EVENT_SERVICE.areOnlyKeysDown(CONFIG_KEY_CREATE_WHILE_LINKING)) {
this.handledNewRerouteKeypress = false;
}
}
getConnectingData() {
var _a, _b, _c, _d;
const oldCanvas = app.canvas;
if (oldCanvas.connecting_node &&
oldCanvas.connecting_slot != null &&
((_a = oldCanvas.connecting_pos) === null || _a === void 0 ? void 0 : _a.length)) {
return {
node: oldCanvas.connecting_node,
input: oldCanvas.connecting_input,
output: oldCanvas.connecting_output,
slot: oldCanvas.connecting_slot,
pos: [...oldCanvas.connecting_pos],
};
}
const canvas = app.canvas;
if ((_b = canvas.connecting_links) === null || _b === void 0 ? void 0 : _b.length) {
const link = canvas.connecting_links[0];
return {
node: link.node,
input: (_c = link.input) !== null && _c !== void 0 ? _c : undefined,
output: (_d = link.output) !== null && _d !== void 0 ? _d : undefined,
slot: link.slot,
pos: [...link.pos],
};
}
throw new Error("Error, handling linking keydown, but there's no link.");
}
setCanvasConnectingData(ctx) {
var _a, _b;
const oldCanvas = app.canvas;
if (oldCanvas.connecting_node &&
oldCanvas.connecting_slot != null &&
((_a = oldCanvas.connecting_pos) === null || _a === void 0 ? void 0 : _a.length)) {
oldCanvas.connecting_node = ctx.node;
oldCanvas.connecting_input = ctx.input;
oldCanvas.connecting_output = ctx.output;
oldCanvas.connecting_slot = ctx.slot;
oldCanvas.connecting_pos = ctx.pos;
}
const canvas = app.canvas;
if ((_b = canvas.connecting_links) === null || _b === void 0 ? void 0 : _b.length) {
const link = canvas.connecting_links[0];
link.node = ctx.node;
link.input = ctx.input;
link.output = ctx.output;
link.slot = ctx.slot;
link.pos = ctx.pos;
}
}
insertNewRerouteWhileLinking() {
var _a;
const canvas = app.canvas;
this.connectingData = this.getConnectingData();
if (!this.connectingData) {
throw new Error("Error, handling linking keydown, but there's no link.");
}
const data = this.connectingData;
const node = LiteGraph.createNode("Reroute (rgthree)");
const entry = {
node,
previous: { ...this.connectingData },
current: undefined,
};
this.fastReroutesHistory.push(entry);
let connectingDir = (_a = (data.input || data.output)) === null || _a === void 0 ? void 0 : _a.dir;
if (!connectingDir) {
connectingDir = data.input ? LiteGraph.LEFT : LiteGraph.RIGHT;
}
let newPos = canvas.convertEventToCanvasOffset({
clientX: Math.round(canvas.last_mouse_position[0] / 10) * 10,
clientY: Math.round(canvas.last_mouse_position[1] / 10) * 10,
});
entry.node.pos = newPos;
canvas.graph.add(entry.node);
canvas.selectNode(entry.node);
const distX = entry.node.pos[0] - data.pos[0];
const distY = entry.node.pos[1] - data.pos[1];
const layout = ["Left", "Right"];
if (distX > 0 && Math.abs(distX) > Math.abs(distY)) {
layout[0] = data.output ? "Left" : "Right";
layout[1] = LAYOUT_LABEL_OPPOSITES[layout[0]];
node.pos[0] -= node.size[0] + 10;
node.pos[1] -= Math.round(node.size[1] / 2 / 10) * 10;
}
else if (distX < 0 && Math.abs(distX) > Math.abs(distY)) {
layout[0] = data.output ? "Right" : "Left";
layout[1] = LAYOUT_LABEL_OPPOSITES[layout[0]];
node.pos[1] -= Math.round(node.size[1] / 2 / 10) * 10;
}
else if (distY < 0 && Math.abs(distY) > Math.abs(distX)) {
layout[0] = data.output ? "Bottom" : "Top";
layout[1] = LAYOUT_LABEL_OPPOSITES[layout[0]];
node.pos[0] -= Math.round(node.size[0] / 2 / 10) * 10;
}
else if (distY > 0 && Math.abs(distY) > Math.abs(distX)) {
layout[0] = data.output ? "Top" : "Bottom";
layout[1] = LAYOUT_LABEL_OPPOSITES[layout[0]];
node.pos[0] -= Math.round(node.size[0] / 2 / 10) * 10;
node.pos[1] -= node.size[1] + 10;
}
setConnectionsLayout(entry.node, layout);
if (data.output) {
data.node.connect(data.slot, entry.node, 0);
data.node = entry.node;
data.output = entry.node.outputs[0];
data.slot = 0;
data.pos = entry.node.getConnectionPos(false, 0);
data.direction =
layout[0] === "Top" ? 2 : layout[0] === "Bottom" ? 1 : layout[0] === "Left" ? 4 : 3;
}
else {
entry.node.connect(0, data.node, data.slot);
data.node = entry.node;
data.input = entry.node.inputs[0];
data.slot = 0;
data.pos = entry.node.getConnectionPos(true, 0);
data.direction =
layout[1] === "Top" ? 2 : layout[1] === "Bottom" ? 1 : layout[1] === "Left" ? 4 : 3;
}
this.setCanvasConnectingData(data);
entry.current = { ...this.connectingData };
app.graph.setDirtyCanvas(true, true);
}
handleMoveOrResizeNodeMaybeWhileDragging(node) {
const data = this.connectingData;
if (this.isFastLinking && node === (data === null || data === void 0 ? void 0 : data.node)) {
const entry = this.fastReroutesHistory[this.fastReroutesHistory.length - 1];
if (entry) {
data.pos = entry.node.getConnectionPos(!!data.input, 0);
this.setCanvasConnectingData(data);
}
}
}
handleRemovedNodeMaybeWhileDragging(node) {
const currentEntry = this.fastReroutesHistory[this.fastReroutesHistory.length - 1];
if ((currentEntry === null || currentEntry === void 0 ? void 0 : currentEntry.node) === node) {
this.setCanvasConnectingData(currentEntry.previous);
this.fastReroutesHistory.splice(this.fastReroutesHistory.length - 1, 1);
if (currentEntry.previous.node) {
app.canvas.selectNode(currentEntry.previous.node);
}
}
}
}
const SERVICE = new RerouteService();
class RerouteNode extends RgthreeBaseVirtualNode {
constructor(title = RerouteNode.title) {
super(title);
this.comfyClass = NodeTypesString.REROUTE;
this.isVirtualNode = true;
this.hideSlotLabels = true;
this.schedulePromise = null;
this.defaultConnectionsLayout = Array.from(configLayout);
this.shortcuts = {
rotate: { keys: CONFIG_KEY_ROTATE, state: false },
connection_input: { keys: CONFIG_KEY_CXN_INPUT, state: false },
connection_output: { keys: CONFIG_KEY_CXN_OUTPUT, state: false },
resize: {
keys: CONFIG_KEY_RESIZE,
state: false,
initialMousePos: [-1, -1],
initialNodeSize: [-1, -1],
initialNodePos: [-1, -1],
resizeOnSide: [-1, -1],
},
move: {
keys: CONFIG_KEY_MOVE,
state: false,
initialMousePos: [-1, -1],
initialNodePos: [-1, -1],
},
};
this.onConstructed();
}
onConstructed() {
var _a;
this.setResizable(!!((_a = this.properties["resizable"]) !== null && _a !== void 0 ? _a : configResizable));
this.size = RerouteNode.size;
this.addInput("", "*");
this.addOutput("", "*");
setTimeout(() => this.applyNodeSize(), 20);
return super.onConstructed();
}
configure(info) {
var _a, _b;
if ((_a = info.inputs) === null || _a === void 0 ? void 0 : _a.length) {
info.inputs.length = 1;
}
super.configure(info);
this.configuring = true;
this.setResizable(!!((_b = this.properties["resizable"]) !== null && _b !== void 0 ? _b : configResizable));
this.applyNodeSize();
this.configuring = false;
}
setResizable(resizable) {
this.properties["resizable"] = !!resizable;
this.resizable = this.properties["resizable"];
}
clone() {
const cloned = super.clone();
cloned.inputs[0].type = "*";
cloned.outputs[0].type = "*";
return cloned;
}
onConnectionsChange(type, _slotIndex, connected, _link_info, _ioSlot) {
if (connected && type === LiteGraph.OUTPUT) {
const types = new Set(this.outputs[0].links.map((l) => { var _a; return (_a = app.graph.links[l]) === null || _a === void 0 ? void 0 : _a.type; }).filter((t) => t && t !== "*"));
if (types.size > 1) {
const linksToDisconnect = [];
for (let i = 0; i < this.outputs[0].links.length - 1; i++) {
const linkId = this.outputs[0].links[i];
const link = app.graph.links[linkId];
linksToDisconnect.push(link);
}
for (const link of linksToDisconnect) {
const node = app.graph.getNodeById(link.target_id);
node.disconnectInput(link.target_slot);
}
}
}
this.scheduleStabilize();
}
onDrawForeground(ctx, canvas) {
var _a, _b, _c, _d;
if ((_a = this.properties) === null || _a === void 0 ? void 0 : _a["showLabel"]) {
const low_quality = ((_b = canvas === null || canvas === void 0 ? void 0 : canvas.ds) === null || _b === void 0 ? void 0 : _b.scale) && canvas.ds.scale < 0.6;
if (low_quality || this.size[0] <= 10) {
return;
}
const fontSize = Math.min(14, (this.size[1] * 0.65) | 0);
ctx.save();
ctx.fillStyle = "#888";
ctx.font = `${fontSize}px Arial`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(String(this.title && this.title !== RerouteNode.title
? this.title
: ((_d = (_c = this.outputs) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.type) || ""), this.size[0] / 2, this.size[1] / 2, this.size[0] - 30);
ctx.restore();
}
}
findInputSlot(name, returnObj = false) {
return returnObj ? this.inputs[0] : 0;
}
findOutputSlot(name, returnObj) {
return returnObj ? this.outputs[0] : 0;
}
disconnectOutput(slot, targetNode) {
return super.disconnectOutput(slot, targetNode);
}
disconnectInput(slot) {
var _a;
if (rgthree.replacingReroute != null && ((_a = this.inputs[0]) === null || _a === void 0 ? void 0 : _a.link)) {
const graph = app.graph;
const link = graph.links[this.inputs[0].link];
const node = (link === null || link === void 0 ? void 0 : link.origin_id) != null ? graph.getNodeById(link.origin_id) : null;
if (rgthree.replacingReroute !== (node === null || node === void 0 ? void 0 : node.id)) {
return false;
}
}
return super.disconnectInput(slot);
}
scheduleStabilize(ms = 64) {
if (!this.schedulePromise) {
this.schedulePromise = new Promise((resolve) => {
setTimeout(() => {
this.schedulePromise = null;
this.stabilize();
resolve();
}, ms);
});
}
return this.schedulePromise;
}
stabilize() {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
if (this.configuring) {
return;
}
let currentNode = this;
let updateNodes = [];
let input = null;
let inputType = null;
let inputNode = null;
let inputNodeOutputSlot = null;
while (currentNode) {
updateNodes.unshift(currentNode);
const linkId = currentNode.inputs[0].link;
if (linkId !== null) {
const link = app.graph.links[linkId];
const node = app.graph.getNodeById(link.origin_id);
if (!node) {
app.graph.removeLink(linkId);
currentNode = null;
break;
}
const type = node.constructor.type;
if (type === null || type === void 0 ? void 0 : type.includes("Reroute")) {
if (node === this) {
currentNode.disconnectInput(link.target_slot);
currentNode = null;
}
else {
currentNode = node;
}
}
else {
inputNode = node;
inputNodeOutputSlot = link.origin_slot;
input = (_a = node.outputs[inputNodeOutputSlot]) !== null && _a !== void 0 ? _a : null;
inputType = (_b = input === null || input === void 0 ? void 0 : input.type) !== null && _b !== void 0 ? _b : null;
break;
}
}
else {
currentNode = null;
break;
}
}
const nodes = [this];
let outputNode = null;
let outputType = null;
let outputWidgetConfig = null;
let outputWidget = null;
while (nodes.length) {
currentNode = nodes.pop();
const outputs = (currentNode.outputs ? currentNode.outputs[0].links : []) || [];
if (outputs.length) {
for (const linkId of outputs) {
const link = app.graph.links[linkId];
if (!link)
continue;
const node = app.graph.getNodeById(link.target_id);
if (!node)
continue;
const type = node.constructor.type;
if (type === null || type === void 0 ? void 0 : type.includes("Reroute")) {
nodes.push(node);
updateNodes.push(node);
}
else {
const output = (_d = (_c = node.inputs) === null || _c === void 0 ? void 0 : _c[link.target_slot]) !== null && _d !== void 0 ? _d : null;
const nodeOutType = output === null || output === void 0 ? void 0 : output.type;
if (nodeOutType == null) {
console.warn(`[rgthree] Reroute - Connected node ${node.id} does not have type information for ` +
`slot ${link.target_slot}. Skipping connection enforcement, but something is odd ` +
`with that node.`);
}
else if (inputType &&
inputType !== "*" &&
nodeOutType !== "*" &&
!isValidConnection(input, output)) {
console.warn(`[rgthree] Reroute - Disconnecting connected node's input (${node.id}.${link.target_slot}) (${node.type}) because its type (${String(nodeOutType)}) does not match the reroute type (${String(inputType)})`);
node.disconnectInput(link.target_slot);
}
else {
outputType = nodeOutType;
outputNode = node;
outputWidgetConfig = null;
outputWidget = null;
if (output === null || output === void 0 ? void 0 : output.widget) {
rgthreeApi.print("PRIMITIVE_REROUTE");
try {
const config = getWidgetConfig(output);
if (!outputWidgetConfig && config) {
outputWidgetConfig = (_e = config[1]) !== null && _e !== void 0 ? _e : {};
outputType = config[0];
if (!outputWidget) {
outputWidget = (_f = outputNode.widgets) === null || _f === void 0 ? void 0 : _f.find((w) => { var _a; return w.name === ((_a = output === null || output === void 0 ? void 0 : output.widget) === null || _a === void 0 ? void 0 : _a.name); });
}
const merged = mergeIfValid(output, [config[0], outputWidgetConfig]);
if (merged === null || merged === void 0 ? void 0 : merged.customConfig) {
outputWidgetConfig = merged.customConfig;
}
}
}
catch (e) {
console.error("[rgthree] Could not propagate widget infor for reroute; maybe ComfyUI updated?");
outputWidgetConfig = null;
outputWidget = null;
}
}
}
}
}
}
else {
}
}
const displayType = inputType || outputType || "*";
const color = LGraphCanvas.link_type_colors[displayType];
for (const node of updateNodes) {
node.outputs[0].type = inputType || "*";
node.__outputType = displayType;
node.outputs[0].name = (input === null || input === void 0 ? void 0 : input.name) || "";
node.size = node.computeSize();
(_h = (_g = node).applyNodeSize) === null || _h === void 0 ? void 0 : _h.call(_g);
for (const l of node.outputs[0].links || []) {
const link = app.graph.links[l];
if (link && color) {
link.color = color;
}
}
try {
if (outputWidgetConfig && outputWidget && outputType) {
rgthreeApi.print("PRIMITIVE_REROUTE");
node.inputs[0].widget = { name: "value" };
setWidgetConfig(node.inputs[0], [outputType !== null && outputType !== void 0 ? outputType : displayType, outputWidgetConfig]);
}
else {
setWidgetConfig(node.inputs[0], null);
}
}
catch (e) {
console.error("[rgthree] Could not set widget config for reroute; maybe ComfyUI updated?");
outputWidgetConfig = null;
outputWidget = null;
if ((_j = node.inputs[0]) === null || _j === void 0 ? void 0 : _j.widget) {
delete node.inputs[0].widget;
}
}
}
if (inputNode && inputNodeOutputSlot != null) {
const links = inputNode.outputs[inputNodeOutputSlot].links;
for (const l of links || []) {
const link = app.graph.links[l];
if (link && color) {
link.color = color;
}
}
}
(_k = inputNode === null || inputNode === void 0 ? void 0 : inputNode.onConnectionsChainChange) === null || _k === void 0 ? void 0 : _k.call(inputNode);
(_l = outputNode === null || outputNode === void 0 ? void 0 : outputNode.onConnectionsChainChange) === null || _l === void 0 ? void 0 : _l.call(outputNode);
app.graph.setDirtyCanvas(true, true);
}
setSize(size) {
const oldSize = [...this.size];
const newSize = [...size];
super.setSize(newSize);
this.properties["size"] = [...this.size];
this.stabilizeLayout(oldSize, newSize);
}
stabilizeLayout(oldSize, newSize) {
if (newSize[0] === 10 || newSize[1] === 10) {
const props = this.properties;
props["connections_layout"] = props["connections_layout"] || ["Left", "Right"];
props["connections_dir"] = props["connections_dir"] || [-1, -1];
const layout = props["connections_layout"];
const dir = props["connections_dir"];
if (oldSize[0] > 10 && newSize[0] === 10) {
dir[0] = LiteGraph.DOWN;
dir[1] = LiteGraph.UP;
if (layout[0] === "Bottom") {
layout[1] = "Top";
}
else if (layout[1] === "Top") {
layout[0] = "Bottom";
}
else {
layout[0] = "Top";
layout[1] = "Bottom";
dir[0] = LiteGraph.UP;
dir[1] = LiteGraph.DOWN;
}
this.setDirtyCanvas(true, true);
}
else if (oldSize[1] > 10 && newSize[1] === 10) {
dir[0] = LiteGraph.RIGHT;
dir[1] = LiteGraph.LEFT;
if (layout[0] === "Right") {
layout[1] = "Left";
}
else if (layout[1] === "Left") {
layout[0] = "Right";
}
else {
layout[0] = "Left";
layout[1] = "Right";
dir[0] = LiteGraph.LEFT;
dir[1] = LiteGraph.RIGHT;
}
this.setDirtyCanvas(true, true);
}
}
SERVICE.handleMoveOrResizeNodeMaybeWhileDragging(this);
}
applyNodeSize() {
this.properties["size"] = this.properties["size"] || RerouteNode.size;
this.properties["size"] = [
Number(this.properties["size"][0]),
Number(this.properties["size"][1]),
];
this.size = this.properties["size"];
app.graph.setDirtyCanvas(true, true);
}
rotate(degrees) {
const w = this.size[0];
const h = this.size[1];
this.properties["connections_layout"] =
this.properties["connections_layout"] || this.defaultConnectionsLayout;
const connections_layout = this.properties["connections_layout"];
const inputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[0]);
const outputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[1]);
if (degrees == 90 || degrees === -90) {
if (degrees === -90) {
connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex - 1) % 4) + 4) % 4];
connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex - 1) % 4) + 4) % 4];
}
else {
connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 1) % 4) + 4) % 4];
connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 1) % 4) + 4) % 4];
}
}
else if (degrees === 180) {
connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4];
connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4];
}
this.setSize([h, w]);
}
manuallyHandleMove(event) {
const shortcut = this.shortcuts.move;
if (shortcut.state) {
const diffX = Math.round((event.clientX - shortcut.initialMousePos[0]) / 10) * 10;
const diffY = Math.round((event.clientY - shortcut.initialMousePos[1]) / 10) * 10;
this.pos[0] = shortcut.initialNodePos[0] + diffX;
this.pos[1] = shortcut.initialNodePos[1] + diffY;
this.setDirtyCanvas(true, true);
SERVICE.handleMoveOrResizeNodeMaybeWhileDragging(this);
}
}
manuallyHandleResize(event) {
const shortcut = this.shortcuts.resize;
if (shortcut.state) {
let diffX = Math.round((event.clientX - shortcut.initialMousePos[0]) / 10) * 10;
let diffY = Math.round((event.clientY - shortcut.initialMousePos[1]) / 10) * 10;
diffX *= shortcut.resizeOnSide[0] === LiteGraph.LEFT ? -1 : 1;
diffY *= shortcut.resizeOnSide[1] === LiteGraph.UP ? -1 : 1;
const oldSize = [...this.size];
this.setSize([
Math.max(10, shortcut.initialNodeSize[0] + diffX),
Math.max(10, shortcut.initialNodeSize[1] + diffY),
]);
if (shortcut.resizeOnSide[0] === LiteGraph.LEFT && oldSize[0] > 10) {
this.pos[0] = shortcut.initialNodePos[0] - diffX;
}
if (shortcut.resizeOnSide[1] === LiteGraph.UP && oldSize[1] > 10) {
this.pos[1] = shortcut.initialNodePos[1] - diffY;
}
this.setDirtyCanvas(true, true);
}
}
cycleConnection(ioDir) {
var _a, _b;
const props = this.properties;
props["connections_layout"] = props["connections_layout"] || ["Left", "Right"];
const connections_layout = this.properties["connections_layout"];
const propIdx = ioDir == IoDirection.INPUT ? 0 : 1;
const oppositeIdx = propIdx ? 0 : 1;
let currentLayout = connections_layout[propIdx];
let oppositeLayout = connections_layout[oppositeIdx];
if (this.size[0] === 10 || this.size[1] === 10) {
props["connections_dir"] = props["connections_dir"] || [-1, -1];
const connections_dir = this.properties["connections_dir"];
let currentDir = connections_dir[propIdx];
const options = this.size[0] === 10
? currentLayout === "Bottom"
? [LiteGraph.DOWN, LiteGraph.RIGHT, LiteGraph.LEFT]
: [LiteGraph.UP, LiteGraph.LEFT, LiteGraph.RIGHT]
: currentLayout === "Right"
? [LiteGraph.RIGHT, LiteGraph.DOWN, LiteGraph.UP]
: [LiteGraph.LEFT, LiteGraph.UP, LiteGraph.DOWN];
let idx = options.indexOf(currentDir);
let next = (_a = options[idx + 1]) !== null && _a !== void 0 ? _a : options[0];
connections_dir[propIdx] = next;
return;
}
let next = currentLayout;
do {
let idx = LAYOUT_CLOCKWISE.indexOf(next);
next = (_b = LAYOUT_CLOCKWISE[idx + 1]) !== null && _b !== void 0 ? _b : LAYOUT_CLOCKWISE[0];
} while (next === oppositeLayout);
connections_layout[propIdx] = next;
this.setDirtyCanvas(true, true);
}
onMouseMove(event) {
if (this.shortcuts.move.state) {
const shortcut = this.shortcuts.move;
if (shortcut.initialMousePos[0] === -1) {
shortcut.initialMousePos[0] = event.clientX;
shortcut.initialMousePos[1] = event.clientY;
shortcut.initialNodePos[0] = this.pos[0];
shortcut.initialNodePos[1] = this.pos[1];
}
this.manuallyHandleMove(event);
}
else if (this.shortcuts.resize.state) {
const shortcut = this.shortcuts.resize;
if (shortcut.initialMousePos[0] === -1) {
shortcut.initialMousePos[0] = event.clientX;
shortcut.initialMousePos[1] = event.clientY;
shortcut.initialNodeSize[0] = this.size[0];
shortcut.initialNodeSize[1] = this.size[1];
shortcut.initialNodePos[0] = this.pos[0];
shortcut.initialNodePos[1] = this.pos[1];
const canvas = app.canvas;
const offset = canvas.convertEventToCanvasOffset(event);
shortcut.resizeOnSide[0] = this.pos[0] > offset[0] ? LiteGraph.LEFT : LiteGraph.RIGHT;
shortcut.resizeOnSide[1] = this.pos[1] > offset[1] ? LiteGraph.UP : LiteGraph.DOWN;
}
this.manuallyHandleResize(event);
}
}
onKeyDown(event) {
super.onKeyDown(event);
const canvas = app.canvas;
if (CONFIG_FAST_REROUTE_ENABLED) {
for (const [key, shortcut] of Object.entries(this.shortcuts)) {
if (!shortcut.state) {
const keys = KEY_EVENT_SERVICE.areOnlyKeysDown(shortcut.keys);
if (keys) {
shortcut.state = true;
if (key === "rotate") {
this.rotate(90);
}
else if (key.includes("connection")) {
this.cycleConnection(key.includes("input") ? IoDirection.INPUT : IoDirection.OUTPUT);
}
if (shortcut.initialMousePos) {
canvas.node_capturing_input = this;
}
}
}
}
}
}
onKeyUp(event) {
super.onKeyUp(event);
const canvas = app.canvas;
if (CONFIG_FAST_REROUTE_ENABLED) {
for (const [key, shortcut] of Object.entries(this.shortcuts)) {
if (shortcut.state) {
const keys = KEY_EVENT_SERVICE.areOnlyKeysDown(shortcut.keys);
if (!keys) {
shortcut.state = false;
if (shortcut.initialMousePos) {
shortcut.initialMousePos = [-1, -1];
if ((canvas.node_capturing_input = this)) {
canvas.node_capturing_input = null;
}
this.setDirtyCanvas(true, true);
}
}
}
}
}
}
onDeselected() {
var _a;
(_a = super.onDeselected) === null || _a === void 0 ? void 0 : _a.call(this);
const canvas = app.canvas;
for (const [key, shortcut] of Object.entries(this.shortcuts)) {
shortcut.state = false;
if (shortcut.initialMousePos) {
shortcut.initialMousePos = [-1, -1];
if ((canvas.node_capturing_input = this)) {
canvas.node_capturing_input = null;
}
this.setDirtyCanvas(true, true);
}
}
}
onRemoved() {
var _a;
(_a = super.onRemoved) === null || _a === void 0 ? void 0 : _a.call(this);
setTimeout(() => {
SERVICE.handleRemovedNodeMaybeWhileDragging(this);
}, 32);
}
getHelp() {
return `
<p>
Finally, a comfortable, powerful reroute node with true multi-direction and powerful
shortcuts to bring your workflow to the next level.
</p>
${!CONFIG_FAST_REROUTE_ENABLED
? `<p><i>Fast Shortcuts are currently disabled.</b>`
: `
<ul>
<li><p>
<code>${CONFIG_KEY_CREATE_WHILE_LINKING}</code> Create a new reroute node while dragging
a link, connecting it to the link in the place and continuing the link.
</p></li>
<li><p>
<code>${CONFIG_KEY_ROTATE}</code> Rotate the selected reroute node counter clockwise 90
degrees.
</p></li>
<li><p>
<code>${CONFIG_KEY_RESIZE}</code> Resize the selected reroute node from the nearest
corner by holding down and moving your mouse.
</p></li>
<li><p>
<code>${CONFIG_KEY_MOVE}</code> Move the selected reroute node by holding down and
moving your mouse.
</p></li>
<li><p>
<code>${CONFIG_KEY_CXN_INPUT}</code> Change the input layout/direction of the selected
reroute node.
</p></li>
<li><p>
<code>${CONFIG_KEY_CXN_OUTPUT}</code> Change the output layout/direction of the selected
reroute node.
</p></li>
</ul>
`}
<p><small>
To change, ${!CONFIG_FAST_REROUTE_ENABLED ? "enable" : "disable"} or configure sohrtcuts,
make a copy of
<code>/custom_nodes/rgthree-comfy/rgthree_config.json.default</code> to
<code>/custom_nodes/rgthree-comfy/rgthree_config.json</code> and configure under
<code>nodes > reroute > fast_reroute</code>.
</small></p>
`;
}
}
RerouteNode.title = NodeTypesString.REROUTE;
RerouteNode.type = NodeTypesString.REROUTE;
RerouteNode.title_mode = LiteGraph.NO_TITLE;
RerouteNode.collapsable = false;
RerouteNode.layout_slot_offset = 5;
RerouteNode.size = configDefaultSize;
addMenuItem(RerouteNode, app, {
name: (node) => { var _a; return `${((_a = node.properties) === null || _a === void 0 ? void 0 : _a["showLabel"]) ? "Hide" : "Show"} Label/Title`; },
property: "showLabel",
callback: async (node, value) => {
app.graph.setDirtyCanvas(true, true);
},
});
addMenuItem(RerouteNode, app, {
name: (node) => `${node.resizable ? "No" : "Allow"} Resizing`,
callback: (node) => {
node.setResizable(!node.resizable);
node.size[0] = Math.max(40, node.size[0]);
node.size[1] = Math.max(30, node.size[1]);
node.applyNodeSize();
},
});
addMenuItem(RerouteNode, app, {
name: "Static Width",
property: "size",
subMenuOptions: (() => {
const options = [];
for (let w = 8; w > 0; w--) {
options.push(`${w * 10}`);
}
return options;
})(),
prepareValue: (value, node) => [Number(value), node.size[1]],
callback: (node) => {
node.setResizable(false);
node.applyNodeSize();
},
});
addMenuItem(RerouteNode, app, {
name: "Static Height",
property: "size",
subMenuOptions: (() => {
const options = [];
for (let w = 8; w > 0; w--) {
options.push(`${w * 10}`);
}
return options;
})(),
prepareValue: (value, node) => [node.size[0], Number(value)],
callback: (node) => {
node.setResizable(false);
node.applyNodeSize();
},
});
addConnectionLayoutSupport(RerouteNode, app, [
["Left", "Right"],
["Left", "Top"],
["Left", "Bottom"],
["Right", "Left"],
["Right", "Top"],
["Right", "Bottom"],
["Top", "Left"],
["Top", "Right"],
["Top", "Bottom"],
["Bottom", "Left"],
["Bottom", "Right"],
["Bottom", "Top"],
], (node) => {
node.applyNodeSize();
});
addMenuItem(RerouteNode, app, {
name: "Rotate",
subMenuOptions: [
"Rotate 90° Clockwise",
"Rotate 90° Counter-Clockwise",
"Rotate 180°",
null,
"Flip Horizontally",
"Flip Vertically",
],
callback: (node_, value) => {
const node = node_;
if (value === null || value === void 0 ? void 0 : value.startsWith("Rotate 90° Clockwise")) {
node.rotate(90);
}
else if (value === null || value === void 0 ? void 0 : value.startsWith("Rotate 90° Counter-Clockwise")) {
node.rotate(-90);
}
else if (value === null || value === void 0 ? void 0 : value.startsWith("Rotate 180°")) {
node.rotate(180);
}
else {
const connections_layout = node.properties["connections_layout"];
const inputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[0]);
const outputDirIndex = LAYOUT_CLOCKWISE.indexOf(connections_layout[1]);
if (value === null || value === void 0 ? void 0 : value.startsWith("Flip Horizontally")) {
if (["Left", "Right"].includes(connections_layout[0])) {
connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4];
}
if (["Left", "Right"].includes(connections_layout[1])) {
connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4];
}
}
else if (value === null || value === void 0 ? void 0 : value.startsWith("Flip Vertically")) {
if (["Top", "Bottom"].includes(connections_layout[0])) {
connections_layout[0] = LAYOUT_CLOCKWISE[(((inputDirIndex + 2) % 4) + 4) % 4];
}
if (["Top", "Bottom"].includes(connections_layout[1])) {
connections_layout[1] = LAYOUT_CLOCKWISE[(((outputDirIndex + 2) % 4) + 4) % 4];
}
}
}
},
});
addMenuItem(RerouteNode, app, {
name: "Clone New Reroute...",
subMenuOptions: ["Before", "After"],
callback: async (node, value) => {
const clone = node.clone();
const pos = [...node.pos];
if (value === "Before") {
clone.pos = [pos[0] - 20, pos[1] - 20];
app.graph.add(clone);
await wait();
const inputLinks = getSlotLinks(node.inputs[0]);
for (const inputLink of inputLinks) {
const link = inputLink.link;
const linkedNode = app.graph.getNodeById(link.origin_id);
if (linkedNode) {
linkedNode.connect(0, clone, 0);
}
}
clone.connect(0, node, 0);
}
else {
clone.pos = [pos[0] + 20, pos[1] + 20];
app.graph.add(clone);
await wait();
const outputLinks = getSlotLinks(node.outputs[0]);
node.connect(0, clone, 0);
for (const outputLink of outputLinks) {
const link = outputLink.link;
const linkedNode = app.graph.getNodeById(link.target_id);
if (linkedNode) {
clone.connect(0, linkedNode, link.target_slot);
}
}
}
},
});
app.registerExtension({
name: "rgthree.Reroute",
registerCustomNodes() {
RerouteNode.setUp();
},
});

View File

@@ -0,0 +1,696 @@
@charset "UTF-8";
.rgthree-top-messages-container {
position: fixed;
z-index: 9999;
top: 0;
left: 0;
width: 100%;
height: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: start;
}
.rgthree-top-messages-container > div {
position: relative;
height: fit-content;
padding: 4px;
margin-top: -100px; /* re-set by JS */
opacity: 0;
transition: all 0.33s ease-in-out;
z-index: 3;
}
.rgthree-top-messages-container > div:last-child {
z-index: 2;
}
.rgthree-top-messages-container > div:not(.-show) {
z-index: 1;
}
.rgthree-top-messages-container > div.-show {
opacity: 1;
margin-top: 0px !important;
}
.rgthree-top-messages-container > div.-show {
opacity: 1;
transform: translateY(0%);
}
.rgthree-top-messages-container > div > div {
position: relative;
background: #353535;
color: #fff;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: fit-content;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.88);
padding: 6px 12px;
border-radius: 4px;
font-family: Arial, sans-serif;
font-size: 14px;
}
.rgthree-top-messages-container > div > div > span {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.rgthree-top-messages-container > div > div > span svg {
width: 20px;
height: auto;
margin-right: 8px;
}
.rgthree-top-messages-container > div > div > span svg.icon-checkmark {
fill: #2e9720;
}
.rgthree-top-messages-container [type=warn]::before,
.rgthree-top-messages-container [type=success]::before {
content: "⚠️";
display: inline-block;
flex: 0 0 auto;
font-size: 18px;
margin-right: 4px;
line-height: 1;
}
.rgthree-top-messages-container [type=success]::before {
content: "🎉";
}
.rgthree-top-messages-container a {
cursor: pointer;
text-decoration: underline;
color: #fc0;
margin-left: 4px;
display: inline-block;
line-height: 1;
}
.rgthree-top-messages-container a:hover {
color: #fc0;
text-decoration: none;
}
/* Fix node selector being crazy long b/c of array types. */
.litegraph.litesearchbox input,
.litegraph.litesearchbox select {
max-width: 250px;
}
/* There's no reason for this z-index to be so high. It layers on top of things it shouldn't,
(like pythongssss' image gallery, the properties panel, etc.) */
.comfy-multiline-input {
z-index: 1 !important;
}
.comfy-multiline-input:focus {
z-index: 2 !important;
}
.litegraph .dialog {
z-index: 3 !important; /* This is set to 1, but goes under the multi-line inputs, so bump it. */
}
:not(#fakeid) .rgthree-button-reset {
position: relative;
appearance: none;
cursor: pointer;
border: 0;
background: transparent;
color: inherit;
padding: 0;
margin: 0;
}
:not(#fakeid) .rgthree-button {
--padding-top: 7px;
--padding-bottom: 9px;
--padding-x: 16px;
position: relative;
cursor: pointer;
border: 0;
border-radius: 0.33rem;
background: rgba(0, 0, 0, 0.5);
color: white;
font-family: system-ui, sans-serif;
font-size: 1rem;
line-height: 1;
white-space: nowrap;
text-decoration: none;
margin: 0.25rem;
box-shadow: 0px 0px 2px rgb(0, 0, 0);
background: #212121;
transition: all 0.1s ease-in-out;
padding: var(--padding-top) var(--padding-x) var(--padding-bottom);
display: inline-flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
:not(#fakeid) .rgthree-button::before, :not(#fakeid) .rgthree-button::after {
content: "";
display: block;
position: absolute;
border-radius: 0.33rem;
left: 0;
top: 0;
width: 100%;
height: 100%;
box-shadow: inset 1px 1px 0px rgba(255, 255, 255, 0.12), inset -1px -1px 0px rgba(0, 0, 0, 0.75);
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.15));
mix-blend-mode: screen;
}
:not(#fakeid) .rgthree-button::after {
mix-blend-mode: multiply;
}
:not(#fakeid) .rgthree-button:hover {
background: #303030;
}
:not(#fakeid) .rgthree-button:active {
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0);
background: #121212;
padding: calc(var(--padding-top) + 1px) calc(var(--padding-x) - 1px) calc(var(--padding-bottom) - 1px) calc(var(--padding-x) + 1px);
}
:not(#fakeid) .rgthree-button:active::before, :not(#fakeid) .rgthree-button:active::after {
box-shadow: 1px 1px 0px rgba(255, 255, 255, 0.15), inset 1px 1px 0px rgba(0, 0, 0, 0.5), inset 1px 3px 5px rgba(0, 0, 0, 0.33);
}
:not(#fakeid) .rgthree-button.-blue {
background: #346599 !important;
}
:not(#fakeid) .rgthree-button.-blue:hover {
background: #3b77b8 !important;
}
:not(#fakeid) .rgthree-button.-blue:active {
background: #1d5086 !important;
}
:not(#fakeid) .rgthree-button.-green {
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.15)), #14580b;
}
:not(#fakeid) .rgthree-button.-green:hover {
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.15)), #1a6d0f;
}
:not(#fakeid) .rgthree-button.-green:active {
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.06)), #0f3f09;
}
:not(#fakeid) .rgthree-button[disabled] {
box-shadow: none;
background: #666 !important;
color: #aaa;
pointer-events: none;
}
:not(#fakeid) .rgthree-button[disabled]::before, :not(#fakeid) .rgthree-button[disabled]::after {
display: none;
}
:not(#fakeid) .rgthree-comfybar-top-button-group {
font-size: 0;
flex: 1 1 auto;
display: flex;
align-items: stretch;
}
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button {
margin: 0;
flex: 1 1;
height: 36px;
padding: 0 12px;
border-radius: 0;
background: var(--p-button-secondary-background);
color: var(--p-button-secondary-color);
}
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button.-primary {
background: var(--p-button-primary-background);
color: var(--p-button-primary-color);
}
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button::before, :not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button::after {
border-radius: 0;
}
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button svg {
fill: currentColor;
width: 28px;
height: 28px;
}
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button:first-of-type,
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button:first-of-type::before,
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button:first-of-type::after {
border-top-left-radius: 0.33rem;
border-bottom-left-radius: 0.33rem;
}
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button:last-of-type,
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button:last-of-type::before,
:not(#fakeid) .rgthree-comfybar-top-button-group .rgthree-comfybar-top-button:last-of-type::after {
border-top-right-radius: 0.33rem;
border-bottom-right-radius: 0.33rem;
}
.rgthree-dialog {
outline: 0;
border: 0;
border-radius: 6px;
background: #414141;
color: #fff;
box-shadow: inset 1px 1px 0px rgba(255, 255, 255, 0.05), inset -1px -1px 0px rgba(0, 0, 0, 0.5), 2px 2px 20px rgb(0, 0, 0);
max-width: 800px;
box-sizing: border-box;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
font-size: 1rem;
padding: 0;
max-height: calc(100% - 32px);
}
.rgthree-dialog *, .rgthree-dialog *::before, .rgthree-dialog *::after {
box-sizing: inherit;
}
.rgthree-dialog-container > * {
padding: 8px 16px;
}
.rgthree-dialog-container > *:first-child {
padding-top: 16px;
}
.rgthree-dialog-container > *:last-child {
padding-bottom: 16px;
}
.rgthree-dialog.-iconed::after {
content: "";
font-size: 276px;
position: absolute;
right: 0px;
bottom: 0px;
opacity: 0.15;
display: block;
width: 237px;
overflow: hidden;
height: 186px;
line-height: 1;
pointer-events: none;
z-index: -1;
}
.rgthree-dialog.-iconed.-help::after {
content: "🛟";
}
.rgthree-dialog.-iconed.-settings::after {
content: "⚙️";
}
@media (max-width: 832px) {
.rgthree-dialog {
max-width: calc(100% - 32px);
}
}
.rgthree-dialog-container-title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: start;
}
.rgthree-dialog-container-title > svg:first-child {
width: 36px;
height: 36px;
margin-right: 16px;
}
.rgthree-dialog-container-title h2 {
font-size: 1.375rem;
margin: 0;
font-weight: bold;
}
.rgthree-dialog-container-title h2 small {
font-size: 0.8125rem;
font-weight: normal;
opacity: 0.75;
}
.rgthree-dialog-container-content {
overflow: auto;
max-height: calc(100vh - 200px); /* Arbitrary height to copensate for margin, title, and footer.*/
}
.rgthree-dialog-container-content p {
font-size: 0.8125rem;
margin-top: 0;
}
.rgthree-dialog-container-content ul li p {
margin-bottom: 4px;
}
.rgthree-dialog-container-content ul li p + p {
margin-top: 0.5em;
}
.rgthree-dialog-container-content ul li ul {
margin-top: 0.5em;
margin-bottom: 1em;
}
.rgthree-dialog-container-content p code {
display: inline-block;
padding: 2px 4px;
margin: 0px 2px;
border: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 3px;
background: rgba(255, 255, 255, 0.1);
}
.rgthree-dialog-container-footer {
display: flex;
align-items: center;
justify-content: center;
}
body.rgthree-dialog-open > *:not(.rgthree-dialog):not(.rgthree-top-messages-container) {
filter: blur(5px);
}
.rgthree-menu {
list-style: none;
padding: 0;
margin: 0;
position: fixed;
z-index: 999999;
pointer-events: none;
opacity: 0;
transition: opacity 0.08s ease-in-out;
color: #dde;
background-color: #111;
font-size: 12px;
box-shadow: 0 0 10px black !important;
}
.rgthree-menu > li {
position: relative;
padding: 4px 6px;
z-index: 9999;
white-space: nowrap;
}
.rgthree-menu > li[role=button] {
background-color: var(--comfy-menu-bg) !important;
color: var(--input-text);
cursor: pointer;
}
.rgthree-menu > li[role=button]:hover {
filter: brightness(155%);
}
.rgthree-menu[state^=measuring] {
display: block;
opacity: 0;
}
.rgthree-menu[state=open] {
display: block;
opacity: 1;
pointer-events: all;
}
.rgthree-top-menu {
box-sizing: border-box;
white-space: nowrap;
background: var(--content-bg);
color: var(--content-fg);
display: flex;
flex-direction: column;
list-style: none;
padding: 0;
margin: 0;
}
.rgthree-top-menu * {
box-sizing: inherit;
}
.rgthree-top-menu > li:not(#fakeid) {
list-style: none;
padding: 0;
margin: 0;
position: relative;
z-index: 2;
}
.rgthree-top-menu > li:not(#fakeid) > button {
cursor: pointer;
padding: 8px 12px 8px 8px;
width: 100%;
text-align: start;
display: flex;
flex-direction: row;
align-items: center;
justify-content: start;
}
.rgthree-top-menu > li:not(#fakeid) > button:hover {
background-color: var(--comfy-input-bg);
}
.rgthree-top-menu > li:not(#fakeid) > button svg {
height: 16px;
width: auto;
margin-inline-end: 0.6em;
}
.rgthree-top-menu > li:not(#fakeid) > button svg.github-star {
fill: rgb(227, 179, 65);
}
.rgthree-top-menu > li:not(#fakeid).rgthree-message {
min-height: 32px;
}
.rgthree-top-menu > li:not(#fakeid).rgthree-message > span {
padding: 8px 12px;
display: block;
width: 100%;
text-align: center;
font-style: italic;
font-size: 12px;
}
.rgthree-top-menu.-modal::after {
content: "";
display: block;
position: fixed;
z-index: 1;
inset: 0;
background: rgba(0, 0, 0, 0.0666666667);
}
body.rgthree-modal-menu-open > *:not(.rgthree-menu):not(.rgthree-top-messages-container) {
filter: blur(2px);
}
.rgthree-dialog.-settings {
width: 100%;
}
.rgthree-dialog.-settings fieldset {
border: 1px solid rgba(255, 255, 255, 0.25);
padding: 0 12px 8px;
margin-bottom: 16px;
}
.rgthree-dialog.-settings fieldset > legend {
margin-left: 8px;
padding: 0 8px;
opacity: 0.5;
}
.rgthree-dialog.-settings .formrow {
display: flex;
flex-direction: column;
}
.rgthree-dialog.-settings .formrow + .formrow {
border-top: 1px solid rgba(255, 255, 255, 0.25);
}
.rgthree-dialog.-settings .fieldrow {
display: flex;
flex-direction: row;
}
.rgthree-dialog.-settings .fieldrow > label {
flex: 1 1 auto;
user-select: none;
padding: 8px 12px 12px;
}
.rgthree-dialog.-settings .fieldrow > label span {
font-weight: bold;
}
.rgthree-dialog.-settings .fieldrow > label small {
display: block;
margin-top: 4px;
font-size: 0.6875rem;
opacity: 0.75;
padding-left: 16px;
}
.rgthree-dialog.-settings .fieldrow ~ .fieldrow {
font-size: 0.9rem;
border-top: 1px dotted rgba(255, 255, 255, 0.25);
}
.rgthree-dialog.-settings .fieldrow ~ .fieldrow label {
padding-left: 28px;
}
.rgthree-dialog.-settings .fieldrow:first-child:not(.-checked) ~ .fieldrow {
display: none;
}
.rgthree-dialog.-settings .fieldrow:hover {
background: rgba(255, 255, 255, 0.1);
}
.rgthree-dialog.-settings .fieldrow ~ .fieldrow span {
font-weight: normal;
}
.rgthree-dialog.-settings .fieldrow > .fieldrow-value {
display: flex;
align-items: center;
justify-content: end;
flex: 0 0 auto;
width: 50%;
max-width: 230px;
}
.rgthree-dialog.-settings .fieldrow.-type-boolean > .fieldrow-value {
max-width: 64px;
}
.rgthree-dialog.-settings .fieldrow.-type-number input {
width: 48px;
text-align: right;
}
.rgthree-dialog.-settings .fieldrow input[type=checkbox] {
width: 24px;
height: 24px;
cursor: pointer;
}
.rgthree-dialog.-settings .fieldrow fieldset.rgthree-checklist-group {
padding: 0;
border: 0;
margin: 0;
}
.rgthree-dialog.-settings .fieldrow fieldset.rgthree-checklist-group > span.rgthree-checklist-item {
display: inline-block;
white-space: nowrap;
padding-right: 6px;
vertical-align: middle;
}
.rgthree-dialog.-settings .fieldrow fieldset.rgthree-checklist-group > span.rgthree-checklist-item input[type=checkbox] {
width: 16px;
height: 16px;
}
.rgthree-dialog.-settings .fieldrow fieldset.rgthree-checklist-group > span.rgthree-checklist-item label {
padding-left: 4px;
text-align: left;
cursor: pointer;
}
.rgthree-comfyui-settings-row div {
display: flex;
flex-direction: row;
align-items: center;
justify-content: end;
}
.rgthree-comfyui-settings-row div svg {
width: 36px;
height: 36px;
margin-right: 16px;
}
.litegraph.litecontextmenu .litemenu-title .rgthree-contextmenu-title-rgthree-comfy,
.litegraph.litecontextmenu .litemenu-entry.rgthree-contextmenu-item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: start;
}
.litegraph.litecontextmenu .litemenu-title .rgthree-contextmenu-title-rgthree-comfy svg,
.litegraph.litecontextmenu .litemenu-entry.rgthree-contextmenu-item svg {
fill: currentColor;
width: auto;
height: 16px;
margin-right: 6px;
}
.litegraph.litecontextmenu .litemenu-entry.rgthree-contextmenu-item svg.github-star {
fill: rgb(227, 179, 65);
}
.litegraph.litecontextmenu .litemenu-title .rgthree-contextmenu-title-rgthree-comfy,
.litegraph.litecontextmenu .litemenu-entry.rgthree-contextmenu-label {
color: #dde;
background-color: #212121 !important;
margin: 0;
padding: 2px;
cursor: default;
opacity: 1;
padding: 4px;
font-weight: bold;
}
.litegraph.litecontextmenu .litemenu-title .rgthree-contextmenu-title-rgthree-comfy {
font-size: 1.1em;
color: #fff;
background-color: #090909 !important;
justify-content: center;
padding: 4px 8px;
}
rgthree-progress-bar {
display: block;
position: relative;
z-index: 999;
top: 0;
left: 0;
height: 14px;
font-size: 10px;
width: 100%;
overflow: hidden;
box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.25);
box-shadow: inset 0px -1px 0px rgba(0, 0, 0, 0.25), 0px 1px 0px rgba(255, 255, 255, 0.125);
}
* ~ rgthree-progress-bar,
.comfyui-body-bottom rgthree-progress-bar {
box-shadow: 0px -1px 0px rgb(0, 0, 0), inset 0px 1px 0px rgba(255, 255, 255, 0.15), inset 0px -1px 0px rgba(0, 0, 0, 0.25), 0px 1px 0px rgba(255, 255, 255, 0.125);
}
body:not([style*=grid]):not([class*=grid]) rgthree-progress-bar {
position: fixed;
top: 0px;
bottom: auto;
}
body:not([style*=grid]):not([class*=grid]) rgthree-progress-bar.rgthree-pos-bottom {
top: auto;
bottom: 0px;
}
.rgthree-debug-keydowns {
display: block;
position: fixed;
z-index: 1050;
top: 3px;
right: 8px;
font-size: 10px;
color: #fff;
font-family: sans-serif;
pointer-events: none;
}
.rgthree-comfy-about-badge-logo {
width: 20px;
height: 20px;
background: url(/rgthree/logo.svg?bg=transparent&fg=%2393c5fd);
background-size: 100% 100%;
}

View File

@@ -0,0 +1,747 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { SERVICE as CONFIG_SERVICE } from "./services/config_service.js";
import { SERVICE as BOOKMARKS_SERVICE } from "./services/bookmarks_services.js";
import { SERVICE as KEY_EVENT_SERVICE } from "./services/key_events_services.js";
import { WorkflowLinkFixer } from "../../rgthree/common/link_fixer.js";
import { injectCss, wait } from "../../rgthree/common/shared_utils.js";
import { replaceNode, waitForCanvas, waitForGraph } from "./utils.js";
import { NodeTypesString, addRgthree, getNodeTypeStrings } from "./constants.js";
import { RgthreeProgressBar } from "../../rgthree/common/progress_bar.js";
import { RgthreeConfigDialog } from "./config.js";
import { iconGear, iconNode, iconReplace, iconStarFilled, logoRgthree, } from "../../rgthree/common/media/svgs.js";
import { createElement, queryAll, query } from "../../rgthree/common/utils_dom.js";
export var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["IMPORTANT"] = 1] = "IMPORTANT";
LogLevel[LogLevel["ERROR"] = 2] = "ERROR";
LogLevel[LogLevel["WARN"] = 3] = "WARN";
LogLevel[LogLevel["INFO"] = 4] = "INFO";
LogLevel[LogLevel["DEBUG"] = 5] = "DEBUG";
LogLevel[LogLevel["DEV"] = 6] = "DEV";
})(LogLevel || (LogLevel = {}));
const LogLevelKeyToLogLevel = {
IMPORTANT: LogLevel.IMPORTANT,
ERROR: LogLevel.ERROR,
WARN: LogLevel.WARN,
INFO: LogLevel.INFO,
DEBUG: LogLevel.DEBUG,
DEV: LogLevel.DEV,
};
const LogLevelToMethod = {
[LogLevel.IMPORTANT]: "log",
[LogLevel.ERROR]: "error",
[LogLevel.WARN]: "warn",
[LogLevel.INFO]: "info",
[LogLevel.DEBUG]: "log",
[LogLevel.DEV]: "log",
};
const LogLevelToCSS = {
[LogLevel.IMPORTANT]: "font-weight: bold; color: blue;",
[LogLevel.ERROR]: "",
[LogLevel.WARN]: "",
[LogLevel.INFO]: "font-style: italic; color: blue;",
[LogLevel.DEBUG]: "font-style: italic; color: #444;",
[LogLevel.DEV]: "color: #004b68;",
};
let GLOBAL_LOG_LEVEL = LogLevel.ERROR;
const apiURL = api.apiURL;
api.apiURL = function (route) {
if (route.includes("rgthree/")) {
return (this.api_base + "/" + route).replace(/\/\//g, "/");
}
return apiURL.apply(this, arguments);
};
const INVOKE_EXTENSIONS_BLOCKLIST = [
{
name: "Comfy.WidgetInputs",
reason: "Major conflict with rgthree-comfy nodes' inputs causing instability and " +
"repeated link disconnections.",
},
{
name: "efficiency.widgethider",
reason: "Overrides value getter before widget getter is prepared. Can be lifted if/when " +
"https://github.com/jags111/efficiency-nodes-comfyui/pull/203 is pulled.",
},
];
class Logger {
log(level, message, ...args) {
var _a;
const [n, v] = this.logParts(level, message, ...args);
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
}
logParts(level, message, ...args) {
if (level <= GLOBAL_LOG_LEVEL) {
const css = LogLevelToCSS[level] || "";
if (level === LogLevel.DEV) {
message = `🔧 ${message}`;
}
return [LogLevelToMethod[level], [`%c${message}`, css, ...args]];
}
return ["none", []];
}
}
class LogSession {
constructor(name) {
this.name = name;
this.logger = new Logger();
this.logsCache = {};
}
logParts(level, message, ...args) {
message = `${this.name || ""}${message ? " " + message : ""}`;
return this.logger.logParts(level, message, ...args);
}
logPartsOnceForTime(level, time, message, ...args) {
message = `${this.name || ""}${message ? " " + message : ""}`;
const cacheKey = `${level}:${message}`;
const cacheEntry = this.logsCache[cacheKey];
const now = +new Date();
if (cacheEntry && cacheEntry.lastShownTime + time > now) {
return ["none", []];
}
const parts = this.logger.logParts(level, message, ...args);
if (console[parts[0]]) {
this.logsCache[cacheKey] = this.logsCache[cacheKey] || {};
this.logsCache[cacheKey].lastShownTime = now;
}
return parts;
}
debugParts(message, ...args) {
return this.logParts(LogLevel.DEBUG, message, ...args);
}
infoParts(message, ...args) {
return this.logParts(LogLevel.INFO, message, ...args);
}
warnParts(message, ...args) {
return this.logParts(LogLevel.WARN, message, ...args);
}
errorParts(message, ...args) {
return this.logParts(LogLevel.ERROR, message, ...args);
}
newSession(name) {
return new LogSession(`${this.name}${name}`);
}
}
class Rgthree extends EventTarget {
constructor() {
var _a, _b, _c, _d;
super();
this.api = api;
this.settingsDialog = null;
this.progressBarEl = null;
this.queueNodeIds = null;
this.version = CONFIG_SERVICE.getConfigValue("version");
this.logger = new LogSession("[rgthree]");
this.monitorBadLinksAlerted = false;
this.monitorLinkTimeout = null;
this.processingQueue = false;
this.loadingApiJson = null;
this.replacingReroute = null;
this.processingMouseDown = false;
this.processingMouseUp = false;
this.processingMouseMove = false;
this.lastCanvasMouseEvent = null;
this.canvasCurrentlyCopyingToClipboard = false;
this.canvasCurrentlyCopyingToClipboardWithMultipleNodes = false;
this.canvasCurrentlyPastingFromClipboard = false;
this.canvasCurrentlyPastingFromClipboardWithMultipleNodes = false;
this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = null;
this.isMac = !!(((_a = navigator.platform) === null || _a === void 0 ? void 0 : _a.toLocaleUpperCase().startsWith("MAC")) ||
((_c = (_b = navigator.userAgentData) === null || _b === void 0 ? void 0 : _b.platform) === null || _c === void 0 ? void 0 : _c.toLocaleUpperCase().startsWith("MAC")));
const logLevel = (_d = LogLevelKeyToLogLevel[CONFIG_SERVICE.getConfigValue("log_level")]) !== null && _d !== void 0 ? _d : GLOBAL_LOG_LEVEL;
this.setLogLevel(logLevel);
this.initializeGraphAndCanvasHooks();
this.initializeComfyUIHooks();
this.initializeContextMenu();
this.rgthreeCssPromise = injectCss("extensions/rgthree-comfy/rgthree.css");
this.initializeProgressBar();
CONFIG_SERVICE.addEventListener("config-change", ((e) => {
var _a, _b;
if ((_b = (_a = e.detail) === null || _a === void 0 ? void 0 : _a.key) === null || _b === void 0 ? void 0 : _b.includes("features.progress_bar")) {
this.initializeProgressBar();
}
}));
if (CONFIG_SERVICE.getConfigValue("debug.keys_down.enabled")) {
const elDebugKeydowns = createElement("div.rgthree-debug-keydowns", {
parent: document.body,
});
const updateDebugKeyDown = () => {
elDebugKeydowns.innerText = Object.keys(KEY_EVENT_SERVICE.downKeys).join(" ");
};
KEY_EVENT_SERVICE.addEventListener("keydown", updateDebugKeyDown);
KEY_EVENT_SERVICE.addEventListener("keyup", updateDebugKeyDown);
}
}
async initializeProgressBar() {
var _a;
if (CONFIG_SERVICE.getConfigValue("features.progress_bar.enabled")) {
await this.rgthreeCssPromise;
if (!this.progressBarEl) {
this.progressBarEl = RgthreeProgressBar.create();
this.progressBarEl.setAttribute("title", "Progress Bar by rgthree. right-click for rgthree menu.");
this.progressBarEl.addEventListener("contextmenu", async (e) => {
e.stopPropagation();
e.preventDefault();
});
this.progressBarEl.addEventListener("pointerdown", async (e) => {
var _a;
LiteGraph.closeAllContextMenus();
if (e.button == 2) {
const canvas = await waitForCanvas();
new LiteGraph.ContextMenu(this.getRgthreeIContextMenuValues(), {
title: `<div class="rgthree-contextmenu-item rgthree-contextmenu-title-rgthree-comfy">${logoRgthree} rgthree-comfy</div>`,
left: e.clientX,
top: 5,
});
return;
}
if (e.button == 0) {
const nodeId = (_a = this.progressBarEl) === null || _a === void 0 ? void 0 : _a.currentNodeId;
if (nodeId) {
const [canvas, graph] = await Promise.all([waitForCanvas(), waitForGraph()]);
const node = graph.getNodeById(Number(nodeId));
if (node) {
canvas.centerOnNode(node);
e.stopPropagation();
e.preventDefault();
}
}
return;
}
});
}
const isUpdatedComfyBodyClasses = !!query(".comfyui-body-top");
const position = CONFIG_SERVICE.getConfigValue("features.progress_bar.position");
this.progressBarEl.classList.toggle("rgthree-pos-bottom", position === "bottom");
if (isUpdatedComfyBodyClasses) {
if (position === "bottom") {
query(".comfyui-body-bottom").appendChild(this.progressBarEl);
}
else {
query(".comfyui-body-top").appendChild(this.progressBarEl);
}
}
else {
document.body.appendChild(this.progressBarEl);
}
const height = CONFIG_SERVICE.getConfigValue("features.progress_bar.height") || 14;
this.progressBarEl.style.height = `${height}px`;
const fontSize = Math.max(10, Number(height) - 10);
this.progressBarEl.style.fontSize = `${fontSize}px`;
this.progressBarEl.style.fontWeight = fontSize <= 12 ? "bold" : "normal";
}
else {
(_a = this.progressBarEl) === null || _a === void 0 ? void 0 : _a.remove();
}
}
async initializeGraphAndCanvasHooks() {
const rgthree = this;
const graphSerialize = LGraph.prototype.serialize;
LGraph.prototype.serialize = function () {
const response = graphSerialize.apply(this, [...arguments]);
rgthree.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = response;
return response;
};
const processMouseDown = LGraphCanvas.prototype.processMouseDown;
LGraphCanvas.prototype.processMouseDown = function (e) {
rgthree.processingMouseDown = true;
const returnVal = processMouseDown.apply(this, [...arguments]);
rgthree.dispatchCustomEvent("on-process-mouse-down", { originalEvent: e });
rgthree.processingMouseDown = false;
return returnVal;
};
const adjustMouseEvent = LGraphCanvas.prototype.adjustMouseEvent;
LGraphCanvas.prototype.adjustMouseEvent = function (e) {
adjustMouseEvent.apply(this, [...arguments]);
rgthree.lastCanvasMouseEvent = e;
};
const copyToClipboard = LGraphCanvas.prototype.copyToClipboard;
LGraphCanvas.prototype.copyToClipboard = function (items) {
rgthree.canvasCurrentlyCopyingToClipboard = true;
rgthree.canvasCurrentlyCopyingToClipboardWithMultipleNodes =
Object.values(items || this.selected_nodes || []).length > 1;
const value = copyToClipboard.apply(this, [...arguments]);
rgthree.canvasCurrentlyCopyingToClipboard = false;
rgthree.canvasCurrentlyCopyingToClipboardWithMultipleNodes = false;
return value;
};
const pasteFromClipboard = LGraphCanvas.prototype.pasteFromClipboard;
LGraphCanvas.prototype.pasteFromClipboard = function (...args) {
rgthree.canvasCurrentlyPastingFromClipboard = true;
pasteFromClipboard.apply(this, [...arguments]);
rgthree.canvasCurrentlyPastingFromClipboard = false;
};
const onGroupAdd = LGraphCanvas.onGroupAdd;
LGraphCanvas.onGroupAdd = function (...args) {
const graph = app.canvas.getCurrentGraph();
onGroupAdd.apply(this, [...args]);
LGraphCanvas.onShowPropertyEditor({}, null, null, null, graph._groups[graph._groups.length - 1]);
};
}
async invokeExtensionsAsync(method, ...args) {
var _a;
const comfyapp = app;
if (CONFIG_SERVICE.getConfigValue("features.invoke_extensions_async.node_created") === false) {
const [m, a] = this.logParts(LogLevel.INFO, `Skipping invokeExtensionsAsync for applicable rgthree-comfy nodes`);
(_a = console[m]) === null || _a === void 0 ? void 0 : _a.call(console, ...a);
return Promise.resolve();
}
return await Promise.all(comfyapp.extensions.map(async (ext) => {
var _a, _b;
if (ext === null || ext === void 0 ? void 0 : ext[method]) {
try {
const blocked = INVOKE_EXTENSIONS_BLOCKLIST.find((block) => ext.name.toLowerCase().startsWith(block.name.toLowerCase()));
if (blocked) {
const [n, v] = this.logger.logPartsOnceForTime(LogLevel.WARN, 5000, `Blocked extension '${ext.name}' method '${method}' for rgthree-nodes because: ${blocked.reason}`);
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
return Promise.resolve();
}
return await ext[method](...args, comfyapp);
}
catch (error) {
const [n, v] = this.logParts(LogLevel.ERROR, `Error calling extension '${ext.name}' method '${method}' for rgthree-node.`, { error }, { extension: ext }, { args });
(_b = console[n]) === null || _b === void 0 ? void 0 : _b.call(console, ...v);
}
}
}));
}
dispatchCustomEvent(event, detail) {
if (detail != null) {
return this.dispatchEvent(new CustomEvent(event, { detail }));
}
return this.dispatchEvent(new CustomEvent(event));
}
async initializeContextMenu() {
const that = this;
setTimeout(async () => {
const getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args) {
let existingOptions = getCanvasMenuOptions.apply(this, [...args]);
const options = [];
options.push(null);
options.push(null);
options.push(null);
options.push({
content: logoRgthree + `rgthree-comfy`,
className: "rgthree-contextmenu-item rgthree-contextmenu-main-item-rgthree-comfy",
submenu: {
options: that.getRgthreeIContextMenuValues(),
},
});
options.push(null);
options.push(null);
let idx = null;
idx = idx || existingOptions.findIndex((o) => { var _a, _b; return (_b = (_a = o === null || o === void 0 ? void 0 : o.content) === null || _a === void 0 ? void 0 : _a.startsWith) === null || _b === void 0 ? void 0 : _b.call(_a, "Queue Group"); }) + 1;
idx =
idx || existingOptions.findIndex((o) => { var _a, _b; return (_b = (_a = o === null || o === void 0 ? void 0 : o.content) === null || _a === void 0 ? void 0 : _a.startsWith) === null || _b === void 0 ? void 0 : _b.call(_a, "Queue Selected"); }) + 1;
idx = idx || existingOptions.findIndex((o) => { var _a, _b; return (_b = (_a = o === null || o === void 0 ? void 0 : o.content) === null || _a === void 0 ? void 0 : _a.startsWith) === null || _b === void 0 ? void 0 : _b.call(_a, "Convert to Group"); });
idx = idx || existingOptions.findIndex((o) => { var _a, _b; return (_b = (_a = o === null || o === void 0 ? void 0 : o.content) === null || _a === void 0 ? void 0 : _a.startsWith) === null || _b === void 0 ? void 0 : _b.call(_a, "Arrange ("); });
idx = idx || existingOptions.findIndex((o) => !o) + 1;
idx = idx || 3;
existingOptions.splice(idx, 0, ...options);
for (let i = existingOptions.length; i > 0; i--) {
if (existingOptions[i] === null && existingOptions[i + 1] === null) {
existingOptions.splice(i, 1);
}
}
return existingOptions;
};
}, 1016);
}
getRgthreeIContextMenuValues() {
const [canvas, graph] = [app.canvas, app.canvas.getCurrentGraph()];
const selectedNodes = Object.values(canvas.selected_nodes || {});
let rerouteNodes = [];
if (selectedNodes.length) {
rerouteNodes = selectedNodes.filter((n) => n.type === "Reroute");
}
else {
rerouteNodes = graph._nodes.filter((n) => n.type == "Reroute");
}
const rerouteLabel = selectedNodes.length ? "selected" : "all";
const showBookmarks = CONFIG_SERVICE.getFeatureValue("menu_bookmarks.enabled");
const bookmarkMenuItems = showBookmarks ? getBookmarks() : [];
return [
{
content: "Nodes",
disabled: true,
className: "rgthree-contextmenu-item rgthree-contextmenu-label",
},
{
content: iconNode + "All",
className: "rgthree-contextmenu-item",
has_submenu: true,
submenu: {
options: getNodeTypeStrings(),
callback: (value, options, event) => {
const node = LiteGraph.createNode(addRgthree(value));
if (node) {
node.pos = [
rgthree.lastCanvasMouseEvent.canvasX,
rgthree.lastCanvasMouseEvent.canvasY,
];
canvas.graph.add(node);
canvas.selectNode(node);
graph.setDirtyCanvas(true, true);
}
},
extra: { rgthree_doNotNest: true },
},
},
{
content: "Actions",
disabled: true,
className: "rgthree-contextmenu-item rgthree-contextmenu-label",
},
{
content: iconGear + "Settings (rgthree-comfy)",
disabled: !!this.settingsDialog,
className: "rgthree-contextmenu-item",
callback: (...args) => {
this.settingsDialog = new RgthreeConfigDialog().show();
this.settingsDialog.addEventListener("close", (e) => {
this.settingsDialog = null;
});
},
},
{
content: iconReplace + ` Convert ${rerouteLabel} Reroutes`,
disabled: !rerouteNodes.length,
className: "rgthree-contextmenu-item",
callback: (...args) => {
const msg = `Convert ${rerouteLabel} ComfyUI Reroutes to Reroute (rgthree) nodes? \n` +
`(First save a copy of your workflow & check reroute connections afterwards)`;
if (!window.confirm(msg)) {
return;
}
(async () => {
for (const node of [...rerouteNodes]) {
if (node.type == "Reroute") {
this.replacingReroute = node.id;
await replaceNode(node, NodeTypesString.REROUTE);
this.replacingReroute = null;
}
}
})();
},
},
...bookmarkMenuItems,
{
content: "More...",
disabled: true,
className: "rgthree-contextmenu-item rgthree-contextmenu-label",
},
{
content: iconStarFilled + "Star on Github",
className: "rgthree-contextmenu-item rgthree-contextmenu-github",
callback: (...args) => {
window.open("https://github.com/rgthree/rgthree-comfy", "_blank");
},
},
];
}
async queueOutputNodes(nodes) {
var _a;
const nodeIds = nodes.map((n) => n.id);
try {
this.queueNodeIds = nodeIds;
await app.queuePrompt(0);
}
catch (e) {
const [n, v] = this.logParts(LogLevel.ERROR, `There was an error queuing nodes ${nodeIds}`, e);
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
}
finally {
this.queueNodeIds = null;
}
}
recursiveAddNodes(nodeId, oldOutput, newOutput) {
let currentId = nodeId;
let currentNode = oldOutput[currentId];
if (newOutput[currentId] == null) {
newOutput[currentId] = currentNode;
for (const inputValue of Object.values(currentNode.inputs || [])) {
if (Array.isArray(inputValue)) {
this.recursiveAddNodes(inputValue[0], oldOutput, newOutput);
}
}
}
return newOutput;
}
initializeComfyUIHooks() {
const rgthree = this;
const queuePrompt = app.queuePrompt;
app.queuePrompt = async function (number, batchCount) {
rgthree.processingQueue = true;
rgthree.dispatchCustomEvent("queue");
try {
return await queuePrompt.apply(app, [...arguments]);
}
finally {
rgthree.processingQueue = false;
rgthree.dispatchCustomEvent("queue-end");
}
};
const loadApiJson = app.loadApiJson;
app.loadApiJson = async function (apiData, fileName) {
rgthree.loadingApiJson = apiData;
try {
loadApiJson.apply(app, [...arguments]);
}
finally {
rgthree.loadingApiJson = null;
}
};
const graphToPrompt = app.graphToPrompt;
app.graphToPrompt = async function () {
rgthree.dispatchCustomEvent("graph-to-prompt");
let promise = graphToPrompt.apply(app, [...arguments]);
await promise;
rgthree.dispatchCustomEvent("graph-to-prompt-end");
return promise;
};
const apiQueuePrompt = api.queuePrompt;
api.queuePrompt = async function (index, prompt, ...args) {
var _a;
if (((_a = rgthree.queueNodeIds) === null || _a === void 0 ? void 0 : _a.length) && prompt.output) {
const oldOutput = prompt.output;
let newOutput = {};
for (const queueNodeId of rgthree.queueNodeIds) {
rgthree.recursiveAddNodes(String(queueNodeId), oldOutput, newOutput);
}
prompt.output = newOutput;
}
rgthree.dispatchCustomEvent("comfy-api-queue-prompt-before", {
workflow: prompt.workflow,
output: prompt.output,
});
const response = apiQueuePrompt.apply(app, [index, prompt, ...args]);
rgthree.dispatchCustomEvent("comfy-api-queue-prompt-end");
return response;
};
const clean = app.clean;
app.clean = function () {
rgthree.clearAllMessages();
clean && clean.apply(app, [...arguments]);
};
const loadGraphData = app.loadGraphData;
app.loadGraphData = function (graph) {
if (rgthree.monitorLinkTimeout) {
clearTimeout(rgthree.monitorLinkTimeout);
rgthree.monitorLinkTimeout = null;
}
rgthree.clearAllMessages();
let graphCopy;
try {
graphCopy = JSON.parse(JSON.stringify(graph));
}
catch (e) {
graphCopy = null;
}
setTimeout(() => {
var _a, _b, _c;
const wasLoadingAborted = (_b = (_a = document
.querySelector(".comfy-modal-content")) === null || _a === void 0 ? void 0 : _a.textContent) === null || _b === void 0 ? void 0 : _b.includes("Loading aborted due");
const graphToUse = wasLoadingAborted ? graphCopy || graph : app.graph;
const fixer = WorkflowLinkFixer.create(graphToUse);
const fixBadLinksResult = fixer.check();
if (fixBadLinksResult.hasBadLinks) {
const [n, v] = rgthree.logParts(LogLevel.WARN, `The workflow you've loaded has corrupt linking data. Open ${new URL(location.href).origin}/rgthree/link_fixer to try to fix.`);
(_c = console[n]) === null || _c === void 0 ? void 0 : _c.call(console, ...v);
if (CONFIG_SERVICE.getConfigValue("features.show_alerts_for_corrupt_workflows")) {
rgthree.showMessage({
id: "bad-links",
type: "warn",
message: "The workflow you've loaded has corrupt linking data that may be able to be fixed.",
actions: [
{
label: "Open fixer",
href: "/rgthree/link_fixer",
},
{
label: "Fix in place",
href: "/rgthree/link_fixer",
callback: (event) => {
event.stopPropagation();
event.preventDefault();
if (confirm("This will attempt to fix in place. Please make sure to have a saved copy of your workflow.")) {
try {
const fixBadLinksResult = fixer.fix();
if (!fixBadLinksResult.hasBadLinks) {
rgthree.hideMessage("bad-links");
alert("Success! It's possible some valid links may have been affected. Please check and verify your workflow.");
wasLoadingAborted && app.loadGraphData(fixBadLinksResult.graph);
if (CONFIG_SERVICE.getConfigValue("features.monitor_for_corrupt_links") ||
CONFIG_SERVICE.getConfigValue("features.monitor_bad_links")) {
rgthree.monitorLinkTimeout = setTimeout(() => {
rgthree.monitorBadLinks();
}, 5000);
}
}
}
catch (e) {
console.error(e);
alert("Unsuccessful at fixing corrupt data. :(");
rgthree.hideMessage("bad-links");
}
}
},
},
],
});
}
}
else if (CONFIG_SERVICE.getConfigValue("features.monitor_for_corrupt_links") ||
CONFIG_SERVICE.getConfigValue("features.monitor_bad_links")) {
rgthree.monitorLinkTimeout = setTimeout(() => {
rgthree.monitorBadLinks();
}, 5000);
}
}, 100);
return loadGraphData && loadGraphData.apply(app, [...arguments]);
};
}
getNodeFromInitialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff(node) {
var _a, _b, _c;
return ((_c = (_b = (_a = this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff) === null || _a === void 0 ? void 0 : _a.nodes) === null || _b === void 0 ? void 0 : _b.find((n) => n.id === node.id)) !== null && _c !== void 0 ? _c : null);
}
async showMessage(data) {
let container = document.querySelector(".rgthree-top-messages-container");
if (!container) {
container = document.createElement("div");
container.classList.add("rgthree-top-messages-container");
document.body.appendChild(container);
}
const dialogs = queryAll("dialog[open]");
if (dialogs.length) {
let dialog = dialogs[dialogs.length - 1];
dialog.appendChild(container);
dialog.addEventListener("close", (e) => {
document.body.appendChild(container);
});
}
await this.hideMessage(data.id);
const messageContainer = document.createElement("div");
messageContainer.setAttribute("type", data.type || "info");
const message = document.createElement("span");
message.innerHTML = data.message;
messageContainer.appendChild(message);
for (let a = 0; a < (data.actions || []).length; a++) {
const action = data.actions[a];
if (a > 0) {
const sep = document.createElement("span");
sep.innerHTML = "&nbsp;|&nbsp;";
messageContainer.appendChild(sep);
}
const actionEl = document.createElement("a");
actionEl.innerText = action.label;
if (action.href) {
actionEl.target = "_blank";
actionEl.href = action.href;
}
if (action.callback) {
actionEl.onclick = (e) => {
return action.callback(e);
};
}
messageContainer.appendChild(actionEl);
}
const messageAnimContainer = document.createElement("div");
messageAnimContainer.setAttribute("msg-id", data.id);
messageAnimContainer.appendChild(messageContainer);
container.appendChild(messageAnimContainer);
await wait(64);
messageAnimContainer.style.marginTop = `-${messageAnimContainer.offsetHeight}px`;
await wait(64);
messageAnimContainer.classList.add("-show");
if (data.timeout) {
await wait(data.timeout);
this.hideMessage(data.id);
}
}
async hideMessage(id) {
const msg = document.querySelector(`.rgthree-top-messages-container > [msg-id="${id}"]`);
if (msg === null || msg === void 0 ? void 0 : msg.classList.contains("-show")) {
msg.classList.remove("-show");
await wait(750);
}
msg && msg.remove();
}
async clearAllMessages() {
let container = document.querySelector(".rgthree-top-messages-container");
container && (container.innerHTML = "");
}
setLogLevel(level) {
if (typeof level === "string") {
level = LogLevelKeyToLogLevel[CONFIG_SERVICE.getConfigValue("log_level")];
}
if (level != null) {
GLOBAL_LOG_LEVEL = level;
}
}
logParts(level, message, ...args) {
return this.logger.logParts(level, message, ...args);
}
newLogSession(name) {
return this.logger.newSession(name);
}
isDebugMode() {
if (window.location.href.includes("rgthree-debug=false")) {
return false;
}
return GLOBAL_LOG_LEVEL >= LogLevel.DEBUG || window.location.href.includes("rgthree-debug");
}
isDevMode() {
if (window.location.href.includes("rgthree-dev=false")) {
return false;
}
return GLOBAL_LOG_LEVEL >= LogLevel.DEV || window.location.href.includes("rgthree-dev");
}
monitorBadLinks() {
const badLinksFound = WorkflowLinkFixer.create(app.graph).check();
if (badLinksFound.hasBadLinks && !this.monitorBadLinksAlerted) {
this.monitorBadLinksAlerted = true;
alert(`Problematic links just found in live data. Can you save your workflow and file a bug with ` +
`the last few steps you took to trigger this at ` +
`https://github.com/rgthree/rgthree-comfy/issues. Thank you!`);
}
else if (!badLinksFound.hasBadLinks) {
this.monitorBadLinksAlerted = false;
}
this.monitorLinkTimeout = setTimeout(() => {
this.monitorBadLinks();
}, 5000);
}
}
function getBookmarks() {
const bookmarks = BOOKMARKS_SERVICE.getCurrentBookmarks();
const bookmarkItems = bookmarks.map((n) => ({
content: `[${n.shortcutKey}] ${n.title}`,
className: "rgthree-contextmenu-item",
callback: () => {
n.canvasToBookmark();
},
}));
return !bookmarkItems.length
? []
: [
{
content: "🔖 Bookmarks",
disabled: true,
className: "rgthree-contextmenu-item rgthree-contextmenu-label",
},
...bookmarkItems,
];
}
export const rgthree = new Rgthree();
window.rgthree = rgthree;
app.registerExtension({
name: "Comfy.RgthreeComfy",
aboutPageBadges: [
{
label: `rgthree-comfy v${rgthree.version}`,
url: "https://github.com/rgthree/rgthree-comfy",
icon: "rgthree-comfy-about-badge-logo",
},
],
});

View File

@@ -0,0 +1,202 @@
import { app } from "../../scripts/app.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
import { RgthreeBaseServerNode } from "./base_node.js";
import { rgthree } from "./rgthree.js";
import { addConnectionLayoutSupport } from "./utils.js";
import { NodeTypesString } from "./constants.js";
const LAST_SEED_BUTTON_LABEL = "♻️ (Use Last Queued Seed)";
const SPECIAL_SEED_RANDOM = -1;
const SPECIAL_SEED_INCREMENT = -2;
const SPECIAL_SEED_DECREMENT = -3;
const SPECIAL_SEEDS = [SPECIAL_SEED_RANDOM, SPECIAL_SEED_INCREMENT, SPECIAL_SEED_DECREMENT];
class RgthreeSeed extends RgthreeBaseServerNode {
constructor(title = RgthreeSeed.title) {
super(title);
this.serialize_widgets = true;
this.logger = rgthree.newLogSession(`[Seed]`);
this.lastSeed = undefined;
this.serializedCtx = {};
this.lastSeedValue = null;
this.handleApiHijackingBound = this.handleApiHijacking.bind(this);
this.properties["randomMax"] = 1125899906842624;
this.properties["randomMin"] = 0;
rgthree.addEventListener("comfy-api-queue-prompt-before", this.handleApiHijackingBound);
}
onPropertyChanged(prop, value, prevValue) {
if (prop === 'randomMax') {
this.properties["randomMax"] = Math.min(1125899906842624, Number(value));
}
else if (prop === 'randomMin') {
this.properties["randomMin"] = Math.max(-1125899906842624, Number(value));
}
return true;
}
onRemoved() {
rgthree.addEventListener("comfy-api-queue-prompt-before", this.handleApiHijackingBound);
}
configure(info) {
var _a;
super.configure(info);
if ((_a = this.properties) === null || _a === void 0 ? void 0 : _a["showLastSeed"]) {
this.addLastSeedValue();
}
}
async handleAction(action) {
if (action === "Randomize Each Time") {
this.seedWidget.value = SPECIAL_SEED_RANDOM;
}
else if (action === "Use Last Queued Seed") {
this.seedWidget.value = this.lastSeed != null ? this.lastSeed : this.seedWidget.value;
this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL;
this.lastSeedButton.disabled = true;
}
}
onNodeCreated() {
var _a;
(_a = super.onNodeCreated) === null || _a === void 0 ? void 0 : _a.call(this);
for (const [i, w] of this.widgets.entries()) {
if (w.name === "seed") {
this.seedWidget = w;
this.seedWidget.value = SPECIAL_SEED_RANDOM;
}
else if (w.name === "control_after_generate") {
this.widgets.splice(i, 1);
}
}
this.addWidget("button", "🎲 Randomize Each Time", "", () => {
this.seedWidget.value = SPECIAL_SEED_RANDOM;
}, { serialize: false });
this.addWidget("button", "🎲 New Fixed Random", "", () => {
this.seedWidget.value = this.generateRandomSeed();
}, { serialize: false });
this.lastSeedButton = this.addWidget("button", LAST_SEED_BUTTON_LABEL, "", () => {
this.seedWidget.value = this.lastSeed != null ? this.lastSeed : this.seedWidget.value;
this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL;
this.lastSeedButton.disabled = true;
}, { width: 50, serialize: false });
this.lastSeedButton.disabled = true;
}
generateRandomSeed() {
let step = this.seedWidget.options.step || 1;
const randomMin = Number(this.properties['randomMin'] || 0);
const randomMax = Number(this.properties['randomMax'] || 1125899906842624);
const randomRange = (randomMax - randomMin) / (step / 10);
let seed = Math.floor(Math.random() * randomRange) * (step / 10) + randomMin;
if (SPECIAL_SEEDS.includes(seed)) {
seed = 0;
}
return seed;
}
getExtraMenuOptions(canvas, options) {
var _a;
(_a = super.getExtraMenuOptions) === null || _a === void 0 ? void 0 : _a.apply(this, [...arguments]);
options.splice(options.length - 1, 0, {
content: "Show/Hide Last Seed Value",
callback: (_value, _options, _event, _parentMenu, _node) => {
this.properties["showLastSeed"] = !this.properties["showLastSeed"];
if (this.properties["showLastSeed"]) {
this.addLastSeedValue();
}
else {
this.removeLastSeedValue();
}
},
});
return [];
}
addLastSeedValue() {
if (this.lastSeedValue)
return;
this.lastSeedValue = ComfyWidgets["STRING"](this, "last_seed", ["STRING", { multiline: true }], app).widget;
this.lastSeedValue.inputEl.readOnly = true;
this.lastSeedValue.inputEl.style.fontSize = "0.75rem";
this.lastSeedValue.inputEl.style.textAlign = "center";
this.computeSize();
}
removeLastSeedValue() {
if (!this.lastSeedValue)
return;
this.lastSeedValue.inputEl.remove();
this.widgets.splice(this.widgets.indexOf(this.lastSeedValue), 1);
this.lastSeedValue = null;
this.computeSize();
}
handleApiHijacking(e) {
var _a, _b, _c, _d;
if (this.mode === LiteGraph.NEVER || this.mode === 4) {
return;
}
const workflow = e.detail.workflow;
const output = e.detail.output;
let workflowNode = (_b = (_a = workflow === null || workflow === void 0 ? void 0 : workflow.nodes) === null || _a === void 0 ? void 0 : _a.find((n) => n.id === this.id)) !== null && _b !== void 0 ? _b : null;
let outputInputs = (_c = output === null || output === void 0 ? void 0 : output[this.id]) === null || _c === void 0 ? void 0 : _c.inputs;
if (!workflowNode ||
!outputInputs ||
outputInputs[this.seedWidget.name || "seed"] === undefined) {
const [n, v] = this.logger.warnParts(`Node ${this.id} not found in prompt data sent to server. This may be fine if only ` +
`queuing part of the workflow. If not, then this could be a bug.`);
(_d = console[n]) === null || _d === void 0 ? void 0 : _d.call(console, ...v);
return;
}
const seedToUse = this.getSeedToUse();
const seedWidgetndex = this.widgets.indexOf(this.seedWidget);
workflowNode.widgets_values[seedWidgetndex] = seedToUse;
outputInputs[this.seedWidget.name || "seed"] = seedToUse;
this.lastSeed = seedToUse;
if (seedToUse != this.seedWidget.value) {
this.lastSeedButton.name = `♻️ ${this.lastSeed}`;
this.lastSeedButton.disabled = false;
}
else {
this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL;
this.lastSeedButton.disabled = true;
}
if (this.lastSeedValue) {
this.lastSeedValue.value = `Last Seed: ${this.lastSeed}`;
}
}
getSeedToUse() {
const inputSeed = Number(this.seedWidget.value);
let seedToUse = null;
if (SPECIAL_SEEDS.includes(inputSeed)) {
if (typeof this.lastSeed === "number" && !SPECIAL_SEEDS.includes(this.lastSeed)) {
if (inputSeed === SPECIAL_SEED_INCREMENT) {
seedToUse = this.lastSeed + 1;
}
else if (inputSeed === SPECIAL_SEED_DECREMENT) {
seedToUse = this.lastSeed - 1;
}
}
if (seedToUse == null || SPECIAL_SEEDS.includes(seedToUse)) {
seedToUse = this.generateRandomSeed();
}
}
return seedToUse !== null && seedToUse !== void 0 ? seedToUse : inputSeed;
}
static setUp(comfyClass, nodeData) {
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, RgthreeSeed);
}
static onRegisteredForOverride(comfyClass, ctxClass) {
addConnectionLayoutSupport(RgthreeSeed, app, [
["Left", "Right"],
["Right", "Left"],
]);
setTimeout(() => {
RgthreeSeed.category = comfyClass.category;
});
}
}
RgthreeSeed.title = NodeTypesString.SEED;
RgthreeSeed.type = NodeTypesString.SEED;
RgthreeSeed.comfyClass = NodeTypesString.SEED;
RgthreeSeed.exposedActions = ["Randomize Each Time", "Use Last Queued Seed"];
RgthreeSeed["@randomMax"] = { type: "number" };
RgthreeSeed["@randomMin"] = { type: "number" };
app.registerExtension({
name: "rgthree.Seed",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name === RgthreeSeed.type) {
RgthreeSeed.setUp(nodeType, nodeData);
}
},
});

View File

@@ -0,0 +1,24 @@
import { app } from "../../../scripts/app.js";
import { NodeTypesString } from "../constants.js";
import { reduceNodesDepthFirst } from "../utils.js";
const SHORTCUT_DEFAULTS = "1234567890abcdefghijklmnopqrstuvwxyz".split("");
class BookmarksService {
getCurrentBookmarks() {
return reduceNodesDepthFirst(app.graph.nodes, (n, acc) => {
if (n.type === NodeTypesString.BOOKMARK) {
acc.push(n);
}
}, []).sort((a, b) => a.title.localeCompare(b.title));
}
getExistingShortcuts() {
const bookmarkNodes = this.getCurrentBookmarks();
const usedShortcuts = new Set(bookmarkNodes.map((n) => n.shortcutKey));
return usedShortcuts;
}
getNextShortcut() {
var _a;
const existingShortcuts = this.getExistingShortcuts();
return (_a = SHORTCUT_DEFAULTS.find((char) => !existingShortcuts.has(char))) !== null && _a !== void 0 ? _a : "1";
}
}
export const SERVICE = new BookmarksService();

View File

@@ -0,0 +1,28 @@
import { rgthreeConfig } from "../../../rgthree/config.js";
import { getObjectValue, setObjectValue } from "../../../rgthree/common/shared_utils.js";
import { rgthreeApi } from "../../../rgthree/common/rgthree_api.js";
class ConfigService extends EventTarget {
getConfigValue(key, def) {
return getObjectValue(rgthreeConfig, key, def);
}
getFeatureValue(key, def) {
key = "features." + key.replace(/^features\./, "");
return getObjectValue(rgthreeConfig, key, def);
}
async setConfigValues(changed) {
const body = new FormData();
body.append("json", JSON.stringify(changed));
const response = await rgthreeApi.fetchJson("/config", { method: "POST", body });
if (response.status === "ok") {
for (const [key, value] of Object.entries(changed)) {
setObjectValue(rgthreeConfig, key, value);
this.dispatchEvent(new CustomEvent("config-change", { detail: { key, value } }));
}
}
else {
return false;
}
return true;
}
}
export const SERVICE = new ConfigService();

View File

@@ -0,0 +1,51 @@
import { getConnectedOutputNodesAndFilterPassThroughs } from "../utils.js";
export let SERVICE;
const OWNED_PREFIX = "+";
const REGEX_PREFIX = /^[\+⚠️]\s*/;
const REGEX_EMPTY_INPUT = /^\+\s*$/;
export function stripContextInputPrefixes(name) {
return name.replace(REGEX_PREFIX, "");
}
export function getContextOutputName(inputName) {
if (inputName === "base_ctx")
return "CONTEXT";
return stripContextInputPrefixes(inputName).toUpperCase();
}
export var InputMutationOperation;
(function (InputMutationOperation) {
InputMutationOperation[InputMutationOperation["UNKNOWN"] = 0] = "UNKNOWN";
InputMutationOperation[InputMutationOperation["ADDED"] = 1] = "ADDED";
InputMutationOperation[InputMutationOperation["REMOVED"] = 2] = "REMOVED";
InputMutationOperation[InputMutationOperation["RENAMED"] = 3] = "RENAMED";
})(InputMutationOperation || (InputMutationOperation = {}));
export class ContextService {
constructor() {
if (SERVICE) {
throw new Error("ContextService was already instantiated.");
}
}
onInputChanges(node, mutation) {
const childCtxs = getConnectedOutputNodesAndFilterPassThroughs(node, node, 0);
for (const childCtx of childCtxs) {
childCtx.handleUpstreamMutation(mutation);
}
}
getDynamicContextInputsData(node) {
return node
.getContextInputsList()
.map((input, index) => ({
name: stripContextInputPrefixes(input.name),
type: String(input.type),
index,
}))
.filter((i) => i.type !== "*");
}
getDynamicContextOutputsData(node) {
return node.outputs.map((output, index) => ({
name: stripContextInputPrefixes(output.name),
type: String(output.type),
index,
}));
}
}
SERVICE = new ContextService();

View File

@@ -0,0 +1,163 @@
import { app } from "../../../scripts/app.js";
import { getGraphDependantNodeKey, getGroupNodes, reduceNodesDepthFirst } from "../utils.js";
class FastGroupsService {
constructor() {
this.msThreshold = 400;
this.msLastUnsorted = 0;
this.msLastAlpha = 0;
this.msLastPosition = 0;
this.groupsUnsorted = [];
this.groupsSortedAlpha = [];
this.groupsSortedPosition = [];
this.fastGroupNodes = [];
this.runScheduledForMs = null;
this.runScheduleTimeout = null;
this.runScheduleAnimation = null;
this.cachedNodeBoundings = null;
}
addFastGroupNode(node) {
this.fastGroupNodes.push(node);
this.scheduleRun(8);
}
removeFastGroupNode(node) {
var _a;
const index = this.fastGroupNodes.indexOf(node);
if (index > -1) {
this.fastGroupNodes.splice(index, 1);
}
if (!((_a = this.fastGroupNodes) === null || _a === void 0 ? void 0 : _a.length)) {
this.clearScheduledRun();
this.groupsUnsorted = [];
this.groupsSortedAlpha = [];
this.groupsSortedPosition = [];
}
}
run() {
if (!this.runScheduledForMs) {
return;
}
for (const node of this.fastGroupNodes) {
node.refreshWidgets();
}
this.clearScheduledRun();
this.scheduleRun();
}
scheduleRun(ms = 500) {
if (this.runScheduledForMs && ms < this.runScheduledForMs) {
this.clearScheduledRun();
}
if (!this.runScheduledForMs && this.fastGroupNodes.length) {
this.runScheduledForMs = ms;
this.runScheduleTimeout = setTimeout(() => {
this.runScheduleAnimation = requestAnimationFrame(() => this.run());
}, ms);
}
}
clearScheduledRun() {
this.runScheduleTimeout && clearTimeout(this.runScheduleTimeout);
this.runScheduleAnimation && cancelAnimationFrame(this.runScheduleAnimation);
this.runScheduleTimeout = null;
this.runScheduleAnimation = null;
this.runScheduledForMs = null;
}
getBoundingsForAllNodes() {
if (!this.cachedNodeBoundings) {
this.cachedNodeBoundings = reduceNodesDepthFirst(app.graph._nodes, (node, acc) => {
var _a, _b;
let bounds = node.getBounding();
if (bounds[0] === 0 && bounds[1] === 0 && bounds[2] === 0 && bounds[3] === 0) {
const ctx = (_b = (_a = node.graph) === null || _a === void 0 ? void 0 : _a.primaryCanvas) === null || _b === void 0 ? void 0 : _b.canvas.getContext("2d");
if (ctx) {
node.updateArea(ctx);
bounds = node.getBounding();
}
}
acc[getGraphDependantNodeKey(node)] = bounds;
}, {});
setTimeout(() => {
this.cachedNodeBoundings = null;
}, 50);
}
return this.cachedNodeBoundings;
}
recomputeInsideNodesForGroup(group) {
if (app.canvas.isDragging)
return;
const cachedBoundings = this.getBoundingsForAllNodes();
const nodes = group.graph.nodes;
group._children.clear();
group.nodes.length = 0;
for (const node of nodes) {
const nodeBounding = cachedBoundings[getGraphDependantNodeKey(node)];
const nodeCenter = nodeBounding &&
[nodeBounding[0] + nodeBounding[2] * 0.5, nodeBounding[1] + nodeBounding[3] * 0.5];
if (nodeCenter) {
const grouBounds = group._bounding;
if (nodeCenter[0] >= grouBounds[0] &&
nodeCenter[0] < grouBounds[0] + grouBounds[2] &&
nodeCenter[1] >= grouBounds[1] &&
nodeCenter[1] < grouBounds[1] + grouBounds[3]) {
group._children.add(node);
group.nodes.push(node);
}
}
}
}
getGroupsUnsorted(now) {
var _a, _b, _c;
const canvas = app.canvas;
const graph = (_a = canvas.getCurrentGraph()) !== null && _a !== void 0 ? _a : app.graph;
if (!canvas.selected_group_moving &&
(!this.groupsUnsorted.length || now - this.msLastUnsorted > this.msThreshold)) {
this.groupsUnsorted = [...graph._groups];
const subgraphs = (_b = graph.subgraphs) === null || _b === void 0 ? void 0 : _b.values();
if (subgraphs) {
let s;
while ((s = subgraphs.next().value))
this.groupsUnsorted.push(...((_c = s.groups) !== null && _c !== void 0 ? _c : []));
}
for (const group of this.groupsUnsorted) {
this.recomputeInsideNodesForGroup(group);
group.rgthree_hasAnyActiveNode = getGroupNodes(group).some((n) => n.mode === LiteGraph.ALWAYS);
}
this.msLastUnsorted = now;
}
return this.groupsUnsorted;
}
getGroupsAlpha(now) {
if (!this.groupsSortedAlpha.length || now - this.msLastAlpha > this.msThreshold) {
this.groupsSortedAlpha = [...this.getGroupsUnsorted(now)].sort((a, b) => {
return a.title.localeCompare(b.title);
});
this.msLastAlpha = now;
}
return this.groupsSortedAlpha;
}
getGroupsPosition(now) {
if (!this.groupsSortedPosition.length || now - this.msLastPosition > this.msThreshold) {
this.groupsSortedPosition = [...this.getGroupsUnsorted(now)].sort((a, b) => {
const aY = Math.floor(a._pos[1] / 30);
const bY = Math.floor(b._pos[1] / 30);
if (aY == bY) {
const aX = Math.floor(a._pos[0] / 30);
const bX = Math.floor(b._pos[0] / 30);
return aX - bX;
}
return aY - bY;
});
this.msLastPosition = now;
}
return this.groupsSortedPosition;
}
getGroups(sort) {
const now = +new Date();
if (sort === "alphanumeric") {
return this.getGroupsAlpha(now);
}
if (sort === "position") {
return this.getGroupsPosition(now);
}
return this.getGroupsUnsorted(now);
}
}
export const SERVICE = new FastGroupsService();

View File

@@ -0,0 +1,115 @@
class KeyEventService extends EventTarget {
constructor() {
var _a, _b, _c;
super();
this.downKeys = {};
this.shiftDownKeys = {};
this.ctrlKey = false;
this.altKey = false;
this.metaKey = false;
this.shiftKey = false;
this.isMac = !!(((_a = navigator.platform) === null || _a === void 0 ? void 0 : _a.toLocaleUpperCase().startsWith("MAC")) ||
((_c = (_b = navigator.userAgentData) === null || _b === void 0 ? void 0 : _b.platform) === null || _c === void 0 ? void 0 : _c.toLocaleUpperCase().startsWith("MAC")));
this.initialize();
}
initialize() {
const that = this;
const processKey = LGraphCanvas.prototype.processKey;
LGraphCanvas.prototype.processKey = function (e) {
if (e.type === "keydown" || e.type === "keyup") {
that.handleKeyDownOrUp(e);
}
return processKey.apply(this, [...arguments]);
};
window.addEventListener("keydown", (e) => {
that.handleKeyDownOrUp(e);
});
window.addEventListener("keyup", (e) => {
that.handleKeyDownOrUp(e);
});
document.addEventListener("visibilitychange", (e) => {
this.clearKeydowns();
});
window.addEventListener("blur", (e) => {
this.clearKeydowns();
});
}
handleKeyDownOrUp(e) {
const key = e.key.toLocaleUpperCase();
if ((e.type === 'keydown' && this.downKeys[key] === true)
|| (e.type === 'keyup' && this.downKeys[key] === undefined)) {
return;
}
this.ctrlKey = !!e.ctrlKey;
this.altKey = !!e.altKey;
this.metaKey = !!e.metaKey;
this.shiftKey = !!e.shiftKey;
if (e.type === "keydown") {
this.downKeys[key] = true;
this.dispatchCustomEvent("keydown", { originalEvent: e });
if (this.shiftKey && key !== 'SHIFT') {
this.shiftDownKeys[key] = true;
}
}
else if (e.type === "keyup") {
if (key === "META" && this.isMac) {
this.clearKeydowns();
}
else {
delete this.downKeys[key];
}
if (key === 'SHIFT') {
for (const key in this.shiftDownKeys) {
delete this.downKeys[key];
delete this.shiftDownKeys[key];
}
}
this.dispatchCustomEvent("keyup", { originalEvent: e });
}
}
clearKeydowns() {
this.ctrlKey = false;
this.altKey = false;
this.metaKey = false;
this.shiftKey = false;
for (const key in this.downKeys)
delete this.downKeys[key];
}
dispatchCustomEvent(event, detail) {
if (detail != null) {
return this.dispatchEvent(new CustomEvent(event, { detail }));
}
return this.dispatchEvent(new CustomEvent(event));
}
getKeysFromShortcut(shortcut) {
let keys;
if (typeof shortcut === "string") {
shortcut = shortcut.replace(/\s/g, "");
shortcut = shortcut.replace(/^\+/, "__PLUS__").replace(/\+\+/, "+__PLUS__");
keys = shortcut.split("+").map((i) => i.replace("__PLUS__", "+"));
}
else {
keys = [...shortcut];
}
return keys.map((k) => k.toLocaleUpperCase());
}
areAllKeysDown(keys) {
keys = this.getKeysFromShortcut(keys);
return keys.every((k) => {
return this.downKeys[k];
});
}
areOnlyKeysDown(keys, alsoAllowShift = false) {
keys = this.getKeysFromShortcut(keys);
const allKeysDown = this.areAllKeysDown(keys);
const downKeysLength = Object.values(this.downKeys).length;
if (allKeysDown && keys.length === downKeysLength) {
return true;
}
if (alsoAllowShift && !keys.includes("SHIFT") && keys.length === downKeysLength - 1) {
return allKeysDown && this.areAllKeysDown(["SHIFT"]);
}
return false;
}
}
export const SERVICE = new KeyEventService();

View File

@@ -0,0 +1,723 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { getResolver, wait } from "../../rgthree/common/shared_utils.js";
import { RgthreeHelpDialog } from "../../rgthree/common/dialog.js";
const oldApiGetNodeDefs = api.getNodeDefs;
api.getNodeDefs = async function () {
const defs = await oldApiGetNodeDefs.call(api);
this.dispatchEvent(new CustomEvent("fresh-node-defs", { detail: defs }));
return defs;
};
export var IoDirection;
(function (IoDirection) {
IoDirection[IoDirection["INPUT"] = 0] = "INPUT";
IoDirection[IoDirection["OUTPUT"] = 1] = "OUTPUT";
})(IoDirection || (IoDirection = {}));
const PADDING = 0;
export const LAYOUT_LABEL_TO_DATA = {
Left: [LiteGraph.LEFT, [0, 0.5], [PADDING, 0]],
Right: [LiteGraph.RIGHT, [1, 0.5], [-PADDING, 0]],
Top: [LiteGraph.UP, [0.5, 0], [0, PADDING]],
Bottom: [LiteGraph.DOWN, [0.5, 1], [0, -PADDING]],
};
export const LAYOUT_LABEL_OPPOSITES = {
Left: "Right",
Right: "Left",
Top: "Bottom",
Bottom: "Top",
};
export const LAYOUT_CLOCKWISE = ["Top", "Right", "Bottom", "Left"];
export function addMenuItem(node, _app, config, after = "Shape") {
const oldGetExtraMenuOptions = node.prototype.getExtraMenuOptions;
node.prototype.getExtraMenuOptions = function (canvas, menuOptions) {
oldGetExtraMenuOptions && oldGetExtraMenuOptions.apply(this, [canvas, menuOptions]);
addMenuItemOnExtraMenuOptions(this, config, menuOptions, after);
};
}
let canvasResolver = null;
export function waitForCanvas() {
if (canvasResolver === null) {
canvasResolver = getResolver();
function _waitForCanvas() {
if (!canvasResolver.completed) {
if (app === null || app === void 0 ? void 0 : app.canvas) {
canvasResolver.resolve(app.canvas);
}
else {
requestAnimationFrame(_waitForCanvas);
}
}
}
_waitForCanvas();
}
return canvasResolver.promise;
}
let graphResolver = null;
export function waitForGraph() {
if (graphResolver === null) {
graphResolver = getResolver();
function _wait() {
if (!graphResolver.completed) {
if (app === null || app === void 0 ? void 0 : app.graph) {
graphResolver.resolve(app.graph);
}
else {
requestAnimationFrame(_wait);
}
}
}
_wait();
}
return graphResolver.promise;
}
export function addMenuItemOnExtraMenuOptions(node, config, menuOptions, after = "Shape") {
let idx = menuOptions
.slice()
.reverse()
.findIndex((option) => option === null || option === void 0 ? void 0 : option.isRgthree);
if (idx == -1) {
idx = menuOptions.findIndex((option) => { var _a; return (_a = option === null || option === void 0 ? void 0 : option.content) === null || _a === void 0 ? void 0 : _a.includes(after); }) + 1;
if (!idx) {
idx = menuOptions.length - 1;
}
menuOptions.splice(idx, 0, null);
idx++;
}
else {
idx = menuOptions.length - idx;
}
const subMenuOptions = typeof config.subMenuOptions === "function"
? config.subMenuOptions(node)
: config.subMenuOptions;
menuOptions.splice(idx, 0, {
content: typeof config.name == "function" ? config.name(node) : config.name,
has_submenu: !!(subMenuOptions === null || subMenuOptions === void 0 ? void 0 : subMenuOptions.length),
isRgthree: true,
callback: (value, _options, event, parentMenu, _node) => {
if (!!(subMenuOptions === null || subMenuOptions === void 0 ? void 0 : subMenuOptions.length)) {
new LiteGraph.ContextMenu(subMenuOptions.map((option) => (option ? { content: option } : null)), {
event,
parentMenu,
callback: (subValue, _options, _event, _parentMenu, _node) => {
if (config.property) {
node.properties = node.properties || {};
node.properties[config.property] = config.prepareValue
? config.prepareValue(subValue.content || "", node)
: subValue.content || "";
}
config.callback && config.callback(node, subValue === null || subValue === void 0 ? void 0 : subValue.content);
},
});
return;
}
if (config.property) {
node.properties = node.properties || {};
node.properties[config.property] = config.prepareValue
? config.prepareValue(node.properties[config.property], node)
: !node.properties[config.property];
}
config.callback && config.callback(node, value === null || value === void 0 ? void 0 : value.content);
},
});
}
export function addConnectionLayoutSupport(node, app, options = [
["Left", "Right"],
["Right", "Left"],
], callback) {
addMenuItem(node, app, {
name: "Connections Layout",
property: "connections_layout",
subMenuOptions: options.map((option) => option[0] + (option[1] ? " -> " + option[1] : "")),
prepareValue: (value, node) => {
var _a;
const values = String(value).split(" -> ");
if (!values[1] && !((_a = node.outputs) === null || _a === void 0 ? void 0 : _a.length)) {
values[1] = LAYOUT_LABEL_OPPOSITES[values[0]];
}
if (!LAYOUT_LABEL_TO_DATA[values[0]] || !LAYOUT_LABEL_TO_DATA[values[1]]) {
throw new Error(`New Layout invalid: [${values[0]}, ${values[1]}]`);
}
return values;
},
callback: (node) => {
var _a;
callback && callback(node);
(_a = node.graph) === null || _a === void 0 ? void 0 : _a.setDirtyCanvas(true, true);
},
});
node.prototype.getConnectionPos = function (isInput, slotNumber, out) {
return getConnectionPosForLayout(this, isInput, slotNumber, out);
};
node.prototype.getInputPos = function (slotNumber) {
return getConnectionPosForLayout(this, true, slotNumber, [0, 0]);
};
node.prototype.getOutputPos = function (slotNumber) {
return getConnectionPosForLayout(this, false, slotNumber, [0, 0]);
};
}
export function setConnectionsLayout(node, newLayout) {
var _a;
newLayout = newLayout || node.defaultConnectionsLayout || ["Left", "Right"];
if (!newLayout[1] && !((_a = node.outputs) === null || _a === void 0 ? void 0 : _a.length)) {
newLayout[1] = LAYOUT_LABEL_OPPOSITES[newLayout[0]];
}
if (!LAYOUT_LABEL_TO_DATA[newLayout[0]] || !LAYOUT_LABEL_TO_DATA[newLayout[1]]) {
throw new Error(`New Layout invalid: [${newLayout[0]}, ${newLayout[1]}]`);
}
node.properties = node.properties || {};
node.properties["connections_layout"] = newLayout;
}
export function setConnectionsCollapse(node, collapseConnections = null) {
node.properties = node.properties || {};
collapseConnections =
collapseConnections !== null ? collapseConnections : !node.properties["collapse_connections"];
node.properties["collapse_connections"] = collapseConnections;
}
export function getConnectionPosForLayout(node, isInput, slotNumber, out) {
var _a, _b, _c;
out = out || new Float32Array(2);
node.properties = node.properties || {};
const layout = node.properties["connections_layout"] ||
node.defaultConnectionsLayout || ["Left", "Right"];
const collapseConnections = node.properties["collapse_connections"] || false;
const offset = (_a = node.constructor.layout_slot_offset) !== null && _a !== void 0 ? _a : LiteGraph.NODE_SLOT_HEIGHT * 0.5;
let side = isInput ? layout[0] : layout[1];
const otherSide = isInput ? layout[1] : layout[0];
let data = LAYOUT_LABEL_TO_DATA[side];
const slotList = node[isInput ? "inputs" : "outputs"];
const cxn = slotList[slotNumber];
if (!cxn) {
console.log("No connection found.. weird", isInput, slotNumber);
return out;
}
if (cxn.disabled) {
if (cxn.color_on !== "#666665") {
cxn._color_on_org = cxn._color_on_org || cxn.color_on;
cxn._color_off_org = cxn._color_off_org || cxn.color_off;
}
cxn.color_on = "#666665";
cxn.color_off = "#666665";
}
else if (cxn.color_on === "#666665") {
cxn.color_on = cxn._color_on_org || undefined;
cxn.color_off = cxn._color_off_org || undefined;
}
const displaySlot = collapseConnections
? 0
: slotNumber -
slotList.reduce((count, ioput, index) => {
count += index < slotNumber && ioput.hidden ? 1 : 0;
return count;
}, 0);
cxn.dir = data[0];
const connections_dir = node.properties["connections_dir"];
if ((node.size[0] == 10 || node.size[1] == 10) && connections_dir) {
cxn.dir = connections_dir[isInput ? 0 : 1];
}
if (side === "Left") {
if (node.flags.collapsed) {
var w = node._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH;
out[0] = node.pos[0];
out[1] = node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5;
}
else {
toggleConnectionLabel(cxn, !isInput || collapseConnections || !!node.hideSlotLabels);
out[0] = node.pos[0] + offset;
if ((_b = node.constructor) === null || _b === void 0 ? void 0 : _b.type.includes("Reroute")) {
out[1] = node.pos[1] + node.size[1] * 0.5;
}
else {
out[1] =
node.pos[1] +
(displaySlot + 0.7) * LiteGraph.NODE_SLOT_HEIGHT +
(node.constructor.slot_start_y || 0);
}
}
}
else if (side === "Right") {
if (node.flags.collapsed) {
var w = node._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH;
out[0] = node.pos[0] + w;
out[1] = node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5;
}
else {
toggleConnectionLabel(cxn, isInput || collapseConnections || !!node.hideSlotLabels);
out[0] = node.pos[0] + node.size[0] + 1 - offset;
if ((_c = node.constructor) === null || _c === void 0 ? void 0 : _c.type.includes("Reroute")) {
out[1] = node.pos[1] + node.size[1] * 0.5;
}
else {
out[1] =
node.pos[1] +
(displaySlot + 0.7) * LiteGraph.NODE_SLOT_HEIGHT +
(node.constructor.slot_start_y || 0);
}
}
}
else if (side === "Top") {
if (!cxn.has_old_label) {
cxn.has_old_label = true;
cxn.old_label = cxn.label;
cxn.label = " ";
}
out[0] = node.pos[0] + node.size[0] * 0.5;
out[1] = node.pos[1] + offset;
}
else if (side === "Bottom") {
if (!cxn.has_old_label) {
cxn.has_old_label = true;
cxn.old_label = cxn.label;
cxn.label = " ";
}
out[0] = node.pos[0] + node.size[0] * 0.5;
out[1] = node.pos[1] + node.size[1] - offset;
}
return out;
}
function toggleConnectionLabel(cxn, hide = true) {
if (hide) {
if (!cxn.has_old_label) {
cxn.has_old_label = true;
cxn.old_label = cxn.label;
}
cxn.label = " ";
}
else if (!hide && cxn.has_old_label) {
cxn.has_old_label = false;
cxn.label = cxn.old_label;
cxn.old_label = undefined;
}
return cxn;
}
export function addHelpMenuItem(node, content, menuOptions) {
addMenuItemOnExtraMenuOptions(node, {
name: "🛟 Node Help",
callback: (node) => {
if (node.showHelp) {
node.showHelp();
}
else {
new RgthreeHelpDialog(node, content).show();
}
},
}, menuOptions, "Properties Panel");
}
export var PassThroughFollowing;
(function (PassThroughFollowing) {
PassThroughFollowing[PassThroughFollowing["ALL"] = 0] = "ALL";
PassThroughFollowing[PassThroughFollowing["NONE"] = 1] = "NONE";
PassThroughFollowing[PassThroughFollowing["REROUTE_ONLY"] = 2] = "REROUTE_ONLY";
})(PassThroughFollowing || (PassThroughFollowing = {}));
export function shouldPassThrough(node, passThroughFollowing = PassThroughFollowing.ALL) {
var _a;
const type = (_a = node === null || node === void 0 ? void 0 : node.constructor) === null || _a === void 0 ? void 0 : _a.type;
if (!type || passThroughFollowing === PassThroughFollowing.NONE) {
return false;
}
if (passThroughFollowing === PassThroughFollowing.REROUTE_ONLY) {
return type.includes("Reroute");
}
return (type.includes("Reroute") || type.includes("Node Combiner") || type.includes("Node Collector"));
}
function filterOutPassthroughNodes(infos, passThroughFollowing = PassThroughFollowing.ALL) {
return infos.filter((i) => !shouldPassThrough(i.node, passThroughFollowing));
}
export function getConnectedInputNodes(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) {
return getConnectedNodesInfo(startNode, IoDirection.INPUT, currentNode, slot, passThroughFollowing).map((n) => n.node);
}
export function getConnectedInputInfosAndFilterPassThroughs(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) {
return filterOutPassthroughNodes(getConnectedNodesInfo(startNode, IoDirection.INPUT, currentNode, slot, passThroughFollowing), passThroughFollowing);
}
export function getConnectedInputNodesAndFilterPassThroughs(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) {
return getConnectedInputInfosAndFilterPassThroughs(startNode, currentNode, slot, passThroughFollowing).map((n) => n.node);
}
export function getConnectedOutputNodes(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) {
return getConnectedNodesInfo(startNode, IoDirection.OUTPUT, currentNode, slot, passThroughFollowing).map((n) => n.node);
}
export function getConnectedOutputNodesAndFilterPassThroughs(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) {
return filterOutPassthroughNodes(getConnectedNodesInfo(startNode, IoDirection.OUTPUT, currentNode, slot, passThroughFollowing), passThroughFollowing).map((n) => n.node);
}
export function getConnectedNodesInfo(startNode, dir = IoDirection.INPUT, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL, originTravelFromSlot) {
var _a, _b, _c, _d, _e, _f, _g, _h;
currentNode = currentNode || startNode;
let rootNodes = [];
if (startNode === currentNode || shouldPassThrough(currentNode, passThroughFollowing)) {
let linkIds;
slot = slot != null && slot > -1 ? slot : undefined;
if (dir == IoDirection.OUTPUT) {
if (slot != null) {
linkIds = [...(((_b = (_a = currentNode.outputs) === null || _a === void 0 ? void 0 : _a[slot]) === null || _b === void 0 ? void 0 : _b.links) || [])];
}
else {
linkIds = ((_c = currentNode.outputs) === null || _c === void 0 ? void 0 : _c.flatMap((i) => i.links)) || [];
}
}
else {
if (slot != null) {
linkIds = [(_e = (_d = currentNode.inputs) === null || _d === void 0 ? void 0 : _d[slot]) === null || _e === void 0 ? void 0 : _e.link];
}
else {
linkIds = ((_f = currentNode.inputs) === null || _f === void 0 ? void 0 : _f.map((i) => i.link)) || [];
}
}
const graph = (_g = currentNode.graph) !== null && _g !== void 0 ? _g : app.graph;
for (const linkId of linkIds) {
let link = null;
if (typeof linkId == "number") {
link = (_h = graph.links[linkId]) !== null && _h !== void 0 ? _h : null;
}
if (!link) {
continue;
}
const travelFromSlot = dir == IoDirection.OUTPUT ? link.origin_slot : link.target_slot;
const connectedId = dir == IoDirection.OUTPUT ? link.target_id : link.origin_id;
const travelToSlot = dir == IoDirection.OUTPUT ? link.target_slot : link.origin_slot;
originTravelFromSlot = originTravelFromSlot != null ? originTravelFromSlot : travelFromSlot;
const originNode = graph.getNodeById(connectedId);
if (!link) {
console.error("No connected node found... weird");
continue;
}
if (rootNodes.some((n) => n.node == originNode)) {
console.log(`${startNode.title} (${startNode.id}) seems to have two links to ${originNode.title} (${originNode.id}). One may be stale: ${linkIds.join(", ")}`);
}
else {
rootNodes.push({ node: originNode, travelFromSlot, travelToSlot, originTravelFromSlot });
if (shouldPassThrough(originNode, passThroughFollowing)) {
for (const foundNode of getConnectedNodesInfo(startNode, dir, originNode, undefined, undefined, originTravelFromSlot)) {
if (!rootNodes.map((n) => n.node).includes(foundNode.node)) {
rootNodes.push(foundNode);
}
}
}
}
}
}
return rootNodes;
}
export function followConnectionUntilType(node, dir, slotNum, skipSelf = false) {
const slots = dir === IoDirection.OUTPUT ? node.outputs : node.inputs;
if (!slots || !slots.length) {
return null;
}
let type = null;
if (slotNum) {
if (!slots[slotNum]) {
return null;
}
type = getTypeFromSlot(slots[slotNum], dir, skipSelf);
}
else {
for (const slot of slots) {
type = getTypeFromSlot(slot, dir, skipSelf);
if (type) {
break;
}
}
}
return type;
}
function getTypeFromSlot(slot, dir, skipSelf = false) {
let graph = app.canvas.getCurrentGraph();
let type = slot === null || slot === void 0 ? void 0 : slot.type;
if (!skipSelf && type != null && type != "*") {
return { type: type, label: slot === null || slot === void 0 ? void 0 : slot.label, name: slot === null || slot === void 0 ? void 0 : slot.name };
}
const links = getSlotLinks(slot);
for (const link of links) {
const connectedId = dir == IoDirection.OUTPUT ? link.link.target_id : link.link.origin_id;
const connectedSlotNum = dir == IoDirection.OUTPUT ? link.link.target_slot : link.link.origin_slot;
const connectedNode = graph.getNodeById(connectedId);
const connectedSlots = dir === IoDirection.OUTPUT ? connectedNode.inputs : connectedNode.outputs;
let connectedSlot = connectedSlots[connectedSlotNum];
if ((connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.type) != null && (connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.type) != "*") {
return {
type: connectedSlot.type,
label: connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.label,
name: connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.name,
};
}
else if ((connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.type) == "*") {
return followConnectionUntilType(connectedNode, dir);
}
}
return null;
}
export async function replaceNode(existingNode, typeOrNewNode, inputNameMap) {
const existingCtor = existingNode.constructor;
const newNode = typeof typeOrNewNode === "string" ? LiteGraph.createNode(typeOrNewNode) : typeOrNewNode;
if (existingNode.title != existingCtor.title) {
newNode.title = existingNode.title;
}
newNode.pos = [...existingNode.pos];
newNode.properties = { ...existingNode.properties };
const oldComputeSize = [...existingNode.computeSize()];
const oldSize = [
existingNode.size[0] === oldComputeSize[0] ? null : existingNode.size[0],
existingNode.size[1] === oldComputeSize[1] ? null : existingNode.size[1],
];
let setSizeIters = 0;
const setSizeFn = () => {
const newComputesize = newNode.computeSize();
newNode.size[0] = Math.max(oldSize[0] || 0, newComputesize[0]);
newNode.size[1] = Math.max(oldSize[1] || 0, newComputesize[1]);
setSizeIters++;
if (setSizeIters > 10) {
requestAnimationFrame(setSizeFn);
}
};
setSizeFn();
const links = [];
const graph = existingNode.graph || app.graph;
for (const [index, output] of existingNode.outputs.entries()) {
for (const linkId of output.links || []) {
const link = graph.links[linkId];
if (!link)
continue;
const targetNode = graph.getNodeById(link.target_id);
links.push({ node: newNode, slot: output.name, targetNode, targetSlot: link.target_slot });
}
}
for (const [index, input] of existingNode.inputs.entries()) {
const linkId = input.link;
if (linkId) {
const link = graph.links[linkId];
const originNode = graph.getNodeById(link.origin_id);
links.push({
node: originNode,
slot: link.origin_slot,
targetNode: newNode,
targetSlot: (inputNameMap === null || inputNameMap === void 0 ? void 0 : inputNameMap.has(input.name))
? inputNameMap.get(input.name)
: input.name || index,
});
}
}
graph.add(newNode);
await wait();
for (const link of links) {
link.node.connect(link.slot, link.targetNode, link.targetSlot);
}
await wait();
graph.remove(existingNode);
newNode.size = newNode.computeSize();
newNode.setDirtyCanvas(true, true);
return newNode;
}
export function getOriginNodeByLink(linkId) {
let node = null;
if (linkId != null) {
const link = getLinkById(linkId);
node = (link != null && getNodeById(link.origin_id)) || null;
}
return node;
}
export function getLinkById(linkId) {
var _a, _b, _c;
if (linkId == null)
return null;
let link = (_a = app.graph.links[linkId]) !== null && _a !== void 0 ? _a : null;
link = (_c = link !== null && link !== void 0 ? link : (_b = app.canvas.getCurrentGraph()) === null || _b === void 0 ? void 0 : _b.links[linkId]) !== null && _c !== void 0 ? _c : null;
return link || findSomethingInAllSubgraphs((subgraph) => { var _a; return (_a = subgraph === null || subgraph === void 0 ? void 0 : subgraph.links[linkId]) !== null && _a !== void 0 ? _a : null; });
}
export function getNodeById(id) {
var _a, _b;
if (id == null)
return null;
let node = app.graph.getNodeById(id);
node = (_b = node !== null && node !== void 0 ? node : (_a = app.canvas.getCurrentGraph()) === null || _a === void 0 ? void 0 : _a.getNodeById(id)) !== null && _b !== void 0 ? _b : null;
return node || findSomethingInAllSubgraphs((subgraph) => { var _a; return (_a = subgraph === null || subgraph === void 0 ? void 0 : subgraph.getNodeById(id)) !== null && _a !== void 0 ? _a : null; });
}
export function findFromNodeForSubgraph(subgraphId) {
var _a;
const node = (_a = findSomethingInAllSubgraphs((subgraph) => subgraph.nodes
.filter((node) => node.isSubgraphNode())
.find((node) => node.subgraph.id === subgraphId))) !== null && _a !== void 0 ? _a : null;
return node;
}
function findSomethingInAllSubgraphs(fn) {
var _a, _b;
const rootGraph = (_a = app.rootGraph) !== null && _a !== void 0 ? _a : app.graph.rootGraph;
const subgraphs = [rootGraph, ...(_b = rootGraph.subgraphs) === null || _b === void 0 ? void 0 : _b.values()];
for (const subgraph of subgraphs) {
const thing = fn(subgraph);
if (thing)
return thing;
}
return null;
}
export function applyMixins(original, constructors) {
constructors.forEach((baseCtor) => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
Object.defineProperty(original.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name) || Object.create(null));
});
});
}
export function getSlotLinks(inputOrOutput) {
var _a;
const links = [];
if (!inputOrOutput) {
return links;
}
if ((_a = inputOrOutput.links) === null || _a === void 0 ? void 0 : _a.length) {
const output = inputOrOutput;
for (const linkId of output.links || []) {
const link = app.graph.links[linkId];
if (link) {
links.push({ id: linkId, link: link });
}
}
}
if (inputOrOutput.link) {
const input = inputOrOutput;
const link = app.graph.links[input.link];
if (link) {
links.push({ id: input.link, link: link });
}
}
return links;
}
export async function matchLocalSlotsToServer(node, direction, serverNodeData) {
var _a, _b, _c;
const serverSlotNames = direction == IoDirection.INPUT
? Object.keys(((_a = serverNodeData.input) === null || _a === void 0 ? void 0 : _a.optional) || {})
: serverNodeData.output_name;
const serverSlotTypes = direction == IoDirection.INPUT
? Object.values(((_b = serverNodeData.input) === null || _b === void 0 ? void 0 : _b.optional) || {}).map((i) => i[0])
: serverNodeData.output;
const slots = direction == IoDirection.INPUT ? node.inputs : node.outputs;
let firstIndex = slots.findIndex((o, i) => i !== serverSlotNames.indexOf(o.name));
if (firstIndex > -1) {
const links = {};
slots.map((slot) => {
var _a;
links[slot.name] = links[slot.name] || [];
(_a = links[slot.name]) === null || _a === void 0 ? void 0 : _a.push(...getSlotLinks(slot));
});
for (const [index, serverSlotName] of serverSlotNames.entries()) {
const currentNodeSlot = slots.map((s) => s.name).indexOf(serverSlotName);
if (currentNodeSlot > -1) {
if (currentNodeSlot != index) {
const splicedItem = slots.splice(currentNodeSlot, 1)[0];
slots.splice(index, 0, splicedItem);
}
}
else if (currentNodeSlot === -1) {
const splicedItem = {
name: serverSlotName,
type: serverSlotTypes[index],
links: [],
};
slots.splice(index, 0, splicedItem);
}
}
if (slots.length > serverSlotNames.length) {
for (let i = slots.length - 1; i > serverSlotNames.length - 1; i--) {
if (direction == IoDirection.INPUT) {
node.disconnectInput(i);
node.removeInput(i);
}
else {
node.disconnectOutput(i);
node.removeOutput(i);
}
}
}
for (const [name, slotLinks] of Object.entries(links)) {
let currentNodeSlot = slots.map((s) => s.name).indexOf(name);
if (currentNodeSlot > -1) {
for (const linkData of slotLinks) {
if (direction == IoDirection.INPUT) {
linkData.link.target_slot = currentNodeSlot;
}
else {
linkData.link.origin_slot = currentNodeSlot;
const nextNode = app.graph.getNodeById(linkData.link.target_id);
if (nextNode && ((_c = nextNode.constructor) === null || _c === void 0 ? void 0 : _c.type.includes("Reroute"))) {
nextNode.stabilize && nextNode.stabilize();
}
}
}
}
}
}
}
export function isValidConnection(ioA, ioB) {
if (!ioA || !ioB) {
return false;
}
const typeA = String(ioA.type);
const typeB = String(ioB.type);
let isValid = LiteGraph.isValidConnection(typeA, typeB);
if (!isValid) {
let areCombos = (typeA.includes(",") && typeB === "COMBO") || (typeA === "COMBO" && typeB.includes(","));
if (areCombos) {
const nameA = ioA.name.toUpperCase().replace("_NAME", "").replace("CKPT", "MODEL");
const nameB = ioB.name.toUpperCase().replace("_NAME", "").replace("CKPT", "MODEL");
isValid = nameA.includes(nameB) || nameB.includes(nameA);
}
}
return isValid;
}
const oldIsValidConnection = LiteGraph.isValidConnection;
LiteGraph.isValidConnection = function (typeA, typeB) {
let isValid = oldIsValidConnection.call(LiteGraph, typeA, typeB);
if (!isValid) {
typeA = String(typeA);
typeB = String(typeB);
let areCombos = (typeA.includes(",") && typeB === "COMBO") || (typeA === "COMBO" && typeB.includes(","));
isValid = areCombos;
}
return isValid;
};
export function getOutputNodes(nodes) {
return ((nodes === null || nodes === void 0 ? void 0 : nodes.filter((n) => {
var _a;
return (n.mode != LiteGraph.NEVER && ((_a = n.constructor.nodeData) === null || _a === void 0 ? void 0 : _a.output_node));
})) || []);
}
export function changeModeOfNodes(nodeOrNodes, mode) {
reduceNodesDepthFirst(nodeOrNodes, (n) => {
n.mode = mode;
});
}
export function reduceNodesDepthFirst(nodeOrNodes, reduceFn, reduceTo) {
var _a;
const nodes = Array.isArray(nodeOrNodes) ? nodeOrNodes : [nodeOrNodes];
const stack = nodes.map((node) => ({ node }));
while (stack.length > 0) {
const { node } = stack.pop();
const result = reduceFn(node, reduceTo);
if (result !== undefined && result !== reduceTo) {
reduceTo = result;
}
if (((_a = node.isSubgraphNode) === null || _a === void 0 ? void 0 : _a.call(node)) && node.subgraph) {
const children = node.subgraph.nodes;
for (let i = children.length - 1; i >= 0; i--) {
stack.push({ node: children[i] });
}
}
}
return reduceTo;
}
export function getGroupNodes(group) {
return Array.from(group._children).filter((c) => c instanceof LGraphNode);
}
export function getGraphDependantNodeKey(node) {
var _a;
const graph = (_a = node.graph) !== null && _a !== void 0 ? _a : app.graph;
return `${graph.id}:${node.id}`;
}
export function getFullColor(color, liteGraphKey = "color") {
if (!color) {
return "";
}
if (LGraphCanvas.node_colors[color]) {
color = LGraphCanvas.node_colors[color][liteGraphKey];
}
color = color.replace("#", "").toLocaleLowerCase();
if (color.length === 3) {
color = color.replace(/(.)(.)(.)/, "$1$1$2$2$3$3");
}
return `#${color}`;
}

View File

@@ -0,0 +1,225 @@
import { app } from "../../scripts/app.js";
function binarySearch(max, getValue, match) {
let min = 0;
while (min <= max) {
let guess = Math.floor((min + max) / 2);
const compareVal = getValue(guess);
if (compareVal === match)
return guess;
if (compareVal < match)
min = guess + 1;
else
max = guess - 1;
}
return max;
}
export function fitString(ctx, str, maxWidth) {
let width = ctx.measureText(str).width;
const ellipsis = "…";
const ellipsisWidth = measureText(ctx, ellipsis);
if (width <= maxWidth || width <= ellipsisWidth) {
return str;
}
const index = binarySearch(str.length, (guess) => measureText(ctx, str.substring(0, guess)), maxWidth - ellipsisWidth);
return str.substring(0, index) + ellipsis;
}
export function measureText(ctx, str) {
return ctx.measureText(str).width;
}
export function isLowQuality() {
var _a;
const canvas = app.canvas;
return (((_a = canvas.ds) === null || _a === void 0 ? void 0 : _a.scale) || 1) <= 0.5;
}
export function drawNodeWidget(ctx, options) {
const lowQuality = isLowQuality();
const data = {
width: options.size[0],
height: options.size[1],
posY: options.pos[1],
lowQuality,
margin: 15,
colorOutline: LiteGraph.WIDGET_OUTLINE_COLOR,
colorBackground: LiteGraph.WIDGET_BGCOLOR,
colorText: LiteGraph.WIDGET_TEXT_COLOR,
colorTextSecondary: LiteGraph.WIDGET_SECONDARY_TEXT_COLOR,
};
ctx.strokeStyle = options.colorStroke || data.colorOutline;
ctx.fillStyle = options.colorBackground || data.colorBackground;
ctx.beginPath();
ctx.roundRect(data.margin, data.posY, data.width - data.margin * 2, data.height, lowQuality ? [0] : options.borderRadius ? [options.borderRadius] : [options.size[1] * 0.5]);
ctx.fill();
if (!lowQuality) {
ctx.stroke();
}
return data;
}
export function drawRoundedRectangle(ctx, options) {
const lowQuality = isLowQuality();
options = { ...options };
ctx.save();
ctx.strokeStyle = options.colorStroke || LiteGraph.WIDGET_OUTLINE_COLOR;
ctx.fillStyle = options.colorBackground || LiteGraph.WIDGET_BGCOLOR;
ctx.beginPath();
ctx.roundRect(...options.pos, ...options.size, lowQuality ? [0] : options.borderRadius ? [options.borderRadius] : [options.size[1] * 0.5]);
ctx.fill();
!lowQuality && ctx.stroke();
ctx.restore();
}
export function drawNumberWidgetPart(ctx, options) {
const arrowWidth = 9;
const arrowHeight = 10;
const innerMargin = 3;
const numberWidth = 32;
const xBoundsArrowLess = [0, 0];
const xBoundsNumber = [0, 0];
const xBoundsArrowMore = [0, 0];
ctx.save();
let posX = options.posX;
const { posY, height, value, textColor } = options;
const midY = posY + height / 2;
if (options.direction === -1) {
posX = posX - arrowWidth - innerMargin - numberWidth - innerMargin - arrowWidth;
}
ctx.fill(new Path2D(`M ${posX} ${midY} l ${arrowWidth} ${arrowHeight / 2} l 0 -${arrowHeight} L ${posX} ${midY} z`));
xBoundsArrowLess[0] = posX;
xBoundsArrowLess[1] = arrowWidth;
posX += arrowWidth + innerMargin;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const oldTextcolor = ctx.fillStyle;
if (textColor) {
ctx.fillStyle = textColor;
}
ctx.fillText(fitString(ctx, value.toFixed(2), numberWidth), posX + numberWidth / 2, midY);
ctx.fillStyle = oldTextcolor;
xBoundsNumber[0] = posX;
xBoundsNumber[1] = numberWidth;
posX += numberWidth + innerMargin;
ctx.fill(new Path2D(`M ${posX} ${midY - arrowHeight / 2} l ${arrowWidth} ${arrowHeight / 2} l -${arrowWidth} ${arrowHeight / 2} v -${arrowHeight} z`));
xBoundsArrowMore[0] = posX;
xBoundsArrowMore[1] = arrowWidth;
ctx.restore();
return [xBoundsArrowLess, xBoundsNumber, xBoundsArrowMore];
}
drawNumberWidgetPart.WIDTH_TOTAL = 9 + 3 + 32 + 3 + 9;
export function drawTogglePart(ctx, options) {
const lowQuality = isLowQuality();
ctx.save();
const { posX, posY, height, value } = options;
const toggleRadius = height * 0.36;
const toggleBgWidth = height * 1.5;
if (!lowQuality) {
ctx.beginPath();
ctx.roundRect(posX + 4, posY + 4, toggleBgWidth - 8, height - 8, [height * 0.5]);
ctx.globalAlpha = app.canvas.editor_alpha * 0.25;
ctx.fillStyle = "rgba(255,255,255,0.45)";
ctx.fill();
ctx.globalAlpha = app.canvas.editor_alpha;
}
ctx.fillStyle = value === true ? "#89B" : "#888";
const toggleX = lowQuality || value === false
? posX + height * 0.5
: value === true
? posX + height
: posX + height * 0.75;
ctx.beginPath();
ctx.arc(toggleX, posY + height * 0.5, toggleRadius, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
return [posX, toggleBgWidth];
}
export function drawInfoIcon(ctx, x, y, size = 12) {
ctx.save();
ctx.beginPath();
ctx.roundRect(x, y, size, size, [size * 0.1]);
ctx.fillStyle = "#2f82ec";
ctx.strokeStyle = "#0f2a5e";
ctx.fill();
ctx.strokeStyle = "#FFF";
ctx.lineWidth = 2;
const midX = x + size / 2;
const serifSize = size * 0.175;
ctx.stroke(new Path2D(`
M ${midX} ${y + size * 0.15}
v 2
M ${midX - serifSize} ${y + size * 0.45}
h ${serifSize}
v ${size * 0.325}
h ${serifSize}
h -${serifSize * 2}
`));
ctx.restore();
}
export function drawPlusIcon(ctx, x, midY, size = 12) {
ctx.save();
const s = size / 3;
const plus = new Path2D(`
M ${x} ${midY + s / 2}
v-${s} h${s} v-${s} h${s}
v${s} h${s} v${s} h-${s}
v${s} h-${s} v-${s} h-${s}
z
`);
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.fillStyle = "#3a3";
ctx.strokeStyle = "#383";
ctx.fill(plus);
ctx.stroke(plus);
ctx.restore();
}
export function drawWidgetButton(ctx, options, text = null, isMouseDownedAndOver = false) {
var _a;
const borderRadius = isLowQuality() ? 0 : ((_a = options.borderRadius) !== null && _a !== void 0 ? _a : 4);
ctx.save();
if (!isLowQuality() && !isMouseDownedAndOver) {
drawRoundedRectangle(ctx, {
size: [options.size[0] - 2, options.size[1]],
pos: [options.pos[0] + 1, options.pos[1] + 1],
borderRadius,
colorBackground: "#000000aa",
colorStroke: "#000000aa",
});
}
drawRoundedRectangle(ctx, {
size: options.size,
pos: [options.pos[0], options.pos[1] + (isMouseDownedAndOver ? 1 : 0)],
borderRadius,
colorBackground: isMouseDownedAndOver ? "#444" : LiteGraph.WIDGET_BGCOLOR,
colorStroke: "transparent",
});
if (isLowQuality()) {
ctx.restore();
return;
}
if (!isMouseDownedAndOver) {
drawRoundedRectangle(ctx, {
size: [options.size[0] - 0.75, options.size[1] - 0.75],
pos: options.pos,
borderRadius: borderRadius - 0.5,
colorBackground: "transparent",
colorStroke: "#00000044",
});
drawRoundedRectangle(ctx, {
size: [options.size[0] - 0.75, options.size[1] - 0.75],
pos: [options.pos[0] + 0.75, options.pos[1] + 0.75],
borderRadius: borderRadius - 0.5,
colorBackground: "transparent",
colorStroke: "#ffffff11",
});
}
drawRoundedRectangle(ctx, {
size: options.size,
pos: [options.pos[0], options.pos[1] + (isMouseDownedAndOver ? 1 : 0)],
borderRadius,
colorBackground: "transparent",
});
if (!isLowQuality() && text) {
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;
ctx.fillText(text, options.size[0] / 2, options.pos[1] + options.size[1] / 2 + (isMouseDownedAndOver ? 1 : 0));
}
ctx.restore();
}

View File

@@ -0,0 +1,177 @@
import { app } from "../../scripts/app.js";
function isPrimitiveNode(node) {
return node.type === "PrimitiveNode";
}
function getWidgetGetConfigSymbols(slot) {
var _a;
const widget = slot === null || slot === void 0 ? void 0 : slot.widget;
if (!widget)
return {};
const syms = Object.getOwnPropertySymbols(widget || {});
for (const sym of syms) {
const symVal = widget[sym];
const isGetConfig = typeof symVal === "function";
let maybeCfg = isGetConfig ? symVal() : symVal;
if (Array.isArray(maybeCfg) &&
maybeCfg.length >= 2 &&
typeof maybeCfg[0] === "string" &&
(maybeCfg[0] === "*" || typeof ((_a = maybeCfg[1]) === null || _a === void 0 ? void 0 : _a.type) === "string")) {
return isGetConfig ? { GET_CONFIG: sym } : { CONFIG: sym };
}
}
return {};
}
export function getWidgetConfig(slot) {
var _a, _b, _c;
const configSyms = getWidgetGetConfigSymbols(slot);
const widget = slot.widget || {};
return ((_c = (_a = (configSyms.CONFIG && widget[configSyms.CONFIG])) !== null && _a !== void 0 ? _a : (configSyms.GET_CONFIG && ((_b = widget[configSyms.GET_CONFIG]) === null || _b === void 0 ? void 0 : _b.call(widget)))) !== null && _c !== void 0 ? _c : ["*", {}]);
}
export function setWidgetConfig(slot, config) {
var _a;
if (!(slot === null || slot === void 0 ? void 0 : slot.widget))
return;
if (config) {
const configSyms = getWidgetGetConfigSymbols(slot);
const widget = slot.widget || {};
if (configSyms.GET_CONFIG) {
widget[configSyms.GET_CONFIG] = () => config;
}
else if (configSyms.CONFIG) {
widget[configSyms.CONFIG] = config;
}
else {
console.error("Cannot set widget Config. This is due to ComfyUI removing the ability to call legacy " +
"JavaScript APIs that are now deprecated without new, supported APIs. It's possible " +
"some things in rgthree-comfy do not work correctly. If you see this, please file a bug.");
}
}
else {
delete slot.widget;
}
if ("link" in slot) {
const link = app.graph.links[(_a = slot === null || slot === void 0 ? void 0 : slot.link) !== null && _a !== void 0 ? _a : -1];
if (link) {
const originNode = app.graph.getNodeById(link.origin_id);
if (originNode && isPrimitiveNode(originNode)) {
if (config) {
originNode.recreateWidget();
}
else if (!app.configuringGraph) {
originNode.disconnectOutput(0);
originNode.onLastDisconnect();
}
}
}
}
}
export function mergeIfValid(output, config2) {
var _a;
const config1 = getWidgetConfig(output);
const customSpec = mergeInputSpec(config1, config2);
if (customSpec) {
setWidgetConfig(output, customSpec);
}
return (_a = customSpec === null || customSpec === void 0 ? void 0 : customSpec[1]) !== null && _a !== void 0 ? _a : null;
}
const mergeInputSpec = (spec1, spec2) => {
const type1 = getInputSpecType(spec1);
const type2 = getInputSpecType(spec2);
if (type1 !== type2) {
return null;
}
if (isIntInputSpec(spec1) || isFloatInputSpec(spec1)) {
return mergeNumericInputSpec(spec1, spec2);
}
if (isComboInputSpec(spec1)) {
return mergeComboInputSpec(spec1, spec2);
}
return mergeCommonInputSpec(spec1, spec2);
};
function getInputSpecType(inputSpec) {
return isComboInputSpec(inputSpec) ? "COMBO" : inputSpec[0];
}
function isComboInputSpecV1(inputSpec) {
return Array.isArray(inputSpec[0]);
}
function isIntInputSpec(inputSpec) {
return inputSpec[0] === "INT";
}
function isFloatInputSpec(inputSpec) {
return inputSpec[0] === "FLOAT";
}
function isComboInputSpecV2(inputSpec) {
return inputSpec[0] === "COMBO";
}
function isComboInputSpec(inputSpec) {
return isComboInputSpecV1(inputSpec) || isComboInputSpecV2(inputSpec);
}
const getRange = (options) => {
var _a, _b;
const min = (_a = options.min) !== null && _a !== void 0 ? _a : -Infinity;
const max = (_b = options.max) !== null && _b !== void 0 ? _b : Infinity;
return { min, max };
};
const mergeNumericInputSpec = (spec1, spec2) => {
var _a, _b, _c, _d;
const type = spec1[0];
const options1 = (_a = spec1[1]) !== null && _a !== void 0 ? _a : {};
const options2 = (_b = spec2[1]) !== null && _b !== void 0 ? _b : {};
const range1 = getRange(options1);
const range2 = getRange(options2);
if (range1.min > range2.max || range1.max < range2.min) {
return null;
}
const step1 = (_c = options1.step) !== null && _c !== void 0 ? _c : 1;
const step2 = (_d = options2.step) !== null && _d !== void 0 ? _d : 1;
const mergedOptions = {
min: Math.max(range1.min, range2.min),
max: Math.min(range1.max, range2.max),
step: lcm(step1, step2),
};
return mergeCommonInputSpec([type, { ...options1, ...mergedOptions }], [type, { ...options2, ...mergedOptions }]);
};
const mergeComboInputSpec = (spec1, spec2) => {
var _a, _b;
const options1 = (_a = spec1[1]) !== null && _a !== void 0 ? _a : {};
const options2 = (_b = spec2[1]) !== null && _b !== void 0 ? _b : {};
const comboOptions1 = getComboSpecComboOptions(spec1);
const comboOptions2 = getComboSpecComboOptions(spec2);
const intersection = comboOptions1.filter((value) => comboOptions2.includes(value));
if (intersection.length === 0) {
return null;
}
return mergeCommonInputSpec(["COMBO", { ...options1, options: intersection }], ["COMBO", { ...options2, options: intersection }]);
};
const mergeCommonInputSpec = (spec1, spec2) => {
var _a, _b;
const type = getInputSpecType(spec1);
const options1 = (_a = spec1[1]) !== null && _a !== void 0 ? _a : {};
const options2 = (_b = spec2[1]) !== null && _b !== void 0 ? _b : {};
const compareKeys = [...new Set([...Object.keys(options1), ...Object.keys(options2)])].filter((key) => !IGNORE_KEYS.has(key));
const mergeIsValid = compareKeys.every((key) => {
const value1 = options1[key];
const value2 = options2[key];
return value1 === value2 || (value1 == null && value2 == null);
});
return mergeIsValid ? [type, { ...options1, ...options2 }] : null;
};
const IGNORE_KEYS = new Set([
"default",
"forceInput",
"defaultInput",
"control_after_generate",
"multiline",
"tooltip",
"dynamicPrompts",
]);
function getComboSpecComboOptions(inputSpec) {
var _a, _b;
return (_b = (isComboInputSpecV2(inputSpec) ? (_a = inputSpec[1]) === null || _a === void 0 ? void 0 : _a.options : inputSpec[0])) !== null && _b !== void 0 ? _b : [];
}
const lcm = (a, b) => {
return Math.abs(a * b) / gcd(a, b);
};
const gcd = (a, b) => {
return b === 0 ? a : gcd(b, a % b);
};

View File

@@ -0,0 +1,14 @@
export function removeUnusedInputsFromEnd(node, minNumber = 1, nameMatch) {
var _a;
if (node.removed)
return;
for (let i = node.inputs.length - 1; i >= minNumber; i--) {
if (!((_a = node.inputs[i]) === null || _a === void 0 ? void 0 : _a.link)) {
if (!nameMatch || nameMatch.test(node.inputs[i].name)) {
node.removeInput(i);
}
continue;
}
break;
}
}

View File

@@ -0,0 +1,55 @@
import { app } from "../../scripts/app.js";
import { rgthreeApi } from "../../rgthree/common/rgthree_api.js";
const PASS_THROUGH = function (item) {
return item;
};
export async function showLoraChooser(event, callback, parentMenu, loras) {
var _a, _b;
const canvas = app.canvas;
if (!loras) {
loras = ["None", ...(await rgthreeApi.getLoras().then((loras) => loras.map((l) => l.file)))];
}
new LiteGraph.ContextMenu(loras, {
event: event,
parentMenu: parentMenu != null ? parentMenu : undefined,
title: "Choose a lora",
scale: Math.max(1, (_b = (_a = canvas.ds) === null || _a === void 0 ? void 0 : _a.scale) !== null && _b !== void 0 ? _b : 1),
className: "dark",
callback,
});
}
export function showNodesChooser(event, mapFn, callback, parentMenu) {
var _a, _b;
const canvas = app.canvas;
const nodesOptions = app.graph._nodes
.map(mapFn)
.filter((e) => e != null);
nodesOptions.sort((a, b) => {
return a.value - b.value;
});
new LiteGraph.ContextMenu(nodesOptions, {
event: event,
parentMenu,
title: "Choose a node id",
scale: Math.max(1, (_b = (_a = canvas.ds) === null || _a === void 0 ? void 0 : _a.scale) !== null && _b !== void 0 ? _b : 1),
className: "dark",
callback,
});
}
export function showWidgetsChooser(event, node, mapFn, callback, parentMenu) {
var _a, _b;
const options = (node.widgets || [])
.map(mapFn)
.filter((e) => e != null);
if (options.length) {
const canvas = app.canvas;
new LiteGraph.ContextMenu(options, {
event,
parentMenu,
title: "Choose an input/widget",
scale: Math.max(1, (_b = (_a = canvas.ds) === null || _a === void 0 ? void 0 : _a.scale) !== null && _b !== void 0 ? _b : 1),
className: "dark",
callback,
});
}
}

View File

@@ -0,0 +1,291 @@
import { app } from "../../scripts/app.js";
import { drawNodeWidget, drawWidgetButton, fitString, isLowQuality } from "./utils_canvas.js";
export function drawLabelAndValue(ctx, label, value, width, posY, height, options) {
var _a;
const outerMargin = 15;
const innerMargin = 10;
const midY = posY + height / 2;
ctx.save();
ctx.textAlign = "left";
ctx.textBaseline = "middle";
ctx.fillStyle = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR;
const labelX = outerMargin + innerMargin + ((_a = options === null || options === void 0 ? void 0 : options.offsetLeft) !== null && _a !== void 0 ? _a : 0);
ctx.fillText(label, labelX, midY);
const valueXLeft = labelX + ctx.measureText(label).width + 7;
const valueXRight = width - (outerMargin + innerMargin);
ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;
ctx.textAlign = "right";
ctx.fillText(fitString(ctx, value, valueXRight - valueXLeft), valueXRight, midY);
ctx.restore();
}
export class RgthreeBaseWidget {
constructor(name) {
this.type = "custom";
this.options = {};
this.y = 0;
this.last_y = 0;
this.mouseDowned = null;
this.isMouseDownedAndOver = false;
this.hitAreas = {};
this.downedHitAreasForMove = [];
this.downedHitAreasForClick = [];
this.name = name;
}
serializeValue(node, index) {
return this.value;
}
clickWasWithinBounds(pos, bounds) {
let xStart = bounds[0];
let xEnd = xStart + (bounds.length > 2 ? bounds[2] : bounds[1]);
const clickedX = pos[0] >= xStart && pos[0] <= xEnd;
if (bounds.length === 2) {
return clickedX;
}
return clickedX && pos[1] >= bounds[1] && pos[1] <= bounds[1] + bounds[3];
}
mouse(event, pos, node) {
var _a, _b, _c;
const canvas = app.canvas;
if (event.type == "pointerdown") {
this.mouseDowned = [...pos];
this.isMouseDownedAndOver = true;
this.downedHitAreasForMove.length = 0;
this.downedHitAreasForClick.length = 0;
let anyHandled = false;
for (const part of Object.values(this.hitAreas)) {
if (this.clickWasWithinBounds(pos, part.bounds)) {
if (part.onMove) {
this.downedHitAreasForMove.push(part);
}
if (part.onClick) {
this.downedHitAreasForClick.push(part);
}
if (part.onDown) {
const thisHandled = part.onDown.apply(this, [event, pos, node, part]);
anyHandled = anyHandled || thisHandled == true;
}
part.wasMouseClickedAndIsOver = true;
}
}
return (_a = this.onMouseDown(event, pos, node)) !== null && _a !== void 0 ? _a : anyHandled;
}
if (event.type == "pointerup") {
if (!this.mouseDowned)
return true;
this.downedHitAreasForMove.length = 0;
const wasMouseDownedAndOver = this.isMouseDownedAndOver;
this.cancelMouseDown();
let anyHandled = false;
for (const part of Object.values(this.hitAreas)) {
if (part.onUp && this.clickWasWithinBounds(pos, part.bounds)) {
const thisHandled = part.onUp.apply(this, [event, pos, node, part]);
anyHandled = anyHandled || thisHandled == true;
}
part.wasMouseClickedAndIsOver = false;
}
for (const part of this.downedHitAreasForClick) {
if (this.clickWasWithinBounds(pos, part.bounds)) {
const thisHandled = part.onClick.apply(this, [event, pos, node, part]);
anyHandled = anyHandled || thisHandled == true;
}
}
this.downedHitAreasForClick.length = 0;
if (wasMouseDownedAndOver) {
const thisHandled = this.onMouseClick(event, pos, node);
anyHandled = anyHandled || thisHandled == true;
}
return (_b = this.onMouseUp(event, pos, node)) !== null && _b !== void 0 ? _b : anyHandled;
}
if (event.type == "pointermove") {
this.isMouseDownedAndOver = !!this.mouseDowned;
if (this.mouseDowned &&
(pos[0] < 15 ||
pos[0] > node.size[0] - 15 ||
pos[1] < this.last_y ||
pos[1] > this.last_y + LiteGraph.NODE_WIDGET_HEIGHT)) {
this.isMouseDownedAndOver = false;
}
for (const part of Object.values(this.hitAreas)) {
if (this.downedHitAreasForMove.includes(part)) {
part.onMove.apply(this, [event, pos, node, part]);
}
if (this.downedHitAreasForClick.includes(part)) {
part.wasMouseClickedAndIsOver = this.clickWasWithinBounds(pos, part.bounds);
}
}
return (_c = this.onMouseMove(event, pos, node)) !== null && _c !== void 0 ? _c : true;
}
return false;
}
cancelMouseDown() {
this.mouseDowned = null;
this.isMouseDownedAndOver = false;
this.downedHitAreasForMove.length = 0;
}
onMouseDown(event, pos, node) {
return;
}
onMouseUp(event, pos, node) {
return;
}
onMouseClick(event, pos, node) {
return;
}
onMouseMove(event, pos, node) {
return;
}
}
export class RgthreeBetterButtonWidget extends RgthreeBaseWidget {
constructor(name, mouseClickCallback, label) {
super(name);
this.type = "custom";
this.value = "";
this.label = "";
this.mouseClickCallback = mouseClickCallback;
this.label = label || name;
}
draw(ctx, node, width, y, height) {
drawWidgetButton(ctx, { size: [width - 30, height], pos: [15, y] }, this.label, this.isMouseDownedAndOver);
}
onMouseClick(event, pos, node) {
return this.mouseClickCallback(event, pos, node);
}
}
export class RgthreeBetterTextWidget extends RgthreeBaseWidget {
constructor(name, value) {
super(name);
this.name = name;
this.value = value;
}
draw(ctx, node, width, y, height) {
const widgetData = drawNodeWidget(ctx, { size: [width, height], pos: [15, y] });
if (!widgetData.lowQuality) {
drawLabelAndValue(ctx, this.name, this.value, width, y, height);
}
}
mouse(event, pos, node) {
const canvas = app.canvas;
if (event.type == "pointerdown") {
canvas.prompt("Label", this.value, (v) => (this.value = v), event);
return true;
}
return false;
}
}
export class RgthreeDividerWidget extends RgthreeBaseWidget {
constructor(widgetOptions) {
super("divider");
this.value = {};
this.options = { serialize: false };
this.type = "custom";
this.widgetOptions = {
marginTop: 7,
marginBottom: 7,
marginLeft: 15,
marginRight: 15,
color: LiteGraph.WIDGET_OUTLINE_COLOR,
thickness: 1,
};
Object.assign(this.widgetOptions, widgetOptions || {});
}
draw(ctx, node, width, posY, h) {
if (this.widgetOptions.thickness) {
ctx.strokeStyle = this.widgetOptions.color;
const x = this.widgetOptions.marginLeft;
const y = posY + this.widgetOptions.marginTop;
const w = width - this.widgetOptions.marginLeft - this.widgetOptions.marginRight;
ctx.stroke(new Path2D(`M ${x} ${y} h ${w}`));
}
}
computeSize(width) {
return [
width,
this.widgetOptions.marginTop + this.widgetOptions.marginBottom + this.widgetOptions.thickness,
];
}
}
export class RgthreeLabelWidget extends RgthreeBaseWidget {
constructor(name, widgetOptions) {
super(name);
this.type = "custom";
this.options = { serialize: false };
this.value = "";
this.widgetOptions = {};
this.posY = 0;
Object.assign(this.widgetOptions, widgetOptions);
}
update(widgetOptions) {
Object.assign(this.widgetOptions, widgetOptions);
}
draw(ctx, node, width, posY, height) {
var _a;
this.posY = posY;
ctx.save();
let text = (_a = this.widgetOptions.text) !== null && _a !== void 0 ? _a : this.name;
if (typeof text === "function") {
text = text();
}
ctx.textAlign = this.widgetOptions.align || "left";
ctx.fillStyle = this.widgetOptions.color || LiteGraph.WIDGET_TEXT_COLOR;
const oldFont = ctx.font;
if (this.widgetOptions.italic) {
ctx.font = "italic " + ctx.font;
}
if (this.widgetOptions.size) {
ctx.font = ctx.font.replace(/\d+px/, `${this.widgetOptions.size}px`);
}
const midY = posY + height / 2;
ctx.textBaseline = "middle";
if (this.widgetOptions.align === "center") {
ctx.fillText(text, node.size[0] / 2, midY);
}
else {
ctx.fillText(text, 15, midY);
}
ctx.font = oldFont;
if (this.widgetOptions.actionLabel === "__PLUS_ICON__") {
const plus = new Path2D(`M${node.size[0] - 15 - 2} ${posY + 7} v4 h-4 v4 h-4 v-4 h-4 v-4 h4 v-4 h4 v4 h4 z`);
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.fillStyle = "#3a3";
ctx.strokeStyle = "#383";
ctx.fill(plus);
ctx.stroke(plus);
}
ctx.restore();
}
mouse(event, nodePos, node) {
if (event.type !== "pointerdown" ||
isLowQuality() ||
!this.widgetOptions.actionLabel ||
!this.widgetOptions.actionCallback) {
return false;
}
const pos = [nodePos[0], nodePos[1] - this.posY];
const rightX = node.size[0] - 15;
if (pos[0] > rightX || pos[0] < rightX - 16) {
return false;
}
this.widgetOptions.actionCallback(event);
return true;
}
}
export class RgthreeInvisibleWidget extends RgthreeBaseWidget {
constructor(name, type, value, serializeValueFn) {
super(name);
this.type = "custom";
this.value = value;
this.serializeValueFn = serializeValueFn;
}
draw() {
return;
}
computeSize(width) {
return [0, 0];
}
serializeValue(node, index) {
return this.serializeValueFn != null
? this.serializeValueFn(node, index)
: super.serializeValue(node, index);
}
}