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
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:
253
custom_nodes/rgthree-comfy/web/comfyui/dynamic_context.js
Normal file
253
custom_nodes/rgthree-comfy/web/comfyui/dynamic_context.js
Normal 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);
|
||||
}
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user