mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
Add Lora syntax send to comfyui functionality: implement API endpoint and frontend integration for sending and updating LoRA codes in ComfyUI nodes.
This commit is contained in:
@@ -4,6 +4,7 @@ import asyncio
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
from server import PromptServer # type: ignore
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from ..services.settings_manager import settings
|
from ..services.settings_manager import settings
|
||||||
from ..utils.usage_stats import UsageStats
|
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/pause-example-images', MiscRoutes.pause_example_images)
|
||||||
app.router.add_post('/api/resume-example-images', MiscRoutes.resume_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
|
@staticmethod
|
||||||
async def update_settings(request):
|
async def update_settings(request):
|
||||||
"""Update application settings"""
|
"""Update application settings"""
|
||||||
@@ -765,3 +769,62 @@ class MiscRoutes:
|
|||||||
|
|
||||||
# Set download status to not downloading
|
# Set download status to not downloading
|
||||||
is_downloading = False
|
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": "<lora:modelname:1.0>", # 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)
|
||||||
|
|||||||
@@ -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 { state } from '../state/index.js';
|
||||||
import { showLoraModal } from './loraModal/index.js';
|
import { showLoraModal } from './loraModal/index.js';
|
||||||
import { bulkManager } from '../managers/BulkManager.js';
|
import { bulkManager } from '../managers/BulkManager.js';
|
||||||
@@ -51,9 +51,14 @@ function handleLoraCardEvent(event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.target.closest('.fa-copy')) {
|
if (event.target.closest('.fa-copy') || event.target.closest('.fa-paper-plane')) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
copyLoraCode(card);
|
const useSendButton = state.settings.useSendButton || true;
|
||||||
|
if (useSendButton) {
|
||||||
|
sendLoraToComfyUI(card, event.shiftKey);
|
||||||
|
} else {
|
||||||
|
copyLoraCode(card);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,6 +186,15 @@ async function copyLoraCode(card) {
|
|||||||
await copyToClipboard(loraSyntax, 'LoRA syntax copied');
|
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 = `<lora:${card.dataset.file_name}:${strength}>`;
|
||||||
|
|
||||||
|
sendLoraToWorkflow(loraSyntax, replaceMode);
|
||||||
|
}
|
||||||
|
|
||||||
export function createLoraCard(lora) {
|
export function createLoraCard(lora) {
|
||||||
const card = document.createElement('div');
|
const card = document.createElement('div');
|
||||||
card.className = 'lora-card';
|
card.className = 'lora-card';
|
||||||
@@ -244,6 +258,8 @@ export function createLoraCard(lora) {
|
|||||||
|
|
||||||
// Get favorite status from the lora data
|
// Get favorite status from the lora data
|
||||||
const isFavorite = lora.favorite === true;
|
const isFavorite = lora.favorite === true;
|
||||||
|
// Check if we're using send button instead of copy button
|
||||||
|
const useSendButton = state.settings.useSendButton || true;
|
||||||
|
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
<div class="card-preview ${shouldBlur ? 'blurred' : ''}">
|
<div class="card-preview ${shouldBlur ? 'blurred' : ''}">
|
||||||
@@ -269,8 +285,8 @@ export function createLoraCard(lora) {
|
|||||||
title="${lora.from_civitai ? 'View on Civitai' : 'Not available from Civitai'}"
|
title="${lora.from_civitai ? 'View on Civitai' : 'Not available from Civitai'}"
|
||||||
${!lora.from_civitai ? 'style="opacity: 0.5; cursor: not-allowed"' : ''}>
|
${!lora.from_civitai ? 'style="opacity: 0.5; cursor: not-allowed"' : ''}>
|
||||||
</i>
|
</i>
|
||||||
<i class="fas fa-copy"
|
<i class="${useSendButton ? 'fas fa-paper-plane' : 'fas fa-copy'}"
|
||||||
title="Copy LoRA Syntax">
|
title="${useSendButton ? 'Send to ComfyUI (Click: Append, Shift+Click: Replace)' : 'Copy LoRA Syntax'}">
|
||||||
</i>
|
</i>
|
||||||
<i class="fas fa-trash"
|
<i class="fas fa-trash"
|
||||||
title="Delete Model">
|
title="Delete Model">
|
||||||
|
|||||||
@@ -351,4 +351,66 @@ export function getNSFWLevelName(level) {
|
|||||||
if (level >= 2) return 'PG13';
|
if (level >= 2) return 'PG13';
|
||||||
if (level >= 1) return 'PG';
|
if (level >= 1) return 'PG';
|
||||||
return 'Unknown';
|
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<boolean>} - 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
collectActiveLorasFromChain,
|
collectActiveLorasFromChain,
|
||||||
updateConnectedTriggerWords
|
updateConnectedTriggerWords
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
|
import { api } from "../../scripts/api.js";
|
||||||
|
|
||||||
function mergeLoras(lorasText, lorasArr) {
|
function mergeLoras(lorasText, lorasArr) {
|
||||||
const result = [];
|
const result = [];
|
||||||
@@ -38,6 +39,45 @@ function mergeLoras(lorasText, lorasArr) {
|
|||||||
app.registerExtension({
|
app.registerExtension({
|
||||||
name: "LoraManager.LoraLoader",
|
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) {
|
async nodeCreated(node) {
|
||||||
if (node.comfyClass === "Lora Loader (LoraManager)") {
|
if (node.comfyClass === "Lora Loader (LoraManager)") {
|
||||||
// Enable widget serialization
|
// Enable widget serialization
|
||||||
|
|||||||
Reference in New Issue
Block a user