Ensure full LoRA node chain is considered when updating TriggerWord Toggle nodes

This commit is contained in:
Will Miao
2025-05-13 20:33:52 +08:00
parent c7627fe0de
commit cb539314de
3 changed files with 178 additions and 135 deletions

View File

@@ -1,61 +1,10 @@
import { app } from "../../scripts/app.js"; import { app } from "../../scripts/app.js";
import { dynamicImportByVersion } from "./utils.js"; import {
getLorasWidgetModule,
// Update pattern to match both formats: <lora:name:model_strength> or <lora:name:model_strength:clip_strength> LORA_PATTERN,
const LORA_PATTERN = /<lora:([^:]+):([-\d\.]+)(?::([-\d\.]+))?>/g; collectActiveLorasFromChain,
updateConnectedTriggerWords
// Function to get the appropriate loras widget based on ComfyUI version } from "./utils.js";
async function getLorasWidgetModule() {
return await dynamicImportByVersion("./loras_widget.js", "./legacy_loras_widget.js");
}
// Function to get connected trigger toggle nodes
function getConnectedTriggerToggleNodes(node) {
const connectedNodes = [];
// Check if node has outputs
if (node.outputs && node.outputs.length > 0) {
// For each output slot
for (const output of node.outputs) {
// Check if this output has any links
if (output.links && output.links.length > 0) {
// For each link, get the target node
for (const linkId of output.links) {
const link = app.graph.links[linkId];
if (link) {
const targetNode = app.graph.getNodeById(link.target_id);
if (targetNode && targetNode.comfyClass === "TriggerWord Toggle (LoraManager)") {
connectedNodes.push(targetNode.id);
}
}
}
}
}
}
return connectedNodes;
}
// Function to update trigger words for connected toggle nodes
function updateConnectedTriggerWords(node, text) {
const connectedNodeIds = getConnectedTriggerToggleNodes(node);
if (connectedNodeIds.length > 0) {
const loraNames = new Set();
let match;
LORA_PATTERN.lastIndex = 0;
while ((match = LORA_PATTERN.exec(text)) !== null) {
loraNames.add(match[1]);
}
fetch("/loramanager/get_trigger_words", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
lora_names: Array.from(loraNames),
node_ids: connectedNodeIds
})
}).catch(err => console.error("Error fetching trigger words:", err));
}
}
function mergeLoras(lorasText, lorasArr) { function mergeLoras(lorasText, lorasArr) {
const result = []; const result = [];
@@ -107,9 +56,8 @@ app.registerExtension({
// Restore saved value if exists // Restore saved value if exists
let existingLoras = []; let existingLoras = [];
if (node.widgets_values && node.widgets_values.length > 0) { if (node.widgets_values && node.widgets_values.length > 0) {
// TODO: This is a workaround for the issue caused by [AlekPet custom nodes](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet) // 0 for input widget, 1 for loras widget
// TODO: Need to be revisited when the issue is fixed. https://github.com/willmiao/ComfyUI-Lora-Manager/issues/176 const savedValue = node.widgets_values[1];
const savedValue = node.widgets_values[node.widgets_values.length - 1];
existingLoras = savedValue || []; existingLoras = savedValue || [];
} }
// Merge the loras data // Merge the loras data
@@ -145,8 +93,11 @@ app.registerExtension({
inputWidget.value = newText; inputWidget.value = newText;
// Add this line to update trigger words when lorasWidget changes cause inputWidget value to change // Collect all active loras from this node and its input chain
updateConnectedTriggerWords(node, newText); const allActiveLoraNames = collectActiveLorasFromChain(node);
// Update trigger words for connected toggle nodes with the aggregated lora names
updateConnectedTriggerWords(node, allActiveLoraNames);
} finally { } finally {
isUpdating = false; isUpdating = false;
} }
@@ -166,8 +117,11 @@ app.registerExtension({
node.lorasWidget.value = mergedLoras; node.lorasWidget.value = mergedLoras;
// Replace the existing trigger word update code with the new function // Collect all active loras from this node and its input chain
updateConnectedTriggerWords(node, value); const allActiveLoraNames = collectActiveLorasFromChain(node);
// Update trigger words for connected toggle nodes with the aggregated lora names
updateConnectedTriggerWords(node, allActiveLoraNames);
} finally { } finally {
isUpdating = false; isUpdating = false;
} }

View File

@@ -1,57 +1,11 @@
import { app } from "../../scripts/app.js"; import { app } from "../../scripts/app.js";
import { dynamicImportByVersion } from "./utils.js"; import {
getLorasWidgetModule,
// Update pattern to match both formats: <lora:name:model_strength> or <lora:name:model_strength:clip_strength> LORA_PATTERN,
const LORA_PATTERN = /<lora:([^:]+):([-\d\.]+)(?::([-\d\.]+))?>/g; getActiveLorasFromNode,
collectActiveLorasFromChain,
// Function to get the appropriate loras widget based on ComfyUI version updateConnectedTriggerWords
async function getLorasWidgetModule() { } from "./utils.js";
return await dynamicImportByVersion("./loras_widget.js", "./legacy_loras_widget.js");
}
// Function to get connected trigger toggle nodes
function getConnectedTriggerToggleNodes(node) {
const connectedNodes = [];
if (node.outputs && node.outputs.length > 0) {
for (const output of node.outputs) {
if (output.links && output.links.length > 0) {
for (const linkId of output.links) {
const link = app.graph.links[linkId];
if (link) {
const targetNode = app.graph.getNodeById(link.target_id);
if (targetNode && targetNode.comfyClass === "TriggerWord Toggle (LoraManager)") {
connectedNodes.push(targetNode.id);
}
}
}
}
}
}
return connectedNodes;
}
// Function to update trigger words for connected toggle nodes
function updateConnectedTriggerWords(node, text) {
const connectedNodeIds = getConnectedTriggerToggleNodes(node);
if (connectedNodeIds.length > 0) {
const loraNames = new Set();
let match;
LORA_PATTERN.lastIndex = 0;
while ((match = LORA_PATTERN.exec(text)) !== null) {
loraNames.add(match[1]);
}
fetch("/loramanager/get_trigger_words", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
lora_names: Array.from(loraNames),
node_ids: connectedNodeIds
})
}).catch(err => console.error("Error fetching trigger words:", err));
}
}
function mergeLoras(lorasText, lorasArr) { function mergeLoras(lorasText, lorasArr) {
const result = []; const result = [];
@@ -99,19 +53,9 @@ app.registerExtension({
// Restore saved value if exists // Restore saved value if exists
let existingLoras = []; let existingLoras = [];
if (node.widgets_values && node.widgets_values.length > 0) { if (node.widgets_values && node.widgets_values.length > 0) {
// 0 for input widget, 1 for loras widget
const savedValue = node.widgets_values[1]; const savedValue = node.widgets_values[1];
// TODO: clean up this code existingLoras = savedValue || [];
try {
// Check if the value is already an array/object
if (typeof savedValue === 'object' && savedValue !== null) {
existingLoras = savedValue;
} else if (typeof savedValue === 'string') {
existingLoras = JSON.parse(savedValue);
}
} catch (e) {
console.warn("Failed to parse loras data:", e);
existingLoras = [];
}
} }
// Merge the loras data // Merge the loras data
const mergedLoras = mergeLoras(node.widgets[0].value, existingLoras); const mergedLoras = mergeLoras(node.widgets[0].value, existingLoras);
@@ -145,8 +89,17 @@ app.registerExtension({
inputWidget.value = newText; inputWidget.value = newText;
// Update trigger words when lorasWidget changes // Update this stacker's direct trigger toggles with its own active loras
updateConnectedTriggerWords(node, newText); const activeLoraNames = new Set();
value.forEach(lora => {
if (lora.active) {
activeLoraNames.add(lora.name);
}
});
updateConnectedTriggerWords(node, activeLoraNames);
// Find all Lora Loader nodes in the chain that might need updates
updateDownstreamLoaders(node);
} finally { } finally {
isUpdating = false; isUpdating = false;
} }
@@ -166,8 +119,12 @@ app.registerExtension({
node.lorasWidget.value = mergedLoras; node.lorasWidget.value = mergedLoras;
// Update trigger words when input changes // Update this stacker's direct trigger toggles with its own active loras
updateConnectedTriggerWords(node, value); const activeLoraNames = getActiveLorasFromNode(node);
updateConnectedTriggerWords(node, activeLoraNames);
// Find all Lora Loader nodes in the chain that might need updates
updateDownstreamLoaders(node);
} finally { } finally {
isUpdating = false; isUpdating = false;
} }
@@ -176,3 +133,33 @@ app.registerExtension({
} }
}, },
}); });
// Helper function to find and update downstream Lora Loader nodes
function updateDownstreamLoaders(startNode, visited = new Set()) {
if (visited.has(startNode.id)) return;
visited.add(startNode.id);
// Check each output link
if (startNode.outputs) {
for (const output of startNode.outputs) {
if (output.links) {
for (const linkId of output.links) {
const link = app.graph.links[linkId];
if (link) {
const targetNode = app.graph.getNodeById(link.target_id);
// If target is a Lora Loader, collect all active loras in the chain and update
if (targetNode && targetNode.comfyClass === "Lora Loader (LoraManager)") {
const allActiveLoraNames = collectActiveLorasFromChain(targetNode);
updateConnectedTriggerWords(targetNode, allActiveLoraNames);
}
// If target is another Lora Stacker, recursively check its outputs
else if (targetNode && targetNode.comfyClass === "Lora Stacker (LoraManager)") {
updateDownstreamLoaders(targetNode, visited);
}
}
}
}
}
}
}

View File

@@ -64,3 +64,105 @@ export class DataWrapper {
this.data = data; this.data = data;
} }
} }
// Function to get the appropriate loras widget based on ComfyUI version
export async function getLorasWidgetModule() {
return await dynamicImportByVersion("./loras_widget.js", "./legacy_loras_widget.js");
}
// Update pattern to match both formats: <lora:name:model_strength> or <lora:name:model_strength:clip_strength>
export const LORA_PATTERN = /<lora:([^:]+):([-\d\.]+)(?::([-\d\.]+))?>/g;
// Get connected Lora Stacker nodes that feed into the current node
export function getConnectedInputStackers(node) {
const connectedStackers = [];
if (node.inputs) {
for (const input of node.inputs) {
if (input.name === "lora_stack" && input.link) {
const link = app.graph.links[input.link];
if (link) {
const sourceNode = app.graph.getNodeById(link.origin_id);
if (sourceNode && sourceNode.comfyClass === "Lora Stacker (LoraManager)") {
connectedStackers.push(sourceNode);
}
}
}
}
}
return connectedStackers;
}
// Get connected TriggerWord Toggle nodes that receive output from the current node
export function getConnectedTriggerToggleNodes(node) {
const connectedNodes = [];
if (node.outputs && node.outputs.length > 0) {
for (const output of node.outputs) {
if (output.links && output.links.length > 0) {
for (const linkId of output.links) {
const link = app.graph.links[linkId];
if (link) {
const targetNode = app.graph.getNodeById(link.target_id);
if (targetNode && targetNode.comfyClass === "TriggerWord Toggle (LoraManager)") {
connectedNodes.push(targetNode.id);
}
}
}
}
}
}
return connectedNodes;
}
// Extract active lora names from a node's widgets
export function getActiveLorasFromNode(node) {
const activeLoraNames = new Set();
// For lorasWidget style entries (array of objects)
if (node.lorasWidget && node.lorasWidget.value) {
node.lorasWidget.value.forEach(lora => {
if (lora.active) {
activeLoraNames.add(lora.name);
}
});
}
return activeLoraNames;
}
// Recursively collect all active loras from a node and its input chain
export function collectActiveLorasFromChain(node, visited = new Set()) {
// Prevent infinite loops from circular references
if (visited.has(node.id)) {
return new Set();
}
visited.add(node.id);
// Get active loras from current node
const allActiveLoraNames = getActiveLorasFromNode(node);
// Get connected input stackers and collect their active loras
const inputStackers = getConnectedInputStackers(node);
for (const stacker of inputStackers) {
const stackerLoras = collectActiveLorasFromChain(stacker, visited);
stackerLoras.forEach(name => allActiveLoraNames.add(name));
}
return allActiveLoraNames;
}
// Update trigger words for connected toggle nodes
export function updateConnectedTriggerWords(node, loraNames) {
const connectedNodeIds = getConnectedTriggerToggleNodes(node);
if (connectedNodeIds.length > 0 && loraNames.size > 0) {
fetch("/loramanager/get_trigger_words", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
lora_names: Array.from(loraNames),
node_ids: connectedNodeIds
})
}).catch(err => console.error("Error fetching trigger words:", err));
}
}