Files
2026-03-21 16:05:08 +05:00

280 lines
8.6 KiB
Python

"""
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()