aliensmn's picture
Mirror from https://github.com/sammykumar/ComfyUI-SwissArmyKnife
0997c23 verified
"""
Web API endpoints for ND Super Nodes
"""
from aiohttp import web
import os
try:
import folder_paths
except Exception:
folder_paths = None
from .lora_utils import get_available_loras, extract_trigger_words
from .template_manager import get_template_manager
from ..civitai_service import get_civitai_service
from .version_utils import get_local_version
async def get_loras(request):
"""Get list of available LoRA files"""
try:
loras = get_available_loras()
return web.json_response({"loras": loras})
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
async def get_files(request):
"""Generic file lister using ComfyUI folder_paths (e.g., folder_name=loras|vae|checkpoints)"""
try:
folder_name = request.rel_url.query.get("folder_name")
ext_param = request.rel_url.query.get("extensions", "")
extensions = [e.strip().lower() for e in ext_param.split(",") if e.strip()]
if not folder_name:
return web.json_response({"error": "folder_name is required", "files": []}, status=400)
if folder_paths is None:
return web.json_response({"error": "folder_paths unavailable", "files": []}, status=500)
# Map legacy names and resolve directories
mapped = folder_paths.map_legacy(folder_name)
dirs, supported = folder_paths.folder_names_and_paths.get(mapped, ([], set()))
if not dirs:
# try direct
dirs, supported = folder_paths.folder_names_and_paths.get(folder_name, ([], set()))
# Filter extensions
if extensions:
supported = set([e.lower() for e in extensions])
out_files = []
for d in dirs:
if not os.path.isdir(d):
continue
try:
# Recurse into subfolders
for root, _, files in os.walk(d):
for name in files:
fp = os.path.join(root, name)
if not os.path.isfile(fp):
continue
_, ext = os.path.splitext(name)
if supported and ext.lower() not in supported and supported != {""}:
continue
st = os.stat(fp)
out_files.append({
"name": name,
"path": os.path.relpath(fp, d).replace("\\", "/"),
"extension": ext.lower(),
"size": st.st_size,
"modified": st.st_mtime
})
except Exception:
continue
out_files.sort(key=lambda x: x["name"].lower())
return web.json_response({"files": out_files, "total": len(out_files)})
except Exception as e:
return web.json_response({"error": str(e), "files": []}, status=500)
async def get_templates(request):
"""Get list of available templates or a specific template by query param"""
try:
template_manager = get_template_manager()
# Support GET /super_lora/templates?name=Foo for compatibility
name = request.rel_url.query.get("name")
if name:
template = template_manager.load_template(name)
if template:
return web.json_response(template)
return web.json_response({"error": "Template not found"}, status=404)
templates = template_manager.list_templates()
return web.json_response({"templates": templates})
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
async def save_template(request):
"""Save a LoRA template or handle action-based operations (e.g., delete)"""
try:
data = await request.json()
action = data.get("action")
# Backward-compatible action handler: POST with { action: 'delete', name }
if action == "delete":
name = data.get("name")
if not name:
return web.json_response({"error": "Template name is required"}, status=400)
template_manager = get_template_manager()
deleted = template_manager.delete_template(name)
if deleted:
return web.json_response({"success": True, "message": f"Template '{name}' deleted"})
return web.json_response({"error": "Template not found or could not be deleted"}, status=404)
name = data.get("name")
# Accept both 'lora_configs' (preferred) and 'loras' (compat)
lora_configs = data.get("lora_configs")
if lora_configs is None:
lora_configs = data.get("loras", [])
if not name:
return web.json_response({"error": "Template name is required"}, status=400)
template_manager = get_template_manager()
success = template_manager.save_template(name, lora_configs)
if success:
return web.json_response({"success": True, "message": f"Template '{name}' saved"})
else:
return web.json_response({"error": "Failed to save template"}, status=500)
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
async def load_template(request):
"""Load a LoRA template"""
try:
template_name = request.match_info.get("name")
if not template_name:
return web.json_response({"error": "Template name is required"}, status=400)
template_manager = get_template_manager()
template_data = template_manager.load_template(template_name)
if template_data:
return web.json_response(template_data)
else:
return web.json_response({"error": "Template not found"}, status=404)
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
async def get_civitai_info(request):
"""Get CivitAI info for a LoRA"""
try:
data = await request.json()
lora_filename = data.get("lora_filename")
if not lora_filename:
return web.json_response({"error": "LoRA filename is required"}, status=400)
civitai_service = get_civitai_service()
trigger_words = civitai_service.get_trigger_words_by_filename(lora_filename)
# Fallback: try extracting from LoRA metadata if CivitAI returns nothing
if not trigger_words:
try:
meta_words = extract_trigger_words(lora_filename)
if meta_words:
trigger_words = meta_words
except Exception:
pass
# Return both 'trigger_words' (our API) and 'trainedWords' (frontend compatibility)
payload = {
"lora_filename": lora_filename,
"trigger_words": trigger_words,
"trainedWords": trigger_words,
"success": True
}
return web.json_response(payload)
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
async def delete_template(request):
"""Delete a template via JSON body: { name }"""
try:
data = await request.json()
name = data.get("name")
if not name:
return web.json_response({"error": "Template name is required"}, status=400)
template_manager = get_template_manager()
deleted = template_manager.delete_template(name)
if deleted:
return web.json_response({"success": True, "message": f"Template '{name}' deleted"})
return web.json_response({"error": "Template not found"}, status=404)
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
async def delete_template_by_name(request):
"""Delete a template by path parameter"""
try:
name = request.match_info.get("name")
if not name:
return web.json_response({"error": "Template name is required"}, status=400)
template_manager = get_template_manager()
deleted = template_manager.delete_template(name)
if deleted:
return web.json_response({"success": True, "message": f"Template '{name}' deleted"})
return web.json_response({"error": "Template not found"}, status=404)
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
async def get_version_info(request):
"""Return local version info only (forked version, no update checking)."""
try:
local_version = get_local_version()
return web.json_response({
"localVersion": local_version,
"hasUpdate": False,
"message": "This is a forked version. Update checking disabled."
})
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
# Route registration function
def register_routes(app):
"""Register all Super LoRA Loader routes"""
app.router.add_get("/super_lora/loras", get_loras)
app.router.add_get("/super_lora/files", get_files)
app.router.add_get("/super_lora/templates", get_templates)
app.router.add_post("/super_lora/templates", save_template)
app.router.add_get("/super_lora/templates/{name}", load_template)
# Deletion endpoints (compatibility and RESTful)
app.router.add_delete("/super_lora/templates", delete_template) # expects JSON body { name }
app.router.add_post("/super_lora/templates/delete", delete_template) # expects JSON body { name }
app.router.add_delete("/super_lora/templates/{name}", delete_template_by_name)
app.router.add_post("/super_lora/civitai_info", get_civitai_info)
app.router.add_get("/super_lora/version", get_version_info)
# Legacy aliases without underscore for older frontends / workflows
app.router.add_get("/superlora/loras", get_loras)
app.router.add_get("/superlora/files", get_files)
app.router.add_get("/superlora/templates", get_templates)
app.router.add_post("/superlora/templates", save_template)
app.router.add_get("/superlora/templates/{name}", load_template)
app.router.add_delete("/superlora/templates", delete_template)
app.router.add_post("/superlora/templates/delete", delete_template)
app.router.add_delete("/superlora/templates/{name}", delete_template_by_name)
app.router.add_post("/superlora/civitai_info", get_civitai_info)
app.router.add_get("/superlora/version", get_version_info)