diff --git a/web/comfyui/legacy_loras_widget.js b/web/comfyui/legacy_loras_widget.js
deleted file mode 100644
index bf2ba96f..00000000
--- a/web/comfyui/legacy_loras_widget.js
+++ /dev/null
@@ -1,978 +0,0 @@
-import { api } from "../../scripts/api.js";
-import { app } from "../../scripts/app.js";
-
-export function addLorasWidget(node, name, opts, callback) {
- // Create container for loras
- const container = document.createElement("div");
- container.className = "comfy-loras-container";
- Object.assign(container.style, {
- display: "flex",
- flexDirection: "column",
- gap: "8px",
- padding: "6px",
- backgroundColor: "rgba(40, 44, 52, 0.6)",
- borderRadius: "6px",
- width: "100%",
- });
-
- // Initialize default value
- const defaultValue = opts?.defaultVal || [];
-
- // Parse LoRA entries from value
- const parseLoraValue = (value) => {
- if (!value) return [];
- return Array.isArray(value) ? value : [];
- };
-
- // Format LoRA data
- const formatLoraValue = (loras) => {
- return loras;
- };
-
- // Function to create toggle element
- const createToggle = (active, onChange) => {
- const toggle = document.createElement("div");
- toggle.className = "comfy-lora-toggle";
-
- updateToggleStyle(toggle, active);
-
- toggle.addEventListener("click", (e) => {
- e.stopPropagation();
- onChange(!active);
- });
-
- return toggle;
- };
-
- // Helper function to update toggle style
- function updateToggleStyle(toggleEl, active) {
- Object.assign(toggleEl.style, {
- width: "18px",
- height: "18px",
- borderRadius: "4px",
- cursor: "pointer",
- transition: "all 0.2s ease",
- backgroundColor: active ? "rgba(66, 153, 225, 0.9)" : "rgba(45, 55, 72, 0.7)",
- border: `1px solid ${active ? "rgba(66, 153, 225, 0.9)" : "rgba(226, 232, 240, 0.2)"}`,
- });
-
- // Add hover effect
- toggleEl.onmouseenter = () => {
- toggleEl.style.transform = "scale(1.05)";
- toggleEl.style.boxShadow = "0 2px 4px rgba(0,0,0,0.15)";
- };
-
- toggleEl.onmouseleave = () => {
- toggleEl.style.transform = "scale(1)";
- toggleEl.style.boxShadow = "none";
- };
- }
-
- // Create arrow button for strength adjustment
- const createArrowButton = (direction, onClick) => {
- const button = document.createElement("div");
- button.className = `comfy-lora-arrow comfy-lora-arrow-${direction}`;
-
- Object.assign(button.style, {
- width: "16px",
- height: "16px",
- display: "flex",
- alignItems: "center",
- justifyContent: "center",
- cursor: "pointer",
- userSelect: "none",
- fontSize: "12px",
- color: "rgba(226, 232, 240, 0.8)",
- transition: "all 0.2s ease",
- });
-
- button.textContent = direction === "left" ? "◀" : "▶";
-
- button.addEventListener("click", (e) => {
- e.stopPropagation();
- onClick();
- });
-
- // Add hover effect
- button.onmouseenter = () => {
- button.style.color = "white";
- button.style.transform = "scale(1.2)";
- };
-
- button.onmouseleave = () => {
- button.style.color = "rgba(226, 232, 240, 0.8)";
- button.style.transform = "scale(1)";
- };
-
- return button;
- };
-
- // 添加预览弹窗组件
- class PreviewTooltip {
- constructor() {
- this.element = document.createElement('div');
- Object.assign(this.element.style, {
- position: 'fixed',
- zIndex: 9999,
- background: 'rgba(0, 0, 0, 0.85)',
- borderRadius: '6px',
- boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
- display: 'none',
- overflow: 'hidden',
- maxWidth: '300px',
- });
- document.body.appendChild(this.element);
- this.hideTimeout = null; // 添加超时处理变量
-
- // 添加全局点击事件来隐藏tooltip
- document.addEventListener('click', () => this.hide());
-
- // 添加滚动事件监听
- document.addEventListener('scroll', () => this.hide(), true);
- }
-
- async show(loraName, x, y) {
- try {
- // 清除之前的隐藏定时器
- if (this.hideTimeout) {
- clearTimeout(this.hideTimeout);
- this.hideTimeout = null;
- }
-
- // 如果已经显示同一个lora的预览,则不重复显示
- if (this.element.style.display === 'block' && this.currentLora === loraName) {
- return;
- }
-
- this.currentLora = loraName;
-
- // 获取预览URL
- const response = await api.fetchApi(`/loras/preview-url?name=${encodeURIComponent(loraName)}`, {
- method: 'GET'
- });
-
- if (!response.ok) {
- throw new Error('Failed to fetch preview URL');
- }
-
- const data = await response.json();
- if (!data.success || !data.preview_url) {
- throw new Error('No preview available');
- }
-
- // 清除现有内容
- while (this.element.firstChild) {
- this.element.removeChild(this.element.firstChild);
- }
-
- // Create media container with relative positioning
- const mediaContainer = document.createElement('div');
- Object.assign(mediaContainer.style, {
- position: 'relative',
- maxWidth: '300px',
- maxHeight: '300px',
- });
-
- const isVideo = data.preview_url.endsWith('.mp4');
- const mediaElement = isVideo ? document.createElement('video') : document.createElement('img');
-
- Object.assign(mediaElement.style, {
- maxWidth: '300px',
- maxHeight: '300px',
- objectFit: 'contain',
- display: 'block',
- });
-
- if (isVideo) {
- mediaElement.autoplay = true;
- mediaElement.loop = true;
- mediaElement.muted = true;
- mediaElement.controls = false;
- }
-
- mediaElement.src = data.preview_url;
-
- // Create name label with absolute positioning
- const nameLabel = document.createElement('div');
- nameLabel.textContent = loraName;
- Object.assign(nameLabel.style, {
- position: 'absolute',
- bottom: '0',
- left: '0',
- right: '0',
- padding: '8px',
- color: 'rgba(255, 255, 255, 0.95)',
- fontSize: '13px',
- fontFamily: "'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif",
- background: 'linear-gradient(transparent, rgba(0, 0, 0, 0.8))',
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- textAlign: 'center',
- backdropFilter: 'blur(4px)',
- WebkitBackdropFilter: 'blur(4px)',
- });
-
- mediaContainer.appendChild(mediaElement);
- mediaContainer.appendChild(nameLabel);
- this.element.appendChild(mediaContainer);
-
- // 添加淡入效果
- this.element.style.opacity = '0';
- this.element.style.display = 'block';
- this.position(x, y);
-
- requestAnimationFrame(() => {
- this.element.style.transition = 'opacity 0.15s ease';
- this.element.style.opacity = '1';
- });
- } catch (error) {
- console.warn('Failed to load preview:', error);
- }
- }
-
- position(x, y) {
- // 确保预览框不超出视窗边界
- const rect = this.element.getBoundingClientRect();
- const viewportWidth = window.innerWidth;
- const viewportHeight = window.innerHeight;
-
- let left = x + 10; // 默认在鼠标右侧偏移10px
- let top = y + 10; // 默认在鼠标下方偏移10px
-
- // 检查右边界
- if (left + rect.width > viewportWidth) {
- left = x - rect.width - 10;
- }
-
- // 检查下边界
- if (top + rect.height > viewportHeight) {
- top = y - rect.height - 10;
- }
-
- Object.assign(this.element.style, {
- left: `${left}px`,
- top: `${top}px`
- });
- }
-
- hide() {
- // 使用淡出效果
- if (this.element.style.display === 'block') {
- this.element.style.opacity = '0';
- this.hideTimeout = setTimeout(() => {
- this.element.style.display = 'none';
- this.currentLora = null;
- // 停止视频播放
- const video = this.element.querySelector('video');
- if (video) {
- video.pause();
- }
- this.hideTimeout = null;
- }, 150);
- }
- }
-
- cleanup() {
- if (this.hideTimeout) {
- clearTimeout(this.hideTimeout);
- }
- // 移除所有事件监听器
- document.removeEventListener('click', () => this.hide());
- document.removeEventListener('scroll', () => this.hide(), true);
- this.element.remove();
- }
- }
-
- // 创建预览tooltip实例
- const previewTooltip = new PreviewTooltip();
-
- // Function to handle strength adjustment via dragging
- const handleStrengthDrag = (name, initialStrength, initialX, event, widget) => {
- // Calculate drag sensitivity (how much the strength changes per pixel)
- // Using 0.01 per 10 pixels of movement
- const sensitivity = 0.001;
-
- // Get the current mouse position
- const currentX = event.clientX;
-
- // Calculate the distance moved
- const deltaX = currentX - initialX;
-
- // Calculate the new strength value based on movement
- // Moving right increases, moving left decreases
- let newStrength = Number(initialStrength) + (deltaX * sensitivity);
-
- // Limit the strength to reasonable bounds (now between -10 and 10)
- newStrength = Math.max(-10, Math.min(10, newStrength));
- newStrength = Number(newStrength.toFixed(2));
-
- // Update the lora data
- const lorasData = parseLoraValue(widget.value);
- const loraIndex = lorasData.findIndex(l => l.name === name);
-
- if (loraIndex >= 0) {
- lorasData[loraIndex].strength = newStrength;
-
- // Update the widget value
- widget.value = formatLoraValue(lorasData);
-
- // Force re-render to show updated strength value
- renderLoras(widget.value, widget);
- }
- };
-
- // Function to initialize drag operation
- const initDrag = (loraEl, nameEl, name, widget) => {
- let isDragging = false;
- let initialX = 0;
- let initialStrength = 0;
-
- // Create a style element for drag cursor override if it doesn't exist
- if (!document.getElementById('comfy-lora-drag-style')) {
- const styleEl = document.createElement('style');
- styleEl.id = 'comfy-lora-drag-style';
- styleEl.textContent = `
- body.comfy-lora-dragging,
- body.comfy-lora-dragging * {
- cursor: ew-resize !important;
- }
- `;
- 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) => {
- // 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;
- }
-
- // Store initial values
- const lorasData = parseLoraValue(widget.value);
- const loraData = lorasData.find(l => l.name === name);
-
- if (!loraData) return;
-
- initialX = e.clientX;
- initialStrength = loraData.strength;
- isDragging = true;
-
- // Add class to body to enforce cursor style globally
- document.body.classList.add('comfy-lora-dragging');
-
- // Prevent text selection during drag
- e.preventDefault();
- });
-
- // Use the document for move and up events to ensure drag continues
- // even if mouse leaves the element
- document.addEventListener('mousemove', (e) => {
- if (!isDragging) return;
-
- // Call the strength adjustment function
- handleStrengthDrag(name, initialStrength, initialX, e, widget);
-
- // Prevent showing the preview tooltip during drag
- previewTooltip.hide();
- });
-
- document.addEventListener('mouseup', () => {
- if (isDragging) {
- isDragging = false;
- // Remove the class to restore normal cursor behavior
- document.body.classList.remove('comfy-lora-dragging');
- }
- });
- };
-
- // 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',
- });
-
- // View on Civitai option with globe icon
- const viewOnCivitaiOption = createMenuItem(
- 'View on Civitai',
- '',
- async () => {
- menu.remove();
- document.removeEventListener('click', closeMenu);
-
- try {
- // Get Civitai URL from API
- const response = await api.fetchApi(`/loras/civitai-url?name=${encodeURIComponent(loraName)}`, {
- method: 'GET'
- });
-
- if (!response.ok) {
- const errorText = await response.text();
- throw new Error(errorText || 'Failed to get Civitai URL');
- }
-
- const data = await response.json();
- if (data.success && data.civitai_url) {
- // Open the URL in a new tab
- window.open(data.civitai_url, '_blank');
- } else {
- // Show error message if no Civitai URL
- if (app && app.extensionManager && app.extensionManager.toast) {
- app.extensionManager.toast.add({
- severity: 'warning',
- summary: 'Not Found',
- detail: 'This LoRA has no associated Civitai URL',
- life: 3000
- });
- } else {
- alert('This LoRA has no associated Civitai URL');
- }
- }
- } catch (error) {
- console.error('Error getting Civitai URL:', error);
- if (app && app.extensionManager && app.extensionManager.toast) {
- app.extensionManager.toast.add({
- severity: 'error',
- summary: 'Error',
- detail: error.message || 'Failed to get Civitai URL',
- life: 5000
- });
- } else {
- alert('Error: ' + (error.message || 'Failed to get Civitai URL'));
- }
- }
- }
- );
-
- // 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
- const saveOption = createMenuItem(
- 'Save Recipe',
- '',
- () => {
- menu.remove();
- document.removeEventListener('click', closeMenu);
- saveRecipeDirectly(widget);
- }
- );
-
- // 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(viewOnCivitaiOption); // Add the new menu option
- 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
- while (container.firstChild) {
- container.removeChild(container.firstChild);
- }
-
- // Parse the loras data
- const lorasData = parseLoraValue(value);
-
- if (lorasData.length === 0) {
- // Show message when no loras are added
- const emptyMessage = document.createElement("div");
- emptyMessage.textContent = "No LoRAs added";
- Object.assign(emptyMessage.style, {
- textAlign: "center",
- padding: "20px 0",
- color: "rgba(226, 232, 240, 0.8)",
- fontStyle: "italic",
- userSelect: "none", // Add this line to prevent text selection
- WebkitUserSelect: "none", // For Safari support
- MozUserSelect: "none", // For Firefox support
- msUserSelect: "none", // For IE/Edge support
- });
- container.appendChild(emptyMessage);
- return;
- }
-
- // Create header
- const header = document.createElement("div");
- header.className = "comfy-loras-header";
- Object.assign(header.style, {
- display: "flex",
- justifyContent: "space-between",
- alignItems: "center",
- padding: "4px 8px",
- borderBottom: "1px solid rgba(226, 232, 240, 0.2)",
- marginBottom: "8px"
- });
-
- // Add toggle all control
- const allActive = lorasData.every(lora => lora.active);
- const toggleAll = createToggle(allActive, (active) => {
- // Update all loras active state
- const lorasData = parseLoraValue(widget.value);
- lorasData.forEach(lora => lora.active = active);
-
- const newValue = formatLoraValue(lorasData);
- widget.value = newValue;
- });
-
- // Add label to toggle all
- const toggleLabel = document.createElement("div");
- toggleLabel.textContent = "Toggle All";
- Object.assign(toggleLabel.style, {
- color: "rgba(226, 232, 240, 0.8)",
- fontSize: "13px",
- marginLeft: "8px",
- userSelect: "none", // Add this line to prevent text selection
- WebkitUserSelect: "none", // For Safari support
- MozUserSelect: "none", // For Firefox support
- msUserSelect: "none", // For IE/Edge support
- });
-
- const toggleContainer = document.createElement("div");
- Object.assign(toggleContainer.style, {
- display: "flex",
- alignItems: "center",
- });
- toggleContainer.appendChild(toggleAll);
- toggleContainer.appendChild(toggleLabel);
-
- // Strength label
- const strengthLabel = document.createElement("div");
- strengthLabel.textContent = "Strength";
- Object.assign(strengthLabel.style, {
- color: "rgba(226, 232, 240, 0.8)",
- fontSize: "13px",
- marginRight: "8px",
- userSelect: "none", // Add this line to prevent text selection
- WebkitUserSelect: "none", // For Safari support
- MozUserSelect: "none", // For Firefox support
- msUserSelect: "none", // For IE/Edge support
- });
-
- header.appendChild(toggleContainer);
- header.appendChild(strengthLabel);
- container.appendChild(header);
-
- // Render each lora entry
- lorasData.forEach((loraData) => {
- const { name, strength, active } = loraData;
-
- const loraEl = document.createElement("div");
- loraEl.className = "comfy-lora-entry";
- Object.assign(loraEl.style, {
- display: "flex",
- justifyContent: "space-between",
- alignItems: "center",
- padding: "8px",
- borderRadius: "6px",
- backgroundColor: active ? "rgba(45, 55, 72, 0.7)" : "rgba(35, 40, 50, 0.5)",
- transition: "all 0.2s ease",
- marginBottom: "6px",
- });
-
- // Create toggle for this lora
- const toggle = createToggle(active, (newActive) => {
- // Update this lora's active state
- const lorasData = parseLoraValue(widget.value);
- const loraIndex = lorasData.findIndex(l => l.name === name);
-
- if (loraIndex >= 0) {
- lorasData[loraIndex].active = newActive;
-
- const newValue = formatLoraValue(lorasData);
- widget.value = newValue;
- }
- });
-
- // Create name display
- const nameEl = document.createElement("div");
- nameEl.textContent = name;
- Object.assign(nameEl.style, {
- marginLeft: "10px",
- flex: "1",
- overflow: "hidden",
- textOverflow: "ellipsis",
- whiteSpace: "nowrap",
- color: active ? "rgba(226, 232, 240, 0.9)" : "rgba(226, 232, 240, 0.6)",
- fontSize: "13px",
- cursor: "pointer", // Add pointer cursor to indicate hoverable area
- userSelect: "none", // Add this line to prevent text selection
- WebkitUserSelect: "none", // For Safari support
- MozUserSelect: "none", // For Firefox support
- msUserSelect: "none", // For IE/Edge support
- });
-
- // Move preview tooltip events to nameEl instead of loraEl
- nameEl.addEventListener('mouseenter', async (e) => {
- e.stopPropagation();
- const rect = nameEl.getBoundingClientRect();
- await previewTooltip.show(name, rect.right, rect.top);
- });
-
- nameEl.addEventListener('mouseleave', (e) => {
- e.stopPropagation();
- previewTooltip.hide();
- });
-
- // Remove the preview tooltip events from loraEl
- loraEl.onmouseenter = () => {
- loraEl.style.backgroundColor = active ? "rgba(50, 60, 80, 0.8)" : "rgba(40, 45, 55, 0.6)";
- };
-
- loraEl.onmouseleave = () => {
- 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, {
- display: "flex",
- alignItems: "center",
- gap: "8px",
- });
-
- // Left arrow
- const leftArrow = createArrowButton("left", () => {
- // Decrease strength
- const lorasData = parseLoraValue(widget.value);
- const loraIndex = lorasData.findIndex(l => l.name === name);
-
- if (loraIndex >= 0) {
- lorasData[loraIndex].strength = (lorasData[loraIndex].strength - 0.05).toFixed(2);
-
- const newValue = formatLoraValue(lorasData);
- widget.value = newValue;
- }
- });
-
- // Strength display
- const strengthEl = document.createElement("input");
- strengthEl.type = "text";
- strengthEl.value = typeof strength === 'number' ? strength.toFixed(2) : Number(strength).toFixed(2);
- Object.assign(strengthEl.style, {
- minWidth: "50px",
- width: "50px",
- textAlign: "center",
- color: active ? "rgba(226, 232, 240, 0.9)" : "rgba(226, 232, 240, 0.6)",
- fontSize: "13px",
- background: "none",
- border: "1px solid transparent",
- padding: "2px 4px",
- borderRadius: "3px",
- outline: "none",
- });
-
- // 添加hover效果
- strengthEl.addEventListener('mouseenter', () => {
- strengthEl.style.border = "1px solid rgba(226, 232, 240, 0.2)";
- });
-
- strengthEl.addEventListener('mouseleave', () => {
- if (document.activeElement !== strengthEl) {
- strengthEl.style.border = "1px solid transparent";
- }
- });
-
- // 处理焦点
- strengthEl.addEventListener('focus', () => {
- strengthEl.style.border = "1px solid rgba(66, 153, 225, 0.6)";
- strengthEl.style.background = "rgba(0, 0, 0, 0.2)";
- // 自动选中所有内容
- strengthEl.select();
- });
-
- strengthEl.addEventListener('blur', () => {
- strengthEl.style.border = "1px solid transparent";
- strengthEl.style.background = "none";
- });
-
- // 处理输入变化
- strengthEl.addEventListener('change', () => {
- let newValue = parseFloat(strengthEl.value);
-
- // 验证输入
- if (isNaN(newValue)) {
- newValue = 1.0;
- }
-
- // 更新数值
- const lorasData = parseLoraValue(widget.value);
- const loraIndex = lorasData.findIndex(l => l.name === name);
-
- if (loraIndex >= 0) {
- lorasData[loraIndex].strength = newValue.toFixed(2);
-
- // 更新值并触发回调
- const newLorasValue = formatLoraValue(lorasData);
- widget.value = newLorasValue;
- }
- });
-
- // 处理按键事件
- strengthEl.addEventListener('keydown', (e) => {
- if (e.key === 'Enter') {
- strengthEl.blur();
- }
- });
-
- // Right arrow
- const rightArrow = createArrowButton("right", () => {
- // Increase strength
- const lorasData = parseLoraValue(widget.value);
- const loraIndex = lorasData.findIndex(l => l.name === name);
-
- if (loraIndex >= 0) {
- lorasData[loraIndex].strength = (parseFloat(lorasData[loraIndex].strength) + 0.05).toFixed(2);
-
- const newValue = formatLoraValue(lorasData);
- widget.value = newValue;
- }
- });
-
- strengthControl.appendChild(leftArrow);
- strengthControl.appendChild(strengthEl);
- strengthControl.appendChild(rightArrow);
-
- // Assemble entry
- const leftSection = document.createElement("div");
- Object.assign(leftSection.style, {
- display: "flex",
- alignItems: "center",
- flex: "1",
- minWidth: "0", // Allow shrinking
- });
-
- leftSection.appendChild(toggle);
- leftSection.appendChild(nameEl);
-
- loraEl.appendChild(leftSection);
- loraEl.appendChild(strengthControl);
-
- container.appendChild(loraEl);
-
- // Initialize drag functionality
- initDrag(loraEl, nameEl, name, widget);
- });
- };
-
- // Store the value in a variable to avoid recursion
- let widgetValue = defaultValue;
-
- // Create widget with initial properties
- const widget = node.addDOMWidget(name, "loras", container, {
- getValue: function() {
- return widgetValue;
- },
- setValue: function(v) {
- // Remove duplicates by keeping the last occurrence of each lora name
- const uniqueValue = (v || []).reduce((acc, lora) => {
- // Remove any existing lora with the same name
- const filtered = acc.filter(l => l.name !== lora.name);
- // Add the current lora
- return [...filtered, lora];
- }, []);
-
- widgetValue = uniqueValue;
- renderLoras(widgetValue, widget);
-
- // Update container height after rendering
- requestAnimationFrame(() => {
- const minHeight = this.getMinHeight();
- container.style.height = `${minHeight}px`;
-
- // Force node to update size
- node.setSize([node.size[0], node.computeSize()[1]]);
- node.setDirtyCanvas(true, true);
- });
- },
- getMinHeight: function() {
- // Calculate height based on content
- const lorasCount = parseLoraValue(widgetValue).length;
- return Math.max(
- 100,
- lorasCount > 0 ? 60 + lorasCount * 44 : 60
- );
- },
- });
-
- widget.value = defaultValue;
-
- 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 }
- ];
- }
-
- widget.onRemove = () => {
- container.remove();
- previewTooltip.cleanup();
- };
-
- return { minWidth: 400, minHeight: 200, widget };
-}
-
-// Function to directly save the recipe without dialog
-async function saveRecipeDirectly(widget) {
- try {
- // Show loading toast
- if (app && app.extensionManager && app.extensionManager.toast) {
- app.extensionManager.toast.add({
- severity: 'info',
- summary: 'Saving Recipe',
- detail: 'Please wait...',
- life: 2000
- });
- }
-
- // Send the request
- const response = await fetch('/api/recipes/save-from-widget', {
- method: 'POST'
- });
-
- const result = await response.json();
-
- // Show result toast
- if (app && app.extensionManager && app.extensionManager.toast) {
- if (result.success) {
- app.extensionManager.toast.add({
- severity: 'success',
- summary: 'Recipe Saved',
- detail: 'Recipe has been saved successfully',
- life: 3000
- });
- } else {
- app.extensionManager.toast.add({
- severity: 'error',
- summary: 'Error',
- detail: result.error || 'Failed to save recipe',
- life: 5000
- });
- }
- }
- } catch (error) {
- console.error('Error saving recipe:', error);
-
- // Show error toast
- if (app && app.extensionManager && app.extensionManager.toast) {
- app.extensionManager.toast.add({
- severity: 'error',
- summary: 'Error',
- detail: 'Failed to save recipe: ' + (error.message || 'Unknown error'),
- life: 5000
- });
- }
- }
-}
diff --git a/web/comfyui/legacy_tags_widget.js b/web/comfyui/legacy_tags_widget.js
deleted file mode 100644
index d43cb016..00000000
--- a/web/comfyui/legacy_tags_widget.js
+++ /dev/null
@@ -1,193 +0,0 @@
-export function addTagsWidget(node, name, opts, callback) {
- // Create container for tags
- const container = document.createElement("div");
- container.className = "comfy-tags-container";
- Object.assign(container.style, {
- display: "flex",
- flexWrap: "wrap",
- gap: "4px", // 从8px减小到4px
- padding: "6px",
- minHeight: "30px",
- backgroundColor: "rgba(40, 44, 52, 0.6)", // Darker, more modern background
- borderRadius: "6px", // Slightly larger radius
- width: "100%",
- });
-
- // Initialize default value as array
- const initialTagsData = opts?.defaultVal || [];
-
- // Function to render tags from array data
- const renderTags = (tagsData, widget) => {
- // Clear existing tags
- while (container.firstChild) {
- container.removeChild(container.firstChild);
- }
-
- const normalizedTags = tagsData;
-
- if (normalizedTags.length === 0) {
- // Show message when no tags are present
- const emptyMessage = document.createElement("div");
- emptyMessage.textContent = "No trigger words detected";
- Object.assign(emptyMessage.style, {
- textAlign: "center",
- padding: "20px 0",
- color: "rgba(226, 232, 240, 0.8)",
- fontStyle: "italic",
- userSelect: "none",
- WebkitUserSelect: "none",
- MozUserSelect: "none",
- msUserSelect: "none",
- });
- container.appendChild(emptyMessage);
- return;
- }
-
- normalizedTags.forEach((tagData, index) => {
- const { text, active } = tagData;
- const tagEl = document.createElement("div");
- tagEl.className = "comfy-tag";
-
- updateTagStyle(tagEl, active);
-
- tagEl.textContent = text;
- tagEl.title = text; // Set tooltip for full content
-
- // Add click handler to toggle state
- tagEl.addEventListener("click", (e) => {
- e.stopPropagation();
-
- // Toggle active state for this specific tag using its index
- const updatedTags = [...widget.value];
- updatedTags[index].active = !updatedTags[index].active;
- updateTagStyle(tagEl, updatedTags[index].active);
-
- widget.value = updatedTags;
- });
-
- container.appendChild(tagEl);
- });
- };
-
- // Helper function to update tag style based on active state
- function updateTagStyle(tagEl, active) {
- const baseStyles = {
- padding: "4px 12px", // 垂直内边距从6px减小到4px
- borderRadius: "6px", // Matching container radius
- maxWidth: "200px", // Increased max width
- overflow: "hidden",
- textOverflow: "ellipsis",
- whiteSpace: "nowrap",
- fontSize: "13px", // Slightly larger font
- cursor: "pointer",
- transition: "all 0.2s ease", // Smoother transition
- border: "1px solid transparent",
- display: "inline-block",
- boxShadow: "0 1px 2px rgba(0,0,0,0.1)",
- margin: "2px", // 从4px减小到2px
- userSelect: "none", // Add this line to prevent text selection
- WebkitUserSelect: "none", // For Safari support
- MozUserSelect: "none", // For Firefox support
- msUserSelect: "none", // For IE/Edge support
- };
-
- if (active) {
- Object.assign(tagEl.style, {
- ...baseStyles,
- backgroundColor: "rgba(66, 153, 225, 0.9)", // Modern blue
- color: "white",
- borderColor: "rgba(66, 153, 225, 0.9)",
- });
- } else {
- Object.assign(tagEl.style, {
- ...baseStyles,
- backgroundColor: "rgba(45, 55, 72, 0.7)", // Darker inactive state
- color: "rgba(226, 232, 240, 0.8)", // Lighter text for contrast
- borderColor: "rgba(226, 232, 240, 0.2)",
- });
- }
-
- // Add hover effect
- tagEl.onmouseenter = () => {
- tagEl.style.transform = "translateY(-1px)";
- tagEl.style.boxShadow = "0 2px 4px rgba(0,0,0,0.15)";
- };
-
- tagEl.onmouseleave = () => {
- tagEl.style.transform = "translateY(0)";
- tagEl.style.boxShadow = "0 1px 2px rgba(0,0,0,0.1)";
- };
- }
-
- // Store the value as array
- let widgetValue = initialTagsData;
-
- // Create widget with initial properties
- const widget = node.addDOMWidget(name, "tags", container, {
- getValue: function() {
- return widgetValue;
- },
- setValue: function(v) {
- widgetValue = v;
- renderTags(widgetValue, widget);
-
- // Update container height after rendering
- requestAnimationFrame(() => {
- const minHeight = this.getMinHeight();
- container.style.height = `${minHeight}px`;
-
- // Force node to update size
- node.setSize([node.size[0], node.computeSize()[1]]);
- node.setDirtyCanvas(true, true);
- });
- },
- getMinHeight: function() {
- const minHeight = 150;
- // If no tags or only showing the empty message, return a minimum height
- if (widgetValue.length === 0) {
- return minHeight; // Height for empty state with message
- }
-
- // Get all tag elements
- const tagElements = container.querySelectorAll('.comfy-tag');
-
- if (tagElements.length === 0) {
- return minHeight; // Fallback if elements aren't rendered yet
- }
-
- // Calculate the actual height based on tag positions
- let maxBottom = 0;
-
- tagElements.forEach(tag => {
- const rect = tag.getBoundingClientRect();
- const tagBottom = rect.bottom - container.getBoundingClientRect().top;
- maxBottom = Math.max(maxBottom, tagBottom);
- });
-
- // Add padding (top and bottom padding of container)
- const computedStyle = window.getComputedStyle(container);
- const paddingTop = parseInt(computedStyle.paddingTop, 10) || 0;
- const paddingBottom = parseInt(computedStyle.paddingBottom, 10) || 0;
-
- // Add extra buffer for potential wrapping issues and to ensure no clipping
- const extraBuffer = 20;
-
- // Round up to nearest 5px for clean sizing and ensure minimum height
- return Math.max(minHeight, Math.ceil((maxBottom + paddingBottom + extraBuffer) / 5) * 5);
- },
- });
-
- widget.value = initialTagsData;
-
- widget.callback = callback;
-
- widget.serializeValue = () => {
- // Add dummy items to avoid the 2-element serialization issue, a bug in comfyui
- return [...widgetValue,
- { text: "__dummy_item__", active: false, _isDummy: true },
- { text: "__dummy_item__", active: false, _isDummy: true }
- ];
- };
-
- return { minWidth: 300, minHeight: 150, widget };
-}
diff --git a/web/comfyui/lora_loader.js b/web/comfyui/lora_loader.js
index aa62252f..f13f48a6 100644
--- a/web/comfyui/lora_loader.js
+++ b/web/comfyui/lora_loader.js
@@ -176,32 +176,6 @@ app.registerExtension({
inputWidget,
originalCallback
);
-
- // Register this node with the backend
- this.registerNode = async () => {
- try {
- await fetch("/api/register-node", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- node_id: this.id,
- bgcolor: this.bgcolor,
- title: this.title,
- graph_id: this.graph.id,
- }),
- });
- } catch (error) {
- console.warn("Failed to register node:", error);
- }
- };
-
- // Ensure the node is registered after creation
- // Call registration
- // setTimeout(() => {
- // this.registerNode();
- // }, 0);
});
}
},
diff --git a/web/comfyui/lora_stacker.js b/web/comfyui/lora_stacker.js
index 9b812926..5648891d 100644
--- a/web/comfyui/lora_stacker.js
+++ b/web/comfyui/lora_stacker.js
@@ -101,31 +101,6 @@ app.registerExtension({
inputWidget,
originalCallback
);
-
- // Register this node with the backend
- this.registerNode = async () => {
- try {
- await fetch("/api/register-node", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- node_id: this.id,
- bgcolor: this.bgcolor,
- title: this.title,
- graph_id: this.graph.id,
- }),
- });
- } catch (error) {
- console.warn("Failed to register node:", error);
- }
- };
-
- // Call registration
- // setTimeout(() => {
- // this.registerNode();
- // }, 0);
});
}
},
diff --git a/web/comfyui/trigger_word_toggle.js b/web/comfyui/trigger_word_toggle.js
index 74a3fe1d..57541bd4 100644
--- a/web/comfyui/trigger_word_toggle.js
+++ b/web/comfyui/trigger_word_toggle.js
@@ -1,11 +1,7 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
-import { CONVERTED_TYPE, dynamicImportByVersion } from "./utils.js";
-
-// Function to get the appropriate tags widget based on ComfyUI version
-async function getTagsWidgetModule() {
- return await dynamicImportByVersion("./tags_widget.js", "./legacy_tags_widget.js");
-}
+import { CONVERTED_TYPE } from "./utils.js";
+import { addTagsWidget } from "./tags_widget.js";
// TriggerWordToggle extension for ComfyUI
app.registerExtension({
@@ -30,10 +26,6 @@ app.registerExtension({
// Wait for node to be properly initialized
requestAnimationFrame(async () => {
- // Dynamically import the appropriate tags widget module
- const tagsModule = await getTagsWidgetModule();
- const { addTagsWidget } = tagsModule;
-
// Get the widget object directly from the returned object
const result = addTagsWidget(node, "toggle_trigger_words", {
defaultVal: []
diff --git a/web/comfyui/utils.js b/web/comfyui/utils.js
index 760cb46a..08cbff7a 100644
--- a/web/comfyui/utils.js
+++ b/web/comfyui/utils.js
@@ -20,10 +20,6 @@ export function chainCallback(object, property, callback) {
}
}
-export function getComfyUIFrontendVersion() {
- return window['__COMFYUI_FRONTEND_VERSION__'] || "0.0.0";
-}
-
/**
* Show a toast notification
* @param {Object|string} options - Toast options object or message string for backward compatibility
@@ -78,29 +74,6 @@ export function showToast(options, type = 'info') {
}
}
-// Dynamically import the appropriate widget based on app version
-export async function dynamicImportByVersion(latestModulePath, legacyModulePath) {
- // Parse app version and compare with 1.12.6 (version when tags widget API changed)
- const currentVersion = getComfyUIFrontendVersion();
- const versionParts = currentVersion.split('.').map(part => parseInt(part, 10));
- const requiredVersion = [1, 12, 6];
-
- // Compare version numbers
- for (let i = 0; i < 3; i++) {
- if (versionParts[i] > requiredVersion[i]) {
- console.log(`Using latest widget: ${latestModulePath}`);
- return import(latestModulePath);
- } else if (versionParts[i] < requiredVersion[i]) {
- console.log(`Using legacy widget: ${legacyModulePath}`);
- return import(legacyModulePath);
- }
- }
-
- // If we get here, versions are equal, use the latest module
- console.log(`Using latest widget: ${latestModulePath}`);
- return import(latestModulePath);
-}
-
export function hideWidgetForGood(node, widget, suffix = "") {
widget.origType = widget.type;
widget.origComputeSize = widget.computeSize;
@@ -124,11 +97,6 @@ export function hideWidgetForGood(node, widget, suffix = "") {
}
}
-// 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;