diff --git a/web/comfyui/lora_loader.js b/web/comfyui/lora_loader.js index 01e0a90b..cc76e2e6 100644 --- a/web/comfyui/lora_loader.js +++ b/web/comfyui/lora_loader.js @@ -1,61 +1,10 @@ import { app } from "../../scripts/app.js"; -import { dynamicImportByVersion } from "./utils.js"; - -// Update pattern to match both formats: or -const LORA_PATTERN = //g; - -// Function to get the appropriate loras widget based on ComfyUI version -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)); - } -} +import { + getLorasWidgetModule, + LORA_PATTERN, + collectActiveLorasFromChain, + updateConnectedTriggerWords +} from "./utils.js"; function mergeLoras(lorasText, lorasArr) { const result = []; @@ -107,9 +56,8 @@ app.registerExtension({ // Restore saved value if exists let existingLoras = []; 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) - // TODO: Need to be revisited when the issue is fixed. https://github.com/willmiao/ComfyUI-Lora-Manager/issues/176 - const savedValue = node.widgets_values[node.widgets_values.length - 1]; + // 0 for input widget, 1 for loras widget + const savedValue = node.widgets_values[1]; existingLoras = savedValue || []; } // Merge the loras data @@ -129,7 +77,7 @@ app.registerExtension({ // Prevent recursive calls if (isUpdating) return; isUpdating = true; - + try { // Remove loras that are not in the value array const inputWidget = node.widgets[0]; @@ -145,8 +93,11 @@ app.registerExtension({ inputWidget.value = newText; - // Add this line to update trigger words when lorasWidget changes cause inputWidget value to change - updateConnectedTriggerWords(node, newText); + // Collect all active loras from this node and its input chain + const allActiveLoraNames = collectActiveLorasFromChain(node); + + // Update trigger words for connected toggle nodes with the aggregated lora names + updateConnectedTriggerWords(node, allActiveLoraNames); } finally { isUpdating = false; } @@ -166,8 +117,11 @@ app.registerExtension({ node.lorasWidget.value = mergedLoras; - // Replace the existing trigger word update code with the new function - updateConnectedTriggerWords(node, value); + // Collect all active loras from this node and its input chain + const allActiveLoraNames = collectActiveLorasFromChain(node); + + // Update trigger words for connected toggle nodes with the aggregated lora names + updateConnectedTriggerWords(node, allActiveLoraNames); } finally { isUpdating = false; } diff --git a/web/comfyui/lora_stacker.js b/web/comfyui/lora_stacker.js index b97484a7..9388483a 100644 --- a/web/comfyui/lora_stacker.js +++ b/web/comfyui/lora_stacker.js @@ -1,57 +1,11 @@ import { app } from "../../scripts/app.js"; -import { dynamicImportByVersion } from "./utils.js"; - -// Update pattern to match both formats: or -const LORA_PATTERN = //g; - -// Function to get the appropriate loras widget based on ComfyUI version -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 = []; - - 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)); - } -} +import { + getLorasWidgetModule, + LORA_PATTERN, + getActiveLorasFromNode, + collectActiveLorasFromChain, + updateConnectedTriggerWords +} from "./utils.js"; function mergeLoras(lorasText, lorasArr) { const result = []; @@ -99,19 +53,9 @@ app.registerExtension({ // Restore saved value if exists let existingLoras = []; if (node.widgets_values && node.widgets_values.length > 0) { + // 0 for input widget, 1 for loras widget const savedValue = node.widgets_values[1]; - // TODO: clean up this code - 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 = []; - } + existingLoras = savedValue || []; } // Merge the loras data const mergedLoras = mergeLoras(node.widgets[0].value, existingLoras); @@ -145,8 +89,17 @@ app.registerExtension({ inputWidget.value = newText; - // Update trigger words when lorasWidget changes - updateConnectedTriggerWords(node, newText); + // Update this stacker's direct trigger toggles with its own active loras + 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 { isUpdating = false; } @@ -166,8 +119,12 @@ app.registerExtension({ node.lorasWidget.value = mergedLoras; - // Update trigger words when input changes - updateConnectedTriggerWords(node, value); + // Update this stacker's direct trigger toggles with its own active loras + const activeLoraNames = getActiveLorasFromNode(node); + updateConnectedTriggerWords(node, activeLoraNames); + + // Find all Lora Loader nodes in the chain that might need updates + updateDownstreamLoaders(node); } finally { isUpdating = false; } @@ -175,4 +132,34 @@ app.registerExtension({ }); } }, -}); \ No newline at end of file +}); + +// 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); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/web/comfyui/utils.js b/web/comfyui/utils.js index 8d9c1f6c..9fcebb7d 100644 --- a/web/comfyui/utils.js +++ b/web/comfyui/utils.js @@ -63,4 +63,106 @@ export class DataWrapper { setData(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: or +export const LORA_PATTERN = //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)); + } } \ No newline at end of file