mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-21 11:11:26 -03:00
28fbb86dce
Other Models management is now opt-in: enable_other_models (default false) plus the enabled_other_sub_types allow-list replace the unreleased additive enabled_other_folders key. - config._get_enabled_other_folder_keys() is the single scan gate; a new refresh_other_roots() rebuilds roots and preview roots on toggle. - ModelScanner gains a _should_keep_cached_entry() hydration hook and on_library_changed(reconcile=...) so switching a sub_type off drops its entries (and hash/autov3 rows) at load time and switching it on rescans. - OtherScanner filters location-derived entries accordingly. - Other routes reject every other type while off (or a disabled sub_type) and expose an "other_disabled" page flag; download routing returns a disabled marker instead of guessing; the download manager refuses other-type downloads and default-path routing for switched-off sub_types. - Doctor / init-status / refresh-all skip the other scanner while off; the scanner stays registered so staged pending-deletes still merge. - Tests updated with explicit opt-in fixtures plus new gating coverage.
386 lines
11 KiB
Python
386 lines
11 KiB
Python
from typing import Any, Dict, List
|
|
|
|
NSFW_LEVELS = {
|
|
"PG": 1,
|
|
"PG13": 2,
|
|
"R": 4,
|
|
"X": 8,
|
|
"XXX": 16,
|
|
"Blocked": 32, # Probably not actually visible through the API without being logged in on model owner account?
|
|
}
|
|
|
|
# Node type constants
|
|
NODE_TYPES = {
|
|
"Lora Loader (LoraManager)": 1,
|
|
"Lora Stacker (LoraManager)": 2,
|
|
"WanVideo Lora Select (LoraManager)": 3,
|
|
"Create Hook LoRA (LoraManager)": 4,
|
|
}
|
|
|
|
# Default ComfyUI node color when bgcolor is null
|
|
DEFAULT_NODE_COLOR = "#353535"
|
|
|
|
# preview extensions
|
|
PREVIEW_EXTENSIONS = [
|
|
".webp",
|
|
".preview.webp",
|
|
".preview.png",
|
|
".preview.jpeg",
|
|
".preview.jpg",
|
|
".preview.mp4",
|
|
".png",
|
|
".jpeg",
|
|
".jpg",
|
|
".mp4",
|
|
".gif",
|
|
".webm",
|
|
".avif",
|
|
".jxl",
|
|
]
|
|
|
|
# Card preview image width
|
|
CARD_PREVIEW_WIDTH = 480
|
|
|
|
# Width for optimized example images
|
|
EXAMPLE_IMAGE_WIDTH = 832
|
|
|
|
# Supported media extensions for example downloads
|
|
SUPPORTED_MEDIA_EXTENSIONS = {
|
|
"images": [".jpg", ".jpeg", ".png", ".webp", ".gif", ".avif", ".jxl"],
|
|
"videos": [".mp4", ".webm"],
|
|
}
|
|
|
|
# Model weight file extensions recognised by scanners.
|
|
# This is the union of all scanner extensions (lora, checkpoint, embedding).
|
|
MODEL_FILE_EXTENSIONS = {
|
|
".safetensors",
|
|
".ckpt",
|
|
".pt",
|
|
".pt2",
|
|
".bin",
|
|
".pth",
|
|
".pkl",
|
|
".sft",
|
|
".gguf",
|
|
}
|
|
|
|
# CivitAI ModelFile.type values eligible as the main download file.
|
|
# Mirrors CivitAI's getPrimaryFile() (model-helpers.ts): weight types are
|
|
# preferred, but any file CivitAI marks `primary` is accepted — newer types
|
|
# like 'Enhancement LoRA' (Anima/AIR image-editing LoRAs) are valid primary
|
|
# files despite not being in the traditional weights allowlist.
|
|
MODEL_WEIGHT_FILE_TYPES = (
|
|
"Model",
|
|
"Pruned Model",
|
|
"Negative",
|
|
"UNet",
|
|
"Diffusion Model",
|
|
"Enhancement LoRA",
|
|
)
|
|
|
|
# Valid sub-types for each scanner type
|
|
VALID_LORA_SUB_TYPES = ["lora", "locon", "dora"]
|
|
VALID_CHECKPOINT_SUB_TYPES = ["checkpoint", "diffusion_model"]
|
|
VALID_EMBEDDING_SUB_TYPES = ["embedding"]
|
|
|
|
# folder_paths key -> sub_type; single source of truth for extensibility.
|
|
# Adding support for a new ComfyUI folder category is a one-line change here.
|
|
OTHER_MODEL_FOLDER_SUBTYPES = {
|
|
"vae": "vae",
|
|
"upscale_models": "upscaler",
|
|
"text_encoders": "text_encoder",
|
|
"clip": "text_encoder", # legacy ComfyUI key
|
|
"clip_vision": "clip_vision",
|
|
"controlnet": "controlnet",
|
|
}
|
|
VALID_OTHER_SUB_TYPES = ["vae", "upscaler", "text_encoder", "clip_vision", "controlnet"]
|
|
# Sub-types managed when the (opt-in) Other Models feature is switched on.
|
|
# The feature itself defaults to off (``enable_other_models`` = False), so
|
|
# nothing here is scanned until the user enables it.
|
|
DEFAULT_ENABLED_OTHER_SUB_TYPES: List[str] = [
|
|
"vae",
|
|
"upscaler",
|
|
"text_encoder",
|
|
"clip_vision",
|
|
]
|
|
|
|
|
|
def other_sub_type_folder_keys() -> Dict[str, List[str]]:
|
|
"""Invert OTHER_MODEL_FOLDER_SUBTYPES into sub_type -> folder_paths keys.
|
|
|
|
``text_encoder`` maps to two folder keys (``text_encoders`` and the legacy
|
|
``clip``), so every consumer that resolves a sub_type back to folders must
|
|
merge both.
|
|
"""
|
|
mapping: Dict[str, List[str]] = {}
|
|
for folder_key, sub_type in OTHER_MODEL_FOLDER_SUBTYPES.items():
|
|
mapping.setdefault(sub_type, []).append(folder_key)
|
|
return mapping
|
|
|
|
|
|
# Precomputed inverse of OTHER_MODEL_FOLDER_SUBTYPES, keeping the table order.
|
|
OTHER_SUB_TYPE_FOLDER_KEYS: Dict[str, List[str]] = other_sub_type_folder_keys()
|
|
|
|
|
|
def normalize_other_sub_types(value: Any) -> List[str]:
|
|
"""Normalize a stored/requested enabled-sub_type list.
|
|
|
|
Unknown values and duplicates are dropped; the result follows the
|
|
canonical VALID_OTHER_SUB_TYPES order so the stored setting and the UI
|
|
stay stable. Non-list input falls back to the defaults.
|
|
"""
|
|
if isinstance(value, str):
|
|
candidates: Any = [value]
|
|
elif isinstance(value, (list, tuple, set)):
|
|
candidates = value
|
|
else:
|
|
return list(DEFAULT_ENABLED_OTHER_SUB_TYPES)
|
|
|
|
allowed = {item for item in candidates if isinstance(item, str)}
|
|
return [sub_type for sub_type in VALID_OTHER_SUB_TYPES if sub_type in allowed]
|
|
# CivitAI model.type values accepted by the "other" page's fetch-metadata
|
|
# validation (lowercased). CLIP/CLIPVision are retired upstream but still
|
|
# appear on grandfathered models.
|
|
VALID_OTHER_CIVITAI_TYPES = {
|
|
"vae",
|
|
"upscaler",
|
|
"textencoder",
|
|
"clip",
|
|
"clipvision",
|
|
"controlnet",
|
|
"other",
|
|
}
|
|
# CivitAI model.type -> internal sub_type for the "other" model page.
|
|
CIVITAI_TYPE_TO_OTHER_SUB_TYPE = {
|
|
"vae": "vae",
|
|
"upscaler": "upscaler",
|
|
"textencoder": "text_encoder",
|
|
"clip": "text_encoder",
|
|
"clipvision": "clip_vision",
|
|
"controlnet": "controlnet",
|
|
}
|
|
|
|
# CivitAI ModelFile.type values -> internal sub_type for the "other" model
|
|
# page. Used for download routing only, and strictly as an explicit user file
|
|
# pick or a fallback when model.type maps to nothing — checkpoint models
|
|
# routinely bundle VAE/Text Encoder component files, so file types must never
|
|
# override a mapped model.type.
|
|
CIVITAI_FILE_TYPE_TO_OTHER_SUB_TYPE = {
|
|
"VAE": "vae",
|
|
"Upscaler": "upscaler",
|
|
"Text Encoder": "text_encoder",
|
|
"Vision Encoder": "clip_vision",
|
|
"CLIPVision": "clip_vision",
|
|
"ControlNet": "controlnet",
|
|
}
|
|
|
|
# Backward compatibility alias
|
|
VALID_LORA_TYPES = VALID_LORA_SUB_TYPES
|
|
|
|
# Supported Civitai model types for user model queries (case-insensitive)
|
|
CIVITAI_USER_MODEL_TYPES = [
|
|
*VALID_LORA_TYPES,
|
|
"textualinversion",
|
|
"checkpoint",
|
|
*sorted(VALID_OTHER_CIVITAI_TYPES),
|
|
]
|
|
|
|
# Default chunk size in megabytes used for hashing large files.
|
|
DEFAULT_HASH_CHUNK_SIZE_MB = 4
|
|
|
|
# Upper bound for a safetensors header block (bytes). Real headers are at most
|
|
# a few MB (tensor name/shape lists); the cap prevents a crafted file with an
|
|
# absurd 64-bit header length from forcing a multi-GB allocation during scan.
|
|
MAX_SAFETENSORS_HEADER_BYTES = 64 * 1024 * 1024
|
|
|
|
# SHA256 of an empty byte string. Some (re-packaging) training tools write a
|
|
# truncated form of this placeholder into safetensors metadata (as
|
|
# ``modelspec.hash_sha256`` / ``sshs_model_hash``), and hashing an empty or
|
|
# unreadable file produces it directly. It must never be treated as a valid
|
|
# hash: several broken models share it, CivitAI's by-hash index can contain
|
|
# such polluted entries, and matching it falsely attributes recipes.
|
|
EMPTY_HASH_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
INVALID_AUTOV3_EMPTY_HASH = EMPTY_HASH_SHA256[:12]
|
|
INVALID_AUTOV2_EMPTY_HASH = EMPTY_HASH_SHA256[:10]
|
|
|
|
|
|
def is_empty_placeholder_hash(value: Any) -> bool:
|
|
"""True for a 10/12/64-hex-char spelling of the empty-hash placeholder.
|
|
|
|
These are the AutoV2, AutoV3 and full-SHA256 forms of the placeholder;
|
|
such values identify no real model and must never be resolved against
|
|
local files or CivitAI.
|
|
"""
|
|
if not isinstance(value, str):
|
|
return False
|
|
v = value.strip().lower()
|
|
if len(v) not in (10, 12, 64):
|
|
return False
|
|
return v == EMPTY_HASH_SHA256[: len(v)]
|
|
|
|
# Auto-organize settings
|
|
AUTO_ORGANIZE_BATCH_SIZE = (
|
|
50 # Process models in batches to avoid overwhelming the system
|
|
)
|
|
|
|
# Civitai model tags in priority order for subfolder organization
|
|
CIVITAI_MODEL_TAGS = [
|
|
"character",
|
|
"concept",
|
|
"clothing",
|
|
"realistic",
|
|
"anime",
|
|
"toon",
|
|
"furry",
|
|
"style",
|
|
"poses",
|
|
"background",
|
|
"tool",
|
|
"vehicle",
|
|
"buildings",
|
|
"objects",
|
|
"assets",
|
|
"animal",
|
|
"action",
|
|
]
|
|
|
|
# Default priority tag configuration strings for each model type
|
|
DEFAULT_PRIORITY_TAG_CONFIG = {
|
|
"lora": ", ".join(CIVITAI_MODEL_TAGS),
|
|
"checkpoint": ", ".join(CIVITAI_MODEL_TAGS),
|
|
"embedding": ", ".join(CIVITAI_MODEL_TAGS),
|
|
}
|
|
|
|
# baseModel values from CivitAI that should be treated as diffusion models (unet)
|
|
# These model types are incorrectly labeled as "checkpoint" by CivitAI but are actually diffusion models
|
|
DIFFUSION_MODEL_BASE_MODELS = frozenset(
|
|
[
|
|
"Anima",
|
|
# Flux series — DiT architecture, loaded via UNETLoader in ComfyUI
|
|
"Flux.1 D",
|
|
"Flux.1 S",
|
|
"Flux.1 Krea",
|
|
"Flux.1 Kontext",
|
|
"Flux.2 D",
|
|
"Flux.2 Klein 9B",
|
|
"Flux.2 Klein 9B-base",
|
|
"Flux.2 Klein 4B",
|
|
"Flux.2 Klein 4B-base",
|
|
# Non-UNet / DiT image diffusion models
|
|
"AuraFlow",
|
|
"Chroma",
|
|
"HiDream",
|
|
"Hunyuan 1",
|
|
"Kolors",
|
|
"Lumina",
|
|
"PixArt a",
|
|
"PixArt E",
|
|
# Video diffusion models
|
|
"CogVideoX",
|
|
"Hunyuan Video",
|
|
"LTXV",
|
|
"LTXV2",
|
|
"LTXV 2.3",
|
|
"Mochi",
|
|
"SVD",
|
|
"Wan Video",
|
|
"Wan Video 1.3B t2v",
|
|
"Wan Video 14B t2v",
|
|
"Wan Video 14B i2v 480p",
|
|
"Wan Video 14B i2v 720p",
|
|
"Wan Video 2.2 TI2V-5B",
|
|
"Wan Video 2.2 I2V-A14B",
|
|
"Wan Video 2.2 T2V-A14B",
|
|
"Wan Video 2.5 T2V",
|
|
"Wan Video 2.5 I2V",
|
|
# Other diffusion models
|
|
"Ernie",
|
|
"Ernie Turbo",
|
|
"Nucleus",
|
|
"Qwen",
|
|
"ZImageBase",
|
|
"ZImageTurbo",
|
|
# Krea 2 — loaded via UNETLoader in ComfyUI
|
|
"Krea 2",
|
|
]
|
|
)
|
|
|
|
# Supported baseModel values for download exclusion settings.
|
|
# Keep this aligned with static/js/utils/constants.js, excluding the generic "Other" value.
|
|
SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS = frozenset(
|
|
[
|
|
"SD 1.4",
|
|
"SD 1.5",
|
|
"SD 1.5 LCM",
|
|
"SD 1.5 Hyper",
|
|
"SD 2.0",
|
|
"SD 2.1",
|
|
"SD 3",
|
|
"SD 3.5",
|
|
"SD 3.5 Medium",
|
|
"SD 3.5 Large",
|
|
"SD 3.5 Large Turbo",
|
|
"SDXL 1.0",
|
|
"SDXL Lightning",
|
|
"SDXL Hyper",
|
|
"Flux.1 D",
|
|
"Flux.1 S",
|
|
"Flux.1 Krea",
|
|
"Flux.1 Kontext",
|
|
"Flux.2 D",
|
|
"Flux.2 Klein 9B",
|
|
"Flux.2 Klein 9B-base",
|
|
"Flux.2 Klein 4B",
|
|
"Flux.2 Klein 4B-base",
|
|
"AuraFlow",
|
|
"Chroma",
|
|
"PixArt a",
|
|
"PixArt E",
|
|
"Hunyuan 1",
|
|
"Lumina",
|
|
"Kolors",
|
|
"NoobAI",
|
|
"Illustrious",
|
|
"Pony",
|
|
"Pony V7",
|
|
"HiDream",
|
|
"Qwen",
|
|
"ZImageTurbo",
|
|
"ZImageBase",
|
|
"SVD",
|
|
"LTXV",
|
|
"LTXV2",
|
|
"LTXV 2.3",
|
|
"CogVideoX",
|
|
"Mochi",
|
|
"Wan Video",
|
|
"Wan Video 1.3B t2v",
|
|
"Wan Video 14B t2v",
|
|
"Wan Video 14B i2v 480p",
|
|
"Wan Video 14B i2v 720p",
|
|
"Wan Video 2.2 TI2V-5B",
|
|
"Wan Video 2.2 T2V-A14B",
|
|
"Wan Video 2.2 I2V-A14B",
|
|
"Wan Video 2.5 T2V",
|
|
"Wan Video 2.5 I2V",
|
|
"Hunyuan Video",
|
|
"Anima",
|
|
"ACE Audio",
|
|
"Boogu",
|
|
"Ernie",
|
|
"Ernie Turbo",
|
|
"Grok",
|
|
"HappyHorse",
|
|
"HiDream-O1",
|
|
"Ideogram 4.0",
|
|
"Krea 2",
|
|
"Lens",
|
|
"MAI",
|
|
"Nucleus",
|
|
"Qwen 2",
|
|
"Upscaler",
|
|
"Wan Image 2.7",
|
|
"Wan Video 2.7",
|
|
]
|
|
)
|