From 64a906ca5ec0cb10ee329901b28e0228408cd96e Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Wed, 14 May 2025 21:09:36 +0800 Subject: [PATCH] Add Lora syntax send to comfyui functionality: implement API endpoint and frontend integration for sending and updating LoRA codes in ComfyUI nodes. --- py/routes/misc_routes.py | 63 ++++++++++++++++++++++++++++++++ static/js/components/LoraCard.js | 26 ++++++++++--- static/js/utils/uiHelpers.js | 62 +++++++++++++++++++++++++++++++ web/comfyui/lora_loader.js | 40 ++++++++++++++++++++ 4 files changed, 186 insertions(+), 5 deletions(-) diff --git a/py/routes/misc_routes.py b/py/routes/misc_routes.py index 93b1d4d8..23c0d7e8 100644 --- a/py/routes/misc_routes.py +++ b/py/routes/misc_routes.py @@ -4,6 +4,7 @@ import asyncio import json import time import aiohttp +from server import PromptServer # type: ignore from aiohttp import web from ..services.settings_manager import settings from ..utils.usage_stats import UsageStats @@ -49,6 +50,9 @@ class MiscRoutes: app.router.add_post('/api/pause-example-images', MiscRoutes.pause_example_images) app.router.add_post('/api/resume-example-images', MiscRoutes.resume_example_images) + # Lora code update endpoint + app.router.add_post('/api/update-lora-code', MiscRoutes.update_lora_code) + @staticmethod async def update_settings(request): """Update application settings""" @@ -765,3 +769,62 @@ class MiscRoutes: # Set download status to not downloading is_downloading = False + + @staticmethod + async def update_lora_code(request): + """ + Update Lora code in ComfyUI nodes + + Expects a JSON body with: + { + "node_ids": [123, 456], # List of node IDs to update + "lora_code": "", # The Lora code to send + "mode": "append" # or "replace" - whether to append or replace existing code + } + """ + try: + # Parse the request body + data = await request.json() + node_ids = data.get('node_ids', []) + lora_code = data.get('lora_code', '') + mode = data.get('mode', 'append') + + if not node_ids or not lora_code: + return web.json_response({ + 'success': False, + 'error': 'Missing node_ids or lora_code parameter' + }, status=400) + + # Send the lora code update to each node + results = [] + for node_id in node_ids: + try: + # Send the message to the frontend + PromptServer.instance.send_sync("lora_code_update", { + "id": node_id, + "lora_code": lora_code, + "mode": mode + }) + results.append({ + 'node_id': node_id, + 'success': True + }) + except Exception as e: + logger.error(f"Error sending lora code to node {node_id}: {e}") + results.append({ + 'node_id': node_id, + 'success': False, + 'error': str(e) + }) + + return web.json_response({ + 'success': True, + 'results': results + }) + + except Exception as e: + logger.error(f"Failed to update lora code: {e}", exc_info=True) + return web.json_response({ + 'success': False, + 'error': str(e) + }, status=500) diff --git a/static/js/components/LoraCard.js b/static/js/components/LoraCard.js index be53ba41..0d98b6bb 100644 --- a/static/js/components/LoraCard.js +++ b/static/js/components/LoraCard.js @@ -1,4 +1,4 @@ -import { showToast, openCivitai, copyToClipboard } from '../utils/uiHelpers.js'; +import { showToast, openCivitai, copyToClipboard, sendLoraToWorkflow } from '../utils/uiHelpers.js'; import { state } from '../state/index.js'; import { showLoraModal } from './loraModal/index.js'; import { bulkManager } from '../managers/BulkManager.js'; @@ -51,9 +51,14 @@ function handleLoraCardEvent(event) { return; } - if (event.target.closest('.fa-copy')) { + if (event.target.closest('.fa-copy') || event.target.closest('.fa-paper-plane')) { event.stopPropagation(); - copyLoraCode(card); + const useSendButton = state.settings.useSendButton || true; + if (useSendButton) { + sendLoraToComfyUI(card, event.shiftKey); + } else { + copyLoraCode(card); + } return; } @@ -181,6 +186,15 @@ async function copyLoraCode(card) { await copyToClipboard(loraSyntax, 'LoRA syntax copied'); } +// New function to send LoRA to ComfyUI workflow +async function sendLoraToComfyUI(card, replaceMode) { + const usageTips = JSON.parse(card.dataset.usage_tips || '{}'); + const strength = usageTips.strength || 1; + const loraSyntax = ``; + + sendLoraToWorkflow(loraSyntax, replaceMode); +} + export function createLoraCard(lora) { const card = document.createElement('div'); card.className = 'lora-card'; @@ -244,6 +258,8 @@ export function createLoraCard(lora) { // Get favorite status from the lora data const isFavorite = lora.favorite === true; + // Check if we're using send button instead of copy button + const useSendButton = state.settings.useSendButton || true; card.innerHTML = `
@@ -269,8 +285,8 @@ export function createLoraCard(lora) { title="${lora.from_civitai ? 'View on Civitai' : 'Not available from Civitai'}" ${!lora.from_civitai ? 'style="opacity: 0.5; cursor: not-allowed"' : ''}> - + diff --git a/static/js/utils/uiHelpers.js b/static/js/utils/uiHelpers.js index a9da3b64..f7b2fd56 100644 --- a/static/js/utils/uiHelpers.js +++ b/static/js/utils/uiHelpers.js @@ -351,4 +351,66 @@ export function getNSFWLevelName(level) { if (level >= 2) return 'PG13'; if (level >= 1) return 'PG'; return 'Unknown'; +} + +/** + * Sends LoRA syntax to the active ComfyUI workflow + * @param {string} loraSyntax - The LoRA syntax to send + * @param {boolean} replaceMode - Whether to replace existing LoRAs (true) or append (false) + * @returns {Promise} - Whether the operation was successful + */ +export async function sendLoraToWorkflow(loraSyntax, replaceMode = false) { + try { + // Get the current workflow from localStorage + const workflowData = localStorage.getItem('workflow'); + if (!workflowData) { + showToast('No active workflow found', 'error'); + return false; + } + + // Parse the workflow JSON + const workflow = JSON.parse(workflowData); + + // Find all Lora Loader (LoraManager) nodes + const loraNodes = []; + if (workflow.nodes && Array.isArray(workflow.nodes)) { + for (const node of workflow.nodes) { + if (node.type === "Lora Loader (LoraManager)") { + loraNodes.push(node.id); + } + } + } + + if (loraNodes.length === 0) { + showToast('No Lora Loader nodes found in the workflow', 'warning'); + return false; + } + + // Call the backend API to update the lora code + const response = await fetch('/api/update-lora-code', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + node_ids: loraNodes, + lora_code: loraSyntax, + mode: replaceMode ? 'replace' : 'append' + }) + }); + + const result = await response.json(); + + if (result.success) { + showToast(`LoRA ${replaceMode ? 'replaced' : 'added'} to workflow`, 'success'); + return true; + } else { + showToast(result.error || 'Failed to send LoRA to workflow', 'error'); + return false; + } + } catch (error) { + console.error('Failed to send LoRA to workflow:', error); + showToast('Failed to send LoRA to workflow', 'error'); + return false; + } } \ No newline at end of file diff --git a/web/comfyui/lora_loader.js b/web/comfyui/lora_loader.js index cc76e2e6..1926d5b2 100644 --- a/web/comfyui/lora_loader.js +++ b/web/comfyui/lora_loader.js @@ -5,6 +5,7 @@ import { collectActiveLorasFromChain, updateConnectedTriggerWords } from "./utils.js"; +import { api } from "../../scripts/api.js"; function mergeLoras(lorasText, lorasArr) { const result = []; @@ -38,6 +39,45 @@ function mergeLoras(lorasText, lorasArr) { app.registerExtension({ name: "LoraManager.LoraLoader", + setup() { + // Add message handler to listen for messages from Python + api.addEventListener("lora_code_update", (event) => { + const { id, lora_code, mode } = event.detail; + this.handleLoraCodeUpdate(id, lora_code, mode); + }); + }, + + // Handle lora code updates from Python + handleLoraCodeUpdate(id, loraCode, mode) { + const node = app.graph.getNodeById(+id); + if (!node || node.comfyClass !== "Lora Loader (LoraManager)") { + console.warn("Node not found or not a LoraLoader:", id); + return; + } + + // Update the input widget with new lora code + const inputWidget = node.widgets[0]; + if (!inputWidget) return; + + // Get the current lora code + const currentValue = inputWidget.value || ''; + + // Update based on mode (replace or append) + if (mode === 'replace') { + inputWidget.value = loraCode; + } else { + // Append mode - add a space if the current value isn't empty + inputWidget.value = currentValue.trim() + ? `${currentValue.trim()} ${loraCode}` + : loraCode; + } + + // Trigger the callback to update the loras widget + if (typeof inputWidget.callback === 'function') { + inputWidget.callback(inputWidget.value); + } + }, + async nodeCreated(node) { if (node.comfyClass === "Lora Loader (LoraManager)") { // Enable widget serialization