diff --git a/web/comfyui/lora_loader.js b/web/comfyui/lora_loader.js index 383b84d9..850abff5 100644 --- a/web/comfyui/lora_loader.js +++ b/web/comfyui/lora_loader.js @@ -58,7 +58,19 @@ app.registerExtension({ const result = addLorasWidget(node, "loras", { defaultVal: mergedLoras // Pass object directly }, (value) => { - console.log("Loras data updated:", value); + // Remove loras that are not in the value array + const inputWidget = node.widgets[0]; + const pattern = //g; + const currentLoras = value.map(l => l.name); + + let newText = inputWidget.value.replace(pattern, (match, name, strength) => { + return currentLoras.includes(name) ? match : ''; + }); + + // Clean up multiple spaces and trim + newText = newText.replace(/\s+/g, ' ').trim(); + + inputWidget.value = newText; }); node.lorasWidget = result.widget; diff --git a/web/comfyui/loras_widget.js b/web/comfyui/loras_widget.js index d2c72f93..f99ef6fd 100644 --- a/web/comfyui/loras_widget.js +++ b/web/comfyui/loras_widget.js @@ -229,6 +229,130 @@ export function addLorasWidget(node, name, opts, callback) { // 创建预览tooltip实例 const previewTooltip = new PreviewTooltip(); + // Function to create menu item + const createMenuItem = (text, icon, onClick) => { + const menuItem = document.createElement('div'); + Object.assign(menuItem.style, { + padding: '6px 20px', + cursor: 'pointer', + color: 'rgba(226, 232, 240, 0.9)', + fontSize: '13px', + userSelect: 'none', + display: 'flex', + alignItems: 'center', + gap: '8px', + }); + + // Create icon element + const iconEl = document.createElement('div'); + iconEl.innerHTML = icon; + Object.assign(iconEl.style, { + width: '14px', + height: '14px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }); + + // Create text element + const textEl = document.createElement('span'); + textEl.textContent = text; + + menuItem.appendChild(iconEl); + menuItem.appendChild(textEl); + + menuItem.addEventListener('mouseenter', () => { + menuItem.style.backgroundColor = 'rgba(66, 153, 225, 0.2)'; + }); + + menuItem.addEventListener('mouseleave', () => { + menuItem.style.backgroundColor = 'transparent'; + }); + + if (onClick) { + menuItem.addEventListener('click', onClick); + } + + return menuItem; + }; + + // Function to create context menu + const createContextMenu = (x, y, loraName, widget) => { + // Hide preview tooltip first + previewTooltip.hide(); + + // Remove existing context menu if any + const existingMenu = document.querySelector('.comfy-lora-context-menu'); + if (existingMenu) { + existingMenu.remove(); + } + + const menu = document.createElement('div'); + menu.className = 'comfy-lora-context-menu'; + Object.assign(menu.style, { + position: 'fixed', + left: `${x}px`, + top: `${y}px`, + backgroundColor: 'rgba(30, 30, 30, 0.95)', + border: '1px solid rgba(255, 255, 255, 0.1)', + borderRadius: '4px', + padding: '4px 0', + zIndex: 1000, + boxShadow: '0 2px 10px rgba(0,0,0,0.2)', + minWidth: '180px', + }); + + // Delete option with trash icon + const deleteOption = createMenuItem( + 'Delete', + '', + () => { + menu.remove(); + document.removeEventListener('click', closeMenu); + + const lorasData = parseLoraValue(widget.value).filter(l => l.name !== loraName); + widget.value = formatLoraValue(lorasData); + + if (widget.callback) { + widget.callback(widget.value); + } + } + ); + + // Save recipe option with bookmark icon (WIP) + const saveOption = createMenuItem( + 'Save Recipe (WIP)', + '', + null + ); + Object.assign(saveOption.style, { + opacity: '0.6', + cursor: 'default', + }); + + // Add separator + const separator = document.createElement('div'); + Object.assign(separator.style, { + margin: '4px 0', + borderTop: '1px solid rgba(255, 255, 255, 0.1)', + }); + + menu.appendChild(deleteOption); + menu.appendChild(separator); + menu.appendChild(saveOption); + + document.body.appendChild(menu); + + // Close menu when clicking outside + const closeMenu = (e) => { + if (!menu.contains(e.target)) { + menu.remove(); + document.removeEventListener('click', closeMenu); + } + }; + setTimeout(() => document.addEventListener('click', closeMenu), 0); + }; + // Function to render loras from data const renderLoras = (value, widget) => { // Clear existing content @@ -395,6 +519,13 @@ export function addLorasWidget(node, name, opts, callback) { loraEl.style.backgroundColor = active ? "rgba(45, 55, 72, 0.7)" : "rgba(35, 40, 50, 0.5)"; }; + // Add context menu event + loraEl.addEventListener('contextmenu', (e) => { + e.preventDefault(); + e.stopPropagation(); + createContextMenu(e.clientX, e.clientY, name, widget); + }); + // Create strength control const strengthControl = document.createElement("div"); Object.assign(strengthControl.style, {