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,27 @@
import { api } from "../../scripts/api.js";
function nodeFeedbackHandler(event) {
let nodes = app.graph._nodes_by_id;
let node = nodes[event.detail.node_id];
if(node) {
if(event.detail.type == "text") {
const w = node.widgets.find((w) => event.detail.widget_name === w.name);
if(w) {
w.value = event.detail.data;
}
}
}
}
api.addEventListener("inspire-node-feedback", nodeFeedbackHandler);
function nodeOutputLabelHandler(event) {
let nodes = app.graph._nodes_by_id;
let node = nodes[event.detail.node_id];
if(node) {
node.outputs[event.detail.output_idx].label = event.detail.label;
}
}
api.addEventListener("inspire-node-output-label", nodeOutputLabelHandler);

View File

@@ -0,0 +1,71 @@
import { ComfyApp, app } from "../../scripts/app.js";
function load_image(str) {
let base64String = canvas.toDataURL('image/png');
let img = new Image();
img.src = base64String;
}
app.registerExtension({
name: "Comfy.Inspire.img",
nodeCreated(node, app) {
if(node.comfyClass == "LoadImage //Inspire") {
let w = node.widgets.find(obj => obj.name === 'image_data');
Object.defineProperty(w, 'value', {
set(v) {
if(v != '[IMAGE DATA]')
w._value = v;
},
get() {
const stackTrace = new Error().stack;
if(!stackTrace.includes('draw') && !stackTrace.includes('graphToPrompt') && stackTrace.includes('app.js')) {
return "[IMAGE DATA]";
}
else {
return w._value;
}
}
});
let set_img_act = (v) => {
node._img = v;
var canvas = document.createElement('canvas');
canvas.width = v[0].width;
canvas.height = v[0].height;
var context = canvas.getContext('2d');
context.drawImage(v[0], 0, 0, v[0].width, v[0].height);
var base64Image = canvas.toDataURL('image/png');
w.value = base64Image;
};
Object.defineProperty(node, 'imgs', {
set(v) {
if (v && !v[0].complete) {
let orig_onload = v[0].onload;
v[0].onload = function(v2) {
if(orig_onload)
orig_onload();
set_img_act(v);
};
}
else {
set_img_act(v);
}
},
get() {
if(this._img == undefined && w.value != '') {
this._img = [new Image()];
if(w.value && w.value != '[IMAGE DATA]')
this._img[0].src = w.value;
}
return this._img;
}
});
}
}
})

View File

@@ -0,0 +1,36 @@
import { api } from "../../scripts/api.js";
async function refresh_data(node) {
let response = await api.fetchApi('/inspire/cache/list');
node.widgets[0].value = await response.text();
}
async function remove_key(node, key) {
await api.fetchApi(`/inspire/cache/remove?key=${key}`);
node.widgets[1].value = '';
refresh_data(node);
}
async function clear_data(node) {
await api.fetchApi('/inspire/cache/clear');
refresh_data(node);
}
async function set_cache_settings(node) {
await api.fetchApi('/inspire/cache/settings', {
method: "POST",
headers: {"Content-Type": "application/json",},
body: node.widgets[0].value,
});
refresh_data(node);
}
export function register_cache_info(node, app) {
if(node.comfyClass == "ShowCachedInfo //Inspire") {
node.addWidget("button", "Remove Key", null, () => { remove_key(node, node.widgets[1].value); });
node.addWidget("button", "Save Settings", null, () => { set_cache_settings(node); });
node.addWidget("button", "Refresh", null, () => { refresh_data(node); });
node.addWidget("button", "Clear", null, () => { clear_data(node); });
refresh_data(node);
}
}

View File

@@ -0,0 +1,301 @@
import { ComfyApp, app } from "../../scripts/app.js";
export function register_concat_conditionings_with_multiplier_node(nodeType, nodeData, app) {
if (nodeData.name === 'ConcatConditioningsWithMultiplier //Inspire') {
var input_name = "conditioning";
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
let this_handler = async function (type, index, connected, link_info) {
let last_state = this.state_change_handling;
try {
this.state_change_handling = true;
if(!link_info || link_info.type != 'CONDITIONING')
return;
let self = this;
function get_input_count(prefix, linked_only) {
let cnt = 0;
for(let i in self.inputs) {
if(linked_only && !self.inputs[i].link)
continue;
if(self.inputs[i].name.startsWith(prefix))
cnt+=1;
}
return cnt;
}
function get_widget_count(prefix) {
let cnt = 0;
for(let i in self.widgets) {
if(self.widgets[i].name.startsWith(prefix))
cnt+=1;
}
return cnt;
}
function get_unconnected() {
let unconnected = [];
for(let i in self.inputs) {
let input = self.inputs[i];
if(input.name.startsWith('conditioning')) {
if(input.link == undefined)
unconnected.push(i);
}
}
return unconnected;
}
let unconnected = get_unconnected();
function renames() {
let con_i = 1;
let rename_map = {};
for(let i in self.inputs) {
let input = self.inputs[i];
if(input.name.startsWith('conditioning')) {
let orig_i = Number(input.name.substring(12));
if(orig_i != con_i) {
rename_map[orig_i] = con_i;
input.name = 'conditioning'+con_i;
}
con_i++;
}
}
// update multiplier input
for(let i in self.inputs) {
let input = self.inputs[i];
if(input.name.startsWith('multiplier')) {
let orig_i = Number(input.name.substring(10));
if(rename_map[orig_i]) {
input.name = 'multiplier'+rename_map[orig_i];
}
}
}
// update multiplier widget
for(let i in self.widgets) {
let w = self.widgets[i];
if(w.name.startsWith('multiplier')) {
let orig_i = Number(w.name.substring(10));
if(rename_map[orig_i]) {
w.name = 'multiplier'+rename_map[orig_i];
}
}
}
return con_i;
}
function remove_multiplier_link(i, link_id) {
let link = app.graph.links[link_id];
const node = app.graph.getNodeById(link.origin_id);
let x = node.outputs[link.origin_slot].links.findIndex((w) => w == link_id);
node.outputs[link.origin_slot].links.splice(x, 1);
self.disconnectInput(i);
app.graph.links.splice(link_id, 1);
}
async function remove_target_multiplier(target_name) {
// remove strength from slot
for(let i in self.inputs) {
let input = self.inputs[i];
if(input.name.startsWith(target_name)) {
if(input.link) {
remove_multiplier_link(i, input.link);
}
await self.removeInput(i);
break;
}
}
const widget_index = self.widgets.findIndex((w) => w.name == target_name);
self.widgets.splice(widget_index, 1);
}
async function remove_garbage() {
let unconnected = get_unconnected();
// remove unconnected conditionings
while(unconnected.length > 0) {
let last_one = unconnected.reverse()[0];
self.removeInput(last_one);
unconnected = get_unconnected();
}
// remove dangling multipliers
let conds = new Set();
let muls = new Set();
for(let i in self.inputs) {
let input = self.inputs[i];
if(input.link && input.name.startsWith('conditioning')) {
let index = Number(input.name.substring(12));
conds.add(index);
}
else if(input.name.startsWith('multiplier')) {
let index = Number(input.name.substring(10));
muls.add(index);
}
}
for(let i in self.widgets) {
let index = Number(self.widgets[i].name.substring(10));
muls.add(index);
}
let dangling_muls = [...muls].filter(x => !conds.has(x));
while(dangling_muls.length > 0) {
let remove_target = dangling_muls.pop();
let target_name = `multiplier${remove_target}`;
await remove_target_multiplier(target_name);
}
}
async function ensure_multipliers() {
if(self.ensuring_multipliers) {
return;
}
try {
self.ensuring_multipliers = true;
let ncon = get_input_count('conditioning', true);
let nmul = get_input_count('multiplier', false) + get_widget_count('multiplier');
if(ncon == 0 && nmul == 0)
ncon = 1;
for(let i = nmul+1; i<=ncon; i++) {
let config = { min: 0, max: 10, step: 0.1, round: 0.01, precision: 2 };
// NOTE: addWidget trigger calling ensure_multipliers
let widget = await self.addWidget("number", `multiplier${i}`, 1.0, function (v) {
if (config.round) {
self.value = Math.round(v/config.round)*config.round;
} else {
self.value = v;
}
}, config);
}
}
finally{
self.ensuring_multipliers = null;
}
}
async function recover_multipliers() {
if(self.recover_multipliers) {
return;
}
try {
self.recover_multipliers = true;
for(let i = 1; i<self.widgets_values.length; i++) {
let config = { min: 0, max: 10, step: 0.1, round: 0.01, precision: 2 };
// NOTE: addWidget trigger calling recover_multipliers
let widget = await self.addWidget("number", `multiplier${i+1}`, 1.0, function (v) {
if (config.round) {
self.value = Math.round(v/config.round)*config.round;
} else {
self.value = v;
}
}, config);
}
}
finally{
self.recover_multipliers = null;
}
}
async function ensure_inputs() {
if(get_unconnected() == 0) {
let con_i = renames();
self.addInput(`conditioning${con_i}`, self.outputs[0].type);
}
}
const stackTrace = new Error().stack;
if(!stackTrace.includes('loadGraphData') && !stackTrace.includes('pasteFromClipboard')) {
await remove_garbage();
await ensure_inputs();
}
if(!stackTrace.includes('loadGraphData')) {
await ensure_multipliers();
}
else {
await recover_multipliers();
}
await this.setSize( this.computeSize() );
}
finally {
this.state_change_handling = last_state;
}
}
nodeType.prototype.onConnectionsChange = this_handler;
}
}
function ensure_splitter_outputs(node, output_name, value, type) {
if(node.outputs.length != (value + 1)) {
while(node.outputs.length != (value + 1)) {
if(node.outputs.length > value + 1) {
node.removeOutput(node.outputs.length-1);
}
else {
node.addOutput(`output${node.outputs.length+1}`, type);
}
}
for(let i in node.outputs) {
let output = node.outputs[i];
output.name = `${output_name} ${parseInt(i)+1}`;
}
if(node.outputs[0].label == type || node.outputs[0].label == 'remained')
delete node.outputs[0].label;
let last_output = node.outputs[node.outputs.length-1];
last_output.name = 'remained';
}
}
export function register_splitter(node, app) {
if(node.comfyClass === 'ImageBatchSplitter //Inspire' || node.comfyClass === 'LatentBatchSplitter //Inspire') {
let split_count = node.widgets[0];
let output_name = 'output';
let output_type = "*";
if(node.comfyClass === 'ImageBatchSplitter //Inspire') {
output_name = 'image';
output_type = "IMAGE";
}
else if(node.comfyClass === 'LatentBatchSplitter //Inspire') {
output_name = 'latent';
output_type = "LATENT";
}
ensure_splitter_outputs(node, output_name, split_count.value, output_type);
Object.defineProperty(split_count, "value", {
set: async function(value) {
if(value < 0 || value > 50)
return;
ensure_splitter_outputs(node, output_name, value, output_type);
},
get: function() {
return node.outputs.length - 1;
}
});
}
}

View File

@@ -0,0 +1,7 @@
import { inspireProgressBadge } from "./progress-badge.js"
export function register_loop_node(nodeType, nodeData, app) {
if(nodeData.name == 'ForeachListEnd //Inspire') {
inspireProgressBadge.addStatusHandler(nodeType);
}
}

View File

@@ -0,0 +1,17 @@
import { ComfyApp, app } from "../../scripts/app.js";
import { register_concat_conditionings_with_multiplier_node, register_splitter } from "./inspire-flex.js";
import { register_cache_info } from "./inspire-backend.js";
import { register_loop_node } from "./inspire-loop.js";
app.registerExtension({
name: "Comfy.Inspire",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
await register_concat_conditionings_with_multiplier_node(nodeType, nodeData, app);
await register_loop_node(nodeType, nodeData, app);
},
nodeCreated(node, app) {
register_cache_info(node, app);
register_splitter(node, app);
}
})

View File

@@ -0,0 +1,165 @@
import { ComfyApp, app } from "../../scripts/app.js";
app.registerExtension({
name: "Comfy.Inspire.LBW",
nodeCreated(node, app) {
if(node.comfyClass == "LoraLoaderBlockWeight //Inspire" || node.comfyClass == "MakeLBW //Inspire") {
// category filter
const lora_names_widget = node.widgets[node.widgets.findIndex(obj => obj.name === 'lora_name')];
var full_lora_list = lora_names_widget.options.values;
const category_filter_widget = node.widgets[node.widgets.findIndex(obj => obj.name === 'category_filter')];
Object.defineProperty(lora_names_widget.options, "values", {
set: (x) => {
full_lora_list = x;
},
get: () => {
if(category_filter_widget.value == 'All')
return full_lora_list;
let l = full_lora_list.filter(x => x.startsWith(category_filter_widget.value));
return l;
}
});
// vector selector
let preset_i = 9;
let vector_i = 10;
if(node.comfyClass == "MakeLBW //Inspire") {
preset_i = 7;
vector_i = 8;
}
node._value = "Preset";
node.widgets[preset_i].callback = (v, canvas, node, pos, e) => {
node.widgets[vector_i].value = node._value.split(':')[1];
if(node.widgets_values) {
node.widgets_values[vector_i] = node.widgets[preset_i].value;
}
}
Object.defineProperty(node.widgets[preset_i], "value", {
set: (value) => {
if(value != "Preset")
node._value = value;
},
get: () => {
return node._value;
}
});
}
if(node.comfyClass == "XY Input: Lora Block Weight //Inspire") {
// category filter
const lora_names_widget = node.widgets[node.widgets.findIndex(obj => obj.name === 'lora_name')];
var full_lora_list = lora_names_widget.options.values;
const category_filter_widget = node.widgets[node.widgets.findIndex(obj => obj.name === 'category_filter')];
Object.defineProperty(lora_names_widget.options, "values", {
set: (x) => {
full_lora_list = x;
},
get: () => {
if(category_filter_widget.value == 'All')
return full_lora_list;
let l = full_lora_list.filter(x => x.startsWith(category_filter_widget.value));
return l;
}
});
// vector selector
let preset_i = 9;
let vector_i = 10;
node._value = "Preset";
node.widgets[preset_i].callback = (v, canvas, node, pos, e) => {
let value = node._value;
if(!value.startsWith('@') && node.widgets[vector_i].value != "")
node.widgets[vector_i].value += "\n";
if(value.startsWith('@')) {
let spec = value.split(':')[1];
var n;
var sub_n = null;
var block = null;
if(isNaN(spec)) {
let sub_spec = spec.split(',');
if(sub_spec.length != 3) {
node.widgets_values[vector_i] = '!! SPEC ERROR !!';
node._value = '';
return;
}
n = parseInt(sub_spec[0].trim());
sub_n = parseInt(sub_spec[1].trim());
block = parseInt(sub_spec[2].trim());
}
else {
n = parseInt(spec.trim());
}
node.widgets[vector_i].value = "";
if(sub_n == null) {
for(let i=1; i<=n; i++) {
var temp = "";
for(let j=1; j<=n; j++) {
if(temp!='')
temp += ',';
if(j==i)
temp += 'A';
else
temp += '0';
}
node.widgets[vector_i].value += `B${i}:${temp}\n`;
}
}
else {
for(let i=1; i<=sub_n; i++) {
var temp = "";
for(let j=1; j<=n; j++) {
if(temp!='')
temp += ',';
if(block!=j)
temp += '0';
else {
temp += ' ';
for(let k=1; k<=sub_n; k++) {
if(k==i)
temp += 'A ';
else
temp += '0 ';
}
}
}
node.widgets[vector_i].value += `B${block}.SUB${i}:${temp}\n`;
}
}
}
else {
node.widgets[vector_i].value += `${value}/${value.split(':')[0]}`;
}
if(node.widgets_values) {
node.widgets_values[vector_i] = node.widgets[preset_i].value;
}
}
Object.defineProperty(node.widgets[preset_i], "value", {
set: (value) => {
if(value != 'Preset')
node._value = value;
},
get: () => {
return node._value;
}
});
}
}
});

View File

@@ -0,0 +1,76 @@
import { api } from "../../scripts/api.js";
// copying from https://github.com/pythongosssss/ComfyUI-WD14-Tagger
class InspireProgressBadge {
constructor() {
if (!window.__progress_badge__) {
window.__progress_badge__ = Symbol("__inspire_progress_badge__");
}
this.symbol = window.__progress_badge__;
}
getState(node) {
return node[this.symbol] || {};
}
setState(node, state) {
node[this.symbol] = state;
app.canvas.setDirty(true);
}
addStatusHandler(nodeType) {
if (nodeType[this.symbol]?.statusTagHandler) {
return;
}
if (!nodeType[this.symbol]) {
nodeType[this.symbol] = {};
}
nodeType[this.symbol] = {
statusTagHandler: true,
};
api.addEventListener("inspire/update_status", ({ detail }) => {
let { node, progress, text } = detail;
const n = app.graph.getNodeById(+(node || app.runningNodeId));
if (!n) return;
const state = this.getState(n);
state.status = Object.assign(state.status || {}, { progress: text ? progress : null, text: text || null });
this.setState(n, state);
});
const self = this;
const onDrawForeground = nodeType.prototype.onDrawForeground;
nodeType.prototype.onDrawForeground = function (ctx) {
const r = onDrawForeground?.apply?.(this, arguments);
const state = self.getState(this);
if (!state?.status?.text) {
return r;
}
const { fgColor, bgColor, text, progress, progressColor } = { ...state.status };
ctx.save();
ctx.font = "12px sans-serif";
const sz = ctx.measureText(text);
ctx.fillStyle = bgColor || "dodgerblue";
ctx.beginPath();
ctx.roundRect(0, -LiteGraph.NODE_TITLE_HEIGHT - 20, sz.width + 12, 20, 5);
ctx.fill();
if (progress) {
ctx.fillStyle = progressColor || "green";
ctx.beginPath();
ctx.roundRect(0, -LiteGraph.NODE_TITLE_HEIGHT - 20, (sz.width + 12) * progress, 20, 5);
ctx.fill();
}
ctx.fillStyle = fgColor || "#fff";
ctx.fillText(text, 6, -LiteGraph.NODE_TITLE_HEIGHT - 6);
ctx.restore();
return r;
};
}
}
export const inspireProgressBadge = new InspireProgressBadge();

View File

@@ -0,0 +1,339 @@
import { ComfyApp, app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
let get_wildcards_list;
let get_wildcard_label;
let is_wildcard_label;
let load_wildcard_status;
try {
const ImpactPack = await import("../ComfyUI-Impact-Pack/impact-pack.js");
console.log("[Inspire Pack] Impact Pack module loaded:", ImpactPack);
get_wildcards_list = ImpactPack.get_wildcards_list;
get_wildcard_label = ImpactPack.get_wildcard_label;
is_wildcard_label = ImpactPack.is_wildcard_label;
load_wildcard_status = ImpactPack.load_wildcard_status;
console.log("[Inspire Pack] Functions imported:", {
get_wildcards_list: !!get_wildcards_list,
get_wildcard_label: !!get_wildcard_label,
is_wildcard_label: !!is_wildcard_label,
load_wildcard_status: !!load_wildcard_status
});
}
catch (error) {
console.error("[Inspire Pack] Failed to import Impact Pack module:", error);
}
// Fallback for get_wildcards_list
if(!get_wildcards_list) {
console.warn("[Inspire Pack] get_wildcards_list not available. Using fallback.");
get_wildcards_list = () => {
return ["Impact Pack isn't installed or needs browser cache refresh."];
}
}
// Fallback for on-demand features (backward compatibility with older Impact Pack)
if(!get_wildcard_label) {
get_wildcard_label = () => { return "Select the Wildcard to add to the text"; };
}
if(!is_wildcard_label) {
is_wildcard_label = (value) => { return value === "Select the Wildcard to add to the text"; };
}
if(!load_wildcard_status) {
load_wildcard_status = async () => {}; // No-op for older versions
}
let pb_cache = {};
async function get_prompt_builder_items(category) {
if(pb_cache[category])
return pb_cache[category];
else {
let res = await api.fetchApi(`/inspire/prompt_builder?category=${category}`);
let data = await res.json();
pb_cache[category] = data.presets;
return data.presets;
}
}
app.registerExtension({
name: "Comfy.Inspire.Prompts",
nodeCreated(node, app) {
if(node.comfyClass == "WildcardEncode //Inspire") {
const wildcard_text_widget_index = node.widgets.findIndex((w) => w.name == 'wildcard_text');
const populated_text_widget_index = node.widgets.findIndex((w) => w.name == 'populated_text');
const mode_widget_index = node.widgets.findIndex((w) => w.name == 'mode');
const wildcard_text_widget = node.widgets[wildcard_text_widget_index];
const populated_text_widget = node.widgets[populated_text_widget_index];
// lora selector, wildcard selector
let combo_id = 5;
// lora
node.widgets[combo_id].callback = (value, canvas, node, pos, e) => {
let lora_name = node._value;
if(lora_name.endsWith('.safetensors')) {
lora_name = lora_name.slice(0, -12);
}
wildcard_text_widget.value += `<lora:${lora_name}>`;
}
Object.defineProperty(node.widgets[combo_id], "value", {
set: (value) => {
if (value !== "Select the LoRA to add to the text")
node._value = value;
},
get: () => { return "Select the LoRA to add to the text"; }
});
// wildcard
node.widgets[combo_id+1].callback = async (value, canvas, node, pos, e) => {
if(wildcard_text_widget.value != '')
wildcard_text_widget.value += ', '
wildcard_text_widget.value += node._wildcard_value;
// Reload wildcard status to update loaded count (Impact Pack staged feature)
await load_wildcard_status();
app.canvas.setDirty(true);
}
Object.defineProperty(node.widgets[combo_id+1], "value", {
set: (value) => {
if (!is_wildcard_label(value))
node._wildcard_value = value;
},
get: () => { return get_wildcard_label(); }
});
Object.defineProperty(node.widgets[combo_id+1].options, "values", {
set: (x) => {},
get: () => {
return get_wildcards_list();
}
});
// Preventing validation errors from occurring in any situation.
node.widgets[combo_id].serializeValue = () => { return "Select the LoRA to add to the text"; }
node.widgets[combo_id+1].serializeValue = () => {
// Always serialize as the default label (not the dynamic on-demand label)
return "Select the Wildcard to add to the text";
}
// wildcard populating
populated_text_widget.inputEl.disabled = true;
const mode_widget = node.widgets[mode_widget_index];
// mode combo
Object.defineProperty(mode_widget, "value", {
set: (value) => {
if(value == true)
node._mode_value = "populate";
else if(value == false)
node._mode_value = "fixed";
else
node._mode_value = value; // combo value
populated_text_widget.inputEl.disabled = node._mode_value == 'populate';
},
get: () => {
if(node._mode_value != undefined)
return node._mode_value;
else
return 'populate';
}
});
}
else if(node.comfyClass == "MakeBasicPipe //Inspire") {
const pos_wildcard_text_widget = node.widgets.find((w) => w.name == 'positive_wildcard_text');
const pos_populated_text_widget = node.widgets.find((w) => w.name == 'positive_populated_text');
const neg_wildcard_text_widget = node.widgets.find((w) => w.name == 'negative_wildcard_text');
const neg_populated_text_widget = node.widgets.find((w) => w.name == 'negative_populated_text');
const mode_widget = node.widgets.find((w) => w.name == 'wildcard_mode');
const direction_widget = node.widgets.find((w) => w.name == 'Add selection to');
// lora selector, wildcard selector
let combo_id = 5;
node.widgets[combo_id].callback = (value, canvas, node, pos, e) => {
let lora_name = node._lora_value;
if (lora_name.endsWith('.safetensors')) {
lora_name = lora_name.slice(0, -12);
}
if(direction_widget.value) {
pos_wildcard_text_widget.value += `<lora:${lora_name}>`;
}
else {
neg_wildcard_text_widget.value += `<lora:${lora_name}>`;
}
}
Object.defineProperty(node.widgets[combo_id], "value", {
set: (value) => {
if (value !== "Select the LoRA to add to the text")
node._lora_value = value;
},
get: () => { return "Select the LoRA to add to the text"; }
});
node.widgets[combo_id+1].callback = async (value, canvas, node, pos, e) => {
let w = null;
if(direction_widget.value) {
w = pos_wildcard_text_widget;
}
else {
w = neg_wildcard_text_widget;
}
if(w.value != '')
w.value += ', '
w.value += node._wildcard_value;
// Reload wildcard status to update loaded count (Impact Pack staged feature)
await load_wildcard_status();
app.canvas.setDirty(true);
}
Object.defineProperty(node.widgets[combo_id+1], "value", {
set: (value) => {
if (!is_wildcard_label(value))
node._wildcard_value = value;
},
get: () => { return get_wildcard_label(); }
});
Object.defineProperty(node.widgets[combo_id+1].options, "values", {
set: (x) => {},
get: () => {
return get_wildcards_list();
}
});
// Preventing validation errors from occurring in any situation.
node.widgets[combo_id].serializeValue = () => { return "Select the LoRA to add to the text"; }
node.widgets[combo_id+1].serializeValue = () => {
// Always serialize as the default label (not the dynamic on-demand label)
return "Select the Wildcard to add to the text";
}
// wildcard populating
pos_populated_text_widget.inputEl.disabled = true;
neg_populated_text_widget.inputEl.disabled = true;
// mode combo
Object.defineProperty(mode_widget, "value", {
set: (value) => {
if(value == true)
node._mode_value = "populate";
else if(value == false)
node._mode_value = "fixed";
else
node._mode_value = value; // combo value
pos_populated_text_widget.inputEl.disabled = node._mode_value == 'populate';
neg_populated_text_widget.inputEl.disabled = node._mode_value == 'populate';
},
get: () => {
if(node._mode_value != undefined)
return node._mode_value;
else
return 'populate';
}
});
}
else if(node.comfyClass == "PromptBuilder //Inspire") {
const preset_widget = node.widgets[node.widgets.findIndex(obj => obj.name === 'preset')];
const category_widget = node.widgets[node.widgets.findIndex(obj => obj.name === 'category')];
Object.defineProperty(preset_widget.options, "values", {
set: (x) => {},
get: () => {
get_prompt_builder_items(category_widget.value);
if(pb_cache[category_widget.value] == undefined) {
return ["#PRESET"];
}
return pb_cache[category_widget.value];
}
});
preset_widget.callback = (value, canvas, node, pos, e) => {
if(node.widgets[2].value) {
node.widgets[2].value += ', ';
}
const y = node._preset_value.split(':');
if(y.length == 2)
node.widgets[2].value += y[1].trim();
else
node.widgets[2].value += node._preset_value.trim();
}
Object.defineProperty(preset_widget, "value", {
set: (value) => {
if (value !== "#PRESET")
node._preset_value = value;
},
get: () => { return '#PRESET'; }
});
preset_widget.serializeValue = (workflowNode, widgetIndex) => { return "#PRESET"; };
}
else if(node.comfyClass == "SeedExplorer //Inspire"
|| node.comfyClass == "RegionalSeedExplorerMask //Inspire"
|| node.comfyClass == "RegionalSeedExplorerColorMask //Inspire") {
const prompt_widget = node.widgets[node.widgets.findIndex(obj => obj.name === 'seed_prompt')];
const seed_widget = node.widgets[node.widgets.findIndex(obj => obj.name === 'additional_seed')];
const strength_widget = node.widgets[node.widgets.findIndex(obj => obj.name === 'additional_strength')];
let allow_init_seed = node.comfyClass == "SeedExplorer //Inspire";
node.addWidget("button", "Add to prompt", null, () => {
if(!prompt_widget.value?.trim() && allow_init_seed) {
prompt_widget.value = ''+seed_widget.value;
}
else {
if(prompt_widget.value?.trim())
prompt_widget.value += ', ';
prompt_widget.value += `${seed_widget.value}:${strength_widget.value.toFixed(2)}`;
seed_widget.value += 1;
}
});
}
}
});
const original_queuePrompt = api.queuePrompt;
async function queuePrompt_with_widget_idxs(number, { output, workflow }, ...args) {
workflow.widget_idx_map = {};
for(let i in app.graph._nodes_by_id) {
let widgets = app.graph._nodes_by_id[i].widgets;
if(widgets) {
for(let j in widgets) {
if(['seed', 'noise_seed', 'sampler_name', 'scheduler'].includes(widgets[j].name)
&& widgets[j].type != 'converted-widget') {
if(workflow.widget_idx_map[i] == undefined) {
workflow.widget_idx_map[i] = {};
}
workflow.widget_idx_map[i][widgets[j].name] = parseInt(j);
}
}
}
}
return await original_queuePrompt.call(api, number, { output, workflow }, ...args);
}
api.queuePrompt = queuePrompt_with_widget_idxs;

View File

@@ -0,0 +1,64 @@
import { ComfyApp, app } from "../../scripts/app.js";
import { ComfyDialog, $el } from "../../scripts/ui.js";
import { api } from "../../scripts/api.js";
app.registerExtension({
name: "Comfy.Inspire.Regional",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === 'ApplyRegionalIPAdapters //Inspire') {
var input_name = "input";
var base_slot = 0;
switch(nodeData.name) {
case 'ApplyRegionalIPAdapters //Inspire':
input_name = "regional_ipadapter";
base_slot = 1;
break;
}
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info) {
if(!link_info || type == 2)
return;
if(this.inputs[0].type == '*'){
const node = app.graph.getNodeById(link_info.origin_id);
let origin_type = node.outputs[link_info.origin_slot].type;
if(origin_type == '*') {
this.disconnectInput(link_info.target_slot);
return;
}
for(let i in this.inputs) {
let input_i = this.inputs[i];
if(input_i.name != 'select' && input_i.name != 'sel_mode')
input_i.type = origin_type;
}
}
if (!connected && (this.inputs.length > base_slot+1)) {
const stackTrace = new Error().stack;
if(
!stackTrace.includes('LGraphNode.prototype.connect') && // for touch device
!stackTrace.includes('LGraphNode.connect') && // for mouse device
!stackTrace.includes('loadGraphData')) {
this.removeInput(index);
}
}
let slot_i = 1;
for (let i = base_slot; i < this.inputs.length; i++) {
let input_i = this.inputs[i];
input_i.name = `${input_name}${slot_i}`
slot_i++;
}
let last_slot = this.inputs[this.inputs.length - 1];
if (last_slot.link != undefined) {
this.addInput(`${input_name}${slot_i}`, this.inputs[base_slot].type);
}
}
}
}});

View File

@@ -0,0 +1,28 @@
import { api } from "../../scripts/api.js";
function globalSeedHandler(event) {
let nodes = app.graph._nodes_by_id;
for(let i in nodes) {
let node = nodes[i];
if(node.type == 'GlobalSeed //Inspire') {
if(node.widgets) {
const w = node.widgets.find((w) => w.name == 'value');
const last_w = node.widgets.find((w) => w.name == 'last_seed');
last_w.value = w.value;
if(event.detail.value != null)
w.value = event.detail.value;
}
}
else
if(node.widgets) {
const w = node.widgets.find((w) => (w.name == 'seed' || w.name == 'noise_seed') && w.type == 'number');
if(w && event.detail.seed_map[node.id] != undefined) {
w.value = event.detail.seed_map[node.id];
}
}
}
}
api.addEventListener("inspire-global-seed", globalSeedHandler);