diff --git a/py/nodes/lora_loader.py b/py/nodes/lora_loader.py index 60c91472..8a34f379 100644 --- a/py/nodes/lora_loader.py +++ b/py/nodes/lora_loader.py @@ -53,6 +53,8 @@ class LoraManagerLoader: """Loads multiple LoRAs based on the kwargs input.""" loaded_loras = [] all_trigger_words = [] + + print(f"kwargs: {kwargs}") if 'loras' in kwargs: for lora in kwargs['loras']: diff --git a/py/nodes/trigger_word_toggle.py b/py/nodes/trigger_word_toggle.py index 1d28ea3c..cddb660f 100644 --- a/py/nodes/trigger_word_toggle.py +++ b/py/nodes/trigger_word_toggle.py @@ -1,6 +1,6 @@ +import json from server import PromptServer # type: ignore from .utils import FlexibleOptionalInputType, any_type -import json class TriggerWordToggle: NAME = "TriggerWord Toggle (LoraManager)" @@ -29,13 +29,19 @@ class TriggerWordToggle: "id": id, "message": trigger_words }) + + print(f"kwargs: {kwargs}") filtered_triggers = trigger_words - if 'hidden_trigger_words' in kwargs: + if 'toggle_trigger_words' in kwargs: try: - # Parse the hidden trigger words JSON - trigger_data = json.loads(kwargs['hidden_trigger_words']) if isinstance(kwargs['hidden_trigger_words'], str) else kwargs['hidden_trigger_words'] + # Get trigger word toggle data + trigger_data = kwargs['toggle_trigger_words'] + + # Convert to list if it's a JSON string + if isinstance(trigger_data, str): + trigger_data = json.loads(trigger_data) # Create dictionaries to track active state of words active_state = {item['text']: item.get('active', False) for item in trigger_data} @@ -43,7 +49,7 @@ class TriggerWordToggle: # Split original trigger words original_words = [word.strip() for word in trigger_words.split(',')] - # Filter words: keep those not in hidden_trigger_words or those that are active + # Filter words: keep those not in toggle_trigger_words or those that are active filtered_words = [word for word in original_words if word not in active_state or active_state[word]] # Join them in the same format as input @@ -54,8 +60,5 @@ class TriggerWordToggle: except Exception as e: print(f"Error processing trigger words: {e}") - - for key, value in kwargs.items(): - print(f"{key}: {value}") return (filtered_triggers,) \ No newline at end of file diff --git a/web/comfyui/lora_loader.js b/web/comfyui/lora_loader.js index 22e739bb..58743edd 100644 --- a/web/comfyui/lora_loader.js +++ b/web/comfyui/lora_loader.js @@ -58,7 +58,7 @@ app.registerExtension({ const result = addLorasWidget(node, "loras", { defaultVal: mergedLoras // Pass object directly }, (value) => { - // TODO + console.log("Loras data updated:", value); }); node.lorasWidget = result.widget; @@ -71,7 +71,10 @@ app.registerExtension({ const mergedLoras = mergeLoras(value, currentLoras); node.lorasWidget.value = mergedLoras; + // node.graph.setDirtyCanvas(true, true); }; + + console.log("node: ", node); }); } }, diff --git a/web/comfyui/loras_widget.js b/web/comfyui/loras_widget.js index ee49f6ad..c634d51f 100644 --- a/web/comfyui/loras_widget.js +++ b/web/comfyui/loras_widget.js @@ -277,8 +277,8 @@ export function addLorasWidget(node, name, opts, callback) { widget.value = newValue; widget.callback?.(newValue); - // Re-render - renderLoras(newValue, widget); + // Remove re-render call - this is causing double callback + // renderLoras(newValue, widget); }); // Add label to toggle all @@ -342,8 +342,8 @@ export function addLorasWidget(node, name, opts, callback) { widget.value = newValue; widget.callback?.(newValue); - // Re-render - renderLoras(newValue, widget); + // Remove re-render call - this is causing double callback + // renderLoras(newValue, widget); } }); @@ -404,15 +404,15 @@ export function addLorasWidget(node, name, opts, callback) { const loraIndex = lorasData.findIndex(l => l.name === name); if (loraIndex >= 0) { - lorasData[loraIndex].strength = Math.max(0, lorasData[loraIndex].strength - 0.05).toFixed(2); + lorasData[loraIndex].strength = (lorasData[loraIndex].strength - 0.05).toFixed(2); // Update value and trigger widget callback const newValue = formatLoraValue(lorasData); widget.value = newValue; widget.callback?.(newValue); - // Re-render - renderLoras(newValue, widget); + // Remove re-render call - this is causing double callback + // renderLoras(newValue, widget); } }); @@ -478,8 +478,8 @@ export function addLorasWidget(node, name, opts, callback) { widget.value = newLorasValue; widget.callback?.(newLorasValue); - // 重新渲染 - renderLoras(newLorasValue, widget); + // Remove re-render call - this is causing double callback + // renderLoras(newLorasValue, widget); } }); @@ -504,8 +504,8 @@ export function addLorasWidget(node, name, opts, callback) { widget.value = newValue; widget.callback?.(newValue); - // Re-render - renderLoras(newValue, widget); + // Remove re-render call - this is causing double callback + // renderLoras(newValue, widget); } }); @@ -559,10 +559,10 @@ export function addLorasWidget(node, name, opts, callback) { widget.callback = callback; - widget.computeSize = (width, height) => { - // console.log("loras_widget computeSize called: ", width, height); - return [400, 200]; - }; + // widget.computeSize = (width) => { + // // console.log("loras_widget computeSize called: ", width, height); + // return [400, 200]; + // }; // Render initial state renderLoras(widgetValue, widget); diff --git a/web/comfyui/tags_widget.js b/web/comfyui/tags_widget.js index b2f41278..7899457a 100644 --- a/web/comfyui/tags_widget.js +++ b/web/comfyui/tags_widget.js @@ -13,38 +13,50 @@ export function addTagsWidget(node, name, opts, callback) { width: "100%", }); - // Initialize default value - const defaultValue = opts?.defaultVal || "[]"; + // Initialize default value as array + const defaultValue = opts?.defaultVal || []; + let initialTagsData = []; + + try { + // Convert string input to array if needed + initialTagsData = typeof defaultValue === 'string' ? + JSON.parse(defaultValue) : (Array.isArray(defaultValue) ? defaultValue : []); + } catch (e) { + console.warn("Invalid default tags data format", e); + } - // Parse trigger words and states from string - const parseTagsValue = (value) => { - if (!value) return []; - - try { - return JSON.parse(value); - } catch (e) { - // If it's not valid JSON, try legacy format or return empty array - console.warn("Invalid tags data format", e); - return []; - } + // Normalize tag data to ensure consistent format + const normalizeTagData = (data) => { + if (!Array.isArray(data)) return []; + + return data.map(item => { + // If it's already in the correct format, return as is + if (item && typeof item === 'object' && 'text' in item) { + return { + text: item.text, + active: item.active !== undefined ? item.active : true + }; + } + // If it's just a string, convert to object + else if (typeof item === 'string') { + return { text: item, active: true }; + } + // Default fallback + return { text: String(item), active: true }; + }); }; - // Format tags data back to string - const formatTagsValue = (tagsData) => { - return JSON.stringify(tagsData); - }; - - // Function to render tags from data - const renderTags = (value, widget) => { + // Function to render tags from array data + const renderTags = (tagsData, widget) => { // Clear existing tags while (container.firstChild) { container.removeChild(container.firstChild); } - // Parse the tags data - const tagsData = parseTagsValue(value); + // Ensure we're working with normalized data + const normalizedTags = normalizeTagData(tagsData); - tagsData.forEach((tagData) => { + normalizedTags.forEach((tagData) => { const { text, active } = tagData; const tagEl = document.createElement("div"); tagEl.className = "comfy-tag"; @@ -59,17 +71,16 @@ export function addTagsWidget(node, name, opts, callback) { e.stopPropagation(); // Toggle active state for this tag - const tagsData = parseTagsValue(widget.value); - const tagIndex = tagsData.findIndex((t) => t.text === text); + const updatedTags = [...widget.value]; + const tagIndex = updatedTags.findIndex((t) => t.text === text); if (tagIndex >= 0) { - tagsData[tagIndex].active = !tagsData[tagIndex].active; - updateTagStyle(tagEl, tagsData[tagIndex].active); + updatedTags[tagIndex].active = !updatedTags[tagIndex].active; + updateTagStyle(tagEl, updatedTags[tagIndex].active); - // Update value and trigger widget callback - const newValue = formatTagsValue(tagsData); - widget.value = newValue; - widget.callback?.(newValue); + // Update widget value and trigger callback + widget.value = updatedTags; + widget.callback?.(updatedTags); } }); @@ -123,8 +134,8 @@ export function addTagsWidget(node, name, opts, callback) { }; } - // Store the value in a variable to avoid recursion - let widgetValue = defaultValue; + // Store the value as array + let widgetValue = normalizeTagData(initialTagsData); // Create widget with initial properties const widget = node.addDOMWidget(name, "tags", container, { @@ -132,43 +143,43 @@ export function addTagsWidget(node, name, opts, callback) { return widgetValue; }, setValue: function(v) { - // Format the incoming value if it's not in the expected JSON format - let parsedValue = v; - + // Handle input formats but always normalize to array try { - // Try to parse as JSON first - if (typeof v === "string" && (v.startsWith("[") || v.startsWith("{"))) { - JSON.parse(v); - // If no error, it's already valid JSON - parsedValue = v; - } else if (typeof v === "string") { - // If it's a comma-separated string of trigger words, convert to tag format - const triggerWords = v - .split(",") - .map((word) => word.trim()) - .filter((word) => word); + if (typeof v === "string") { + // If JSON string, parse it + if (v.startsWith("[") || v.startsWith("{")) { + const parsed = JSON.parse(v); + widgetValue = normalizeTagData(parsed); + } else { + // If it's a comma-separated string of tags + const tagStrings = v + .split(",") + .map((word) => word.trim()) + .filter((word) => word); - // Get existing tags to merge with new ones - const existingTags = parseTagsValue(widgetValue || "[]"); - const existingTagsMap = {}; - existingTags.forEach((tag) => { - existingTagsMap[tag.text] = tag.active; - }); + // Preserve active states from existing tags where possible + const existingTagsMap = {}; + widgetValue.forEach((tag) => { + existingTagsMap[tag.text] = tag.active; + }); - // Create new tags with merging logic - const newTags = triggerWords.map((word) => ({ - text: word, - active: word in existingTagsMap ? existingTagsMap[word] : true, - })); - - parsedValue = JSON.stringify(newTags); + widgetValue = tagStrings.map((text) => ({ + text, + active: text in existingTagsMap ? existingTagsMap[text] : true, + })); + } + } else if (Array.isArray(v)) { + // Directly use array input but ensure proper format + widgetValue = normalizeTagData(v); + } else { + // Default to empty array for invalid inputs + widgetValue = []; } } catch (e) { console.warn("Error formatting tags value:", e); - // Keep the original value if there's an error + // Keep existing value if there's an error } - widgetValue = parsedValue || ""; // Store in our local variable instead renderTags(widgetValue, widget); }, getHeight: function() { @@ -187,10 +198,6 @@ export function addTagsWidget(node, name, opts, callback) { widget.options.setValue(defaultValue); widget.callback = callback; - widget.serializeValue = function(workflowNode, widgetIndex) { - console.log("Serializing tags widget", widget.value); - return widget.value; - }; // Render initial state renderTags(widgetValue, widget); diff --git a/web/comfyui/trigger_word_toggle.js b/web/comfyui/trigger_word_toggle.js index 579bd3e4..1253b5f4 100644 --- a/web/comfyui/trigger_word_toggle.js +++ b/web/comfyui/trigger_word_toggle.js @@ -1,7 +1,6 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; import { addTagsWidget } from "./tags_widget.js"; -import { hideWidgetForGood } from "./utils.js"; // TriggerWordToggle extension for ComfyUI app.registerExtension({ @@ -19,22 +18,14 @@ app.registerExtension({ if (node.comfyClass === "TriggerWord Toggle (LoraManager)") { // Enable widget serialization node.serialize_widgets = true; - node.size = [400, 200]; // Wait for node to be properly initialized - requestAnimationFrame(() => { - // add a hidden widget for excluded trigger words to send to Python - node.hiddenWidget = node.addWidget("text", "hidden_trigger_words", "", (value) => { - // empty callback - }); - hideWidgetForGood(node, node.hiddenWidget); - + requestAnimationFrame(() => { // Get the widget object directly from the returned object - const result = addTagsWidget(node, "trigger_words", { - defaultVal: "[]" + const result = addTagsWidget(node, "toggle_trigger_words", { + defaultVal: [] }, (value) => { - // update value of hidden widget - node.hiddenWidget.value = value; + }); node.tagWidget = result.widget; @@ -42,13 +33,11 @@ app.registerExtension({ // Restore saved value if exists if (node.widgets_values && node.widgets_values.length > 0) { // 0 is input, 1 is hidden widget, 2 is tag widget - const savedValue = node.widgets_values[2]; + const savedValue = node.widgets_values[1]; if (savedValue) { result.widget.value = savedValue; } } - - console.log("trigger word toggle node: ", node); }); } }, @@ -68,8 +57,40 @@ app.registerExtension({ } if (node.tagWidget) { - // Use widget.value setter instead of setValue - node.tagWidget.value = message; + // Convert comma-separated message to tag object format + if (typeof message === 'string') { + // Get existing tags to preserve active states + const existingTags = node.tagWidget.value || []; + + const tempWidget = node.tagWidget; + console.log("height of node: ", node.size[1]); + // console.log("tempWidget: ", tempWidget); + console.log("tagWidget height: ", tempWidget.options.getHeight()); + + // Create a map of existing tags and their active states + const existingTagMap = {}; + existingTags.forEach(tag => { + existingTagMap[tag.text] = tag.active; + }); + + // Process the incoming message + const tagArray = message + .split(',') + .map(word => word.trim()) + .filter(word => word) + .map(word => ({ + text: word, + // Keep previous active state if exists, otherwise default to true + active: existingTagMap[word] !== undefined ? existingTagMap[word] : true + })); + + node.tagWidget.value = tagArray; + console.log("tagWidget new height: ", tempWidget.options.getHeight()); + const computed = node.computeSize(); + node.size[1] = computed[1]; + console.log("computed height: ", computed[1]); + node.setDirtyCanvas(true, true); + } } }, });