From 99463ad01cd6111eb1afe97733566eb9e4343697 Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Thu, 8 May 2025 20:16:25 +0800 Subject: [PATCH 1/3] refactor(import-modal): remove outdated duplicate styles and clean up modal button layout --- static/css/components/import-modal.css | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/static/css/components/import-modal.css b/static/css/components/import-modal.css index 86bbf33a..a876e344 100644 --- a/static/css/components/import-modal.css +++ b/static/css/components/import-modal.css @@ -880,29 +880,3 @@ align-items: center; gap: 4px; } - -/* Remove the old duplicate styles that are no longer needed */ -.duplicate-recipe-item, -.duplicate-recipe-content, -.duplicate-recipe-actions, -.danger-btn, -.view-recipe-btn { - /* These styles are being replaced by the card layout */ -} - -/* Modal buttons layout to accommodate multiple buttons */ -.modal-actions { - display: flex; - justify-content: space-between; - gap: 10px; - margin-top: var(--space-3); -} - -.modal-actions button { - flex: 1; - white-space: nowrap; - display: flex; - align-items: center; - justify-content: center; - gap: 6px; -} From 9169bbd04d815fa6e2c405664f0ecb67e2fdaff2 Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Thu, 8 May 2025 20:25:26 +0800 Subject: [PATCH 2/3] refactor(widget-serialization): remove dummy items from serialization which was a fix to ComfyUI issues --- web/comfyui/loras_widget.js | 6 +----- web/comfyui/tags_widget.js | 7 +------ 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/web/comfyui/loras_widget.js b/web/comfyui/loras_widget.js index 242d37ed..2487c612 100644 --- a/web/comfyui/loras_widget.js +++ b/web/comfyui/loras_widget.js @@ -948,11 +948,7 @@ export function addLorasWidget(node, name, opts, callback) { widget.callback = callback; widget.serializeValue = () => { - // Add dummy items to avoid the 2-element serialization issue, a bug in comfyui - return [...widgetValue, - { name: "__dummy_item1__", strength: 0, active: false, _isDummy: true }, - { name: "__dummy_item2__", strength: 0, active: false, _isDummy: true } - ]; + return widgetValue; } widget.onRemove = () => { diff --git a/web/comfyui/tags_widget.js b/web/comfyui/tags_widget.js index cb16c1a6..eec0755b 100644 --- a/web/comfyui/tags_widget.js +++ b/web/comfyui/tags_widget.js @@ -220,13 +220,8 @@ export function addTagsWidget(node, name, opts, callback) { // Set callback widget.callback = callback; - // Add serialization method to avoid ComfyUI serialization issues widget.serializeValue = () => { - // Add dummy items to avoid the 2-element serialization issue - return [...widgetValue, - { text: "__dummy_item__", active: false, _isDummy: true }, - { text: "__dummy_item__", active: false, _isDummy: true } - ]; + return widgetValue }; return { minWidth: 300, minHeight: defaultHeight, widget }; From 969f9493309ea4c2f4da39736d6ec7d1c0302e93 Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Fri, 9 May 2025 11:05:59 +0800 Subject: [PATCH 3/3] refactor(lora-loader, lora-stacker, loras-widget): enhance handling of model and clip strengths; update formatting and UI interactions. Fixes https://github.com/willmiao/ComfyUI-Lora-Manager/issues/171 --- py/nodes/lora_loader.py | 48 ++++-- py/nodes/lora_stacker.py | 22 ++- web/comfyui/lora_loader.js | 31 ++-- web/comfyui/lora_stacker.js | 16 +- web/comfyui/loras_widget.js | 298 ++++++++++++++++++++++++++++++++++-- 5 files changed, 362 insertions(+), 53 deletions(-) diff --git a/py/nodes/lora_loader.py b/py/nodes/lora_loader.py index ab5395c8..66de53ed 100644 --- a/py/nodes/lora_loader.py +++ b/py/nodes/lora_loader.py @@ -1,10 +1,7 @@ import logging from nodes import LoraLoader from comfy.comfy_types import IO # type: ignore -from ..services.lora_scanner import LoraScanner -from ..config import config import asyncio -import os from .utils import FlexibleOptionalInputType, any_type, get_lora_info, extract_lora_name, get_loras_list logger = logging.getLogger(__name__) @@ -51,23 +48,35 @@ class LoraManagerLoader: _, trigger_words = asyncio.run(get_lora_info(lora_name)) all_trigger_words.extend(trigger_words) - loaded_loras.append(f"{lora_name}: {model_strength}") + # Add clip strength to output if different from model strength + if abs(model_strength - clip_strength) > 0.001: + loaded_loras.append(f"{lora_name}: {model_strength},{clip_strength}") + else: + loaded_loras.append(f"{lora_name}: {model_strength}") # Then process loras from kwargs with support for both old and new formats loras_list = get_loras_list(kwargs) + print(f"Loaded loras list: {loras_list}") for lora in loras_list: if not lora.get('active', False): continue lora_name = lora['name'] - strength = float(lora['strength']) + model_strength = float(lora['strength']) + # Get clip strength - use model strength as default if not specified + clip_strength = float(lora.get('clipStrength', model_strength)) # Get lora path and trigger words lora_path, trigger_words = asyncio.run(get_lora_info(lora_name)) - # Apply the LoRA using the resolved path - model, clip = LoraLoader().load_lora(model, clip, lora_path, strength, strength) - loaded_loras.append(f"{lora_name}: {strength}") + # Apply the LoRA using the resolved path with separate strengths + model, clip = LoraLoader().load_lora(model, clip, lora_path, model_strength, clip_strength) + + # Include clip strength in output if different from model strength + if abs(model_strength - clip_strength) > 0.001: + loaded_loras.append(f"{lora_name}: {model_strength},{clip_strength}") + else: + loaded_loras.append(f"{lora_name}: {model_strength}") # Add trigger words to collection all_trigger_words.extend(trigger_words) @@ -75,8 +84,23 @@ class LoraManagerLoader: # use ',, ' to separate trigger words for group mode trigger_words_text = ",, ".join(all_trigger_words) if all_trigger_words else "" - # Format loaded_loras as separated by spaces - formatted_loras = " ".join([f"" - for name, strength in [item.split(':') for item in loaded_loras]]) + # Format loaded_loras with support for both formats + formatted_loras = [] + for item in loaded_loras: + parts = item.split(":") + lora_name = parts[0].strip() + strength_parts = parts[1].strip().split(",") + + if len(strength_parts) > 1: + # Different model and clip strengths + model_str = strength_parts[0].strip() + clip_str = strength_parts[1].strip() + formatted_loras.append(f"") + else: + # Same strength for both + model_str = strength_parts[0].strip() + formatted_loras.append(f"") + + formatted_loras_text = " ".join(formatted_loras) - return (model, clip, trigger_words_text, formatted_loras) \ No newline at end of file + return (model, clip, trigger_words_text, formatted_loras_text) \ No newline at end of file diff --git a/py/nodes/lora_stacker.py b/py/nodes/lora_stacker.py index 7f0a015b..9d87e13d 100644 --- a/py/nodes/lora_stacker.py +++ b/py/nodes/lora_stacker.py @@ -38,7 +38,7 @@ class LoraStacker: # Process existing lora_stack if available lora_stack = kwargs.get('lora_stack', None) - if lora_stack: + if (lora_stack): stack.extend(lora_stack) # Get trigger words from existing stack entries for lora_path, _, _ in lora_stack: @@ -54,7 +54,8 @@ class LoraStacker: lora_name = lora['name'] model_strength = float(lora['strength']) - clip_strength = model_strength # Using same strength for both as in the original loader + # Get clip strength - use model strength as default if not specified + clip_strength = float(lora.get('clipStrength', model_strength)) # Get lora path and trigger words lora_path, trigger_words = asyncio.run(get_lora_info(lora_name)) @@ -62,15 +63,24 @@ class LoraStacker: # Add to stack without loading # replace '/' with os.sep to avoid different OS path format stack.append((lora_path.replace('/', os.sep), model_strength, clip_strength)) - active_loras.append((lora_name, model_strength)) + active_loras.append((lora_name, model_strength, clip_strength)) # Add trigger words to collection all_trigger_words.extend(trigger_words) # use ',, ' to separate trigger words for group mode trigger_words_text = ",, ".join(all_trigger_words) if all_trigger_words else "" - # Format active_loras as separated by spaces - active_loras_text = " ".join([f"" - for name, strength in active_loras]) + + # Format active_loras with support for both formats + formatted_loras = [] + for name, model_strength, clip_strength in active_loras: + if abs(model_strength - clip_strength) > 0.001: + # Different model and clip strengths + formatted_loras.append(f"") + else: + # Same strength for both + formatted_loras.append(f"") + + active_loras_text = " ".join(formatted_loras) return (stack, trigger_words_text, active_loras_text) diff --git a/web/comfyui/lora_loader.js b/web/comfyui/lora_loader.js index 90f1295e..f2e7d4a6 100644 --- a/web/comfyui/lora_loader.js +++ b/web/comfyui/lora_loader.js @@ -1,8 +1,8 @@ import { app } from "../../scripts/app.js"; import { dynamicImportByVersion } from "./utils.js"; -// Extract pattern into a constant for consistent use -const LORA_PATTERN = //g; +// 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() { @@ -61,10 +61,15 @@ function mergeLoras(lorasText, lorasArr) { const result = []; let match; + // Reset pattern index before using + LORA_PATTERN.lastIndex = 0; + // Parse text input and create initial entries while ((match = LORA_PATTERN.exec(lorasText)) !== null) { const name = match[1]; - const inputStrength = Number(match[2]); + const modelStrength = Number(match[2]); + // Extract clip strength if provided, otherwise use model strength + const clipStrength = match[3] ? Number(match[3]) : modelStrength; // Find if this lora exists in the array data const existingLora = lorasArr.find(l => l.name === name); @@ -72,8 +77,9 @@ function mergeLoras(lorasText, lorasArr) { result.push({ name: name, // Use existing strength if available, otherwise use input strength - strength: existingLora ? existingLora.strength : inputStrength, - active: existingLora ? existingLora.active : true + strength: existingLora ? existingLora.strength : modelStrength, + active: existingLora ? existingLora.active : true, + clipStrength: existingLora ? existingLora.clipStrength : clipStrength, }); } @@ -102,18 +108,7 @@ app.registerExtension({ let existingLoras = []; if (node.widgets_values && node.widgets_values.length > 0) { 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); @@ -139,7 +134,7 @@ app.registerExtension({ const currentLoras = value.map(l => l.name); // Use the constant pattern here as well - let newText = inputWidget.value.replace(LORA_PATTERN, (match, name, strength) => { + let newText = inputWidget.value.replace(LORA_PATTERN, (match, name, strength, clipStrength) => { return currentLoras.includes(name) ? match : ''; }); diff --git a/web/comfyui/lora_stacker.js b/web/comfyui/lora_stacker.js index a22a6062..b97484a7 100644 --- a/web/comfyui/lora_stacker.js +++ b/web/comfyui/lora_stacker.js @@ -1,8 +1,8 @@ import { app } from "../../scripts/app.js"; import { dynamicImportByVersion } from "./utils.js"; -// Extract pattern into a constant for consistent use -const LORA_PATTERN = //g; +// 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() { @@ -57,10 +57,15 @@ function mergeLoras(lorasText, lorasArr) { const result = []; let match; + // Reset pattern index before using + LORA_PATTERN.lastIndex = 0; + // Parse text input and create initial entries while ((match = LORA_PATTERN.exec(lorasText)) !== null) { const name = match[1]; - const inputStrength = Number(match[2]); + const modelStrength = Number(match[2]); + // Extract clip strength if provided, otherwise use model strength + const clipStrength = match[3] ? Number(match[3]) : modelStrength; // Find if this lora exists in the array data const existingLora = lorasArr.find(l => l.name === name); @@ -68,8 +73,9 @@ function mergeLoras(lorasText, lorasArr) { result.push({ name: name, // Use existing strength if available, otherwise use input strength - strength: existingLora ? existingLora.strength : inputStrength, - active: existingLora ? existingLora.active : true + strength: existingLora ? existingLora.strength : modelStrength, + active: existingLora ? existingLora.active : true, + clipStrength: existingLora ? existingLora.clipStrength : clipStrength, }); } diff --git a/web/comfyui/loras_widget.js b/web/comfyui/loras_widget.js index 2487c612..05283348 100644 --- a/web/comfyui/loras_widget.js +++ b/web/comfyui/loras_widget.js @@ -29,10 +29,13 @@ export function addLorasWidget(node, name, opts, callback) { // Fixed sizes for component calculations const LORA_ENTRY_HEIGHT = 40; // Height of a single lora entry + const CLIP_ENTRY_HEIGHT = 40; // Height of a clip entry const HEADER_HEIGHT = 40; // Height of the header section const CONTAINER_PADDING = 12; // Top and bottom padding const EMPTY_CONTAINER_HEIGHT = 100; // Height when no loras are present + // Remove expandedClipEntries Set since we'll determine expansion based on strength values + // Parse LoRA entries from value const parseLoraValue = (value) => { if (!value) return []; @@ -367,7 +370,7 @@ export function addLorasWidget(node, name, opts, callback) { }; // Function to handle strength adjustment via dragging - const handleStrengthDrag = (name, initialStrength, initialX, event, widget) => { + const handleStrengthDrag = (name, initialStrength, initialX, event, widget, isClipStrength = false) => { // Calculate drag sensitivity (how much the strength changes per pixel) // Using 0.01 per 10 pixels of movement const sensitivity = 0.001; @@ -391,7 +394,12 @@ export function addLorasWidget(node, name, opts, callback) { const loraIndex = lorasData.findIndex(l => l.name === name); if (loraIndex >= 0) { - lorasData[loraIndex].strength = newStrength; + // Update the appropriate strength property based on isClipStrength flag + if (isClipStrength) { + lorasData[loraIndex].clipStrength = newStrength; + } else { + lorasData[loraIndex].strength = newStrength; + } // Update the widget value widget.value = formatLoraValue(lorasData); @@ -402,7 +410,7 @@ export function addLorasWidget(node, name, opts, callback) { }; // Function to initialize drag operation - const initDrag = (loraEl, nameEl, name, widget) => { + const initDrag = (dragEl, name, widget, isClipStrength = false) => { let isDragging = false; let initialX = 0; let initialStrength = 0; @@ -420,9 +428,8 @@ export function addLorasWidget(node, name, opts, callback) { document.head.appendChild(styleEl); } - // Create a drag handler that's applied to the entire lora entry - // except toggle and strength controls - loraEl.addEventListener('mousedown', (e) => { + // Create a drag handler + dragEl.addEventListener('mousedown', (e) => { // Skip if clicking on toggle or strength control areas if (e.target.closest('.comfy-lora-toggle') || e.target.closest('input') || @@ -437,7 +444,7 @@ export function addLorasWidget(node, name, opts, callback) { if (!loraData) return; initialX = e.clientX; - initialStrength = loraData.strength; + initialStrength = isClipStrength ? loraData.clipStrength : loraData.strength; isDragging = true; // Add class to body to enforce cursor style globally @@ -453,7 +460,7 @@ export function addLorasWidget(node, name, opts, callback) { if (!isDragging) return; // Call the strength adjustment function - handleStrengthDrag(name, initialStrength, initialX, e, widget); + handleStrengthDrag(name, initialStrength, initialX, e, widget, isClipStrength); // Prevent showing the preview tooltip during drag previewTooltip.hide(); @@ -691,10 +698,17 @@ export function addLorasWidget(node, name, opts, callback) { header.appendChild(strengthLabel); container.appendChild(header); + // Track the total visible entries for height calculation + let totalVisibleEntries = lorasData.length; + // Render each lora entry lorasData.forEach((loraData) => { - const { name, strength, active } = loraData; + const { name, strength, clipStrength, active } = loraData; + // Determine expansion state using our helper function + const isExpanded = shouldShowClipEntry(loraData); + + // Create the main LoRA entry const loraEl = document.createElement("div"); loraEl.className = "comfy-lora-entry"; Object.assign(loraEl.style, { @@ -708,6 +722,41 @@ export function addLorasWidget(node, name, opts, callback) { marginBottom: "4px", }); + // Add double-click handler to toggle clip entry + loraEl.addEventListener('dblclick', (e) => { + // Skip if clicking on toggle or strength control areas + if (e.target.closest('.comfy-lora-toggle') || + e.target.closest('input') || + e.target.closest('.comfy-lora-arrow')) { + return; + } + + // Prevent default behavior + e.preventDefault(); + e.stopPropagation(); + + // Toggle the clip entry expanded state + const lorasData = parseLoraValue(widget.value); + const loraIndex = lorasData.findIndex(l => l.name === name); + + if (loraIndex >= 0) { + // Explicitly toggle the expansion state + const currentExpanded = shouldShowClipEntry(lorasData[loraIndex]); + lorasData[loraIndex].expanded = !currentExpanded; + + // If collapsing, set clipStrength = strength + if (!lorasData[loraIndex].expanded) { + lorasData[loraIndex].clipStrength = lorasData[loraIndex].strength; + } + + // Update the widget value + widget.value = formatLoraValue(lorasData); + + // Re-render to show/hide clip entry + renderLoras(widget.value, widget); + } + }); + // Create toggle for this lora const toggle = createToggle(active, (newActive) => { // Update this lora's active state @@ -740,6 +789,14 @@ export function addLorasWidget(node, name, opts, callback) { msUserSelect: "none", }); + // Add expand indicator to name element + const expandIndicator = document.createElement("span"); + expandIndicator.textContent = isExpanded ? " ▼" : " ▶"; + expandIndicator.style.opacity = "0.7"; + expandIndicator.style.fontSize = "9px"; + expandIndicator.style.marginLeft = "4px"; + nameEl.appendChild(expandIndicator); + // Move preview tooltip events to nameEl instead of loraEl nameEl.addEventListener('mouseenter', async (e) => { e.stopPropagation(); @@ -753,7 +810,7 @@ export function addLorasWidget(node, name, opts, callback) { }); // Initialize drag functionality for strength adjustment - initDrag(loraEl, nameEl, name, widget); + initDrag(loraEl, name, widget, false); // Remove the preview tooltip events from loraEl loraEl.onmouseenter = () => { @@ -897,10 +954,185 @@ export function addLorasWidget(node, name, opts, callback) { loraEl.appendChild(strengthControl); container.appendChild(loraEl); + + // If expanded, show the clip entry + if (isExpanded) { + totalVisibleEntries++; + const clipEl = document.createElement("div"); + clipEl.className = "comfy-lora-clip-entry"; + Object.assign(clipEl.style, { + display: "flex", + justifyContent: "space-between", + alignItems: "center", + padding: "6px", + paddingLeft: "20px", // Indent to align with parent name + borderRadius: "6px", + backgroundColor: active ? "rgba(65, 70, 90, 0.6)" : "rgba(50, 55, 65, 0.5)", + borderLeft: "2px solid rgba(72, 118, 255, 0.6)", + transition: "all 0.2s ease", + marginBottom: "4px", + marginLeft: "10px", + marginTop: "-2px" + }); + + // Create clip name display + const clipNameEl = document.createElement("div"); + clipNameEl.textContent = "[clip] " + name; + Object.assign(clipNameEl.style, { + flex: "1", + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap", + color: active ? "rgba(200, 215, 240, 0.9)" : "rgba(200, 215, 240, 0.6)", + fontSize: "13px", + userSelect: "none", + WebkitUserSelect: "none", + MozUserSelect: "none", + msUserSelect: "none", + }); + + // Create clip strength control + const clipStrengthControl = document.createElement("div"); + Object.assign(clipStrengthControl.style, { + display: "flex", + alignItems: "center", + gap: "8px", + }); + + // Left arrow for clip + const clipLeftArrow = createArrowButton("left", () => { + // Decrease clip strength + const lorasData = parseLoraValue(widget.value); + const loraIndex = lorasData.findIndex(l => l.name === name); + + if (loraIndex >= 0) { + lorasData[loraIndex].clipStrength = (parseFloat(lorasData[loraIndex].clipStrength) - 0.05).toFixed(2); + + const newValue = formatLoraValue(lorasData); + widget.value = newValue; + } + }); + + // Clip strength display + const clipStrengthEl = document.createElement("input"); + clipStrengthEl.type = "text"; + clipStrengthEl.value = typeof clipStrength === 'number' ? clipStrength.toFixed(2) : Number(clipStrength).toFixed(2); + Object.assign(clipStrengthEl.style, { + minWidth: "50px", + width: "50px", + textAlign: "center", + color: active ? "rgba(200, 215, 240, 0.9)" : "rgba(200, 215, 240, 0.6)", + fontSize: "13px", + background: "none", + border: "1px solid transparent", + padding: "2px 4px", + borderRadius: "3px", + outline: "none", + }); + + // Add hover effect + clipStrengthEl.addEventListener('mouseenter', () => { + clipStrengthEl.style.border = "1px solid rgba(226, 232, 240, 0.2)"; + }); + + clipStrengthEl.addEventListener('mouseleave', () => { + if (document.activeElement !== clipStrengthEl) { + clipStrengthEl.style.border = "1px solid transparent"; + } + }); + + // Handle focus + clipStrengthEl.addEventListener('focus', () => { + clipStrengthEl.style.border = "1px solid rgba(72, 118, 255, 0.6)"; + clipStrengthEl.style.background = "rgba(0, 0, 0, 0.2)"; + // Auto-select all content + clipStrengthEl.select(); + }); + + clipStrengthEl.addEventListener('blur', () => { + clipStrengthEl.style.border = "1px solid transparent"; + clipStrengthEl.style.background = "none"; + }); + + // Handle input changes + clipStrengthEl.addEventListener('change', () => { + let newValue = parseFloat(clipStrengthEl.value); + + // Validate input + if (isNaN(newValue)) { + newValue = 1.0; + } + + // Update value + const lorasData = parseLoraValue(widget.value); + const loraIndex = lorasData.findIndex(l => l.name === name); + + if (loraIndex >= 0) { + lorasData[loraIndex].clipStrength = newValue.toFixed(2); + + // Update value and trigger callback + const newLorasValue = formatLoraValue(lorasData); + widget.value = newLorasValue; + } + }); + + // Handle key events + clipStrengthEl.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + clipStrengthEl.blur(); + } + }); + + // Right arrow for clip + const clipRightArrow = createArrowButton("right", () => { + // Increase clip strength + const lorasData = parseLoraValue(widget.value); + const loraIndex = lorasData.findIndex(l => l.name === name); + + if (loraIndex >= 0) { + lorasData[loraIndex].clipStrength = (parseFloat(lorasData[loraIndex].clipStrength) + 0.05).toFixed(2); + + const newValue = formatLoraValue(lorasData); + widget.value = newValue; + } + }); + + clipStrengthControl.appendChild(clipLeftArrow); + clipStrengthControl.appendChild(clipStrengthEl); + clipStrengthControl.appendChild(clipRightArrow); + + // Assemble clip entry + const clipLeftSection = document.createElement("div"); + Object.assign(clipLeftSection.style, { + display: "flex", + alignItems: "center", + flex: "1", + minWidth: "0", // Allow shrinking + }); + + clipLeftSection.appendChild(clipNameEl); + + clipEl.appendChild(clipLeftSection); + clipEl.appendChild(clipStrengthControl); + + // Hover effects for clip entry + clipEl.onmouseenter = () => { + clipEl.style.backgroundColor = active ? "rgba(70, 75, 95, 0.7)" : "rgba(55, 60, 70, 0.6)"; + }; + + clipEl.onmouseleave = () => { + clipEl.style.backgroundColor = active ? "rgba(65, 70, 90, 0.6)" : "rgba(50, 55, 65, 0.5)"; + }; + + // Add drag functionality to clip entry + initDrag(clipEl, name, widget, true); + + container.appendChild(clipEl); + } }); // Calculate height based on number of loras and fixed sizes - const calculatedHeight = CONTAINER_PADDING + HEADER_HEIGHT + (Math.min(lorasData.length, 5) * LORA_ENTRY_HEIGHT); + const calculatedHeight = CONTAINER_PADDING + HEADER_HEIGHT + (Math.min(totalVisibleEntries, 8) * LORA_ENTRY_HEIGHT); updateWidgetHeight(calculatedHeight); }; @@ -921,7 +1153,37 @@ export function addLorasWidget(node, name, opts, callback) { return [...filtered, lora]; }, []); - widgetValue = uniqueValue; + // Preserve clip strengths and expanded state when updating the value + const oldLoras = parseLoraValue(widgetValue); + + // Apply existing clip strength values and transfer them to the new value + const updatedValue = uniqueValue.map(lora => { + const existingLora = oldLoras.find(oldLora => oldLora.name === lora.name); + + // If there's an existing lora with the same name, preserve its clip strength and expanded state + if (existingLora) { + return { + ...lora, + clipStrength: existingLora.clipStrength || lora.strength, + expanded: existingLora.hasOwnProperty('expanded') ? + existingLora.expanded : + Number(existingLora.clipStrength || lora.strength) !== Number(lora.strength) + }; + } + + // For new loras, default clip strength to model strength and expanded to false + // unless clipStrength is already different from strength + const clipStrength = lora.clipStrength || lora.strength; + return { + ...lora, + clipStrength: clipStrength, + expanded: lora.hasOwnProperty('expanded') ? + lora.expanded : + Number(clipStrength) !== Number(lora.strength) + }; + }); + + widgetValue = updatedValue; renderLoras(widgetValue, widget); }, getMinHeight: function() { @@ -962,6 +1224,8 @@ export function addLorasWidget(node, name, opts, callback) { // Function to directly save the recipe without dialog async function saveRecipeDirectly(widget) { try { + const prompt = await app.graphToPrompt(); + console.log(prompt); // Show loading toast if (app && app.extensionManager && app.extensionManager.toast) { app.extensionManager.toast.add({ @@ -1010,4 +1274,14 @@ async function saveRecipeDirectly(widget) { }); } } +} + +// Determine if clip entry should be shown - now based on expanded property or initial diff values +const shouldShowClipEntry = (loraData) => { + // If expanded property exists, use that + if (loraData.hasOwnProperty('expanded')) { + return loraData.expanded; + } + // Otherwise use the legacy logic - if values differ, it should be expanded + return Number(loraData.strength) !== Number(loraData.clipStrength); } \ No newline at end of file