Files
ComfyUI AtticandClaude Sonnet 4.6 6891811209 Add visual file browser modal to replace drive/sub_path text fields
Replace the drive dropdown and sub_path free-text input with a Browse
button that opens a Windows Explorer-style modal. Users can now navigate
drives and folders visually, click into directories, and select a LoRA
file directly without typing paths manually.

Backend: add POST /external_lora/browse endpoint that returns sorted
subdirectories and LoRA files for a given path; empty drive returns the
drive list as a root-level sentinel. Fixes _is_within_drive to handle
Windows drive roots correctly (avoids double-separator bug).

Frontend: modal with breadcrumb navigation, folder/file list, Escape-
to-close, race-condition guard (navGen token), multi-node-safe class-
based DOM (no id collisions), read-only path label, and backward-compat
workflow-load restoration from persisted widget values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 21:00:54 -04:00

63 lines
2.2 KiB
Python

# This file contains the ExternalLoraLoader node class.
import os
from .api import DRIVE_LIST
from .cache import LORA_CACHE
class ExternalLoraLoader:
RETURN_TYPES = ("MODEL", "CLIP")
FUNCTION = "execute"
CATEGORY = "loaders"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("MODEL",),
"clip": ("CLIP",),
"drive": (DRIVE_LIST or ["C:/"],),
"sub_path": ("STRING", {"default": "", "multiline": False}),
# Keep this as a STRING on the backend so ComfyUI does not
# validate against a stale static combo list before execution.
"lora_name": ("STRING", {"default": "none", "multiline": False}),
"model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"max_cache_mb": ("INT", {"default": 2048, "min": 128, "max": 32768, "step": 128}),
}
}
def execute(self, model, clip, drive, sub_path, lora_name, model_strength, clip_strength, max_cache_mb):
# 1. If lora_name is "none", return unchanged
if lora_name == "none":
return (model, clip)
# 2. Build normalized path
full_path = os.path.normpath(os.path.join(drive, sub_path, lora_name))
if not os.path.isfile(full_path):
raise FileNotFoundError(
f"[ExternalLoraLoader] LoRA file not found: {full_path!r}"
)
# 3. Update cache size limit
if LORA_CACHE.max_mb != max_cache_mb:
LORA_CACHE.set_max_mb(max_cache_mb)
# 4. Cache lookup
tensors = LORA_CACHE.get(full_path)
# 5. Cache miss: load from disk
if tensors is None:
import comfy.utils
tensors = comfy.utils.load_torch_file(full_path, safe_load=True)
LORA_CACHE.put(full_path, tensors)
# 6. Apply LoRA patch
import comfy.sd
model_patched, clip_patched = comfy.sd.load_lora_for_models(
model, clip, tensors, model_strength, clip_strength
)
return (model_patched, clip_patched)