commit 44afa76a06bc51fba3db054a42caaf94eb12cd2d Author: Geekatplay Dev Date: Sat Feb 7 10:47:39 2026 -0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..444e997 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Python +__pycache__/ +*.pyc + +# Editors +.vscode/ +.idea/ + +# OS +.DS_Store +Thumbs.db diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..278baef --- /dev/null +++ b/__init__.py @@ -0,0 +1,40 @@ +from .nodes_ollama_router_new import ( + OllamaPromptPlanner, + OllamaVisionStylePlanner, + DynamicCheckpointLoader, + DynamicLoraStack, + RouteConditioningByType, + OptionalControlNetApply, + RouteLatentByBool, + RouteVaeByBool, + OllamaDebugInfo, + OllamaRegistryInfo, +) + +NODE_CLASS_MAPPINGS = { + "OllamaPromptPlanner": OllamaPromptPlanner, + "OllamaVisionStylePlanner": OllamaVisionStylePlanner, + "DynamicCheckpointLoader": DynamicCheckpointLoader, + "DynamicLoraStack": DynamicLoraStack, + "RouteConditioningByType": RouteConditioningByType, + "OptionalControlNetApply": OptionalControlNetApply, + "RouteLatentByBool": RouteLatentByBool, + "RouteVaeByBool": RouteVaeByBool, + "OllamaDebugInfo": OllamaDebugInfo, + "OllamaRegistryInfo": OllamaRegistryInfo, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "OllamaPromptPlanner": "Ollama Prompt Planner", + "OllamaVisionStylePlanner": "Ollama Vision Style Planner", + "DynamicCheckpointLoader": "Dynamic Checkpoint Loader (Universal)", + "DynamicLoraStack": "Dynamic LoRA Stack", + "RouteConditioningByType": "Route Conditioning (SDXL/SD1.5)", + "OptionalControlNetApply": "Optional ControlNet Apply", + "RouteLatentByBool": "Route Latent (Bool)", + "RouteVaeByBool": "Route VAE (Bool)", + "OllamaDebugInfo": "Ollama Plan Debug Info", + "OllamaRegistryInfo": "Ollama Registry Debug Info", +} + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/create_registry.py b/create_registry.py new file mode 100644 index 0000000..fb56d72 --- /dev/null +++ b/create_registry.py @@ -0,0 +1,279 @@ + +import json + +data_checkpoints = """ +I. Photorealism & People +Photorealism | Flux.2-dev-fp16.safetensors | https://huggingface.co/black-forest-labs/FLUX.2-dev | hyper-realism, skin-texture, text-rendering, high-fidelity | Group A (Flux VAE / T5-CLIP) +Photorealism | RealVisXL_V6.0_B1.safetensors | https://civitai.com/models/150629 | raw-photo, dslr, 8k, professional-photography, anatomy | Group C (SDXL VAE / CLIP-L) +Photorealism | Juggernaut_XL_v10.safetensors | https://civitai.com/models/133005 | cinematic, lighting, generalist, commercial-photography | Group C (SDXL VAE) +Photorealism | Z-Image-Turbo-v1.safetensors | https://huggingface.co/Tongyi-MAI/Z-Image-Turbo | speed, realism, architectural, clean-lighting | Group B (Z-VAE / Z-CLIP) +Photorealism | epiCPhotoGasm_v2.safetensors | https://civitai.com/models/132632 | studio-lighting, portraits, soft-skin, bokeh | Group D (SD1.5 VAE) +Photorealism | Realistic_Vision_V7.0.safetensors | https://civitai.com/models/4201 | analog-film, retro, human-anatomy, natural-poses | Group D (SD1.5 VAE) +Photorealism | IC-Light-XL-Realistic.safetensors | https://huggingface.co/lllyasviel/ic-light | relighting, shadow-control, background-blending | Group C (SDXL VAE) +Photorealism | Sateluco-IOR-XL.safetensors | https://civitai.com/models/sateluco | extreme-clarity, high-dynamic-range, reflection-logic | Group C (SDXL VAE) +Photorealism | AnalogMadness_XL_v4.safetensors | https://civitai.com/models/analog-xl | film-grain, kodak-portra, fuji-film, nostalgia | Group C (SDXL VAE) +Photorealism | Photopedia_XL_v2.safetensors | https://civitai.com/models/photopedia | stock-photography, documentary-style, authentic | Group C (SDXL VAE) +Photorealism | Mobius_XL_v1.safetensors | https://civitai.com/models/mobius | lighting-consistency, character-depth, sharp-focus | Group C (SDXL VAE) +Photorealism | AbsoluteReality_v1.8.safetensors | https://civitai.com/models/81458 | sharp-details, natural-skin, non-ai-look | Group D (SD1.5 VAE) +Photorealism | ThinkDiffusionXL_v1.safetensors | https://civitai.com/models/thinkdiffusion | 4k-resolution, crisp-edges, studio-render | Group C (SDXL VAE) +Photorealism | NightVision_XL_v9.safetensors | https://civitai.com/models/nightvision | night-photography, low-light, street-lighting | Group C (SDXL VAE) +Photorealism | CyberRealistic_XL_v6.safetensors | https://civitai.com/models/cyberrealistic | hyper-detail, intricate-skin, photorealism | Group C (SDXL VAE) + +II. Anime & Stylized +Anime | Illustrious-XL-v1.0.safetensors | https://civitai.com/models/illustrious | anime, high-res, cel-shaded, modern-anime | Group C (SDXL VAE) +Anime | PonyDiffusion_V7_XL.safetensors | https://civitai.com/models/257 | character-design, complex-poses, fandom, expressive | Group C (SDXL VAE) +Anime | AAM_XL_AnimeMix_v2.safetensors | https://civitai.com/models/anime-mix | vibrant, fantasy-anime, wallpaper-quality | Group C (SDXL VAE) +Anime | MeinaMix_V11.safetensors | https://civitai.com/models/meinamix | soft-anime, romantic-style, clean-lines | Group D (SD1.5 VAE) +Anime | Cetus-Mix-Final.safetensors | https://civitai.com/models/cetus | painterly-anime, 2.5D, semi-realistic | Group D (SD1.5 VAE) +Anime | NoobAI-XL-v1.safetensors | https://civitai.com/models/noobai | danbooru-tags, technical-prompts, precision | Group C (SDXL VAE) +Anime | Animagine_XL_v4.0.safetensors | https://civitai.com/models/animagine | consistent-character, high-quality-anime, official-art-style | Group C (SDXL VAE) +Anime | AbyssOrangeMix3_AOM3.safetensors | https://civitai.com/models/abyssorangemix | dark-anime, detailed-eyes, aesthetic | Group D (SD1.5 VAE) +Anime | Counterfeit-V3.0.safetensors | https://civitai.com/models/counterfeit | background-focused, atmospheric-anime | Group D (SD1.5 VAE) +Anime | BlueberryMix_XL.safetensors | https://civitai.com/models/blueberry | moe-style, soft-colors, kawaii | Group C (SDXL VAE) +Anime | Mistoon_Anime_v2.safetensors | https://civitai.com/models/mistoon | flat-color, vector-style, clean-anime | Group D (SD1.5 VAE) +Anime | Pastel-Mix-Stylized.safetensors | https://civitai.com/models/pastel-mix | pastel-colors, soft-lighting, artistic-anime | Group D (SD1.5 VAE) +Anime | Real-Anime-XL.safetensors | https://civitai.com/models/real-anime | semi-realistic, 3d-anime-hybrid, thick-shading | Group C (SDXL VAE) +Anime | HoloDayo_XL_v1.safetensors | https://civitai.com/models/holodayo | hololive-consistent, fanart-style, vibrant | Group C (SDXL VAE) +Anime | Waifu-Diffusion-XL.safetensors | https://huggingface.co/waifu-diffusion | base-anime, massive-dataset, legacy | Group C (SDXL VAE) + +III. Artistic & Painting +Artistic | Painterly_XL_Check_v2.safetensors | https://civitai.com/models/painterly | oil-painting, canvas, impasto, brushstrokes | Group C (SDXL VAE) +Artistic | DreamShaper_XL_Turbo.safetensors | https://civitai.com/models/112902 | digital-art, fantasy, concept-art, versatile | Group C (SDXL VAE) +Artistic | Watercolor_Stylized_v3.safetensors | https://civitai.com/models/watercolor | water-wash, ink-bleed, traditional-media | Group C (SDXL VAE) +Artistic | Copax_TimeLess_XL_v9.safetensors | https://civitai.com/models/timeless | atmospheric, ethereal, fine-art | Group C (SDXL VAE) +Artistic | Oil_Brush_Pro_v2.safetensors | https://civitai.com/models/oilbrush | thick-paint, museum-quality, traditional | Group C (SDXL VAE) +Artistic | Arcane_Style_XL.safetensors | https://civitai.com/models/arcane | stylized, neon, paint-splatter, arcane-tv-style | Group C (SDXL VAE) +Artistic | Inkpunk_Diffusion_v2.safetensors | https://civitai.com/models/inkpunk | ink-sketch, messy-lines, cyberpunk-art | Group D (SD1.5 VAE) +Artistic | Abstract_Master_XL.safetensors | https://civitai.com/models/abstract | geometric, non-representative, fine-art-abstract | Group C (SDXL VAE) +Artistic | Charcoal_Pro_v1.safetensors | https://civitai.com/models/charcoal | black-and-white, hand-drawn, smudge-texture | Group C (SDXL VAE) +Artistic | Ukiyo-e_XL_v2.safetensors | https://civitai.com/models/ukiyoe | japanese-print, woodblock, historical-art | Group C (SDXL VAE) +Artistic | ComicMix_XL_v3.safetensors | https://civitai.com/models/comicmix | western-comic, marvel-style, clean-inking | Group C (SDXL VAE) +Artistic | Gouache_Style_XL.safetensors | https://civitai.com/models/gouache | matte-paint, thick-wash, opaque-watercolor | Group C (SDXL VAE) +Artistic | Pencil-Sketch-XL.safetensors | https://civitai.com/models/pencil-sketch | graphite, cross-hatching, realistic-sketch | Group C (SDXL VAE) +Artistic | Synthwave_Master_XL.safetensors | https://civitai.com/models/synthwave | 80s-aesthetic, retro-future, vibrant-pink-blue | Group C (SDXL VAE) +Artistic | Dark-Vibe-XL.safetensors | https://civitai.com/models/dark-vibe | gothic, macabre, detailed-darkness | Group C (SDXL VAE) + +IV. Architecture & Interiors +Architecture | Modern_Architecture_XL.safetensors | https://civitai.com/models/arch | blueprints, glass, minimalism, luxury-homes | Group C (SDXL VAE) +Architecture | Interior_Design_Flux_v2.safetensors | https://civitai.com/models/interior | home-staging, photorealistic-rooms, furniture-detail | Group A (Flux VAE) +Architecture | Abandoned_Places_XL.safetensors | https://civitai.com/models/overgrown | ruins, nature-reclaimed, moss, urban-decay | Group C (SDXL VAE) +Architecture | CyberCity_XL_Pro.safetensors | https://civitai.com/models/cybercity | skyscrapers, neon, futuristic-urbanism | Group C (SDXL VAE) +Architecture | Brutalist_Logic_XL.safetensors | https://civitai.com/models/brutalist | concrete, geometric, stark-shadows | Group C (SDXL VAE) +Architecture | Gothic_Stone_Carving.safetensors | https://civitai.com/models/gothic | cathedral, intricate-stone, historical | Group C (SDXL VAE) +Architecture | TinyHouse_Master_XL.safetensors | https://civitai.com/models/tinyhouse | compact-living, modular, wood-texture | Group C (SDXL VAE) +Architecture | Steampunk_City_v2.safetensors | https://civitai.com/models/steamcity | brass, pipes, smoke, industrial-victorian | Group C (SDXL VAE) +Architecture | Skyscraper_High_Angle.safetensors | https://civitai.com/models/skyscraper | aerial-view, glass-reflections, urban-density | Group C (SDXL VAE) +Architecture | Japanese_Traditional_House.safetensors | https://civitai.com/models/japanesehouse | tatami, sliding-doors, zen-garden | Group C (SDXL VAE) + +V. Landscapes & Environments +Landscape | Landscape_Anime_Max.safetensors | https://civitai.com/models/93931 | ghibli-style, lush-scenery, cloud-formations | Group C (SDXL VAE) +Landscape | GrandCanyon_RedRock_XL.safetensors | https://civitai.com/models/canyon | desert-textures, utah-scenery, sandstone | Group C (SDXL VAE) +Landscape | Misty_Forest_XL_v3.safetensors | https://civitai.com/models/misty | atmospheric-fog, pine-trees, moody-lighting | Group C (SDXL VAE) +Landscape | Arctic_Tundra_Pro.safetensors | https://civitai.com/models/arctic | snow-ice, blue-hour, aurora-borealis | Group C (SDXL VAE) +Landscape | Biolume_Jungle_XL.safetensors | https://civitai.com/models/biolume | glowing-plants, bioluminescence, exotic-nature | Group C (SDXL VAE) +Landscape | Volcano_Fury_XL.safetensors | https://civitai.com/models/volcano | lava, volcanic-smoke, dark-ash-sky | Group C (SDXL VAE) +Landscape | Alpine_Valley_v2.safetensors | https://civitai.com/models/alpine | mountains, green-valley, sharp-horizons | Group C (SDXL VAE) +Landscape | Lavender_Sunset_XL.safetensors | https://civitai.com/models/lavender | floral-fields, soft-sunset, horizon-line | Group C (SDXL VAE) +Landscape | Ocean_Deep_Pro.safetensors | https://civitai.com/models/ocean | underwater, coral-reefs, caustic-lighting | Group C (SDXL VAE) +Landscape | Martian_Colony_XL.safetensors | https://civitai.com/models/mars | red-dust, alien-sky, crater-landscape | Group C (SDXL VAE) + +VI. Specialized & Niche (3D, Video, Utility) +Video | Wan_2.1_T2V_14B.safetensors | https://huggingface.co/Wan-AI/Wan2.1-T2V-14B | text-to-video, cinematic-motion, high-consistency | Group V (Wan VAE) +Video | HunyuanVideo_v1.safetensors | https://huggingface.co/tencent/HunyuanVideo | ai-video, long-duration, high-fps | Group V (Hunyuan VAE) +Video | SVD-XT-1.1.safetensors | https://huggingface.co/stabilityai/stable-video-diffusion | image-to-video, professional-motion | Group V (SVD VAE) +Video | LivePortrait_XL_v2.safetensors | https://civitai.com/models/liveportrait | facial-animation, vlog-automation, lip-sync | Group C (SDXL VAE) +3D Render | Disney_Pixar_Cartoon_XL.safetensors | https://civitai.com/models/75650 | 3d-animation, subsurface-scattering, toy-like | Group C (SDXL VAE) +3D Render | Blender-Style-XL.safetensors | https://civitai.com/models/blender | cycles-render, hard-surface, pbr-textures | Group C (SDXL VAE) +3D Render | Clay-Aardman-Style.safetensors | https://civitai.com/models/clay | stop-motion, fingerprint-texture, handmade | Group C (SDXL VAE) +3D Render | Unreal-Engine-5-XL.safetensors | https://civitai.com/models/ue5 | game-engine-lighting, high-poly, nanite-detail | Group C (SDXL VAE) +3D Render | ZBrush-Sculpt-XL.safetensors | https://civitai.com/models/zbrush | digital-sculpt, grey-clay, intricate-carving | Group C (SDXL VAE) +Utility | ControlNet-Union-SDXL.safetensors | https://huggingface.co/xinsir/controlnet-union | depth, canny, pose, all-in-one-control | Group C +Utility | IP-Adapter-FaceID-XL.safetensors | https://civitai.com/models/faceid | face-preservation, identity-consistency | Group C +Utility | OmniGen-v2.safetensors | https://huggingface.co/BectorSpaceLab/OmniGen-v2 | multi-image-composition, instruct-editing | Group A +Utility | Hyper-SDXL-Lightning.safetensors | https://huggingface.co/ByteDance/Hyper-SD | 1-step-generation, real-time-previews | Group C +Utility | Segment-Anything-2.safetensors | https://huggingface.co/facebook/sam2 | object-masking, surgical-selection, video-tracking | Group ALL +Photography | Food-Photography-XL.safetensors | https://civitai.com/models/food | restaurant-lighting, macro-food, professional-plating | Group C +Photography | Macro-Nature-XL.safetensors | https://civitai.com/models/macro | insect-detail, shallow-depth, dew-drops | Group C +Photography | Product-Commercial-XL.safetensors | https://civitai.com/models/product | clean-background, product-lighting, minimalist | Group C +Portrait | Mature-Faces-XL-v2.safetensors | https://civitai.com/models/silver | elderly, silver-hair, character-wrinkles | Group C +Portrait | Fashion-Editorial-XL.safetensors | https://civitai.com/models/fashion | vogue-style, high-contrast, model-poses | Group C +Niche | Tarot-Card-Check-XL.safetensors | https://civitai.com/models/tarot | mystical-border, symbolic-art, gold-leaf | Group C +Niche | Stained-Glass-v4.safetensors | https://civitai.com/models/glass | backlit, translucent, religious-art | Group C +Niche | Blueprint-Draft-XL.safetensors | https://civitai.com/models/blueprint | technical-drawing, grid-lines, industrial-design | Group C +Niche | Pixel-Art-Ultra-XL.safetensors | https://civitai.com/models/pixelart | retro-gaming, 64-bit, sprites | Group C +Niche | Graffiti-Wall-Pro-XL.safetensors | https://civitai.com/models/graffiti | spray-paint, street-art, tag-style | Group C +Niche | Chalkboard-Menu-XL.safetensors | https://civitai.com/models/chalk | dusty-chalk, handwritten, slate-texture | Group C +Niche | Embroidery-Craft-XL.safetensors | https://civitai.com/models/embroidery | sewn-texture, thread-patterns, fabric-art | Group C +Niche | Neon-Signs-XL.safetensors | https://civitai.com/models/neonsign | glowing-tubes, dark-city-streets | Group C +Niche | Infographic-Clean-XL.safetensors | https://civitai.com/models/info | charts, clean-typography, icons | Group C +Logic | Qwen2.5-VL-7B-Instruct.safetensors | https://huggingface.co/Qwen/Qwen2.5-VL | vlm, image-understanding, prompt-logic | Group L +Logic | Llama-4-8B-Vision.safetensors | https://huggingface.co/meta-llama/Llama-4 | reasoning, captioning, visual-logic | Group L +Niche | Medical-Anatomy-XL.safetensors | https://civitai.com/models/medical | scientific-illustration, biological-accuracy | Group C +Niche | Jewelry-Detail-XL.safetensors | https://civitai.com/models/jewelry | gold-reflections, gems, high-macro | Group C +Niche | Comic-Book-Inker-XL.safetensors | https://civitai.com/models/inker | thick-black-lines, cross-hatching | Group C +Niche | Retro-VCR-XL.safetensors | https://civitai.com/models/vcr | glitch-art, tracking-lines, 80s-video | Group C +Niche | Miniature-World-XL.safetensors | https://civitai.com/models/miniature | tilt-shift, toy-figures, macro-landscape | Group C +""" + +data_loras = """ +I. People & Realism +Realism | Boreal_Realism_Flux.safetensors | https://huggingface.co/kudzueye/Boreal | hyper-realism, candid, natural-skin | Group A +Realism | Smartphone_Snap_v2.safetensors | https://civitai.com/models/652699 | iphone-photo, grainy, vertical, casual | Group A +Realism | RealLife_Skin_Fix.safetensors | https://civitai.com/models/114321 | detailed-pores, realistic-lighting | Group C +Realism | Mature_Faces_Slider.safetensors | https://civitai.com/models/16543 | wrinkles, aging, silver-hair, character | Group C +Realism | Fashion_Editorial_Flux.safetensors | https://huggingface.co/alvdansen/flux-koda | vogue, high-fashion, studio-lighting | Group A +Realism | Cinematic_Shot_XL.safetensors | https://civitai.com/models/cinematic | anamorphic, color-grade, movie-look | Group C +Realism | Steve_McCurry_Photography.safetensors | https://civitai.com/models/mccurry | nat-geo, sharp-eyes, travel-photography | Group C +Realism | Low-Light_Night_Photography.safetensors | https://civitai.com/models/nightphoto | night-vision, low-noise, street-ambient | Group C +Realism | 8k_Ultra_Detail_Flux.safetensors | https://huggingface.co/prithivMLmods/Canopus | sharpness, intricate, masterpiece | Group A +Realism | Amateur_Style_Snapshot.safetensors | https://civitai.com/models/amateur | shaky-cam, low-quality-aesthetic, real-photo | Group C +Realism | Facial_Expression_Extreme.safetensors | https://civitai.com/models/extreme-face | angry, happy, crying, slider | Group A +Realism | Skin_Texture_Macro_Flux.safetensors | https://civitai.com/models/macro-skin | extreme-close-up, pores, hair-follicles | Group A +Realism | Candid_Street_Photography.safetensors | https://civitai.com/models/streetphoto | urban, crowd, natural-lighting | Group C +Realism | Kodak_Portra_400_XL.safetensors | https://civitai.com/models/kodak | warm-tones, film-grain, analog-photography | Group C +Realism | Hand_Fixer_v2.safetensors | https://civitai.com/models/handfix | perfect-fingers, anatomy-correction | Group C + +II. Anime & Stylized Characters +Anime | Disney_Pixar_Style_v2.safetensors | https://civitai.com/models/459655 | disney, pixar, 3d-render, cartoon | Group C +Anime | Sakimi-Chan_Style_Flux.safetensors | https://civitai.com/models/21133 | digital-painting, vibrant, stylized-eyes | Group A +Anime | Makoto_Shinkai_Vibe.safetensors | https://civitai.com/models/shinkai | vibrant-sky, lens-flare, ghibli-hybrid | Group C +Anime | Arcane_Texture_XL.safetensors | https://civitai.com/models/arcane-lora | thick-paint, neon-accents, arcane-art | Group C +Anime | Spider-Verse_VFX.safetensors | https://civitai.com/models/spiderverse | chromatic-aberration, comic-dots | Group A +Anime | Retro_Anime_90s.safetensors | https://civitai.com/models/retro90s | vhs-aesthetic, scanlines, soft-colors | Group C +Anime | Claymation_Stop_Motion.safetensors | https://civitai.com/models/8876 | handmade, fingerprints, clay-texture | Group C +Anime | Papercraft_Origami_XL.safetensors | https://civitai.com/models/9987 | layered-paper, crafting, 3d-depth | Group C +Anime | Comic_Ink_Lineart.safetensors | https://civitai.com/models/lineart | manga-lineart, clean-inking, b&w | Group C +Anime | Ghibli_Background_Artist.safetensors | https://civitai.com/models/ghibliback | scenic, nature, watercolor-anime | Group C +Anime | Neon_Cyberpunk_Fashion.safetensors | https://civitai.com/models/neoncloth | glowing-fabric, techwear, futuristic | Group A +Anime | Elf_Ears_Fantasy.safetensors | https://civitai.com/models/elfears | pointed-ears, lotr-style, fantasy | Group C +Anime | Super_Hero_Suit_Texture.safetensors | https://civitai.com/models/supersuit | spandex, mask, metallic-armor | Group C +Anime | Cybernetic_Implants.safetensors | https://civitai.com/models/cybertech | augmented-parts, wires, metal-skin | Group A +Anime | Expressions_Anime_XL.safetensors | https://civitai.com/models/animeface | exaggerated, comedy, expressive | Group C + +III. Artistic & Painting Styles +Painting | Oil_Impasto_Texture.safetensors | https://civitai.com/models/3030 | thick-paint, palette-knife, heavy-texture | Group C +Painting | Watercolor_Wash_Pro.safetensors | https://civitai.com/models/4040 | soft-edges, paper-texture, artistic-wash | Group C +Painting | Charcoal_Sketch_Flux.safetensors | https://huggingface.co/b_lora/charcoal | dark-lines, smudged, hand-drawn | Group A +Painting | Ukiyo-e_Traditional.safetensors | https://civitai.com/models/5050 | japanese-woodblock, flat-colors | Group C +Painting | Art_Nouveau_Mucha.safetensors | https://civitai.com/models/6060 | ornate, flowing-hair, organic-patterns | Group C +Painting | Synthwave_Grid_Vibe.safetensors | https://civitai.com/models/synthglow | 80s-retro, pink-grid, laser-beams | Group A +Painting | Gothic_Noir_XL.safetensors | https://civitai.com/models/noir | high-contrast, dark-mystery, moody | Group C +Painting | Minimalist_Vector_Art.safetensors | https://huggingface.co/renderartist/vector | clean-lines, flat-design, corporate | Group A +Painting | Steampunk_Gears_Brass.safetensors | https://civitai.com/models/9090 | victorian-tech, steam, pipes | Group C +Painting | Chalkboard_Art_Texture.safetensors | https://civitai.com/models/3236 | handwritten, dusty, white-chalk | Group A +Painting | Embroidery_Sewn_Art.safetensors | https://civitai.com/models/4347 | thread-work, stitch-patterns, fabric | Group C +Painting | Graffiti_Drip_Style.safetensors | https://civitai.com/models/2125 | urban-tags, paint-drips, street-art | Group C +Painting | Inkpunk_Splash_Art.safetensors | https://civitai.com/models/2020 | sketchy, industrial, ink-wash | Group C +Painting | Pastel_Dream_Vibe.safetensors | https://civitai.com/models/pastel | soft-colors, airy, ethereal | Group C +Painting | Pop_Art_Lichtenstein.safetensors | https://civitai.com/models/popart | halftone-dots, primary-colors, bold-inking | Group C + +IV. Architecture & Environments +Architecture | Modern_Interior_Design.safetensors | https://civitai.com/models/1111 | minimalist, glass, luxury, staging | Group C +Architecture | Abandoned_Overgrown_Flux.safetensors | https://civitai.com/models/abandoned | ruins, nature-reclaimed, decay | Group A +Architecture | Cyber_City_Neon_Grid.safetensors | https://civitai.com/models/2222 | skyscrapers, neon-signs, futuristic | Group C +Architecture | Cozy_Cottage_Garden.safetensors | https://civitai.com/models/3333 | rural, warm-lighting, wood-textures | Group C +Architecture | Brutalist_Concrete_Flux.safetensors | https://civitai.com/models/4444 | stark-shadows, geometric, grey | Group A +Architecture | Gothic_Stone_Cathedral.safetensors | https://civitai.com/models/5555 | religious-art, intricate-carving | Group C +Architecture | Treehouse_Natural_Build.safetensors | https://civitai.com/models/6666 | hanging-structures, forest, wood | Group C +Architecture | Martian_Base_Modular.safetensors | https://civitai.com/models/7777 | scifi-domes, red-dust, futuristic | Group A +Architecture | Underwater_Biosphere.safetensors | https://civitai.com/models/8888 | glass-tunnels, coral, bioluminescence | Group C +Architecture | Tiny_House_Organization.safetensors | https://civitai.com/models/9999 | compact, smart-design, modern | Group C +Environment | Kitchen_Luxury_Flux.safetensors | https://civitai.com/models/4345 | marble-counters, high-end-appliances | Group A +Environment | Gamer_Room_RGB.safetensors | https://civitai.com/models/5456 | pc-setup, neon-strips, peripherals | Group C +Environment | Old_Library_Mahogany.safetensors | https://civitai.com/models/6567 | leather-books, dust-motes, wood-panels | Group C +Environment | Zen_Garden_Tranquil.safetensors | https://civitai.com/models/7678 | sand-patterns, bonsai, koi-pond | Group C +Environment | Industrial_Loft_Flux.safetensors | https://civitai.com/models/8789 | exposed-brick, high-ceilings, metal | Group A + +V. Landscape & Photography Utility +Landscape | Arctic_Ice_Tundra.safetensors | https://civitai.com/models/1020 | snow-glare, blue-hour, cold | Group A +Landscape | Tropical_Island_Beach.safetensors | https://civitai.com/models/2030 | palm-trees, clear-water, sunny | Group C +Landscape | Grand_Canyon_Utah_XL.safetensors | https://civitai.com/models/3040 | red-rock, desert, high-dynamic-range | Group C +Landscape | Misty_Forest_Flux_Atmo.safetensors | https://civitai.com/models/4050 | fog, pine-trees, mysterious | Group A +Landscape | Volcano_Eruption_Fire.safetensors | https://civitai.com/models/5060 | lava, ash-cloud, cinematic-destruction | Group C +Landscape | Lavender_Field_Horizon.safetensors | https://civitai.com/models/6070 | purple, sunset-scenery, flora | Group C +Landscape | Space_Nebula_Starfield.safetensors | https://civitai.com/models/7080 | deep-space, galaxies, vibrant-colors | Group A +Landscape | Swiss_Alps_Summit.safetensors | https://civitai.com/models/8090 | mountain-peak, village, sharp-focus | Group C +Landscape | Autumn_Fall_Colors.safetensors | https://civitai.com/models/9001 | orange-leaves, crisp-air, seasonal | Group C +Landscape | Biolume_Crystal_Cave.safetensors | https://civitai.com/models/1002 | glow, fantasy-nature, subterranean | Group A +Utility | Detailifier_Enhance_XL.safetensors | https://civitai.com/models/126343 | sharpen, micro-detail, 8k-upscale | Group C +Utility | Lighting_Master_Pro.safetensors | https://civitai.com/models/2002 | rim-lighting, moody-shadows | Group C +Utility | No-Background_Sticker.safetensors | https://civitai.com/models/3003 | flat-background, cutout, white | Group A +Utility | Text_Spelling_Fixer.safetensors | https://civitai.com/models/4004 | coherent-text, correct-letters | Group A +Utility | Color_Pop_Vibrant.safetensors | https://civitai.com/models/1011 | saturation, vivid, contrast | Group A + +VI. Niche, Clothes & Items (Remaining 25) +Clothing | Tactical_Armor_XL.safetensors | https://civitai.com/models/2022 | military-gear, straps, metal-texture | Group C +Clothing | Victorian_Lace_Dress.safetensors | https://civitai.com/models/3033 | historical-fabric, intricate-detail | Group C +Clothing | Silk_Satin_Flow_Flux.safetensors | https://civitai.com/models/2233 | reflective-fabric, soft-draping | Group A +Jewelry | Diamond_Gold_Macro.safetensors | https://civitai.com/models/5055 | reflective, gems, high-detail | Group C +Items | Sneaker_Head_Designer.safetensors | https://civitai.com/models/7077 | modern-shoes, street-fashion | Group A +Items | Mechanical_Clock_Gears.safetensors | https://civitai.com/models/gears | brass, intricate, movement | Group C +Items | Antique_Book_Binding.safetensors | https://civitai.com/models/oldbook | leather, gold-embossing, worn | Group C +Items | Glass_Orb_Reflection.safetensors | https://civitai.com/models/glassorb | distorted-view, caustic-light | Group C +Items | Potion_Bottle_Fantasy.safetensors | https://civitai.com/models/potion | liquid-fx, glowing, cork | Group C +VFX | Magic_Spell_Particles.safetensors | https://civitai.com/models/magic | glow, sparks, floating-runes | Group C +VFX | Glitch_Digital_Artifacts.safetensors | https://civitai.com/models/glitch | data-corruption, neon-smear | Group C +VFX | Smoke_Fog_Volumetric.safetensors | https://civitai.com/models/smokefx | swirling, density, realistic-mist | Group C +Niche | Tarot_Card_Frame.safetensors | https://civitai.com/models/5457 | mystical, border, card-art | Group A +Niche | Blueprint_Technical.safetensors | https://civitai.com/models/6568 | grid-lines, blue, drafting | Group C +Niche | Stained_Glass_Effect.safetensors | https://civitai.com/models/8790 | translucent, backlit, colorful | Group C +Niche | Origami_Paper_Fold.safetensors | https://civitai.com/models/9891 | geometric, sharp-creases | Group A +Niche | Tattoo_Traditional_Flash.safetensors | https://civitai.com/models/1014 | ink, white-background, classic | Group C +Niche | Neon_Sign_Glow.safetensors | https://civitai.com/models/5458 | night, glass-tubes, ambient-light | Group A +Niche | Miniature_Tilt_Shift.safetensors | https://civitai.com/models/tiltshift | diorama, blurred-top-bottom | Group C +Niche | Hologram_Projection.safetensors | https://civitai.com/models/holo | transparent, scan-lines, blue-glow | Group C +Niche | Infographic_Icon_Set.safetensors | https://civitai.com/models/icons | flat, minimalist, vector-symbols | Group C +Niche | Food_Plating_Gourmet.safetensors | https://civitai.com/models/plating | chef-style, fine-dining, sauce-drip | Group C +Niche | Sci-Fi_HUD_Overlay.safetensors | https://civitai.com/models/hud | user-interface, tech-graphics | Group C +Niche | Post-Apocalypse_Dust.safetensors | https://civitai.com/models/dust | hazy, gritty, sun-beams | Group C +Niche | Bioluminescent_Creatures.safetensors | https://civitai.com/models/biolumecreature | avatar-style, glowing-skin | Group C +""" + +def parse_data(raw_text, item_type="checkpoint"): + items = [] + lines = raw_text.strip().split('\n') + for line in lines: + line = line.strip() + if not line or line.startswith("I"): continue # Skip headers/empty + + parts = [p.strip() for p in line.split("|")] + if len(parts) >= 5: + category = parts[0] + filename = parts[1] + url = parts[2] + tags = [t.strip() for t in parts[3].split(",")] + group_info = parts[4].lower() + + # Simple type inference based on Group info + model_type = "sdxl" + if "flux" in group_info: model_type = "flux" + elif "sd1.5" in group_info: model_type = "sd15" + elif "svd" in group_info: model_type = "svd" + elif "wan" in group_info: model_type = "wan" + elif "hunyuan" in group_info: model_type = "hunyuan" + + item = { + "name": filename, + "category": category, + "url": url, + "tags": tags, + "type": model_type, + "group": parts[4], # Keep original group string + "recommended": True + } + items.append(item) + return items + +checkpoints = parse_data(data_checkpoints, "checkpoint") +loras = parse_data(data_loras, "lora") + +registry = { + "version": 1, + "checkpoints": checkpoints, + "loras": loras +} + +with open("d:\\ComfyUI\\0.10\\ComfyUI\\custom_nodes\\ComfyUI-Cluster\\model_registry.json", "w", encoding="utf-8") as f: + json.dump(registry, f, indent=2) + +print("Registry created successfully.") diff --git a/install.bat b/install.bat new file mode 100644 index 0000000..6ef04f7 --- /dev/null +++ b/install.bat @@ -0,0 +1,60 @@ +@echo off +setlocal enabledelayedexpansion + +REM Resolve paths +set "SCRIPT_DIR=%~dp0" +for %%I in ("%SCRIPT_DIR%..\\..") do set "COMFY_ROOT=%%~fI" +set "DEFAULT_MODEL_ROOT=%COMFY_ROOT%\\models" +set "MANIFEST=%SCRIPT_DIR%install_manifest.json" + +echo. +echo ComfyUI-Cluster Installer +echo ------------------------- +echo ComfyUI Root: %COMFY_ROOT% +echo Default Model Root: %DEFAULT_MODEL_ROOT% +echo. + +set /p MODEL_ROOT=Enter model root path (press Enter to use default): +if "%MODEL_ROOT%"=="" set "MODEL_ROOT=%DEFAULT_MODEL_ROOT%" + +echo. +echo Using model root: %MODEL_ROOT% +echo. + +if not exist "%MANIFEST%" ( + echo ERROR: install_manifest.json not found at %MANIFEST% + exit /b 1 +) + +REM Download models using PowerShell +powershell -NoProfile -ExecutionPolicy Bypass -Command ^ + "$ErrorActionPreference='Stop';" ^ + "$manifest = Get-Content -Raw -Path '%MANIFEST%' | ConvertFrom-Json;" ^ + "$root = '%MODEL_ROOT%';" ^ + "function Download-File([string]$url,[string]$out){ if(Test-Path $out){ Write-Host '[skip]' $out; return } $headers=@{}; if($env:HF_TOKEN){ $headers['Authorization']='Bearer ' + $env:HF_TOKEN } Invoke-WebRequest -Uri $url -OutFile $out -Headers $headers }" ^ + "foreach($item in $manifest.downloads){ $dir = Join-Path $root $item.subdir; New-Item -ItemType Directory -Force -Path $dir | Out-Null; $out = Join-Path $dir $item.filename; Write-Host '[download]' $item.url '->' $out; Download-File $item.url $out }" + +if errorlevel 1 ( + echo. + echo Download step failed. If models require a license, set HF_TOKEN and retry. + echo Example: set HF_TOKEN=your_hf_token + exit /b 1 +) + +REM Update extra_model_paths.yaml +powershell -NoProfile -ExecutionPolicy Bypass -Command ^ + "$yamlPath = Join-Path '%COMFY_ROOT%' 'extra_model_paths.yaml';" ^ + "if(Test-Path $yamlPath){" ^ + " $content = Get-Content -Raw -Path $yamlPath;" ^ + " if($content -notmatch '(?m)^ollama_cluster:'){" ^ + " $base = ('%MODEL_ROOT%' -replace '\\\\','/');" ^ + " $section = \"`nollama_cluster:`n base_path: $base`n is_default: true`n checkpoints: models/checkpoints/`n loras: models/loras/`n controlnet: models/controlnet/`n vae: models/vae/`n embeddings: models/embeddings/`n\";" ^ + " Add-Content -Path $yamlPath -Value $section;" ^ + " Write-Host '[yaml] Added ollama_cluster section to extra_model_paths.yaml';" ^ + " } else { Write-Host '[yaml] ollama_cluster section already present'; }" ^ + "} else { Write-Host '[yaml] extra_model_paths.yaml not found; skipping'; }" + +echo. +echo Done. Restart ComfyUI to load new models and custom nodes. +echo. +exit /b 0 diff --git a/install_manifest.json b/install_manifest.json new file mode 100644 index 0000000..411c88f --- /dev/null +++ b/install_manifest.json @@ -0,0 +1,39 @@ +{ + "downloads": [ + { + "url": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors", + "subdir": "checkpoints", + "filename": "sd_xl_base_1.0.safetensors" + }, + { + "url": "https://huggingface.co/stabilityai/stable-diffusion-xl-refiner-1.0/resolve/main/sd_xl_refiner_1.0.safetensors", + "subdir": "checkpoints", + "filename": "sd_xl_refiner_1.0.safetensors" + }, + { + "url": "https://huggingface.co/ByteDance/SDXL-Lightning/resolve/main/sdxl_lightning_4step.safetensors", + "subdir": "checkpoints", + "filename": "sdxl_lightning_4step.safetensors" + }, + { + "url": "https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors", + "subdir": "checkpoints", + "filename": "v1-5-pruned-emaonly.safetensors" + }, + { + "url": "https://huggingface.co/StableDiffusionVN/Prunedcontrolnet/resolve/main/control_v11p_sd15_canny.safetensors", + "subdir": "controlnet", + "filename": "control_v11p_sd15_canny.safetensors" + }, + { + "url": "https://huggingface.co/StableDiffusionVN/Prunedcontrolnet/resolve/main/control_v11p_sd15_openpose.safetensors", + "subdir": "controlnet", + "filename": "control_v11p_sd15_openpose.safetensors" + }, + { + "url": "https://huggingface.co/latent-consistency/lcm-lora-sdxl/resolve/main/pytorch_lora_weights.safetensors", + "subdir": "loras", + "filename": "lcm_lora_sdxl.safetensors" + } + ] +} diff --git a/model_registry.json b/model_registry.json new file mode 100644 index 0000000..846255f --- /dev/null +++ b/model_registry.json @@ -0,0 +1,2610 @@ +{ + "version": 1, + "checkpoints": [ + { + "name": "Flux.1-dev-fp8.safetensors", + "category": "Photorealism", + "url": "https://huggingface.co/black-forest-labs/FLUX.1-dev", + "tags": [ + "flux", + "dev", + "fp8", + "realism" + ], + "type": "flux", + "group": "Group A (Flux VAE / T5-CLIP)", + "recommended": true, + "required_vae": "ae.safetensors", + "required_clip": "clip_l.safetensors, t5xxl_fp8_e4m3fn.safetensors", + "folder_type": "diffusion_models" + }, + { + "name": "Flux.2-dev-fp16.safetensors", + "category": "Photorealism", + "url": "https://huggingface.co/black-forest-labs/FLUX.2-dev", + "tags": [ + "hyper-realism", + "skin-texture", + "text-rendering", + "high-fidelity" + ], + "type": "flux", + "group": "Group A (Flux VAE / T5-CLIP)", + "recommended": true + }, + { + "name": "RealVisXL_V6.0_B1.safetensors", + "category": "Photorealism", + "url": "https://civitai.com/models/150629", + "tags": [ + "raw-photo", + "dslr", + "8k", + "professional-photography", + "anatomy" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE / CLIP-L)", + "recommended": true + }, + { + "name": "Juggernaut_XL_v10.safetensors", + "category": "Photorealism", + "url": "https://civitai.com/models/133005", + "tags": [ + "cinematic", + "lighting", + "generalist", + "commercial-photography" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Z-Image-Turbo-v1.safetensors", + "category": "Photorealism", + "url": "https://huggingface.co/Tongyi-MAI/Z-Image-Turbo", + "tags": [ + "speed", + "realism", + "architectural", + "clean-lighting" + ], + "type": "sdxl", + "group": "Group B (Z-VAE / Z-CLIP)", + "recommended": true + }, + { + "name": "epiCPhotoGasm_v2.safetensors", + "category": "Photorealism", + "url": "https://civitai.com/models/132632", + "tags": [ + "studio-lighting", + "portraits", + "soft-skin", + "bokeh" + ], + "type": "sd15", + "group": "Group D (SD1.5 VAE)", + "recommended": true + }, + { + "name": "Realistic_Vision_V7.0.safetensors", + "category": "Photorealism", + "url": "https://civitai.com/models/4201", + "tags": [ + "analog-film", + "retro", + "human-anatomy", + "natural-poses" + ], + "type": "sd15", + "group": "Group D (SD1.5 VAE)", + "recommended": true + }, + { + "name": "IC-Light-XL-Realistic.safetensors", + "category": "Photorealism", + "url": "https://huggingface.co/lllyasviel/ic-light", + "tags": [ + "relighting", + "shadow-control", + "background-blending" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Sateluco-IOR-XL.safetensors", + "category": "Photorealism", + "url": "https://civitai.com/models/sateluco", + "tags": [ + "extreme-clarity", + "high-dynamic-range", + "reflection-logic" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "AnalogMadness_XL_v4.safetensors", + "category": "Photorealism", + "url": "https://civitai.com/models/analog-xl", + "tags": [ + "film-grain", + "kodak-portra", + "fuji-film", + "nostalgia" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Photopedia_XL_v2.safetensors", + "category": "Photorealism", + "url": "https://civitai.com/models/photopedia", + "tags": [ + "stock-photography", + "documentary-style", + "authentic" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Mobius_XL_v1.safetensors", + "category": "Photorealism", + "url": "https://civitai.com/models/mobius", + "tags": [ + "lighting-consistency", + "character-depth", + "sharp-focus" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "AbsoluteReality_v1.8.safetensors", + "category": "Photorealism", + "url": "https://civitai.com/models/81458", + "tags": [ + "sharp-details", + "natural-skin", + "non-ai-look" + ], + "type": "sd15", + "group": "Group D (SD1.5 VAE)", + "recommended": true + }, + { + "name": "ThinkDiffusionXL_v1.safetensors", + "category": "Photorealism", + "url": "https://civitai.com/models/thinkdiffusion", + "tags": [ + "4k-resolution", + "crisp-edges", + "studio-render" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "NightVision_XL_v9.safetensors", + "category": "Photorealism", + "url": "https://civitai.com/models/nightvision", + "tags": [ + "night-photography", + "low-light", + "street-lighting" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "CyberRealistic_XL_v6.safetensors", + "category": "Photorealism", + "url": "https://civitai.com/models/cyberrealistic", + "tags": [ + "hyper-detail", + "intricate-skin", + "photorealism" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Illustrious-XL-v1.0.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/illustrious", + "tags": [ + "anime", + "high-res", + "cel-shaded", + "modern-anime" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "PonyDiffusion_V7_XL.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/257", + "tags": [ + "character-design", + "complex-poses", + "fandom", + "expressive" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "AAM_XL_AnimeMix_v2.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/anime-mix", + "tags": [ + "vibrant", + "fantasy-anime", + "wallpaper-quality" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "MeinaMix_V11.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/meinamix", + "tags": [ + "soft-anime", + "romantic-style", + "clean-lines" + ], + "type": "sd15", + "group": "Group D (SD1.5 VAE)", + "recommended": true + }, + { + "name": "Cetus-Mix-Final.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/cetus", + "tags": [ + "painterly-anime", + "2.5D", + "semi-realistic" + ], + "type": "sd15", + "group": "Group D (SD1.5 VAE)", + "recommended": true + }, + { + "name": "NoobAI-XL-v1.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/noobai", + "tags": [ + "danbooru-tags", + "technical-prompts", + "precision" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Animagine_XL_v4.0.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/animagine", + "tags": [ + "consistent-character", + "high-quality-anime", + "official-art-style" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "AbyssOrangeMix3_AOM3.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/abyssorangemix", + "tags": [ + "dark-anime", + "detailed-eyes", + "aesthetic" + ], + "type": "sd15", + "group": "Group D (SD1.5 VAE)", + "recommended": true + }, + { + "name": "Counterfeit-V3.0.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/counterfeit", + "tags": [ + "background-focused", + "atmospheric-anime" + ], + "type": "sd15", + "group": "Group D (SD1.5 VAE)", + "recommended": true + }, + { + "name": "BlueberryMix_XL.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/blueberry", + "tags": [ + "moe-style", + "soft-colors", + "kawaii" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Mistoon_Anime_v2.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/mistoon", + "tags": [ + "flat-color", + "vector-style", + "clean-anime" + ], + "type": "sd15", + "group": "Group D (SD1.5 VAE)", + "recommended": true + }, + { + "name": "Pastel-Mix-Stylized.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/pastel-mix", + "tags": [ + "pastel-colors", + "soft-lighting", + "artistic-anime" + ], + "type": "sd15", + "group": "Group D (SD1.5 VAE)", + "recommended": true + }, + { + "name": "Real-Anime-XL.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/real-anime", + "tags": [ + "semi-realistic", + "3d-anime-hybrid", + "thick-shading" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "HoloDayo_XL_v1.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/holodayo", + "tags": [ + "hololive-consistent", + "fanart-style", + "vibrant" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Waifu-Diffusion-XL.safetensors", + "category": "Anime", + "url": "https://huggingface.co/waifu-diffusion", + "tags": [ + "base-anime", + "massive-dataset", + "legacy" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Painterly_XL_Check_v2.safetensors", + "category": "Artistic", + "url": "https://civitai.com/models/painterly", + "tags": [ + "oil-painting", + "canvas", + "impasto", + "brushstrokes" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "DreamShaper_XL_Turbo.safetensors", + "category": "Artistic", + "url": "https://civitai.com/models/112902", + "tags": [ + "digital-art", + "fantasy", + "concept-art", + "versatile" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Watercolor_Stylized_v3.safetensors", + "category": "Artistic", + "url": "https://civitai.com/models/watercolor", + "tags": [ + "water-wash", + "ink-bleed", + "traditional-media" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Copax_TimeLess_XL_v9.safetensors", + "category": "Artistic", + "url": "https://civitai.com/models/timeless", + "tags": [ + "atmospheric", + "ethereal", + "fine-art" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Oil_Brush_Pro_v2.safetensors", + "category": "Artistic", + "url": "https://civitai.com/models/oilbrush", + "tags": [ + "thick-paint", + "museum-quality", + "traditional" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Arcane_Style_XL.safetensors", + "category": "Artistic", + "url": "https://civitai.com/models/arcane", + "tags": [ + "stylized", + "neon", + "paint-splatter", + "arcane-tv-style" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Inkpunk_Diffusion_v2.safetensors", + "category": "Artistic", + "url": "https://civitai.com/models/inkpunk", + "tags": [ + "ink-sketch", + "messy-lines", + "cyberpunk-art" + ], + "type": "sd15", + "group": "Group D (SD1.5 VAE)", + "recommended": true + }, + { + "name": "Abstract_Master_XL.safetensors", + "category": "Artistic", + "url": "https://civitai.com/models/abstract", + "tags": [ + "geometric", + "non-representative", + "fine-art-abstract" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Charcoal_Pro_v1.safetensors", + "category": "Artistic", + "url": "https://civitai.com/models/charcoal", + "tags": [ + "black-and-white", + "hand-drawn", + "smudge-texture" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Ukiyo-e_XL_v2.safetensors", + "category": "Artistic", + "url": "https://civitai.com/models/ukiyoe", + "tags": [ + "japanese-print", + "woodblock", + "historical-art" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "ComicMix_XL_v3.safetensors", + "category": "Artistic", + "url": "https://civitai.com/models/comicmix", + "tags": [ + "western-comic", + "marvel-style", + "clean-inking" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Gouache_Style_XL.safetensors", + "category": "Artistic", + "url": "https://civitai.com/models/gouache", + "tags": [ + "matte-paint", + "thick-wash", + "opaque-watercolor" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Pencil-Sketch-XL.safetensors", + "category": "Artistic", + "url": "https://civitai.com/models/pencil-sketch", + "tags": [ + "graphite", + "cross-hatching", + "realistic-sketch" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Synthwave_Master_XL.safetensors", + "category": "Artistic", + "url": "https://civitai.com/models/synthwave", + "tags": [ + "80s-aesthetic", + "retro-future", + "vibrant-pink-blue" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Dark-Vibe-XL.safetensors", + "category": "Artistic", + "url": "https://civitai.com/models/dark-vibe", + "tags": [ + "gothic", + "macabre", + "detailed-darkness" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Modern_Architecture_XL.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/arch", + "tags": [ + "blueprints", + "glass", + "minimalism", + "luxury-homes" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Interior_Design_Flux_v2.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/interior", + "tags": [ + "home-staging", + "photorealistic-rooms", + "furniture-detail" + ], + "type": "flux", + "group": "Group A (Flux VAE)", + "recommended": true + }, + { + "name": "Abandoned_Places_XL.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/overgrown", + "tags": [ + "ruins", + "nature-reclaimed", + "moss", + "urban-decay" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "CyberCity_XL_Pro.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/cybercity", + "tags": [ + "skyscrapers", + "neon", + "futuristic-urbanism" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Brutalist_Logic_XL.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/brutalist", + "tags": [ + "concrete", + "geometric", + "stark-shadows" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Gothic_Stone_Carving.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/gothic", + "tags": [ + "cathedral", + "intricate-stone", + "historical" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "TinyHouse_Master_XL.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/tinyhouse", + "tags": [ + "compact-living", + "modular", + "wood-texture" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Steampunk_City_v2.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/steamcity", + "tags": [ + "brass", + "pipes", + "smoke", + "industrial-victorian" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Skyscraper_High_Angle.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/skyscraper", + "tags": [ + "aerial-view", + "glass-reflections", + "urban-density" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Japanese_Traditional_House.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/japanesehouse", + "tags": [ + "tatami", + "sliding-doors", + "zen-garden" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Landscape_Anime_Max.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/93931", + "tags": [ + "ghibli-style", + "lush-scenery", + "cloud-formations" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "GrandCanyon_RedRock_XL.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/canyon", + "tags": [ + "desert-textures", + "utah-scenery", + "sandstone" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Misty_Forest_XL_v3.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/misty", + "tags": [ + "atmospheric-fog", + "pine-trees", + "moody-lighting" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Arctic_Tundra_Pro.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/arctic", + "tags": [ + "snow-ice", + "blue-hour", + "aurora-borealis" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Biolume_Jungle_XL.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/biolume", + "tags": [ + "glowing-plants", + "bioluminescence", + "exotic-nature" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Volcano_Fury_XL.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/volcano", + "tags": [ + "lava", + "volcanic-smoke", + "dark-ash-sky" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Alpine_Valley_v2.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/alpine", + "tags": [ + "mountains", + "green-valley", + "sharp-horizons" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Lavender_Sunset_XL.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/lavender", + "tags": [ + "floral-fields", + "soft-sunset", + "horizon-line" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Ocean_Deep_Pro.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/ocean", + "tags": [ + "underwater", + "coral-reefs", + "caustic-lighting" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Martian_Colony_XL.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/mars", + "tags": [ + "red-dust", + "alien-sky", + "crater-landscape" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Wan_2.1_T2V_14B.safetensors", + "category": "Video", + "url": "https://huggingface.co/Wan-AI/Wan2.1-T2V-14B", + "tags": [ + "text-to-video", + "cinematic-motion", + "high-consistency" + ], + "type": "wan", + "group": "Group V (Wan VAE)", + "recommended": true, + "required_vae": "wan_2.1_vae.safetensors", + "required_clip": "umt5_xxl_fp8_e4m3fn_scaled.safetensors", + "folder_type": "diffusion_models" + }, + { + "name": "Wan2.1-t2v-1.3B.safetensors", + "category": "Video", + "url": "https://huggingface.co/Wan-AI/Wan2.1-T2V-1.3B", + "tags": [ + "text-to-video", + "efficient", + "fast" + ], + "type": "wan", + "group": "Group V (Wan VAE)", + "recommended": true, + "required_vae": "wan_2.1_vae.safetensors", + "required_clip": "umt5_xxl_fp8_e4m3fn_scaled.safetensors", + "folder_type": "diffusion_models" + }, + { + "name": "HunyuanVideo_v1.safetensors", + "category": "Video", + "url": "https://huggingface.co/tencent/HunyuanVideo", + "tags": [ + "ai-video", + "long-duration", + "high-fps" + ], + "type": "hunyuan", + "group": "Group V (Hunyuan VAE)", + "recommended": true, + "required_vae": "hunyuan_video_vae.safetensors", + "required_clip": "llava_llama3_fp8_scaled.safetensors, clip_l.safetensors", + "folder_type": "diffusion_models" + }, + { + "name": "SVD-XT-1.1.safetensors", + "category": "Video", + "url": "https://huggingface.co/stabilityai/stable-video-diffusion", + "tags": [ + "image-to-video", + "professional-motion" + ], + "type": "svd", + "group": "Group V (SVD VAE)", + "recommended": true + }, + { + "name": "LivePortrait_XL_v2.safetensors", + "category": "Video", + "url": "https://civitai.com/models/liveportrait", + "tags": [ + "facial-animation", + "vlog-automation", + "lip-sync" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Disney_Pixar_Cartoon_XL.safetensors", + "category": "3D Render", + "url": "https://civitai.com/models/75650", + "tags": [ + "3d-animation", + "subsurface-scattering", + "toy-like" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Blender-Style-XL.safetensors", + "category": "3D Render", + "url": "https://civitai.com/models/blender", + "tags": [ + "cycles-render", + "hard-surface", + "pbr-textures" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Clay-Aardman-Style.safetensors", + "category": "3D Render", + "url": "https://civitai.com/models/clay", + "tags": [ + "stop-motion", + "fingerprint-texture", + "handmade" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "Unreal-Engine-5-XL.safetensors", + "category": "3D Render", + "url": "https://civitai.com/models/ue5", + "tags": [ + "game-engine-lighting", + "high-poly", + "nanite-detail" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "ZBrush-Sculpt-XL.safetensors", + "category": "3D Render", + "url": "https://civitai.com/models/zbrush", + "tags": [ + "digital-sculpt", + "grey-clay", + "intricate-carving" + ], + "type": "sdxl", + "group": "Group C (SDXL VAE)", + "recommended": true + }, + { + "name": "ControlNet-Union-SDXL.safetensors", + "category": "Utility", + "url": "https://huggingface.co/xinsir/controlnet-union", + "tags": [ + "depth", + "canny", + "pose", + "all-in-one-control" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "IP-Adapter-FaceID-XL.safetensors", + "category": "Utility", + "url": "https://civitai.com/models/faceid", + "tags": [ + "face-preservation", + "identity-consistency" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "OmniGen-v2.safetensors", + "category": "Utility", + "url": "https://huggingface.co/BectorSpaceLab/OmniGen-v2", + "tags": [ + "multi-image-composition", + "instruct-editing" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Hyper-SDXL-Lightning.safetensors", + "category": "Utility", + "url": "https://huggingface.co/ByteDance/Hyper-SD", + "tags": [ + "1-step-generation", + "real-time-previews" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Segment-Anything-2.safetensors", + "category": "Utility", + "url": "https://huggingface.co/facebook/sam2", + "tags": [ + "object-masking", + "surgical-selection", + "video-tracking" + ], + "type": "sdxl", + "group": "Group ALL", + "recommended": true + }, + { + "name": "Food-Photography-XL.safetensors", + "category": "Photography", + "url": "https://civitai.com/models/food", + "tags": [ + "restaurant-lighting", + "macro-food", + "professional-plating" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Macro-Nature-XL.safetensors", + "category": "Photography", + "url": "https://civitai.com/models/macro", + "tags": [ + "insect-detail", + "shallow-depth", + "dew-drops" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Product-Commercial-XL.safetensors", + "category": "Photography", + "url": "https://civitai.com/models/product", + "tags": [ + "clean-background", + "product-lighting", + "minimalist" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Mature-Faces-XL-v2.safetensors", + "category": "Portrait", + "url": "https://civitai.com/models/silver", + "tags": [ + "elderly", + "silver-hair", + "character-wrinkles" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Fashion-Editorial-XL.safetensors", + "category": "Portrait", + "url": "https://civitai.com/models/fashion", + "tags": [ + "vogue-style", + "high-contrast", + "model-poses" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Tarot-Card-Check-XL.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/tarot", + "tags": [ + "mystical-border", + "symbolic-art", + "gold-leaf" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Stained-Glass-v4.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/glass", + "tags": [ + "backlit", + "translucent", + "religious-art" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Blueprint-Draft-XL.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/blueprint", + "tags": [ + "technical-drawing", + "grid-lines", + "industrial-design" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Pixel-Art-Ultra-XL.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/pixelart", + "tags": [ + "retro-gaming", + "64-bit", + "sprites" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Graffiti-Wall-Pro-XL.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/graffiti", + "tags": [ + "spray-paint", + "street-art", + "tag-style" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Chalkboard-Menu-XL.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/chalk", + "tags": [ + "dusty-chalk", + "handwritten", + "slate-texture" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Embroidery-Craft-XL.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/embroidery", + "tags": [ + "sewn-texture", + "thread-patterns", + "fabric-art" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Neon-Signs-XL.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/neonsign", + "tags": [ + "glowing-tubes", + "dark-city-streets" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Infographic-Clean-XL.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/info", + "tags": [ + "charts", + "clean-typography", + "icons" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Qwen2.5-VL-7B-Instruct.safetensors", + "category": "Logic", + "url": "https://huggingface.co/Qwen/Qwen2.5-VL", + "tags": [ + "vlm", + "image-understanding", + "prompt-logic" + ], + "type": "sdxl", + "group": "Group L", + "recommended": true + }, + { + "name": "Llama-4-8B-Vision.safetensors", + "category": "Logic", + "url": "https://huggingface.co/meta-llama/Llama-4", + "tags": [ + "reasoning", + "captioning", + "visual-logic" + ], + "type": "sdxl", + "group": "Group L", + "recommended": true + }, + { + "name": "Medical-Anatomy-XL.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/medical", + "tags": [ + "scientific-illustration", + "biological-accuracy" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Jewelry-Detail-XL.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/jewelry", + "tags": [ + "gold-reflections", + "gems", + "high-macro" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Comic-Book-Inker-XL.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/inker", + "tags": [ + "thick-black-lines", + "cross-hatching" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Retro-VCR-XL.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/vcr", + "tags": [ + "glitch-art", + "tracking-lines", + "80s-video" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Miniature-World-XL.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/miniature", + "tags": [ + "tilt-shift", + "toy-figures", + "macro-landscape" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + } + ], + "loras": [ + { + "name": "Boreal_Realism_Flux.safetensors", + "category": "Realism", + "url": "https://huggingface.co/kudzueye/Boreal", + "tags": [ + "hyper-realism", + "candid", + "natural-skin" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Smartphone_Snap_v2.safetensors", + "category": "Realism", + "url": "https://civitai.com/models/652699", + "tags": [ + "iphone-photo", + "grainy", + "vertical", + "casual" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "RealLife_Skin_Fix.safetensors", + "category": "Realism", + "url": "https://civitai.com/models/114321", + "tags": [ + "detailed-pores", + "realistic-lighting" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Mature_Faces_Slider.safetensors", + "category": "Realism", + "url": "https://civitai.com/models/16543", + "tags": [ + "wrinkles", + "aging", + "silver-hair", + "character" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Fashion_Editorial_Flux.safetensors", + "category": "Realism", + "url": "https://huggingface.co/alvdansen/flux-koda", + "tags": [ + "vogue", + "high-fashion", + "studio-lighting" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Cinematic_Shot_XL.safetensors", + "category": "Realism", + "url": "https://civitai.com/models/cinematic", + "tags": [ + "anamorphic", + "color-grade", + "movie-look" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Steve_McCurry_Photography.safetensors", + "category": "Realism", + "url": "https://civitai.com/models/mccurry", + "tags": [ + "nat-geo", + "sharp-eyes", + "travel-photography" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Low-Light_Night_Photography.safetensors", + "category": "Realism", + "url": "https://civitai.com/models/nightphoto", + "tags": [ + "night-vision", + "low-noise", + "street-ambient" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "8k_Ultra_Detail_Flux.safetensors", + "category": "Realism", + "url": "https://huggingface.co/prithivMLmods/Canopus", + "tags": [ + "sharpness", + "intricate", + "masterpiece" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Amateur_Style_Snapshot.safetensors", + "category": "Realism", + "url": "https://civitai.com/models/amateur", + "tags": [ + "shaky-cam", + "low-quality-aesthetic", + "real-photo" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Facial_Expression_Extreme.safetensors", + "category": "Realism", + "url": "https://civitai.com/models/extreme-face", + "tags": [ + "angry", + "happy", + "crying", + "slider" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Skin_Texture_Macro_Flux.safetensors", + "category": "Realism", + "url": "https://civitai.com/models/macro-skin", + "tags": [ + "extreme-close-up", + "pores", + "hair-follicles" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Candid_Street_Photography.safetensors", + "category": "Realism", + "url": "https://civitai.com/models/streetphoto", + "tags": [ + "urban", + "crowd", + "natural-lighting" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Kodak_Portra_400_XL.safetensors", + "category": "Realism", + "url": "https://civitai.com/models/kodak", + "tags": [ + "warm-tones", + "film-grain", + "analog-photography" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Hand_Fixer_v2.safetensors", + "category": "Realism", + "url": "https://civitai.com/models/handfix", + "tags": [ + "perfect-fingers", + "anatomy-correction" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Disney_Pixar_Style_v2.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/459655", + "tags": [ + "disney", + "pixar", + "3d-render", + "cartoon" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Sakimi-Chan_Style_Flux.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/21133", + "tags": [ + "digital-painting", + "vibrant", + "stylized-eyes" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Makoto_Shinkai_Vibe.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/shinkai", + "tags": [ + "vibrant-sky", + "lens-flare", + "ghibli-hybrid" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Arcane_Texture_XL.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/arcane-lora", + "tags": [ + "thick-paint", + "neon-accents", + "arcane-art" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Spider-Verse_VFX.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/spiderverse", + "tags": [ + "chromatic-aberration", + "comic-dots" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Retro_Anime_90s.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/retro90s", + "tags": [ + "vhs-aesthetic", + "scanlines", + "soft-colors" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Claymation_Stop_Motion.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/8876", + "tags": [ + "handmade", + "fingerprints", + "clay-texture" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Papercraft_Origami_XL.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/9987", + "tags": [ + "layered-paper", + "crafting", + "3d-depth" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Comic_Ink_Lineart.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/lineart", + "tags": [ + "manga-lineart", + "clean-inking", + "b&w" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Ghibli_Background_Artist.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/ghibliback", + "tags": [ + "scenic", + "nature", + "watercolor-anime" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Neon_Cyberpunk_Fashion.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/neoncloth", + "tags": [ + "glowing-fabric", + "techwear", + "futuristic" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Elf_Ears_Fantasy.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/elfears", + "tags": [ + "pointed-ears", + "lotr-style", + "fantasy" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Super_Hero_Suit_Texture.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/supersuit", + "tags": [ + "spandex", + "mask", + "metallic-armor" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Cybernetic_Implants.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/cybertech", + "tags": [ + "augmented-parts", + "wires", + "metal-skin" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Expressions_Anime_XL.safetensors", + "category": "Anime", + "url": "https://civitai.com/models/animeface", + "tags": [ + "exaggerated", + "comedy", + "expressive" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Oil_Impasto_Texture.safetensors", + "category": "Painting", + "url": "https://civitai.com/models/3030", + "tags": [ + "thick-paint", + "palette-knife", + "heavy-texture" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Watercolor_Wash_Pro.safetensors", + "category": "Painting", + "url": "https://civitai.com/models/4040", + "tags": [ + "soft-edges", + "paper-texture", + "artistic-wash" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Charcoal_Sketch_Flux.safetensors", + "category": "Painting", + "url": "https://huggingface.co/b_lora/charcoal", + "tags": [ + "dark-lines", + "smudged", + "hand-drawn" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Ukiyo-e_Traditional.safetensors", + "category": "Painting", + "url": "https://civitai.com/models/5050", + "tags": [ + "japanese-woodblock", + "flat-colors" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Art_Nouveau_Mucha.safetensors", + "category": "Painting", + "url": "https://civitai.com/models/6060", + "tags": [ + "ornate", + "flowing-hair", + "organic-patterns" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Synthwave_Grid_Vibe.safetensors", + "category": "Painting", + "url": "https://civitai.com/models/synthglow", + "tags": [ + "80s-retro", + "pink-grid", + "laser-beams" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Gothic_Noir_XL.safetensors", + "category": "Painting", + "url": "https://civitai.com/models/noir", + "tags": [ + "high-contrast", + "dark-mystery", + "moody" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Minimalist_Vector_Art.safetensors", + "category": "Painting", + "url": "https://huggingface.co/renderartist/vector", + "tags": [ + "clean-lines", + "flat-design", + "corporate" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Steampunk_Gears_Brass.safetensors", + "category": "Painting", + "url": "https://civitai.com/models/9090", + "tags": [ + "victorian-tech", + "steam", + "pipes" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Chalkboard_Art_Texture.safetensors", + "category": "Painting", + "url": "https://civitai.com/models/3236", + "tags": [ + "handwritten", + "dusty", + "white-chalk" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Embroidery_Sewn_Art.safetensors", + "category": "Painting", + "url": "https://civitai.com/models/4347", + "tags": [ + "thread-work", + "stitch-patterns", + "fabric" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Graffiti_Drip_Style.safetensors", + "category": "Painting", + "url": "https://civitai.com/models/2125", + "tags": [ + "urban-tags", + "paint-drips", + "street-art" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Inkpunk_Splash_Art.safetensors", + "category": "Painting", + "url": "https://civitai.com/models/2020", + "tags": [ + "sketchy", + "industrial", + "ink-wash" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Pastel_Dream_Vibe.safetensors", + "category": "Painting", + "url": "https://civitai.com/models/pastel", + "tags": [ + "soft-colors", + "airy", + "ethereal" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Pop_Art_Lichtenstein.safetensors", + "category": "Painting", + "url": "https://civitai.com/models/popart", + "tags": [ + "halftone-dots", + "primary-colors", + "bold-inking" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Modern_Interior_Design.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/1111", + "tags": [ + "minimalist", + "glass", + "luxury", + "staging" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Abandoned_Overgrown_Flux.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/abandoned", + "tags": [ + "ruins", + "nature-reclaimed", + "decay" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Cyber_City_Neon_Grid.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/2222", + "tags": [ + "skyscrapers", + "neon-signs", + "futuristic" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Cozy_Cottage_Garden.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/3333", + "tags": [ + "rural", + "warm-lighting", + "wood-textures" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Brutalist_Concrete_Flux.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/4444", + "tags": [ + "stark-shadows", + "geometric", + "grey" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Gothic_Stone_Cathedral.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/5555", + "tags": [ + "religious-art", + "intricate-carving" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Treehouse_Natural_Build.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/6666", + "tags": [ + "hanging-structures", + "forest", + "wood" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Martian_Base_Modular.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/7777", + "tags": [ + "scifi-domes", + "red-dust", + "futuristic" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Underwater_Biosphere.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/8888", + "tags": [ + "glass-tunnels", + "coral", + "bioluminescence" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Tiny_House_Organization.safetensors", + "category": "Architecture", + "url": "https://civitai.com/models/9999", + "tags": [ + "compact", + "smart-design", + "modern" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Kitchen_Luxury_Flux.safetensors", + "category": "Environment", + "url": "https://civitai.com/models/4345", + "tags": [ + "marble-counters", + "high-end-appliances" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Gamer_Room_RGB.safetensors", + "category": "Environment", + "url": "https://civitai.com/models/5456", + "tags": [ + "pc-setup", + "neon-strips", + "peripherals" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Old_Library_Mahogany.safetensors", + "category": "Environment", + "url": "https://civitai.com/models/6567", + "tags": [ + "leather-books", + "dust-motes", + "wood-panels" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Zen_Garden_Tranquil.safetensors", + "category": "Environment", + "url": "https://civitai.com/models/7678", + "tags": [ + "sand-patterns", + "bonsai", + "koi-pond" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Industrial_Loft_Flux.safetensors", + "category": "Environment", + "url": "https://civitai.com/models/8789", + "tags": [ + "exposed-brick", + "high-ceilings", + "metal" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Arctic_Ice_Tundra.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/1020", + "tags": [ + "snow-glare", + "blue-hour", + "cold" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Tropical_Island_Beach.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/2030", + "tags": [ + "palm-trees", + "clear-water", + "sunny" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Grand_Canyon_Utah_XL.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/3040", + "tags": [ + "red-rock", + "desert", + "high-dynamic-range" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Misty_Forest_Flux_Atmo.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/4050", + "tags": [ + "fog", + "pine-trees", + "mysterious" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Volcano_Eruption_Fire.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/5060", + "tags": [ + "lava", + "ash-cloud", + "cinematic-destruction" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Lavender_Field_Horizon.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/6070", + "tags": [ + "purple", + "sunset-scenery", + "flora" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Space_Nebula_Starfield.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/7080", + "tags": [ + "deep-space", + "galaxies", + "vibrant-colors" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Swiss_Alps_Summit.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/8090", + "tags": [ + "mountain-peak", + "village", + "sharp-focus" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Autumn_Fall_Colors.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/9001", + "tags": [ + "orange-leaves", + "crisp-air", + "seasonal" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Biolume_Crystal_Cave.safetensors", + "category": "Landscape", + "url": "https://civitai.com/models/1002", + "tags": [ + "glow", + "fantasy-nature", + "subterranean" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Detailifier_Enhance_XL.safetensors", + "category": "Utility", + "url": "https://civitai.com/models/126343", + "tags": [ + "sharpen", + "micro-detail", + "8k-upscale" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Lighting_Master_Pro.safetensors", + "category": "Utility", + "url": "https://civitai.com/models/2002", + "tags": [ + "rim-lighting", + "moody-shadows" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "No-Background_Sticker.safetensors", + "category": "Utility", + "url": "https://civitai.com/models/3003", + "tags": [ + "flat-background", + "cutout", + "white" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Text_Spelling_Fixer.safetensors", + "category": "Utility", + "url": "https://civitai.com/models/4004", + "tags": [ + "coherent-text", + "correct-letters" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Color_Pop_Vibrant.safetensors", + "category": "Utility", + "url": "https://civitai.com/models/1011", + "tags": [ + "saturation", + "vivid", + "contrast" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Tactical_Armor_XL.safetensors", + "category": "Clothing", + "url": "https://civitai.com/models/2022", + "tags": [ + "military-gear", + "straps", + "metal-texture" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Victorian_Lace_Dress.safetensors", + "category": "Clothing", + "url": "https://civitai.com/models/3033", + "tags": [ + "historical-fabric", + "intricate-detail" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Silk_Satin_Flow_Flux.safetensors", + "category": "Clothing", + "url": "https://civitai.com/models/2233", + "tags": [ + "reflective-fabric", + "soft-draping" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Diamond_Gold_Macro.safetensors", + "category": "Jewelry", + "url": "https://civitai.com/models/5055", + "tags": [ + "reflective", + "gems", + "high-detail" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Magic_Spell_Particles.safetensors", + "category": "VFX", + "url": "https://civitai.com/models/magic", + "tags": [ + "glow", + "sparks", + "floating-runes" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Glitch_Digital_Artifacts.safetensors", + "category": "VFX", + "url": "https://civitai.com/models/glitch", + "tags": [ + "data-corruption", + "neon-smear" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Smoke_Fog_Volumetric.safetensors", + "category": "VFX", + "url": "https://civitai.com/models/smokefx", + "tags": [ + "swirling", + "density", + "realistic-mist" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Tarot_Card_Frame.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/5457", + "tags": [ + "mystical", + "border", + "card-art" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Blueprint_Technical.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/6568", + "tags": [ + "grid-lines", + "blue", + "drafting" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Stained_Glass_Effect.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/8790", + "tags": [ + "translucent", + "backlit", + "colorful" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Origami_Paper_Fold.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/9891", + "tags": [ + "geometric", + "sharp-creases" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Tattoo_Traditional_Flash.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/1014", + "tags": [ + "ink", + "white-background", + "classic" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Neon_Sign_Glow.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/5458", + "tags": [ + "night", + "glass-tubes", + "ambient-light" + ], + "type": "sdxl", + "group": "Group A", + "recommended": true + }, + { + "name": "Miniature_Tilt_Shift.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/tiltshift", + "tags": [ + "diorama", + "blurred-top-bottom" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Hologram_Projection.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/holo", + "tags": [ + "transparent", + "scan-lines", + "blue-glow" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Infographic_Icon_Set.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/icons", + "tags": [ + "flat", + "minimalist", + "vector-symbols" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Food_Plating_Gourmet.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/plating", + "tags": [ + "chef-style", + "fine-dining", + "sauce-drip" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Sci-Fi_HUD_Overlay.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/hud", + "tags": [ + "user-interface", + "tech-graphics" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Post-Apocalypse_Dust.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/dust", + "tags": [ + "hazy", + "gritty", + "sun-beams" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + }, + { + "name": "Bioluminescent_Creatures.safetensors", + "category": "Niche", + "url": "https://civitai.com/models/biolumecreature", + "tags": [ + "avatar-style", + "glowing-skin" + ], + "type": "sdxl", + "group": "Group C", + "recommended": true + } + ], + "controlnets": [ + { + "name": "control_v11p_sd15_canny.safetensors", + "category": "Edges", + "url": "https://huggingface.co/StableDiffusionVN/Prunedcontrolnet", + "tags": [ + "canny", + "edge", + "lineart" + ], + "type": "sd15", + "group": "ControlNet SD15", + "recommended": true, + "strength": 0.8 + }, + { + "name": "control_v11p_sd15_openpose.safetensors", + "category": "Pose", + "url": "https://huggingface.co/StableDiffusionVN/Prunedcontrolnet", + "tags": [ + "pose", + "openpose", + "skeleton" + ], + "type": "sd15", + "group": "ControlNet SD15", + "recommended": true, + "strength": 0.8 + } + ] +} diff --git a/nodes_ollama_router-new.py b/nodes_ollama_router-new.py new file mode 100644 index 0000000..0c85ffa --- /dev/null +++ b/nodes_ollama_router-new.py @@ -0,0 +1,779 @@ + +import base64 +import io +import json +import os +import re +import time +import urllib.request +import urllib.error + +import numpy as np +from PIL import Image + +import folder_paths +import comfy.sd +import comfy.utils + + +_DEFAULT_REGISTRY = { + "version": 1, + "checkpoints": [], + "loras": [], +} + + +def _read_registry(registry_path: str) -> dict: + path = registry_path + if not os.path.isabs(path): + path = os.path.join(os.path.dirname(__file__), path) + if not os.path.exists(path): + return _DEFAULT_REGISTRY + try: + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + if isinstance(data, dict): + return data + except Exception: + pass + return _DEFAULT_REGISTRY + + +def _compact_registry(registry: dict, max_items: int = 50) -> dict: + checkpoints = registry.get("checkpoints", [])[:max_items] + loras = registry.get("loras", [])[:max_items] + + def _strip(items): + compact = [] + for item in items: + compact.append({ + "name": item.get("name", ""), + "type": item.get("type", ""), + "tags": item.get("tags", []), + "recommended": bool(item.get("recommended", False)), + }) + return compact + + return {"checkpoints": _strip(checkpoints), "loras": _strip(loras)} + + +def _score_tags(prompt: str, tags: list[str]) -> int: + score = 0 + for tag in tags: + if tag and tag in prompt: + score += 3 + return score + + +def _heuristic_plan(prompt: str, registry: dict) -> dict: + prompt_l = prompt.lower() + checkpoints = registry.get("checkpoints", []) + loras = registry.get("loras", []) + + best_ckpt = None + best_score = -1 + for ckpt in checkpoints: + tags = [t.lower() for t in ckpt.get("tags", [])] + score = _score_tags(prompt_l, tags) + if ckpt.get("recommended"): + score += 1 + if score > best_score: + best_score = score + best_ckpt = ckpt + + if best_ckpt is None and checkpoints: + best_ckpt = checkpoints[0] + + selected_loras = [] + selected_strengths = [] + for lora in loras: + tags = [t.lower() for t in lora.get("tags", [])] + if _score_tags(prompt_l, tags) > 0: + selected_loras.append(lora.get("name", "")) + selected_strengths.append(float(lora.get("strength", 0.7))) + + model_type = (best_ckpt or {}).get("type", "sdxl") + return { + "model_type": model_type or "sdxl", + "checkpoint": (best_ckpt or {}).get("name", ""), + "loras": selected_loras, + "lora_strengths": selected_strengths, + "positive_prompt": prompt.strip(), + "negative_prompt": "", + "steps": 25, + "cfg": 6.5, + "width": 1024 if model_type == "sdxl" else 768, + "height": 1024 if model_type == "sdxl" else 768, + "seed": int(time.time()) % 2_000_000_000, + } + + +def _ollama_chat(model: str, system_prompt: str, user_prompt: str, host: str = "localhost", port: int = 11434, timeout: int = 40) -> str: + url = f"http://{host}:{port}/api/chat" + payload = { + "model": model, + "messages": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt}, + ], + "format": "json", + "stream": False, + "options": { + "temperature": 0.2, + }, + } + data = json.dumps(payload).encode("utf-8") + req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"}) + with urllib.request.urlopen(req, timeout=timeout) as resp: + raw = resp.read().decode("utf-8", errors="ignore") + try: + parsed = json.loads(raw) + if isinstance(parsed, dict) and "message" in parsed and isinstance(parsed["message"], dict): + return parsed["message"].get("content", "") + except Exception: + pass + return raw + + +def _encode_image_base64(image) -> str: + # image is a ComfyUI IMAGE tensor (batch, height, width, channels) in 0..1 + if image is None: + return "" + try: + img = image + if hasattr(img, "cpu"): + img = img.cpu().numpy() + if isinstance(img, np.ndarray) and img.ndim == 4: + img = img[0] + if isinstance(img, np.ndarray): + img = np.clip(img * 255.0, 0, 255).astype(np.uint8) + pil = Image.fromarray(img) + buf = io.BytesIO() + pil.save(buf, format="PNG") + return base64.b64encode(buf.getvalue()).decode("ascii") + except Exception: + return "" + return "" + + +def _ollama_chat_with_images(model: str, system_prompt: str, user_prompt: str, images: list[str], host: str = "localhost", port: int = 11434, timeout: int = 60) -> str: + url = f"http://{host}:{port}/api/chat" + payload = { + "model": model, + "messages": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt, "images": images}, + ], + "format": "json", + "stream": False, + "options": { + "temperature": 0.2, + }, + } + data = json.dumps(payload).encode("utf-8") + req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"}) + with urllib.request.urlopen(req, timeout=timeout) as resp: + raw = resp.read().decode("utf-8", errors="ignore") + try: + parsed = json.loads(raw) + if isinstance(parsed, dict) and "message" in parsed and isinstance(parsed["message"], dict): + return parsed["message"].get("content", "") + except Exception: + pass + return raw + + +def _parse_plan(raw: str) -> dict | None: + try: + data = json.loads(raw) + if isinstance(data, dict): + return data + except Exception: + pass + match = re.search(r"\{.*\}", raw, flags=re.S) + if match: + try: + return json.loads(match.group(0)) + except Exception: + return None + return None + + +class OllamaPromptPlanner: + """ + Use local Ollama to pick checkpoint + LoRAs + params based on the prompt and registry. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "prompt": ("STRING", {"multiline": True}), + "ollama_model": ("STRING", {"default": "qwen2.5:7b"}), + "registry_path": ("STRING", {"default": "model_registry.json"}), + "task_hint": (["auto", "text2img", "img2img", "inpaint", "sdxl", "sd15", "flux"],), + "user_negative": ("STRING", {"multiline": True, "default": ""}), + "ollama_host": ("STRING", {"default": "localhost"}), + "ollama_port": ("INT", {"default": 11434, "min": 1, "max": 65535}), + } + } + + RETURN_TYPES = ( + "STRING", + "STRING", + "STRING", + "STRING", + "STRING", + "STRING", + "INT", + "FLOAT", + "INT", + "INT", + "INT", + "STRING", + "STRING", + "STRING", + ) + RETURN_NAMES = ( + "checkpoint", + "vae_name", + "clip_name", + "loras", + "lora_strengths", + "model_type", + "steps", + "cfg", + "width", + "height", + "seed", + "positive_prompt", + "negative_prompt", + "plan_json", + ) + FUNCTION = "plan" + CATEGORY = "Ollama/Planner" + + def plan(self, prompt, ollama_model, registry_path, task_hint, user_negative, ollama_host="localhost", ollama_port=11434): + registry = _read_registry(registry_path) + compact = _compact_registry(registry) + system_prompt = ( + "You are a routing planner for image generation. " + "Return ONLY valid JSON with keys: " + "model_type (sdxl|sd15|flux), checkpoint, vae_name (optional), clip_name (optional), " + "loras (array of names), lora_strengths (array of floats), positive_prompt, negative_prompt, " + "steps (int), cfg (float), width (int), height (int), seed (int). " + "Use checkpoint, vae, clip and lora names exactly from the registry. " + "If unsure, pick a recommended checkpoint." + ) + user_payload = { + "task_hint": task_hint, + "prompt": prompt, + "user_negative": user_negative, + "registry": compact, + } + raw = "" + try: + raw = _ollama_chat(ollama_model, system_prompt, json.dumps(user_payload, ensure_ascii=True), host=ollama_host, port=ollama_port) + plan = _parse_plan(raw) + except urllib.error.URLError: + plan = None + except Exception: + plan = None + + if plan is None: + plan = _heuristic_plan(prompt, registry) + + negative = plan.get("negative_prompt", "") or "" + if user_negative.strip(): + if negative.strip(): + negative = f"{negative}, {user_negative.strip()}" + else: + negative = user_negative.strip() + + loras = plan.get("loras", []) + if isinstance(loras, str): + loras = [l.strip() for l in loras.split(",") if l.strip()] + strengths = plan.get("lora_strengths", []) + if isinstance(strengths, str): + strengths = [s.strip() for s in strengths.split(",") if s.strip()] + + lora_str = ",".join([l for l in loras if l]) + strength_str = ",".join([str(s) for s in strengths if str(s).strip()]) + + return ( + str(plan.get("checkpoint", "")), + str(plan.get("vae_name", "")), + str(plan.get("clip_name", "")), + lora_str, + strength_str, + str(plan.get("model_type", "sdxl")), + int(plan.get("steps", 25)), + float(plan.get("cfg", 6.5)), + int(plan.get("width", 1024)), + int(plan.get("height", 1024)), + int(plan.get("seed", int(time.time()) % 2_000_000_000)), + str(plan.get("positive_prompt", prompt)), + negative, + json.dumps(plan, ensure_ascii=True), + ) + + +class DynamicCheckpointLoader: + """ + Universal Loader: + - Loads Checkpoint (Model/Clip/VAE) + - Supports separate Diffusion/CLIP/VAE loading if overrides provided. + - Implements Fallback logic: If 'checkpoint' is missing, finds the best alternative from registry not just by name but by Group/Category. + - Prompts for download if missing. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "checkpoint": ("STRING", {"default": ""}), + "model_type": (["auto", "sdxl", "sd15", "flux", "svd", "hunyuan", "wan"],), + "registry_path": ("STRING", {"default": "model_registry.json"}), + "custom_path": ("STRING", {"default": ""}), + }, + "optional": { + "vae_override": ("STRING", {"default": ""}), + "clip_override": ("STRING", {"default": ""}), + } + } + + RETURN_TYPES = ("MODEL", "CLIP", "VAE") + FUNCTION = "load" + CATEGORY = "Ollama/Loader" + + def load(self, checkpoint, model_type, registry_path, custom_path="", vae_override="", clip_override=""): + registry = _read_registry(registry_path) + + # 1. Identify target metadata from registry + target_info = None + registry_idx = -1 + for i, ckpt in enumerate(registry.get("checkpoints", [])): + if ckpt.get("name") == checkpoint: + target_info = ckpt + registry_idx = i + break + + resolved_type = model_type + if target_info and model_type == "auto": + resolved_type = target_info.get("type", "sdxl") + + # 2. Resolve Checkpoint Path (or Fallback) + ckpt_path = self._find_path("checkpoints", checkpoint, custom_path) + + if not ckpt_path: + # Checkpoint Missing: logical fallback + print(f"\\n[OllamaRouter] !!! MISSING MODEL: {checkpoint} !!!") + if target_info: + print(f"[OllamaRouter] DOWNLOAD URL: {target_info.get('url', 'N/A')}") + print(f"[OllamaRouter] GROUP: {target_info.get('group', 'N/A')}") + + # Find Fallback + fallback_ckpt = self._find_fallback(registry, target_info, resolved_type, custom_path) + if fallback_ckpt: + print(f"[OllamaRouter] -> Falling back to available model: {fallback_ckpt}") + ckpt_path = self._find_path("checkpoints", fallback_ckpt, custom_path) + else: + raise FileNotFoundError(f"Model '{checkpoint}' not found and no suitable fallback in group/type '{resolved_type}' discovered.") + + # 3. Load Main Components + # If it's a standard checkpoint (safetensors with bundled model/clip/vae) + # Note: folder_paths.get_full_path_or_raise would have raised if we used it directly, but we used _find_path + + out_model, out_clip, out_vae = comfy.sd.load_checkpoint_guess_config( + ckpt_path, + output_vae=True, + output_clip=True, + embedding_directory=folder_paths.get_folder_paths("embeddings"), + )[:3] + + # 4. Handle Overrides (Universal Loading) + if vae_override: + print(f"[OllamaRouter] Attempting VAE Override: {vae_override}") + v_path = self._find_path("vae", vae_override, custom_path) + if v_path: + try: + out_vae = comfy.sd.VAE(sd=comfy.utils.load_torch_file(v_path)) + print(f"[OllamaRouter] VAE loaded: {v_path}") + except Exception as e: + print(f"[OllamaRouter] Failed to load VAE: {e}") + else: + print(f"[OllamaRouter] Warning: VAE Override '{vae_override}' not found.") + + if clip_override: + print(f"[OllamaRouter] Attempting CLIP Override: {clip_override}") + c_path = self._find_path("checkpoints", clip_override, custom_path) + if c_path: + try: + _, out_clip, _ = comfy.sd.load_checkpoint_guess_config( + c_path, + output_vae=False, + output_clip=True, + embedding_directory=folder_paths.get_folder_paths("embeddings") + )[:3] + print(f"[OllamaRouter] CLIP loaded: {c_path}") + except Exception as e: + print(f"[OllamaRouter] Failed to load CLIP: {e}") + else: + print(f"[OllamaRouter] Warning: CLIP Override '{clip_override}' not found.") + + return (out_model, out_clip, out_vae) + + def _find_path(self, folder_type, filename, custom_path): + try: + path = folder_paths.get_full_path(folder_type, filename) + if path: return path + except: pass + + if folder_type != "checkpoints": + try: + path = folder_paths.get_full_path("checkpoints", filename) + if path: return path + except: pass + + if custom_path and os.path.exists(custom_path): + p = os.path.join(custom_path, filename) + if os.path.exists(p): return p + for root, dirs, files in os.walk(custom_path): + if filename in files: + return os.path.join(root, filename) + return None + + def _find_fallback(self, registry, target_info, resolved_type, custom_path): + candidates = registry.get("checkpoints", []) + if not target_info: + for c in candidates: + if c.get("type") == resolved_type: + if self._find_path("checkpoints", c["name"], custom_path): return c["name"] + return None + + grp = target_info.get("group") + cat = target_info.get("category") + + # Priority 1: Same Group & Category + for c in candidates: + if c["name"] == target_info["name"]: continue + if c.get("group") == grp and c.get("category") == cat and c.get("type") == resolved_type: + if self._find_path("checkpoints", c["name"], custom_path): return c["name"] + + # Priority 2: Same Group & Type + for c in candidates: + if c["name"] == target_info["name"]: continue + if c.get("group") == grp and c.get("type") == resolved_type: + if self._find_path("checkpoints", c["name"], custom_path): return c["name"] + + # Priority 3: Same Type + for c in candidates: + if c["name"] == target_info["name"]: continue + if c.get("type") == resolved_type: + if self._find_path("checkpoints", c["name"], custom_path): return c["name"] + + return None + + +class DynamicLoraStack: + """ + Apply a comma-separated list of LoRAs to a model/clip pair. + Includes Smart Fallback lookup if specific LoRA name is missing. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "model": ("MODEL",), + "clip": ("CLIP",), + "loras": ("STRING", {"default": ""}), + "lora_strengths": ("STRING", {"default": ""}), + "custom_path": ("STRING", {"default": ""}), + } + } + + RETURN_TYPES = ("MODEL", "CLIP") + FUNCTION = "apply" + CATEGORY = "Ollama/Loader" + + def apply(self, model, clip, loras, lora_strengths, custom_path=""): + lora_list = [l.strip() for l in loras.split(",") if l.strip()] + strength_list = [s.strip() for s in lora_strengths.split(",") if s.strip()] + strengths = [] + for s in strength_list: + try: + strengths.append(float(s)) + except Exception: + strengths.append(0.7) + + if not lora_list: + return (model, clip) + + registry = {} + try: + registry = _read_registry("model_registry.json") + except: + pass + + model_out, clip_out = model, clip + for idx, lora_name in enumerate(lora_list): + strength = strengths[idx] if idx < len(strengths) else 0.7 + + lora_path = self._find_path(lora_name, custom_path) + + if not lora_path: + target_info = None + for l in registry.get("loras", []): + if l.get("name") == lora_name: + target_info = l + break + + print(f"\\n[OllamaRouter] !!! MISSING LORA: {lora_name} !!!") + if target_info: + print(f"[OllamaRouter] DOWNLOAD URL: {target_info.get('url', 'N/A')}") + + # Try Fallback + fallback_name = None + if target_info: + fallback_name = self._find_fallback(registry, target_info, custom_path) + + if fallback_name: + print(f"[OllamaRouter] -> Falling back to LoRA: {fallback_name}") + lora_path = self._find_path(fallback_name, custom_path) + else: + print(f"[OllamaRouter] -> Skipping missing LoRA.") + continue + + # Load + try: + lora = comfy.utils.load_torch_file(lora_path, safe_load=True) + model_out, clip_out = comfy.sd.load_lora_for_models(model_out, clip_out, lora, strength, strength) + print(f"[OllamaRouter] LoRA Loaded: {os.path.basename(lora_path)}") + except Exception as e: + print(f"[OllamaRouter] Error loading LoRA {lora_path}: {e}") + + return (model_out, clip_out) + + def _find_path(self, filename, custom_path): + try: + path = folder_paths.get_full_path("loras", filename) + if path: return path + except: pass + if custom_path and os.path.exists(custom_path): + p = os.path.join(custom_path, filename) + if os.path.exists(p): return p + return None + + def _find_fallback(self, registry, target_info, custom_path): + target_grp = target_info.get("group", "") + target_cat = target_info.get("category", "") + + # Priority 1: Same Group & Category + for l in registry.get("loras", []): + if l["name"] == target_info["name"]: continue + if l.get("group") == target_grp and l.get("category") == target_cat: + if self._find_path(l["name"], custom_path): return l["name"] + + # Priority 2: Same Category only + for l in registry.get("loras", []): + if l["name"] == target_info["name"]: continue + if l.get("category") == target_cat: + if self._find_path(l["name"], custom_path): return l["name"] + return None + + +class RouteConditioningByType: + """ + Route conditioning between SDXL and SD1.5 branches based on model_type. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "model_type": (["sdxl", "sd15", "flux"],), + "sdxl": ("CONDITIONING",), + "sd15": ("CONDITIONING",), + } + } + + RETURN_TYPES = ("CONDITIONING",) + FUNCTION = "route" + CATEGORY = "Ollama/Route" + + def route(self, model_type, sdxl, sd15): + return (sdxl if model_type == "sdxl" else sd15,) + + +class OllamaVisionStylePlanner: + """ + Use local Ollama vision model to analyze an image and pick checkpoint + LoRAs + params. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + "prompt": ("STRING", {"multiline": True, "default": ""}), + "ollama_model": ("STRING", {"default": "llava:7b"}), + "registry_path": ("STRING", {"default": "model_registry.json"}), + "task_hint": (["auto", "img2img", "sdxl", "sd15", "flux"],), + "user_negative": ("STRING", {"multiline": True, "default": ""}), + "ollama_host": ("STRING", {"default": "localhost"}), + "ollama_port": ("INT", {"default": 11434, "min": 1, "max": 65535}), + } + } + + RETURN_TYPES = ( + "STRING", + "STRING", + "STRING", + "STRING", + "STRING", + "STRING", + "INT", + "FLOAT", + "INT", + "INT", + "INT", + "STRING", + "STRING", + "STRING", + ) + RETURN_NAMES = ( + "checkpoint", + "vae_name", + "clip_name", + "loras", + "lora_strengths", + "model_type", + "steps", + "cfg", + "width", + "height", + "seed", + "positive_prompt", + "negative_prompt", + "plan_json", + ) + FUNCTION = "plan" + CATEGORY = "Ollama/Planner" + + def plan(self, image, prompt, ollama_model, registry_path, task_hint, user_negative, ollama_host="localhost", ollama_port=11434): + registry = _read_registry(registry_path) + compact = _compact_registry(registry) + system_prompt = ( + "You analyze an image style and select the best image generation setup. " + "Return ONLY valid JSON with keys: " + "model_type (sdxl|sd15|flux), checkpoint, vae_name (optional), clip_name (optional), " + "loras (array of names), lora_strengths (array of floats), positive_prompt, negative_prompt, " + "steps (int), cfg (float), width (int), height (int), seed (int). " + "Use checkpoint, vae, clip and lora names exactly from the registry. " + "If unsure, pick a recommended checkpoint." + ) + user_payload = { + "task_hint": task_hint, + "prompt": prompt, + "user_negative": user_negative, + "registry": compact, + } + raw = "" + try: + img_b64 = _encode_image_base64(image) + raw = _ollama_chat_with_images( + ollama_model, + system_prompt, + json.dumps(user_payload, ensure_ascii=True), + [img_b64] if img_b64 else [], + host=ollama_host, + port=ollama_port + ) + plan = _parse_plan(raw) + except urllib.error.URLError: + plan = None + except Exception: + plan = None + + if plan is None: + plan = _heuristic_plan(prompt, registry) + + negative = plan.get("negative_prompt", "") or "" + if user_negative.strip(): + if negative.strip(): + negative = f"{negative}, {user_negative.strip()}" + else: + negative = user_negative.strip() + + loras = plan.get("loras", []) + if isinstance(loras, str): + loras = [l.strip() for l in loras.split(",") if l.strip()] + strengths = plan.get("lora_strengths", []) + if isinstance(strengths, str): + strengths = [s.strip() for s in strengths.split(",") if s.strip()] + + lora_str = ",".join([l for l in loras if l]) + strength_str = ",".join([str(s) for s in strengths if str(s).strip()]) + + return ( + str(plan.get("checkpoint", "")), + str(plan.get("vae_name", "")), + str(plan.get("clip_name", "")), + lora_str, + strength_str, + str(plan.get("model_type", "sdxl")), + int(plan.get("steps", 25)), + float(plan.get("cfg", 6.5)), + int(plan.get("width", 1024)), + int(plan.get("height", 1024)), + int(plan.get("seed", int(time.time()) % 2_000_000_000)), + str(plan.get("positive_prompt", prompt)), + negative, + json.dumps(plan, ensure_ascii=True), + ) + + +class OllamaDebugInfo: + """ + Simple debug node to visualize the plan JSON as text. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text": ("STRING", {"forceInput": True}), + } + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "notify" + CATEGORY = "Ollama/Debug" + OUTPUT_NODE = True + + def notify(self, text): + print(f"\n[Ollama Plan Debug]:\n{text}\n") + return {"ui": {"text": [text]}, "result": (text,)} + + +class OllamaRegistryInfo: + """ + Reads model_registry.json and outputs as string for debugging. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "registry_path": ("STRING", {"default": "model_registry.json"}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("json_content",) + FUNCTION = "read_registry" + CATEGORY = "Ollama/Debug" + + def read_registry(self, registry_path): + try: + registry = _read_registry(registry_path) + import json + return (json.dumps(registry, indent=2),) + except Exception as e: + return (f"Error reading registry: {e}",) diff --git a/nodes_ollama_router.py b/nodes_ollama_router.py new file mode 100644 index 0000000..4fe4253 --- /dev/null +++ b/nodes_ollama_router.py @@ -0,0 +1,950 @@ + +import base64 +import io +import json +import os +import re +import time +import urllib.request +import urllib.error + +import numpy as np +from PIL import Image + +import folder_paths +import comfy.sd +import comfy.utils +import comfy.controlnet + + +_DEFAULT_REGISTRY = { + "version": 1, + "checkpoints": [], + "loras": [], + "controlnets": [], +} + + +def _read_registry(registry_path: str) -> dict: + path = registry_path + if not os.path.isabs(path): + path = os.path.join(os.path.dirname(__file__), path) + if not os.path.exists(path): + return _DEFAULT_REGISTRY + try: + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + if isinstance(data, dict): + return data + except Exception: + pass + return _DEFAULT_REGISTRY + + +def _compact_registry(registry: dict, max_items: int = 50) -> dict: + checkpoints = registry.get("checkpoints", [])[:max_items] + loras = registry.get("loras", [])[:max_items] + controlnets = registry.get("controlnets", [])[:max_items] + + def _strip(items): + compact = [] + for item in items: + compact.append({ + "name": item.get("name", ""), + "type": item.get("type", ""), + "tags": item.get("tags", []), + "recommended": bool(item.get("recommended", False)), + }) + return compact + + return { + "checkpoints": _strip(checkpoints), + "loras": _strip(loras), + "controlnets": _strip(controlnets), + } + + +def _score_tags(prompt: str, tags: list[str]) -> int: + score = 0 + for tag in tags: + if tag and tag in prompt: + score += 3 + return score + + +def _heuristic_plan(prompt: str, registry: dict) -> dict: + prompt_l = prompt.lower() + checkpoints = registry.get("checkpoints", []) + loras = registry.get("loras", []) + controlnets = registry.get("controlnets", []) + + best_ckpt = None + best_score = -1 + for ckpt in checkpoints: + tags = [t.lower() for t in ckpt.get("tags", [])] + score = _score_tags(prompt_l, tags) + if ckpt.get("recommended"): + score += 1 + if score > best_score: + best_score = score + best_ckpt = ckpt + + if best_ckpt is None and checkpoints: + best_ckpt = checkpoints[0] + + selected_loras = [] + selected_strengths = [] + for lora in loras: + tags = [t.lower() for t in lora.get("tags", [])] + if _score_tags(prompt_l, tags) > 0: + selected_loras.append(lora.get("name", "")) + selected_strengths.append(float(lora.get("strength", 0.7))) + + model_type = (best_ckpt or {}).get("type", "sdxl") + controlnet_name = "" + controlnet_strength = 0.0 + for cn in controlnets: + tags = [t.lower() for t in cn.get("tags", [])] + if "pose" in prompt_l and ("pose" in tags or "openpose" in tags): + controlnet_name = cn.get("name", "") + controlnet_strength = float(cn.get("strength", 0.8)) + break + if ("edge" in prompt_l or "canny" in prompt_l or "lineart" in prompt_l) and ( + "canny" in tags or "edge" in tags or "lineart" in tags + ): + controlnet_name = cn.get("name", "") + controlnet_strength = float(cn.get("strength", 0.8)) + break + return { + "task": "text2img", + "model_type": model_type or "sdxl", + "checkpoint": (best_ckpt or {}).get("name", ""), + "vae_name": "", + "clip_name": "", + "use_refiner": False, + "refiner_checkpoint": "", + "loras": selected_loras, + "lora_strengths": selected_strengths, + "positive_prompt": prompt.strip(), + "negative_prompt": "", + "steps": 25, + "cfg": 6.5, + "width": 1024 if model_type == "sdxl" else 768, + "height": 1024 if model_type == "sdxl" else 768, + "denoise": 1.0, + "seed": int(time.time()) % 2_000_000_000, + "controlnet_name": controlnet_name, + "controlnet_strength": controlnet_strength, + "controlnet_start": 0.0, + "controlnet_end": 1.0, + } + + +def _ollama_chat(model: str, system_prompt: str, user_prompt: str, host: str = "localhost", port: int = 11434, timeout: int = 40) -> str: + url = f"http://{host}:{port}/api/chat" + payload = { + "model": model, + "messages": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt}, + ], + "format": "json", + "stream": False, + "options": { + "temperature": 0.2, + }, + } + data = json.dumps(payload).encode("utf-8") + req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"}) + with urllib.request.urlopen(req, timeout=timeout) as resp: + raw = resp.read().decode("utf-8", errors="ignore") + try: + parsed = json.loads(raw) + if isinstance(parsed, dict) and "message" in parsed and isinstance(parsed["message"], dict): + return parsed["message"].get("content", "") + except Exception: + pass + return raw + + +def _encode_image_base64(image) -> str: + # image is a ComfyUI IMAGE tensor (batch, height, width, channels) in 0..1 + if image is None: + return "" + try: + img = image + if hasattr(img, "cpu"): + img = img.cpu().numpy() + if isinstance(img, np.ndarray) and img.ndim == 4: + img = img[0] + if isinstance(img, np.ndarray): + img = np.clip(img * 255.0, 0, 255).astype(np.uint8) + pil = Image.fromarray(img) + buf = io.BytesIO() + pil.save(buf, format="PNG") + return base64.b64encode(buf.getvalue()).decode("ascii") + except Exception: + return "" + return "" + + +def _ollama_chat_with_images(model: str, system_prompt: str, user_prompt: str, images: list[str], host: str = "localhost", port: int = 11434, timeout: int = 60) -> str: + url = f"http://{host}:{port}/api/chat" + payload = { + "model": model, + "messages": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt, "images": images}, + ], + "format": "json", + "stream": False, + "options": { + "temperature": 0.2, + }, + } + data = json.dumps(payload).encode("utf-8") + req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"}) + with urllib.request.urlopen(req, timeout=timeout) as resp: + raw = resp.read().decode("utf-8", errors="ignore") + try: + parsed = json.loads(raw) + if isinstance(parsed, dict) and "message" in parsed and isinstance(parsed["message"], dict): + return parsed["message"].get("content", "") + except Exception: + pass + return raw + + +def _parse_plan(raw: str) -> dict | None: + try: + data = json.loads(raw) + if isinstance(data, dict): + return data + except Exception: + pass + match = re.search(r"\{.*\}", raw, flags=re.S) + if match: + try: + return json.loads(match.group(0)) + except Exception: + return None + return None + + +class OllamaPromptPlanner: + """ + Use local Ollama to pick checkpoint + LoRAs + params based on the prompt and registry. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "prompt": ("STRING", {"multiline": True}), + "ollama_model": ("STRING", {"default": "qwen2.5:7b"}), + "registry_path": ("STRING", {"default": "model_registry.json"}), + "task_hint": (["auto", "text2img", "img2img", "inpaint", "sdxl", "sd15", "flux"],), + "user_negative": ("STRING", {"multiline": True, "default": ""}), + "ollama_host": ("STRING", {"default": "localhost"}), + "ollama_port": ("INT", {"default": 11434, "min": 1, "max": 65535}), + } + } + + RETURN_TYPES = ( + "STRING", + "STRING", + "STRING", + "STRING", + "STRING", + "STRING", + "INT", + "FLOAT", + "INT", + "INT", + "INT", + "STRING", + "STRING", + "STRING", + "STRING", + "FLOAT", + "BOOLEAN", + "STRING", + "STRING", + "FLOAT", + "FLOAT", + "FLOAT", + ) + RETURN_NAMES = ( + "checkpoint", + "vae_name", + "clip_name", + "loras", + "lora_strengths", + "model_type", + "steps", + "cfg", + "width", + "height", + "seed", + "positive_prompt", + "negative_prompt", + "plan_json", + "task", + "denoise", + "use_refiner", + "refiner_checkpoint", + "controlnet_name", + "controlnet_strength", + "controlnet_start", + "controlnet_end", + ) + FUNCTION = "plan" + CATEGORY = "Ollama/Planner" + + def plan(self, prompt, ollama_model, registry_path, task_hint, user_negative, ollama_host="localhost", ollama_port=11434): + registry = _read_registry(registry_path) + compact = _compact_registry(registry) + system_prompt = ( + "You are a routing planner for image generation. " + "Return ONLY valid JSON with keys: " + "task (text2img|img2img|inpaint), " + "model_type (sdxl|sd15|flux), checkpoint, vae_name (optional), clip_name (optional), " + "use_refiner (bool), refiner_checkpoint, " + "loras (array of names), lora_strengths (array of floats), " + "controlnet_name, controlnet_strength (float), controlnet_start (float), controlnet_end (float), " + "positive_prompt, negative_prompt, steps (int), cfg (float), width (int), height (int), " + "denoise (float), seed (int). " + "Use checkpoint, vae, clip and lora names exactly from the registry. " + "If unsure, pick a recommended checkpoint." + ) + user_payload = { + "task_hint": task_hint, + "prompt": prompt, + "user_negative": user_negative, + "registry": compact, + } + raw = "" + try: + raw = _ollama_chat(ollama_model, system_prompt, json.dumps(user_payload, ensure_ascii=True), host=ollama_host, port=ollama_port) + plan = _parse_plan(raw) + except urllib.error.URLError: + plan = None + except Exception: + plan = None + + if plan is None: + plan = _heuristic_plan(prompt, registry) + plan["task"] = "img2img" + plan["denoise"] = 0.7 + + negative = plan.get("negative_prompt", "") or "" + if user_negative.strip(): + if negative.strip(): + negative = f"{negative}, {user_negative.strip()}" + else: + negative = user_negative.strip() + + loras = plan.get("loras", []) + if isinstance(loras, str): + loras = [l.strip() for l in loras.split(",") if l.strip()] + strengths = plan.get("lora_strengths", []) + if isinstance(strengths, str): + strengths = [s.strip() for s in strengths.split(",") if s.strip()] + + lora_str = ",".join([l for l in loras if l]) + strength_str = ",".join([str(s) for s in strengths if str(s).strip()]) + + return ( + str(plan.get("checkpoint", "")), + str(plan.get("vae_name", "")), + str(plan.get("clip_name", "")), + lora_str, + strength_str, + str(plan.get("model_type", "sdxl")), + int(plan.get("steps", 25)), + float(plan.get("cfg", 6.5)), + int(plan.get("width", 1024)), + int(plan.get("height", 1024)), + int(plan.get("seed", int(time.time()) % 2_000_000_000)), + str(plan.get("positive_prompt", prompt)), + negative, + json.dumps(plan, ensure_ascii=True), + str(plan.get("task", "text2img")), + float(plan.get("denoise", 1.0)), + bool(plan.get("use_refiner", False)), + str(plan.get("refiner_checkpoint", "")), + str(plan.get("controlnet_name", "")), + float(plan.get("controlnet_strength", 0.0)), + float(plan.get("controlnet_start", 0.0)), + float(plan.get("controlnet_end", 1.0)), + ) + + +class DynamicCheckpointLoader: + """ + Universal Loader: + - Loads Checkpoint (Model/Clip/VAE) + - Supports separate Diffusion/CLIP/VAE loading if overrides provided. + - Implements Fallback logic: If 'checkpoint' is missing, finds the best alternative from registry not just by name but by Group/Category. + - Prompts for download if missing. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "checkpoint": ("STRING", {"default": ""}), + "model_type": (["auto", "sdxl", "sd15", "flux", "svd", "hunyuan", "wan"],), + "registry_path": ("STRING", {"default": "model_registry.json"}), + "custom_path": ("STRING", {"default": ""}), + }, + "optional": { + "vae_override": ("STRING", {"default": ""}), + "clip_override": ("STRING", {"default": ""}), + } + } + + RETURN_TYPES = ("MODEL", "CLIP", "VAE") + FUNCTION = "load" + CATEGORY = "Ollama/Loader" + + def load(self, checkpoint, model_type, registry_path, custom_path="", vae_override="", clip_override=""): + registry = _read_registry(registry_path) + + # 1. Identify target metadata from registry + target_info = None + registry_idx = -1 + for i, ckpt in enumerate(registry.get("checkpoints", [])): + if ckpt.get("name") == checkpoint: + target_info = ckpt + registry_idx = i + break + + resolved_type = model_type + if target_info and model_type == "auto": + resolved_type = target_info.get("type", "sdxl") + + # 2. Resolve Checkpoint Path (or Fallback) + ckpt_path = self._find_path("checkpoints", checkpoint, custom_path) + + if not ckpt_path: + # Checkpoint Missing: logical fallback + print(f"\\n[OllamaRouter] !!! MISSING MODEL: {checkpoint} !!!") + if target_info: + print(f"[OllamaRouter] DOWNLOAD URL: {target_info.get('url', 'N/A')}") + print(f"[OllamaRouter] GROUP: {target_info.get('group', 'N/A')}") + + # Find Fallback + fallback_ckpt = self._find_fallback(registry, target_info, resolved_type, custom_path) + if fallback_ckpt: + print(f"[OllamaRouter] -> Falling back to available model: {fallback_ckpt}") + ckpt_path = self._find_path("checkpoints", fallback_ckpt, custom_path) + else: + raise FileNotFoundError(f"Model '{checkpoint}' not found and no suitable fallback in group/type '{resolved_type}' discovered.") + + # 3. Load Main Components + # If it's a standard checkpoint (safetensors with bundled model/clip/vae) + # Note: folder_paths.get_full_path_or_raise would have raised if we used it directly, but we used _find_path + + out_model, out_clip, out_vae = comfy.sd.load_checkpoint_guess_config( + ckpt_path, + output_vae=True, + output_clip=True, + embedding_directory=folder_paths.get_folder_paths("embeddings"), + )[:3] + + # 4. Handle Overrides (Universal Loading) + if vae_override: + print(f"[OllamaRouter] Attempting VAE Override: {vae_override}") + v_path = self._find_path("vae", vae_override, custom_path) + if v_path: + try: + out_vae = comfy.sd.VAE(sd=comfy.utils.load_torch_file(v_path)) + print(f"[OllamaRouter] VAE loaded: {v_path}") + except Exception as e: + print(f"[OllamaRouter] Failed to load VAE: {e}") + else: + print(f"[OllamaRouter] Warning: VAE Override '{vae_override}' not found.") + + if clip_override: + print(f"[OllamaRouter] Attempting CLIP Override: {clip_override}") + c_path = self._find_path("checkpoints", clip_override, custom_path) + if c_path: + try: + _, out_clip, _ = comfy.sd.load_checkpoint_guess_config( + c_path, + output_vae=False, + output_clip=True, + embedding_directory=folder_paths.get_folder_paths("embeddings") + )[:3] + print(f"[OllamaRouter] CLIP loaded: {c_path}") + except Exception as e: + print(f"[OllamaRouter] Failed to load CLIP: {e}") + else: + print(f"[OllamaRouter] Warning: CLIP Override '{clip_override}' not found.") + + return (out_model, out_clip, out_vae) + + def _find_path(self, folder_type, filename, custom_path): + try: + path = folder_paths.get_full_path(folder_type, filename) + if path: return path + except: pass + + if folder_type != "checkpoints": + try: + path = folder_paths.get_full_path("checkpoints", filename) + if path: return path + except: pass + + if custom_path and os.path.exists(custom_path): + p = os.path.join(custom_path, filename) + if os.path.exists(p): return p + for root, dirs, files in os.walk(custom_path): + if filename in files: + return os.path.join(root, filename) + return None + + def _find_fallback(self, registry, target_info, resolved_type, custom_path): + candidates = registry.get("checkpoints", []) + if not target_info: + for c in candidates: + if c.get("type") == resolved_type: + if self._find_path("checkpoints", c["name"], custom_path): return c["name"] + return None + + grp = target_info.get("group") + cat = target_info.get("category") + + # Priority 1: Same Group & Category + for c in candidates: + if c["name"] == target_info["name"]: continue + if c.get("group") == grp and c.get("category") == cat and c.get("type") == resolved_type: + if self._find_path("checkpoints", c["name"], custom_path): return c["name"] + + # Priority 2: Same Group & Type + for c in candidates: + if c["name"] == target_info["name"]: continue + if c.get("group") == grp and c.get("type") == resolved_type: + if self._find_path("checkpoints", c["name"], custom_path): return c["name"] + + # Priority 3: Same Type + for c in candidates: + if c["name"] == target_info["name"]: continue + if c.get("type") == resolved_type: + if self._find_path("checkpoints", c["name"], custom_path): return c["name"] + + return None + + +class DynamicLoraStack: + """ + Apply a comma-separated list of LoRAs to a model/clip pair. + Includes Smart Fallback lookup if specific LoRA name is missing. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "model": ("MODEL",), + "clip": ("CLIP",), + "loras": ("STRING", {"default": ""}), + "lora_strengths": ("STRING", {"default": ""}), + "custom_path": ("STRING", {"default": ""}), + } + } + + RETURN_TYPES = ("MODEL", "CLIP") + FUNCTION = "apply" + CATEGORY = "Ollama/Loader" + + def apply(self, model, clip, loras, lora_strengths, custom_path=""): + lora_list = [l.strip() for l in loras.split(",") if l.strip()] + strength_list = [s.strip() for s in lora_strengths.split(",") if s.strip()] + strengths = [] + for s in strength_list: + try: + strengths.append(float(s)) + except Exception: + strengths.append(0.7) + + if not lora_list: + return (model, clip) + + registry = {} + try: + registry = _read_registry("model_registry.json") + except: + pass + + model_out, clip_out = model, clip + for idx, lora_name in enumerate(lora_list): + strength = strengths[idx] if idx < len(strengths) else 0.7 + + lora_path = self._find_path(lora_name, custom_path) + + if not lora_path: + target_info = None + for l in registry.get("loras", []): + if l.get("name") == lora_name: + target_info = l + break + + print(f"\\n[OllamaRouter] !!! MISSING LORA: {lora_name} !!!") + if target_info: + print(f"[OllamaRouter] DOWNLOAD URL: {target_info.get('url', 'N/A')}") + + # Try Fallback + fallback_name = None + if target_info: + fallback_name = self._find_fallback(registry, target_info, custom_path) + + if fallback_name: + print(f"[OllamaRouter] -> Falling back to LoRA: {fallback_name}") + lora_path = self._find_path(fallback_name, custom_path) + else: + print(f"[OllamaRouter] -> Skipping missing LoRA.") + continue + + # Load + try: + lora = comfy.utils.load_torch_file(lora_path, safe_load=True) + model_out, clip_out = comfy.sd.load_lora_for_models(model_out, clip_out, lora, strength, strength) + print(f"[OllamaRouter] LoRA Loaded: {os.path.basename(lora_path)}") + except Exception as e: + print(f"[OllamaRouter] Error loading LoRA {lora_path}: {e}") + + return (model_out, clip_out) + + def _find_path(self, filename, custom_path): + try: + path = folder_paths.get_full_path("loras", filename) + if path: return path + except: pass + if custom_path and os.path.exists(custom_path): + p = os.path.join(custom_path, filename) + if os.path.exists(p): return p + return None + + def _find_fallback(self, registry, target_info, custom_path): + target_grp = target_info.get("group", "") + target_cat = target_info.get("category", "") + + # Priority 1: Same Group & Category + for l in registry.get("loras", []): + if l["name"] == target_info["name"]: continue + if l.get("group") == target_grp and l.get("category") == target_cat: + if self._find_path(l["name"], custom_path): return l["name"] + + # Priority 2: Same Category only + for l in registry.get("loras", []): + if l["name"] == target_info["name"]: continue + if l.get("category") == target_cat: + if self._find_path(l["name"], custom_path): return l["name"] + return None + + +class RouteConditioningByType: + """ + Route conditioning between SDXL and SD1.5 branches based on model_type. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "model_type": (["sdxl", "sd15", "flux"],), + "sdxl": ("CONDITIONING",), + "sd15": ("CONDITIONING",), + } + } + + RETURN_TYPES = ("CONDITIONING",) + FUNCTION = "route" + CATEGORY = "Ollama/Route" + + def route(self, model_type, sdxl, sd15): + return (sdxl if model_type == "sdxl" else sd15,) + + +class OllamaVisionStylePlanner: + """ + Use local Ollama vision model to analyze an image and pick checkpoint + LoRAs + params. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + "prompt": ("STRING", {"multiline": True, "default": ""}), + "ollama_model": ("STRING", {"default": "llava:7b"}), + "registry_path": ("STRING", {"default": "model_registry.json"}), + "task_hint": (["auto", "img2img", "sdxl", "sd15", "flux"],), + "user_negative": ("STRING", {"multiline": True, "default": ""}), + "ollama_host": ("STRING", {"default": "localhost"}), + "ollama_port": ("INT", {"default": 11434, "min": 1, "max": 65535}), + } + } + + RETURN_TYPES = ( + "STRING", + "STRING", + "STRING", + "STRING", + "STRING", + "STRING", + "INT", + "FLOAT", + "INT", + "INT", + "INT", + "STRING", + "STRING", + "STRING", + "STRING", + "FLOAT", + "BOOLEAN", + "STRING", + "STRING", + "FLOAT", + "FLOAT", + "FLOAT", + ) + RETURN_NAMES = ( + "checkpoint", + "vae_name", + "clip_name", + "loras", + "lora_strengths", + "model_type", + "steps", + "cfg", + "width", + "height", + "seed", + "positive_prompt", + "negative_prompt", + "plan_json", + "task", + "denoise", + "use_refiner", + "refiner_checkpoint", + "controlnet_name", + "controlnet_strength", + "controlnet_start", + "controlnet_end", + ) + FUNCTION = "plan" + CATEGORY = "Ollama/Planner" + + def plan(self, image, prompt, ollama_model, registry_path, task_hint, user_negative, ollama_host="localhost", ollama_port=11434): + registry = _read_registry(registry_path) + compact = _compact_registry(registry) + system_prompt = ( + "You analyze an image style and select the best image generation setup. " + "Return ONLY valid JSON with keys: " + "task (text2img|img2img|inpaint), " + "model_type (sdxl|sd15|flux), checkpoint, vae_name (optional), clip_name (optional), " + "use_refiner (bool), refiner_checkpoint, " + "loras (array of names), lora_strengths (array of floats), " + "controlnet_name, controlnet_strength (float), controlnet_start (float), controlnet_end (float), " + "positive_prompt, negative_prompt, steps (int), cfg (float), width (int), height (int), " + "denoise (float), seed (int). " + "Use checkpoint, vae, clip and lora names exactly from the registry. " + "If unsure, pick a recommended checkpoint." + ) + user_payload = { + "task_hint": task_hint, + "prompt": prompt, + "user_negative": user_negative, + "registry": compact, + } + raw = "" + try: + img_b64 = _encode_image_base64(image) + raw = _ollama_chat_with_images( + ollama_model, + system_prompt, + json.dumps(user_payload, ensure_ascii=True), + [img_b64] if img_b64 else [], + host=ollama_host, + port=ollama_port + ) + plan = _parse_plan(raw) + except urllib.error.URLError: + plan = None + except Exception: + plan = None + + if plan is None: + plan = _heuristic_plan(prompt, registry) + + negative = plan.get("negative_prompt", "") or "" + if user_negative.strip(): + if negative.strip(): + negative = f"{negative}, {user_negative.strip()}" + else: + negative = user_negative.strip() + + loras = plan.get("loras", []) + if isinstance(loras, str): + loras = [l.strip() for l in loras.split(",") if l.strip()] + strengths = plan.get("lora_strengths", []) + if isinstance(strengths, str): + strengths = [s.strip() for s in strengths.split(",") if s.strip()] + + lora_str = ",".join([l for l in loras if l]) + strength_str = ",".join([str(s) for s in strengths if str(s).strip()]) + + return ( + str(plan.get("checkpoint", "")), + str(plan.get("vae_name", "")), + str(plan.get("clip_name", "")), + lora_str, + strength_str, + str(plan.get("model_type", "sdxl")), + int(plan.get("steps", 25)), + float(plan.get("cfg", 6.5)), + int(plan.get("width", 1024)), + int(plan.get("height", 1024)), + int(plan.get("seed", int(time.time()) % 2_000_000_000)), + str(plan.get("positive_prompt", prompt)), + negative, + json.dumps(plan, ensure_ascii=True), + str(plan.get("task", "img2img")), + float(plan.get("denoise", 0.7)), + bool(plan.get("use_refiner", False)), + str(plan.get("refiner_checkpoint", "")), + str(plan.get("controlnet_name", "")), + float(plan.get("controlnet_strength", 0.0)), + float(plan.get("controlnet_start", 0.0)), + float(plan.get("controlnet_end", 1.0)), + ) + + +class OptionalControlNetApply: + """ + Optionally apply ControlNet based on a boolean flag and controlnet name. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "positive": ("CONDITIONING",), + "negative": ("CONDITIONING",), + "controlnet_name": ("STRING", {"default": ""}), + "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + }, + "optional": { + "image": ("IMAGE",), + "vae": ("VAE",), + }, + } + + RETURN_TYPES = ("CONDITIONING", "CONDITIONING") + RETURN_NAMES = ("positive", "negative") + FUNCTION = "apply" + CATEGORY = "Ollama/ControlNet" + + def apply(self, positive, negative, controlnet_name, strength, start_percent, end_percent, image=None, vae=None): + if (not controlnet_name) or (image is None) or strength == 0: + return (positive, negative) + + controlnet_path = folder_paths.get_full_path_or_raise("controlnet", controlnet_name) + controlnet = comfy.controlnet.load_controlnet(controlnet_path) + if controlnet is None: + raise RuntimeError("ERROR: controlnet file is invalid and does not contain a valid controlnet model.") + + control_hint = image.movedim(-1, 1) + cnets = {} + out = [] + for conditioning in [positive, negative]: + c = [] + for t in conditioning: + d = t[1].copy() + prev_cnet = d.get("control", None) + if prev_cnet in cnets: + c_net = cnets[prev_cnet] + else: + c_net = controlnet.copy().set_cond_hint( + control_hint, + strength, + (start_percent, end_percent), + vae=vae, + extra_concat=[], + ) + c_net.set_previous_controlnet(prev_cnet) + cnets[prev_cnet] = c_net + d["control"] = c_net + d["control_apply_to_uncond"] = True + n = [t[0], d] + c.append(n) + out.append(c) + return (out[0], out[1]) + + +class RouteLatentByBool: + """ + Route between two LATENT inputs based on a boolean. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "use_b": ("BOOLEAN", {"default": False}), + "a": ("LATENT",), + "b": ("LATENT",), + } + } + + RETURN_TYPES = ("LATENT",) + FUNCTION = "route" + CATEGORY = "Ollama/Route" + + def route(self, use_b, a, b): + return (b if use_b else a,) + + +class RouteVaeByBool: + """ + Route between two VAE inputs based on a boolean. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "use_b": ("BOOLEAN", {"default": False}), + "a": ("VAE",), + "b": ("VAE",), + } + } + + RETURN_TYPES = ("VAE",) + FUNCTION = "route" + CATEGORY = "Ollama/Route" + + def route(self, use_b, a, b): + return (b if use_b else a,) + + +class OllamaDebugInfo: + """ + Simple debug node to visualize the plan JSON as text. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text": ("STRING", {"forceInput": True}), + } + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "notify" + CATEGORY = "Ollama/Debug" + OUTPUT_NODE = True + + def notify(self, text): + print(f"\n[Ollama Plan Debug]:\n{text}\n") + return {"ui": {"text": [text]}, "result": (text,)} diff --git a/nodes_ollama_router_new.py b/nodes_ollama_router_new.py new file mode 100644 index 0000000..459c7fb --- /dev/null +++ b/nodes_ollama_router_new.py @@ -0,0 +1,1178 @@ + +import base64 +import io +import json +import os +import re +import time +import urllib.request +import urllib.error + +import numpy as np +from PIL import Image + +import folder_paths +import comfy.sd +import comfy.utils +import comfy.controlnet + + +_DEFAULT_REGISTRY = { + "version": 1, + "checkpoints": [], + "loras": [], + "controlnets": [], +} + + +def _read_registry(registry_path: str) -> dict: + path = registry_path + if not os.path.isabs(path): + path = os.path.join(os.path.dirname(__file__), path) + if not os.path.exists(path): + return _DEFAULT_REGISTRY + try: + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + if isinstance(data, dict): + return data + except Exception: + pass + return _DEFAULT_REGISTRY + + +def _compact_registry(registry: dict, max_items: int = 50) -> dict: + checkpoints = registry.get("checkpoints", [])[:max_items] + loras = registry.get("loras", [])[:max_items] + controlnets = registry.get("controlnets", [])[:max_items] + + def _strip(items): + compact = [] + for item in items: + compact.append({ + "name": item.get("name", ""), + "type": item.get("type", ""), + "tags": item.get("tags", []), + "recommended": bool(item.get("recommended", False)), + }) + return compact + + return { + "checkpoints": _strip(checkpoints), + "loras": _strip(loras), + "controlnets": _strip(controlnets), + } + + +def _score_tags(prompt: str, tags: list[str]) -> int: + score = 0 + for tag in tags: + if tag and tag in prompt: + score += 3 + return score + + +def _heuristic_plan(prompt: str, registry: dict) -> dict: + prompt_l = prompt.lower() + checkpoints = registry.get("checkpoints", []) + loras = registry.get("loras", []) + controlnets = registry.get("controlnets", []) + + best_ckpt = None + best_score = -1 + for ckpt in checkpoints: + tags = [t.lower() for t in ckpt.get("tags", [])] + score = _score_tags(prompt_l, tags) + if ckpt.get("recommended"): + score += 1 + if score > best_score: + best_score = score + best_ckpt = ckpt + + if best_ckpt is None and checkpoints: + best_ckpt = checkpoints[0] + + selected_loras = [] + selected_strengths = [] + for lora in loras: + tags = [t.lower() for t in lora.get("tags", [])] + if _score_tags(prompt_l, tags) > 0: + selected_loras.append(lora.get("name", "")) + selected_strengths.append(float(lora.get("strength", 0.7))) + + model_type = (best_ckpt or {}).get("type", "sdxl") + controlnet_name = "" + controlnet_strength = 0.0 + for cn in controlnets: + tags = [t.lower() for t in cn.get("tags", [])] + if "pose" in prompt_l and ("pose" in tags or "openpose" in tags): + controlnet_name = cn.get("name", "") + controlnet_strength = float(cn.get("strength", 0.8)) + break + if ("edge" in prompt_l or "canny" in prompt_l or "lineart" in prompt_l) and ( + "canny" in tags or "edge" in tags or "lineart" in tags + ): + controlnet_name = cn.get("name", "") + controlnet_strength = float(cn.get("strength", 0.8)) + break + return { + "task": "text2img", + "model_type": model_type or "sdxl", + "checkpoint": (best_ckpt or {}).get("name", ""), + "vae_name": "", + "clip_name": "", + "use_refiner": False, + "refiner_checkpoint": "", + "loras": selected_loras, + "lora_strengths": selected_strengths, + "positive_prompt": prompt.strip(), + "negative_prompt": "", + "steps": 25, + "cfg": 6.5, + "width": 1024 if model_type == "sdxl" else 768, + "height": 1024 if model_type == "sdxl" else 768, + "denoise": 1.0, + "seed": int(time.time()) % 2_000_000_000, + "controlnet_name": controlnet_name, + "controlnet_strength": controlnet_strength, + "controlnet_start": 0.0, + "controlnet_end": 1.0, + } + + +def _ollama_chat(model: str, system_prompt: str, user_prompt: str, host: str = "localhost", port: int = 11434, timeout: int = 40) -> str: + url = f"http://{host}:{port}/api/chat" + payload = { + "model": model, + "messages": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt}, + ], + "format": "json", + "stream": False, + "options": { + "temperature": 0.2, + }, + } + data = json.dumps(payload).encode("utf-8") + req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"}) + with urllib.request.urlopen(req, timeout=timeout) as resp: + raw = resp.read().decode("utf-8", errors="ignore") + try: + parsed = json.loads(raw) + if isinstance(parsed, dict) and "message" in parsed and isinstance(parsed["message"], dict): + return parsed["message"].get("content", "") + except Exception: + pass + return raw + + +def _encode_image_base64(image) -> str: + # image is a ComfyUI IMAGE tensor (batch, height, width, channels) in 0..1 + if image is None: + return "" + try: + img = image + if hasattr(img, "cpu"): + img = img.cpu().numpy() + if isinstance(img, np.ndarray) and img.ndim == 4: + img = img[0] + if isinstance(img, np.ndarray): + img = np.clip(img * 255.0, 0, 255).astype(np.uint8) + pil = Image.fromarray(img) + buf = io.BytesIO() + pil.save(buf, format="PNG") + return base64.b64encode(buf.getvalue()).decode("ascii") + except Exception: + return "" + return "" + + +def _ollama_chat_with_images(model: str, system_prompt: str, user_prompt: str, images: list[str], host: str = "localhost", port: int = 11434, timeout: int = 60) -> str: + url = f"http://{host}:{port}/api/chat" + payload = { + "model": model, + "messages": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt, "images": images}, + ], + "format": "json", + "stream": False, + "options": { + "temperature": 0.2, + }, + } + data = json.dumps(payload).encode("utf-8") + req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"}) + with urllib.request.urlopen(req, timeout=timeout) as resp: + raw = resp.read().decode("utf-8", errors="ignore") + try: + parsed = json.loads(raw) + if isinstance(parsed, dict) and "message" in parsed and isinstance(parsed["message"], dict): + return parsed["message"].get("content", "") + except Exception: + pass + return raw + + +def _parse_plan(raw: str) -> dict | None: + try: + data = json.loads(raw) + if isinstance(data, dict): + return data + except Exception: + pass + match = re.search(r"\{.*\}", raw, flags=re.S) + if match: + try: + return json.loads(match.group(0)) + except Exception: + return None + return None + + +class OllamaPromptPlanner: + """ + Use local Ollama to pick checkpoint + LoRAs + params based on the prompt and registry. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "prompt": ("STRING", {"multiline": True}), + "ollama_model": ("STRING", {"default": "qwen2.5:7b"}), + "registry_path": ("STRING", {"default": "model_registry.json"}), + "task_hint": (["auto", "text2img", "img2img", "inpaint", "sdxl", "sd15", "flux"],), + "user_negative": ("STRING", {"multiline": True, "default": ""}), + "ollama_host": ("STRING", {"default": "localhost"}), + "ollama_port": ("INT", {"default": 11434, "min": 1, "max": 65535}), + } + } + + RETURN_TYPES = ( + "STRING", + "STRING", + "STRING", + "STRING", + "INT", + "FLOAT", + "INT", + "INT", + "INT", + "STRING", + "STRING", + "STRING", + "STRING", + "STRING", + "STRING", + "FLOAT", + "BOOLEAN", + "STRING", + "STRING", + "FLOAT", + "FLOAT", + "FLOAT", + ) + RETURN_NAMES = ( + "checkpoint", + "loras", + "lora_strengths", + "model_type", + "steps", + "cfg", + "width", + "height", + "seed", + "positive_prompt", + "negative_prompt", + "plan_json", + "vae_name", + "clip_name", + "task", + "denoise", + "use_refiner", + "refiner_checkpoint", + "controlnet_name", + "controlnet_strength", + "controlnet_start", + "controlnet_end", + ) + FUNCTION = "plan" + CATEGORY = "Ollama/Planner" + + def plan(self, prompt, ollama_model, registry_path, task_hint, user_negative, ollama_host="localhost", ollama_port=11434): + registry = _read_registry(registry_path) + compact = _compact_registry(registry) + system_prompt = ( + "You are a routing planner for image generation. " + "Return ONLY valid JSON with keys: " + "task (text2img|img2img|inpaint), " + "model_type (sdxl|sd15|flux), checkpoint, vae_name (optional), clip_name (optional), " + "use_refiner (bool), refiner_checkpoint, " + "loras (array of names), lora_strengths (array of floats), " + "controlnet_name, controlnet_strength (float), controlnet_start (float), controlnet_end (float), " + "positive_prompt, negative_prompt, steps (int), cfg (float), width (int), height (int), " + "denoise (float), seed (int). " + "Use checkpoint, vae, clip and lora names exactly from the registry. " + "If unsure, pick a recommended checkpoint." + ) + user_payload = { + "task_hint": task_hint, + "prompt": prompt, + "user_negative": user_negative, + "registry": compact, + } + raw = "" + try: + raw = _ollama_chat(ollama_model, system_prompt, json.dumps(user_payload, ensure_ascii=True), host=ollama_host, port=ollama_port) + plan = _parse_plan(raw) + except urllib.error.URLError: + plan = None + except Exception: + plan = None + + if plan is None: + plan = _heuristic_plan(prompt, registry) + plan["task"] = "img2img" + plan["denoise"] = 0.7 + + negative = plan.get("negative_prompt", "") or "" + if user_negative.strip(): + if negative.strip(): + negative = f"{negative}, {user_negative.strip()}" + else: + negative = user_negative.strip() + + loras = plan.get("loras", []) + if isinstance(loras, str): + loras = [l.strip() for l in loras.split(",") if l.strip()] + strengths = plan.get("lora_strengths", []) + if isinstance(strengths, str): + strengths = [s.strip() for s in strengths.split(",") if s.strip()] + + lora_str = ",".join([l for l in loras if l]) + strength_str = ",".join([str(s) for s in strengths if str(s).strip()]) + + return ( + str(plan.get("checkpoint", "")), + lora_str, + strength_str, + str(plan.get("model_type", "sdxl")), + int(plan.get("steps", 25)), + float(plan.get("cfg", 6.5)), + int(plan.get("width", 1024)), + int(plan.get("height", 1024)), + int(plan.get("seed", int(time.time()) % 2_000_000_000)), + str(plan.get("positive_prompt", prompt)), + negative, + json.dumps(plan, ensure_ascii=True), + str(plan.get("vae_name", "")), + str(plan.get("clip_name", "")), + str(plan.get("task", "text2img")), + float(plan.get("denoise", 1.0)), + bool(plan.get("use_refiner", False)), + str(plan.get("refiner_checkpoint", "")), + str(plan.get("controlnet_name", "")), + float(plan.get("controlnet_strength", 0.0)), + float(plan.get("controlnet_start", 0.0)), + float(plan.get("controlnet_end", 1.0)), + ) + + +class DynamicCheckpointLoader: + """ + Universal Loader: + - Loads Checkpoint (Model/Clip/VAE) + - Supports separate Diffusion/CLIP/VAE loading if overrides provided. + - Implements Fallback logic: If 'checkpoint' is missing, finds the best alternative from registry not just by name but by Group/Category. + - Prompts for download if missing. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "checkpoint": ("STRING", {"default": ""}), + "model_type": (["auto", "sdxl", "sd15", "flux", "svd", "hunyuan", "wan"],), + "registry_path": ("STRING", {"default": "model_registry.json"}), + "custom_path": ("STRING", {"default": ""}), + }, + "optional": { + "vae_override": ("STRING", {"default": ""}), + "clip_override": ("STRING", {"default": ""}), + } + } + + RETURN_TYPES = ("MODEL", "CLIP", "VAE", "STRING") + RETURN_NAMES = ("MODEL", "CLIP", "VAE", "debug_info") + FUNCTION = "load" + CATEGORY = "Ollama/Loader" + + def load(self, checkpoint, model_type, registry_path, custom_path="", vae_override="", clip_override=""): + registry = _read_registry(registry_path) + log_lines = [] + + # Log Search Paths + search_paths = folder_paths.get_folder_paths("checkpoints") + log_lines.append(f"--- Search Context ---") + log_lines.append(f"ComfyUI Checkpoint Paths: {search_paths}") + if custom_path: + log_lines.append(f"Custom Path: {custom_path}") + else: + log_lines.append(f"Custom Path: (None)") + + log_lines.append(f"--- Request ---") + log_lines.append(f"Model: '{checkpoint}'") + log_lines.append(f"Type Constraint: {model_type}") + + # 1. Identify target metadata from registry + target_info = None + registry_idx = -1 + for i, ckpt in enumerate(registry.get("checkpoints", [])): + if ckpt.get("name") == checkpoint: + target_info = ckpt + registry_idx = i + break + + resolved_type = model_type + if target_info and model_type == "auto": + resolved_type = target_info.get("type", "sdxl") + + # Determine Folder Type (default to checkpoints) + preferred_folder = "checkpoints" + if target_info and target_info.get("folder_type"): + preferred_folder = target_info.get("folder_type") + + log_lines.append(f"Resolved Type: {resolved_type}") + log_lines.append(f"Search Folder Strategy: {preferred_folder}") + if target_info: + log_lines.append(f"Registry Metadata: Group='{target_info.get('group')}', Category='{target_info.get('category')}'") + else: + log_lines.append(f"Registry Metadata: Not found in registry (using raw filename)") + + # 2. Resolve Checkpoint Path (or Fallback) + log_lines.append(f"--- Resolution ---") + ckpt_path = self._find_path(preferred_folder, checkpoint, custom_path) + + # Track Active Model Name for overrides + active_model_name = checkpoint + is_fallback = False + + if ckpt_path: + log_lines.append(f"Found directly at: {ckpt_path}") + else: + # Checkpoint Missing: logical fallback + log_lines.append(f"primary lookup failed for '{checkpoint}'") + print(f"\\n[OllamaRouter] !!! MISSING MODEL: {checkpoint} !!!") + + # Find Fallback + # Update fallback search to respect preferred folder of CANDIDATES if possible, + # but _find_fallback uses _find_path internally? No. + # _find_fallback returns a NAME. We need to resolve that name. + + fallback_name = self._find_fallback(registry, target_info, resolved_type, custom_path, start_index=registry_idx, log_store=log_lines) + if fallback_name: + msg = f"-> Fallback SUCCESS: Selected '{fallback_name}'" + log_lines.append(msg) + print(f"[OllamaRouter] {msg}") + + # We need to determine the folder type for the FALLBACK model too! + fallback_info = None + for c in registry.get("checkpoints", []): + if c.get("name") == fallback_name: + fallback_info = c + break + + fallback_folder = "checkpoints" + if fallback_info and fallback_info.get("folder_type"): + fallback_folder = fallback_info.get("folder_type") + + ckpt_path = self._find_path(fallback_folder, fallback_name, custom_path) + log_lines.append(f"Fallback Path: {ckpt_path} (Type: {fallback_folder})") + active_model_name = fallback_name + is_fallback = True + else: + error_msg = f"FATAL: Model '{checkpoint}' not found and no suitable fallback in group/type '{resolved_type}' discovered." + log_lines.append(error_msg) + full_log = "\n".join(log_lines) + raise FileNotFoundError(f"{error_msg}\n\nDEBUG LOG:\n{full_log}") + + log_lines.append(f"--- Loading ---") + # 3. Load Main Components + out_model, out_clip, out_vae = comfy.sd.load_checkpoint_guess_config( + ckpt_path, + output_vae=True, + output_clip=True, + embedding_directory=folder_paths.get_folder_paths("embeddings"), + )[:3] + log_lines.append(f"Main Model Loaded.") + + # 4. Handle Overrides (Universal Loading) + # Determine effective overrides (Use Native if Fallback) + effective_vae = vae_override + effective_clip = clip_override + + # Mandatory Components Check (Flux, Wan, Hunyuan, etc) + # Some models require explicit loading of separate CLIP/VAE files. + # We check the registry info for the ACTIVE model (either requested or fallback). + active_registry_info = target_info + if is_fallback and active_model_name != checkpoint: + # Look up fallback metadata + for ckpt in registry.get("checkpoints", []): + if ckpt.get("name") == active_model_name: + active_registry_info = ckpt + break + + if active_registry_info: + # Check for MANDATORY fields 'required_vae' or 'required_clip' + # Or just standard 'vae'/'clip' fields which imply a preference + reg_vae = active_registry_info.get("vae") or active_registry_info.get("required_vae") + reg_clip = active_registry_info.get("clip") or active_registry_info.get("required_clip") + + if reg_vae: + log_lines.append(f"-> Registry MANDATE: Model requires VAE '{reg_vae}'") + effective_vae = reg_vae + + if reg_clip: + log_lines.append(f"-> Registry MANDATE: Model requires CLIP '{reg_clip}'") + effective_clip = reg_clip + + if is_fallback and not active_registry_info: + # Fallback active but no registry info found -> assume native + log_lines.append(f"-> Fallback active but no registry metadata. Resetting overrides to Native.") + effective_vae = "" + effective_clip = "" + + if effective_vae: + # Handle comma-separated VAEs (rare, but possibly for fallbacks? Take first) + if "," in effective_vae: + log_lines.append(f"-> Detected multiple VAEs '{effective_vae}', using first one.") + effective_vae = effective_vae.split(",")[0].strip() + + log_lines.append(f"Override VAE: '{effective_vae}' ...") + # Special handling for "default" or "native" keywords to skip loading + if effective_vae.lower() in ["default", "native", "none"]: + log_lines.append(f"-> Using Native VAE (explicitly requested).") + else: + v_path = self._find_path("vae", effective_vae, custom_path) + if v_path: + try: + out_vae = comfy.sd.VAE(sd=comfy.utils.load_torch_file(v_path)) + log_lines.append(f"-> VAE Loaded from: {v_path}") + except Exception as e: + log_lines.append(f"-> VAE Load Failed: {e}") + else: + log_lines.append(f"-> VAE Not Found!") + + if effective_clip: + log_lines.append(f"Override CLIP: '{effective_clip}' ...") + # Special handling for "default" or "native" keywords to skip loading + if effective_clip.lower() in ["default", "native", "none"]: + log_lines.append(f"-> Using Native CLIP (explicitly requested).") + else: + # SPLIT logic for comma-separated CLIPs (e.g. "clip_l, t5") + # We will try to find the FIRST available one from the list to avoid errors. + clip_candidates = [c.strip() for c in effective_clip.split(",") if c.strip()] + + found_clip_path = None + for c_cand in clip_candidates: + path_res = self._find_path("clip", c_cand, custom_path) + if path_res: + found_clip_path = path_res + if len(clip_candidates) > 1: + log_lines.append(f"-> Selected '{c_cand}' from list.") + break + else: + log_lines.append(f"-> Candidate '{c_cand}' missing.") + + if found_clip_path: + try: + # Optimization: Use load_clip specifically if possible, else checkpoint guesser works for most safetensors + if "checkpoint" in folder_paths.get_folder_paths("checkpoints"): # Checking context + # Try generic loader first + pass + + # Note: load_checkpoint_guess_config is robust for safetensors clips too usually + _, out_clip, _ = comfy.sd.load_checkpoint_guess_config( + found_clip_path, + output_vae=False, + output_clip=True, + embedding_directory=folder_paths.get_folder_paths("embeddings") + )[:3] + log_lines.append(f"-> CLIP Loaded from: {found_clip_path}") + except Exception as e: + log_lines.append(f"-> CLIP Load Failed: {e}") + else: + log_lines.append(f"-> CLIP Not Found! (Searched: {clip_candidates})") + + return (out_model, out_clip, out_vae, "\n".join(log_lines)) + + def _find_path(self, folder_type, filename, custom_path): + folder_types = [folder_type] + # Expand searches for common types to support 'extra_model_paths.yaml' variations + if folder_type == "checkpoints": + folder_types = ["checkpoints", "diffusion_models", "unet"] + elif folder_type == "clip": + folder_types = ["clip", "text_encoders", "checkpoints"] + elif folder_type == "diffusion_models": + folder_types = ["diffusion_models", "unet", "checkpoints"] + elif folder_type == "vae": + folder_types = ["vae", "checkpoints"] + + for ft in folder_types: + try: + path = folder_paths.get_full_path(ft, filename) + if path: return path + except: pass + + # Checkpoint fallback (original behavior) if not covered above + if folder_type != "checkpoints" and "checkpoints" not in folder_types: + try: + path = folder_paths.get_full_path("checkpoints", filename) + if path: return path + except: pass + + # Custom Path Smart Search + if custom_path and os.path.exists(custom_path): + # 1. Direct path check + p = os.path.join(custom_path, filename) + if os.path.isfile(p): return p + + # 2. Subfolder check matching preferred types + # Note: We reuse the folder_types list expanded above + for ft in folder_types: + p_sub = os.path.join(custom_path, ft, filename) + if os.path.isfile(p_sub): return p_sub + + # 3. Recursive walk (Last resort) + # Only do this if specific lookups fail to avoid performance hits or permissions issues + try: + for root, dirs, files in os.walk(custom_path): + if filename in files: + return os.path.join(root, filename) + except Exception: + pass + + return None + + def _find_fallback(self, registry, target_info, resolved_type, custom_path, start_index=-1, log_store=None): + if log_store is None: log_store = [] + candidates = registry.get("checkpoints", []) + if not candidates: + log_store.append("Fallback: No candidates in registry.") + return None + + idx_start = start_index if start_index >= 0 else 0 + ordered_candidates = candidates[idx_start:] + candidates[:idx_start] + + # Add next-in-line logging + log_store.append(f"Fallback Search: checking {len(ordered_candidates)} candidates starting from index {idx_start}") + if len(ordered_candidates) > 1: + next_model = ordered_candidates[1].get("name") if len(ordered_candidates) > 1 else "None" + log_store.append(f"Next in line model would be: '{next_model}'") + + if not target_info: + log_store.append("Fallback: No target info, searching by type only...") + for c in ordered_candidates: + # If mode is 'auto', accept any type, otherwise must match + if resolved_type == "auto" or c.get("type") == resolved_type: + # We need to find the correct folder type for this candidate to check existence + cand_folder = c.get("folder_type", "checkpoints") + found = self._find_path(cand_folder, c["name"], custom_path) + if found: + log_store.append(f"-> Found candidate '{c['name']}' in {cand_folder}") + return c["name"] + return None + + grp = target_info.get("group") + cat = target_info.get("category") + log_store.append(f"Fallback Strategy: Match Group='{grp}' AND Category='{cat}' AND Type='{resolved_type}'") + + # Priority 1: Same Group & Category + for c in ordered_candidates: + if c["name"] == target_info["name"]: continue + if c.get("group") == grp and c.get("category") == cat and c.get("type") == resolved_type: + found = self._find_path("checkpoints", c["name"], custom_path) + if found: return c["name"] + # log_store.append(f"Candidate '{c['name']}' matched metadata but file not found.") + + # Priority 2: Same Group & Type + log_store.append(f"Fallback Strategy: Relaxed -> Match Group='{grp}' AND Type='{resolved_type}'") + for c in ordered_candidates: + if c["name"] == target_info["name"]: continue + if c.get("group") == grp and c.get("type") == resolved_type: + if self._find_path("checkpoints", c["name"], custom_path): return c["name"] + + # Priority 3: Same Type + log_store.append(f"Fallback Strategy: Relaxed -> Match Type='{resolved_type}' only") + for c in ordered_candidates: + if c["name"] == target_info["name"]: continue + if c.get("type") == resolved_type: + if self._find_path("checkpoints", c["name"], custom_path): return c["name"] + + log_store.append("Fallback: No suitable candidate found on disk.") + return None + + +class DynamicLoraStack: + """ + Apply a comma-separated list of LoRAs to a model/clip pair. + Includes Smart Fallback lookup if specific LoRA name is missing. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "model": ("MODEL",), + "clip": ("CLIP",), + "loras": ("STRING", {"default": ""}), + "lora_strengths": ("STRING", {"default": ""}), + "custom_path": ("STRING", {"default": ""}), + } + } + + RETURN_TYPES = ("MODEL", "CLIP", "STRING") + RETURN_NAMES = ("MODEL", "CLIP", "debug_info") + FUNCTION = "apply" + CATEGORY = "Ollama/Loader" + + def apply(self, model, clip, loras, lora_strengths, custom_path=""): + log_lines = [] + log_lines.append(f"--- LoRA Stack Request ---") + + lora_list = [l.strip() for l in loras.split(",") if l.strip()] + strength_list = [s.strip() for s in lora_strengths.split(",") if s.strip()] + strengths = [] + for s in strength_list: + try: + strengths.append(float(s)) + except Exception: + strengths.append(0.7) + + log_lines.append(f"LoRAs: {lora_list}") + log_lines.append(f"Search Paths: {folder_paths.get_folder_paths('loras')}") + if custom_path: log_lines.append(f"Custom Path: {custom_path}") + + if not lora_list: + log_lines.append("No LoRAs requested.") + return (model, clip, "\n".join(log_lines)) + + registry = {} + try: + registry = _read_registry("model_registry.json") + except: + pass + + model_out, clip_out = model, clip + for idx, lora_name in enumerate(lora_list): + strength = strengths[idx] if idx < len(strengths) else 0.7 + log_lines.append(f"\nProcessing '{lora_name}' (Strength: {strength})") + + lora_path = self._find_path(lora_name, custom_path) + + if not lora_path: + target_info = None + for l in registry.get("loras", []): + if l.get("name") == lora_name: + target_info = l + break + + log_lines.append(f"!!! MISSING LORA: {lora_name} !!!") + print(f"\\n[OllamaRouter] !!! MISSING LORA: {lora_name} !!!") + if target_info: + info_str = f"Registry Info: Group={target_info.get('group')}, Category={target_info.get('category')}" + log_lines.append(info_str) + print(f"[OllamaRouter] {info_str}") + + # Try Fallback + fallback_name = None + if target_info: + fallback_name = self._find_fallback(registry, target_info, custom_path, log_store=log_lines) + + if fallback_name: + msg = f"-> Falling back to LoRA: {fallback_name}" + log_lines.append(msg) + print(f"[OllamaRouter] {msg}") + lora_path = self._find_path(fallback_name, custom_path) + else: + msg = f"-> Skipping missing LoRA '{lora_name}' (No fallback found)" + log_lines.append(msg) + print(f"[OllamaRouter] {msg}") + continue + + # Load + try: + lora = comfy.utils.load_torch_file(lora_path, safe_load=True) + model_out, clip_out = comfy.sd.load_lora_for_models(model_out, clip_out, lora, strength, strength) + msg = f"Loaded: {lora_path}" + log_lines.append(msg) + print(f"[OllamaRouter] {msg}") + except Exception as e: + err = f"Error loading LoRA {lora_path}: {e}" + log_lines.append(err) + print(f"[OllamaRouter] {err}") + + return (model_out, clip_out, "\n".join(log_lines)) + + def _find_path(self, filename, custom_path): + try: + path = folder_paths.get_full_path("loras", filename) + if path: return path + except: pass + if custom_path and os.path.exists(custom_path): + p = os.path.join(custom_path, filename) + if os.path.exists(p): return p + return None + + def _find_fallback(self, registry, target_info, custom_path, log_store=None): + if log_store is None: log_store = [] + target_grp = target_info.get("group", "") + target_cat = target_info.get("category", "") + + # Priority 1: Same Group & Category + for l in registry.get("loras", []): + if l["name"] == target_info["name"]: continue + if l.get("group") == target_grp and l.get("category") == target_cat: + if self._find_path(l["name"], custom_path): return l["name"] + + # Priority 2: Same Category only + for l in registry.get("loras", []): + if l["name"] == target_info["name"]: continue + if l.get("category") == target_cat: + if self._find_path(l["name"], custom_path): return l["name"] + + log_store.append("Fallback: No suitable LoRA candidate found.") + return None + + +class RouteConditioningByType: + """ + Route conditioning between SDXL and SD1.5 branches based on model_type. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "model_type": (["sdxl", "sd15", "flux"],), + "sdxl": ("CONDITIONING",), + "sd15": ("CONDITIONING",), + } + } + + RETURN_TYPES = ("CONDITIONING",) + FUNCTION = "route" + CATEGORY = "Ollama/Route" + + def route(self, model_type, sdxl, sd15): + return (sdxl if model_type == "sdxl" else sd15,) + + +class OllamaVisionStylePlanner: + """ + Use local Ollama vision model to analyze an image and pick checkpoint + LoRAs + params. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + "prompt": ("STRING", {"multiline": True, "default": ""}), + "ollama_model": ("STRING", {"default": "llava:7b"}), + "registry_path": ("STRING", {"default": "model_registry.json"}), + "task_hint": (["auto", "img2img", "sdxl", "sd15", "flux"],), + "user_negative": ("STRING", {"multiline": True, "default": ""}), + "ollama_host": ("STRING", {"default": "localhost"}), + "ollama_port": ("INT", {"default": 11434, "min": 1, "max": 65535}), + } + } + + RETURN_TYPES = ( + "STRING", + "STRING", + "STRING", + "STRING", + "INT", + "FLOAT", + "INT", + "INT", + "INT", + "STRING", + "STRING", + "STRING", + "STRING", + "STRING", + "STRING", + "FLOAT", + "BOOLEAN", + "STRING", + "STRING", + "FLOAT", + "FLOAT", + "FLOAT", + ) + RETURN_NAMES = ( + "checkpoint", + "loras", + "lora_strengths", + "model_type", + "steps", + "cfg", + "width", + "height", + "seed", + "positive_prompt", + "negative_prompt", + "plan_json", + "vae_name", + "clip_name", + "task", + "denoise", + "use_refiner", + "refiner_checkpoint", + "controlnet_name", + "controlnet_strength", + "controlnet_start", + "controlnet_end", + ) + FUNCTION = "plan" + CATEGORY = "Ollama/Planner" + + def plan(self, image, prompt, ollama_model, registry_path, task_hint, user_negative, ollama_host="localhost", ollama_port=11434): + registry = _read_registry(registry_path) + compact = _compact_registry(registry) + system_prompt = ( + "You analyze an image style and select the best image generation setup. " + "Return ONLY valid JSON with keys: " + "task (text2img|img2img|inpaint), " + "model_type (sdxl|sd15|flux), checkpoint, vae_name (optional), clip_name (optional), " + "use_refiner (bool), refiner_checkpoint, " + "loras (array of names), lora_strengths (array of floats), " + "controlnet_name, controlnet_strength (float), controlnet_start (float), controlnet_end (float), " + "positive_prompt, negative_prompt, steps (int), cfg (float), width (int), height (int), " + "denoise (float), seed (int). " + "Use checkpoint, vae, clip and lora names exactly from the registry. " + "If unsure, pick a recommended checkpoint." + ) + user_payload = { + "task_hint": task_hint, + "prompt": prompt, + "user_negative": user_negative, + "registry": compact, + } + raw = "" + try: + img_b64 = _encode_image_base64(image) + raw = _ollama_chat_with_images( + ollama_model, + system_prompt, + json.dumps(user_payload, ensure_ascii=True), + [img_b64] if img_b64 else [], + host=ollama_host, + port=ollama_port + ) + plan = _parse_plan(raw) + except urllib.error.URLError: + plan = None + except Exception: + plan = None + + if plan is None: + plan = _heuristic_plan(prompt, registry) + + negative = plan.get("negative_prompt", "") or "" + if user_negative.strip(): + if negative.strip(): + negative = f"{negative}, {user_negative.strip()}" + else: + negative = user_negative.strip() + + loras = plan.get("loras", []) + if isinstance(loras, str): + loras = [l.strip() for l in loras.split(",") if l.strip()] + strengths = plan.get("lora_strengths", []) + if isinstance(strengths, str): + strengths = [s.strip() for s in strengths.split(",") if s.strip()] + + lora_str = ",".join([l for l in loras if l]) + strength_str = ",".join([str(s) for s in strengths if str(s).strip()]) + + return ( + str(plan.get("checkpoint", "")), + lora_str, + strength_str, + str(plan.get("model_type", "sdxl")), + int(plan.get("steps", 25)), + float(plan.get("cfg", 6.5)), + int(plan.get("width", 1024)), + int(plan.get("height", 1024)), + int(plan.get("seed", int(time.time()) % 2_000_000_000)), + str(plan.get("positive_prompt", prompt)), + negative, + json.dumps(plan, ensure_ascii=True), + str(plan.get("vae_name", "")), + str(plan.get("clip_name", "")), + str(plan.get("task", "img2img")), + float(plan.get("denoise", 0.7)), + bool(plan.get("use_refiner", False)), + str(plan.get("refiner_checkpoint", "")), + str(plan.get("controlnet_name", "")), + float(plan.get("controlnet_strength", 0.0)), + float(plan.get("controlnet_start", 0.0)), + float(plan.get("controlnet_end", 1.0)), + ) + + +class OptionalControlNetApply: + """ + Optionally apply ControlNet based on a boolean flag and controlnet name. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "positive": ("CONDITIONING",), + "negative": ("CONDITIONING",), + "controlnet_name": ("STRING", {"default": ""}), + "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + }, + "optional": { + "image": ("IMAGE",), + "vae": ("VAE",), + }, + } + + RETURN_TYPES = ("CONDITIONING", "CONDITIONING") + RETURN_NAMES = ("positive", "negative") + FUNCTION = "apply" + CATEGORY = "Ollama/ControlNet" + + def apply(self, positive, negative, controlnet_name, strength, start_percent, end_percent, image=None, vae=None): + if (not controlnet_name) or (image is None) or strength == 0: + return (positive, negative) + + controlnet_path = folder_paths.get_full_path_or_raise("controlnet", controlnet_name) + controlnet = comfy.controlnet.load_controlnet(controlnet_path) + if controlnet is None: + raise RuntimeError("ERROR: controlnet file is invalid and does not contain a valid controlnet model.") + + control_hint = image.movedim(-1, 1) + cnets = {} + out = [] + for conditioning in [positive, negative]: + c = [] + for t in conditioning: + d = t[1].copy() + prev_cnet = d.get("control", None) + if prev_cnet in cnets: + c_net = cnets[prev_cnet] + else: + c_net = controlnet.copy().set_cond_hint( + control_hint, + strength, + (start_percent, end_percent), + vae=vae, + extra_concat=[], + ) + c_net.set_previous_controlnet(prev_cnet) + cnets[prev_cnet] = c_net + d["control"] = c_net + d["control_apply_to_uncond"] = True + n = [t[0], d] + c.append(n) + out.append(c) + return (out[0], out[1]) + + +class RouteLatentByBool: + """ + Route between two LATENT inputs based on a boolean. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "use_b": ("BOOLEAN", {"default": False}), + "a": ("LATENT",), + "b": ("LATENT",), + } + } + + RETURN_TYPES = ("LATENT",) + FUNCTION = "route" + CATEGORY = "Ollama/Route" + + def route(self, use_b, a, b): + return (b if use_b else a,) + + +class RouteVaeByBool: + """ + Route between two VAE inputs based on a boolean. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "use_b": ("BOOLEAN", {"default": False}), + "a": ("VAE",), + "b": ("VAE",), + } + } + + RETURN_TYPES = ("VAE",) + FUNCTION = "route" + CATEGORY = "Ollama/Route" + + def route(self, use_b, a, b): + return (b if use_b else a,) + + +class OllamaDebugInfo: + """ + Simple debug node to visualize the plan JSON as text. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text": ("STRING", {"forceInput": True}), + } + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "notify" + CATEGORY = "Ollama/Debug" + OUTPUT_NODE = True + + def notify(self, text): + print(f"\n[Ollama Plan Debug]:\n{text}\n") + return {"ui": {"text": [text]}, "result": (text,)} + + +class OllamaRegistryInfo: + """ + Reads model_registry.json and outputs as string for debugging. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "registry_path": ("STRING", {"default": "model_registry.json"}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("json_content",) + FUNCTION = "read_registry" + CATEGORY = "Ollama/Debug" + + def read_registry(self, registry_path): + try: + registry = _read_registry(registry_path) + import json + return (json.dumps(registry, indent=2),) + except Exception as e: + return (f"Error reading registry: {e}",) diff --git a/temp_reg.py b/temp_reg.py new file mode 100644 index 0000000..f95b700 --- /dev/null +++ b/temp_reg.py @@ -0,0 +1,26 @@ + + +class OllamaRegistryInfo: + """ + Reads model_registry.json and outputs as string for debugging. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "registry_path": ("STRING", {"default": "model_registry.json"}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("json_content",) + FUNCTION = "read_registry" + CATEGORY = "Ollama/Debug" + + def read_registry(self, registry_path): + try: + registry = _read_registry(registry_path) + import json + return (json.dumps(registry, indent=2),) + except Exception as e: + return (f"Error reading registry: {e}",) diff --git a/workflows/ollama_auto_model.json b/workflows/ollama_auto_model.json new file mode 100644 index 0000000..2e783de --- /dev/null +++ b/workflows/ollama_auto_model.json @@ -0,0 +1,915 @@ +{ + "id": "00000000-0000-0000-0000-000000000000", + "revision": 0, + "last_node_id": 14, + "last_link_id": 48, + "nodes": [ + { + "id": 8, + "type": "VAEDecode", + "pos": [ + 2303.30234375, + 130 + ], + "size": [ + 140, + 46 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 41 + }, + { + "name": "vae", + "type": "VAE", + "link": 42 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 43 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "VAEDecode" + }, + "widgets_values": [] + }, + { + "id": 9, + "type": "SaveImage", + "pos": [ + 2543.30234375, + 130 + ], + "size": [ + 270, + 270 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 43 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0" + }, + "widgets_values": [ + "ollama_auto" + ] + }, + { + "id": 4, + "type": "CLIPTextEncode", + "pos": [ + 1391.7685886264053, + 131.73057313014968 + ], + "size": [ + 400, + 200 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 29 + }, + { + "name": "text", + "type": "STRING", + "widget": { + "name": "text" + }, + "link": 28 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 38 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "" + ] + }, + { + "id": 3, + "type": "DynamicLoraStack", + "pos": [ + 1087.5303675720959, + -30.078014538852617 + ], + "size": [ + 270, + 126 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 24 + }, + { + "name": "clip", + "type": "CLIP", + "link": 25 + }, + { + "name": "loras", + "type": "STRING", + "widget": { + "name": "loras" + }, + "link": 26 + }, + { + "name": "lora_strengths", + "type": "STRING", + "widget": { + "name": "lora_strengths" + }, + "link": 27 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 37 + ] + }, + { + "name": "CLIP", + "type": "CLIP", + "links": [ + 29, + 31 + ] + } + ], + "properties": { + "Node name for S&R": "DynamicLoraStack" + }, + "widgets_values": [ + "", + "", + "" + ] + }, + { + "id": 11, + "type": "PreviewAny", + "pos": [ + 1067.2037566842428, + -484.3435773139466 + ], + "size": [ + 472.7961826788911, + 313.6265408674249 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "source", + "type": "*", + "link": 45 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "PreviewAny" + }, + "widgets_values": [ + null, + null, + false + ] + }, + { + "id": 6, + "type": "EmptyLatentImage", + "pos": [ + 653.1736472060104, + 248.8277805706228 + ], + "size": [ + 270, + 106 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "width", + "type": "INT", + "widget": { + "name": "width" + }, + "link": 32 + }, + { + "name": "height", + "type": "INT", + "widget": { + "name": "height" + }, + "link": 33 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 40 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "EmptyLatentImage" + }, + "widgets_values": [ + 512, + 512, + 1 + ] + }, + { + "id": 7, + "type": "KSampler", + "pos": [ + 1934.2541589715825, + 187.10891329494214 + ], + "size": [ + 270, + 262 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 37 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 38 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 39 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 40 + }, + { + "name": "seed", + "type": "INT", + "widget": { + "name": "seed" + }, + "link": 34 + }, + { + "name": "steps", + "type": "INT", + "widget": { + "name": "steps" + }, + "link": 35 + }, + { + "name": "cfg", + "type": "FLOAT", + "widget": { + "name": "cfg" + }, + "link": 36 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 41 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 123911257713729, + "randomize", + 20, + 8, + "euler", + "normal", + 1 + ] + }, + { + "id": 2, + "type": "DynamicCheckpointLoader", + "pos": [ + 514.2501014010804, + -201.5778117366933 + ], + "size": [ + 363.30234375, + 218 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "checkpoint", + "type": "STRING", + "widget": { + "name": "checkpoint" + }, + "link": 23 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 24 + ] + }, + { + "name": "CLIP", + "type": "CLIP", + "links": [ + 25 + ] + }, + { + "name": "VAE", + "type": "VAE", + "links": [ + 42 + ] + } + ], + "properties": { + "Node name for S&R": "DynamicCheckpointLoader" + }, + "widgets_values": [ + "", + "auto", + "model_registry.json", + "I:\\models", + "", + "" + ] + }, + { + "id": 10, + "type": "OllamaDebugInfo", + "pos": [ + 631.3720013304425, + 494.3530839150838 + ], + "size": [ + 419.316538251496, + 321.9995165419782 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "text", + "type": "STRING", + "link": 44 + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": null + } + ], + "properties": { + "Node name for S&R": "OllamaDebugInfo" + }, + "widgets_values": [] + }, + { + "id": 5, + "type": "CLIPTextEncode", + "pos": [ + 1433.30234375, + 460 + ], + "size": [ + 400, + 200 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 31 + }, + { + "name": "text", + "type": "STRING", + "widget": { + "name": "text" + }, + "link": 30 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 39 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "" + ] + }, + { + "id": 13, + "type": "PreviewAny", + "pos": [ + 165.12385051624818, + 524.8214814554493 + ], + "size": [ + 472.7961826788911, + 313.6265408674249 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "source", + "type": "*", + "link": 47 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "PreviewAny" + }, + "widgets_values": [ + null, + null, + null + ] + }, + { + "id": 14, + "type": "PreviewAny", + "pos": [ + 174.88292662416, + 941.9574724006135 + ], + "size": [ + 472.7961826788911, + 313.6265408674249 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "source", + "type": "*", + "link": 48 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "PreviewAny" + }, + "widgets_values": [ + null, + null, + null + ] + }, + { + "id": 1, + "type": "OllamaPromptPlanner", + "pos": [ + -621.4759379594332, + 47.1920757223342 + ], + "size": [ + 400, + 522 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "checkpoint", + "type": "STRING", + "links": [ + 23, + 45 + ] + }, + { + "name": "loras", + "type": "STRING", + "links": [ + 26 + ] + }, + { + "name": "lora_strengths", + "type": "STRING", + "links": [ + 27 + ] + }, + { + "name": "model_type", + "type": "STRING", + "links": null + }, + { + "name": "steps", + "type": "INT", + "links": [ + 35 + ] + }, + { + "name": "cfg", + "type": "FLOAT", + "links": [ + 36 + ] + }, + { + "name": "width", + "type": "INT", + "links": [ + 32 + ] + }, + { + "name": "height", + "type": "INT", + "links": [ + 33 + ] + }, + { + "name": "seed", + "type": "INT", + "links": [ + 34 + ] + }, + { + "name": "positive_prompt", + "type": "STRING", + "links": [ + 28 + ] + }, + { + "name": "negative_prompt", + "type": "STRING", + "links": [ + 30 + ] + }, + { + "name": "plan_json", + "type": "STRING", + "links": [ + 44 + ] + }, + { + "name": "vae_name", + "type": "STRING", + "links": [ + 47 + ] + }, + { + "name": "clip_name", + "type": "STRING", + "links": [ + 48 + ] + } + ], + "properties": { + "Node name for S&R": "OllamaPromptPlanner" + }, + "widgets_values": [ + "Photorealistic Portrait", + "qwen2.5:7b", + "model_registry.json", + "auto", + "blurry, extra fingers, deformed", + "localhost", + 11434 + ] + } + ], + "links": [ + [ + 23, + 1, + 0, + 2, + 0, + "STRING" + ], + [ + 24, + 2, + 0, + 3, + 0, + "MODEL" + ], + [ + 25, + 2, + 1, + 3, + 1, + "CLIP" + ], + [ + 26, + 1, + 1, + 3, + 2, + "STRING" + ], + [ + 27, + 1, + 2, + 3, + 3, + "STRING" + ], + [ + 28, + 1, + 9, + 4, + 1, + "STRING" + ], + [ + 29, + 3, + 1, + 4, + 0, + "CLIP" + ], + [ + 30, + 1, + 10, + 5, + 1, + "STRING" + ], + [ + 31, + 3, + 1, + 5, + 0, + "CLIP" + ], + [ + 32, + 1, + 6, + 6, + 0, + "INT" + ], + [ + 33, + 1, + 7, + 6, + 1, + "INT" + ], + [ + 34, + 1, + 8, + 7, + 4, + "INT" + ], + [ + 35, + 1, + 4, + 7, + 5, + "INT" + ], + [ + 36, + 1, + 5, + 7, + 6, + "FLOAT" + ], + [ + 37, + 3, + 0, + 7, + 0, + "MODEL" + ], + [ + 38, + 4, + 0, + 7, + 1, + "CONDITIONING" + ], + [ + 39, + 5, + 0, + 7, + 2, + "CONDITIONING" + ], + [ + 40, + 6, + 0, + 7, + 3, + "LATENT" + ], + [ + 41, + 7, + 0, + 8, + 0, + "LATENT" + ], + [ + 42, + 2, + 2, + 8, + 1, + "VAE" + ], + [ + 43, + 8, + 0, + 9, + 0, + "IMAGE" + ], + [ + 44, + 1, + 11, + 10, + 0, + "STRING" + ], + [ + 45, + 1, + 0, + 11, + 0, + "STRING" + ], + [ + 47, + 1, + 12, + 13, + 0, + "STRING" + ], + [ + 48, + 1, + 13, + 14, + 0, + "STRING" + ] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 0.5930499084847113, + "offset": [ + 1623.436749086202, + 823.3015688105553 + ] + }, + "frontendVersion": "1.37.11", + "VHS_latentpreview": false, + "VHS_latentpreviewrate": 0, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true + }, + "version": 0.4 +} \ No newline at end of file diff --git a/workflows/ollama_auto_model_.json b/workflows/ollama_auto_model_.json new file mode 100644 index 0000000..6f23100 --- /dev/null +++ b/workflows/ollama_auto_model_.json @@ -0,0 +1,1054 @@ +{ + "id": "00000000-0000-0000-0000-000000000000", + "revision": 0, + "last_node_id": 19, + "last_link_id": 52, + "nodes": [ + { + "id": 8, + "type": "VAEDecode", + "pos": [ + 2303.30234375, + 130 + ], + "size": [ + 140, + 46 + ], + "flags": {}, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 41 + }, + { + "name": "vae", + "type": "VAE", + "link": 42 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 43 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "VAEDecode" + }, + "widgets_values": [] + }, + { + "id": 9, + "type": "SaveImage", + "pos": [ + 2543.30234375, + 130 + ], + "size": [ + 270, + 270 + ], + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 43 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0" + }, + "widgets_values": [ + "ollama_auto" + ] + }, + { + "id": 4, + "type": "CLIPTextEncode", + "pos": [ + 1391.7685886264053, + 131.73057313014968 + ], + "size": [ + 400, + 200 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 29 + }, + { + "name": "text", + "type": "STRING", + "widget": { + "name": "text" + }, + "link": 28 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 38 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "" + ] + }, + { + "id": 3, + "type": "DynamicLoraStack", + "pos": [ + 1087.5303675720959, + -30.078014538852617 + ], + "size": [ + 270, + 146 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 24 + }, + { + "name": "clip", + "type": "CLIP", + "link": 25 + }, + { + "name": "loras", + "type": "STRING", + "widget": { + "name": "loras" + }, + "link": 26 + }, + { + "name": "lora_strengths", + "type": "STRING", + "widget": { + "name": "lora_strengths" + }, + "link": 27 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 37 + ] + }, + { + "name": "CLIP", + "type": "CLIP", + "links": [ + 29, + 31 + ] + }, + { + "name": "debug_info", + "type": "STRING", + "links": null + } + ], + "properties": { + "Node name for S&R": "DynamicLoraStack" + }, + "widgets_values": [ + "", + "", + "" + ] + }, + { + "id": 6, + "type": "EmptyLatentImage", + "pos": [ + 653.1736472060104, + 248.8277805706228 + ], + "size": [ + 270, + 106 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "width", + "type": "INT", + "widget": { + "name": "width" + }, + "link": 32 + }, + { + "name": "height", + "type": "INT", + "widget": { + "name": "height" + }, + "link": 33 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 40 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "EmptyLatentImage" + }, + "widgets_values": [ + 512, + 512, + 1 + ] + }, + { + "id": 7, + "type": "KSampler", + "pos": [ + 1934.2541589715825, + 187.10891329494214 + ], + "size": [ + 270, + 262 + ], + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 37 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 38 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 39 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 40 + }, + { + "name": "seed", + "type": "INT", + "widget": { + "name": "seed" + }, + "link": 34 + }, + { + "name": "steps", + "type": "INT", + "widget": { + "name": "steps" + }, + "link": 35 + }, + { + "name": "cfg", + "type": "FLOAT", + "widget": { + "name": "cfg" + }, + "link": 36 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 41 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 537717937623601, + "randomize", + 20, + 8, + "euler", + "normal", + 1 + ] + }, + { + "id": 5, + "type": "CLIPTextEncode", + "pos": [ + 1433.30234375, + 460 + ], + "size": [ + 400, + 200 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 31 + }, + { + "name": "text", + "type": "STRING", + "widget": { + "name": "text" + }, + "link": 30 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 39 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "" + ] + }, + { + "id": 10, + "type": "OllamaDebugInfo", + "pos": [ + 631.3720013304425, + 494.3530839150838 + ], + "size": [ + 419.316538251496, + 321.9995165419782 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "text", + "type": "STRING", + "link": 44 + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": null + } + ], + "properties": { + "Node name for S&R": "OllamaDebugInfo" + }, + "widgets_values": [] + }, + { + "id": 11, + "type": "PreviewAny", + "pos": [ + -782.8394895054017, + -557.6333493757888 + ], + "size": [ + 472.7961826788911, + 313.6265408674249 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "source", + "type": "*", + "link": 45 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "PreviewAny" + }, + "widgets_values": [ + null, + null, + null + ] + }, + { + "id": 17, + "type": "PreviewAny", + "pos": [ + 872.6586655661922, + -444.9324869670516 + ], + "size": [ + 472.7961826788911, + 313.6265408674249 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "source", + "type": "*", + "link": 50 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "PreviewAny" + }, + "widgets_values": [ + null, + null, + null + ] + }, + { + "id": 18, + "type": "PreviewAny", + "pos": [ + -262.91400269486513, + -559.3692310578996 + ], + "size": [ + 472.7961826788911, + 313.6265408674249 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "source", + "type": "*", + "link": 51 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "PreviewAny" + }, + "widgets_values": [ + null, + null, + null + ] + }, + { + "id": 14, + "type": "PreviewAny", + "pos": [ + -762.9004350804421, + 1060.3269456603005 + ], + "size": [ + 472.7961826788911, + 313.6265408674249 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "source", + "type": "*", + "link": 48 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "PreviewAny" + }, + "widgets_values": [ + null, + null, + null + ] + }, + { + "id": 1, + "type": "OllamaPromptPlanner", + "pos": [ + -1341.773785664658, + 1.8356532851194416 + ], + "size": [ + 400, + 522 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "checkpoint", + "type": "STRING", + "links": [ + 23, + 45 + ] + }, + { + "name": "loras", + "type": "STRING", + "links": [ + 26 + ] + }, + { + "name": "lora_strengths", + "type": "STRING", + "links": [ + 27 + ] + }, + { + "name": "model_type", + "type": "STRING", + "links": [ + 51 + ] + }, + { + "name": "steps", + "type": "INT", + "links": [ + 35 + ] + }, + { + "name": "cfg", + "type": "FLOAT", + "links": [ + 36 + ] + }, + { + "name": "width", + "type": "INT", + "links": [ + 32 + ] + }, + { + "name": "height", + "type": "INT", + "links": [ + 33 + ] + }, + { + "name": "seed", + "type": "INT", + "links": [ + 34 + ] + }, + { + "name": "positive_prompt", + "type": "STRING", + "links": [ + 28 + ] + }, + { + "name": "negative_prompt", + "type": "STRING", + "links": [ + 30 + ] + }, + { + "name": "plan_json", + "type": "STRING", + "links": [ + 44, + 52 + ] + }, + { + "name": "vae_name", + "type": "STRING", + "links": [ + 47 + ] + }, + { + "name": "clip_name", + "type": "STRING", + "links": [ + 48 + ] + } + ], + "properties": { + "Node name for S&R": "OllamaPromptPlanner" + }, + "widgets_values": [ + "Photorealistic Portrait", + "qwen2.5:7b", + "model_registry.json", + "auto", + "blurry, extra fingers, deformed", + "localhost", + 11434 + ] + }, + { + "id": 2, + "type": "DynamicCheckpointLoader", + "pos": [ + -164.23436084774724, + -171.12413155053335 + ], + "size": [ + 363.30234375, + 238 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "checkpoint", + "type": "STRING", + "widget": { + "name": "checkpoint" + }, + "link": 23 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 24 + ] + }, + { + "name": "CLIP", + "type": "CLIP", + "links": [ + 25 + ] + }, + { + "name": "VAE", + "type": "VAE", + "links": [ + 42 + ] + }, + { + "name": "debug_info", + "type": "STRING", + "links": [ + 50 + ] + } + ], + "properties": { + "Node name for S&R": "DynamicCheckpointLoader" + }, + "widgets_values": [ + "", + "auto", + "model_registry.json", + "I:\\models", + "", + "" + ] + }, + { + "id": 13, + "type": "PreviewAny", + "pos": [ + -776.4478595062302, + 648.9521589972428 + ], + "size": [ + 472.7961826788911, + 313.6265408674249 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "source", + "type": "*", + "link": 47 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "PreviewAny" + }, + "widgets_values": [ + null, + null, + null + ] + }, + { + "id": 19, + "type": "PreviewAny", + "pos": [ + -221.67175396217817, + 648.6899723995068 + ], + "size": [ + 473.7479979004735, + 563.0021289220049 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "source", + "type": "*", + "link": 52 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.12.0", + "Node name for S&R": "PreviewAny" + }, + "widgets_values": [ + null, + null, + null + ] + } + ], + "links": [ + [ + 23, + 1, + 0, + 2, + 0, + "STRING" + ], + [ + 24, + 2, + 0, + 3, + 0, + "MODEL" + ], + [ + 25, + 2, + 1, + 3, + 1, + "CLIP" + ], + [ + 26, + 1, + 1, + 3, + 2, + "STRING" + ], + [ + 27, + 1, + 2, + 3, + 3, + "STRING" + ], + [ + 28, + 1, + 9, + 4, + 1, + "STRING" + ], + [ + 29, + 3, + 1, + 4, + 0, + "CLIP" + ], + [ + 30, + 1, + 10, + 5, + 1, + "STRING" + ], + [ + 31, + 3, + 1, + 5, + 0, + "CLIP" + ], + [ + 32, + 1, + 6, + 6, + 0, + "INT" + ], + [ + 33, + 1, + 7, + 6, + 1, + "INT" + ], + [ + 34, + 1, + 8, + 7, + 4, + "INT" + ], + [ + 35, + 1, + 4, + 7, + 5, + "INT" + ], + [ + 36, + 1, + 5, + 7, + 6, + "FLOAT" + ], + [ + 37, + 3, + 0, + 7, + 0, + "MODEL" + ], + [ + 38, + 4, + 0, + 7, + 1, + "CONDITIONING" + ], + [ + 39, + 5, + 0, + 7, + 2, + "CONDITIONING" + ], + [ + 40, + 6, + 0, + 7, + 3, + "LATENT" + ], + [ + 41, + 7, + 0, + 8, + 0, + "LATENT" + ], + [ + 42, + 2, + 2, + 8, + 1, + "VAE" + ], + [ + 43, + 8, + 0, + 9, + 0, + "IMAGE" + ], + [ + 44, + 1, + 11, + 10, + 0, + "STRING" + ], + [ + 45, + 1, + 0, + 11, + 0, + "STRING" + ], + [ + 47, + 1, + 12, + 13, + 0, + "STRING" + ], + [ + 48, + 1, + 13, + 14, + 0, + "STRING" + ], + [ + 50, + 2, + 3, + 17, + 0, + "STRING" + ], + [ + 51, + 1, + 3, + 18, + 0, + "STRING" + ], + [ + 52, + 1, + 11, + 19, + 0, + "STRING" + ] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 0.6523548993331835, + "offset": [ + 1709.8961132606705, + 738.1445270352696 + ] + }, + "workflowRendererVersion": "LG", + "frontendVersion": "1.37.11", + "VHS_latentpreview": false, + "VHS_latentpreviewrate": 0, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true + }, + "version": 0.4 +} \ No newline at end of file diff --git a/workflows/ollama_img2img_style.json b/workflows/ollama_img2img_style.json new file mode 100644 index 0000000..6979d16 --- /dev/null +++ b/workflows/ollama_img2img_style.json @@ -0,0 +1,203 @@ +{ + "1": { + "inputs": { + "image": "example.png" + }, + "class_type": "LoadImage" + }, + "2": { + "inputs": { + "image": [ + "1", + 0 + ], + "prompt": "match the style but improve detail and color grading", + "ollama_model": "llava:7b", + "registry_path": "model_registry.json", + "task_hint": "img2img", + "user_negative": "blurry, deformed, watermark", + "ollama_host": "localhost", + "ollama_port": 11434 + }, + "class_type": "OllamaVisionStylePlanner" + }, + "3": { + "inputs": { + "checkpoint": [ + "2", + 0 + ], + "model_type": "auto", + "registry_path": "model_registry.json", + "custom_path": "", + "vae_override": "", + "clip_override": "" + }, + "class_type": "DynamicCheckpointLoader" + }, + "4": { + "inputs": { + "model": [ + "3", + 0 + ], + "clip": [ + "3", + 1 + ], + "loras": [ + "2", + 1 + ], + "lora_strengths": [ + "2", + 2 + ], + "custom_path": "" + }, + "class_type": "DynamicLoraStack" + }, + "5": { + "inputs": { + "text": [ + "2", + 9 + ], + "clip": [ + "4", + 1 + ] + }, + "class_type": "CLIPTextEncode" + }, + "6": { + "inputs": { + "text": [ + "2", + 10 + ], + "clip": [ + "4", + 1 + ] + }, + "class_type": "CLIPTextEncode" + }, + "7": { + "inputs": { + "samples": [ + "1", + 0 + ], + "vae": [ + "3", + 2 + ] + }, + "class_type": "VAEEncode" + }, + "8": { + "inputs": { + "seed": [ + "2", + 8 + ], + "steps": [ + "2", + 4 + ], + "cfg": [ + "2", + 5 + ], + "sampler_name": "euler", + "scheduler": "normal", + "denoise": [ + "2", + 15 + ], + "model": [ + "4", + 0 + ], + "positive": [ + "12", + 0 + ], + "negative": [ + "12", + 1 + ], + "latent_image": [ + "7", + 0 + ] + }, + "class_type": "KSampler" + }, + "12": { + "inputs": { + "positive": [ + "5", + 0 + ], + "negative": [ + "6", + 0 + ], + "controlnet_name": [ + "2", + 18 + ], + "strength": [ + "2", + 19 + ], + "start_percent": [ + "2", + 20 + ], + "end_percent": [ + "2", + 21 + ], + "image": [ + "1", + 0 + ] + }, + "class_type": "OptionalControlNetApply" + }, + "9": { + "inputs": { + "samples": [ + "8", + 0 + ], + "vae": [ + "3", + 2 + ] + }, + "class_type": "VAEDecode" + }, + "10": { + "inputs": { + "filename_prefix": "ollama_img2img", + "images": [ + "9", + 0 + ] + }, + "class_type": "SaveImage" + }, + "11": { + "inputs": { + "text": [ + "2", + 11 + ] + }, + "class_type": "OllamaDebugInfo" + } +} diff --git a/workflows/ollama_router_full.json b/workflows/ollama_router_full.json new file mode 100644 index 0000000..a6ae41d --- /dev/null +++ b/workflows/ollama_router_full.json @@ -0,0 +1,325 @@ +{ + "1": { + "inputs": { + "prompt": "cinematic portrait, moody lighting, high detail", + "ollama_model": "qwen2.5:7b", + "registry_path": "model_registry.json", + "task_hint": "auto", + "user_negative": "blurry, extra fingers, deformed", + "ollama_host": "localhost", + "ollama_port": 11434 + }, + "class_type": "OllamaPromptPlanner" + }, + "2": { + "inputs": { + "width": [ + "1", + 6 + ], + "height": [ + "1", + 7 + ], + "batch_size": 1, + "color": 0 + }, + "class_type": "EmptyImage" + }, + "3": { + "inputs": { + "checkpoint": [ + "1", + 0 + ], + "model_type": "auto", + "registry_path": "model_registry.json", + "custom_path": "", + "vae_override": [ + "1", + 12 + ], + "clip_override": [ + "1", + 13 + ] + }, + "class_type": "DynamicCheckpointLoader" + }, + "4": { + "inputs": { + "model": [ + "3", + 0 + ], + "clip": [ + "3", + 1 + ], + "loras": [ + "1", + 1 + ], + "lora_strengths": [ + "1", + 2 + ], + "custom_path": "" + }, + "class_type": "DynamicLoraStack" + }, + "5": { + "inputs": { + "text": [ + "1", + 9 + ], + "clip": [ + "4", + 1 + ] + }, + "class_type": "CLIPTextEncode" + }, + "6": { + "inputs": { + "text": [ + "1", + 10 + ], + "clip": [ + "4", + 1 + ] + }, + "class_type": "CLIPTextEncode" + }, + "7": { + "inputs": { + "positive": [ + "5", + 0 + ], + "negative": [ + "6", + 0 + ], + "controlnet_name": [ + "1", + 18 + ], + "strength": [ + "1", + 19 + ], + "start_percent": [ + "1", + 20 + ], + "end_percent": [ + "1", + 21 + ], + "image": [ + "2", + 0 + ] + }, + "class_type": "OptionalControlNetApply" + }, + "8": { + "inputs": { + "width": [ + "1", + 6 + ], + "height": [ + "1", + 7 + ], + "batch_size": 1 + }, + "class_type": "EmptyLatentImage" + }, + "9": { + "inputs": { + "seed": [ + "1", + 8 + ], + "steps": [ + "1", + 4 + ], + "cfg": [ + "1", + 5 + ], + "sampler_name": "euler", + "scheduler": "normal", + "denoise": [ + "1", + 15 + ], + "model": [ + "4", + 0 + ], + "positive": [ + "7", + 0 + ], + "negative": [ + "7", + 1 + ], + "latent_image": [ + "8", + 0 + ] + }, + "class_type": "KSampler" + }, + "10": { + "inputs": { + "checkpoint": [ + "1", + 17 + ], + "model_type": "sdxl", + "registry_path": "model_registry.json", + "custom_path": "", + "vae_override": "", + "clip_override": "" + }, + "class_type": "DynamicCheckpointLoader" + }, + "11": { + "inputs": { + "text": [ + "1", + 9 + ], + "clip": [ + "10", + 1 + ] + }, + "class_type": "CLIPTextEncode" + }, + "12": { + "inputs": { + "text": [ + "1", + 10 + ], + "clip": [ + "10", + 1 + ] + }, + "class_type": "CLIPTextEncode" + }, + "13": { + "inputs": { + "seed": [ + "1", + 8 + ], + "steps": [ + "1", + 4 + ], + "cfg": [ + "1", + 5 + ], + "sampler_name": "euler", + "scheduler": "normal", + "denoise": 0.3, + "model": [ + "10", + 0 + ], + "positive": [ + "11", + 0 + ], + "negative": [ + "12", + 0 + ], + "latent_image": [ + "9", + 0 + ] + }, + "class_type": "KSampler" + }, + "14": { + "inputs": { + "use_b": [ + "1", + 16 + ], + "a": [ + "9", + 0 + ], + "b": [ + "13", + 0 + ] + }, + "class_type": "RouteLatentByBool" + }, + "15": { + "inputs": { + "use_b": [ + "1", + 16 + ], + "a": [ + "3", + 2 + ], + "b": [ + "10", + 2 + ] + }, + "class_type": "RouteVaeByBool" + }, + "16": { + "inputs": { + "samples": [ + "14", + 0 + ], + "vae": [ + "15", + 0 + ] + }, + "class_type": "VAEDecode" + }, + "17": { + "inputs": { + "filename_prefix": "ollama_full", + "images": [ + "16", + 0 + ] + }, + "class_type": "SaveImage" + }, + "18": { + "inputs": { + "text": [ + "1", + 11 + ] + }, + "class_type": "OllamaDebugInfo" + } +} diff --git a/workflows/ollama_universal_fallback.json b/workflows/ollama_universal_fallback.json new file mode 100644 index 0000000..163d2b3 --- /dev/null +++ b/workflows/ollama_universal_fallback.json @@ -0,0 +1,284 @@ +{ + "last_node_id": 15, + "last_link_id": 30, + "nodes": [ + { + "id": 1, + "type": "OllamaPromptPlanner", + "pos": [100, 100], + "size": [400, 460], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { "name": "checkpoint", "type": "STRING", "links": [1], "slot_index": 0 }, + { "name": "loras", "type": "STRING", "links": [7], "slot_index": 1 }, + { "name": "lora_strengths", "type": "STRING", "links": [8], "slot_index": 2 }, + { "name": "model_type", "type": "STRING", "links": [2], "slot_index": 3 }, + { "name": "steps", "type": "INT", "links": [20], "slot_index": 4 }, + { "name": "cfg", "type": "FLOAT", "links": [21], "slot_index": 5 }, + { "name": "width", "type": "INT", "links": [22], "slot_index": 6 }, + { "name": "height", "type": "INT", "links": [23], "slot_index": 7 }, + { "name": "seed", "type": "INT", "links": [24], "slot_index": 8 }, + { "name": "positive_prompt", "type": "STRING", "links": [11], "slot_index": 9 }, + { "name": "negative_prompt", "type": "STRING", "links": [12], "slot_index": 10 }, + { "name": "plan_json", "type": "STRING", "links": [25], "slot_index": 11 }, + { "name": "vae_name", "type": "STRING", "links": [3], "slot_index": 12 }, + { "name": "clip_name", "type": "STRING", "links": [4], "slot_index": 13 } + ], + "properties": { + "Node name for S&R": "OllamaPromptPlanner" + }, + "widgets_values": [ + "A highly detailed photograph of a futuristic city with flying cars, neon lights, cyberpunk aesthetic, rain slicked streets", + "qwen2.5:7b", + "model_registry.json", + "auto", + "blurry, low quality, watermark, text, ugly, deformed", + "localhost", + 11434 + ] + }, + { + "id": 2, + "type": "DynamicCheckpointLoader", + "pos": [600, 100], + "size": [400, 200], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { "name": "checkpoint", "type": "STRING", "link": 1 }, + { "name": "model_type", "type": "STRING", "link": 2 }, + { "name": "vae_override", "type": "STRING", "link": 3 }, + { "name": "clip_override", "type": "STRING", "link": 4 } + ], + "outputs": [ + { "name": "MODEL", "type": "MODEL", "links": [5], "slot_index": 0 }, + { "name": "CLIP", "type": "CLIP", "links": [6], "slot_index": 1 }, + { "name": "VAE", "type": "VAE", "links": [18], "slot_index": 2 } + ], + "properties": { + "Node name for S&R": "DynamicCheckpointLoader" + }, + "widgets_values": [ + "model_registry.json", + "" + ] + }, + { + "id": 3, + "type": "DynamicLoraStack", + "pos": [1100, 100], + "size": [400, 200], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { "name": "model", "type": "MODEL", "link": 5 }, + { "name": "clip", "type": "CLIP", "link": 6 }, + { "name": "loras", "type": "STRING", "link": 7 }, + { "name": "lora_strengths", "type": "STRING", "link": 8 } + ], + "outputs": [ + { "name": "MODEL", "type": "MODEL", "links": [13], "slot_index": 0 }, + { "name": "CLIP", "type": "CLIP", "links": [9, 10], "slot_index": 1 } + ], + "properties": { + "Node name for S&R": "DynamicLoraStack" + }, + "widgets_values": [ + "" + ] + }, + { + "id": 4, + "type": "CLIPTextEncode", + "pos": [1600, 50], + "size": [400, 200], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { "name": "clip", "type": "CLIP", "link": 9 }, + { "name": "text", "type": "STRING", "link": 11 } + ], + "outputs": [ + { "name": "CONDITIONING", "type": "CONDITIONING", "links": [14], "slot_index": 0 } + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "" + ] + }, + { + "id": 5, + "type": "CLIPTextEncode", + "pos": [1600, 300], + "size": [400, 200], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { "name": "clip", "type": "CLIP", "link": 10 }, + { "name": "text", "type": "STRING", "link": 12 } + ], + "outputs": [ + { "name": "CONDITIONING", "type": "CONDITIONING", "links": [15], "slot_index": 0 } + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "" + ] + }, + { + "id": 6, + "type": "EmptyLatentImage", + "pos": [1600, 550], + "size": [315, 106], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { "name": "width", "type": "INT", "link": 22 }, + { "name": "height", "type": "INT", "link": 23 } + ], + "outputs": [ + { "name": "LATENT", "type": "LATENT", "links": [16], "slot_index": 0 } + ], + "properties": { + "Node name for S&R": "EmptyLatentImage" + }, + "widgets_values": [ + 512, + 512, + 1 + ] + }, + { + "id": 7, + "type": "KSampler", + "pos": [2100, 100], + "size": [315, 474], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { "name": "model", "type": "MODEL", "link": 13 }, + { "name": "positive", "type": "CONDITIONING", "link": 14 }, + { "name": "negative", "type": "CONDITIONING", "link": 15 }, + { "name": "latent_image", "type": "LATENT", "link": 16 }, + { "name": "seed", "type": "INT", "link": 24 }, + { "name": "steps", "type": "INT", "link": 20 }, + { "name": "cfg", "type": "FLOAT", "link": 21 } + ], + "outputs": [ + { "name": "LATENT", "type": "LATENT", "links": [17], "slot_index": 0 } + ], + "properties": { + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 0, + "fixed", + 20, + 8, + "euler", + "normal", + 1 + ] + }, + { + "id": 8, + "type": "VAEDecode", + "pos": [2500, 100], + "size": [210, 46], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { "name": "samples", "type": "LATENT", "link": 17 }, + { "name": "vae", "type": "VAE", "link": 18 } + ], + "outputs": [ + { "name": "IMAGE", "type": "IMAGE", "links": [19], "slot_index": 0 } + ], + "properties": { + "Node name for S&R": "VAEDecode" + } + }, + { + "id": 9, + "type": "SaveImage", + "pos": [2800, 100], + "size": [500, 600], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { "name": "images", "type": "IMAGE", "link": 19 } + ], + "outputs": [], + "properties": { + "Node name for S&R": "SaveImage" + }, + "widgets_values": [ + "ComfyUI" + ] + }, + { + "id": 10, + "type": "OllamaDebugInfo", + "pos": [100, 650], + "size": [400, 200], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { "name": "text", "type": "STRING", "link": 25 } + ], + "outputs": [ + { "name": "STRING", "type": "STRING", "links": [], "slot_index": 0 } + ], + "properties": { + "Node name for S&R": "OllamaDebugInfo" + } + } + ], + "links": [ + [1, 1, 0, 2, 0, "STRING"], + [2, 1, 3, 2, 1, "STRING"], + [3, 1, 12, 2, 2, "STRING"], + [4, 1, 13, 2, 3, "STRING"], + [5, 2, 0, 3, 0, "MODEL"], + [6, 2, 1, 3, 1, "CLIP"], + [7, 1, 1, 3, 2, "STRING"], + [8, 1, 2, 3, 3, "STRING"], + [9, 3, 1, 4, 0, "CLIP"], + [10, 3, 1, 5, 0, "CLIP"], + [11, 1, 9, 4, 1, "STRING"], + [12, 1, 10, 5, 1, "STRING"], + [13, 3, 0, 7, 0, "MODEL"], + [14, 4, 0, 7, 1, "CONDITIONING"], + [15, 5, 0, 7, 2, "CONDITIONING"], + [16, 6, 0, 7, 3, "LATENT"], + [17, 7, 0, 8, 0, "LATENT"], + [18, 2, 2, 8, 1, "VAE"], + [19, 8, 0, 9, 0, "IMAGE"], + [20, 1, 4, 7, 5, "INT"], + [21, 1, 5, 7, 6, "FLOAT"], + [22, 1, 6, 6, 0, "INT"], + [23, 1, 7, 6, 1, "INT"], + [24, 1, 8, 7, 4, "INT"], + [25, 1, 11, 10, 0, "STRING"] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +}