Initial commit
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Editors
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
+40
@@ -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"]
|
||||
@@ -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
@@ -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
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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}",)
|
||||
@@ -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
@@ -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}",)
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user