diff --git a/folder_paths.py b/folder_paths.py index 9c96540e3..b9022fbba 100644 --- a/folder_paths.py +++ b/folder_paths.py @@ -472,6 +472,18 @@ def get_save_image_path(filename_prefix: str, output_dir: str, image_width=0, im counter = 1 return full_output_folder, filename, counter, subfolder, filename_prefix +def get_model_placeholder(folder_name: str) -> str: + """Generate placeholder text for empty model dropdowns. + + Args: + folder_name: The name of the model folder (e.g., "checkpoints", "loras"). + + Returns: + A user-friendly placeholder string indicating where models should be placed. + """ + return f"No models found in ComfyUI/models/{folder_name} folder . . ." + + def get_input_subfolders() -> list[str]: """Returns a list of all subfolder paths in the input directory, recursively. diff --git a/nodes.py b/nodes.py index bf6ce5736..dcab9f817 100644 --- a/nodes.py +++ b/nodes.py @@ -589,7 +589,10 @@ class CheckpointLoaderSimple: def INPUT_TYPES(s): return { "required": { - "ckpt_name": (folder_paths.get_filename_list("checkpoints"), {"tooltip": "The name of the checkpoint (model) to load."}), + "ckpt_name": (folder_paths.get_filename_list("checkpoints"), { + "tooltip": "The name of the checkpoint (model) to load.", + "placeholder": folder_paths.get_model_placeholder("checkpoints") + }), } } RETURN_TYPES = ("MODEL", "CLIP", "VAE") @@ -639,7 +642,9 @@ class DiffusersLoader: class unCLIPCheckpointLoader: @classmethod def INPUT_TYPES(s): - return {"required": { "ckpt_name": (folder_paths.get_filename_list("checkpoints"), ), + return {"required": { "ckpt_name": (folder_paths.get_filename_list("checkpoints"), { + "placeholder": folder_paths.get_model_placeholder("checkpoints") + }), }} RETURN_TYPES = ("MODEL", "CLIP", "VAE", "CLIP_VISION") FUNCTION = "load_checkpoint" @@ -679,7 +684,10 @@ class LoraLoader: "required": { "model": ("MODEL", {"tooltip": "The diffusion model the LoRA will be applied to."}), "clip": ("CLIP", {"tooltip": "The CLIP model the LoRA will be applied to."}), - "lora_name": (folder_paths.get_filename_list("loras"), {"tooltip": "The name of the LoRA."}), + "lora_name": (folder_paths.get_filename_list("loras"), { + "tooltip": "The name of the LoRA.", + "placeholder": folder_paths.get_model_placeholder("loras") + }), "strength_model": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01, "tooltip": "How strongly to modify the diffusion model. This value can be negative."}), "strength_clip": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01, "tooltip": "How strongly to modify the CLIP model. This value can be negative."}), } @@ -716,7 +724,9 @@ class LoraLoaderModelOnly(LoraLoader): @classmethod def INPUT_TYPES(s): return {"required": { "model": ("MODEL",), - "lora_name": (folder_paths.get_filename_list("loras"), ), + "lora_name": (folder_paths.get_filename_list("loras"), { + "placeholder": folder_paths.get_model_placeholder("loras") + }), "strength_model": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}), }} RETURN_TYPES = ("MODEL",) @@ -806,7 +816,9 @@ class VAELoader: @classmethod def INPUT_TYPES(s): - return {"required": { "vae_name": (s.vae_list(s), )}} + return {"required": { "vae_name": (s.vae_list(s), { + "placeholder": folder_paths.get_model_placeholder("vae") + })}} RETURN_TYPES = ("VAE",) FUNCTION = "load_vae" @@ -833,7 +845,9 @@ class VAELoader: class ControlNetLoader: @classmethod def INPUT_TYPES(s): - return {"required": { "control_net_name": (folder_paths.get_filename_list("controlnet"), )}} + return {"required": { "control_net_name": (folder_paths.get_filename_list("controlnet"), { + "placeholder": folder_paths.get_model_placeholder("controlnet") + })}} RETURN_TYPES = ("CONTROL_NET",) FUNCTION = "load_controlnet" @@ -852,7 +866,9 @@ class DiffControlNetLoader: @classmethod def INPUT_TYPES(s): return {"required": { "model": ("MODEL",), - "control_net_name": (folder_paths.get_filename_list("controlnet"), )}} + "control_net_name": (folder_paths.get_filename_list("controlnet"), { + "placeholder": folder_paths.get_model_placeholder("controlnet") + })}} RETURN_TYPES = ("CONTROL_NET",) FUNCTION = "load_controlnet" @@ -950,7 +966,9 @@ class ControlNetApplyAdvanced: class UNETLoader: @classmethod def INPUT_TYPES(s): - return {"required": { "unet_name": (folder_paths.get_filename_list("diffusion_models"), ), + return {"required": { "unet_name": (folder_paths.get_filename_list("diffusion_models"), { + "placeholder": folder_paths.get_model_placeholder("diffusion_models") + }), "weight_dtype": (["default", "fp8_e4m3fn", "fp8_e4m3fn_fast", "fp8_e5m2"],) }} RETURN_TYPES = ("MODEL",) @@ -975,7 +993,9 @@ class UNETLoader: class CLIPLoader: @classmethod def INPUT_TYPES(s): - return {"required": { "clip_name": (folder_paths.get_filename_list("text_encoders"), ), + return {"required": { "clip_name": (folder_paths.get_filename_list("text_encoders"), { + "placeholder": folder_paths.get_model_placeholder("text_encoders") + }), "type": (["stable_diffusion", "stable_cascade", "sd3", "stable_audio", "mochi", "ltxv", "pixart", "cosmos", "lumina2", "wan", "hidream", "chroma", "ace", "omnigen2", "qwen_image", "hunyuan_image", "flux2", "ovis"], ), }, "optional": { @@ -1002,8 +1022,12 @@ class CLIPLoader: class DualCLIPLoader: @classmethod def INPUT_TYPES(s): - return {"required": { "clip_name1": (folder_paths.get_filename_list("text_encoders"), ), - "clip_name2": (folder_paths.get_filename_list("text_encoders"), ), + return {"required": { "clip_name1": (folder_paths.get_filename_list("text_encoders"), { + "placeholder": folder_paths.get_model_placeholder("text_encoders") + }), + "clip_name2": (folder_paths.get_filename_list("text_encoders"), { + "placeholder": folder_paths.get_model_placeholder("text_encoders") + }), "type": (["sdxl", "sd3", "flux", "hunyuan_video", "hidream", "hunyuan_image", "hunyuan_video_15", "kandinsky5", "kandinsky5_image", "ltxv", "newbie", "ace"], ), }, "optional": { @@ -1032,7 +1056,9 @@ class DualCLIPLoader: class CLIPVisionLoader: @classmethod def INPUT_TYPES(s): - return {"required": { "clip_name": (folder_paths.get_filename_list("clip_vision"), ), + return {"required": { "clip_name": (folder_paths.get_filename_list("clip_vision"), { + "placeholder": folder_paths.get_model_placeholder("clip_vision") + }), }} RETURN_TYPES = ("CLIP_VISION",) FUNCTION = "load_clip"