Initial commit

This commit is contained in:
Geekatplay Dev
2026-02-07 10:47:39 -07:00
commit 44afa76a06
15 changed files with 8753 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
# Python
__pycache__/
*.pyc
# Editors
.vscode/
.idea/
# OS
.DS_Store
Thumbs.db
+40
View File
@@ -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"]
+279
View File
@@ -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.")
+60
View File
@@ -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
+39
View File
@@ -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"
}
]
}
+2610
View File
File diff suppressed because it is too large Load Diff
+779
View File
@@ -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}",)
+950
View File
@@ -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,)}
File diff suppressed because it is too large Load Diff
+26
View File
@@ -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}",)
+915
View File
@@ -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
}
File diff suppressed because it is too large Load Diff
+203
View File
@@ -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"
}
}
+325
View File
@@ -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"
}
}
+284
View File
@@ -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
}