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);
}
}

View File

@@ -0,0 +1,32 @@
const shimCache = new Map();
async function shimComfyUiModule(moduleName, prop) {
var _a, _b;
let module = shimCache.get(moduleName);
if (!module) {
if ((_a = window.comfyAPI) === null || _a === void 0 ? void 0 : _a[moduleName]) {
module = (_b = window.comfyAPI) === null || _b === void 0 ? void 0 : _b[moduleName];
}
else {
module = await import(`./comfyui_shim_${moduleName}.js`);
}
if (!module) {
throw new Error(`Module ${moduleName} could not be loaded.`);
}
shimCache.set(moduleName, module);
}
if (prop) {
if (!module[prop]) {
throw new Error(`Property ${prop} on module ${moduleName} could not be loaded.`);
}
return module[prop];
}
return module;
}
export async function getPngMetadata(file) {
const fn = (await shimComfyUiModule("pnginfo", "getPngMetadata"));
return fn(file);
}
export async function getWebpMetadata(file) {
const fn = (await shimComfyUiModule("pnginfo", "getWebpMetadata"));
return fn(file);
}

View File

@@ -0,0 +1,392 @@
import { rgthreeApi } from "./rgthree_api.js";
const api = {
async getEmbeddings() {
const resp = await rgthreeApi.fetchComfyApi('/embeddings', { cache: 'no-store' });
return await resp.json();
}
};
function getFromPngBuffer(buffer) {
const pngData = new Uint8Array(buffer);
const dataView = new DataView(pngData.buffer);
if (dataView.getUint32(0) !== 0x89504e47) {
console.error('Not a valid PNG file');
return;
}
let offset = 8;
let txt_chunks = {};
while (offset < pngData.length) {
const length = dataView.getUint32(offset);
const type = String.fromCharCode(...pngData.slice(offset + 4, offset + 8));
if (type === 'tEXt' || type == 'comf' || type === 'iTXt') {
let keyword_end = offset + 8;
while (pngData[keyword_end] !== 0) {
keyword_end++;
}
const keyword = String.fromCharCode(...pngData.slice(offset + 8, keyword_end));
const contentArraySegment = pngData.slice(keyword_end + 1, offset + 8 + length);
const contentJson = new TextDecoder('utf-8').decode(contentArraySegment);
txt_chunks[keyword] = contentJson;
}
offset += 12 + length;
}
return txt_chunks;
}
function getFromPngFile(file) {
return new Promise((r) => {
const reader = new FileReader();
reader.onload = (event) => {
r(getFromPngBuffer(event.target.result));
};
reader.readAsArrayBuffer(file);
});
}
function parseExifData(exifData) {
const isLittleEndian = String.fromCharCode(...exifData.slice(0, 2)) === 'II';
function readInt(offset, isLittleEndian, length) {
let arr = exifData.slice(offset, offset + length);
if (length === 2) {
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength).getUint16(0, isLittleEndian);
}
else if (length === 4) {
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength).getUint32(0, isLittleEndian);
}
throw new Error('Shouldn\'t get here.');
}
const ifdOffset = readInt(4, isLittleEndian, 4);
function parseIFD(offset) {
const numEntries = readInt(offset, isLittleEndian, 2);
const result = {};
for (let i = 0; i < numEntries; i++) {
const entryOffset = offset + 2 + i * 12;
const tag = readInt(entryOffset, isLittleEndian, 2);
const type = readInt(entryOffset + 2, isLittleEndian, 2);
const numValues = readInt(entryOffset + 4, isLittleEndian, 4);
const valueOffset = readInt(entryOffset + 8, isLittleEndian, 4);
let value;
if (type === 2) {
value = new TextDecoder('utf-8').decode(exifData.subarray(valueOffset, valueOffset + numValues - 1));
}
result[tag] = value;
}
return result;
}
const ifdData = parseIFD(ifdOffset);
return ifdData;
}
function splitValues(input) {
var output = {};
for (var key in input) {
var value = input[key];
var splitValues = value.split(':', 2);
output[splitValues[0]] = splitValues[1];
}
return output;
}
export function getPngMetadata(file) {
return getFromPngFile(file);
}
export function getWebpMetadata(file) {
return new Promise((r) => {
const reader = new FileReader();
reader.onload = (event) => {
const webp = new Uint8Array(event.target.result);
const dataView = new DataView(webp.buffer);
if (dataView.getUint32(0) !== 0x52494646 ||
dataView.getUint32(8) !== 0x57454250) {
console.error('Not a valid WEBP file');
r({});
return;
}
let offset = 12;
let txt_chunks = {};
while (offset < webp.length) {
const chunk_length = dataView.getUint32(offset + 4, true);
const chunk_type = String.fromCharCode(...webp.slice(offset, offset + 4));
if (chunk_type === 'EXIF') {
if (String.fromCharCode(...webp.slice(offset + 8, offset + 8 + 6)) ==
'Exif\0\0') {
offset += 6;
}
let data = parseExifData(webp.slice(offset + 8, offset + 8 + chunk_length));
for (var key in data) {
const value = data[key];
if (typeof value === 'string') {
const index = value.indexOf(':');
txt_chunks[value.slice(0, index)] = value.slice(index + 1);
}
}
break;
}
offset += 8 + chunk_length;
}
r(txt_chunks);
};
reader.readAsArrayBuffer(file);
});
}
export function getLatentMetadata(file) {
return new Promise((r) => {
const reader = new FileReader();
reader.onload = (event) => {
const safetensorsData = new Uint8Array(event.target.result);
const dataView = new DataView(safetensorsData.buffer);
let header_size = dataView.getUint32(0, true);
let offset = 8;
let header = JSON.parse(new TextDecoder().decode(safetensorsData.slice(offset, offset + header_size)));
r(header.__metadata__);
};
var slice = file.slice(0, 1024 * 1024 * 4);
reader.readAsArrayBuffer(slice);
});
}
export async function importA1111(graph, parameters) {
const p = parameters.lastIndexOf('\nSteps:');
if (p > -1) {
const embeddings = await api.getEmbeddings();
const opts = parameters
.substr(p)
.split('\n')[1]
.match(new RegExp('\\s*([^:]+:\\s*([^"\\{].*?|".*?"|\\{.*?\\}))\\s*(,|$)', 'g'))
.reduce((p, n) => {
const s = n.split(':');
if (s[1].endsWith(',')) {
s[1] = s[1].substr(0, s[1].length - 1);
}
p[s[0].trim().toLowerCase()] = s[1].trim();
return p;
}, {});
const p2 = parameters.lastIndexOf('\nNegative prompt:', p);
if (p2 > -1) {
let positive = parameters.substr(0, p2).trim();
let negative = parameters.substring(p2 + 18, p).trim();
const ckptNode = LiteGraph.createNode('CheckpointLoaderSimple');
const clipSkipNode = LiteGraph.createNode('CLIPSetLastLayer');
const positiveNode = LiteGraph.createNode('CLIPTextEncode');
const negativeNode = LiteGraph.createNode('CLIPTextEncode');
const samplerNode = LiteGraph.createNode('KSampler');
const imageNode = LiteGraph.createNode('EmptyLatentImage');
const vaeNode = LiteGraph.createNode('VAEDecode');
const vaeLoaderNode = LiteGraph.createNode('VAELoader');
const saveNode = LiteGraph.createNode('SaveImage');
let hrSamplerNode = null;
let hrSteps = null;
const ceil64 = (v) => Math.ceil(v / 64) * 64;
const getWidget = (node, name) => {
return node.widgets.find((w) => w.name === name);
};
const setWidgetValue = (node, name, value, isOptionPrefix) => {
const w = getWidget(node, name);
if (isOptionPrefix) {
const o = w.options.values.find((w) => w.startsWith(value));
if (o) {
w.value = o;
}
else {
console.warn(`Unknown value '${value}' for widget '${name}'`, node);
w.value = value;
}
}
else {
w.value = value;
}
};
const createLoraNodes = (clipNode, text, prevClip, prevModel) => {
const loras = [];
text = text.replace(/<lora:([^:]+:[^>]+)>/g, function (m, c) {
const s = c.split(':');
const weight = parseFloat(s[1]);
if (isNaN(weight)) {
console.warn('Invalid LORA', m);
}
else {
loras.push({ name: s[0], weight });
}
return '';
});
for (const l of loras) {
const loraNode = LiteGraph.createNode('LoraLoader');
graph.add(loraNode);
setWidgetValue(loraNode, 'lora_name', l.name, true);
setWidgetValue(loraNode, 'strength_model', l.weight);
setWidgetValue(loraNode, 'strength_clip', l.weight);
prevModel.node.connect(prevModel.index, loraNode, 0);
prevClip.node.connect(prevClip.index, loraNode, 1);
prevModel = { node: loraNode, index: 0 };
prevClip = { node: loraNode, index: 1 };
}
prevClip.node.connect(1, clipNode, 0);
prevModel.node.connect(0, samplerNode, 0);
if (hrSamplerNode) {
prevModel.node.connect(0, hrSamplerNode, 0);
}
return { text, prevModel, prevClip };
};
const replaceEmbeddings = (text) => {
if (!embeddings.length)
return text;
return text.replaceAll(new RegExp('\\b(' +
embeddings
.map((e) => e.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
.join('\\b|\\b') +
')\\b', 'ig'), 'embedding:$1');
};
const popOpt = (name) => {
const v = opts[name];
delete opts[name];
return v;
};
graph.clear();
graph.add(ckptNode);
graph.add(clipSkipNode);
graph.add(positiveNode);
graph.add(negativeNode);
graph.add(samplerNode);
graph.add(imageNode);
graph.add(vaeNode);
graph.add(vaeLoaderNode);
graph.add(saveNode);
ckptNode.connect(1, clipSkipNode, 0);
clipSkipNode.connect(0, positiveNode, 0);
clipSkipNode.connect(0, negativeNode, 0);
ckptNode.connect(0, samplerNode, 0);
positiveNode.connect(0, samplerNode, 1);
negativeNode.connect(0, samplerNode, 2);
imageNode.connect(0, samplerNode, 3);
vaeNode.connect(0, saveNode, 0);
samplerNode.connect(0, vaeNode, 0);
vaeLoaderNode.connect(0, vaeNode, 1);
const handlers = {
model(v) {
setWidgetValue(ckptNode, 'ckpt_name', v, true);
},
vae(v) {
setWidgetValue(vaeLoaderNode, 'vae_name', v, true);
},
'cfg scale'(v) {
setWidgetValue(samplerNode, 'cfg', +v);
},
'clip skip'(v) {
setWidgetValue(clipSkipNode, 'stop_at_clip_layer', -v);
},
sampler(v) {
let name = v.toLowerCase().replace('++', 'pp').replaceAll(' ', '_');
if (name.includes('karras')) {
name = name.replace('karras', '').replace(/_+$/, '');
setWidgetValue(samplerNode, 'scheduler', 'karras');
}
else {
setWidgetValue(samplerNode, 'scheduler', 'normal');
}
const w = getWidget(samplerNode, 'sampler_name');
const o = w.options.values.find((w) => w === name || w === 'sample_' + name);
if (o) {
setWidgetValue(samplerNode, 'sampler_name', o);
}
},
size(v) {
const wxh = v.split('x');
const w = ceil64(+wxh[0]);
const h = ceil64(+wxh[1]);
const hrUp = popOpt('hires upscale');
const hrSz = popOpt('hires resize');
hrSteps = popOpt('hires steps');
let hrMethod = popOpt('hires upscaler');
setWidgetValue(imageNode, 'width', w);
setWidgetValue(imageNode, 'height', h);
if (hrUp || hrSz) {
let uw, uh;
if (hrUp) {
uw = w * hrUp;
uh = h * hrUp;
}
else {
const s = hrSz.split('x');
uw = +s[0];
uh = +s[1];
}
let upscaleNode;
let latentNode;
if (hrMethod.startsWith('Latent')) {
latentNode = upscaleNode = LiteGraph.createNode('LatentUpscale');
graph.add(upscaleNode);
samplerNode.connect(0, upscaleNode, 0);
switch (hrMethod) {
case 'Latent (nearest-exact)':
hrMethod = 'nearest-exact';
break;
}
setWidgetValue(upscaleNode, 'upscale_method', hrMethod, true);
}
else {
const decode = LiteGraph.createNode('VAEDecodeTiled');
graph.add(decode);
samplerNode.connect(0, decode, 0);
vaeLoaderNode.connect(0, decode, 1);
const upscaleLoaderNode = LiteGraph.createNode('UpscaleModelLoader');
graph.add(upscaleLoaderNode);
setWidgetValue(upscaleLoaderNode, 'model_name', hrMethod, true);
const modelUpscaleNode = LiteGraph.createNode('ImageUpscaleWithModel');
graph.add(modelUpscaleNode);
decode.connect(0, modelUpscaleNode, 1);
upscaleLoaderNode.connect(0, modelUpscaleNode, 0);
upscaleNode = LiteGraph.createNode('ImageScale');
graph.add(upscaleNode);
modelUpscaleNode.connect(0, upscaleNode, 0);
const vaeEncodeNode = (latentNode =
LiteGraph.createNode('VAEEncodeTiled'));
graph.add(vaeEncodeNode);
upscaleNode.connect(0, vaeEncodeNode, 0);
vaeLoaderNode.connect(0, vaeEncodeNode, 1);
}
setWidgetValue(upscaleNode, 'width', ceil64(uw));
setWidgetValue(upscaleNode, 'height', ceil64(uh));
hrSamplerNode = LiteGraph.createNode('KSampler');
graph.add(hrSamplerNode);
ckptNode.connect(0, hrSamplerNode, 0);
positiveNode.connect(0, hrSamplerNode, 1);
negativeNode.connect(0, hrSamplerNode, 2);
latentNode.connect(0, hrSamplerNode, 3);
hrSamplerNode.connect(0, vaeNode, 0);
}
},
steps(v) {
setWidgetValue(samplerNode, 'steps', +v);
},
seed(v) {
setWidgetValue(samplerNode, 'seed', +v);
}
};
for (const opt in opts) {
if (opt in handlers) {
handlers[opt](popOpt(opt));
}
}
if (hrSamplerNode) {
setWidgetValue(hrSamplerNode, 'steps', hrSteps ? +hrSteps : getWidget(samplerNode, 'steps').value);
setWidgetValue(hrSamplerNode, 'cfg', getWidget(samplerNode, 'cfg').value);
setWidgetValue(hrSamplerNode, 'scheduler', getWidget(samplerNode, 'scheduler').value);
setWidgetValue(hrSamplerNode, 'sampler_name', getWidget(samplerNode, 'sampler_name').value);
setWidgetValue(hrSamplerNode, 'denoise', +(popOpt('denoising strength') || '1'));
}
let n = createLoraNodes(positiveNode, positive, { node: clipSkipNode, index: 0 }, { node: ckptNode, index: 0 });
positive = n.text;
n = createLoraNodes(negativeNode, negative, n.prevClip, n.prevModel);
negative = n.text;
setWidgetValue(positiveNode, 'text', replaceEmbeddings(positive));
setWidgetValue(negativeNode, 'text', replaceEmbeddings(negative));
graph.arrange();
for (const opt of [
'model hash',
'ensd',
'version',
'vae hash',
'ti hashes',
'lora hashes',
'hashes'
]) {
delete opts[opt];
}
console.warn('Unhandled parameters:', opts);
}
}
}

View File

@@ -0,0 +1,207 @@
import { $el, getActionEls } from "../../common/utils_dom.js";
import { bind } from "../utils_templates.js";
const CSS_STYLE_SHEETS = new Map();
const CSS_STYLE_SHEETS_ADDED = new Map();
const HTML_TEMPLATE_FILES = new Map();
function getCommonPath(name, extension) {
return `rgthree/common/components/${name.replace("rgthree-", "").replace(/\-/g, "_")}.${extension}`;
}
async function getStyleSheet(name, markupOrPath) {
if (markupOrPath.includes("{")) {
return markupOrPath;
}
if (!CSS_STYLE_SHEETS.has(name)) {
try {
const path = markupOrPath || getCommonPath(name, "css");
const text = await (await fetch(path)).text();
CSS_STYLE_SHEETS.set(name, text);
}
catch (e) {
}
}
return CSS_STYLE_SHEETS.get(name);
}
async function addStyleSheet(name, markupOrPath) {
if (markupOrPath.includes("{")) {
throw new Error("Page-level stylesheets should be passed a path.");
}
if (!CSS_STYLE_SHEETS_ADDED.has(name)) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = markupOrPath;
document.head.appendChild(link);
CSS_STYLE_SHEETS_ADDED.set(name, link);
}
return CSS_STYLE_SHEETS_ADDED.get(name);
}
async function getTemplateMarkup(name, markupOrPath) {
if (markupOrPath.includes("<template")) {
return markupOrPath;
}
if (!HTML_TEMPLATE_FILES.has(name)) {
try {
const path = markupOrPath || getCommonPath(name, "html");
const text = await (await fetch(path)).text();
HTML_TEMPLATE_FILES.set(name, text);
}
catch (e) {
}
}
return HTML_TEMPLATE_FILES.get(name);
}
export class RgthreeCustomElement extends HTMLElement {
constructor() {
super(...arguments);
this.ctor = this.constructor;
this.hasBeenConnected = false;
this.connected = false;
this.templates = new Map();
this.firstConnectedPromise = new Promise((resolve) => (this.firstConnectedPromiseResolver = resolve));
this.eventElements = new Map();
}
static create() {
if (this.NAME === "rgthree-override") {
throw new Error("Must override component NAME");
}
if (!window.customElements.get(this.NAME)) {
window.customElements.define(this.NAME, this);
}
return document.createElement(this.NAME);
}
onFirstConnected() {
}
onReconnected() {
}
onConnected() {
}
onDisconnected() {
}
onAction(action, e) {
console.log("onAction", action, e);
}
getElement(query) {
const el = this.querySelector(query);
if (!el) {
throw new Error("No element found for query: " + query);
}
return el;
}
onActionInternal(action, e) {
if (typeof this[action] === "function") {
this[action](e);
}
else {
this.onAction(action, e);
}
}
onConnectedInternal() {
this.connectActionElements();
this.onConnected();
}
onDisconnectedInternal() {
this.disconnectActionElements();
this.onDisconnected();
}
async connectedCallback() {
const elementName = this.ctor.NAME;
const wasConnected = this.connected;
if (!wasConnected) {
this.connected = true;
}
if (!this.hasBeenConnected) {
const [stylesheet, markup] = await Promise.all([
this.ctor.USE_SHADOW
? getStyleSheet(elementName, this.ctor.CSS)
: addStyleSheet(elementName, this.ctor.CSS),
getTemplateMarkup(elementName, this.ctor.TEMPLATES),
]);
if (markup) {
const temp = $el("div");
const templatesMarkup = markup.match(/<template[^]*?<\/template>/gm) || [];
for (const markup of templatesMarkup) {
temp.innerHTML = markup;
const template = temp.children[0];
if (!(template instanceof HTMLTemplateElement)) {
throw new Error("Not a template element.");
}
let id = template.getAttribute("id");
if (!id) {
id = this.ctor.NAME;
}
this.templates.set(id, template);
}
}
if (this.ctor.USE_SHADOW) {
this.root = this.attachShadow({ mode: "open" });
if (typeof stylesheet === "string") {
const sheet = new CSSStyleSheet();
sheet.replaceSync(stylesheet);
this.root.adoptedStyleSheets = [sheet];
}
}
else {
this.root = this;
}
let template;
if (this.templates.has(elementName)) {
template = this.templates.get(elementName);
}
else if (this.templates.has(elementName.replace("rgthree-", ""))) {
template = this.templates.get(elementName.replace("rgthree-", ""));
}
if (template) {
this.root.appendChild(template.content.cloneNode(true));
for (const name of template.getAttributeNames()) {
if (name != "id" && template.getAttribute(name)) {
this.setAttribute(name, template.getAttribute(name));
}
}
}
this.onFirstConnected();
this.hasBeenConnected = true;
this.firstConnectedPromiseResolver();
}
else {
this.onReconnected();
}
this.onConnectedInternal();
}
disconnectedCallback() {
this.connected = false;
this.onDisconnected();
}
connectActionElements() {
const data = getActionEls(this);
for (const dataItem of Object.values(data)) {
const mapItem = this.eventElements.get(dataItem.el) || {};
for (const [event, action] of Object.entries(dataItem.actions)) {
if (mapItem[event]) {
console.warn(`Element already has an event for ${event}`);
continue;
}
mapItem[event] = (e) => {
this.onActionInternal(action, e);
};
dataItem.el.addEventListener(event, mapItem[event]);
}
}
}
disconnectActionElements() {
for (const [el, eventData] of this.eventElements.entries()) {
for (const [event, fn] of Object.entries(eventData)) {
el.removeEventListener(event, fn);
}
}
}
async bindWhenConnected(data, el) {
await this.firstConnectedPromise;
this.bind(data, el);
}
bind(data, el) {
bind(el || this.root, data);
}
}
RgthreeCustomElement.NAME = "rgthree-override";
RgthreeCustomElement.USE_SHADOW = true;
RgthreeCustomElement.TEMPLATES = "";
RgthreeCustomElement.CSS = "";

View File

@@ -0,0 +1,130 @@
: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;
}

View File

@@ -0,0 +1,124 @@
@charset "UTF-8";
.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);
}

View File

@@ -0,0 +1,143 @@
.rgthree-lora-chooser-dialog {
max-width: 100%;
}
.rgthree-lora-chooser-dialog .rgthree-dialog-container-title {
display: flex;
flex-direction: column;
}
.rgthree-lora-chooser-dialog .rgthree-dialog-container-title h2 {
display: flex;
width: 100%;
}
.rgthree-lora-chooser-dialog .rgthree-lora-chooser-search {
margin-left: auto;
border-radius: 50px;
width: 50%;
max-width: 170px;
padding: 2px 8px;
}
.rgthree-lora-chooser-dialog .rgthree-lora-chooser-header {
display: flex;
flex-direction: row;
}
.rgthree-lora-chooser-dialog .rgthree-lora-filters-container svg {
width: 16px;
height: 16px;
}
.rgthree-lora-chooser-dialog .rgthree-dialog-container-content {
width: 80vw;
height: 80vh;
}
.rgthree-lora-chooser-dialog .rgthree-button-reset {
width: 32px;
height: 32px;
}
.rgthree-lora-chooser-dialog .rgthree-button-reset > svg {
width: 100%;
height: 100%;
}
.rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list {
list-style: none;
margin: 0;
padding: 0;
position: relative;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: start;
justify-content: space-around;
}
.rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li {
position: relative;
flex: 0 0 auto;
width: 170px;
max-width: 100%;
margin: 8px 8px 16px;
}
.rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li label {
position: absolute;
display: block;
inset: 0;
z-index: 3;
cursor: pointer;
}
.rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li input[type=checkbox] {
position: absolute;
right: 8px;
top: 8px;
margin: 0;
z-index: 2;
appearance: none;
background-color: #fff;
width: 48px;
height: 48px;
border-radius: 4px;
border: 1px solid rgb(120, 120, 120);
opacity: 0;
transition: opacity 0.15s ease-in-out;
}
.rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li input[type=checkbox]:checked {
opacity: 1;
background: #0060df;
}
.rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li input[type=checkbox]:checked::before {
content: "";
display: block;
width: 100%;
height: 100%;
box-shadow: inset 100px 100px #fff;
clip-path: polygon(40.13% 68.39%, 23.05% 51.31%, 17.83% 48.26%, 12.61% 49.57%, 9.57% 53.04%, 8% 60%, 34.13% 85.87%, 39.82% 89.57%, 45.88% 86.73%, 90.66% 32.39%, 88.92% 26.1%, 83.03% 22.17%, 76.94% 22.62%);
}
.rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li figure {
position: relative;
display: block;
margin: 0 0 8px;
padding: 0;
border: 1px solid rgba(120, 120, 120, 0.8);
background: rgba(120, 120, 120, 0.5);
width: 100%;
padding-top: 120%;
transition: box-shadow 0.15s ease-in-out;
opacity: 0.75;
}
.rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li figure::after {
content: "";
display: block;
position: absolute;
inset: 0;
}
.rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li figure:empty::before {
content: "No image.";
color: rgba(200, 200, 200, 0.8);
position: absolute;
display: block;
inset: 0;
font-size: 1.2em;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li figure > img, .rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li figure > video {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
object-fit: cover;
}
.rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li div {
word-wrap: break-word;
font-size: 0.8rem;
opacity: 0.75;
}
.rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li:hover figure::after {
box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.75);
}
.rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li :checked ~ figure::after {
box-shadow: 0 0 5px #fff, 0px 0px 15px rgba(49, 131, 255, 0.88), inset 0 0 3px #fff, inset 0px 0px 5px rgba(49, 131, 255, 0.88);
}
.rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li:hover *, .rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li:hover input[type=checkbox],
.rgthree-lora-chooser-dialog ul.rgthree-lora-chooser-list > li :checked ~ * {
opacity: 1;
}

View File

@@ -0,0 +1,336 @@
.rgthree-info-dialog {
width: 90vw;
max-width: 960px;
}
.rgthree-info-dialog .rgthree-info-area {
list-style: none;
padding: 0;
margin: 0;
display: flex;
}
.rgthree-info-dialog .rgthree-info-area > li {
display: inline-flex;
margin: 0;
vertical-align: top;
}
.rgthree-info-dialog .rgthree-info-area > li + li {
margin-left: 6px;
}
.rgthree-info-dialog .rgthree-info-area > li:not(.-link) + li.-link {
margin-left: auto;
}
.rgthree-info-dialog .rgthree-info-area > li.rgthree-info-tag > * {
min-height: 24px;
border-radius: 4px;
line-height: 1;
color: rgba(255, 255, 255, 0.85);
background: rgb(69, 92, 85);
font-size: 14px;
font-weight: bold;
text-decoration: none;
display: flex;
height: 1.6em;
padding-left: 0.5em;
padding-right: 0.5em;
padding-bottom: 0.1em;
align-content: center;
justify-content: center;
align-items: center;
box-shadow: inset 0px 0px 0 1px rgba(0, 0, 0, 0.5);
}
.rgthree-info-dialog .rgthree-info-area > li.rgthree-info-tag > * > svg {
width: 16px;
height: 16px;
}
.rgthree-info-dialog .rgthree-info-area > li.rgthree-info-tag > * > svg:last-child {
margin-left: 0.5em;
}
.rgthree-info-dialog .rgthree-info-area > li.rgthree-info-tag > *[href] {
box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.25), inset 0px -1px 0px rgba(0, 0, 0, 0.66);
}
.rgthree-info-dialog .rgthree-info-area > li.rgthree-info-tag > *:empty {
display: none;
}
.rgthree-info-dialog .rgthree-info-area > li.-type > * {
background: rgb(73, 54, 94);
color: rgb(228, 209, 248);
}
.rgthree-info-dialog .rgthree-info-area > li.rgthree-info-menu {
margin-left: auto;
}
:not(#fakeid) .rgthree-info-dialog .rgthree-info-area > li.rgthree-info-menu .rgthree-button {
margin: 0;
min-height: 24px;
padding: 0 12px;
}
.rgthree-info-dialog .rgthree-info-area > li.rgthree-info-menu svg {
width: 16px;
height: 16px;
}
.rgthree-info-dialog .rgthree-info-table {
border-collapse: collapse;
margin: 16px 0px;
width: 100%;
font-size: 12px;
}
.rgthree-info-dialog .rgthree-info-table tr.editable button {
display: flex;
width: 28px;
height: 28px;
align-items: center;
justify-content: center;
}
.rgthree-info-dialog .rgthree-info-table tr.editable button svg + svg {
display: none;
}
.rgthree-info-dialog .rgthree-info-table tr.editable.-rgthree-editing button svg {
display: none;
}
.rgthree-info-dialog .rgthree-info-table tr.editable.-rgthree-editing button svg + svg {
display: inline-block;
}
.rgthree-info-dialog .rgthree-info-table td {
position: relative;
border: 1px solid rgba(255, 255, 255, 0.25);
padding: 0;
vertical-align: top;
}
.rgthree-info-dialog .rgthree-info-table td:first-child {
background: rgba(255, 255, 255, 0.075);
width: 10px;
}
.rgthree-info-dialog .rgthree-info-table td:first-child > *:first-child {
white-space: nowrap;
padding-right: 32px;
}
.rgthree-info-dialog .rgthree-info-table td:first-child small {
display: block;
margin-top: 2px;
opacity: 0.75;
}
.rgthree-info-dialog .rgthree-info-table td:first-child small > [data-action] {
text-decoration: underline;
cursor: pointer;
}
.rgthree-info-dialog .rgthree-info-table td:first-child small > [data-action]:hover {
text-decoration: none;
}
.rgthree-info-dialog .rgthree-info-table td a, .rgthree-info-dialog .rgthree-info-table td a:hover, .rgthree-info-dialog .rgthree-info-table td a:visited {
color: inherit;
}
.rgthree-info-dialog .rgthree-info-table td svg {
width: 1.3333em;
height: 1.3333em;
vertical-align: -0.285em;
}
.rgthree-info-dialog .rgthree-info-table td svg.logo-civitai {
margin-right: 0.3333em;
}
.rgthree-info-dialog .rgthree-info-table td > *:first-child {
display: block;
padding: 6px 10px;
}
.rgthree-info-dialog .rgthree-info-table td > input, .rgthree-info-dialog .rgthree-info-table td > textarea {
padding: 5px 10px;
border: 0;
box-shadow: inset 1px 1px 5px 0px rgba(0, 0, 0, 0.5);
font: inherit;
appearance: none;
background: #fff;
color: #121212;
resize: vertical;
}
.rgthree-info-dialog .rgthree-info-table td > input:only-child, .rgthree-info-dialog .rgthree-info-table td > textarea:only-child {
width: 100%;
}
:not(#fakeid) .rgthree-info-dialog .rgthree-info-table td .rgthree-button[data-action=fetch-civitai] {
font-size: inherit;
padding: 6px 16px;
margin: 2px;
}
.rgthree-info-dialog .rgthree-info-table tr[data-field-name=userNote] td > span:first-child {
white-space: pre;
}
.rgthree-info-dialog .rgthree-info-table tr.rgthree-info-table-break-row td {
border: 0;
background: transparent;
padding: 12px 4px 4px;
font-size: 1.2em;
}
.rgthree-info-dialog .rgthree-info-table tr.rgthree-info-table-break-row td > small {
font-style: italic;
opacity: 0.66;
}
.rgthree-info-dialog .rgthree-info-table tr.rgthree-info-table-break-row td:empty {
padding: 4px;
}
.rgthree-info-dialog .rgthree-info-table td .-help {
border: 1px solid currentColor;
position: absolute;
right: 5px;
top: 6px;
line-height: 1;
font-size: 11px;
width: 12px;
height: 12px;
border-radius: 8px;
display: flex;
align-content: center;
justify-content: center;
cursor: help;
}
.rgthree-info-dialog .rgthree-info-table td .-help::before {
content: "?";
}
.rgthree-info-dialog .rgthree-info-table td > ul.rgthree-info-trained-words-list {
list-style: none;
padding: 2px 8px;
margin: 0;
display: flex;
flex-direction: row;
flex-wrap: wrap;
max-height: 15vh;
overflow: auto;
}
.rgthree-info-dialog .rgthree-info-table td > ul.rgthree-info-trained-words-list > li {
display: inline-flex;
margin: 2px;
vertical-align: top;
border-radius: 4px;
line-height: 1;
color: rgba(255, 255, 255, 0.85);
background: rgb(73, 91, 106);
font-size: 1.2em;
font-weight: 600;
text-decoration: none;
display: flex;
height: 1.6em;
align-content: center;
justify-content: center;
align-items: center;
box-shadow: inset 0px 0px 0 1px rgba(0, 0, 0, 0.5);
cursor: pointer;
white-space: nowrap;
max-width: 183px;
}
.rgthree-info-dialog .rgthree-info-table td > ul.rgthree-info-trained-words-list > li:hover {
background: rgb(68, 109, 142);
}
.rgthree-info-dialog .rgthree-info-table td > ul.rgthree-info-trained-words-list > li > svg {
width: auto;
height: 1.2em;
}
.rgthree-info-dialog .rgthree-info-table td > ul.rgthree-info-trained-words-list > li > span {
padding-left: 0.5em;
padding-right: 0.5em;
padding-bottom: 0.1em;
text-overflow: ellipsis;
overflow: hidden;
}
.rgthree-info-dialog .rgthree-info-table td > ul.rgthree-info-trained-words-list > li > small {
align-self: stretch;
display: flex;
align-items: center;
justify-content: center;
padding: 0 0.5em;
background: rgba(0, 0, 0, 0.2);
}
.rgthree-info-dialog .rgthree-info-table td > ul.rgthree-info-trained-words-list > li.-rgthree-is-selected {
background: rgb(42, 126, 193);
}
.rgthree-info-dialog .rgthree-info-images {
list-style: none;
padding: 0;
margin: 0;
scroll-snap-type: x mandatory;
display: flex;
flex-direction: row;
overflow: auto;
}
.rgthree-info-dialog .rgthree-info-images > li {
scroll-snap-align: start;
max-width: 90%;
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
overflow: hidden;
padding: 0;
margin: 6px;
font-size: 0;
position: relative;
}
.rgthree-info-dialog .rgthree-info-images > li figure {
margin: 0;
position: static;
}
.rgthree-info-dialog .rgthree-info-images > li figure video, .rgthree-info-dialog .rgthree-info-images > li figure img {
max-height: 45vh;
}
.rgthree-info-dialog .rgthree-info-images > li figure figcaption {
position: absolute;
left: 0;
width: 100%;
bottom: 0;
padding: 12px;
font-size: 12px;
background: rgba(0, 0, 0, 0.85);
opacity: 0;
transform: translateY(50px);
transition: all 0.25s ease-in-out;
}
.rgthree-info-dialog .rgthree-info-images > li figure figcaption > span {
display: inline-block;
padding: 2px 4px;
margin: 2px;
border-radius: 2px;
border: 1px solid rgba(255, 255, 255, 0.2);
word-break: break-word;
}
.rgthree-info-dialog .rgthree-info-images > li figure figcaption > span label {
display: inline;
padding: 0;
margin: 0;
opacity: 0.5;
pointer-events: none;
user-select: none;
}
.rgthree-info-dialog .rgthree-info-images > li figure figcaption > span a {
color: inherit;
text-decoration: underline;
}
.rgthree-info-dialog .rgthree-info-images > li figure figcaption > span a:hover {
text-decoration: none;
}
.rgthree-info-dialog .rgthree-info-images > li figure figcaption > span a svg {
height: 10px;
margin-left: 4px;
fill: currentColor;
}
.rgthree-info-dialog .rgthree-info-images > li figure figcaption:empty {
text-align: center;
}
.rgthree-info-dialog .rgthree-info-images > li figure figcaption:empty::before {
content: "No data.";
}
.rgthree-info-dialog .rgthree-info-images > li:hover figure figcaption {
opacity: 1;
transform: translateY(0px);
}
.rgthree-info-dialog .rgthree-info-images > li .rgthree-info-table {
width: calc(100% - 16px);
}
.rgthree-info-dialog .rgthree-info-civitai-link {
margin: 8px;
color: #eee;
}
.rgthree-info-dialog .rgthree-info-civitai-link a, .rgthree-info-dialog .rgthree-info-civitai-link a:hover, .rgthree-info-dialog .rgthree-info-civitai-link a:visited {
color: inherit;
text-decoration: none;
}
.rgthree-info-dialog .rgthree-info-civitai-link > svg {
width: 16px;
height: 16px;
margin-right: 8px;
}

View File

@@ -0,0 +1,103 @@
.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);
}

View File

@@ -0,0 +1,66 @@
html {
font-size: 100%;
overflow-y: scroll;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
:root {
--header-height: 56px;
--progress-height: 12px;
}
button {
all: unset;
}
.-bevel {
position: relative;
}
.-bevel::before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border: 1px solid red;
border-color: rgba(255, 255, 255, 0.15) rgba(255, 255, 255, 0.15) rgba(0, 0, 0, 0.5) rgba(0, 0, 0, 0.5);
z-index: 5;
pointer-events: none;
}
body {
background: #202020;
font-family: Arial, sans-serif;
font-size: 1rem;
font-weight: 400;
margin: 0;
padding-top: calc(var(--header-height) + var(--progress-height));
color: #ffffff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: start;
}
.app-header {
height: var(--header-height);
padding: 0;
position: fixed;
z-index: 99;
top: 0;
left: 0;
width: 100%;
background: #353535;
display: flex;
flex-direction: row;
align-items: center;
justify-content: start;
}

View File

@@ -0,0 +1,109 @@
import { createElement as $el, getClosestOrSelf, setAttributes } from "./utils_dom.js";
export class RgthreeDialog extends EventTarget {
constructor(options) {
super();
this.options = options;
let container = $el("div.rgthree-dialog-container");
this.element = $el("dialog", {
classes: ["rgthree-dialog", options.class || ""],
child: container,
parent: document.body,
events: {
click: (event) => {
if (!this.element.open ||
event.target === container ||
getClosestOrSelf(event.target, `.rgthree-dialog-container`) === container) {
return;
}
return this.close();
},
},
});
this.element.addEventListener("close", (event) => {
this.onDialogElementClose();
});
this.titleElement = $el("div.rgthree-dialog-container-title", {
parent: container,
children: !options.title
? null
: options.title instanceof Element || Array.isArray(options.title)
? options.title
: typeof options.title === "string"
? !options.title.includes("<h2")
? $el("h2", { html: options.title })
: options.title
: options.title,
});
this.contentElement = $el("div.rgthree-dialog-container-content", {
parent: container,
child: options.content,
});
const footerEl = $el("footer.rgthree-dialog-container-footer", { parent: container });
for (const button of options.buttons || []) {
$el("button", {
text: button.label,
className: button.className,
disabled: !!button.disabled,
parent: footerEl,
events: {
click: (e) => {
var _a;
(_a = button.callback) === null || _a === void 0 ? void 0 : _a.call(button, e);
},
},
});
}
if (options.closeButtonLabel !== false) {
$el("button", {
text: options.closeButtonLabel || "Close",
className: "rgthree-button",
parent: footerEl,
events: {
click: (e) => {
this.close(e);
},
},
});
}
}
setTitle(content) {
const title = typeof content !== "string" || content.includes("<h2")
? content
: $el("h2", { html: content });
setAttributes(this.titleElement, { children: title });
}
setContent(content) {
setAttributes(this.contentElement, { children: content });
}
show() {
document.body.classList.add("rgthree-dialog-open");
this.element.showModal();
this.dispatchEvent(new CustomEvent("show"));
return this;
}
async close(e) {
if (this.options.onBeforeClose && !(await this.options.onBeforeClose())) {
return;
}
this.element.close();
}
onDialogElementClose() {
document.body.classList.remove("rgthree-dialog-open");
this.element.remove();
this.dispatchEvent(new CustomEvent("close", this.getCloseEventDetail()));
}
getCloseEventDetail() {
return { detail: null };
}
}
export class RgthreeHelpDialog extends RgthreeDialog {
constructor(node, content, opts = {}) {
const title = (node.type || node.title || "").replace(/\s*\(rgthree\).*/, " <small>by rgthree</small>");
const options = Object.assign({}, opts, {
class: "-iconed -help",
title,
content,
});
super(options);
}
}

View File

@@ -0,0 +1,365 @@
var IoDirection;
(function (IoDirection) {
IoDirection[IoDirection["INPUT"] = 0] = "INPUT";
IoDirection[IoDirection["OUTPUT"] = 1] = "OUTPUT";
})(IoDirection || (IoDirection = {}));
function getLinksData(links) {
if (links instanceof Map) {
const data = [];
for (const [key, llink] of links.entries()) {
if (!llink)
continue;
data.push(llink);
}
return data;
}
if (!Array.isArray(links)) {
const data = [];
for (const key in links) {
const llink = (links.hasOwnProperty(key) && links[key]) || null;
if (!llink)
continue;
data.push(llink);
}
return data;
}
return links.map((link) => ({
id: link[0],
origin_id: link[1],
origin_slot: link[2],
target_id: link[3],
target_slot: link[4],
type: link[5],
}));
}
export class WorkflowLinkFixer {
static create(graph) {
if (typeof graph.getNodeById === "function") {
return new WorkflowLinkFixerGraph(graph);
}
return new WorkflowLinkFixerSerialized(graph);
}
constructor(graph) {
this.silent = false;
this.checkedData = null;
this.logger = console;
this.patchedNodeSlots = {};
this.instructions = [];
this.graph = graph;
}
check(force = false) {
var _a, _b;
if (this.checkedData && !force) {
return { ...this.checkedData };
}
this.instructions = [];
this.patchedNodeSlots = {};
const instructions = [];
const links = getLinksData(this.graph.links);
links.reverse();
for (const link of links) {
if (!link)
continue;
const originNode = this.getNodeById(link.origin_id);
const originHasLink = () => this.nodeHasLinkId(originNode, IoDirection.OUTPUT, link.origin_slot, link.id);
const patchOrigin = (op, id = link.id) => this.getNodePatchInstruction(originNode, IoDirection.OUTPUT, link.origin_slot, id, op);
const targetNode = this.getNodeById(link.target_id);
const targetHasLink = () => this.nodeHasLinkId(targetNode, IoDirection.INPUT, link.target_slot, link.id);
const targetHasAnyLink = () => this.nodeHasAnyLink(targetNode, IoDirection.INPUT, link.target_slot);
const patchTarget = (op, id = link.id) => this.getNodePatchInstruction(targetNode, IoDirection.INPUT, link.target_slot, id, op);
const originLog = `origin(${link.origin_id}).outputs[${link.origin_slot}].links`;
const targetLog = `target(${link.target_id}).inputs[${link.target_slot}].link`;
if (!originNode || !targetNode) {
if (!originNode && !targetNode) {
}
else if (!originNode && targetNode) {
this.log(`Link ${link.id} is funky... ` +
`origin ${link.origin_id} does not exist, but target ${link.target_id} does.`);
if (targetHasLink()) {
this.log(` > [PATCH] ${targetLog} does have link, will remove the inputs' link first.`);
instructions.push(patchTarget("REMOVE", -1));
}
}
else if (!targetNode && originNode) {
this.log(`Link ${link.id} is funky... ` +
`target ${link.target_id} does not exist, but origin ${link.origin_id} does.`);
if (originHasLink()) {
this.log(` > [PATCH] Origin's links' has ${link.id}; will remove the link first.`);
instructions.push(patchOrigin("REMOVE"));
}
}
continue;
}
if (targetHasLink() || originHasLink()) {
if (!originHasLink()) {
this.log(`${link.id} is funky... ${originLog} does NOT contain it, but ${targetLog} does.`);
this.log(` > [PATCH] Attempt a fix by adding this ${link.id} to ${originLog}.`);
instructions.push(patchOrigin("ADD"));
}
else if (!targetHasLink()) {
this.log(`${link.id} is funky... ${targetLog} is NOT correct (is ${(_b = (_a = targetNode.inputs) === null || _a === void 0 ? void 0 : _a[link.target_slot]) === null || _b === void 0 ? void 0 : _b.link}), but ${originLog} contains it`);
if (!targetHasAnyLink()) {
this.log(` > [PATCH] ${targetLog} is not defined, will set to ${link.id}.`);
let instruction = patchTarget("ADD");
if (!instruction) {
this.log(` > [PATCH] Nvm, ${targetLog} already patched. Removing ${link.id} from ${originLog}.`);
instruction = patchOrigin("REMOVE");
}
instructions.push(instruction);
}
else {
this.log(` > [PATCH] ${targetLog} is defined, removing ${link.id} from ${originLog}.`);
instructions.push(patchOrigin("REMOVE"));
}
}
}
}
for (let link of links) {
if (!link)
continue;
const originNode = this.getNodeById(link.origin_id);
const targetNode = this.getNodeById(link.target_id);
if (!originNode && !targetNode) {
instructions.push({
op: "DELETE",
linkId: link.id,
reason: `Both nodes #${link.origin_id} & #${link.target_id} are removed`,
});
}
if ((!originNode ||
!this.nodeHasLinkId(originNode, IoDirection.OUTPUT, link.origin_slot, link.id)) &&
(!targetNode ||
!this.nodeHasLinkId(targetNode, IoDirection.INPUT, link.target_slot, link.id))) {
instructions.push({
op: "DELETE",
linkId: link.id,
reason: `both origin node #${link.origin_id} ` +
`${!originNode ? "is removed" : `is missing link id output slot ${link.origin_slot}`}` +
`and target node #${link.target_id} ` +
`${!targetNode ? "is removed" : `is missing link id input slot ${link.target_slot}`}.`,
});
continue;
}
}
this.instructions = instructions.filter((i) => !!i);
this.checkedData = {
hasBadLinks: !!this.instructions.length,
graph: this.graph,
patches: this.instructions.filter((i) => !!i.node)
.length,
deletes: this.instructions.filter((i) => i.op === "DELETE").length,
};
return { ...this.checkedData };
}
fix(force = false, times) {
var _a, _b, _c, _d, _e, _f, _g;
if (!this.checkedData || force) {
this.check(force);
}
let patches = 0;
let deletes = 0;
for (const instruction of this.instructions) {
if (instruction.node) {
let { node, slot, linkIdToUse, dir, op } = instruction;
if (dir == IoDirection.INPUT) {
node.inputs = node.inputs || [];
const old = (_a = node.inputs[slot]) === null || _a === void 0 ? void 0 : _a.link;
node.inputs[slot] = node.inputs[slot] || {};
node.inputs[slot].link = linkIdToUse;
this.log(`Node #${node.id}: Set link ${linkIdToUse} to input slot ${slot} (was ${old})`);
}
else if (op === "ADD" && linkIdToUse != null) {
node.outputs = node.outputs || [];
node.outputs[slot] = node.outputs[slot] || {};
node.outputs[slot].links = node.outputs[slot].links || [];
node.outputs[slot].links.push(linkIdToUse);
this.log(`Node #${node.id}: Add link ${linkIdToUse} to output slot #${slot}`);
}
else if (op === "REMOVE" && linkIdToUse != null) {
if (((_d = (_c = (_b = node.outputs) === null || _b === void 0 ? void 0 : _b[slot]) === null || _c === void 0 ? void 0 : _c.links) === null || _d === void 0 ? void 0 : _d.length) === undefined) {
this.log(`Node #${node.id}: Couldn't remove link ${linkIdToUse} from output slot #${slot}` +
` because it didn't exist.`);
}
else {
let linkIdIndex = node.outputs[slot].links.indexOf(linkIdToUse);
node.outputs[slot].links.splice(linkIdIndex, 1);
this.log(`Node #${node.id}: Remove link ${linkIdToUse} from output slot #${slot}`);
}
}
else {
throw new Error("Unhandled Node Instruction");
}
patches++;
}
else if (instruction.op === "DELETE") {
const wasDeleted = this.deleteGraphLink(instruction.linkId);
if (wasDeleted === true) {
this.log(`Link #${instruction.linkId}: Removed workflow link b/c ${instruction.reason}`);
}
else {
this.log(`Error Link #${instruction.linkId} was not removed!`);
}
deletes += wasDeleted ? 1 : 0;
}
else {
throw new Error("Unhandled Instruction");
}
}
const newCheck = this.check(force);
times = times == null ? 5 : times;
let newFix = null;
if (newCheck.hasBadLinks && times > 0) {
newFix = this.fix(true, times - 1);
}
return {
hasBadLinks: (_e = newFix === null || newFix === void 0 ? void 0 : newFix.hasBadLinks) !== null && _e !== void 0 ? _e : newCheck.hasBadLinks,
graph: this.graph,
patches: patches + ((_f = newFix === null || newFix === void 0 ? void 0 : newFix.patches) !== null && _f !== void 0 ? _f : 0),
deletes: deletes + ((_g = newFix === null || newFix === void 0 ? void 0 : newFix.deletes) !== null && _g !== void 0 ? _g : 0),
};
}
log(...args) {
if (this.silent)
return;
this.logger.log(...args);
}
getNodePatchInstruction(node, ioDir, slot, linkId, op) {
var _a, _b;
const nodeId = node.id;
this.patchedNodeSlots[nodeId] = this.patchedNodeSlots[nodeId] || {};
const patchedNode = this.patchedNodeSlots[nodeId];
if (ioDir == IoDirection.INPUT) {
patchedNode["inputs"] = patchedNode["inputs"] || {};
if (patchedNode["inputs"][slot] !== undefined) {
this.log(` > Already set ${nodeId}.inputs[${slot}] to ${patchedNode["inputs"][slot]} Skipping.`);
return null;
}
let linkIdToUse = op === "REMOVE" ? null : linkId;
patchedNode["inputs"][slot] = linkIdToUse;
return { node, dir: ioDir, op, slot, linkId, linkIdToUse };
}
patchedNode["outputs"] = patchedNode["outputs"] || {};
patchedNode["outputs"][slot] = patchedNode["outputs"][slot] || {
links: [...(((_b = (_a = node.outputs) === null || _a === void 0 ? void 0 : _a[slot]) === null || _b === void 0 ? void 0 : _b.links) || [])],
changes: {},
};
if (patchedNode["outputs"][slot]["changes"][linkId] !== undefined) {
this.log(` > Already set ${nodeId}.outputs[${slot}] to ${patchedNode["outputs"][slot]}! Skipping.`);
return null;
}
patchedNode["outputs"][slot]["changes"][linkId] = op;
if (op === "ADD") {
let linkIdIndex = patchedNode["outputs"][slot]["links"].indexOf(linkId);
if (linkIdIndex !== -1) {
this.log(` > Hmmm.. asked to add ${linkId} but it is already in list...`);
return null;
}
patchedNode["outputs"][slot]["links"].push(linkId);
return { node, dir: ioDir, op, slot, linkId, linkIdToUse: linkId };
}
let linkIdIndex = patchedNode["outputs"][slot]["links"].indexOf(linkId);
if (linkIdIndex === -1) {
this.log(` > Hmmm.. asked to remove ${linkId} but it doesn't exist...`);
return null;
}
patchedNode["outputs"][slot]["links"].splice(linkIdIndex, 1);
return { node, dir: ioDir, op, slot, linkId, linkIdToUse: linkId };
}
nodeHasLinkId(node, ioDir, slot, linkId) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
const nodeId = node.id;
let has = false;
if (ioDir === IoDirection.INPUT) {
let nodeHasIt = ((_b = (_a = node.inputs) === null || _a === void 0 ? void 0 : _a[slot]) === null || _b === void 0 ? void 0 : _b.link) === linkId;
if ((_c = this.patchedNodeSlots[nodeId]) === null || _c === void 0 ? void 0 : _c["inputs"]) {
let patchedHasIt = this.patchedNodeSlots[nodeId]["inputs"][slot] === linkId;
has = patchedHasIt;
}
else {
has = nodeHasIt;
}
}
else {
let nodeHasIt = (_f = (_e = (_d = node.outputs) === null || _d === void 0 ? void 0 : _d[slot]) === null || _e === void 0 ? void 0 : _e.links) === null || _f === void 0 ? void 0 : _f.includes(linkId);
if ((_j = (_h = (_g = this.patchedNodeSlots[nodeId]) === null || _g === void 0 ? void 0 : _g["outputs"]) === null || _h === void 0 ? void 0 : _h[slot]) === null || _j === void 0 ? void 0 : _j["changes"][linkId]) {
let patchedHasIt = this.patchedNodeSlots[nodeId]["outputs"][slot].links.includes(linkId);
has = !!patchedHasIt;
}
else {
has = !!nodeHasIt;
}
}
return has;
}
nodeHasAnyLink(node, ioDir, slot) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
const nodeId = node.id;
let hasAny = false;
if (ioDir === IoDirection.INPUT) {
let nodeHasAny = ((_b = (_a = node.inputs) === null || _a === void 0 ? void 0 : _a[slot]) === null || _b === void 0 ? void 0 : _b.link) != null;
if ((_c = this.patchedNodeSlots[nodeId]) === null || _c === void 0 ? void 0 : _c["inputs"]) {
let patchedHasAny = this.patchedNodeSlots[nodeId]["inputs"][slot] != null;
hasAny = patchedHasAny;
}
else {
hasAny = !!nodeHasAny;
}
}
else {
let nodeHasAny = (_f = (_e = (_d = node.outputs) === null || _d === void 0 ? void 0 : _d[slot]) === null || _e === void 0 ? void 0 : _e.links) === null || _f === void 0 ? void 0 : _f.length;
if ((_j = (_h = (_g = this.patchedNodeSlots[nodeId]) === null || _g === void 0 ? void 0 : _g["outputs"]) === null || _h === void 0 ? void 0 : _h[slot]) === null || _j === void 0 ? void 0 : _j["changes"]) {
let patchedHasAny = (_k = this.patchedNodeSlots[nodeId]["outputs"][slot].links) === null || _k === void 0 ? void 0 : _k.length;
hasAny = !!patchedHasAny;
}
else {
hasAny = !!nodeHasAny;
}
}
return hasAny;
}
}
class WorkflowLinkFixerSerialized extends WorkflowLinkFixer {
constructor(graph) {
super(graph);
}
getNodeById(id) {
var _a;
return (_a = this.graph.nodes.find((node) => Number(node.id) === id)) !== null && _a !== void 0 ? _a : null;
}
fix(force = false, times) {
const ret = super.fix(force, times);
this.graph.links = this.graph.links.filter((l) => !!l);
return ret;
}
deleteGraphLink(id) {
const idx = this.graph.links.findIndex((l) => l && (l[0] === id || l.id === id));
if (idx === -1) {
return `Link #${id} not found in workflow links.`;
}
this.graph.links.splice(idx, 1);
return true;
}
}
class WorkflowLinkFixerGraph extends WorkflowLinkFixer {
constructor(graph) {
super(graph);
}
getNodeById(id) {
var _a;
return (_a = this.graph.getNodeById(id)) !== null && _a !== void 0 ? _a : null;
}
deleteGraphLink(id) {
if (this.graph.links instanceof Map) {
if (!this.graph.links.has(id)) {
return `Link #${id} not found in workflow links.`;
}
this.graph.links.delete(id);
return true;
}
if (this.graph.links[id] == null) {
return `Link #${id} not found in workflow links.`;
}
delete this.graph.links[id];
return true;
}
}

View File

@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor">
<path d="M88.503,158.997 L152.731,196.103 L152.738,196.092 L152.762,196.103 L152.769,196.106 L152.771,196.103 L183.922,142.084 L174.153,136.437 L148.611,180.676 L101.512,153.484 L132.193,30.415 L156.124,71.869 L165.896,66.225 L128.002,0.59 z"></path>
<path d="M55.586,148.581l13.44,47.521l0.014,0.051l0.168-0.051l10.689-3.022l-6.589-23.313l45.609,26.335l0.087,0.051l0.027-0.051 l5.617-9.718l-42.648-24.622l35.771-143.45L33.232,164.729l9.77,5.645L55.586,148.581z M87.394,93.484l-16.708,67.018l-5.018-17.747 l-8.028,2.27L87.394,93.484z"></path>
<path d="M189.85,107.717 L137.892,137.718 L143.532,147.49 L185.723,123.133 L231.109,201.746 L24.895,201.746 L37.363,180.146 L27.592,174.505 L5.347,213.03 L250.653,213.03 z"></path>
<path d="M5.347,247.299v8.111h245.307v-8.111l-41.94-0.003c-1.336,0-2.404-1.065-2.441-2.396v-12.14 c0.037-1.315,1.089-2.368,2.41-2.385h41.972v-8.11H5.347v8.11h41.951c1.338,0.017,2.427,1.104,2.427,2.449v12.01 c0,1.365-1.105,2.462-2.457,2.462L5.347,247.299z M139.438,247.296c-1.334,0-2.406-1.065-2.439-2.396v-12.14 c0.033-1.315,1.085-2.368,2.41-2.385h46.415c1.335,0.017,2.425,1.104,2.425,2.449v12.01c0,1.365-1.103,2.462-2.459,2.462H139.438z M70.193,247.296c-1.339,0-2.408-1.065-2.441-2.396v-12.14c0.033-1.315,1.086-2.368,2.407-2.385h46.418 c1.336,0.017,2.425,1.104,2.425,2.449v12.01c0,1.365-1.103,2.462-2.458,2.462H70.193z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,183 @@
import { createElement as $el } from "../utils_dom.js";
export let logoRgthree = "";
export async function logoRgthreeAsync() {
var _a;
if (logoRgthree)
return logoRgthree;
let baseUrl = null;
if (window.location.pathname.includes("/rgthree/")) {
const parts = (_a = window.location.pathname.split("/rgthree/")[1]) === null || _a === void 0 ? void 0 : _a.split("/");
if (parts && parts.length) {
baseUrl = parts.map(() => "../").join("") + "rgthree";
}
}
baseUrl = baseUrl || "./rgthree";
return fetch(`${baseUrl}/logo_markup.svg?fg=currentColor&cssClass=rgthree-logo&w=auto&h=auto`)
.then((r) => r.text())
.then((t) => {
if (t.length < 100) {
t = `<svg viewBox="0 0 256 256" fill="currentColor" class="rgthree-logo">
<path d="M88.503,158.997 L152.731,196.103 L152.738,196.092 L152.762,196.103 L152.769,196.106 L152.771,196.103 L183.922,142.084 L174.153,136.437 L148.611,180.676 L101.512,153.484 L132.193,30.415 L156.124,71.869 L165.896,66.225 L128.002,0.59 "></path>
<path d="M55.586,148.581l13.44,47.521l0.014,0.051l0.168-0.051l10.689-3.022l-6.589-23.313l45.609,26.335l0.087,0.051l0.027-0.051 l5.617-9.718l-42.648-24.622l35.771-143.45L33.232,164.729l9.77,5.645L55.586,148.581z M87.394,93.484l-16.708,67.018l-5.018-17.747 l-8.028,2.27L87.394,93.484z"></path>
<path d="M189.85,107.717 L137.892,137.718 L143.532,147.49 L185.723,123.133 L231.109,201.746 L24.895,201.746 L37.363,180.146 L27.592,174.505 L5.347,213.03 L250.653,213.03 "></path>
<path d="M5.347,247.299v8.111h245.307v-8.111l-41.94-0.003c-1.336,0-2.404-1.065-2.441-2.396v-12.14 c0.037-1.315,1.089-2.368,2.41-2.385h41.972v-8.11H5.347v8.11h41.951c1.338,0.017,2.427,1.104,2.427,2.449v12.01 c0,1.365-1.105,2.462-2.457,2.462L5.347,247.299z M139.438,247.296c-1.334,0-2.406-1.065-2.439-2.396v-12.14 c0.033-1.315,1.085-2.368,2.41-2.385h46.415c1.335,0.017,2.425,1.104,2.425,2.449v12.01c0,1.365-1.103,2.462-2.459,2.462H139.438z M70.193,247.296c-1.339,0-2.408-1.065-2.441-2.396v-12.14c0.033-1.315,1.086-2.368,2.407-2.385h46.418 c1.336,0.017,2.425,1.104,2.425,2.449v12.01c0,1.365-1.103,2.462-2.458,2.462H70.193z"></path>
</svg>`;
}
logoRgthree = t;
return t;
});
}
logoRgthreeAsync();
export const github = `<svg viewBox="0 0 16 16" fill="currentColor" class="github-logo">
<path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"></path>
</svg>`;
export const iconStarFilled = `<svg viewBox="0 0 16 16" fill="currentColor" class="github-star">
<path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"></path>
</svg>`;
export const iconReplace = `<svg viewBox="0 0 52 52" fill="currentColor">
<path d="M20,37.5c0-0.8-0.7-1.5-1.5-1.5h-15C2.7,36,2,36.7,2,37.5v11C2,49.3,2.7,50,3.5,50h15c0.8,0,1.5-0.7,1.5-1.5 V37.5z"/>
<path d="M8.1,22H3.2c-1,0-1.5,0.9-0.9,1.4l8,8.3c0.4,0.3,1,0.3,1.4,0l8-8.3c0.6-0.6,0.1-1.4-0.9-1.4h-4.7 c0-5,4.9-10,9.9-10V6C15,6,8.1,13,8.1,22z"/>
<path d="M41.8,20.3c-0.4-0.3-1-0.3-1.4,0l-8,8.3c-0.6,0.6-0.1,1.4,0.9,1.4h4.8c0,6-4.1,10-10.1,10v6 c9,0,16.1-7,16.1-16H49c1,0,1.5-0.9,0.9-1.4L41.8,20.3z"/>
<path d="M50,3.5C50,2.7,49.3,2,48.5,2h-15C32.7,2,32,2.7,32,3.5v11c0,0.8,0.7,1.5,1.5,1.5h15c0.8,0,1.5-0.7,1.5-1.5 V3.5z"/>
</svg>`;
export const iconNode = `<svg viewBox="0 -0.5 25 25" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.5 19H9.5C7.29086 19 5.5 17.2091 5.5 15V9C5.5 6.79086 7.29086 5 9.5 5H15.5C17.7091 5 19.5 6.79086 19.5 9V15C19.5 17.2091 17.7091 19 15.5 19Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.5 9.75C19.9142 9.75 20.25 9.41421 20.25 9C20.25 8.58579 19.9142 8.25 19.5 8.25V9.75ZM5.5 8.25C5.08579 8.25 4.75 8.58579 4.75 9C4.75 9.41421 5.08579 9.75 5.5 9.75V8.25ZM11.5 14.25C11.0858 14.25 10.75 14.5858 10.75 15C10.75 15.4142 11.0858 15.75 11.5 15.75V14.25ZM13.5 15.75C13.9142 15.75 14.25 15.4142 14.25 15C14.25 14.5858 13.9142 14.25 13.5 14.25V15.75ZM19.5 8.25H5.5V9.75H19.5V8.25ZM11.5 15.75H13.5V14.25H11.5V15.75Z" fill="currentColor" />
</svg>`;
export const iconGear = `<svg viewBox="0 0 24 24" fill="currentColor">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.7848 0.449982C13.8239 0.449982 14.7167 1.16546 14.9122 2.15495L14.9991 2.59495C15.3408 4.32442 17.1859 5.35722 18.9016 4.7794L19.3383 4.63233C20.3199 4.30175 21.4054 4.69358 21.9249 5.56605L22.7097 6.88386C23.2293 7.75636 23.0365 8.86366 22.2504 9.52253L21.9008 9.81555C20.5267 10.9672 20.5267 13.0328 21.9008 14.1844L22.2504 14.4774C23.0365 15.1363 23.2293 16.2436 22.7097 17.1161L21.925 18.4339C21.4054 19.3064 20.3199 19.6982 19.3382 19.3676L18.9017 19.2205C17.1859 18.6426 15.3408 19.6754 14.9991 21.405L14.9122 21.845C14.7167 22.8345 13.8239 23.55 12.7848 23.55H11.2152C10.1761 23.55 9.28331 22.8345 9.08781 21.8451L9.00082 21.4048C8.65909 19.6754 6.81395 18.6426 5.09822 19.2205L4.66179 19.3675C3.68016 19.6982 2.59465 19.3063 2.07505 18.4338L1.2903 17.1161C0.770719 16.2436 0.963446 15.1363 1.74956 14.4774L2.09922 14.1844C3.47324 13.0327 3.47324 10.9672 2.09922 9.8156L1.74956 9.52254C0.963446 8.86366 0.77072 7.75638 1.2903 6.8839L2.07508 5.56608C2.59466 4.69359 3.68014 4.30176 4.66176 4.63236L5.09831 4.77939C6.81401 5.35722 8.65909 4.32449 9.00082 2.59506L9.0878 2.15487C9.28331 1.16542 10.176 0.449982 11.2152 0.449982H12.7848ZM12 15.3C13.8225 15.3 15.3 13.8225 15.3 12C15.3 10.1774 13.8225 8.69998 12 8.69998C10.1774 8.69998 8.69997 10.1774 8.69997 12C8.69997 13.8225 10.1774 15.3 12 15.3Z" />
</svg>`;
export const checkmark = `<svg viewBox="0 0 32 32" fill="currentColor" class="icon-checkmark">
<g transform="translate(-518.000000, -1039.000000)">
<path d="M548.783,1040.2 C547.188,1038.57 544.603,1038.57 543.008,1040.2 L528.569,1054.92 L524.96,1051.24 C523.365,1049.62 520.779,1049.62 519.185,1051.24 C517.59,1052.87 517.59,1055.51 519.185,1057.13 L525.682,1063.76 C527.277,1065.39 529.862,1065.39 531.457,1063.76 L548.783,1046.09 C550.378,1044.46 550.378,1041.82 548.783,1040.2"></path>
</g>
</svg>`;
export const logoCivitai = `<svg viewBox="0 0 178 178" class="logo-civitai">
<defs>
<linearGradient id="bgblue" gradientUnits="userSpaceOnUse" x1="89.3" y1="-665.5" x2="89.3" y2="-841.1" gradientTransform="matrix(1 0 0 -1 0 -664)">
<stop offset="0" style="stop-color:#1284F7"/>
<stop offset="1" style="stop-color:#0A20C9"/>
</linearGradient>
</defs>
<path fill="#000" d="M13.3,45.4v87.7l76,43.9l76-43.9V45.4l-76-43.9L13.3,45.4z"/>
<path style="fill:url(#bgblue);" d="M89.3,29.2l52,30v60l-52,30l-52-30v-60 L89.3,29.2 M89.3,1.5l-76,43.9v87.8l76,43.9l76-43.9V45.4L89.3,1.5z" />
<path fill="#FFF" d="M104.1,97.2l-14.9,8.5l-14.9-8.5v-17l14.9-8.5l14.9,8.5h18.2V69.7l-33-19l-33,19v38.1l33,19l33-19V97.2H104.1z" />
</svg>`;
export const iconOutLink = `<svg viewBox="0 0 32 32">
<path d="M 18 5 L 18 7 L 23.5625 7 L 11.28125 19.28125 L 12.71875 20.71875 L 25 8.4375 L 25 14 L 27 14 L 27 5 Z M 5 9 L 5 27 L 23 27 L 23 14 L 21 16 L 21 25 L 7 25 L 7 11 L 16 11 L 18 9 Z"></path>
</svg>`;
export const link = `<svg viewBox="0 0 640 512">
<path d="M598.6 41.41C570.1 13.8 534.8 0 498.6 0s-72.36 13.8-99.96 41.41l-43.36 43.36c15.11 8.012 29.47 17.58 41.91 30.02c3.146 3.146 5.898 6.518 8.742 9.838l37.96-37.96C458.5 72.05 477.1 64 498.6 64c20.67 0 40.1 8.047 54.71 22.66c14.61 14.61 22.66 34.04 22.66 54.71s-8.049 40.1-22.66 54.71l-133.3 133.3C405.5 343.1 386 352 365.4 352s-40.1-8.048-54.71-22.66C296 314.7 287.1 295.3 287.1 274.6s8.047-40.1 22.66-54.71L314.2 216.4C312.1 212.5 309.9 208.5 306.7 205.3C298.1 196.7 286.8 192 274.6 192c-11.93 0-23.1 4.664-31.61 12.97c-30.71 53.96-23.63 123.6 22.39 169.6C293 402.2 329.2 416 365.4 416c36.18 0 72.36-13.8 99.96-41.41L598.6 241.3c28.45-28.45 42.24-66.01 41.37-103.3C639.1 102.1 625.4 68.16 598.6 41.41zM234 387.4L196.1 425.3C181.5 439.1 162 448 141.4 448c-20.67 0-40.1-8.047-54.71-22.66c-14.61-14.61-22.66-34.04-22.66-54.71s8.049-40.1 22.66-54.71l133.3-133.3C234.5 168 253.1 160 274.6 160s40.1 8.048 54.71 22.66c14.62 14.61 22.66 34.04 22.66 54.71s-8.047 40.1-22.66 54.71L325.8 295.6c2.094 3.939 4.219 7.895 7.465 11.15C341.9 315.3 353.3 320 365.4 320c11.93 0 23.1-4.664 31.61-12.97c30.71-53.96 23.63-123.6-22.39-169.6C346.1 109.8 310.8 96 274.6 96C238.4 96 202.3 109.8 174.7 137.4L41.41 270.7c-27.6 27.6-41.41 63.78-41.41 99.96c-.0001 36.18 13.8 72.36 41.41 99.97C69.01 498.2 105.2 512 141.4 512c36.18 0 72.36-13.8 99.96-41.41l43.36-43.36c-15.11-8.012-29.47-17.58-41.91-30.02C239.6 394.1 236.9 390.7 234 387.4z"/>
</svg>`;
export const pencil = `<svg viewBox="0 0 24 24">
<path d="M 16.9375 1.0625 L 3.875 14.125 L 1.0742188 22.925781 L 9.875 20.125 L 22.9375 7.0625 C 22.9375 7.0625 22.8375 4.9615 20.9375 3.0625 C 19.0375 1.1625 16.9375 1.0625 16.9375 1.0625 z M 17.3125 2.6875 C 18.3845 2.8915 19.237984 3.3456094 19.896484 4.0214844 C 20.554984 4.6973594 21.0185 5.595 21.3125 6.6875 L 19.5 8.5 L 15.5 4.5 L 16.9375 3.0625 L 17.3125 2.6875 z M 4.9785156 15.126953 C 4.990338 15.129931 6.1809555 15.430955 7.375 16.625 C 8.675 17.825 8.875 18.925781 8.875 18.925781 L 8.9179688 18.976562 L 5.3691406 20.119141 L 3.8730469 18.623047 L 4.9785156 15.126953 z"/>
</svg>`;
export const dotdotdot = `<svg viewBox="0 0 24 24" fill="currentColor">
<circle cy="12" r="3" cx="3"></circle>
<circle cy="12" r="3" cx="12"></circle>
<circle cx="21" cy="12" r="3"></circle>
</svg>`;
export const models = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 4h6v6h-6z"></path>
<path d="M14 4h6v6h-6z"></path>
<path d="M4 14h6v6h-6z"></path>
<path d="M17 17m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0"></path>
</svg>`;
export const pencilColored = `<svg viewBox="0 0 64 64">
<path fill="#ffce31" d="M7.934 41.132L39.828 9.246l14.918 14.922l-31.895 31.886z"></path>
<path d="M61.3 4.6l-1.9-1.9C55.8-.9 50-.9 46.3 2.7l-6.5 6.5l15 15l6.5-6.5c3.6-3.6 3.6-9.5 0-13.1" fill="#ed4c5c"></path>
<path fill="#93a2aa" d="M35.782 13.31l4.1-4.102l14.92 14.92l-4.1 4.101z"></path>
<path fill="#c7d3d8" d="M37.338 14.865l4.1-4.101l11.739 11.738l-4.102 4.1z"></path>
<path fill="#fed0ac" d="M7.9 41.1l-6.5 17l4.5 4.5l17-6.5z"/>
<path d="M.3 61.1c-.9 2.4.3 3.5 2.7 2.6l8.2-3.1l-7.7-7.7l-3.2 8.2" fill="#333"></path>
<path fill="#ffdf85" d="M7.89 41.175l27.86-27.86l4.95 4.95l-27.86 27.86z"/>
<path fill="#ff8736" d="M17.904 51.142l27.86-27.86l4.95 4.95l-27.86 27.86z"></path>
</svg>`;
export const diskColored = `<svg viewBox="-0.01 -0.008 100.016 100.016">
<path fill="#26f" fill_="#23475F" d="M88.555-.008H83v.016a2 2 0 0 1-2 2H19a2 2 0 0 1-2-2v-.016H4a4 4 0 0 0-4 4v92.016a4 4 0 0 0 4 4h92a4 4 0 0 0 4-4V11.517c.049-.089-11.436-11.454-11.445-11.525z"/>
<path fill="#04d" fill_="#1C3C50" d="M81.04 53.008H18.96a2 2 0 0 0-2 2v45h66.08v-45c0-1.106-.895-2-2-2zm-61.957-10h61.834a2 2 0 0 0 2-2V.547A1.993 1.993 0 0 1 81 2.007H19c-.916 0-1.681-.62-1.917-1.46v40.46a2 2 0 0 0 2 2.001z"/>
<path fill="#EBF0F1" d="M22 55.977h56a2 2 0 0 1 2 2v37.031a2 2 0 0 1-2 2H22c-1.104 0-2-.396-2-1.5V57.977a2 2 0 0 1 2-2z"/>
<path fill="#BCC4C8" d="M25 77.008h50v1H25v-1zm0 10h50v1H25v-1z"/>
<path fill="#1C3C50" d="M7 84.008h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2zm83 0h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2z"/>
<path fill="#BCC4C8" d="M37 1.981v36.026a2 2 0 0 0 2 2h39a2 2 0 0 0 2-2V1.981c0 .007-42.982.007-43 0zm37 29.027a2 2 0 0 1-2 2h-6a2 2 0 0 1-2-2V10.981a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v20.027z"/>
<path fill="#FF9D00" d="M78 55.977H22a2 2 0 0 0-2 2v10.031h60V57.977a2 2 0 0 0-2-2z"/>
</svg>`;
export const folderColored = `<svg viewBox="0 0 501.379 501.379">
<path style="fill:#EF9F2C;" d="M406.423,93.889H205.889c-17.067,0-30.933-13.867-30.933-30.933s-13.867-30.933-30.933-30.933H30.956
c-17.067,0-30.933,13.867-30.933,30.933v375.467c0,17.067,13.867,30.933,30.933,30.933h375.467
c17.067,0,30.933-13.867,30.933-30.933v-313.6C436.289,107.756,422.423,93.889,406.423,93.889z"/>
<path style="fill:#FEC656;" d="M470.423,157.889H97.089c-13.867,0-26.667,9.6-29.867,22.4l-66.133,249.6
c-5.333,19.2,9.6,38.4,29.867,38.4h373.333c13.867,0,26.667-9.6,29.867-22.4l66.133-248.533
C505.623,177.089,490.689,157.889,470.423,157.889z"/>
</svg>`;
export const modelsColored = `<svg viewBox="0 0 24 24">
<path fill="#aa3366" d="M0 0h10v10h-10z"></path>
<path d="M14 0h10v10h-10z" fill="#3366aa"></path>
<path d="M0 14h10v10h-10z" fill="#66aa33"></path>
<path fill="#dd9922" d="M19 19m-5 0 a5 5 0 1 0 10 0 a5 5 0 1 0 -10 0"></path>
</svg>`;
export const legoBlocksColored = `<svg viewBox="0 0 512 512">
<g>
<rect x="57.67" style="fill:#00BAB9;" width="101.275" height="78.769"/>
<rect x="205.363" style="fill:#00BAB9;" width="101.275" height="78.769"/>
<rect x="353.055" style="fill:#00BAB9;" width="101.275" height="78.769"/>
</g>
<polygon style="fill:#B8DE6F;" points="478.242,289.758 478.242,512 33.758,512 33.758,289.758 256,267.253 "/>
<polygon style="fill:#41D4D3;" points="478.242,67.516 478.242,289.758 33.758,289.758 33.758,67.516 57.67,67.516 158.945,67.516
205.363,67.516 306.637,67.516 353.055,67.516 454.33,67.516 "/>
<g>
<circle style="fill:#00BAB9;" cx="402.286" cy="143.473" r="8.44"/>
<circle style="fill:#00BAB9;" cx="368.527" cy="177.231" r="8.44"/>
</g>
<circle style="fill:#7BD288;" cx="109.714" cy="436.044" r="8.44"/>
</svg>`;
export const legoBlockColored = `<svg viewBox="0 0 256 256">
<style>
.s0 { fill: #ff0000 }
.s1 { fill: #c30000 }
.s2 { fill: #800000 }
.s3 { fill: #cc0000 }
.s4 { fill: #e00000 }
</style>
<g id="Folder 2">
<path id="Shape 1 copy 2" class="s0" d="m128 61l116 45-116 139-116-139z"/>
<path id="Shape 1" class="s1" d="m12 106l116 45v95l-116-45z"/>
<path id="Shape 1 copy" class="s2" d="m244 106l-116 45v95l116-45z"/>
<g id="Folder 1">
<path id="Shape 2" class="s3" d="m102 111.2c0-6.1 11.4-9.9 25.5-9.9 14.1 0 25.5 3.8 25.5 9.9 0 3.3 0 13.3 0 16.6 0 6.1-11.4 10.9-25.5 10.9-14.1 0-25.5-4.8-25.5-10.9 0-3.3 0-13.3 0-16.6z"/>
<path id="Shape 2 copy 4" class="s1" d="m102 111.2c0-6.1 11.4-9.9 25.5-9.9 14.1 0 25.5 3.8 25.5 9.9 0 3.3 0 13.3 0 16.6 0 6.1-11.4 10.9-25.5 10.9-14.1 0-25.5-4.8-25.5-10.9 0-3.3 0-13.3 0-16.6z"/>
<path id="Shape 2 copy 2" class="s2" d="m127.5 101.3c14.1 0 25.5 3.8 25.5 9.9 0 3.3 0 13.3 0 16.6 0 6.1-11.4 10.9-25.5 10.9 0-13.1 0-25.7 0-37.4z"/>
<path id="Shape 2 copy" class="s0" d="m127.5 118.8c-12.2 0-22-3.4-22-7.6 0-4.2 9.8-7.7 22-7.7 12.2 0 22 3.5 22 7.7 0 4.2-9.8 7.6-22 7.6zm0 0c-12.2 0-22-3.4-22-7.6 0-4.2 9.8-7.7 22-7.7 12.2 0 22 3.5 22 7.7 0 4.2-9.8 7.6-22 7.6z"/>
</g>
<g id="Folder 1 copy">
<path id="Shape 2" class="s4" d="m103 67.5c0-5.8 11-9.5 24.5-9.5 13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5-13.5 0-24.5-4.7-24.5-10.5 0-3.2 0-12.8 0-16z"/>
<path id="Shape 2 copy 4" class="s1" d="m103 67.5c0-5.8 11-9.5 24.5-9.5 13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5-13.5 0-24.5-4.7-24.5-10.5 0-3.2 0-12.8 0-16z"/>
<path id="Shape 2 copy 2" class="s2" d="m127.5 58c13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5 0-12.6 0-24.8 0-36z"/>
<path id="Shape 2 copy" class="s0" d="m127.5 74.9c-11.7 0-21.2-3.3-21.2-7.4 0-4.1 9.5-7.4 21.2-7.4 11.7 0 21.2 3.3 21.2 7.4 0 4.1-9.5 7.4-21.2 7.4zm0 0c-11.7 0-21.2-3.3-21.2-7.4 0-4.1 9.5-7.4 21.2-7.4 11.7 0 21.2 3.3 21.2 7.4 0 4.1-9.5 7.4-21.2 7.4z"/>
</g>
<g id="Folder 1 copy 2">
<path id="Shape 2" class="s4" d="m161 89.5c0-5.8 11-9.5 24.5-9.5 13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5-13.5 0-24.5-4.7-24.5-10.5 0-3.2 0-12.8 0-16z"/>
<path id="Shape 2 copy 4" class="s1" d="m161 89.5c0-5.8 11-9.5 24.5-9.5 13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5-13.5 0-24.5-4.7-24.5-10.5 0-3.2 0-12.8 0-16z"/>
<path id="Shape 2 copy 2" class="s2" d="m185.5 80c13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5 0-12.6 0-24.8 0-36z"/>
<path id="Shape 2 copy" class="s0" d="m185.5 96.9c-11.7 0-21.2-3.3-21.2-7.4 0-4.1 9.5-7.4 21.2-7.4 11.7 0 21.2 3.3 21.2 7.4 0 4.1-9.5 7.4-21.2 7.4zm0 0c-11.7 0-21.2-3.3-21.2-7.4 0-4.1 9.5-7.4 21.2-7.4 11.7 0 21.2 3.3 21.2 7.4 0 4.1-9.5 7.4-21.2 7.4z"/>
</g>
<g id="Folder 1 copy 3">
<path id="Shape 2" class="s4" d="m45 89.5c0-5.8 11-9.5 24.5-9.5 13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5-13.5 0-24.5-4.7-24.5-10.5 0-3.2 0-12.8 0-16z"/>
<path id="Shape 2 copy 4" class="s1" d="m45 89.5c0-5.8 11-9.5 24.5-9.5 13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5-13.5 0-24.5-4.7-24.5-10.5 0-3.2 0-12.8 0-16z"/>
<path id="Shape 2 copy 2" class="s2" d="m69.5 80c13.5 0 24.5 3.7 24.5 9.5 0 3.2 0 12.8 0 16 0 5.8-11 10.5-24.5 10.5 0-12.6 0-24.8 0-36z"/>
<path id="Shape 2 copy" class="s0" d="m69.5 96.9c-11.7 0-21.2-3.3-21.2-7.4 0-4.1 9.5-7.4 21.2-7.4 11.7 0 21.2 3.3 21.2 7.4 0 4.1-9.5 7.4-21.2 7.4zm0 0c-11.7 0-21.2-3.3-21.2-7.4 0-4.1 9.5-7.4 21.2-7.4 11.7 0 21.2 3.3 21.2 7.4 0 4.1-9.5 7.4-21.2 7.4z"/>
</g>
</g>
</svg>`;
export const gearColored = `<svg viewBox="0 0 128 128" preserveAspectRatio="xMidYMid meet">
<path d="M124 71.85v-15.7c0-.59-.45-1.09-1.03-1.15l-17.83-1.89c-.47-.05-.85-.38-.98-.83c-.86-2.95-2.03-5.76-3.48-8.39c-.23-.41-.19-.92.11-1.28l11.28-13.94c.37-.46.34-1.13-.08-1.54l-11.1-11.1a1.15 1.15 0 0 0-1.54-.08L85.39 27.22c-.37.3-.87.33-1.28.11a41.796 41.796 0 0 0-8.39-3.48c-.45-.13-.78-.51-.83-.98L73 5.03C72.94 4.45 72.44 4 71.85 4h-15.7c-.59 0-1.09.45-1.15 1.03l-1.89 17.83c-.05.47-.38.85-.83.98c-2.95.86-5.76 2.03-8.39 3.48c-.41.23-.92.19-1.28-.11L28.67 15.94a1.15 1.15 0 0 0-1.54.08l-11.1 11.1a1.15 1.15 0 0 0-.08 1.54L27.23 42.6c.3.37.33.87.11 1.28a41.796 41.796 0 0 0-3.48 8.39c-.13.45-.51.78-.98.83L5.03 55c-.58.06-1.03.56-1.03 1.15v15.7c0 .59.45 1.09 1.03 1.15l17.83 1.89c.47.05.85.38.98.83c.86 2.95 2.03 5.76 3.48 8.39c.23.41.19.92-.11 1.28L15.94 99.33c-.37.46-.34 1.13.08 1.54l11.1 11.1c.42.42 1.08.45 1.54.08l13.94-11.28c.37-.3.87-.33 1.28-.11c2.64 1.45 5.45 2.62 8.39 3.48c.45.13.78.51.83.98l1.9 17.85c.06.59.56 1.03 1.15 1.03h15.7c.59 0 1.09-.45 1.15-1.03l1.89-17.83c.05-.47.38-.85.83-.98c2.95-.86 5.76-2.03 8.39-3.48c.41-.23.92-.19 1.28.11l13.94 11.28c.46.37 1.13.34 1.54-.08l11.1-11.1c.42-.42.45-1.08.08-1.54l-11.28-13.94c-.3-.37-.33-.87-.11-1.28c1.45-2.64 2.62-5.45 3.48-8.39c.13-.45.51-.78.98-.83L122.97 73c.58-.06 1.03-.56 1.03-1.15zm-60 3.43c-6.23 0-11.28-5.05-11.28-11.28S57.77 52.72 64 52.72S75.28 57.77 75.28 64S70.23 75.28 64 75.28z" fill="#82aec0"></path>
<path d="M80.56 49.48c3.67 4.18 5.78 9.77 5.43 15.85c-.65 11.16-9.83 20.19-21 20.68c-4.75.21-9.18-1.09-12.86-3.45c-.28-.18-.58.2-.34.44a22.412 22.412 0 0 0 17.85 6.67c10.78-.85 19.56-9.5 20.55-20.27c.77-8.36-3.06-15.87-9.23-20.33c-.29-.2-.62.15-.4.41z" fill="#2f7889"></path>
<path d="M43.87 65.32c-.67-13.15 7.83-22.79 20.01-22.79c.65 0 1.68 0 2.48.92c1.01 1.18 1.1 2.6 0 3.77c-.81.86-1.95.92-2.53 1c-12.3 1.59-15.18 9.35-15.83 16.77c-.03.33.06 2.35-1.71 2.56c-2.15.25-2.41-1.91-2.42-2.23z" fill="#b9e4ea"></path>
<path d="M25.24 65.87c-.01-22.03 15.9-40.19 38.13-41.05c.68-.03 2.45 0 3.55.99c1.01.91 1.38 2.51.79 3.82c-.95 2.11-2.85 2.07-3.36 2.09c-18.51.66-34.18 15.73-34.19 33.95c0 .29-.05.58-.15.84l-.1.25c-.76 1.98-3.52 2.09-4.43.18c-.15-.34-.24-.7-.24-1.07z" fill="#94d1e0"></path>
</svg>`;
export function $svg(markup, attrs) {
if (!markup.match(/^\s*<svg/)) {
throw new Error("Cannot call $svg with non-svg markup.");
}
return $el(markup, attrs || {});
}

View File

@@ -0,0 +1,98 @@
import { generateId, wait } from "./shared_utils.js";
import { createElement as $el, getClosestOrSelf, setAttributes } from "./utils_dom.js";
class Menu {
constructor(options) {
this.element = $el('menu.rgthree-menu');
this.callbacks = new Map();
this.handleWindowPointerDownBound = this.handleWindowPointerDown.bind(this);
this.setOptions(options);
this.element.addEventListener('pointerup', async (e) => {
var _a, _b;
const target = getClosestOrSelf(e.target, "[data-callback],menu");
if (e.which !== 1) {
return;
}
const callback = (_a = target === null || target === void 0 ? void 0 : target.dataset) === null || _a === void 0 ? void 0 : _a['callback'];
if (callback) {
const halt = await ((_b = this.callbacks.get(callback)) === null || _b === void 0 ? void 0 : _b(e));
if (halt !== false) {
this.close();
}
}
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
});
}
setOptions(options) {
for (const option of options) {
if (option.type === 'title') {
this.element.appendChild($el(`li`, {
html: option.label
}));
}
else {
const id = generateId(8);
this.callbacks.set(id, async (e) => { var _a; return (_a = option === null || option === void 0 ? void 0 : option.callback) === null || _a === void 0 ? void 0 : _a.call(option, e); });
this.element.appendChild($el(`li[role="button"][data-callback="${id}"]`, {
html: option.label
}));
}
}
}
toElement() {
return this.element;
}
async open(e) {
const parent = e.target.closest('div,dialog,body');
parent.appendChild(this.element);
setAttributes(this.element, {
style: {
left: `${e.clientX + 16}px`,
top: `${e.clientY - 16}px`,
}
});
this.element.setAttribute('state', 'measuring-open');
await wait(16);
const rect = this.element.getBoundingClientRect();
if (rect.right > window.innerWidth) {
this.element.style.left = `${e.clientX - rect.width - 16}px`;
await wait(16);
}
this.element.setAttribute('state', 'open');
setTimeout(() => {
window.addEventListener('pointerdown', this.handleWindowPointerDownBound);
});
}
handleWindowPointerDown(e) {
if (!this.element.contains(e.target)) {
this.close();
}
}
async close() {
window.removeEventListener('pointerdown', this.handleWindowPointerDownBound);
this.element.setAttribute('state', 'measuring-closed');
await wait(16);
this.element.setAttribute('state', 'closed');
this.element.remove();
}
isOpen() {
return (this.element.getAttribute('state') || '').includes('open');
}
}
export class MenuButton {
constructor(options) {
this.element = $el('button.rgthree-button[data-action="open-menu"]');
this.options = options;
this.element.innerHTML = options.icon;
this.menu = new Menu(options.options);
this.element.addEventListener('pointerdown', (e) => {
if (!this.menu.isOpen()) {
this.menu.open(e);
}
});
}
toElement() {
return this.element;
}
}

View File

@@ -0,0 +1,72 @@
import { rgthreeApi } from "./rgthree_api.js";
import { api } from "../../scripts/api.js";
class BaseModelInfoService extends EventTarget {
constructor() {
super();
this.fileToInfo = new Map();
this.init();
}
init() {
api.addEventListener(this.apiRefreshEventString, this.handleAsyncUpdate.bind(this));
}
async getInfo(file, refresh, light) {
if (this.fileToInfo.has(file) && !refresh) {
return this.fileToInfo.get(file);
}
return this.fetchInfo(file, refresh, light);
}
async refreshInfo(file) {
return this.fetchInfo(file, true);
}
async clearFetchedInfo(file) {
await rgthreeApi.clearModelsInfo({ type: this.modelInfoType, files: [file] });
this.fileToInfo.delete(file);
return null;
}
async savePartialInfo(file, data) {
let info = await rgthreeApi.saveModelInfo(this.modelInfoType, file, data);
this.fileToInfo.set(file, info);
return info;
}
handleAsyncUpdate(event) {
var _a;
const info = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.data;
if (info === null || info === void 0 ? void 0 : info.file) {
this.setFreshInfo(info.file, info);
}
}
async fetchInfo(file, refresh = false, light = false) {
var _a;
let info = null;
if (!refresh) {
info = await rgthreeApi.getModelsInfo({ type: this.modelInfoType, files: [file], light });
}
else {
info = await rgthreeApi.refreshModelsInfo({ type: this.modelInfoType, files: [file] });
}
info = (_a = info === null || info === void 0 ? void 0 : info[0]) !== null && _a !== void 0 ? _a : null;
if (!light) {
this.fileToInfo.set(file, info);
}
return info;
}
setFreshInfo(file, info) {
this.fileToInfo.set(file, info);
}
}
class LoraInfoService extends BaseModelInfoService {
constructor() {
super(...arguments);
this.apiRefreshEventString = "rgthree-refreshed-loras-info";
this.modelInfoType = 'loras';
}
}
class CheckpointInfoService extends BaseModelInfoService {
constructor() {
super(...arguments);
this.apiRefreshEventString = "rgthree-refreshed-checkpoints-info";
this.modelInfoType = 'checkpoints';
}
}
export const LORA_INFO_SERVICE = new LoraInfoService();
export const CHECKPOINT_INFO_SERVICE = new CheckpointInfoService();

View File

@@ -0,0 +1,179 @@
import { SERVICE as PROMPT_SERVICE } from "../common/prompt_service.js";
import { createElement } from "./utils_dom.js";
export class RgthreeProgressBar extends HTMLElement {
static create() {
return document.createElement(RgthreeProgressBar.NAME);
}
get currentNodeId() {
var _a, _b;
const prompt = this.currentPromptExecution;
const nodeId = ((_a = prompt === null || prompt === void 0 ? void 0 : prompt.errorDetails) === null || _a === void 0 ? void 0 : _a.node_id) || ((_b = prompt === null || prompt === void 0 ? void 0 : prompt.currentlyExecuting) === null || _b === void 0 ? void 0 : _b.nodeId);
return nodeId || null;
}
constructor() {
super();
this.shadow = null;
this.currentPromptExecution = null;
this.onProgressUpdateBound = this.onProgressUpdate.bind(this);
this.connected = false;
}
onProgressUpdate(e) {
var _a, _b, _c, _d;
if (!this.connected)
return;
const prompt = e.detail.prompt;
this.currentPromptExecution = prompt;
if (prompt === null || prompt === void 0 ? void 0 : prompt.errorDetails) {
let progressText = `${(_a = prompt.errorDetails) === null || _a === void 0 ? void 0 : _a.exception_type} ${((_b = prompt.errorDetails) === null || _b === void 0 ? void 0 : _b.node_id) || ""} ${((_c = prompt.errorDetails) === null || _c === void 0 ? void 0 : _c.node_type) || ""}`;
this.progressTextEl.innerText = progressText;
this.progressNodesEl.classList.add("-error");
this.progressStepsEl.classList.add("-error");
return;
}
if (prompt === null || prompt === void 0 ? void 0 : prompt.currentlyExecuting) {
this.progressNodesEl.classList.remove("-error");
this.progressStepsEl.classList.remove("-error");
const current = prompt === null || prompt === void 0 ? void 0 : prompt.currentlyExecuting;
let progressText = `(${e.detail.queue}) `;
if (!prompt.totalNodes) {
progressText += `??%`;
this.progressNodesEl.style.width = `0%`;
}
else {
const percent = (prompt.executedNodeIds.length / prompt.totalNodes) * 100;
this.progressNodesEl.style.width = `${Math.max(2, percent)}%`;
progressText += `${Math.round(percent)}%`;
}
let nodeLabel = (_d = current.nodeLabel) === null || _d === void 0 ? void 0 : _d.trim();
let stepsLabel = "";
if (current.step != null && current.maxSteps) {
const percent = (current.step / current.maxSteps) * 100;
this.progressStepsEl.style.width = `${percent}%`;
if (current.pass > 1 || current.maxPasses != null) {
stepsLabel += `#${current.pass}`;
if (current.maxPasses && current.maxPasses > 0) {
stepsLabel += `/${current.maxPasses}`;
}
stepsLabel += ` - `;
}
stepsLabel += `${Math.round(percent)}%`;
}
if (nodeLabel || stepsLabel) {
progressText += ` - ${nodeLabel || "???"}${stepsLabel ? ` (${stepsLabel})` : ""}`;
}
if (!stepsLabel) {
this.progressStepsEl.style.width = `0%`;
}
this.progressTextEl.innerText = progressText;
}
else {
if (e === null || e === void 0 ? void 0 : e.detail.queue) {
this.progressTextEl.innerText = `(${e.detail.queue}) Running... in another tab`;
}
else {
this.progressTextEl.innerText = "Idle";
}
this.progressNodesEl.style.width = `0%`;
this.progressStepsEl.style.width = `0%`;
}
}
connectedCallback() {
if (!this.connected) {
PROMPT_SERVICE.addEventListener("progress-update", this.onProgressUpdateBound);
this.connected = true;
}
if (this.shadow) {
this.progressTextEl.innerText = "Idle";
this.progressNodesEl.style.width = `0%`;
this.progressStepsEl.style.width = `0%`;
return;
}
this.shadow = this.attachShadow({ mode: "open" });
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
:host {
position: relative;
overflow: hidden;
box-sizing: border-box;
background: var(--rgthree-progress-bg-color);
--rgthree-progress-bg-color: rgba(23, 23, 23, 0.9);
--rgthree-progress-nodes-bg-color: rgb(0, 128, 0);
--rgthree-progress-steps-bg-color: rgb(0, 128, 0);
--rgthree-progress-error-bg-color: rgb(128, 0, 0);
--rgthree-progress-text-color: #fff;
}
:host * {
box-sizing: inherit;
}
:host > div.bar {
background: var(--rgthree-progress-nodes-bg-color);
position: absolute;
left: 0;
top: 0;
width: 0%;
height: 50%;
z-index: 1;
transition: width 50ms ease-in-out;
}
:host > div.bar + div.bar {
background: var(--rgthree-progress-steps-bg-color);
top: 50%;
height: 50%;
z-index: 2;
}
:host > div.bar.-error {
background: var(--rgthree-progress-error-bg-color);
}
:host > .overlay {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 5;
background: linear-gradient(to bottom, rgba(255,255,255,0.25), rgba(0,0,0,0.25));
mix-blend-mode: overlay;
}
:host > span {
position: relative;
z-index: 4;
text-align: left;
font-size: inherit;
height: 100%;
font-family: sans-serif;
text-shadow: 1px 1px 0px #000;
display: flex;
flex-direction: row;
padding: 0 6px;
align-items: center;
justify-content: start;
color: var(--rgthree-progress-text-color);
text-shadow: black 0px 0px 2px;
}
:host > div.bar[style*="width: 0%"]:first-child,
:host > div.bar[style*="width:0%"]:first-child {
height: 0%;
}
:host > div.bar[style*="width: 0%"]:first-child + div,
:host > div.bar[style*="width:0%"]:first-child + div {
bottom: 0%;
}
`);
this.shadow.adoptedStyleSheets = [sheet];
const overlayEl = createElement(`div.overlay[part="overlay"]`, { parent: this.shadow });
this.progressNodesEl = createElement(`div.bar[part="progress-nodes"]`, { parent: this.shadow });
this.progressStepsEl = createElement(`div.bar[part="progress-steps"]`, { parent: this.shadow });
this.progressTextEl = createElement(`span[part="text"]`, { text: "Idle", parent: this.shadow });
}
disconnectedCallback() {
this.connected = false;
PROMPT_SERVICE.removeEventListener("progress-update", this.onProgressUpdateBound);
}
}
RgthreeProgressBar.NAME = "rgthree-progress-bar";
customElements.define(RgthreeProgressBar.NAME, RgthreeProgressBar);

View File

@@ -0,0 +1,188 @@
import { api } from "../../scripts/api.js";
import { getResolver } from "./shared_utils.js";
export class PromptExecution {
constructor(id) {
this.promptApi = null;
this.executedNodeIds = [];
this.totalNodes = 0;
this.currentlyExecuting = null;
this.errorDetails = null;
this.apiPrompt = getResolver();
this.id = id;
}
setPrompt(prompt) {
this.promptApi = prompt.output;
this.totalNodes = Object.keys(this.promptApi).length;
this.apiPrompt.resolve(null);
}
getApiNode(nodeId) {
var _a;
return ((_a = this.promptApi) === null || _a === void 0 ? void 0 : _a[String(nodeId)]) || null;
}
getNodeLabel(nodeId) {
var _a, _b;
const apiNode = this.getApiNode(nodeId);
let label = ((_a = apiNode === null || apiNode === void 0 ? void 0 : apiNode._meta) === null || _a === void 0 ? void 0 : _a.title) || (apiNode === null || apiNode === void 0 ? void 0 : apiNode.class_type) || undefined;
if (!label) {
const graphNode = (_b = this.maybeGetComfyGraph()) === null || _b === void 0 ? void 0 : _b.getNodeById(Number(nodeId));
label = (graphNode === null || graphNode === void 0 ? void 0 : graphNode.title) || (graphNode === null || graphNode === void 0 ? void 0 : graphNode.type) || undefined;
}
return label;
}
executing(nodeId, step, maxSteps) {
var _a;
if (nodeId == null) {
this.currentlyExecuting = null;
return;
}
if (((_a = this.currentlyExecuting) === null || _a === void 0 ? void 0 : _a.nodeId) !== nodeId) {
if (this.currentlyExecuting != null) {
this.executedNodeIds.push(nodeId);
}
this.currentlyExecuting = { nodeId, nodeLabel: this.getNodeLabel(nodeId), pass: 0 };
this.apiPrompt.promise.then(() => {
var _a;
if (this.currentlyExecuting == null) {
return;
}
const apiNode = this.getApiNode(nodeId);
if (!this.currentlyExecuting.nodeLabel) {
this.currentlyExecuting.nodeLabel = this.getNodeLabel(nodeId);
}
if ((apiNode === null || apiNode === void 0 ? void 0 : apiNode.class_type) === "UltimateSDUpscale") {
this.currentlyExecuting.pass--;
this.currentlyExecuting.maxPasses = -1;
}
else if ((apiNode === null || apiNode === void 0 ? void 0 : apiNode.class_type) === "IterativeImageUpscale") {
this.currentlyExecuting.maxPasses = (_a = apiNode === null || apiNode === void 0 ? void 0 : apiNode.inputs["steps"]) !== null && _a !== void 0 ? _a : -1;
}
});
}
if (step != null) {
if (!this.currentlyExecuting.step || step < this.currentlyExecuting.step) {
this.currentlyExecuting.pass++;
}
this.currentlyExecuting.step = step;
this.currentlyExecuting.maxSteps = maxSteps;
}
}
error(details) {
this.errorDetails = details;
}
maybeGetComfyGraph() {
var _a;
return ((_a = window === null || window === void 0 ? void 0 : window.app) === null || _a === void 0 ? void 0 : _a.graph) || null;
}
}
class PromptService extends EventTarget {
constructor(api) {
super();
this.promptsMap = new Map();
this.currentExecution = null;
this.lastQueueRemaining = 0;
const that = this;
const queuePrompt = api.queuePrompt;
api.queuePrompt = async function (num, prompt, ...args) {
let response;
try {
response = await queuePrompt.apply(api, [...arguments]);
}
catch (e) {
const promptExecution = that.getOrMakePrompt("error");
promptExecution.error({ exception_type: "Unknown." });
throw e;
}
const promptExecution = that.getOrMakePrompt(response.prompt_id);
promptExecution.setPrompt(prompt);
if (!that.currentExecution) {
that.currentExecution = promptExecution;
}
that.promptsMap.set(response.prompt_id, promptExecution);
that.dispatchEvent(new CustomEvent("queue-prompt", {
detail: {
prompt: promptExecution,
},
}));
return response;
};
api.addEventListener("status", (e) => {
var _a;
if (!((_a = e.detail) === null || _a === void 0 ? void 0 : _a.exec_info))
return;
this.lastQueueRemaining = e.detail.exec_info.queue_remaining;
this.dispatchProgressUpdate();
});
api.addEventListener("execution_start", (e) => {
if (!this.promptsMap.has(e.detail.prompt_id)) {
console.warn("'execution_start' fired before prompt was made.");
}
const prompt = this.getOrMakePrompt(e.detail.prompt_id);
this.currentExecution = prompt;
this.dispatchProgressUpdate();
});
api.addEventListener("executing", (e) => {
if (!this.currentExecution) {
this.currentExecution = this.getOrMakePrompt("unknown");
console.warn("'executing' fired before prompt was made.");
}
this.currentExecution.executing(e.detail);
this.dispatchProgressUpdate();
if (e.detail == null) {
this.currentExecution = null;
}
});
api.addEventListener("progress", (e) => {
if (!this.currentExecution) {
this.currentExecution = this.getOrMakePrompt(e.detail.prompt_id);
console.warn("'progress' fired before prompt was made.");
}
this.currentExecution.executing(e.detail.node, e.detail.value, e.detail.max);
this.dispatchProgressUpdate();
});
api.addEventListener("execution_cached", (e) => {
if (!this.currentExecution) {
this.currentExecution = this.getOrMakePrompt(e.detail.prompt_id);
console.warn("'execution_cached' fired before prompt was made.");
}
for (const cached of e.detail.nodes) {
this.currentExecution.executing(cached);
}
this.dispatchProgressUpdate();
});
api.addEventListener("executed", (e) => {
if (!this.currentExecution) {
this.currentExecution = this.getOrMakePrompt(e.detail.prompt_id);
console.warn("'executed' fired before prompt was made.");
}
});
api.addEventListener("execution_error", (e) => {
var _a;
if (!this.currentExecution) {
this.currentExecution = this.getOrMakePrompt(e.detail.prompt_id);
console.warn("'execution_error' fired before prompt was made.");
}
(_a = this.currentExecution) === null || _a === void 0 ? void 0 : _a.error(e.detail);
this.dispatchProgressUpdate();
});
}
async queuePrompt(prompt) {
return await api.queuePrompt(-1, prompt);
}
dispatchProgressUpdate() {
this.dispatchEvent(new CustomEvent("progress-update", {
detail: {
queue: this.lastQueueRemaining,
prompt: this.currentExecution,
},
}));
}
getOrMakePrompt(id) {
let prompt = this.promptsMap.get(id);
if (!prompt) {
prompt = new PromptExecution(id);
this.promptsMap.set(id, prompt);
}
return prompt;
}
}
export const SERVICE = new PromptService(api);

View File

@@ -0,0 +1,717 @@
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 _PyDict_dict;
import { check, deepFreeze } from "./shared_utils.js";
const MEMOIZED = { parser: null };
class ExecuteContext {
constructor(existing = {}) {
Object.assign(this, !!window.structuredClone ? structuredClone(existing) : { ...existing });
}
}
class InitialExecuteContext extends ExecuteContext {
}
const TYPE_TO_HANDLER = new Map([
["module", handleChildren],
["expression_statement", handleChildren],
["interpolation", handleInterpolation],
["block", handleChildren],
["comment", handleSwallow],
["return_statement", handleReturn],
["assignment", handleAssignment],
["named_expression", handleNamedExpression],
["identifier", handleIdentifier],
["attribute", handleAttribute],
["subscript", handleSubscript],
["call", handleCall],
["argument_list", handleArgumentsList],
["for_statement", handleForStatement],
["list_comprehension", handleListComprehension],
["comparison_operator", handleComparisonOperator],
["boolean_operator", handleBooleanOperator],
["binary_operator", handleBinaryOperator],
["not_operator", handleNotOperator],
["unary_operator", handleUnaryOperator],
["integer", handleNumber],
["float", handleNumber],
["string", handleString],
["tuple", handleList],
["list", handleList],
["dictionary", handleDictionary],
["pair", handleDictionaryPair],
["true", async (...args) => true],
["false", async (...args) => false],
]);
const DEFAULT_BUILT_INS = {
round: { fn: (n) => Math.round(Number(n)) },
ceil: { fn: (n) => Math.ceil(Number(n)) },
floor: { fn: (n) => Math.floor(Number(n)) },
len: { fn: (n) => { var _a, _b; return (_b = (_a = n === null || n === void 0 ? void 0 : n.__len__) === null || _a === void 0 ? void 0 : _a.call(n)) !== null && _b !== void 0 ? _b : n === null || n === void 0 ? void 0 : n.length; } },
int: { fn: (n) => Math.floor(Number(n)) },
float: { fn: (n) => Number(n) },
str: { fn: (n) => String(n) },
bool: { fn: (n) => !!n },
list: { fn: (tupl = []) => new PyList(tupl) },
tuple: { fn: (list = []) => new PyTuple(list) },
dict: { fn: (dict = {}) => new PyDict(dict) },
dir: { fn: (...args) => console.dir(...__unwrap__(...args)) },
print: { fn: (...args) => console.log(...__unwrap__(...args)) },
log: { fn: (...args) => console.log(...__unwrap__(...args)) },
};
export async function execute(code, ctx, additionalBuiltins) {
var _a, _b;
const builtIns = deepFreeze({ ...DEFAULT_BUILT_INS, ...(additionalBuiltins !== null && additionalBuiltins !== void 0 ? additionalBuiltins : {}) });
ctx = new InitialExecuteContext(ctx);
const root = (await parse(code)).rootNode;
const value = await handleNode(new Node(root), ctx, builtIns);
console.log("=====");
console.log(`value`, (_b = (_a = value === null || value === void 0 ? void 0 : value.__unwrap__) === null || _a === void 0 ? void 0 : _a.call(value)) !== null && _b !== void 0 ? _b : value);
console.log("context", ctx);
return value;
}
async function parse(code) {
if (!MEMOIZED.parser) {
const TreeSitter = (await import("../lib/tree-sitter.js"));
await TreeSitter.Parser.init();
const lang = await TreeSitter.Language.load("rgthree/lib/tree-sitter-python.wasm");
MEMOIZED.parser = new TreeSitter.Parser();
MEMOIZED.parser.setLanguage(lang);
}
return MEMOIZED.parser.parse(code);
}
async function handleNode(node, ctx, builtIns) {
const type = node.type;
if (ctx.hasOwnProperty("__returned__"))
return ctx["__returned__"];
const handler = TYPE_TO_HANDLER.get(type);
check(handler, "Unhandled type: " + type, node);
return handler(node, ctx, builtIns);
}
async function handleChildren(node, ctx, builtIns) {
let lastValue = null;
for (const child of node.children) {
if (!child)
continue;
lastValue = await handleNode(child, ctx, builtIns);
}
return lastValue;
}
async function handleSwallow(node, ctx, builtIns) {
}
async function handleReturn(node, ctx, builtIns) {
const value = node.children.length > 1 ? handleNode(node.child(1), ctx, builtIns) : undefined;
ctx["__returned__"] = value;
return value;
}
async function handleIdentifier(node, ctx, builtIns) {
var _a, _b;
let value = ctx[node.text];
if (value === undefined) {
value = (_b = (_a = builtIns[node.text]) === null || _a === void 0 ? void 0 : _a.fn) !== null && _b !== void 0 ? _b : undefined;
}
return maybeWrapValue(value);
}
async function handleAttribute(node, ctx, builtIns) {
const children = node.children;
check(children.length === 3, "Expected 3 children for attribute.");
check(children[1].type === ".", "Expected middle child to be '.' for attribute.");
const inst = await handleNode(children[0], ctx, builtIns);
const attr = children[2].text;
checkAttributeAccessibility(inst, attr);
let attribute = maybeWrapValue(inst[attr]);
return typeof attribute === "function" ? attribute.bind(inst) : attribute;
}
async function handleSubscript(node, ctx, builtIns) {
const children = node.children;
check(children.length === 4, "Expected 4 children for subscript.");
check(children[1].type === "[", "Expected 2nd child to be '[' for subscript.");
check(children[3].type === "]", "Expected 4thd child to be ']' for subscript.");
const inst = await handleNode(children[0], ctx, builtIns);
const attr = await handleNode(children[2], ctx, builtIns);
if (inst instanceof PyTuple && isInt(attr)) {
return maybeWrapValue(inst.__at__(attr));
}
if (inst instanceof PyDict && typeof attr === "string") {
return maybeWrapValue(inst.get(attr));
}
checkAttributeAccessibility(inst, attr);
let attribute = maybeWrapValue(inst[attr]);
return typeof attribute === "function" ? attribute.bind(inst) : attribute;
}
async function handleAssignment(node, ctx, builtIns) {
check(node.children.length === 3, "Expected 3 children for assignment: identifier/attr, =, and value.");
check(node.children[1].type === "=", "Expected middle child to be an '='.");
let right = await handleNode(node.children[2], ctx, builtIns);
const leftNode = node.children[0];
let leftObj = ctx;
let leftProp = "";
if (leftNode.type === "identifier") {
leftProp = leftNode.text;
}
else if (leftNode.type === "attribute") {
leftObj = await handleNode(leftNode.children[0], ctx, builtIns);
check(leftNode.children[2].type === "identifier", "Expected left hand assignment attribute to be an identifier.", leftNode);
leftProp = leftNode.children[2].text;
}
else if (leftNode.type === "subscript") {
leftObj = await handleNode(leftNode.children[0], ctx, builtIns);
check(leftNode.children[1].type === "[");
check(leftNode.children[3].type === "]");
leftProp = await handleNode(leftNode.children[2], ctx, builtIns);
}
else {
throw new Error(`Unhandled left-hand assignement type: ${leftNode.type}`);
}
if (leftProp == null) {
throw new Error(`No property to assign value`);
}
if (leftObj instanceof PyTuple) {
check(isInt(leftProp), "Expected an int for list assignment");
leftObj.__put__(leftProp, right);
}
else if (leftObj instanceof PyDict) {
check(typeof leftProp === "string", "Expected a string for dict assignment");
leftObj.__put__(leftProp, right);
}
else {
check(typeof leftProp === "string", "Expected a string for object assignment");
if (!(leftObj instanceof InitialExecuteContext)) {
checkAttributeAccessibility(leftObj, leftProp);
}
leftObj[leftProp] = right;
}
return right;
}
async function handleNamedExpression(node, ctx, builtIns) {
check(node.children.length === 3, "Expected three children for named expression.");
check(node.child(0).type === "identifier", "Expected identifier first in named expression.");
const varName = node.child(0).text;
ctx[varName] = await handleNode(node.child(2), ctx, builtIns);
return maybeWrapValue(ctx[varName]);
}
async function handleCall(node, ctx, builtIns) {
check(node.children.length === 2, "Expected 2 children for call, identifier and arguments.");
const fn = await handleNode(node.children[0], ctx, builtIns);
const args = await handleNode(node.children[1], ctx, builtIns);
console.log("handleCall", fn, args);
return fn(...args);
}
async function handleArgumentsList(node, ctx, builtIns) {
const args = (await handleList(node, ctx, builtIns)).__unwrap__(false);
return [...args];
}
async function handleForStatement(node, ctx, builtIns) {
const childs = node.children;
check(childs.length === 6);
check(childs[4].type === ":");
check(childs[5].type === "block");
await helperGetLoopForIn(node, ctx, builtIns, async (forCtx) => {
await handleNode(childs[5], forCtx, builtIns);
});
}
async function handleListComprehension(node, ctx, builtIns) {
const finalList = new PyList();
const newCtx = { ...ctx };
let finalEntryNode;
const loopNodes = [];
for (const child of node.children) {
if (!child || ["[", "]"].includes(child.type))
continue;
if (child.type === "identifier" || child.type === "attribute") {
if (finalEntryNode) {
throw Error("Already have a list comprehension finalEntryNode.");
}
finalEntryNode = child;
}
else if (child.type === "for_in_clause") {
loopNodes.push({ forIn: child });
}
else if (child.type === "if_clause") {
loopNodes[loopNodes.length - 1]["if"] = child;
}
}
if (!finalEntryNode) {
throw Error("No list comprehension finalEntryNode.");
}
console.log(`handleListComprehension.loopNodes`, loopNodes);
const handleLoop = async (loopNodes) => {
const loopNode = loopNodes.shift();
await helperGetLoopForIn(loopNode.forIn, newCtx, builtIns, async (forCtx) => {
if (loopNode.if) {
const ifNode = loopNode.if;
check(ifNode.children.length === 2, "Expected 2 children for if_clause.");
check(ifNode.child(0).text === "if", "Expected first child to be 'if'.");
const good = await handleNode(ifNode.child(1), forCtx, builtIns);
if (!good)
return;
}
Object.assign(newCtx, forCtx);
if (loopNodes.length) {
await handleLoop(loopNodes);
}
else {
finalList.append(await handleNode(finalEntryNode, newCtx, builtIns));
}
}, () => ({ ...newCtx }));
loopNodes.unshift(loopNode);
};
await handleLoop(loopNodes);
return finalList;
}
async function helperGetLoopForIn(node, ctx, builtIns, eachFn, provideForCtx) {
var _a;
const childs = node.children;
check(childs.length >= 3);
check(childs[0].type === "for");
check(["identifier", "pattern_list"].includes(childs[1].type), "Expected identifier for for loop.");
check(childs[2].type === "in");
let identifiers;
if (childs[1].type === "identifier") {
identifiers = [childs[1].text];
}
else {
identifiers = childs[1].children
.map((n) => {
if (n.type === ",")
return null;
check(n.type === "identifier");
return node.text;
})
.filter((n) => n != null);
}
const iterable = await handleNode(childs[3], ctx, builtIns);
check(iterable instanceof PyTuple, "Expected for loop instance to be a list/tuple.");
for (const item of iterable.__unwrap__(false)) {
const forCtx = (_a = provideForCtx === null || provideForCtx === void 0 ? void 0 : provideForCtx()) !== null && _a !== void 0 ? _a : ctx;
if (identifiers.length === 1) {
forCtx[identifiers[0]] = item;
}
else {
check(Array.isArray(item) && identifiers.length === item.length, "Expected iterable to be a list, like using dict.items()");
for (let i = 0; i < identifiers.length; i++) {
forCtx[identifiers[i]] = item[i];
}
}
await eachFn(forCtx);
}
}
async function handleNumber(node, ctx, builtIns) {
return Number(node.text);
}
async function handleString(node, ctx, builtIns) {
let str = "";
for (const child of node.children) {
if (!child || ["string_start", "string_end"].includes(child.type))
continue;
if (child.type === "string_content") {
str += child.text;
}
else if (child.type === "interpolation") {
check(child.children.length === 3, "Expected interpolation");
str += await handleNode(child, ctx, builtIns);
}
}
return str;
}
async function handleInterpolation(node, ...args) {
check(node.children.length === 3, "Expected interpolation to be three nodes length.");
check(node.children[0].type === "{" && node.children[2].type === "}", 'Expected interpolation to be wrapped in "{" and "}".');
return await handleNode(node.children[1], ...args);
}
async function handleList(node, ctx, builtIns) {
const list = [];
for (const child of node.children) {
if (!child || ["(", "[", ",", "]", ")"].includes(child.type))
continue;
list.push(await handleNode(child, ctx, builtIns));
}
if (node.type === "tuple") {
return new PyTuple(list);
}
return new PyList(list);
}
async function handleComparisonOperator(node, ctx, builtIns) {
const op = node.child(1).text;
const left = await handleNode(node.child(0), ctx, builtIns);
const right = await handleNode(node.child(2), ctx, builtIns);
if (op === "==")
return left === right;
if (op === "!=")
return left !== right;
if (op === ">")
return left > right;
if (op === ">=")
return left >= right;
if (op === "<")
return left < right;
if (op === "<=")
return left <= right;
if (op === "in")
return (right.__unwrap__ ? right.__unwrap__(false) : right).includes(left);
throw new Error(`Comparison not handled: "${op}"`);
}
async function handleBooleanOperator(node, ctx, builtIns) {
const op = node.child(1).text;
const left = await handleNode(node.child(0), ctx, builtIns);
if (!left && op === "and")
return left;
const right = await handleNode(node.child(2), ctx, builtIns);
if (op === "and")
return left && right;
if (op === "or")
return left || right;
}
async function handleBinaryOperator(node, ctx, builtIns) {
const op = node.child(1).text;
const left = await handleNode(node.child(0), ctx, builtIns);
const right = await handleNode(node.child(2), ctx, builtIns);
if (left.constructor !== right.constructor) {
throw new Error(`Can only run ${op} operator on same type.`);
}
if (op === "+")
return left.__add__ ? left.__add__(right) : left + right;
if (op === "-")
return left - right;
if (op === "/")
return left / right;
if (op === "//")
return Math.floor(left / right);
if (op === "*")
return left * right;
if (op === "%")
return left % right;
if (op === "&")
return left & right;
if (op === "|")
return left | right;
if (op === "^")
return left ^ right;
if (op === "<<")
return left << right;
if (op === ">>")
return left >> right;
throw new Error(`Comparison not handled: "${op}"`);
}
async function handleNotOperator(node, ctx, builtIns) {
check(node.children.length === 2, "Expected 2 children for not operator.");
check(node.child(0).text === "not", "Expected first child to be 'not'.");
const value = await handleNode(node.child(1), ctx, builtIns);
return !value;
}
async function handleUnaryOperator(node, ctx, builtIns) {
check(node.children.length === 2, "Expected 2 children for not operator.");
const value = await handleNode(node.child(1), ctx, builtIns);
const op = node.child(0).text;
if (op === "-")
return value * -1;
console.warn(`Unhandled unary operator: ${op}`);
return value;
}
async function handleDictionary(node, ctx, builtIns) {
const dict = new PyDict();
for (const child of node.children) {
if (!child || ["{", ",", "}"].includes(child.type))
continue;
check(child.type === "pair", "Expected a pair type for dict.");
const pair = await handleNode(child, ctx, builtIns);
dict.__put__(pair[0], pair[1]);
}
return dict;
}
async function handleDictionaryPair(node, ctx, builtIns) {
check(node.children.length === 3, "Expected 3 children for dict pair.");
let varName = await handleNode(node.child(0), ctx, builtIns);
let varValue = await handleNode(node.child(2), ctx, builtIns);
check(typeof varName === "string", "Expected varname to be string.");
return [varName, varValue];
}
class Node {
constructor(node) {
this.type = node.type;
this.text = node.text;
if (this.type === "ERROR") {
throw new Error(`Error found in parsing near "${this.text}"`);
}
this.children = [];
for (const child of node.children) {
this.children.push(new Node(child));
}
this.node = node;
}
child(index) {
const child = this.children[index];
if (!child)
throw Error(`No child at index ${index}.`);
return child;
}
log(tab = "", showNode = false) {
console.log(`${tab}--- Node`);
console.log(`${tab} type: ${this.type}`);
console.log(`${tab} text: ${this.text}`);
console.log(`${tab} children:`, this.children);
if (showNode) {
console.log(`${tab} node:`, this.node);
}
}
}
export class PyTuple {
constructor(...args) {
if (args.length === 1 && args[0] instanceof PyTuple) {
args = args[0].__unwrap__(false);
}
if (args.length === 1 && Array.isArray(args[0])) {
args = [...args[0]];
}
this.list = [...args];
}
count(v) {
}
index() {
}
__at__(index) {
index = this.__get_relative_index__(index);
return this.list[index];
}
__len__() {
return this.list.length;
}
__add__(v) {
if (!(v instanceof PyTuple)) {
throw new Error("Can only concatenate tuple to tuple.");
}
return new PyTuple(this.__unwrap__(false).concat(v.__unwrap__(false)));
}
__put__(index, v) {
throw new Error("Tuple does not support item assignment");
}
__get_relative_index__(index) {
if (index >= 0) {
check(this.list.length > index, `Index ${index} out of range.`);
return index;
}
const relIndex = this.list.length + index;
check(relIndex >= 0, `Index ${index} out of range.`);
return relIndex;
}
__unwrap__(deep = true) {
var _a;
const l = [...this.list];
if (deep) {
for (let i = 0; i < l.length; i++) {
l[i] = ((_a = l[i]) === null || _a === void 0 ? void 0 : _a.__unwrap__) ? l[i].__unwrap__(deep) : l[i];
}
}
return l;
}
}
__decorate([
Exposed
], PyTuple.prototype, "count", null);
__decorate([
Exposed
], PyTuple.prototype, "index", null);
export class PyList extends PyTuple {
append(...args) {
this.list.push(...args);
}
clear() {
this.list.length = 0;
}
copy() {
}
count() {
}
extend() {
}
index() {
}
insert() {
}
pop() {
}
remove() {
}
reverse() {
}
sort() {
}
__add__(v) {
if (!(v instanceof PyList)) {
throw new Error("Can only concatenate list to list.");
}
return new PyList(this.__unwrap__(false).concat(v.__unwrap__(false)));
}
__put__(index, v) {
index = this.__get_relative_index__(index);
this.list[index] = v;
}
}
__decorate([
Exposed
], PyList.prototype, "append", null);
__decorate([
Exposed
], PyList.prototype, "clear", null);
__decorate([
Exposed
], PyList.prototype, "copy", null);
__decorate([
Exposed
], PyList.prototype, "count", null);
__decorate([
Exposed
], PyList.prototype, "extend", null);
__decorate([
Exposed
], PyList.prototype, "index", null);
__decorate([
Exposed
], PyList.prototype, "insert", null);
__decorate([
Exposed
], PyList.prototype, "pop", null);
__decorate([
Exposed
], PyList.prototype, "remove", null);
__decorate([
Exposed
], PyList.prototype, "reverse", null);
__decorate([
Exposed
], PyList.prototype, "sort", null);
class PyInt {
}
class PyDict {
constructor(dict) {
_PyDict_dict.set(this, void 0);
__classPrivateFieldSet(this, _PyDict_dict, { ...(dict !== null && dict !== void 0 ? dict : {}) }, "f");
}
clear() { }
copy() { }
fromkeys() { }
get(key) {
return __classPrivateFieldGet(this, _PyDict_dict, "f")[key];
}
items() {
return new PyTuple(Object.entries(__classPrivateFieldGet(this, _PyDict_dict, "f")).map((e) => new PyTuple(e)));
}
keys() { }
pop() { }
popitem() { }
setdefault() { }
update() { }
values() { }
__put__(key, v) {
__classPrivateFieldGet(this, _PyDict_dict, "f")[key] = v;
}
__len__() {
return Object.keys(__classPrivateFieldGet(this, _PyDict_dict, "f")).length;
}
__unwrap__(deep = true) {
var _a;
const d = { ...__classPrivateFieldGet(this, _PyDict_dict, "f") };
if (deep) {
for (let k of Object.keys(d)) {
d[k] = ((_a = d[k]) === null || _a === void 0 ? void 0 : _a.__unwrap__) ? d[k].__unwrap__(deep) : d[k];
}
}
return d;
}
}
_PyDict_dict = new WeakMap();
__decorate([
Exposed
], PyDict.prototype, "clear", null);
__decorate([
Exposed
], PyDict.prototype, "copy", null);
__decorate([
Exposed
], PyDict.prototype, "fromkeys", null);
__decorate([
Exposed
], PyDict.prototype, "get", null);
__decorate([
Exposed
], PyDict.prototype, "items", null);
__decorate([
Exposed
], PyDict.prototype, "keys", null);
__decorate([
Exposed
], PyDict.prototype, "pop", null);
__decorate([
Exposed
], PyDict.prototype, "popitem", null);
__decorate([
Exposed
], PyDict.prototype, "setdefault", null);
__decorate([
Exposed
], PyDict.prototype, "update", null);
__decorate([
Exposed
], PyDict.prototype, "values", null);
function __unwrap__(...args) {
var _a;
for (let i = 0; i < args.length; i++) {
args[i] = ((_a = args[i]) === null || _a === void 0 ? void 0 : _a.__unwrap__) ? args[i].__unwrap__(true) : args[i];
}
return args;
}
function checkAttributeAccessibility(inst, attr) {
var _a, _b, _c, _d, _e, _f;
const instType = typeof inst;
check(instType === "object" || instType === "function", `Instance of type ${instType} does not have attributes.`);
check(!attr.startsWith("__") && !attr.endsWith("__"), `"${attr}" is not accessible.`);
const attrType = typeof inst[attr];
if (attrType === "function") {
const allowedMethods = (_c = (_b = (_a = inst.constructor) === null || _a === void 0 ? void 0 : _a.__ALLOWED_METHODS__) !== null && _b !== void 0 ? _b : inst.__ALLOWED_METHODS__) !== null && _c !== void 0 ? _c : [];
check(allowedMethods.includes(attr), `Method ${attr} is not accessible.`);
}
else {
const allowedProps = (_f = (_e = (_d = inst.constructor) === null || _d === void 0 ? void 0 : _d.__ALLOWED_PROPERTIES__) !== null && _e !== void 0 ? _e : inst.__ALLOWED_PROPERTIES__) !== null && _f !== void 0 ? _f : [];
check(allowedProps.includes(attr), `Property ${attr} is not accessible.`);
}
}
function maybeWrapValue(value) {
if (Array.isArray(value)) {
return new PyList(value);
}
return value;
}
function isInt(value) {
return typeof value === "number" && Math.round(value) === value;
}
function isIntLike(value) {
let is = isInt(value);
if (!is) {
is = typeof value === "string" && !!/^\d+$/.exec(value);
}
return is;
}
export function Exposed(target, key) {
const descriptor = Object.getOwnPropertyDescriptor(target, key);
if (typeof (descriptor === null || descriptor === void 0 ? void 0 : descriptor.value) === "function") {
target.constructor.__ALLOWED_METHODS__ = target.constructor.__ALLOWED_METHODS__ || [];
target.constructor.__ALLOWED_METHODS__.push(key);
}
else {
target.constructor.__ALLOWED_PROPERTIES__ = target.constructor.__ALLOWED_PROPERTIES__ || [];
target.constructor.__ALLOWED_PROPERTIES__.push(key);
}
}

View File

@@ -0,0 +1,131 @@
class RgthreeApi {
constructor(baseUrl) {
this.getCheckpointsPromise = null;
this.getSamplersPromise = null;
this.getSchedulersPromise = null;
this.getLorasPromise = null;
this.getWorkflowsPromise = null;
this.setBaseUrl(baseUrl);
}
setBaseUrl(baseUrlArg) {
var _a;
let baseUrl = null;
if (baseUrlArg) {
baseUrl = baseUrlArg;
}
else if (window.location.pathname.includes("/rgthree/")) {
const parts = (_a = window.location.pathname.split("/rgthree/")[1]) === null || _a === void 0 ? void 0 : _a.split("/");
if (parts && parts.length) {
baseUrl = parts.map(() => "../").join("") + "rgthree/api";
}
}
this.baseUrl = baseUrl || "./rgthree/api";
const comfyBasePathname = location.pathname.includes("/rgthree/")
? location.pathname.split("rgthree/")[0]
: location.pathname;
this.comfyBaseUrl = comfyBasePathname.split("/").slice(0, -1).join("/");
}
apiURL(route) {
return `${this.baseUrl}${route}`;
}
fetchApi(route, options) {
return fetch(this.apiURL(route), options);
}
async fetchJson(route, options) {
const r = await this.fetchApi(route, options);
return await r.json();
}
async postJson(route, json) {
const body = new FormData();
body.append("json", JSON.stringify(json));
return await rgthreeApi.fetchJson(route, { method: "POST", body });
}
getLoras(force = false) {
if (!this.getLorasPromise || force) {
this.getLorasPromise = this.fetchJson("/loras?format=details", { cache: "no-store" });
}
return this.getLorasPromise;
}
async fetchApiJsonOrNull(route, options) {
const response = await this.fetchJson(route, options);
if (response.status === 200 && response.data) {
return response.data || null;
}
return null;
}
async getModelsInfo(options) {
var _a;
const params = new URLSearchParams();
if ((_a = options.files) === null || _a === void 0 ? void 0 : _a.length) {
params.set("files", options.files.join(","));
}
if (options.light) {
params.set("light", "1");
}
if (options.format) {
params.set("format", options.format);
}
const path = `/${options.type}/info?` + params.toString();
return (await this.fetchApiJsonOrNull(path)) || [];
}
async getLorasInfo(options = {}) {
return this.getModelsInfo({ type: "loras", ...options });
}
async getCheckpointsInfo(options = {}) {
return this.getModelsInfo({ type: "checkpoints", ...options });
}
async refreshModelsInfo(options) {
var _a;
const params = new URLSearchParams();
if ((_a = options.files) === null || _a === void 0 ? void 0 : _a.length) {
params.set("files", options.files.join(","));
}
const path = `/${options.type}/info/refresh?` + params.toString();
const infos = await this.fetchApiJsonOrNull(path);
return infos;
}
async refreshLorasInfo(options = {}) {
return this.refreshModelsInfo({ type: "loras", ...options });
}
async refreshCheckpointsInfo(options = {}) {
return this.refreshModelsInfo({ type: "checkpoints", ...options });
}
async clearModelsInfo(options) {
var _a;
const params = new URLSearchParams();
if ((_a = options.files) === null || _a === void 0 ? void 0 : _a.length) {
params.set("files", options.files.join(","));
}
const path = `/${options.type}/info/clear?` + params.toString();
await this.fetchApiJsonOrNull(path);
return;
}
async clearLorasInfo(options = {}) {
return this.clearModelsInfo({ type: "loras", ...options });
}
async clearCheckpointsInfo(options = {}) {
return this.clearModelsInfo({ type: "checkpoints", ...options });
}
async saveModelInfo(type, file, data) {
const body = new FormData();
body.append("json", JSON.stringify(data));
return await this.fetchApiJsonOrNull(`/${type}/info?file=${encodeURIComponent(file)}`, { cache: "no-store", method: "POST", body });
}
async saveLoraInfo(file, data) {
return this.saveModelInfo("loras", file, data);
}
async saveCheckpointsInfo(file, data) {
return this.saveModelInfo("checkpoints", file, data);
}
fetchComfyApi(route, options) {
const url = this.comfyBaseUrl + "/api" + route;
options = options || {};
options.headers = options.headers || {};
options.cache = options.cache || "no-cache";
return fetch(url, options);
}
print(messageType) {
this.fetchApi(`/print?type=${messageType}`, {});
}
}
export const rgthreeApi = new RgthreeApi();

View File

@@ -0,0 +1,416 @@
export function getResolver(timeout = 5000) {
const resolver = {};
resolver.id = generateId(8);
resolver.completed = false;
resolver.resolved = false;
resolver.rejected = false;
resolver.promise = new Promise((resolve, reject) => {
resolver.reject = (e) => {
resolver.completed = true;
resolver.rejected = true;
reject(e);
};
resolver.resolve = (data) => {
resolver.completed = true;
resolver.resolved = true;
resolve(data);
};
});
resolver.timeout = setTimeout(() => {
if (!resolver.completed) {
resolver.reject();
}
}, timeout);
return resolver;
}
const DEBOUNCE_FN_TO_PROMISE = new WeakMap();
export function debounce(fn, ms = 64) {
if (!DEBOUNCE_FN_TO_PROMISE.get(fn)) {
DEBOUNCE_FN_TO_PROMISE.set(fn, wait(ms).then(() => {
DEBOUNCE_FN_TO_PROMISE.delete(fn);
fn();
}));
}
return DEBOUNCE_FN_TO_PROMISE.get(fn);
}
export function check(value, msg = "", ...args) {
if (!value) {
console.error(msg, ...(args || []));
throw new Error(msg || "Error");
}
}
export function wait(ms = 16) {
if (ms === 16) {
return new Promise((resolve) => {
requestAnimationFrame(() => {
resolve();
});
});
}
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
}
export function deepFreeze(obj) {
const propNames = Reflect.ownKeys(obj);
for (const name of propNames) {
const value = obj[name];
if ((value && typeof value === "object") || typeof value === "function") {
deepFreeze(value);
}
}
return Object.freeze(obj);
}
function dec2hex(dec) {
return dec.toString(16).padStart(2, "0");
}
export function generateId(length) {
const arr = new Uint8Array(length / 2);
crypto.getRandomValues(arr);
return Array.from(arr, dec2hex).join("");
}
export function getObjectValue(obj, objKey, def) {
if (!obj || !objKey)
return def;
const keys = objKey.split(".");
const key = keys.shift();
const found = obj[key];
if (keys.length) {
return getObjectValue(found, keys.join("."), def);
}
return found;
}
export function setObjectValue(obj, objKey, value, createMissingObjects = true) {
if (!obj || !objKey)
return obj;
const keys = objKey.split(".");
const key = keys.shift();
if (obj[key] === undefined) {
if (!createMissingObjects) {
return;
}
obj[key] = {};
}
if (!keys.length) {
obj[key] = value;
}
else {
if (typeof obj[key] != "object") {
obj[key] = {};
}
setObjectValue(obj[key], keys.join("."), value, createMissingObjects);
}
return obj;
}
export function moveArrayItem(arr, itemOrFrom, to) {
const from = typeof itemOrFrom === "number" ? itemOrFrom : arr.indexOf(itemOrFrom);
arr.splice(to, 0, arr.splice(from, 1)[0]);
}
export function removeArrayItem(arr, itemOrIndex) {
const index = typeof itemOrIndex === "number" ? itemOrIndex : arr.indexOf(itemOrIndex);
arr.splice(index, 1);
}
export function injectCss(href) {
if (document.querySelector(`link[href^="${href}"]`)) {
return Promise.resolve();
}
return new Promise((resolve) => {
const link = document.createElement("link");
link.setAttribute("rel", "stylesheet");
link.setAttribute("type", "text/css");
const timeout = setTimeout(resolve, 1000);
link.addEventListener("load", (e) => {
clearInterval(timeout);
resolve();
});
link.href = href;
document.head.appendChild(link);
});
}
export function defineProperty(instance, property, desc) {
var _a, _b, _c, _d, _e, _f;
const existingDesc = Object.getOwnPropertyDescriptor(instance, property);
if ((existingDesc === null || existingDesc === void 0 ? void 0 : existingDesc.configurable) === false) {
throw new Error(`Error: rgthree-comfy cannot define un-configurable property "${property}"`);
}
if ((existingDesc === null || existingDesc === void 0 ? void 0 : existingDesc.get) && desc.get) {
const descGet = desc.get;
desc.get = () => {
existingDesc.get.apply(instance, []);
return descGet.apply(instance, []);
};
}
if ((existingDesc === null || existingDesc === void 0 ? void 0 : existingDesc.set) && desc.set) {
const descSet = desc.set;
desc.set = (v) => {
existingDesc.set.apply(instance, [v]);
return descSet.apply(instance, [v]);
};
}
desc.enumerable = (_b = (_a = desc.enumerable) !== null && _a !== void 0 ? _a : existingDesc === null || existingDesc === void 0 ? void 0 : existingDesc.enumerable) !== null && _b !== void 0 ? _b : true;
desc.configurable = (_d = (_c = desc.configurable) !== null && _c !== void 0 ? _c : existingDesc === null || existingDesc === void 0 ? void 0 : existingDesc.configurable) !== null && _d !== void 0 ? _d : true;
if (!desc.get && !desc.set) {
desc.writable = (_f = (_e = desc.writable) !== null && _e !== void 0 ? _e : existingDesc === null || existingDesc === void 0 ? void 0 : existingDesc.writable) !== null && _f !== void 0 ? _f : true;
}
return Object.defineProperty(instance, property, desc);
}
export function areDataViewsEqual(a, b) {
if (a.byteLength !== b.byteLength) {
return false;
}
for (let i = 0; i < a.byteLength; i++) {
if (a.getUint8(i) !== b.getUint8(i)) {
return false;
}
}
return true;
}
function looksLikeBase64(source) {
return source.length > 500 || source.startsWith("data:") || source.includes(";base64,");
}
export function areArrayBuffersEqual(a, b) {
if (a == b || !a || !b) {
return a == b;
}
return areDataViewsEqual(new DataView(a), new DataView(b));
}
export function newCanvas(widthOrPtOrImage, height) {
let width;
if (typeof widthOrPtOrImage !== "number") {
width = widthOrPtOrImage.width;
height = widthOrPtOrImage.height;
}
else {
width = widthOrPtOrImage;
height = height;
}
if (height == null) {
throw new Error("Invalid height supplied when creating new canvas object.");
}
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
if (widthOrPtOrImage instanceof HTMLImageElement) {
const ctx = canvas.getContext("2d");
ctx.drawImage(widthOrPtOrImage, 0, 0, width, height);
}
return canvas;
}
export function getCanvasImageData(image) {
const canvas = newCanvas(image);
const ctx = canvas.getContext("2d");
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
return [canvas, ctx, imageData];
}
export async function convertToBase64(source) {
if (source instanceof Promise) {
source = await source;
}
if (typeof source === "string" && looksLikeBase64(source)) {
return source;
}
if (typeof source === "string" || source instanceof Blob || source instanceof ArrayBuffer) {
return convertToBase64(await loadImage(source));
}
if (source instanceof HTMLImageElement) {
if (looksLikeBase64(source.src)) {
return source.src;
}
const [canvas, ctx, imageData] = getCanvasImageData(source);
return convertToBase64(canvas);
}
if (source instanceof HTMLCanvasElement) {
return source.toDataURL("image/png");
}
throw Error("Unknown source to convert to base64.");
}
export async function convertToArrayBuffer(source) {
if (source instanceof Promise) {
source = await source;
}
if (source instanceof ArrayBuffer) {
return source;
}
if (typeof source === "string") {
if (looksLikeBase64(source)) {
var binaryString = atob(source.replace(/^.*?;base64,/, ""));
var bytes = new Uint8Array(binaryString.length);
for (var i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
return convertToArrayBuffer(await loadImage(source));
}
if (source instanceof HTMLImageElement) {
const [canvas, ctx, imageData] = getCanvasImageData(source);
return convertToArrayBuffer(canvas);
}
if (source instanceof HTMLCanvasElement) {
return convertToArrayBuffer(source.toDataURL());
}
if (source instanceof Blob) {
return source.arrayBuffer();
}
throw Error("Unknown source to convert to arraybuffer.");
}
export async function loadImage(source) {
if (source instanceof Promise) {
source = await source;
}
if (source instanceof HTMLImageElement) {
return loadImage(source.src);
}
if (source instanceof Blob) {
return loadImage(source.arrayBuffer());
}
if (source instanceof HTMLCanvasElement) {
return loadImage(source.toDataURL());
}
if (source instanceof ArrayBuffer) {
var binary = "";
var bytes = new Uint8Array(source);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return loadImage(`data:${getMimeTypeFromArrayBuffer(bytes)};base64,${btoa(binary)}`);
}
return new Promise((resolve, reject) => {
const img = new Image();
img.addEventListener("load", () => {
resolve(img);
});
img.addEventListener("error", () => {
reject(img);
});
img.src = source;
});
}
function getMimeTypeFromArrayBuffer(buffer) {
const len = 4;
if (buffer.length >= len) {
let signatureArr = new Array(len);
for (let i = 0; i < len; i++)
signatureArr[i] = buffer[i].toString(16);
const signature = signatureArr.join("").toUpperCase();
switch (signature) {
case "89504E47":
return "image/png";
case "47494638":
return "image/gif";
case "25504446":
return "application/pdf";
case "FFD8FFDB":
case "FFD8FFE0":
return "image/jpeg";
case "504B0304":
return "application/zip";
default:
return null;
}
}
return null;
}
export class Broadcaster extends EventTarget {
constructor(channelName) {
super();
this.queue = {};
this.queue = {};
this.channel = new BroadcastChannel(channelName);
this.channel.addEventListener("message", (e) => {
this.onMessage(e);
});
}
getId() {
let id;
do {
id = generateId(6);
} while (this.queue[id]);
return id;
}
async broadcastAndWait(action, payload, options) {
const id = this.getId();
this.queue[id] = getResolver(options === null || options === void 0 ? void 0 : options.timeout);
this.channel.postMessage({
id,
action,
payload,
});
let response;
try {
response = await this.queue[id].promise;
}
catch (e) {
console.log("CAUGHT", e);
response = [];
}
return response;
}
broadcast(action, payload) {
this.channel.postMessage({
id: this.getId(),
action,
payload,
});
}
reply(replyId, action, payload) {
this.channel.postMessage({
id: this.getId(),
replyId,
action,
payload,
});
}
openWindowAndWaitForMessage(rgthreePath, windowName) {
const id = this.getId();
this.queue[id] = getResolver();
const win = window.open(`/rgthree/${rgthreePath}#broadcastLoadMsgId=${id}`, windowName);
return { window: win, promise: this.queue[id].promise };
}
onMessage(e) {
var _a, _b;
const msgId = ((_a = e.data) === null || _a === void 0 ? void 0 : _a.replyId) || "";
const queueItem = this.queue[msgId];
if (queueItem) {
if (queueItem.completed) {
console.error(`${msgId} already completed..`);
}
queueItem.deferment = queueItem.deferment || { data: [] };
queueItem.deferment.data.push(e.data.payload);
queueItem.deferment.timeout && clearTimeout(queueItem.deferment.timeout);
queueItem.deferment.timeout = setTimeout(() => {
queueItem.resolve(queueItem.deferment.data);
}, 250);
}
else {
this.dispatchEvent(new CustomEvent("rgthree-broadcast-message", {
detail: Object.assign({ replyTo: (_b = e.data) === null || _b === void 0 ? void 0 : _b.id }, e.data),
}));
}
}
addMessageListener(callback, options) {
return super.addEventListener("rgthree-broadcast-message", callback, options);
}
}
const broadcastChannelMap = new Map();
export function broadcastOnChannel(channel, action, payload) {
let queue = broadcastChannelMap.get(channel);
if (!queue) {
broadcastChannelMap.set(channel, {});
queue = broadcastChannelMap.get(channel);
}
let id;
do {
id = generateId(6);
} while (queue[id]);
queue[id] = getResolver();
channel.postMessage({
id,
action,
payload,
});
return queue[id].promise;
}

View File

@@ -0,0 +1,363 @@
const DIRECT_ATTRIBUTE_MAP = {
cellpadding: "cellPadding",
cellspacing: "cellSpacing",
colspan: "colSpan",
frameborder: "frameBorder",
height: "height",
maxlength: "maxLength",
nonce: "nonce",
role: "role",
rowspan: "rowSpan",
type: "type",
usemap: "useMap",
valign: "vAlign",
width: "width",
};
const RGX_NUMERIC_STYLE_UNIT = "px";
const RGX_NUMERIC_STYLE = /^((max|min)?(width|height)|margin|padding|(margin|padding)?(left|top|bottom|right)|fontsize|borderwidth)$/i;
const RGX_DEFAULT_VALUE_PROP = /input|textarea|select/i;
function localAssertNotFalsy(input, errorMsg = `Input is not of type.`) {
if (input == null) {
throw new Error(errorMsg);
}
return input;
}
const RGX_STRING_VALID = "[a-z0-9_-]";
const RGX_TAG = new RegExp(`^([a-z]${RGX_STRING_VALID}*)(\\.|\\[|\\#|$)`, "i");
const RGX_ATTR_ID = new RegExp(`#(${RGX_STRING_VALID}+)`, "gi");
const RGX_ATTR_CLASS = new RegExp(`(^|\\S)\\.([a-z0-9_\\-\\.]+)`, "gi");
const RGX_STRING_CONTENT_TO_SQUARES = "(.*?)(\\[|\\])";
const RGX_ATTRS_MAYBE_OPEN = new RegExp(`\\[${RGX_STRING_CONTENT_TO_SQUARES}`, "gi");
const RGX_ATTRS_FOLLOW_OPEN = new RegExp(`^${RGX_STRING_CONTENT_TO_SQUARES}`, "gi");
export function queryAll(selectors, parent = document) {
return Array.from(parent.querySelectorAll(selectors)).filter((n) => !!n);
}
export function query(selectors, parent = document) {
var _a;
return (_a = parent.querySelector(selectors)) !== null && _a !== void 0 ? _a : null;
}
export function createText(text) {
return document.createTextNode(text);
}
export function getClosestOrSelf(element, query) {
const el = element;
return ((el === null || el === void 0 ? void 0 : el.closest) && ((el.matches(query) && el) || el.closest(query))) || null;
}
export function containsOrSelf(parent, contained) {
var _a;
return (parent === contained || ((_a = parent === null || parent === void 0 ? void 0 : parent.contains) === null || _a === void 0 ? void 0 : _a.call(parent, contained)) || false);
}
export function createElement(selectorOrMarkup, attrs) {
const frag = getHtmlFragment(selectorOrMarkup);
let element = frag === null || frag === void 0 ? void 0 : frag.firstElementChild;
let selector = "";
if (!element) {
selector = selectorOrMarkup.replace(/[\r\n]\s*/g, "");
const tag = getSelectorTag(selector) || "div";
element = document.createElement(tag);
selector = selector.replace(RGX_TAG, "$2");
const brackets = selector.match(/(\[[^\]]+\])/g) || [];
for (const bracket of brackets) {
selector = selector.replace(bracket, "");
}
selector = selector.replace(RGX_ATTR_ID, '[id="$1"]');
selector = selector.replace(RGX_ATTR_CLASS, (match, p1, p2) => `${p1}[class="${p2.replace(/\./g, " ")}"]`);
selector += brackets.join("");
}
const selectorAttrs = getSelectorAttributes(selector);
if (selectorAttrs) {
for (const attr of selectorAttrs) {
let matches = attr.substring(1, attr.length - 1).split("=");
let key = localAssertNotFalsy(matches.shift());
let value = matches.join("=");
if (value === undefined) {
setAttribute(element, key, true);
}
else {
value = value.replace(/^['"](.*)['"]$/, "$1");
setAttribute(element, key, value);
}
}
}
if (attrs) {
setAttributes(element, attrs);
}
return element;
}
export const $el = createElement;
function getSelectorTag(str) {
return tryMatch(str, RGX_TAG);
}
function getSelectorAttributes(selector) {
RGX_ATTRS_MAYBE_OPEN.lastIndex = 0;
let attrs = [];
let result;
while ((result = RGX_ATTRS_MAYBE_OPEN.exec(selector))) {
let attr = result[0];
if (attr.endsWith("]")) {
attrs.push(attr);
}
else {
attr =
result[0] + getOpenAttributesRecursive(selector.substr(RGX_ATTRS_MAYBE_OPEN.lastIndex), 2);
RGX_ATTRS_MAYBE_OPEN.lastIndex += attr.length - result[0].length;
attrs.push(attr);
}
}
return attrs;
}
function getOpenAttributesRecursive(selectorSubstring, openCount) {
let matches = selectorSubstring.match(RGX_ATTRS_FOLLOW_OPEN);
let result = "";
if (matches && matches.length) {
result = matches[0];
openCount += result.endsWith("]") ? -1 : 1;
if (openCount > 0) {
result += getOpenAttributesRecursive(selectorSubstring.substr(result.length), openCount);
}
}
return result;
}
function tryMatch(str, rgx, index = 1) {
var _a;
let found = "";
try {
found = ((_a = str.match(rgx)) === null || _a === void 0 ? void 0 : _a[index]) || "";
}
catch (e) {
found = "";
}
return found;
}
export function setAttributes(element, data) {
let attr;
for (attr in data) {
if (data.hasOwnProperty(attr)) {
setAttribute(element, attr, data[attr]);
}
}
}
function getHtmlFragment(value) {
if (value.match(/^\s*<.*?>[\s\S]*<\/[a-z0-9]+>\s*$/)) {
return document.createRange().createContextualFragment(value.trim());
}
return null;
}
function getChild(value) {
if (value instanceof Node) {
return value;
}
if (typeof value === "string") {
let child = getHtmlFragment(value);
if (child) {
return child;
}
if (getSelectorTag(value)) {
return createElement(value);
}
return createText(value);
}
if (value && typeof value.toElement === "function") {
return value.toElement();
}
return null;
}
export function setAttribute(element, attribute, value) {
let isRemoving = value == null;
if (attribute === "default") {
attribute = RGX_DEFAULT_VALUE_PROP.test(element.nodeName) ? "value" : "text";
}
if (attribute === "text") {
empty(element).appendChild(createText(value != null ? String(value) : ""));
}
else if (attribute === "html") {
empty(element).innerHTML += value != null ? String(value) : "";
}
else if (attribute == "style") {
if (typeof value === "string") {
element.style.cssText = isRemoving ? "" : value != null ? String(value) : "";
}
else {
for (const [styleKey, styleValue] of Object.entries(value)) {
element.style[styleKey] = styleValue;
}
}
}
else if (attribute == "events") {
for (const [key, fn] of Object.entries(value)) {
addEvent(element, key, fn);
}
}
else if (attribute === "parent") {
value.appendChild(element);
}
else if (attribute === "child" || attribute === "children") {
if (typeof value === "string" && /^\[[^\[\]]+\]$/.test(value)) {
const parseable = value.replace(/^\[([^\[\]]+)\]$/, '["$1"]').replace(/,/g, '","');
try {
const parsed = JSON.parse(parseable);
value = parsed;
}
catch (e) {
console.error(e);
}
}
if (attribute === "children") {
empty(element);
}
let children = value instanceof Array ? value : [value];
for (let child of children) {
child = getChild(child);
if (child instanceof Node) {
if (element instanceof HTMLTemplateElement) {
element.content.appendChild(child);
}
else {
element.appendChild(child);
}
}
}
}
else if (attribute == "for") {
element.htmlFor = value != null ? String(value) : "";
if (isRemoving) {
element.removeAttribute("for");
}
}
else if (attribute === "class" || attribute === "className" || attribute === "classes") {
element.className = isRemoving ? "" : Array.isArray(value) ? value.join(" ") : String(value);
}
else if (attribute === "dataset") {
if (typeof value !== "object") {
console.error("Expecting an object for dataset");
return;
}
for (const [key, val] of Object.entries(value)) {
element.dataset[key] = String(val);
}
}
else if (attribute.startsWith("on") && typeof value === "function") {
element.addEventListener(attribute.substring(2), value);
}
else if (["checked", "disabled", "readonly", "required", "selected"].includes(attribute)) {
element[attribute] = !!value;
if (!value) {
element.removeAttribute(attribute);
}
else {
element.setAttribute(attribute, attribute);
}
}
else if (DIRECT_ATTRIBUTE_MAP.hasOwnProperty(attribute)) {
if (isRemoving) {
element.removeAttribute(DIRECT_ATTRIBUTE_MAP[attribute]);
}
else {
element.setAttribute(DIRECT_ATTRIBUTE_MAP[attribute], String(value));
}
}
else if (isRemoving) {
element.removeAttribute(attribute);
}
else {
let oldVal = element.getAttribute(attribute);
if (oldVal !== value) {
element.setAttribute(attribute, String(value));
}
}
}
function addEvent(element, key, fn) {
element.addEventListener(key, fn);
}
function setStyles(element, styles = null) {
if (styles) {
for (let name in styles) {
setStyle(element, name, styles[name]);
}
}
return element;
}
export function setStyle(element, name, value) {
name = name.indexOf("float") > -1 ? "cssFloat" : name;
if (name.indexOf("-") != -1) {
name = name.replace(/-\D/g, (match) => {
return match.charAt(1).toUpperCase();
});
}
if (value == String(Number(value)) && RGX_NUMERIC_STYLE.test(name)) {
value = value + RGX_NUMERIC_STYLE_UNIT;
}
if (name === "display" && typeof value !== "string") {
value = !!value ? null : "none";
}
element.style[name] = value === null ? null : String(value);
return element;
}
export function empty(element) {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
return element;
}
export function remove(element) {
while (element.parentElement) {
element.parentElement.removeChild(element);
}
return element;
}
export function replaceChild(oldChildNode, newNode) {
oldChildNode.parentNode.replaceChild(newNode, oldChildNode);
return newNode;
}
export function appendChildren(el, children) {
children = !Array.isArray(children) ? [children] : children;
for (let child of children) {
child = getChild(child);
if (child instanceof Node) {
if (el instanceof HTMLTemplateElement) {
el.content.appendChild(child);
}
else {
el.appendChild(child);
}
}
}
}
export function getActionEls(parent = document) {
const els = Array.from(parent.querySelectorAll("[data-action],[on-action],[on]"));
if (parent instanceof Element) {
els.unshift(parent);
}
return els
.map((actionEl) => {
const actions = {};
const actionSegments = (actionEl.getAttribute("data-action") ||
actionEl.getAttribute("on-action") ||
actionEl.getAttribute("on") ||
"").split(";");
for (let segment of actionSegments) {
let actionsData = segment
.trim()
.split(/\s*:\s*/g)
.filter((i) => !!i.trim());
if (!actionsData.length)
continue;
if (actionsData.length === 1) {
if (actionEl instanceof HTMLInputElement) {
actionsData.unshift("input");
}
else {
actionsData.unshift("click");
}
}
if (actionsData[0] && actionsData[1]) {
actions[actionsData[0]] = actionsData[1];
}
}
return {
el: actionEl,
actions,
};
})
.filter((el) => !!el);
}

View File

@@ -0,0 +1,592 @@
import * as dom from "./utils_dom.js";
import { getObjectValue } from "./shared_utils.js";
const CONFIG_DEFAULT = {
attrBind: "data-bind",
attrIf: "data-if",
attrIfIs: "data-if-is",
};
const CONFIG = Object.assign({}, CONFIG_DEFAULT, {
attrBind: "bind",
attrIf: "if",
attrIfIs: "if-is",
});
const RGX_COMPARISON = (() => {
let value = "((?:\\!*)[_a-z0-9\\.\\-\\[\\]'\"]+)";
let comparison = "((?:<|>|==|\\!=)=?)";
return new RegExp(`^(?:\\!*)\\(?${value}\\s*${comparison}\\s*${value}\\)?$`, "i");
})();
const RGXPART_BIND_FN_TEMPLATE_STRING = "template|tpl";
const RGXPART_BIND_FN_ELEMENT_STRING = "element|el";
const RGX_BIND_FN_TEMPLATE = new RegExp(`^(?:${RGXPART_BIND_FN_TEMPLATE_STRING})\\(([^\\)]+)\\)`, "i");
const RGX_BIND_FN_ELEMENT = new RegExp(`^(?:${RGXPART_BIND_FN_ELEMENT_STRING})\\(([^\\)]+)\\)`, "i");
const RGX_BIND_FN_TEMPLATE_OR_ELEMENT = new RegExp(`^(?:${RGXPART_BIND_FN_TEMPLATE_STRING}|${RGXPART_BIND_FN_ELEMENT_STRING})\\(([^\\)]+)\\)`, "i");
const RGX_BIND_FN_LENGTH = /^(?:length|len|size)\(([^\)]+)\)/i;
const RGX_BIND_FN_FORMAT = /^(?:format|fmt)\(([^\,]+),([^\)]+)\)/i;
const RGX_BIND_FN_CALL = /^([^\(]+)\(([^\)]*)\)/i;
const EMPTY_PREPROCESS_FN = (data) => data;
const RGX_BIND_DECLARATIONS = /\s*(\!*(?:[\$_a-z0-9-\.\'\"]|\?\?|\|\||\&\&|(?:(?:<|>|==|\!=)=?))+(?:\`[^\`]+\`)?(?:\([^\)]*\))?)(?::(.*?))?(\s|$)/gi;
function localAssertNotFalsy(input, errorMsg = `Input is not of type.`) {
if (input == null) {
throw new Error(errorMsg);
}
return input;
}
function cleanKey(key) {
return key.toLowerCase().trim().replace(/\s/g, "");
}
function toArray(value) {
if (Array.isArray(value)) {
return value;
}
if (value instanceof Set) {
return Array.from(value);
}
if (typeof value === "object" && typeof value.length === "number") {
return [].slice.call(value);
}
return [value];
}
function flattenArray(arr) {
return toArray(arr).reduce((acc, val) => {
return acc.concat(Array.isArray(val) ? flattenArray(val) : val);
}, []);
}
function getObjValue(lookup, obj) {
let booleanMatch = lookup.match(/^(\!+)(.+?)$/i) || [];
let booleanNots = [];
if (booleanMatch[1] && booleanMatch[2]) {
booleanNots = booleanMatch[1].split("");
lookup = booleanMatch[2];
}
let value = getObjectValue(obj, lookup);
while (booleanNots.length) {
value = !value;
booleanNots.shift();
}
return value;
}
function getPrimitiveOrObjValue(stringValue, data) {
let value;
if (stringValue == null) {
return stringValue;
}
let negate = getNegates(stringValue);
if (negate != null) {
stringValue = stringValue.replace(/^\!+/, "");
}
try {
const cleanedStringValue = stringValue.replace(/^'(.*)'$/, '"$1"');
value = JSON.parse(cleanedStringValue);
}
catch (e) {
value = getObjValue(stringValue, data);
}
value = negate !== null ? (negate === 1 ? !value : !!value) : value;
return value;
}
function getNegates(stringValue) {
let negate = null;
let negateMatches = stringValue.match(/^(\!+)(.*)/);
if (negateMatches && negateMatches.length >= 3) {
negate = negateMatches[1].length % 2;
}
return negate;
}
function getStringComparisonExpression(bindingPropName, data) {
let comparisonMatches = bindingPropName.match(RGX_COMPARISON);
if (!(comparisonMatches === null || comparisonMatches === void 0 ? void 0 : comparisonMatches.length)) {
return null;
}
let a = getPrimitiveOrObjValue(comparisonMatches[1], data);
let b = getPrimitiveOrObjValue(comparisonMatches[3], data);
let c = comparisonMatches[2];
let value = (() => {
switch (c) {
case "===":
return a === b;
case "==":
return a == b;
case "<=":
return a <= b;
case ">=":
return a >= b;
case "!==":
return a !== b;
case "!=":
return a != b;
case "<":
return a < b;
case ">":
return a > b;
default:
return undefined;
}
})();
return value;
}
function replaceTplElementWithChildren(tplEl, fragOrElOrEls) {
const els = Array.isArray(fragOrElOrEls) ? fragOrElOrEls : [fragOrElOrEls];
tplEl.replaceWith(...els);
const numOfChildren = Array.isArray(fragOrElOrEls)
? fragOrElOrEls.length
: fragOrElOrEls.childElementCount;
if (numOfChildren === 1) {
const firstChild = Array.isArray(fragOrElOrEls)
? fragOrElOrEls[0]
: fragOrElOrEls.firstElementChild;
if (firstChild instanceof Element) {
if (tplEl.className.length) {
firstChild.className += ` ${tplEl.className}`;
}
let attr = tplEl.getAttribute("data");
if (attr) {
firstChild.setAttribute("data", attr);
}
attr = tplEl.getAttribute(CONFIG.attrBind);
if (attr) {
firstChild.setAttribute(CONFIG.attrBind, attr);
}
}
}
}
function getValueForBinding(bindingPropName, context) {
console.log("getValueForBinding", bindingPropName, context);
const data = context.data;
let stringTemplate = null;
let stringTemplates = /^(.*?)\`([^\`]*)\`$/.exec(bindingPropName.trim());
if ((stringTemplates === null || stringTemplates === void 0 ? void 0 : stringTemplates.length) === 3) {
bindingPropName = stringTemplates[1];
stringTemplate = stringTemplates[2];
}
let value = null;
let hadALogicalOp = false;
const opsToValidation = new Map([
[/\s*\?\?\s*/, (v) => v != null],
[/\s*\|\|\s*/, (v) => !!v],
[/\s*\&\&\s*/, (v) => !v],
]);
for (const [op, fn] of opsToValidation.entries()) {
if (bindingPropName.match(op)) {
hadALogicalOp = true;
const bindingPropNames = bindingPropName.split(op);
for (const propName of bindingPropNames) {
value = getValueForBindingPropName(propName, context);
if (fn(value)) {
break;
}
}
break;
}
}
if (!hadALogicalOp) {
value = getValueForBindingPropName(bindingPropName, context);
}
return stringTemplate && value != null
? stringTemplate.replace(/\$\{value\}/g, String(value))
: value;
}
function getValueForBindingPropName(bindingPropName, context) {
var _a, _b, _c;
const data = context.data;
let negate = getNegates(bindingPropName);
if (negate != null) {
bindingPropName = bindingPropName.replace(/^\!+/, "");
}
let value;
RGX_COMPARISON.lastIndex = 0;
if (RGX_COMPARISON.test(bindingPropName)) {
value = getStringComparisonExpression(bindingPropName, data);
}
else if (RGX_BIND_FN_LENGTH.test(bindingPropName)) {
bindingPropName = RGX_BIND_FN_LENGTH.exec(bindingPropName)[1];
value = getPrimitiveOrObjValue(bindingPropName, data);
value = (value && value.length) || 0;
}
else if (RGX_BIND_FN_FORMAT.test(bindingPropName)) {
let matches = RGX_BIND_FN_FORMAT.exec(bindingPropName);
bindingPropName = matches[1];
value = getPrimitiveOrObjValue(bindingPropName, data);
value = matches[2].replace(/^['"]/, "").replace(/['"]$/, "").replace(/\$1/g, value);
}
else if (RGX_BIND_FN_CALL.test(bindingPropName)) {
console.log("-----");
console.log(bindingPropName);
let matches = RGX_BIND_FN_CALL.exec(bindingPropName);
const functionName = matches[1];
const maybeDataName = (_a = matches[2]) !== null && _a !== void 0 ? _a : null;
value = getPrimitiveOrObjValue(maybeDataName, data);
console.log(functionName, maybeDataName, value);
if (typeof (value === null || value === void 0 ? void 0 : value[functionName]) === "function") {
value = value[functionName](value, data, context.currentElement, context.contextElement);
}
else if (typeof (data === null || data === void 0 ? void 0 : data[functionName]) === "function") {
value = data[functionName](value, data, context.currentElement, context.contextElement);
}
else if (typeof ((_b = context.currentElement) === null || _b === void 0 ? void 0 : _b[functionName]) === "function") {
value = context.currentElement[functionName](value, data, context.currentElement, context.contextElement);
}
else if (typeof ((_c = context.contextElement) === null || _c === void 0 ? void 0 : _c[functionName]) === "function") {
value = context.contextElement[functionName](value, data, context.currentElement, context.contextElement);
}
else {
console.error(`No method named ${functionName} on data or element instance. Just calling regular value.`);
value = getPrimitiveOrObjValue(bindingPropName, data);
}
}
else {
value = getPrimitiveOrObjValue(bindingPropName, data);
}
if (value !== undefined) {
value = negate !== null ? (negate === 1 ? !value : !!value) : value;
}
return value;
}
function removeBindingAttributes(elOrEls, deep = false) {
flattenArray(elOrEls || []).forEach((el) => {
el.removeAttribute(CONFIG.attrBind);
const innerBinds = dom.queryAll(`:scope [${CONFIG.attrBind}]`, el);
const innerTplBinds = deep ? [] : dom.queryAll(`:scope [data-tpl] [${CONFIG.attrBind}]`);
innerBinds.forEach((el) => {
if (deep || !innerTplBinds.includes(el)) {
el.removeAttribute(CONFIG.attrBind);
}
});
});
}
const templateCache = {};
export function checkKey(key) {
return !!templateCache[cleanKey(key)];
}
export function register(key, htmlOrElement = null, preProcessScript) {
key = cleanKey(key);
if (templateCache[key]) {
return templateCache[key];
}
let fragment = null;
if (typeof htmlOrElement === "string") {
const frag = document.createDocumentFragment();
if (htmlOrElement.includes("<")) {
const html = htmlOrElement.trim();
const htmlParentTag = (html.startsWith("<tr") && "tbody") ||
(/^<t(body|head|foot)/i.test(html) && "table") ||
(/^<t(d|h)/i.test(html) && "tr") ||
"div";
const temp = document.createElement(htmlParentTag);
temp.innerHTML = html;
for (const child of temp.children) {
frag.appendChild(child);
}
}
else {
frag.appendChild(dom.createElement(htmlOrElement));
}
fragment = frag;
}
else if (htmlOrElement instanceof Element) {
const element = htmlOrElement;
const tag = element.nodeName.toLowerCase();
if (tag === "template" && element.content) {
fragment = element.content;
}
else {
throw Error("Non-template element not handled");
}
}
else if (!htmlOrElement) {
let element = dom.query(`template[id="${key}"],template[data-id="${key}"]`);
if (element && element.content) {
fragment = element.content;
}
else {
throw Error("Non-template element not handled");
}
}
if (fragment) {
templateCache[key] = {
fragment,
preProcessScript: preProcessScript || EMPTY_PREPROCESS_FN,
};
}
return templateCache[key] || null;
}
export function getPreProcessScript(keyOrEl) {
var _a;
if (typeof keyOrEl === "string") {
if (!templateCache[keyOrEl]) {
throw Error(`Template key does not exist ${keyOrEl}`);
}
return templateCache[keyOrEl].preProcessScript;
}
if (keyOrEl instanceof Element) {
const tpl = keyOrEl.getAttribute("data-tpl") || "";
return ((_a = templateCache[tpl]) === null || _a === void 0 ? void 0 : _a.preProcessScript) || EMPTY_PREPROCESS_FN;
}
return EMPTY_PREPROCESS_FN;
}
export function getTemplateFragment(key) {
key = cleanKey(key);
if (!checkKey(key)) {
register(key);
}
let templateData = templateCache[key];
if (templateData && templateData.fragment) {
let imported;
if (document.importNode) {
imported = document.importNode(templateData.fragment, true);
}
else {
imported = templateData.fragment.cloneNode(true);
}
imported.__templateid__ = key;
return imported;
}
else {
throw new Error("Ain't no template called " + key + " (" + typeof templateCache[key] + ")");
}
}
export function inflate(nodeOrKey, templateData = null, inflateOptions = {}) {
let node = nodeOrKey;
if (typeof node === "string") {
node = getTemplateFragment(node);
}
if (node) {
const els = dom.queryAll("[data-template], [data-templateid], [template]", node);
for (const child of els) {
let className = child.className || null;
let childTemplateId = localAssertNotFalsy(child.getAttribute("data-template") ||
child.getAttribute("data-templateid") ||
child.getAttribute("template"), "No child template id provided.");
const dataAttribute = child.getAttribute("data") || "";
const childData = (dataAttribute && getObjValue(dataAttribute, templateData)) || templateData;
const tplsInflateOptions = Object.assign({}, inflateOptions);
if (tplsInflateOptions.skipInit != null) {
tplsInflateOptions.skipInit = true;
}
let tpls = localAssertNotFalsy(inflate(childTemplateId, childData, tplsInflateOptions), `No template inflated from ${childTemplateId}.`);
tpls = !Array.isArray(tpls) ? [tpls] : tpls;
if (className) {
for (const tpl of tpls) {
tpl.classList.add(className);
}
}
if (child.nodeName.toUpperCase() === "TPL") {
replaceTplElementWithChildren(child, tpls);
}
else {
child.append(...tpls);
}
child.remove();
}
let children = [];
for (const child of node.children) {
let tplAttributes = (child.getAttribute("data-tpl") || "").split(" ");
if (!tplAttributes.includes(node.__templateid__)) {
tplAttributes.push(node.__templateid__);
}
child.setAttribute("data-tpl", tplAttributes.join(" ").trim());
children.push(child);
}
let childOrChildren = children.length === 1 ? children[0] : children;
if (!inflateOptions.skipInit) {
init(childOrChildren, templateData, inflateOptions.bindOptions);
}
return childOrChildren;
}
return null;
}
export function inflateSingle(nodeOrKey, scriptData = null, bindOptions = {}) {
const inflated = localAssertNotFalsy(inflate(nodeOrKey, scriptData, bindOptions));
return Array.isArray(inflated) ? inflated[0] : inflated;
}
export function inflateOnce(nodeOrKey, templateData = null, inflateOptions = {}) {
let children = inflate(nodeOrKey, templateData, inflateOptions);
children && removeBindingAttributes(children, false);
return children;
}
export function inflateSingleOnce(nodeOrKey, scriptData = null, bindOptions = {}) {
const inflated = inflate(nodeOrKey, scriptData, bindOptions) || [];
removeBindingAttributes(inflated, false);
return Array.isArray(inflated) ? inflated[0] : inflated;
}
export function init(els, data, bindOptions = {}) {
(!els ? [] : els instanceof Element ? [els] : els).forEach((el) => {
const dataTplAttr = el.getAttribute("data-tpl");
if (dataTplAttr) {
const tpls = dataTplAttr.split(" ");
tpls.forEach((tpl) => {
const dataAttribute = el.getAttribute("data") || "";
const childData = (dataAttribute && getObjValue(dataAttribute, data)) || data;
bind(el, childData, bindOptions);
});
}
else {
bind(el, data, bindOptions);
}
});
}
export function bind(elOrEls, data = {}, bindOptions = {}) {
var _a;
if (elOrEls instanceof HTMLElement) {
data = getPreProcessScript(elOrEls)({ ...data });
}
if (typeof data !== "object") {
data = { value: data };
if (elOrEls instanceof HTMLElement &&
elOrEls.children.length === 0 &&
!elOrEls.getAttribute(CONFIG.attrBind)) {
dom.setAttributes(elOrEls, { [CONFIG.attrBind]: "value" });
}
}
let passedEls = !Array.isArray(elOrEls) ? [elOrEls] : elOrEls;
for (const el of passedEls) {
const conditionEls = toArray(dom.queryAll(`[${CONFIG.attrIf}]`, el));
const contextElement = (_a = bindOptions.contextElement) !== null && _a !== void 0 ? _a : (el instanceof ShadowRoot ? el.host : el);
for (const conditionEl of conditionEls) {
getValueForBindingPropName;
let isTrue = getValueForBinding(conditionEl.getAttribute(CONFIG.attrIf), {
data,
contextElement: contextElement,
currentElement: conditionEl,
});
conditionEl.setAttribute(CONFIG.attrIfIs, String(!!isTrue));
}
let toBindEls = toArray(dom.queryAll(`:not([${CONFIG.attrIfIs}="false"]) [${CONFIG.attrBind}]:not([data-tpl]):not([${CONFIG.attrIfIs}="false"])`, el));
if (el instanceof HTMLElement && el.getAttribute(CONFIG.attrBind)) {
toBindEls.unshift(el);
}
if (toBindEls.length) {
let innerBindsElements = dom.queryAll(`:scope [data-tpl] [${CONFIG.attrBind}], :scope [data-autobind="false"] [${CONFIG.attrBind}]`, el);
toBindEls = toBindEls.filter((maybeBind) => !innerBindsElements.includes(maybeBind));
toBindEls.forEach((child) => {
RGX_BIND_DECLARATIONS.lastIndex = 0;
let bindings = [];
let bindingMatch;
while ((bindingMatch = RGX_BIND_DECLARATIONS.exec(child.getAttribute(CONFIG.attrBind).replace(/\s+/, " ").trim())) !== null) {
bindings.push([bindingMatch[1], bindingMatch[2]]);
}
bindings.forEach((bindings) => {
let bindingDataProperty = localAssertNotFalsy(bindings.shift());
let bindingFields = ((bindings.length && bindings[0]) || "default")
.trim()
.replace(/^\[(.*?)\]$/i, "$1")
.split(",");
let value = getValueForBinding(bindingDataProperty, {
data,
contextElement: contextElement,
currentElement: child,
});
if (value === undefined) {
if (bindOptions.onlyDefined === true) {
return;
}
else {
value = null;
}
}
bindingFields.forEach((field) => {
if (field.startsWith("style.")) {
let stringVal = String(value);
if (value &&
!stringVal.includes("url(") &&
stringVal !== "none" &&
(field.includes("background-image") || stringVal.startsWith("http"))) {
value = `url(${value})`;
}
dom.setStyle(child, field.replace("style.", ""), value);
}
else if (field.startsWith("el.")) {
if (field === "el.remove") {
if (value === true) {
child.remove();
}
}
else if (field === "el.toggle") {
dom.setStyle(child, "display", value === true ? "" : "none");
}
else if (field.startsWith("el.classList.toggle")) {
const cssClass = field.replace(/el.classList.toggle\(['"]?(.*?)['"]?\)/, "$1");
child.classList.toggle(cssClass, !!value);
}
}
else if (RGX_BIND_FN_TEMPLATE_OR_ELEMENT.test(field)) {
dom.empty(child);
let elementOrTemplateName = RGX_BIND_FN_TEMPLATE_OR_ELEMENT.exec(field)[1];
if (Array.isArray(value) || value instanceof Set) {
const arrayVals = toArray(value);
let isElement = RGX_BIND_FN_ELEMENT.test(field);
let frag = document.createDocumentFragment();
arrayVals.forEach((item, index) => {
let itemData;
if (typeof item === "object") {
itemData = Object.assign({ $index: index }, item);
}
else {
itemData = { $index: index, value: item };
}
const els = bindToElOrTemplate(elementOrTemplateName, itemData);
frag.append(...els);
});
if (child.nodeName.toUpperCase() === "TPL") {
replaceTplElementWithChildren(child, frag);
}
else {
dom.empty(child).appendChild(frag);
}
}
else if (value) {
const els = bindToElOrTemplate(elementOrTemplateName, value);
if (child.nodeName.toUpperCase() === "TPL") {
replaceTplElementWithChildren(child, els);
}
else {
child.append(...els);
}
}
}
else {
dom.setAttributes(child, { [field]: value });
}
});
});
});
}
if (bindOptions.singleScoped !== true) {
let toInitEls = toArray(el.querySelectorAll(":scope *[data-tpl]"));
if (toInitEls.length) {
let innerInits = dom.queryAll(':scope *[data-tpl] *[data-tpl], :scope [data-autobind="false"] [data-tpl]', el);
toInitEls = toInitEls.filter((maybeInitEl) => {
if (innerInits.includes(maybeInitEl)) {
return false;
}
let tplKey = maybeInitEl.getAttribute("data-tpl");
if (data && (data[tplKey] || data[tplKey.replace("tpl:", "")])) {
return true;
}
return maybeInitEl.getAttribute("data-autobind") !== "false";
});
toInitEls.forEach((toInitEl) => {
var tplKey = toInitEl.getAttribute("data-tpl");
init(toInitEl, (data && (data[tplKey] || data[tplKey.replace("tpl:", "")])) || data);
});
}
}
}
}
function bindToElOrTemplate(elementOrTemplateName, data) {
let el = getTemplateFragment(elementOrTemplateName);
if (!el) {
el = dom.createElement(elementOrTemplateName, data);
}
else {
el = inflateOnce(el, data, { skipInit: false });
}
const els = (Array.isArray(el) ? el : [el]).filter((el) => !!el);
els.forEach((el) => {
el.removeAttribute("data-tpl");
let toBindEls = dom.queryAll("[data-tpl]", el);
let innerBindsElements = dom.queryAll(`:scope [data-tpl] [${CONFIG.attrBind}], :scope [data-autobind="false"] [${CONFIG.attrBind}]`, el);
toBindEls = toBindEls.filter((maybeBind) => !innerBindsElements.includes(maybeBind));
toBindEls.forEach((c) => c.removeAttribute("data-tpl"));
});
return els;
}

View File

@@ -0,0 +1,55 @@
import { getResolver } from "./shared_utils.js";
import { getPngMetadata, getWebpMetadata } from "./comfyui_shim.js";
function parseWorkflowJson(stringJson) {
stringJson = stringJson || "null";
stringJson = stringJson.replace(/:\s*NaN/g, ": null");
return JSON.parse(stringJson);
}
export async function tryToGetWorkflowDataFromEvent(e) {
var _a, _b, _c, _d;
let work;
for (const file of ((_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.files) || []) {
const data = await tryToGetWorkflowDataFromFile(file);
if (data.workflow || data.prompt) {
return data;
}
}
const validTypes = ["text/uri-list", "text/x-moz-url"];
const match = (((_b = e.dataTransfer) === null || _b === void 0 ? void 0 : _b.types) || []).find((t) => validTypes.find((v) => t === v));
if (match) {
const uri = (_d = (_c = e.dataTransfer.getData(match)) === null || _c === void 0 ? void 0 : _c.split("\n")) === null || _d === void 0 ? void 0 : _d[0];
if (uri) {
return tryToGetWorkflowDataFromFile(await (await fetch(uri)).blob());
}
}
return { workflow: null, prompt: null };
}
export async function tryToGetWorkflowDataFromFile(file) {
var _a;
if (file.type === "image/png") {
const pngInfo = await getPngMetadata(file);
return {
workflow: parseWorkflowJson(pngInfo === null || pngInfo === void 0 ? void 0 : pngInfo.workflow),
prompt: parseWorkflowJson(pngInfo === null || pngInfo === void 0 ? void 0 : pngInfo.prompt),
};
}
if (file.type === "image/webp") {
const pngInfo = await getWebpMetadata(file);
const workflow = parseWorkflowJson((pngInfo === null || pngInfo === void 0 ? void 0 : pngInfo.workflow) || (pngInfo === null || pngInfo === void 0 ? void 0 : pngInfo.Workflow) || "null");
const prompt = parseWorkflowJson((pngInfo === null || pngInfo === void 0 ? void 0 : pngInfo.prompt) || (pngInfo === null || pngInfo === void 0 ? void 0 : pngInfo.Prompt) || "null");
return { workflow, prompt };
}
if (file.type === "application/json" || ((_a = file.name) === null || _a === void 0 ? void 0 : _a.endsWith(".json"))) {
const resolver = getResolver();
const reader = new FileReader();
reader.onload = async () => {
const json = parseWorkflowJson(reader.result);
const isApiJson = Object.values(json).every((v) => v.class_type);
const prompt = isApiJson ? json : null;
const workflow = !isApiJson && !(json === null || json === void 0 ? void 0 : json.templates) ? json : null;
return { workflow, prompt };
};
return resolver.promise;
}
return { workflow: null, prompt: null };
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -0,0 +1,140 @@
<!doctype html>
<html>
<head>
<title>rgthree's comfy: Workflow Link Fixer</title>
<style>
html, body {
}
html {
font-size: 100%;
overflow-y: scroll;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
box-sizing: border-box
}
*, *:before, *:after {
box-sizing: inherit
}
body {
background: #222;
font-family: Arial, sans-serif;
font-size: calc(16 * 0.0625rem);
font-weight: 400;
margin: 0;
color: #ffffff;
padding-bottom: 64px;
display: flex;
}
.box, figure, picture {
display: flex;
flex-direction: column;
align-items: center;
}
.box {
margin: 32px auto;
max-width: 720px;
padding: 16px 16px 32px;
background: rgba(125,125,125,0.33);
box-shadow: 0px 8px 10px rgba(0,0,0,0.85);
border-radius: 8px;
}
h1 {
margin-top: 0;
}
.box > * {
text-align: center;
}
.box > p {
margin: 0 0 .6em;
text-align: left;
line-height: 1.25;
}
.box > small {
margin: 0 0 .6em;
text-align: left;
line-height: 1.25;
opacity: 0.75;
}
picture > img {
display: block;
height: 256px;
min-width: 256px;
border: 10px dashed rgba(125,125,125,0.66);
border-radius: 32px;
object-fit: contain;
pointer-events: none;
}
figcaption {
opacity: 0.66;
padding: 4px;
font-size: calc(11 * 0.0625rem);
}
.-has-file picture > img {
border-color: transparent;
border-radius: 32px;
}
.output {
margin: 8px 8px 32px;
border-radius: 4px;
border: 1px solid rgba(125,125,125,0.66);
padding: 4px 8px;
min-width: 80%;
}
.output:empty {
display: none;
}
.btn-fix {
display: none;
cursor: pointer;
font-size: calc(24 * 0.0625rem);
}
.-has-fixable-results .btn-fix {
display: inline-block;
}
</style>
</head>
<body>
<div class="box">
<h1>rgthree's Workflow Link Fixer</h1>
<p>
Drag and drop a comfy-generated image or workflow json into this window to check its
serialized links and attempt to fix.
</p>
<small>
Sometimes as you have complex workflows the internal data can become corrupt, and the
ComfyUI doesn't always understand or display correctly. Maybe links disappear, or reconnect
to another node when changing something. Load it here to detect and, if possible, attempt
to fix it (sometimes, however, fixing it just isn't feasible, so fingers crossed).
</small>
<figure>
<picture>
<img class="output-image" />
</picture>
<figcaption></figcaption>
</figure>
<div class="output"></div>
<button class="btn-fix">Fix & Save new workflow</button>
</div>
<script type="module">
import {LinkPage} from './link_page.js';
new LinkPage();
</script>
</body>
</html>

View File

@@ -0,0 +1,189 @@
import { WorkflowLinkFixer } from "../common/link_fixer.js";
import { getPngMetadata } from "../common/comfyui_shim.js";
function wait(ms = 16, value) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(value);
}, ms);
});
}
const logger = {
logTo: console,
log: (...args) => {
logger.logTo === console
? console.log(...args)
: (logger.logTo.innerText += args.join(",") + "\n");
},
};
export class LinkPage {
constructor() {
this.fixer = null;
this.containerEl = document.querySelector(".box");
this.figcaptionEl = document.querySelector("figcaption");
this.outputeMessageEl = document.querySelector(".output");
this.outputImageEl = document.querySelector(".output-image");
this.btnFix = document.querySelector(".btn-fix");
document.addEventListener("dragover", (e) => {
e.preventDefault();
}, false);
document.addEventListener("drop", (e) => {
this.onDrop(e);
});
this.btnFix.addEventListener("click", (e) => {
this.onFixClick(e);
});
}
async onFixClick(e) {
var _a;
if (!((_a = this.fixer) === null || _a === void 0 ? void 0 : _a.checkedData) || !this.graph) {
this.updateUi("⛔ Fix button click without results.");
return;
}
this.graphFinalResults = this.fixer.fix();
if (this.graphFinalResults.hasBadLinks) {
this.updateUi("⛔ Hmm... Still detecting bad links. Can you file an issue at https://github.com/rgthree/rgthree-comfy/issues with your image/workflow.");
}
else {
this.updateUi("✅ Workflow fixed.<br><br><small>Please load new saved workflow json and double check linking and execution.</small>");
}
await wait(16);
await this.saveFixedWorkflow();
}
async onDrop(event) {
var _a, _b, _c, _d;
if (!event.dataTransfer) {
return;
}
this.reset();
event.preventDefault();
event.stopPropagation();
if (event.dataTransfer.files.length && ((_b = (_a = event.dataTransfer.files) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.type) !== "image/bmp") {
await this.handleFile(event.dataTransfer.files[0]);
return;
}
const validTypes = ["text/uri-list", "text/x-moz-url"];
const match = [...event.dataTransfer.types].find((t) => validTypes.find((v) => t === v));
if (match) {
const uri = (_d = (_c = event.dataTransfer.getData(match)) === null || _c === void 0 ? void 0 : _c.split("\n")) === null || _d === void 0 ? void 0 : _d[0];
if (uri) {
await this.handleFile(await (await fetch(uri)).blob());
}
}
}
reset() {
this.file = undefined;
this.graph = undefined;
this.graphResults = undefined;
this.graphFinalResults = undefined;
this.updateUi();
}
updateUi(msg) {
this.outputeMessageEl.innerHTML = "";
if (this.file && !this.containerEl.classList.contains("-has-file")) {
this.containerEl.classList.add("-has-file");
this.figcaptionEl.innerHTML = this.file.name || this.file.type;
if (this.file.type === "application/json") {
this.outputImageEl.src = "icon_file_json.png";
}
else {
const reader = new FileReader();
reader.onload = () => (this.outputImageEl.src = reader.result);
reader.readAsDataURL(this.file);
}
}
else if (!this.file && this.containerEl.classList.contains("-has-file")) {
this.containerEl.classList.remove("-has-file");
this.outputImageEl.src = "";
this.outputImageEl.removeAttribute("src");
}
if (this.graphResults) {
this.containerEl.classList.add("-has-results");
if (!this.graphResults.patches && !this.graphResults.deletes) {
this.outputeMessageEl.innerHTML = "✅ No bad links detected in the workflow.";
}
else {
this.containerEl.classList.add("-has-fixable-results");
this.outputeMessageEl.innerHTML = `⚠️ Found ${this.graphResults.patches} links to fix, and ${this.graphResults.deletes} to be removed.`;
}
}
else {
this.containerEl.classList.remove("-has-results");
this.containerEl.classList.remove("-has-fixable-results");
}
if (msg) {
this.outputeMessageEl.innerHTML = msg;
}
}
async handleFile(file) {
this.file = file;
this.updateUi();
let workflow = null;
if (file.type.startsWith("image/")) {
const pngInfo = await getPngMetadata(file);
workflow = pngInfo === null || pngInfo === void 0 ? void 0 : pngInfo.workflow;
}
else if (file.type === "application/json" ||
(file instanceof File && file.name.endsWith(".json"))) {
workflow = await new Promise((resolve) => {
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result);
};
reader.readAsText(file);
});
}
if (!workflow) {
this.updateUi("⛔ No workflow found in dropped item.");
}
else {
try {
this.graph = JSON.parse(workflow);
}
catch (e) {
this.graph = undefined;
}
if (!this.graph) {
this.updateUi("⛔ Invalid workflow found in dropped item.");
}
else {
this.loadGraphData(this.graph);
}
}
}
async loadGraphData(graphData) {
this.fixer = WorkflowLinkFixer.create(graphData);
this.graphResults = this.fixer.check();
this.updateUi();
}
async saveFixedWorkflow() {
if (!this.graphFinalResults) {
this.updateUi("⛔ Save w/o final graph patched.");
return false;
}
let filename = this.file.name || "workflow.json";
let filenames = filename.split(".");
filenames.pop();
filename = filenames.join(".");
filename += "_fixed.json";
filename = prompt("Save workflow as:", filename);
if (!filename)
return false;
if (!filename.toLowerCase().endsWith(".json")) {
filename += ".json";
}
const json = JSON.stringify(this.graphFinalResults.graph, null, 2);
const blob = new Blob([json], { type: "application/json" });
const url = URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.download = filename;
anchor.href = url;
anchor.style.display = "none";
document.body.appendChild(anchor);
await wait();
anchor.click();
await wait();
anchor.remove();
window.URL.revokeObjectURL(url);
return true;
}
}

View File

@@ -0,0 +1,32 @@
.rgthree-model-info-card {
display: block;
padding: 8px;
}
.-is-hidden {
display: none;
}
.rgthree-model-info-card {
display: flex;
flex-direction: row;
}
.rgthree-model-info-card > .rgthree-model-info-card-media-container {
width: 100px;
height: auto;
display: block;
margin: 0 8px 0 0;
padding: 0;
flex: 0 0 auto;
}
.rgthree-model-info-card > .rgthree-model-info-card-media-container > img,
.rgthree-model-info-card > .rgthree-model-info-card-media-container > video {
width: 100%;
height: 100%;
object-fit: contain;
}
.rgthree-model-info-card > .rgthree-model-info-card-data-container [bind*="name:"] {
font-size: 1.3em;
margin-bottom: 4px;
font-weight: bold;
}

View File

@@ -0,0 +1,18 @@
<template>
<div class="rgthree-model-info-card">
<figure class="rgthree-model-info-card-media-container">
<video if="images.0 && images.0.type == 'video'" bind="images.0.url:src" loop autoplay></video>
<img if="images.0 && images.0.type != 'video'" bind="images.0.url:src" loading="lazy" />
</figure>
<div class="rgthree-model-info-card-data-container">
<div bind="name:text"></div>
<div bind="file:text"></div>
<div bind="getModified(modified):text"></div>
<div bind="sha256:text"></div>
<div bind="hasInfoFile"></div>
<a if="getCivitaiLink(links)" bind="getCivitaiLink(links):href">Civitai</a>
</div>
</div>
</template>

View File

@@ -0,0 +1,41 @@
import { RgthreeCustomElement } from "../../common/components/base_custom_element.js";
export class RgthreeModelInfoCard extends RgthreeCustomElement {
constructor() {
super(...arguments);
this.data = {};
}
getModified(value, data, currentElement, contextElement) {
const date = new Date(value);
return String(`${date.toLocaleDateString()} ${date.toLocaleTimeString()}`);
}
getCivitaiLink(links) {
return (links === null || links === void 0 ? void 0 : links.find((i) => i.includes("civitai.com/models"))) || null;
}
setModelData(data) {
this.data = data;
}
hasBaseModel(baseModel) {
return this.data.baseModel === baseModel;
}
hasData(field) {
var _a;
if (field === "civitai") {
return !!((_a = this.getCivitaiLink(this.data.links)) === null || _a === void 0 ? void 0 : _a.length);
}
return !!this.data[field];
}
matchesQueryText(query) {
var _a;
return (_a = (this.data.name || this.data.file)) === null || _a === void 0 ? void 0 : _a.includes(query);
}
hide() {
this.classList.add("-is-hidden");
}
show() {
this.classList.remove("-is-hidden");
}
}
RgthreeModelInfoCard.NAME = "rgthree-model-info-card";
RgthreeModelInfoCard.TEMPLATES = "components/model-info-card.html";
RgthreeModelInfoCard.CSS = "components/model-info-card.css";
RgthreeModelInfoCard.USE_SHADOW = false;

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>rgthree-comfy: Models Manager</title>
<link href="./models.css" rel="stylesheet">
<!-- <link rel="icon" href="./favicon.webp"> -->
</head>
<body>
<header>
<input type="text" placeholder="Search" id="searchbox" />
</header>
<ul id="models-list"></ul>
<script type="module">
import {ModelsInfoPage} from './models_info_page.js';
window.page = new ModelsInfoPage();
</script>
</body>
</html>

View File

@@ -0,0 +1,38 @@
:root {
--rgthree-bg-color: rgba(23, 23, 23, 0.9);
--rgthree-on-bg-color: rgba(48, 48, 48, 0.9);
}
html, body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
box-sizing: border-body;
background: var(--rgthree-bg-color);
}
*, *::before, *::after {
box-sizing: inherit;
}
[if-is=false] {
display: none;
}
.models-list {
list-style: none;
margin: 0;
padding: 0;
}
.model-item {
display: block;
margin: 8px;
}
rgthree-model-info-card {
background: var(--rgthree-on-bg-color);
margin: 16px;
display: block;
border-radius: 4px;
}

View File

@@ -0,0 +1,121 @@
import { createElement, getActionEls, query, queryAll } from "../common/utils_dom.js";
import { rgthreeApi } from "../common/rgthree_api.js";
import { RgthreeModelInfoCard } from "./components/model-info-card.js";
function parseQuery(query) {
const matches = query.match(/"[^\"]+"/g) || [];
for (const match of matches) {
let cleaned = match.substring(1, match.length - 1);
cleaned = cleaned.replace(/\s+/g, " ").trim().replace(/\s/g, "__SPACE__");
query = query.replace(match, ` ${cleaned} `);
}
const queryParts = query
.replace(/\s+/g, " ")
.trim()
.split(" ")
.map((p) => p.replace(/__SPACE__/g, " "));
return queryParts;
}
export class ModelsInfoPage {
constructor() {
this.selectBaseModel = createElement('select[name="baseModel"][on="change:filter"]');
this.searchbox = query("#searchbox");
this.modelsList = query("#models-list");
this.queryLast = "";
this.doSearchDebounce = 0;
console.log("hello model page");
this.init();
}
async init() {
this.searchbox.addEventListener("input", (e) => {
if (this.doSearchDebounce) {
return;
}
this.doSearchDebounce = setTimeout(() => {
this.doSearch();
this.doSearchDebounce = 0;
}, 250);
});
const loras = await rgthreeApi.getLorasInfo({ light: true });
console.log(loras);
const baseModels = new Set();
for (const lora of loras) {
const el = RgthreeModelInfoCard.create();
el.setModelData(lora);
el.bindWhenConnected(lora);
console.log(el);
lora.baseModel && baseModels.add(lora.baseModel);
this.modelsList.appendChild(createElement("li.model-item", { child: el }));
}
if (baseModels.size > 1) {
createElement(`option[value="ALL"][text="Choose base model."]`, {
parent: this.selectBaseModel,
});
for (const baseModel of baseModels.values()) {
createElement(`option[value="${baseModel}"][text="${baseModel}"]`, {
parent: this.selectBaseModel,
});
}
this.searchbox.insertAdjacentElement("afterend", this.selectBaseModel);
}
const data = getActionEls(document.body);
for (const dataItem of Object.values(data)) {
for (const [event, action] of Object.entries(dataItem.actions)) {
dataItem.el.addEventListener(event, (e) => {
if (typeof this[action] != "function") {
throw new Error(`"${action}" does not exist on instance.`);
}
this[action](e);
});
}
}
}
filter() {
const parts = parseQuery(this.queryLast);
const baseModel = this.selectBaseModel.value;
const els = queryAll(RgthreeModelInfoCard.NAME);
const shouldHide = (el) => {
let hide = baseModel !== "ALL" && !el.hasBaseModel(baseModel);
if (!hide) {
for (let part of parts) {
let negate = false;
if (part.startsWith("-")) {
negate = true;
part = part.substring(1);
}
if (!part)
continue;
if (part.startsWith("has:")) {
if (part === "has:civitai") {
hide = !el.hasData(part.replace("has:", ""));
}
}
else {
hide = !el.matchesQueryText(part);
}
hide = negate ? !hide : hide;
if (hide) {
break;
}
}
}
return hide;
};
for (const el of els) {
const hide = shouldHide(el);
if (hide) {
el.hide();
}
else {
el.show();
}
}
console.log("filter");
}
doSearch() {
const query = this.searchbox.value.trim();
if (this.queryLast != query) {
this.queryLast = query;
this.filter();
}
}
}