Add Local Database status and update feature for model-list.json

Add backend endpoints (/model_linker/model-list/status, /model_linker/model-list/update)
that let users check whether their bundled ComfyUI-Manager model-list.json is current
and download the latest version from GitHub, with SHA tracking and cache invalidation.

Add corresponding UI in the Options dialog under a new 'Local Database' section
with status display, remote check, and update controls.
This commit is contained in:
Dariusz L
2026-06-04 19:48:12 +02:00
parent 8d7799e0af
commit f9e8ce3eb4
6 changed files with 613 additions and 71 deletions
+31
View File
@@ -94,6 +94,8 @@ class ModelLinkerExtension:
from .core.sources.model_list import (
search_model_list,
search_model_list_multiple,
get_model_list_update_status,
update_model_list_from_remote,
)
from .core.sources.huggingface import (
search_huggingface_for_file,
@@ -1266,6 +1268,35 @@ class ModelLinkerExtension:
log_exception(f"Clear search cache error: {e}")
return web.json_response({"error": str(e)}, status=500)
@routes.get("/model_linker/model-list/status")
async def model_list_status_route(request):
"""Return local model-list status and optionally compare with GitHub."""
try:
check_remote = (
str(request.query.get("check_remote", "")).lower()
in {"1", "true", "yes"}
)
return web.json_response(
get_model_list_update_status(check_remote=check_remote)
)
except Exception as e:
log_exception(f"Model list status error: {e}")
return web.json_response({"error": str(e)}, status=500)
@routes.post("/model_linker/model-list/update")
async def model_list_update_route(request):
"""Download latest ComfyUI-Manager model-list.json."""
try:
result = await asyncio.to_thread(update_model_list_from_remote)
clear_huggingface_search_cache()
clear_civitai_search_cache()
clear_civarchive_search_cache()
clear_lora_manager_archive_search_cache()
return web.json_response(result)
except Exception as e:
log_exception(f"Model list update error: {e}")
return web.json_response({"error": str(e)}, status=500)
@routes.post("/model_linker/download")
async def download_model(request):
"""Start downloading a model."""
+167
View File
@@ -6,8 +6,13 @@ Search the ComfyUI Manager model-list.json database with fuzzy matching.
import os
import json
import hashlib
import shutil
import tempfile
from datetime import datetime, timezone
from typing import Dict, Any, Optional, List
from difflib import SequenceMatcher
from urllib.request import Request, urlopen
from ..log_system.log_funcs import (
log_debug,
@@ -22,11 +27,102 @@ METADATA_DIR = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "metadata"
)
MODEL_LIST_FILE = os.path.join(METADATA_DIR, "model-list.json")
MODEL_LIST_META_FILE = os.path.join(METADATA_DIR, "model-list.meta.json")
MODEL_LIST_SOURCE_URL = (
"https://raw.githubusercontent.com/Comfy-Org/ComfyUI-Manager/main/model-list.json"
)
MODEL_LIST_GITHUB_API_URL = (
"https://api.github.com/repos/Comfy-Org/ComfyUI-Manager/contents/model-list.json?ref=main"
)
HTTP_TIMEOUT = 30
# Cache for loaded data
_model_list_cache: Optional[List[Dict]] = None
def _utc_now_iso() -> str:
return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
def _fetch_json_url(url: str) -> Dict[str, Any]:
request = Request(
url,
headers={
"Accept": "application/json",
"User-Agent": "comfyui-model-linker",
},
)
with urlopen(request, timeout=HTTP_TIMEOUT) as response:
return json.loads(response.read().decode("utf-8"))
def _read_json_file(path: str, default: Any) -> Any:
try:
if os.path.exists(path):
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
except Exception as e:
log_warn(f"Error reading {os.path.basename(path)}: {e}")
return default
def _write_json_file_atomic(path: str, data: Any):
os.makedirs(os.path.dirname(path), exist_ok=True)
fd, tmp_path = tempfile.mkstemp(
prefix=f".{os.path.basename(path)}.",
suffix=".tmp",
dir=os.path.dirname(path),
text=True,
)
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
f.write("\n")
os.replace(tmp_path, path)
except Exception:
try:
os.remove(tmp_path)
except OSError:
pass
raise
def _read_model_list_file() -> Dict[str, Any]:
return _read_json_file(MODEL_LIST_FILE, {"models": []})
def _read_model_list_meta() -> Dict[str, Any]:
return _read_json_file(MODEL_LIST_META_FILE, {})
def _get_local_model_list_sha() -> str:
try:
with open(MODEL_LIST_FILE, "rb") as f:
content = f.read()
header = f"blob {len(content)}\0".encode("utf-8")
return hashlib.sha1(header + content).hexdigest()
except Exception as e:
log_warn(f"Error calculating model-list.json SHA: {e}")
return ""
def _get_remote_model_list_info() -> Dict[str, Any]:
data = _fetch_json_url(MODEL_LIST_GITHUB_API_URL)
return {
"sha": data.get("sha") or "",
"size": data.get("size") or 0,
"download_url": data.get("download_url") or MODEL_LIST_SOURCE_URL,
"html_url": data.get("html_url") or "https://github.com/Comfy-Org/ComfyUI-Manager/blob/main/model-list.json",
}
def reload_model_list():
"""Clear in-memory cache so the next search reads model-list.json again."""
global _model_list_cache
_model_list_cache = None
_load_model_list()
def _load_model_list() -> List[Dict]:
"""Load model list database."""
global _model_list_cache
@@ -50,6 +146,77 @@ def _load_model_list() -> List[Dict]:
return _model_list_cache
def get_model_list_update_status(check_remote: bool = False) -> Dict[str, Any]:
"""Return local model-list metadata and optionally compare it with GitHub."""
data = _read_model_list_file()
models = data.get("models", []) if isinstance(data, dict) else []
meta = _read_model_list_meta()
local_sha = meta.get("sha") or _get_local_model_list_sha()
local_updated_at = meta.get("updated_at") or ""
status = {
"source_url": MODEL_LIST_SOURCE_URL,
"github_url": "https://github.com/Comfy-Org/ComfyUI-Manager/blob/main/model-list.json",
"local_count": len(models) if isinstance(models, list) else 0,
"local_sha": local_sha,
"local_updated_at": local_updated_at,
"remote_sha": "",
"remote_size": 0,
"remote_checked_at": "",
"update_available": False,
"can_compare": bool(local_sha),
}
if check_remote:
remote = _get_remote_model_list_info()
remote_sha = remote.get("sha") or ""
status.update(
{
"remote_sha": remote_sha,
"remote_size": remote.get("size") or 0,
"remote_checked_at": _utc_now_iso(),
"update_available": bool(remote_sha and local_sha and remote_sha != local_sha),
"can_compare": bool(local_sha and remote_sha),
}
)
return status
def update_model_list_from_remote() -> Dict[str, Any]:
"""Download the latest ComfyUI-Manager model-list.json and refresh cache."""
remote = _get_remote_model_list_info()
download_url = remote.get("download_url") or MODEL_LIST_SOURCE_URL
data = _fetch_json_url(download_url)
models = data.get("models", []) if isinstance(data, dict) else []
if not isinstance(models, list) or not models:
raise ValueError("Downloaded model-list.json does not contain a non-empty models list")
if os.path.exists(MODEL_LIST_FILE):
shutil.copy2(MODEL_LIST_FILE, f"{MODEL_LIST_FILE}.bak")
_write_json_file_atomic(MODEL_LIST_FILE, data)
meta = {
"source": "Comfy-Org/ComfyUI-Manager",
"source_url": MODEL_LIST_SOURCE_URL,
"github_url": remote.get("html_url")
or "https://github.com/Comfy-Org/ComfyUI-Manager/blob/main/model-list.json",
"sha": remote.get("sha") or "",
"size": remote.get("size") or 0,
"model_count": len(models),
"updated_at": _utc_now_iso(),
}
_write_json_file_atomic(MODEL_LIST_META_FILE, meta)
reload_model_list()
return {
**get_model_list_update_status(check_remote=False),
"updated": True,
"remote_sha": meta["sha"],
"remote_size": meta["size"],
}
def _normalize_filename(filename: str) -> str:
"""Normalize filename for comparison."""
# Remove extension
+199 -69
View File
@@ -572,7 +572,6 @@
"url": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors",
"size": "49.6MB"
},
{
"name": "SDXL Lightning LoRA (2steps)",
"type": "lora",
@@ -606,7 +605,6 @@
"url": "https://huggingface.co/ByteDance/SDXL-Lightning/resolve/main/sdxl_lightning_8step_lora.safetensors",
"size": "393.9MB"
},
{
"name": "DMD2 LoRA (4steps)",
"type": "lora",
@@ -629,7 +627,6 @@
"url": "https://huggingface.co/tianweiy/DMD2/resolve/main/dmd2_sdxl_4step_lora_fp16.safetensors",
"size": "394MB"
},
{
"name": "Hyper-SD LoRA (8steps) - FLUX.1 [Dev]",
"type": "lora",
@@ -652,7 +649,6 @@
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-FLUX.1-dev-16steps-lora.safetensors",
"size": "1.39GB"
},
{
"name": "Hyper-SD LoRA (1step) - SD1.5",
"type": "lora",
@@ -719,7 +715,6 @@
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-12steps-CFG-lora.safetensors",
"size": "269MB"
},
{
"name": "Hyper-SD LoRA (1step) - SDXL",
"type": "lora",
@@ -786,7 +781,6 @@
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-12steps-CFG-lora.safetensors",
"size": "787MB"
},
{
"name": "Hyper-SD CFG LoRA (4steps) - SD3",
"type": "lora",
@@ -820,7 +814,6 @@
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD3-16steps-CFG-lora.safetensors",
"size": "472MB"
},
{
"name": "comfyanonymous/flux_text_encoders - t5xxl (fp16)",
"type": "clip",
@@ -854,7 +847,6 @@
"url": "https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/t5xxl_fp8_e4m3fn_scaled.safetensors",
"size": "5.16GB"
},
{
"name": "comfyanonymous/cosmos_cv8x8x8_1.0.safetensors",
"type": "VAE",
@@ -910,7 +902,6 @@
"url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-14B-Video2World.safetensors",
"size": "28.5GB"
},
{
"name": "google-t5/t5-base",
"type": "clip",
@@ -944,8 +935,6 @@
"url": "https://huggingface.co/mcmonkey/google_t5-v1_1-xxl_encoderonly/resolve/main/t5xxl_fp8_e4m3fn.safetensors",
"size": "4.89GB"
},
{
"name": "city96/t5-v1_1-xxl-encoder-Q3_K_L.gguf",
"type": "clip",
@@ -1067,7 +1056,6 @@
"url": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf/resolve/main/t5-v1_1-xxl-encoder-f32.gguf",
"size": "19.1GB"
},
{
"name": "Comfy-Org/clip_l",
"type": "clip",
@@ -1090,7 +1078,6 @@
"url": "https://huggingface.co/Comfy-Org/stable-diffusion-3.5-fp8/resolve/main/text_encoders/clip_g.safetensors",
"size": "1.39GB"
},
{
"name": "v1-5-pruned-emaonly.ckpt",
"type": "checkpoint",
@@ -1553,7 +1540,6 @@
"url": "https://huggingface.co/CiaraRowles/controlnet-temporalnet-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors",
"size": "5.00GB"
},
{
"name": "Comfy-Org/sigclip_vision_384 (patch14_384)",
"type": "clip_vision",
@@ -1565,7 +1551,6 @@
"url": "https://huggingface.co/Comfy-Org/sigclip_vision_384/resolve/main/sigclip_vision_patch14_384.safetensors",
"size": "857MB"
},
{
"name": "CLIPVision model (stabilityai/clip_vision_g)",
"type": "clip_vision",
@@ -1973,7 +1958,6 @@
"url": "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth",
"size": "375.0MB"
},
{
"name": "sam2.1_hiera_tiny.pt",
"type": "sam2.1",
@@ -2018,7 +2002,6 @@
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_large.pt",
"size": "857.0MB"
},
{
"name": "sam2_hiera_tiny.pt",
"type": "sam2",
@@ -2063,7 +2046,6 @@
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_large.pt",
"size": "857.0MB"
},
{
"name": "seecoder v1.0",
"type": "seecoder",
@@ -2394,7 +2376,6 @@
"url": "https://huggingface.co/Leoxing/PIA/resolve/main/pia.ckpt",
"size": "1.67GB"
},
{
"name": "animatediff/v2_lora_PanLeft.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
"type": "motion lora",
@@ -2879,9 +2860,6 @@
"url": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-FaceID-Plus/resolve/main/ipa-faceid-plus.bin",
"size": "2.39GB"
},
{
"name": "GFPGANv1.4.pth",
"type": "GFPGAN",
@@ -3509,7 +3487,6 @@
"url": "https://huggingface.co/ViperYX/RGT/resolve/main/RGT_S/RGT_S_x4.pth",
"size": "136.0MB"
},
{
"name": "InstantX/FLUX.1-dev Controlnet (Union)",
"type": "controlnet",
@@ -3521,8 +3498,6 @@
"url": "https://huggingface.co/InstantX/FLUX.1-dev-Controlnet-Union/resolve/main/diffusion_pytorch_model.safetensors",
"size": "6.6GB"
},
{
"name": "InstantX/FLUX.1-dev-IP-Adapter",
"type": "IP-Adapter",
@@ -3556,7 +3531,6 @@
"url": "https://huggingface.co/Kijai/flux-fp8/resolve/main/flux_shakker_labs_union_pro-fp8_e4m3fn.safetensors",
"size": "3.3GB"
},
{
"name": "jasperai/FLUX.1-dev-Controlnet-Upscaler",
"type": "controlnet",
@@ -3590,7 +3564,6 @@
"url": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Surface-Normals/resolve/main/diffusion_pytorch_model.safetensors",
"size": "3.58GB"
},
{
"name": "xinsir/ControlNet++: All-in-one ControlNet",
"type": "controlnet",
@@ -3690,7 +3663,6 @@
"url": "https://huggingface.co/xinsir/controlnet-tile-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors",
"size": "2.50GB"
},
{
"name": "InstantX/SD3-Controlnet-Canny",
"type": "controlnet",
@@ -3724,8 +3696,6 @@
"url": "https://huggingface.co/InstantX/SD3-Controlnet-Tile/resolve/main/diffusion_pytorch_model.safetensors",
"size": "1.19GB"
},
{
"name": "stabilityai/SD3.5-Large-Controlnet-Blur",
"type": "controlnet",
@@ -3759,7 +3729,6 @@
"url": "https://huggingface.co/stabilityai/stable-diffusion-3.5-controlnets/resolve/main/sd3.5_large_controlnet_depth.safetensors",
"size": "8.65GB"
},
{
"name": "Kijai/ToonCrafter model checkpoint (interpolation fp16)",
"type": "checkpoint",
@@ -3947,7 +3916,6 @@
"url": "https://huggingface.co/Kijai/DepthAnythingV2-safetensors/resolve/main/depth_anything_v2_vits_fp32.safetensors",
"size": "99.2MB"
},
{
"name": "PixArt-Sigma-XL-2-1024-MS.pth (checkpoint)",
"type": "checkpoint",
@@ -3959,7 +3927,6 @@
"url": "https://huggingface.co/PixArt-alpha/PixArt-Sigma/resolve/main/PixArt-Sigma-XL-2-1024-MS.pth",
"size": "2.47GB"
},
{
"name": "PixArt-Sigma-XL-2-512-MS.safetensors (diffusion)",
"type": "diffusion_model",
@@ -3993,8 +3960,6 @@
"url": "https://huggingface.co/PixArt-alpha/PixArt-XL-2-1024-MS/resolve/main/transformer/diffusion_pytorch_model.safetensors",
"size": "2.45GB"
},
{
"name": "hunyuan_dit_1.2.safetensors",
"type": "checkpoint",
@@ -4028,7 +3993,6 @@
"url": "https://huggingface.co/comfyanonymous/hunyuan_dit_comfyui/resolve/main/hunyuan_dit_1.0.safetensors",
"size": "8.24GB"
},
{
"name": "Comfy-Org/hunyuan_video_t2v_720p_bf16.safetensors",
"type": "diffusion_model",
@@ -4062,7 +4026,6 @@
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/diffusion_models/hunyuan_video_image_to_video_720p_bf16.safetensors",
"size": "25.6GB"
},
{
"name": "Comfy-Org/llava_llama3_fp8_scaled.safetensors",
"type": "clip",
@@ -4096,7 +4059,6 @@
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/clip_vision/llava_llama3_vision.safetensors",
"size": "649MB"
},
{
"name": "Comfy-Org/omnigen2_fp16.safetensors",
"type": "diffusion_model",
@@ -4119,7 +4081,6 @@
"url": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged/resolve/main/split_files/text_encoders/qwen_2.5_vl_fp16.safetensors",
"size": "7.51GB"
},
{
"name": "FLUX.1 [Schnell] Diffusion model",
"type": "diffusion_model",
@@ -4131,7 +4092,6 @@
"url": "https://huggingface.co/black-forest-labs/FLUX.1-schnell/resolve/main/flux1-schnell.safetensors",
"size": "23.8GB"
},
{
"name": "FLUX.1 VAE model",
"type": "VAE",
@@ -4143,7 +4103,6 @@
"url": "https://huggingface.co/black-forest-labs/FLUX.1-schnell/resolve/main/ae.safetensors",
"size": "335MB"
},
{
"name": "kijai/FLUX.1 [schnell] Diffusion model (float8_e4m3fn)",
"type": "diffusion_model",
@@ -4155,7 +4114,6 @@
"url": "https://huggingface.co/Kijai/flux-fp8/resolve/main/flux1-schnell-fp8.safetensors",
"size": "11.9GB"
},
{
"name": "FLUX.1 [Dev] Diffusion model (scaled fp8)",
"type": "diffusion_model",
@@ -4178,7 +4136,6 @@
"url": "https://huggingface.co/Kijai/flux-fp8/resolve/main/flux1-dev-fp8.safetensors",
"size": "11.9GB"
},
{
"name": "Comfy Org/FLUX.1 [dev] Checkpoint model (fp8)",
"type": "checkpoint",
@@ -4201,7 +4158,6 @@
"url": "https://huggingface.co/Comfy-Org/flux1-schnell/resolve/main/flux1-schnell-fp8.safetensors",
"size": "17.2GB"
},
{
"name": "city96/flux1-dev-F16.gguf",
"type": "diffusion_model",
@@ -4323,7 +4279,6 @@
"url": "https://huggingface.co/city96/FLUX.1-dev-gguf/resolve/main/flux1-dev-Q8_0.gguf",
"size": "12.7GB"
},
{
"name": "city96/flux1-schnell-F16.gguf",
"type": "diffusion_model",
@@ -4445,7 +4400,6 @@
"url": "https://huggingface.co/city96/FLUX.1-schnell-gguf/resolve/main/flux1-schnell-Q8_0.gguf",
"size": "12.7GB"
},
{
"name": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors [Long CLIP L]",
"type": "clip",
@@ -4468,7 +4422,6 @@
"url": "https://huggingface.co/zer0int/CLIP-GmP-ViT-L-14/resolve/main/ViT-L-14-TEXT-detail-improved-hiT-GmP-TE-only-HF.safetensors",
"size": "323MB"
},
{
"name": "Depth Pro model",
"type": "depth-pro",
@@ -4480,7 +4433,6 @@
"url": "https://huggingface.co/spacepxl/ml-depth-pro/resolve/main/depth_pro.fp16.safetensors",
"size": "1.9GB"
},
{
"name": "kijai/lotus depth d model v1.1 (fp16)",
"type": "diffusion_model",
@@ -4558,7 +4510,6 @@
"url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-normal-g-v1-0.safetensors",
"size": "3.47GB"
},
{
"name": "Kolors UNet model",
"type": "diffusion_model",
@@ -4614,7 +4565,6 @@
"url": "https://huggingface.co/Kijai/ChatGLM3-safetensors/resolve/main/chatglm3-fp16.safetensors",
"size": "12.52GB"
},
{
"name": "pulid_flux_v0.9.1.safetensors",
"type": "PuLID",
@@ -4637,7 +4587,6 @@
"url": "https://huggingface.co/guozinan/PuLID/resolve/main/pulid_v1.1.safetensors",
"size": "984MB"
},
{
"name": "kijai/MoGe_ViT_L_fp16.safetensors",
"type": "MoGe",
@@ -4660,7 +4609,6 @@
"url": "https://huggingface.co/Kijai/MoGe_safetensors/resolve/main/MoGe_ViT_L_fp16.safetensors",
"size": "1.26GB"
},
{
"name": "LTX-Video 2B v0.9 Checkpoint",
"type": "checkpoint",
@@ -4694,7 +4642,6 @@
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltx-video-2b-v0.9.5.safetensors",
"size": "6.34GB"
},
{
"name": "XLabs-AI/flux-canny-controlnet-v3.safetensors",
"type": "controlnet",
@@ -4728,7 +4675,6 @@
"url": "https://huggingface.co/XLabs-AI/flux-controlnet-collections/resolve/main/flux-hed-controlnet-v3.safetensors",
"size": "1.49GB"
},
{
"name": "XLabs-AI/realism_lora.safetensors",
"type": "lora",
@@ -4762,7 +4708,6 @@
"url": "https://huggingface.co/XLabs-AI/flux-lora-collection/resolve/main/mjv6_lora.safetensors",
"size": "44.8MB"
},
{
"name": "XLabs-AI/flux-ip-adapter",
"type": "lora",
@@ -4774,7 +4719,6 @@
"url": "https://huggingface.co/XLabs-AI/flux-ip-adapter/resolve/main/ip_adapter.safetensors",
"size": "982MB"
},
{
"name": "efficient_sam_s_cpu.jit [ComfyUI-YoloWorld-EfficientSAM]",
"type": "efficient_sam",
@@ -4797,7 +4741,6 @@
"url": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/resolve/main/efficient_sam_s_gpu.jit",
"size": "106.0MB"
},
{
"name": "TencentARC/CustomNet V1",
"type": "CustomNet",
@@ -4820,7 +4763,6 @@
"url": "https://huggingface.co/TencentARC/CustomNet/resolve/main/customnet_inpaint_v1.pt",
"size": "5.71GB"
},
{
"name": "deepseek-ai/Janus-Pro-1B",
"type": "Janus-Pro",
@@ -4865,7 +4807,6 @@
"url": "https://huggingface.co/Kwai-Kolors/Kolors/resolve/main/vae/diffusion_pytorch_model.safetensors",
"size": "335MB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (bf16)",
"type": "diffusion_model",
@@ -4954,7 +4895,6 @@
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp8_scaled.safetensors",
"size": "16.4GB"
},
{
"name": "Comfy-Org/Wan2.1 t2v 1.3B (bf16)",
"type": "diffusion_model",
@@ -4977,7 +4917,6 @@
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_1.3B_fp16.safetensors",
"size": "2.84GB"
},
{
"name": "Comfy-Org/Wan2.1 t2v 14B (bf16)",
"type": "diffusion_model",
@@ -5044,7 +4983,6 @@
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/clip_vision/clip_vision_h.safetensors",
"size": "1.26GB"
},
{
"name": "Comfy-Org/Wan2.2 i2v high noise 14B (fp16)",
"type": "diffusion_model",
@@ -5144,7 +5082,6 @@
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_ti2v_5B_fp16.safetensors",
"size": "10.0GB"
},
{
"name": "Comfy-Org/umt5_xxl_fp16.safetensors",
"type": "clip",
@@ -5167,7 +5104,6 @@
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"size": "6.74GB"
},
{
"name": "lllyasviel/FramePackI2V_HY",
"type": "FramePackI2V",
@@ -5179,7 +5115,204 @@
"url": "lllyasviel/FramePackI2V_HY",
"size": "25.75GB"
},
{
"name": "LTX-2 19B Dev FP8",
"type": "checkpoint",
"base": "LTX-2",
"save_path": "checkpoints/LTX-2",
"description": "LTX-2 19B Dev FP8 model.",
"reference": "https://huggingface.co/Lightricks/LTX-2",
"filename": "ltx-2-19b-dev-fp8.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-19b-dev-fp8.safetensors",
"size": "27.1GB"
},
{
"name": "LTX-2 19B Distilled FP8",
"type": "checkpoint",
"base": "LTX-2",
"save_path": "checkpoints/LTX-2",
"description": "LTX-2 19B Distilled FP8 model.",
"reference": "https://huggingface.co/Lightricks/LTX-2",
"filename": "ltx-2-19b-distilled-fp8.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-19b-distilled-fp8.safetensors",
"size": "27.1GB"
},
{
"name": "LTX-2 19B Dev",
"type": "checkpoint",
"base": "LTX-2",
"save_path": "checkpoints/LTX-2",
"description": "LTX-2 19B Dev model.",
"reference": "https://huggingface.co/Lightricks/LTX-2",
"filename": "ltx-2-19b-dev.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-19b-dev.safetensors",
"size": "43.3GB"
},
{
"name": "LTX-2 19B Distilled",
"type": "checkpoint",
"base": "LTX-2",
"save_path": "checkpoints/LTX-2",
"description": "LTX-2 19B Distilled model.",
"reference": "https://huggingface.co/Lightricks/LTX-2",
"filename": "ltx-2-19b-distilled.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-19b-distilled.safetensors",
"size": "43.3GB"
},
{
"name": "LTX-2 Spatial Upscaler",
"type": "upscale",
"base": "upscale",
"save_path": "default",
"description": "Spatial upscaler model for LTX-2. This model enhances the spatial resolution of generated videos.",
"reference": "https://huggingface.co/Lightricks/LTX-2",
"filename": "ltx-2-spatial-upscaler-x2-1.0.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-spatial-upscaler-x2-1.0.safetensors",
"size": "996MB"
},
{
"name": "LTX-2 Temporal Upscaler",
"type": "upscale",
"base": "upscale",
"save_path": "default",
"description": "Temporal upscaler model for LTX-2. This model enhances the temporal resolution of generated videos.",
"reference": "https://huggingface.co/Lightricks/LTX-2",
"filename": "ltx-2-temporal-upscaler-x2-1.0.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-temporal-upscaler-x2-1.0.safetensors",
"size": "262MB"
},
{
"name": "LTX-2 19B Distilled LoRA",
"type": "lora",
"base": "LTX-2",
"save_path": "loras",
"description": "A LoRA adapter that transforms the standard LTX-2 19B model into a distilled version when loaded.",
"reference": "https://huggingface.co/Lightricks/LTX-2",
"filename": "ltx-2-19b-distilled-lora-384.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-19b-distilled-lora-384.safetensors",
"size": "7.67GB"
},
{
"name": "LTX-2 19B IC LoRA - Canny Control",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for canny control on LTX-2 19B IC model. Intended for advanced edge control and guided generation.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Canny-Control",
"filename": "ltx-2-19b-ic-lora-canny-control.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Canny-Control/resolve/main/ltx-2-19b-ic-lora-canny-control.safetensors",
"size": "654MB"
},
{
"name": "LTX-2 19B IC LoRA - Depth Control",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for depth control on LTX-2 19B IC model. Adds depth-aware generation guidance.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Depth-Control",
"filename": "ltx-2-19b-ic-lora-depth-control.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Depth-Control/resolve/main/ltx-2-19b-ic-lora-depth-control.safetensors",
"size": "654MB"
},
{
"name": "LTX-2 19B IC LoRA - Detailer",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA detailer for LTX-2 19B IC. Improves fine details and sharpness in generated outputs.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Detailer",
"filename": "ltx-2-19b-ic-lora-detailer.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Detailer/resolve/main/ltx-2-19b-ic-lora-detailer.safetensors",
"size": "2.62GB"
},
{
"name": "LTX-2 19B IC LoRA - Pose Control",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for pose control on LTX-2 19B IC model. Enables pose-guided image/video generation.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Pose-Control",
"filename": "ltx-2-19b-ic-lora-pose-control.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Pose-Control/resolve/main/ltx-2-19b-ic-lora-pose-control.safetensors",
"size": "654MB"
},
{
"name": "LTX-2 19B LoRA - Camera Control Dolly In",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for dolly-in camera control with LTX-2 19B. Simulates camera moving closer to subject.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-In",
"filename": "ltx-2-19b-lora-camera-control-dolly-in.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-In/resolve/main/ltx-2-19b-lora-camera-control-dolly-in.safetensors",
"size": "327MB"
},
{
"name": "LTX-2 19B LoRA - Camera Control Dolly Left",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for dolly-left camera control with LTX-2 19B. Simulates camera moving left.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Left",
"filename": "ltx-2-19b-lora-camera-control-dolly-left.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Left/resolve/main/ltx-2-19b-lora-camera-control-dolly-left.safetensors",
"size": "327MB"
},
{
"name": "LTX-2 19B LoRA - Camera Control Dolly Out",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for dolly-out camera control with LTX-2 19B. Simulates camera moving away from subject.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Out",
"filename": "ltx-2-19b-lora-camera-control-dolly-out.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Out/resolve/main/ltx-2-19b-lora-camera-control-dolly-out.safetensors",
"size": "327MB"
},
{
"name": "LTX-2 19B LoRA - Camera Control Dolly Right",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for dolly-right camera control with LTX-2 19B. Simulates camera moving right.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Right",
"filename": "ltx-2-19b-lora-camera-control-dolly-right.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Right/resolve/main/ltx-2-19b-lora-camera-control-dolly-right.safetensors",
"size": "327MB"
},
{
"name": "LTX-2 19B LoRA - Camera Control Jib Down",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for jib-down camera control with LTX-2 19B. Simulates vertical camera movement downwards.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Jib-Down",
"filename": "ltx-2-19b-lora-camera-control-jib-down.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Jib-Down/resolve/main/ltx-2-19b-lora-camera-control-jib-down.safetensors",
"size": "2.21GB"
},
{
"name": "LTX-2 19B LoRA - Camera Control Jib Up",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for jib-up camera control with LTX-2 19B. Simulates vertical camera movement upwards.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Jib-Up",
"filename": "ltx-2-19b-lora-camera-control-jib-up.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Jib-Up/resolve/main/ltx-2-19b-lora-camera-control-jib-up.safetensors",
"size": "2.21GB"
},
{
"name": "LTX-2 19B LoRA - Camera Control Static",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for static camera control with LTX-2 19B. Simulates stationary/static camera view.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Static",
"filename": "ltx-2-19b-lora-camera-control-static.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Static/resolve/main/ltx-2-19b-lora-camera-control-static.safetensors",
"size": "2.21GB"
},
{
"name": "LTX-Video Spatial Upscaler v0.9.7",
"type": "upscale",
@@ -5422,7 +5555,6 @@
"url": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_edit_2509_fp8_e4m3fn.safetensors",
"size": "4.89GB"
},
{
"name": "Qwen-Image-Edit 2509 Diffusion Model (bf16)",
"type": "diffusion_model",
@@ -5434,7 +5566,6 @@
"url": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_edit_2509_bf16.safetensors",
"size": "9.78GB"
},
{
"name": "Qwen-Image-Edit Diffusion Model (fp8_e4m3fn)",
"type": "diffusion_model",
@@ -5446,7 +5577,6 @@
"url": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_edit_fp8_e4m3fn.safetensors",
"size": "4.89GB"
},
{
"name": "Qwen-Image-Edit Diffusion Model (bf16)",
"type": "diffusion_model",
@@ -5668,4 +5798,4 @@
"size": "2.54GB"
}
]
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"source": "Comfy-Org/ComfyUI-Manager",
"source_url": "https://raw.githubusercontent.com/Comfy-Org/ComfyUI-Manager/main/model-list.json",
"github_url": "https://github.com/Comfy-Org/ComfyUI-Manager/blob/main/model-list.json",
"sha": "5fcb5f0637f9fbdedaf8d37da9a390f76ae0ded8",
"size": 268817,
"model_count": 527,
"updated_at": "2026-06-04T17:47:06+00:00"
}
+45
View File
@@ -3372,6 +3372,51 @@
font-weight: 700;
color: var(--ml-text);
}
.ml-options-db-summary {
display: grid;
gap: 1px;
border: 1px solid rgba(255,255,255,0.08);
border-radius: 6px;
overflow: hidden;
background: rgba(255,255,255,0.03);
}
.ml-options-db-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
min-height: 34px;
padding: 7px 10px;
background: rgba(0,0,0,0.16);
color: var(--ml-text-secondary);
font-size: 12px;
}
.ml-options-db-row strong {
color: var(--ml-text);
font-size: 12px;
font-weight: 700;
text-align: right;
}
.ml-options-db-message {
margin-top: 10px;
color: var(--ml-text-secondary);
font-size: 12px;
line-height: 1.35;
}
.ml-options-db-actions {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.ml-options-db-actions .ml-btn {
gap: 6px;
}
.ml-options-db-actions .ml-btn svg {
width: 14px;
height: 14px;
}
.ml-options-field {
display: flex;
flex-direction: column;
+162 -2
View File
@@ -3738,6 +3738,13 @@ class LinkerManagerDialog extends ComfyDialog {
</span>
<span class="ml-options-nav-meta">01</span>
</button>
<button type="button" class="ml-options-nav-btn" data-target="ml-options-section-local-db">
<span class="ml-options-nav-main">
<span class="ml-options-nav-icon" aria-hidden="true">${getSvgIcon('database')}</span>
<span>Local Database</span>
</span>
<span class="ml-options-nav-meta">02</span>
</button>
</div>
</div>
<div class="ml-options-sidebar-group">
@@ -3748,14 +3755,14 @@ class LinkerManagerDialog extends ComfyDialog {
<span class="ml-options-nav-icon ml-options-provider-icon ml-options-provider-icon-civitai" aria-hidden="true">${getSvgIcon('civitai')}</span>
<span>CivitAI</span>
</span>
<span class="ml-options-nav-meta">02</span>
<span class="ml-options-nav-meta">03</span>
</button>
<button type="button" class="ml-options-nav-btn" data-target="ml-options-section-hf">
<span class="ml-options-nav-main">
<span class="ml-options-nav-icon ml-options-provider-icon ml-options-provider-icon-huggingface" aria-hidden="true">${getSvgIcon('huggingface')}</span>
<span>HuggingFace</span>
</span>
<span class="ml-options-nav-meta">03</span>
<span class="ml-options-nav-meta">04</span>
</button>
</div>
</div>
@@ -3777,6 +3784,37 @@ class LinkerManagerDialog extends ComfyDialog {
</div>
</div>
</section>
<section id="ml-options-section-local-db" class="ml-options-card ml-options-section is-hidden">
<div class="ml-options-section-head">
<h4 class="ml-options-section-title">Local Database</h4>
</div>
<div class="ml-options-grid">
<div class="ml-options-panel">
<div class="ml-options-stack">
<div class="ml-options-db-summary">
<div class="ml-options-db-row">
<span>Models</span>
<strong id="ml-options-model-list-count">Loading...</strong>
</div>
<div class="ml-options-db-row">
<span>Last update</span>
<strong id="ml-options-model-list-updated">Loading...</strong>
</div>
<div class="ml-options-db-row">
<span>Status</span>
<strong id="ml-options-model-list-state">Checking local file...</strong>
</div>
</div>
<div id="ml-options-model-list-message" class="ml-options-db-message">Local Database uses ComfyUI-Manager model-list.json.</div>
<div class="ml-options-db-actions">
<button id="ml-options-model-list-check" type="button" class="ml-btn ml-btn-secondary">${getSvgIcon('refreshCw')} Check latest</button>
<button id="ml-options-model-list-update" type="button" class="ml-btn ml-btn-primary">${getSvgIcon('download')} Update Local Database</button>
<a class="ml-options-inline-link" href="https://github.com/Comfy-Org/ComfyUI-Manager/blob/main/model-list.json" target="_blank" rel="noopener noreferrer">Source</a>
</div>
</div>
</div>
</div>
</section>
<section id="ml-options-section-civitai" class="ml-options-card ml-options-section">
<div class="ml-options-section-head">
<h4 class="ml-options-section-title">CivitAI</h4>
@@ -3916,6 +3954,12 @@ class LinkerManagerDialog extends ComfyDialog {
const sourceEnabledInputs = Array.from(this.contentElement.querySelectorAll('.ml-options-source-enabled'));
const status = this.contentElement.querySelector('#ml-options-status');
const saveBtn = this.contentElement.querySelector('#ml-options-save');
const modelListCountEl = this.contentElement.querySelector('#ml-options-model-list-count');
const modelListUpdatedEl = this.contentElement.querySelector('#ml-options-model-list-updated');
const modelListStateEl = this.contentElement.querySelector('#ml-options-model-list-state');
const modelListMessageEl = this.contentElement.querySelector('#ml-options-model-list-message');
const modelListCheckBtn = this.contentElement.querySelector('#ml-options-model-list-check');
const modelListUpdateBtn = this.contentElement.querySelector('#ml-options-model-list-update');
const navButtons = Array.from(this.contentElement.querySelectorAll('.ml-options-nav-btn'));
const optionSections = Array.from(this.contentElement.querySelectorAll('.ml-options-section'));
const trackedInputs = [
@@ -3939,6 +3983,105 @@ class LinkerManagerDialog extends ComfyDialog {
if (mode) status.classList.add(mode);
};
const formatOptionDate = (value) => {
if (!value) return 'Bundled';
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return date.toLocaleString();
};
const shortSha = (value) => value ? String(value).slice(0, 8) : '';
const setModelListBusy = (busy) => {
if (modelListCheckBtn) modelListCheckBtn.disabled = busy;
if (modelListUpdateBtn) modelListUpdateBtn.disabled = busy;
};
const renderModelListStatus = (data = {}, checkedRemote = false) => {
if (modelListCountEl) {
modelListCountEl.textContent = Number.isFinite(Number(data.local_count))
? Number(data.local_count).toLocaleString()
: '-';
}
if (modelListUpdatedEl) {
modelListUpdatedEl.textContent = formatOptionDate(data.local_updated_at);
}
if (modelListStateEl) {
if (checkedRemote && data.can_compare && data.update_available) {
modelListStateEl.textContent = 'Update available';
} else if (checkedRemote && data.can_compare) {
modelListStateEl.textContent = 'Up to date';
} else if (checkedRemote && !data.can_compare) {
modelListStateEl.textContent = 'Remote checked';
} else {
modelListStateEl.textContent = data.local_sha ? 'Tracked' : 'Bundled';
}
}
if (modelListMessageEl) {
const localSha = shortSha(data.local_sha);
const remoteSha = shortSha(data.remote_sha);
if (checkedRemote && data.update_available) {
modelListMessageEl.textContent = `A newer ComfyUI-Manager model-list.json is available (${localSha || 'local'} -> ${remoteSha}).`;
} else if (checkedRemote && data.can_compare) {
modelListMessageEl.textContent = `Local Database is current (${localSha}).`;
} else if (checkedRemote) {
modelListMessageEl.textContent = `Remote file was checked. Local SHA is not recorded yet; run update once to track future changes.`;
} else {
modelListMessageEl.textContent = `Local Database uses ComfyUI-Manager model-list.json.`;
}
}
};
const loadModelListStatus = async (checkRemote = false) => {
try {
setModelListBusy(true);
if (modelListStateEl) {
modelListStateEl.textContent = checkRemote ? 'Checking GitHub...' : 'Loading...';
}
const response = await api.fetchApi(`/model_linker/model-list/status${checkRemote ? '?check_remote=1' : ''}`);
if (!response.ok) {
throw new Error(`Status failed: ${response.status}`);
}
const data = await response.json();
renderModelListStatus(data, checkRemote);
return data;
} catch (error) {
console.error('Model Linker: model-list status error:', error);
if (modelListStateEl) modelListStateEl.textContent = 'Error';
if (modelListMessageEl) modelListMessageEl.textContent = error.message || 'Failed to read Local Database status.';
return null;
} finally {
setModelListBusy(false);
}
};
const updateModelList = async () => {
try {
setModelListBusy(true);
if (modelListStateEl) modelListStateEl.textContent = 'Updating...';
if (modelListMessageEl) modelListMessageEl.textContent = 'Downloading latest ComfyUI-Manager model-list.json...';
const response = await api.fetchApi('/model_linker/model-list/update', {
method: 'POST'
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || `Update failed: ${response.status}`);
}
await this.clearSearchCaches();
renderModelListStatus(data, false);
if (modelListStateEl) modelListStateEl.textContent = 'Updated';
if (modelListMessageEl) modelListMessageEl.textContent = `Local Database updated. ${Number(data.local_count || 0).toLocaleString()} models loaded.`;
this.showNotification('Local Database updated', 'success');
} catch (error) {
console.error('Model Linker: model-list update error:', error);
if (modelListStateEl) modelListStateEl.textContent = 'Error';
if (modelListMessageEl) modelListMessageEl.textContent = error.message || 'Failed to update Local Database.';
this.showNotification('Local Database update failed', 'error');
} finally {
setModelListBusy(false);
}
};
const setActiveNav = (targetId) => {
navButtons.forEach((btn) => {
btn.classList.toggle('is-active', btn.dataset.target === targetId);
@@ -3996,9 +4139,26 @@ class LinkerManagerDialog extends ComfyDialog {
const targetId = btn.dataset.target;
if (!targetId) return;
setVisibleSection(targetId);
if (targetId === 'ml-options-section-local-db') {
loadModelListStatus(false);
}
});
});
if (modelListCheckBtn) {
modelListCheckBtn.addEventListener('click', () => {
loadModelListStatus(true);
});
}
if (modelListUpdateBtn) {
modelListUpdateBtn.addEventListener('click', () => {
updateModelList();
});
}
loadModelListStatus(false);
if (saveBtn) {
saveBtn.addEventListener('click', async () => {
const civitaiCandidateLimitRaw = parseInt(civitaiLimitInput?.value || `${tokens.civitai_candidate_limit}`, 10);