Add files via upload

This commit is contained in:
HalfikChan
2026-03-21 16:05:08 +05:00
committed by GitHub
parent dec0b8eac3
commit d50349dda3
7 changed files with 889 additions and 2 deletions
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

+152 -2
View File
@@ -1,2 +1,152 @@
# ComfyUI-Prompt-DB
A powerful Prompt and LoRA database manager with a Web UI for ComfyUI
# 📦 ComfyUI Prompt Database
A powerful prompt management system for ComfyUI. Store, categorize, search, and combine prompts with full LoRA integration — all from a beautiful built-in web UI.
![Main Pipeline](Main%20Pipeline.png)
---
## ✨ Features
### 🗂️ Prompt Management (Web UI)
- **Categories** — organize prompts into named groups
- **Create / Edit / Delete** prompts with a slide-out drawer
- **Search** across names, text, and categories
- **Drag & Drop** — reorder prompts and move them between categories
- **Grid & List views** — toggle layout with persistent preference
- **Sorting** — by name (A→Z / Z→A), newest, oldest, category, or custom order
- **Export / Import** — backup your entire database as JSON; import with **Merge** (append new, skip duplicates) or **Overwrite**
- **Keyboard shortcuts** — `Ctrl+N` new prompt, `/` focus search, `Esc` close
### 📖 LoRA Integration
- **Attach LoRAs** to any prompt with custom strength values (supports negative values)
- **LoRA auto-discovery** — scans your ComfyUI `loras` folder
- **Per-LoRA enable/disable** toggle in Apply Lora Stack (inline circle ●)
- **Strength adjustment** — ◀ ▶ arrows or manual input, range -2.0 to 2.0
- **Reset to defaults** — restore original prompt-defined LoRA strengths
### 🔗 Node System
Four nodes that work together:
| Node | Purpose |
|------|---------|
| **Prompt DB Manager** | Opens the web UI for managing your database |
| **Prompt Selector** | Pick one prompt from a category → outputs text + LoRA stack |
| **Prompt Combiner** | Connect multiple selectors → combines all prompts into one string + merged LoRA stack |
| **Apply Lora Stack** | Takes a LoRA stack and applies it to model + clip |
---
## 🚀 Installation
### ComfyUI Manager
Search for **"Prompt Database"** in the ComfyUI Manager and install.
### Manual
```bash
cd ComfyUI/custom_nodes/
git clone https://github.com/your-username/comfyui-prompt-db.git
```
Restart ComfyUI. No additional dependencies required.
---
## 📋 Nodes
### Prompt DB Manager
Opens the Prompt Database web interface. Add it to your workflow and click the **"📂 Open Manager"** button.
**Outputs:** None (management only)
---
### Prompt Selector
Select a single prompt from a category.
**Outputs:**
| Output | Type | Description |
|--------|------|-------------|
| `prompt_text` | STRING | The selected prompt's text |
| `db_selection` | DB_SELECTION | Category name (connect to Combiner) |
| `lora_stack` | LORA_STACK | LoRAs attached to this prompt |
---
### Prompt Combiner
Accepts multiple `DB_SELECTION` inputs (from Prompt Selectors). Each connected input shows a dropdown for selecting a prompt within that category. Combines all selected prompts into one string.
**Inputs:**
- `separator` — how to join prompts: `, ` / newline / space / none
- `selection_1..N` — DB_SELECTION inputs (dynamic, auto-expands)
**Outputs:**
| Output | Type | Description |
|--------|------|-------------|
| `combined_prompt` | STRING | All selected prompts joined by separator |
| `lora_stack` | LORA_STACK | Merged LoRAs from all selected prompts |
**Features:**
- **Inline mute toggle** (● circle) — mute individual categories without disconnecting
- **◀ ▶ arrows** — cycle through prompts quickly
- **Click prompt name** — opens dropdown for full selection
- Prompts are combined **in input order** (selection_1, selection_2, ...)
---
### Apply Lora Stack
Applies a `LORA_STACK` to model and clip.
**Inputs:**
| Input | Type | Description |
|-------|------|-------------|
| `model` | MODEL | Base model |
| `clip` | CLIP | CLIP model |
| `lora_stack` | LORA_STACK | LoRAs to apply |
**Outputs:** `model`, `clip` (with LoRAs applied)
**Features:**
- **Per-LoRA toggle** — click the circle (●) to enable/disable individual LoRAs
- **Strength control** — ◀ ▶ arrows to adjust, supports -2.0 to 2.0 range
- **Reset Values** button — restores all strengths to prompt defaults
- Missing LoRA files are skipped gracefully with a console warning
---
## 🔧 Typical Workflow
```
[Prompt Selector: Character] ──► selection_1 ─┐
[Prompt Selector: Mood] ──► selection_2 ─┤
[Prompt Selector: Clothes] ──► selection_3 ─┼──► [Prompt Combiner] ──► combined_prompt ──► [CLIP Text Encode]
[Prompt Selector: Action] ──► selection_4 ─┤ │
[Prompt Selector: Location] ──► selection_5 ─┘ └── lora_stack ──► [Apply Lora Stack] ──► model/clip
```
1. Add a **Prompt DB Manager** node and create your categories & prompts
2. Add **Prompt Selector** nodes for each category
3. Connect them to a **Prompt Combiner** to merge everything
4. Route the `lora_stack` output to **Apply Lora Stack**
---
## 📁 Data Storage
Prompts are stored in `prompts.json` in the node pack directory. Back up this file or use the **Export** feature in the web UI.
---
## ⌨️ Keyboard Shortcuts (Web UI)
| Shortcut | Action |
|----------|--------|
| `Ctrl + N` | Create new prompt |
| `/` | Focus search bar |
| `Esc` | Close drawer / modal |
---
## 📄 License
MIT
+28
View File
@@ -0,0 +1,28 @@
"""
ComfyUI Custom Node Pack: Prompt Database
==========================================
Allows storing, categorizing, and selecting prompts from a local JSON database.
"""
from .nodes import PromptDBManager, PromptSelector, PromptCombiner, ApplyLoraStack
# Register API routes on import
from . import api_routes # noqa: F401
NODE_CLASS_MAPPINGS = {
"PromptDBManager": PromptDBManager,
"PromptSelector": PromptSelector,
"PromptCombiner": PromptCombiner,
"ApplyLoraStack": ApplyLoraStack,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"PromptDBManager": "Prompt DB Manager",
"PromptSelector": "Prompt Selector",
"PromptCombiner": "Prompt Combiner",
"ApplyLoraStack": "Apply Lora Stack",
}
WEB_DIRECTORY = "./web"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
+177
View File
@@ -0,0 +1,177 @@
"""
REST API routes for Prompt Database CRUD operations.
Registered with ComfyUI's PromptServer on import.
"""
import os
from aiohttp import web
from server import PromptServer
from . import db
HTML_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "web")
routes = PromptServer.instance.routes
@routes.get("/prompt_db/manager")
async def serve_manager(request):
"""Serve the Prompt Database Manager HTML page."""
html_path = os.path.join(HTML_DIR, "prompt_manager.html")
return web.FileResponse(html_path)
@routes.get("/prompt_db/categories")
async def get_categories(request):
categories = db.get_categories()
return web.json_response({"categories": categories})
@routes.get("/prompt_db/prompts")
async def get_prompts(request):
category = request.query.get("category", None)
prompts = db.get_prompts(category)
return web.json_response({"prompts": prompts})
@routes.get("/prompt_db/all")
async def get_all(request):
data = db.load_db()
return web.json_response(data)
@routes.get("/prompt_db/loras")
async def get_loras(request):
"""Return list of available LoRA files from ComfyUI's models folder."""
loras = db.get_available_loras()
return web.json_response({"loras": loras})
@routes.post("/prompt_db/prompt")
async def add_prompt(request):
try:
body = await request.json()
category = body.get("category", "")
name = body.get("name", "")
text = body.get("text", "")
loras = body.get("loras", [])
if not category or not name:
return web.json_response(
{"error": "category and name are required"}, status=400
)
prompt = db.add_prompt(category, name, text, loras=loras)
return web.json_response({"prompt": prompt})
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
@routes.put("/prompt_db/prompt/{prompt_id}")
async def update_prompt(request):
try:
prompt_id = request.match_info["prompt_id"]
body = await request.json()
result = db.update_prompt(
prompt_id,
category=body.get("category"),
name=body.get("name"),
text=body.get("text"),
loras=body.get("loras"),
)
if result:
return web.json_response({"prompt": result})
return web.json_response({"error": "Prompt not found"}, status=404)
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
@routes.delete("/prompt_db/prompt/{prompt_id}")
async def delete_prompt(request):
try:
prompt_id = request.match_info["prompt_id"]
if db.delete_prompt(prompt_id):
return web.json_response({"success": True})
return web.json_response({"error": "Prompt not found"}, status=404)
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
@routes.post("/prompt_db/category")
async def add_category(request):
try:
body = await request.json()
name = body.get("name", "")
if not name:
return web.json_response({"error": "name is required"}, status=400)
if db.add_category(name):
return web.json_response({"success": True})
return web.json_response({"error": "Category already exists"}, status=409)
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
@routes.delete("/prompt_db/category/{name}")
async def delete_category(request):
try:
name = request.match_info["name"]
if db.delete_category(name):
return web.json_response({"success": True})
return web.json_response({"error": "Category not found"}, status=404)
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
@routes.put("/prompt_db/categories/reorder")
async def reorder_categories(request):
try:
body = await request.json()
ordered = body.get("categories", [])
if not ordered:
return web.json_response({"error": "categories list required"}, status=400)
if db.reorder_categories(ordered):
return web.json_response({"success": True})
return web.json_response(
{"error": "Category list does not match existing categories"}, status=400
)
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
@routes.put("/prompt_db/prompts/reorder")
async def reorder_prompts(request):
try:
body = await request.json()
ordered_ids = body.get("prompt_ids", [])
if not ordered_ids:
return web.json_response({"error": "prompt_ids list required"}, status=400)
if db.reorder_prompts(ordered_ids):
return web.json_response({"success": True})
return web.json_response({"error": "Reorder failed"}, status=400)
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
@routes.get("/prompt_db/export")
async def export_db_route(request):
try:
data = db.export_db()
return web.json_response(data)
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
@routes.post("/prompt_db/import")
async def import_db_route(request):
try:
body = await request.json()
mode = body.get("mode", "merge")
incoming = body.get("data")
if not incoming or "categories" not in incoming or "prompts" not in incoming:
return web.json_response(
{"error": "Invalid import data: must have categories and prompts"},
status=400,
)
stats = db.import_db(incoming, mode)
return web.json_response({"success": True, **stats})
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
+279
View File
@@ -0,0 +1,279 @@
"""
Thread-safe JSON database for storing prompts with categories.
Prompts can optionally have LoRA attachments.
"""
import json
import os
import uuid
import threading
DB_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "prompts.json")
_lock = threading.Lock()
DEFAULT_DB = {
"categories": ["Location", "Character", "Style", "Mood"],
"prompts": []
}
def load_db():
"""Load the database from disk. Returns default if file missing or corrupted."""
with _lock:
if not os.path.exists(DB_FILE):
save_db_unsafe(DEFAULT_DB)
return DEFAULT_DB.copy()
try:
with open(DB_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
if "categories" not in data or "prompts" not in data:
data = DEFAULT_DB.copy()
save_db_unsafe(data)
# Ensure all prompts have a loras field
for p in data.get("prompts", []):
if "loras" not in p:
p["loras"] = []
return data
except (json.JSONDecodeError, IOError):
save_db_unsafe(DEFAULT_DB)
return DEFAULT_DB.copy()
def save_db_unsafe(data):
"""Save database without acquiring lock (caller must hold lock)."""
with open(DB_FILE, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
def save_db(data):
"""Save database to disk (thread-safe)."""
with _lock:
save_db_unsafe(data)
def get_categories():
"""Return list of category names."""
data = load_db()
return data.get("categories", [])
def get_prompts(category=None):
"""Return prompts, optionally filtered by category."""
data = load_db()
prompts = data.get("prompts", [])
if category:
prompts = [p for p in prompts if p.get("category") == category]
return prompts
def get_prompt_by_id(prompt_id):
"""Return a single prompt by its ID, or None."""
data = load_db()
for p in data.get("prompts", []):
if p.get("id") == prompt_id:
return p
return None
def get_prompt_by_name(name, category=None):
"""Return a single prompt by its name and optionally category, or None."""
data = load_db()
for p in data.get("prompts", []):
if p.get("name") == name:
if category is None or p.get("category") == category:
return p
return None
def add_prompt(category, name, text, loras=None):
"""Add a new prompt. Returns the new prompt dict."""
with _lock:
data = _load_unsafe()
prompt = {
"id": str(uuid.uuid4()),
"category": category,
"name": name,
"text": text,
"loras": loras or [],
}
data["prompts"].append(prompt)
save_db_unsafe(data)
return prompt
def update_prompt(prompt_id, category=None, name=None, text=None, loras=None):
"""Update an existing prompt by ID. Returns updated prompt or None."""
with _lock:
data = _load_unsafe()
for p in data["prompts"]:
if p["id"] == prompt_id:
if category is not None:
p["category"] = category
if name is not None:
p["name"] = name
if text is not None:
p["text"] = text
if loras is not None:
p["loras"] = loras
save_db_unsafe(data)
return p
return None
def delete_prompt(prompt_id):
"""Delete a prompt by ID. Returns True if found and deleted."""
with _lock:
data = _load_unsafe()
original_len = len(data["prompts"])
data["prompts"] = [p for p in data["prompts"] if p["id"] != prompt_id]
if len(data["prompts"]) < original_len:
save_db_unsafe(data)
return True
return False
def add_category(name):
"""Add a new category. Returns True if added, False if already exists."""
with _lock:
data = _load_unsafe()
if name in data["categories"]:
return False
data["categories"].append(name)
save_db_unsafe(data)
return True
def delete_category(name):
"""Delete a category and all its prompts. Returns True if found and deleted."""
with _lock:
data = _load_unsafe()
if name not in data["categories"]:
return False
data["categories"].remove(name)
data["prompts"] = [p for p in data["prompts"] if p["category"] != name]
save_db_unsafe(data)
return True
def reorder_categories(ordered_names):
"""Reorder categories to match the given list. Returns True if successful."""
with _lock:
data = _load_unsafe()
existing = set(data["categories"])
given = set(ordered_names)
if existing != given:
return False
data["categories"] = list(ordered_names)
save_db_unsafe(data)
return True
def reorder_prompts(ordered_ids):
"""Reorder prompts to match the given ID list. Unmentioned prompts keep their relative order at the end."""
with _lock:
data = _load_unsafe()
by_id = {p["id"]: p for p in data["prompts"]}
reordered = []
for pid in ordered_ids:
if pid in by_id:
reordered.append(by_id.pop(pid))
# Append any remaining prompts not in the ordered list
for p in data["prompts"]:
if p["id"] in by_id:
reordered.append(p)
data["prompts"] = reordered
save_db_unsafe(data)
return True
def get_available_loras():
"""Return list of available LoRA filenames from ComfyUI's models folder."""
try:
import folder_paths
return folder_paths.get_filename_list("loras")
except Exception:
return []
def export_db():
"""Return full database dict for export."""
return load_db()
def import_db(incoming, mode="merge"):
"""Import prompts from an incoming dict.
mode='overwrite': replace entire DB.
mode='merge': add new categories and prompts, skip duplicates (by name+category).
Returns dict with stats: {categories_added, prompts_added, prompts_skipped}.
"""
with _lock:
if mode == "overwrite":
# Ensure all prompts have IDs and loras
for p in incoming.get("prompts", []):
if "id" not in p:
p["id"] = str(uuid.uuid4())
if "loras" not in p:
p["loras"] = []
save_db_unsafe(incoming)
return {
"categories_added": len(incoming.get("categories", [])),
"prompts_added": len(incoming.get("prompts", [])),
"prompts_skipped": 0,
}
# Merge mode
data = _load_unsafe()
cats_added = 0
prompts_added = 0
prompts_skipped = 0
# Add missing categories
existing_cats = set(data["categories"])
for cat in incoming.get("categories", []):
if cat not in existing_cats:
data["categories"].append(cat)
existing_cats.add(cat)
cats_added += 1
# Add prompts that don't exist yet (by name + category)
existing_prompts = {(p["name"], p["category"]) for p in data["prompts"]}
for p in incoming.get("prompts", []):
key = (p.get("name", ""), p.get("category", ""))
if key in existing_prompts:
prompts_skipped += 1
continue
prompt = {
"id": p.get("id") or str(uuid.uuid4()),
"name": p.get("name", ""),
"category": p.get("category", ""),
"text": p.get("text", ""),
"loras": p.get("loras", []),
}
data["prompts"].append(prompt)
existing_prompts.add(key)
prompts_added += 1
save_db_unsafe(data)
return {
"categories_added": cats_added,
"prompts_added": prompts_added,
"prompts_skipped": prompts_skipped,
}
def _load_unsafe():
"""Load database without lock (internal use only)."""
if not os.path.exists(DB_FILE):
save_db_unsafe(DEFAULT_DB)
return DEFAULT_DB.copy()
try:
with open(DB_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
# Ensure all prompts have loras field
for p in data.get("prompts", []):
if "loras" not in p:
p["loras"] = []
return data
except (json.JSONDecodeError, IOError):
save_db_unsafe(DEFAULT_DB)
return DEFAULT_DB.copy()
+253
View File
@@ -0,0 +1,253 @@
"""
ComfyUI custom nodes for Prompt Database.
"""
import json as _json
import re as _re
import folder_paths
def _clean_name(name):
"""Strip the 📖 LoRA marker suffix added by the JS frontend."""
return _re.sub(r'\s*📖$', '', name).strip()
import comfy.sd
import comfy.utils
from . import db
class PromptDBManager:
"""
Management node — provides a UI button to open the prompt database manager.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {},
"hidden": {
"unique_id": "UNIQUE_ID",
},
}
RETURN_TYPES = ()
FUNCTION = "manage"
CATEGORY = "Prompt Database"
OUTPUT_NODE = True
def manage(self, unique_id=None):
return {}
class PromptSelector:
"""
Selector node — pick a category and prompt.
Outputs:
- prompt_text (STRING): the selected prompt text (no LoRA tags)
- db_selection (DB_SELECTION): the category name (for PromptCombiner)
- lora_stack (LORA_STACK): LoRAs attached to the selected prompt
"""
@classmethod
def INPUT_TYPES(cls):
categories = db.get_categories()
if not categories:
categories = ["(no categories)"]
all_prompts = db.get_prompts()
prompt_names = list({p["name"] for p in all_prompts})
if not prompt_names:
prompt_names = ["(no prompts)"]
return {
"required": {
"category": (categories, {"default": categories[0]}),
"prompt_name": (prompt_names, {"default": prompt_names[0]}),
},
}
RETURN_TYPES = ("STRING", "DB_SELECTION", "LORA_STACK")
RETURN_NAMES = ("prompt_text", "db_selection", "lora_stack")
FUNCTION = "select_prompt"
CATEGORY = "Prompt Database"
def select_prompt(self, category, prompt_name):
prompt_name = _clean_name(prompt_name)
prompt = db.get_prompt_by_name(prompt_name, category)
if not prompt:
prompt = db.get_prompt_by_name(prompt_name)
if not prompt:
return ("", category, [])
text = prompt["text"]
loras = prompt.get("loras", [])
# Normalize lora entries
lora_stack = []
for l in loras:
lora_stack.append({
"name": l.get("name", ""),
"strength": float(l.get("strength", 1.0)),
})
return (text, category, lora_stack)
@classmethod
def IS_CHANGED(cls, category, prompt_name):
prompt_name = _clean_name(prompt_name)
prompt = db.get_prompt_by_name(prompt_name, category)
if prompt:
return prompt["text"] + str(prompt.get("loras", []))
return ""
class PromptCombiner:
"""
Combiner node — accepts multiple DB_SELECTION inputs (category names),
shows a prompt dropdown per category inside the node,
and combines selected prompts into one STRING + merged LORA_STACK.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"separator": (
["comma_space", "newline", "space", "none"],
{"default": "comma_space"},
),
"prompt_selections": (
"STRING",
{"default": "{}", "multiline": False},
),
},
"optional": {
"selection_1": ("DB_SELECTION", {"forceInput": True}),
},
}
RETURN_TYPES = ("STRING", "LORA_STACK")
RETURN_NAMES = ("combined_prompt", "lora_stack")
FUNCTION = "combine"
CATEGORY = "Prompt Database"
OUTPUT_NODE = True
def combine(self, separator="comma_space", prompt_selections="{}", **kwargs):
sep_map = {
"comma_space": ", ",
"newline": "\n",
"space": " ",
"none": "",
}
sep = sep_map.get(separator, ", ")
try:
selections = _json.loads(prompt_selections)
except _json.JSONDecodeError:
selections = {}
texts = []
merged_loras = []
seen_loras = set()
selection_keys = [k for k in kwargs.keys() if k.startswith("selection_")]
selection_keys.sort(key=lambda k: int(k.split("_")[1]))
for key in selection_keys:
category = kwargs[key]
if not category:
continue
prompt_name = _clean_name(selections.get(key, ""))
if not prompt_name:
continue
prompt = db.get_prompt_by_name(prompt_name, category)
if prompt:
texts.append(prompt["text"])
# Collect LoRAs (deduplicate by name)
for l in prompt.get("loras", []):
lname = l.get("name", "")
if lname and lname not in seen_loras:
seen_loras.add(lname)
merged_loras.append({
"name": lname,
"strength": float(l.get("strength", 1.0)),
})
combined = sep.join(texts)
return (combined, merged_loras)
@classmethod
def IS_CHANGED(cls, separator, prompt_selections="{}", **kwargs):
return prompt_selections + str(kwargs)
class ApplyLoraStack:
"""
Applies LoRAs from a LORA_STACK to a model and clip.
The lora_config hidden widget stores per-LoRA overrides
(enable/disable + custom strength).
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("MODEL",),
"clip": ("CLIP",),
"lora_config": (
"STRING",
{"default": "{}", "multiline": False},
),
},
"optional": {
"lora_stack": ("LORA_STACK",),
},
}
RETURN_TYPES = ("MODEL", "CLIP")
RETURN_NAMES = ("model", "clip")
FUNCTION = "apply_loras"
CATEGORY = "Prompt Database"
def apply_loras(self, model, clip, lora_config="{}", lora_stack=None):
if not lora_stack:
return (model, clip)
# Parse overrides
try:
config = _json.loads(lora_config)
except _json.JSONDecodeError:
config = {}
for lora_entry in lora_stack:
name = lora_entry.get("name", "")
default_strength = lora_entry.get("strength", 1.0)
if not name:
continue
# Apply per-LoRA overrides from config
override = config.get(name, {})
if not override.get("enabled", True):
print(f"[PromptDB] LoRA skipped (disabled): {name}")
continue
strength = float(override.get("strength", default_strength))
# Load LoRA file
lora_path = folder_paths.get_full_path("loras", name)
if not lora_path:
print(f"[PromptDB] LoRA file not found: {name}")
continue
try:
lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
model, clip = comfy.sd.load_lora_for_models(
model, clip, lora, strength, strength
)
print(f"[PromptDB] LoRA applied: {name} (strength={strength:.2f})")
except Exception as e:
print(f"[PromptDB] Error loading LoRA {name}: {e}")
return (model, clip)
@classmethod
def IS_CHANGED(cls, model, clip, lora_config="{}", lora_stack=None):
return str(lora_stack) + lora_config
View File