Merge remote changes, keep our modifications for metadata
Browse files- README.md +154 -152
- __pycache__/app.cpython-311.pyc +0 -0
- app.py +106 -105
- chain_injectors/flux1_ipadapter_injector.py +45 -45
- chain_injectors/sd3_ipadapter_injector.py +65 -65
- comfy_integration/__pycache__/__init__.cpython-311.pyc +0 -0
- comfy_integration/__pycache__/setup.cpython-311.pyc +0 -0
- comfy_integration/setup.py +191 -98
- core/__pycache__/__init__.cpython-311.pyc +0 -0
- core/__pycache__/settings.cpython-311.pyc +0 -0
- core/pipelines/base_pipeline.py +65 -64
- core/pipelines/sd_image_pipeline.py +45 -53
- core/pipelines/workflow_executor.py +12 -11
- core/pipelines/workflow_recipes/_partials/_base_sampler_sd.yaml +29 -36
- core/settings.py +230 -206
- requirements.txt +46 -46
- ui/layout.py +130 -47
- ui/shared/ui_components.py +672 -671
- utils/__pycache__/__init__.cpython-311.pyc +0 -0
- utils/__pycache__/app_utils.cpython-311.pyc +0 -0
README.md
CHANGED
|
@@ -1,152 +1,154 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: ImageGen
|
| 3 |
-
emoji: πΌ
|
| 4 |
-
colorFrom: purple
|
| 5 |
-
colorTo: red
|
| 6 |
-
sdk: gradio
|
| 7 |
-
sdk_version: "5.50.0"
|
| 8 |
-
app_file: app.py
|
| 9 |
-
python_version: 3.12
|
| 10 |
-
short_description: Multi-task image generator with dynamic, chainable workflows
|
| 11 |
-
pinned: true
|
| 12 |
-
models:
|
| 13 |
-
# This Space supports a wide variety of image generation pipelines. To maintain transparency, credit the original creators, and help users explore the Hugging Face ecosystem, we list and link several types of models in our metadata:
|
| 14 |
-
# 1. **Directly Run Models:** Models and checkpoints actively loaded by our pipelines (configured via `yaml/file_list.yaml`).
|
| 15 |
-
# 2. **Upstream Base Models:** The original foundation architectures from which our optimized ports, quantized versions, or wrappers are derived.
|
| 16 |
-
# Directly Run Models
|
| 17 |
-
- AiAF/Illustrious-XL-v0.1.safetensors
|
| 18 |
-
- alibaba-pai/Z-Image-Turbo-Fun-Controlnet-Union-2.1
|
| 19 |
-
- black-forest-labs/FLUX.1-Redux-dev
|
| 20 |
-
- black-forest-labs/FLUX.2-dev-NVFP4
|
| 21 |
-
- black-forest-labs/FLUX.2-klein-4b-nvfp4
|
| 22 |
-
- black-forest-labs/FLUX.2-klein-9b-nvfp4
|
| 23 |
-
- black-forest-labs/FLUX.2-klein-9b-kv-fp8
|
| 24 |
-
- black-forest-labs/FLUX.2-klein-base-4b-nvfp4
|
| 25 |
-
- black-forest-labs/FLUX.2-klein-base-9b-nvfp4
|
| 26 |
-
- bluepen5805/4nima_pencil-XL
|
| 27 |
-
- bluepen5805/anima-models
|
| 28 |
-
- bluepen5805/anima_pencil-XL
|
| 29 |
-
- bluepen5805/blue_pencil-XL
|
| 30 |
-
- bluepen5805/illustrious_pencil-XL
|
| 31 |
-
- bluepen5805/mellow_pencil-XL
|
| 32 |
-
- bluepen5805/noob_v_pencil-XL
|
| 33 |
-
- bluepen5805/pony_pencil-XL
|
| 34 |
-
- cagliostrolab/animagine-xl-3.1
|
| 35 |
-
- cagliostrolab/animagine-xl-4.0
|
| 36 |
-
- ChenkinNoob/ChenkinNoob-XL-V0.5
|
| 37 |
-
- circlestone-labs/Anima
|
| 38 |
-
- Clybius/Chroma-fp8-scaled
|
| 39 |
-
- comfyanonymous/ControlNet-v1-1_fp16_safetensors
|
| 40 |
-
- comfyanonymous/cosmos_1.0_text_encoder_and_VAE_ComfyUI
|
| 41 |
-
- comfyanonymous/flux_text_encoders
|
| 42 |
-
- Comfy-Org/Boogu-Image
|
| 43 |
-
- Comfy-Org/ERNIE-Image
|
| 44 |
-
- Comfy-Org/FLUX.1-Krea-dev_ComfyUI
|
| 45 |
-
- Comfy-Org/flux2-dev
|
| 46 |
-
- Comfy-Org/HiDream-I1_ComfyUI
|
| 47 |
-
- Comfy-Org/HiDream-O1-Image
|
| 48 |
-
- Comfy-Org/HunyuanImage_2.1_ComfyUI
|
| 49 |
-
- Comfy-Org/Ideogram-4
|
| 50 |
-
- Comfy-Org/Krea-2
|
| 51 |
-
- Comfy-Org/Lens
|
| 52 |
-
- Comfy-Org/LongCat-Image
|
| 53 |
-
- Comfy-Org/Lumina_Image_2.0_Repackaged
|
| 54 |
-
- Comfy-Org/NewBie-image-Exp0.1_repackaged
|
| 55 |
-
- Comfy-Org/Omnigen2_ComfyUI_repackaged
|
| 56 |
-
- Comfy-Org/Ovis-Image
|
| 57 |
-
- Comfy-Org/PixelDiT
|
| 58 |
-
- Comfy-Org/Qwen-Image_ComfyUI
|
| 59 |
-
- Comfy-Org/sigclip_vision_384
|
| 60 |
-
- Comfy-Org/stable-diffusion-3.5-fp8
|
| 61 |
-
- Comfy-Org/vae-text-encorder-for-flux-klein-4b
|
| 62 |
-
- Comfy-Org/vae-text-encorder-for-flux-klein-9b
|
| 63 |
-
- Comfy-Org/Wan_2.1_ComfyUI_repackaged
|
| 64 |
-
- Comfy-Org/z_image
|
| 65 |
-
- Comfy-Org/z_image_turbo
|
| 66 |
-
- cyberdelia/CyberRealisticPony
|
| 67 |
-
- diffusionmodels1254ani/hassakuAnima
|
| 68 |
-
- diffusionmodels1254ani/kirazuriAnima_v30AnimaBase1
|
| 69 |
-
- diffusionmodels1254ani/waiANIMA
|
| 70 |
-
- duongve/AnimaYume
|
| 71 |
-
- Eugeoter/noob-sdxl-controlnet-canny
|
| 72 |
-
- Eugeoter/noob-sdxl-controlnet-depth
|
| 73 |
-
- Eugeoter/noob-sdxl-controlnet-lineart_anime
|
| 74 |
-
- Eugeoter/noob-sdxl-controlnet-lineart_realistic
|
| 75 |
-
- Eugeoter/noob-sdxl-controlnet-manga_line
|
| 76 |
-
- Eugeoter/noob-sdxl-controlnet-normal
|
| 77 |
-
- Eugeoter/noob-sdxl-controlnet-softedge_hed
|
| 78 |
-
- Eugeoter/noob-sdxl-controlnet-tile
|
| 79 |
-
- frankjoshua/novaAnimeXL_ilV180
|
| 80 |
-
- h94/IP-Adapter
|
| 81 |
-
- h94/IP-Adapter-FaceID
|
| 82 |
-
- InstantX/FLUX.1-dev-IP-Adapter
|
| 83 |
-
- InstantX/Qwen-Image-ControlNet-Inpainting
|
| 84 |
-
- InstantX/Qwen-Image-ControlNet-Union
|
| 85 |
-
- InstantX/SD3.5-Large-IP-Adapter
|
| 86 |
-
- kandinskylab/Kandinsky-5.0-T2I-Lite
|
| 87 |
-
- Kijai/flux-fp8
|
| 88 |
-
- kohya-ss/Anima-LLLite
|
| 89 |
-
- Laxhar/noob_openpose
|
| 90 |
-
- Laxhar/noobai-XL-1.1
|
| 91 |
-
- Laxhar/noobai-XL-Vpred-1.0
|
| 92 |
-
- licyk/sd_control_collection
|
| 93 |
-
- LyliaEngine/Pony_Diffusion_V6_XL
|
| 94 |
-
- MIC-Lab/illustriousXLv0.1_controlnet
|
| 95 |
-
- MIC-Lab/illustriousXLv1.1_controlnet
|
| 96 |
-
- misri/hassakuXLIllustrious_v30
|
| 97 |
-
- nvidia/Cosmos-Predict2-2B-Text2Image
|
| 98 |
-
- nvidia/Cosmos-Predict2-14B-Text2Image
|
| 99 |
-
- OnomaAIResearch/Illustrious-XL-v1.0
|
| 100 |
-
- OnomaAIResearch/Illustrious-XL-v1.1
|
| 101 |
-
- OnomaAIResearch/Illustrious-XL-v2.0
|
| 102 |
-
- RedRayz/hikari_noob_v-pred_1.2.4
|
| 103 |
-
- Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro-2.0
|
| 104 |
-
- silveroxides/Chroma1-Radiance-fp8-scaled
|
| 105 |
-
- stabilityai/stable-diffusion-3.5-controlnets
|
| 106 |
-
- stabilityai/stable-diffusion-xl-base-1.0
|
| 107 |
-
- stable-diffusion-v1-5/stable-diffusion-v1-5
|
| 108 |
-
- Wenaka/NoobAI_XL_Inpainting_ControlNet_Full
|
| 109 |
-
- xinsir/anime-painter
|
| 110 |
-
- xinsir/controlnet-canny-sdxl-1.0
|
| 111 |
-
- xinsir/controlnet-depth-sdxl-1.0
|
| 112 |
-
- xinsir/controlnet-openpose-sdxl-1.0
|
| 113 |
-
- xinsir/controlnet-scribble-sdxl-1.0
|
| 114 |
-
- xinsir/controlnet-tile-sdxl-1.0
|
| 115 |
-
- xinsir/controlnet-union-sdxl-1.0
|
| 116 |
-
- XLabs-AI/flux-controlnet-collections
|
| 117 |
-
- zhenshipo/waiIllustriousSDXL_v170
|
| 118 |
-
# Upstream Base Models
|
| 119 |
-
- AIDC-AI/Ovis-Image-7B
|
| 120 |
-
- Alpha-VLLM/Lumina-Image-2.0
|
| 121 |
-
- baidu/ERNIE-Image
|
| 122 |
-
- baidu/ERNIE-Image-Turbo
|
| 123 |
-
- black-forest-labs/FLUX.1-dev
|
| 124 |
-
- black-forest-labs/FLUX.1-Krea-dev
|
| 125 |
-
- black-forest-labs/FLUX.1-schnell
|
| 126 |
-
- Boogu/Boogu-Image-0.1-Turbo
|
| 127 |
-
- Boogu/Boogu-Image-0.1-Base
|
| 128 |
-
- HiDream-ai/HiDream-I1-Dev
|
| 129 |
-
- HiDream-ai/HiDream-I1-Fast
|
| 130 |
-
- HiDream-ai/HiDream-I1-Full
|
| 131 |
-
- HiDream-ai/HiDream-O1-Image
|
| 132 |
-
- HiDream-ai/HiDream-O1-Image-Dev
|
| 133 |
-
- ideogram-ai/ideogram-4-fp8
|
| 134 |
-
- krea/Krea-2-Raw
|
| 135 |
-
- krea/Krea-2-Turbo
|
| 136 |
-
- lodestones/Chroma1-HD
|
| 137 |
-
- lodestones/Chroma1-Radiance
|
| 138 |
-
- meituan-longcat/LongCat-Image
|
| 139 |
-
- microsoft/Lens
|
| 140 |
-
- microsoft/Lens-Turbo
|
| 141 |
-
- NewBie-AI/NewBie-image-Exp0.1
|
| 142 |
-
- nvidia/PiD
|
| 143 |
-
- nvidia/PixelDiT-1300M-1024px
|
| 144 |
-
- OmniGen2/OmniGen2
|
| 145 |
-
- Qwen/Qwen-Image
|
| 146 |
-
- Qwen/Qwen-Image-2512
|
| 147 |
-
- stabilityai/stable-diffusion-3.5-large
|
| 148 |
-
- stabilityai/stable-diffusion-3.5-medium
|
| 149 |
-
- tencent/HunyuanImage-2.1
|
| 150 |
-
- Tongyi-MAI/Z-Image
|
| 151 |
-
- Tongyi-MAI/Z-Image-Turbo
|
| 152 |
-
---
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: ImageGen
|
| 3 |
+
emoji: πΌ
|
| 4 |
+
colorFrom: purple
|
| 5 |
+
colorTo: red
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: "5.50.0"
|
| 8 |
+
app_file: app.py
|
| 9 |
+
python_version: 3.12
|
| 10 |
+
short_description: Multi-task image generator with dynamic, chainable workflows
|
| 11 |
+
pinned: true
|
| 12 |
+
models:
|
| 13 |
+
# This Space supports a wide variety of image generation pipelines. To maintain transparency, credit the original creators, and help users explore the Hugging Face ecosystem, we list and link several types of models in our metadata:
|
| 14 |
+
# 1. **Directly Run Models:** Models and checkpoints actively loaded by our pipelines (configured via `yaml/file_list.yaml`).
|
| 15 |
+
# 2. **Upstream Base Models:** The original foundation architectures from which our optimized ports, quantized versions, or wrappers are derived.
|
| 16 |
+
# Directly Run Models
|
| 17 |
+
- AiAF/Illustrious-XL-v0.1.safetensors
|
| 18 |
+
- alibaba-pai/Z-Image-Turbo-Fun-Controlnet-Union-2.1
|
| 19 |
+
- black-forest-labs/FLUX.1-Redux-dev
|
| 20 |
+
- black-forest-labs/FLUX.2-dev-NVFP4
|
| 21 |
+
- black-forest-labs/FLUX.2-klein-4b-nvfp4
|
| 22 |
+
- black-forest-labs/FLUX.2-klein-9b-nvfp4
|
| 23 |
+
- black-forest-labs/FLUX.2-klein-9b-kv-fp8
|
| 24 |
+
- black-forest-labs/FLUX.2-klein-base-4b-nvfp4
|
| 25 |
+
- black-forest-labs/FLUX.2-klein-base-9b-nvfp4
|
| 26 |
+
- bluepen5805/4nima_pencil-XL
|
| 27 |
+
- bluepen5805/anima-models
|
| 28 |
+
- bluepen5805/anima_pencil-XL
|
| 29 |
+
- bluepen5805/blue_pencil-XL
|
| 30 |
+
- bluepen5805/illustrious_pencil-XL
|
| 31 |
+
- bluepen5805/mellow_pencil-XL
|
| 32 |
+
- bluepen5805/noob_v_pencil-XL
|
| 33 |
+
- bluepen5805/pony_pencil-XL
|
| 34 |
+
- cagliostrolab/animagine-xl-3.1
|
| 35 |
+
- cagliostrolab/animagine-xl-4.0
|
| 36 |
+
- ChenkinNoob/ChenkinNoob-XL-V0.5
|
| 37 |
+
- circlestone-labs/Anima
|
| 38 |
+
- Clybius/Chroma-fp8-scaled
|
| 39 |
+
- comfyanonymous/ControlNet-v1-1_fp16_safetensors
|
| 40 |
+
- comfyanonymous/cosmos_1.0_text_encoder_and_VAE_ComfyUI
|
| 41 |
+
- comfyanonymous/flux_text_encoders
|
| 42 |
+
- Comfy-Org/Boogu-Image
|
| 43 |
+
- Comfy-Org/ERNIE-Image
|
| 44 |
+
- Comfy-Org/FLUX.1-Krea-dev_ComfyUI
|
| 45 |
+
- Comfy-Org/flux2-dev
|
| 46 |
+
- Comfy-Org/HiDream-I1_ComfyUI
|
| 47 |
+
- Comfy-Org/HiDream-O1-Image
|
| 48 |
+
- Comfy-Org/HunyuanImage_2.1_ComfyUI
|
| 49 |
+
- Comfy-Org/Ideogram-4
|
| 50 |
+
- Comfy-Org/Krea-2
|
| 51 |
+
- Comfy-Org/Lens
|
| 52 |
+
- Comfy-Org/LongCat-Image
|
| 53 |
+
- Comfy-Org/Lumina_Image_2.0_Repackaged
|
| 54 |
+
- Comfy-Org/NewBie-image-Exp0.1_repackaged
|
| 55 |
+
- Comfy-Org/Omnigen2_ComfyUI_repackaged
|
| 56 |
+
- Comfy-Org/Ovis-Image
|
| 57 |
+
- Comfy-Org/PixelDiT
|
| 58 |
+
- Comfy-Org/Qwen-Image_ComfyUI
|
| 59 |
+
- Comfy-Org/sigclip_vision_384
|
| 60 |
+
- Comfy-Org/stable-diffusion-3.5-fp8
|
| 61 |
+
- Comfy-Org/vae-text-encorder-for-flux-klein-4b
|
| 62 |
+
- Comfy-Org/vae-text-encorder-for-flux-klein-9b
|
| 63 |
+
- Comfy-Org/Wan_2.1_ComfyUI_repackaged
|
| 64 |
+
- Comfy-Org/z_image
|
| 65 |
+
- Comfy-Org/z_image_turbo
|
| 66 |
+
- cyberdelia/CyberRealisticPony
|
| 67 |
+
- diffusionmodels1254ani/hassakuAnima
|
| 68 |
+
- diffusionmodels1254ani/kirazuriAnima_v30AnimaBase1
|
| 69 |
+
- diffusionmodels1254ani/waiANIMA
|
| 70 |
+
- duongve/AnimaYume
|
| 71 |
+
- Eugeoter/noob-sdxl-controlnet-canny
|
| 72 |
+
- Eugeoter/noob-sdxl-controlnet-depth
|
| 73 |
+
- Eugeoter/noob-sdxl-controlnet-lineart_anime
|
| 74 |
+
- Eugeoter/noob-sdxl-controlnet-lineart_realistic
|
| 75 |
+
- Eugeoter/noob-sdxl-controlnet-manga_line
|
| 76 |
+
- Eugeoter/noob-sdxl-controlnet-normal
|
| 77 |
+
- Eugeoter/noob-sdxl-controlnet-softedge_hed
|
| 78 |
+
- Eugeoter/noob-sdxl-controlnet-tile
|
| 79 |
+
- frankjoshua/novaAnimeXL_ilV180
|
| 80 |
+
- h94/IP-Adapter
|
| 81 |
+
- h94/IP-Adapter-FaceID
|
| 82 |
+
- InstantX/FLUX.1-dev-IP-Adapter
|
| 83 |
+
- InstantX/Qwen-Image-ControlNet-Inpainting
|
| 84 |
+
- InstantX/Qwen-Image-ControlNet-Union
|
| 85 |
+
- InstantX/SD3.5-Large-IP-Adapter
|
| 86 |
+
- kandinskylab/Kandinsky-5.0-T2I-Lite
|
| 87 |
+
- Kijai/flux-fp8
|
| 88 |
+
- kohya-ss/Anima-LLLite
|
| 89 |
+
- Laxhar/noob_openpose
|
| 90 |
+
- Laxhar/noobai-XL-1.1
|
| 91 |
+
- Laxhar/noobai-XL-Vpred-1.0
|
| 92 |
+
- licyk/sd_control_collection
|
| 93 |
+
- LyliaEngine/Pony_Diffusion_V6_XL
|
| 94 |
+
- MIC-Lab/illustriousXLv0.1_controlnet
|
| 95 |
+
- MIC-Lab/illustriousXLv1.1_controlnet
|
| 96 |
+
- misri/hassakuXLIllustrious_v30
|
| 97 |
+
- nvidia/Cosmos-Predict2-2B-Text2Image
|
| 98 |
+
- nvidia/Cosmos-Predict2-14B-Text2Image
|
| 99 |
+
- OnomaAIResearch/Illustrious-XL-v1.0
|
| 100 |
+
- OnomaAIResearch/Illustrious-XL-v1.1
|
| 101 |
+
- OnomaAIResearch/Illustrious-XL-v2.0
|
| 102 |
+
- RedRayz/hikari_noob_v-pred_1.2.4
|
| 103 |
+
- Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro-2.0
|
| 104 |
+
- silveroxides/Chroma1-Radiance-fp8-scaled
|
| 105 |
+
- stabilityai/stable-diffusion-3.5-controlnets
|
| 106 |
+
- stabilityai/stable-diffusion-xl-base-1.0
|
| 107 |
+
- stable-diffusion-v1-5/stable-diffusion-v1-5
|
| 108 |
+
- Wenaka/NoobAI_XL_Inpainting_ControlNet_Full
|
| 109 |
+
- xinsir/anime-painter
|
| 110 |
+
- xinsir/controlnet-canny-sdxl-1.0
|
| 111 |
+
- xinsir/controlnet-depth-sdxl-1.0
|
| 112 |
+
- xinsir/controlnet-openpose-sdxl-1.0
|
| 113 |
+
- xinsir/controlnet-scribble-sdxl-1.0
|
| 114 |
+
- xinsir/controlnet-tile-sdxl-1.0
|
| 115 |
+
- xinsir/controlnet-union-sdxl-1.0
|
| 116 |
+
- XLabs-AI/flux-controlnet-collections
|
| 117 |
+
- zhenshipo/waiIllustriousSDXL_v170
|
| 118 |
+
# Upstream Base Models
|
| 119 |
+
- AIDC-AI/Ovis-Image-7B
|
| 120 |
+
- Alpha-VLLM/Lumina-Image-2.0
|
| 121 |
+
- baidu/ERNIE-Image
|
| 122 |
+
- baidu/ERNIE-Image-Turbo
|
| 123 |
+
- black-forest-labs/FLUX.1-dev
|
| 124 |
+
- black-forest-labs/FLUX.1-Krea-dev
|
| 125 |
+
- black-forest-labs/FLUX.1-schnell
|
| 126 |
+
- Boogu/Boogu-Image-0.1-Turbo
|
| 127 |
+
- Boogu/Boogu-Image-0.1-Base
|
| 128 |
+
- HiDream-ai/HiDream-I1-Dev
|
| 129 |
+
- HiDream-ai/HiDream-I1-Fast
|
| 130 |
+
- HiDream-ai/HiDream-I1-Full
|
| 131 |
+
- HiDream-ai/HiDream-O1-Image
|
| 132 |
+
- HiDream-ai/HiDream-O1-Image-Dev
|
| 133 |
+
- ideogram-ai/ideogram-4-fp8
|
| 134 |
+
- krea/Krea-2-Raw
|
| 135 |
+
- krea/Krea-2-Turbo
|
| 136 |
+
- lodestones/Chroma1-HD
|
| 137 |
+
- lodestones/Chroma1-Radiance
|
| 138 |
+
- meituan-longcat/LongCat-Image
|
| 139 |
+
- microsoft/Lens
|
| 140 |
+
- microsoft/Lens-Turbo
|
| 141 |
+
- NewBie-AI/NewBie-image-Exp0.1
|
| 142 |
+
- nvidia/PiD
|
| 143 |
+
- nvidia/PixelDiT-1300M-1024px
|
| 144 |
+
- OmniGen2/OmniGen2
|
| 145 |
+
- Qwen/Qwen-Image
|
| 146 |
+
- Qwen/Qwen-Image-2512
|
| 147 |
+
- stabilityai/stable-diffusion-3.5-large
|
| 148 |
+
- stabilityai/stable-diffusion-3.5-medium
|
| 149 |
+
- tencent/HunyuanImage-2.1
|
| 150 |
+
- Tongyi-MAI/Z-Image
|
| 151 |
+
- Tongyi-MAI/Z-Image-Turbo
|
| 152 |
+
---
|
| 153 |
+
# CPU Compatibility
|
| 154 |
+
This Space has been configured to run on CPU-only environments. The code patches torch to disable CUDA and enforces CPU usage.
|
__pycache__/app.cpython-311.pyc
ADDED
|
Binary file (6.57 kB). View file
|
|
|
app.py
CHANGED
|
@@ -1,106 +1,107 @@
|
|
| 1 |
-
import spaces
|
| 2 |
-
import os
|
| 3 |
-
import sys
|
| 4 |
-
import site
|
| 5 |
-
|
| 6 |
-
if "--use-sage-attention" not in sys.argv:
|
| 7 |
-
sys.argv.append("--use-sage-attention")
|
| 8 |
-
print("π [SageAttention] Injected '--use-sage-attention' into sys.argv.")
|
| 9 |
-
|
| 10 |
-
APP_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 11 |
-
if APP_DIR not in sys.path:
|
| 12 |
-
sys.path.insert(0, APP_DIR)
|
| 13 |
-
print(f"β
Added project root '{APP_DIR}' to sys.path.")
|
| 14 |
-
|
| 15 |
-
SAGE_PATCH_APPLIED = False
|
| 16 |
-
|
| 17 |
-
def apply_sage_attention_patch():
|
| 18 |
-
global SAGE_PATCH_APPLIED
|
| 19 |
-
if SAGE_PATCH_APPLIED:
|
| 20 |
-
return "SageAttention patch already applied."
|
| 21 |
-
|
| 22 |
-
try:
|
| 23 |
-
from comfy import model_management
|
| 24 |
-
import sageattention
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
model_management
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
print("--- [GPU Startup]
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
print(
|
| 56 |
-
print("π¨
|
| 57 |
-
print("
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
from
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
from ui.
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
|
|
|
| 106 |
main()
|
|
|
|
| 1 |
+
import spaces
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
import site
|
| 5 |
+
|
| 6 |
+
if "--use-sage-attention" not in sys.argv:
|
| 7 |
+
sys.argv.append("--use-sage-attention")
|
| 8 |
+
print("π [SageAttention] Injected '--use-sage-attention' into sys.argv.")
|
| 9 |
+
|
| 10 |
+
APP_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 11 |
+
if APP_DIR not in sys.path:
|
| 12 |
+
sys.path.insert(0, APP_DIR)
|
| 13 |
+
print(f"β
Added project root '{APP_DIR}' to sys.path.")
|
| 14 |
+
|
| 15 |
+
SAGE_PATCH_APPLIED = False
|
| 16 |
+
|
| 17 |
+
def apply_sage_attention_patch():
|
| 18 |
+
global SAGE_PATCH_APPLIED
|
| 19 |
+
if SAGE_PATCH_APPLIED:
|
| 20 |
+
return "SageAttention patch already applied."
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
from comfy import model_management
|
| 24 |
+
import sageattention
|
| 25 |
+
print("--- [Runtime Patch] sageattention package found. Applying patch... ---")
|
| 26 |
+
# Disable SageAttention to avoid runtime errors on CPUβonly setups.
|
| 27 |
+
if hasattr(model_management, "sage_attention_enabled"):
|
| 28 |
+
model_management.sage_attention_enabled = lambda: False
|
| 29 |
+
if hasattr(model_management, "pytorch_attention_enabled"):
|
| 30 |
+
model_management.pytorch_attention_enabled = lambda: True
|
| 31 |
+
SAGE_PATCH_APPLIED = True
|
| 32 |
+
return "β
Successfully disabled SageAttention (using standard PyTorch attention)."
|
| 33 |
+
except Exception as e:
|
| 34 |
+
SAGE_PATCH_APPLIED = False
|
| 35 |
+
msg = f"--- [Runtime Patch] β οΈ SageAttention patch could not be applied: {e} ---"
|
| 36 |
+
print(msg)
|
| 37 |
+
return msg
|
| 38 |
+
except Exception as e:
|
| 39 |
+
SAGE_PATCH_APPLIED = False
|
| 40 |
+
msg = f"--- [Runtime Patch] β An error occurred while applying SageAttention patch: {e} ---"
|
| 41 |
+
print(msg)
|
| 42 |
+
return msg
|
| 43 |
+
|
| 44 |
+
# @spaces.cpu (disabled for CPU)
|
| 45 |
+
def dummy_gpu_for_startup():
|
| 46 |
+
try:
|
| 47 |
+
print("--- [GPU Startup] Dummy function for startup check initiated. ---")
|
| 48 |
+
patch_result = apply_sage_attention_patch()
|
| 49 |
+
print(f"--- [GPU Startup] {patch_result} ---")
|
| 50 |
+
print("--- [GPU Startup] Startup check passed. ---")
|
| 51 |
+
return "Startup check passed."
|
| 52 |
+
except BaseException as e:
|
| 53 |
+
err_msg = str(e)
|
| 54 |
+
if "uncorrectable ECC error" in err_msg or "cudaErrorECCUncorrectable" in err_msg:
|
| 55 |
+
print("\n" + "="*80)
|
| 56 |
+
print(f"π¨ [Fatal GPU Error] Captured uncorrectable ECC error during inference: {err_msg}")
|
| 57 |
+
print("π¨ Terminating process to trigger an automatic container restart...")
|
| 58 |
+
print("="*80 + "\n")
|
| 59 |
+
os._exit(1)
|
| 60 |
+
raise e
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def main():
|
| 64 |
+
from comfy_integration import setup as setup_comfyui
|
| 65 |
+
from utils.app_utils import load_ipadapter_presets
|
| 66 |
+
|
| 67 |
+
print("--- [Setup] Starting ComfyUI initialization ---")
|
| 68 |
+
setup_comfyui.initialize_comfyui()
|
| 69 |
+
|
| 70 |
+
print("--- [Setup] Applying SageAttention Runtime Patch ---")
|
| 71 |
+
patch_result = apply_sage_attention_patch()
|
| 72 |
+
print(f"--- [Setup] {patch_result} ---")
|
| 73 |
+
|
| 74 |
+
print("--- [Setup] Reloading site-packages to detect newly installed packages... ---")
|
| 75 |
+
try:
|
| 76 |
+
site.main()
|
| 77 |
+
print("--- [Setup] β
Site-packages reloaded. ---")
|
| 78 |
+
except Exception as e:
|
| 79 |
+
print(f"--- [Setup] β οΈ Warning: Could not fully reload site-packages: {e} ---")
|
| 80 |
+
|
| 81 |
+
print("--- Initiating GPU Startup Check & SageAttention Patch Verification ---")
|
| 82 |
+
try:
|
| 83 |
+
dummy_gpu_for_startup()
|
| 84 |
+
except Exception as e:
|
| 85 |
+
print(f"--- [GPU Startup] β οΈ Warning: Startup check failed: {e} ---")
|
| 86 |
+
|
| 87 |
+
print("--- Starting Application Setup ---")
|
| 88 |
+
|
| 89 |
+
print("--- Loading IPAdapter presets ---")
|
| 90 |
+
load_ipadapter_presets()
|
| 91 |
+
print("--- β
IPAdapter setup complete. ---")
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
print("--- Environment configured. Proceeding with module imports. ---")
|
| 95 |
+
from ui.layout import build_ui
|
| 96 |
+
from ui.events import attach_event_handlers
|
| 97 |
+
|
| 98 |
+
print(f"β
Working directory is stable: {os.getcwd()}")
|
| 99 |
+
|
| 100 |
+
demo = build_ui(attach_event_handlers)
|
| 101 |
+
|
| 102 |
+
print("--- Launching Gradio Interface ---")
|
| 103 |
+
demo.queue().launch(server_name="0.0.0.0", server_port=7860)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
if __name__ == "__main__":
|
| 107 |
main()
|
chain_injectors/flux1_ipadapter_injector.py
CHANGED
|
@@ -1,46 +1,46 @@
|
|
| 1 |
-
def inject(assembler, chain_definition, chain_items):
|
| 2 |
-
if not chain_items:
|
| 3 |
-
return
|
| 4 |
-
|
| 5 |
-
ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
|
| 6 |
-
if ksampler_name not in assembler.node_map:
|
| 7 |
-
print(f"Warning: KSampler node '{ksampler_name}' not found for Flux1 IPAdapter chain. Skipping.")
|
| 8 |
-
return
|
| 9 |
-
|
| 10 |
-
ksampler_id = assembler.node_map[ksampler_name]
|
| 11 |
-
|
| 12 |
-
if 'model' not in assembler.workflow[ksampler_id]['inputs']:
|
| 13 |
-
print(f"Warning: KSampler node '{ksampler_name}' is missing 'model' input. Skipping Flux1 IPAdapter chain.")
|
| 14 |
-
return
|
| 15 |
-
|
| 16 |
-
current_model_connection = assembler.workflow[ksampler_id]['inputs']['model']
|
| 17 |
-
|
| 18 |
-
for item_data in chain_items:
|
| 19 |
-
image_loader_id = assembler._get_unique_id()
|
| 20 |
-
image_loader_node = assembler._get_node_template("LoadImage")
|
| 21 |
-
image_loader_node['inputs']['image'] = item_data['image']
|
| 22 |
-
assembler.workflow[image_loader_id] = image_loader_node
|
| 23 |
-
|
| 24 |
-
ipadapter_loader_id = assembler._get_unique_id()
|
| 25 |
-
ipadapter_loader_node = assembler._get_node_template("IPAdapterFluxLoader")
|
| 26 |
-
ipadapter_loader_node['inputs']['ipadapter'] = "ip-adapter.bin"
|
| 27 |
-
ipadapter_loader_node['inputs']['clip_vision'] = "google/siglip-so400m-patch14-384"
|
| 28 |
-
ipadapter_loader_node['inputs']['provider'] = "
|
| 29 |
-
assembler.workflow[ipadapter_loader_id] = ipadapter_loader_node
|
| 30 |
-
|
| 31 |
-
apply_ipa_id = assembler._get_unique_id()
|
| 32 |
-
apply_ipa_node = assembler._get_node_template("ApplyIPAdapterFlux")
|
| 33 |
-
|
| 34 |
-
apply_ipa_node['inputs']['weight'] = item_data['weight']
|
| 35 |
-
apply_ipa_node['inputs']['start_percent'] = item_data.get('start_percent', 0.0)
|
| 36 |
-
apply_ipa_node['inputs']['end_percent'] = item_data.get('end_percent', 0.6)
|
| 37 |
-
|
| 38 |
-
apply_ipa_node['inputs']['model'] = current_model_connection
|
| 39 |
-
apply_ipa_node['inputs']['ipadapter_flux'] = [ipadapter_loader_id, 0]
|
| 40 |
-
apply_ipa_node['inputs']['image'] = [image_loader_id, 0]
|
| 41 |
-
|
| 42 |
-
assembler.workflow[apply_ipa_id] = apply_ipa_node
|
| 43 |
-
current_model_connection = [apply_ipa_id, 0]
|
| 44 |
-
|
| 45 |
-
assembler.workflow[ksampler_id]['inputs']['model'] = current_model_connection
|
| 46 |
print(f"Flux1 IPAdapter injector applied. KSampler model input re-routed through {len(chain_items)} IPAdapter(s).")
|
|
|
|
| 1 |
+
def inject(assembler, chain_definition, chain_items):
|
| 2 |
+
if not chain_items:
|
| 3 |
+
return
|
| 4 |
+
|
| 5 |
+
ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
|
| 6 |
+
if ksampler_name not in assembler.node_map:
|
| 7 |
+
print(f"Warning: KSampler node '{ksampler_name}' not found for Flux1 IPAdapter chain. Skipping.")
|
| 8 |
+
return
|
| 9 |
+
|
| 10 |
+
ksampler_id = assembler.node_map[ksampler_name]
|
| 11 |
+
|
| 12 |
+
if 'model' not in assembler.workflow[ksampler_id]['inputs']:
|
| 13 |
+
print(f"Warning: KSampler node '{ksampler_name}' is missing 'model' input. Skipping Flux1 IPAdapter chain.")
|
| 14 |
+
return
|
| 15 |
+
|
| 16 |
+
current_model_connection = assembler.workflow[ksampler_id]['inputs']['model']
|
| 17 |
+
|
| 18 |
+
for item_data in chain_items:
|
| 19 |
+
image_loader_id = assembler._get_unique_id()
|
| 20 |
+
image_loader_node = assembler._get_node_template("LoadImage")
|
| 21 |
+
image_loader_node['inputs']['image'] = item_data['image']
|
| 22 |
+
assembler.workflow[image_loader_id] = image_loader_node
|
| 23 |
+
|
| 24 |
+
ipadapter_loader_id = assembler._get_unique_id()
|
| 25 |
+
ipadapter_loader_node = assembler._get_node_template("IPAdapterFluxLoader")
|
| 26 |
+
ipadapter_loader_node['inputs']['ipadapter'] = "ip-adapter.bin"
|
| 27 |
+
ipadapter_loader_node['inputs']['clip_vision'] = "google/siglip-so400m-patch14-384"
|
| 28 |
+
ipadapter_loader_node['inputs']['provider'] = "cpu"
|
| 29 |
+
assembler.workflow[ipadapter_loader_id] = ipadapter_loader_node
|
| 30 |
+
|
| 31 |
+
apply_ipa_id = assembler._get_unique_id()
|
| 32 |
+
apply_ipa_node = assembler._get_node_template("ApplyIPAdapterFlux")
|
| 33 |
+
|
| 34 |
+
apply_ipa_node['inputs']['weight'] = item_data['weight']
|
| 35 |
+
apply_ipa_node['inputs']['start_percent'] = item_data.get('start_percent', 0.0)
|
| 36 |
+
apply_ipa_node['inputs']['end_percent'] = item_data.get('end_percent', 0.6)
|
| 37 |
+
|
| 38 |
+
apply_ipa_node['inputs']['model'] = current_model_connection
|
| 39 |
+
apply_ipa_node['inputs']['ipadapter_flux'] = [ipadapter_loader_id, 0]
|
| 40 |
+
apply_ipa_node['inputs']['image'] = [image_loader_id, 0]
|
| 41 |
+
|
| 42 |
+
assembler.workflow[apply_ipa_id] = apply_ipa_node
|
| 43 |
+
current_model_connection = [apply_ipa_id, 0]
|
| 44 |
+
|
| 45 |
+
assembler.workflow[ksampler_id]['inputs']['model'] = current_model_connection
|
| 46 |
print(f"Flux1 IPAdapter injector applied. KSampler model input re-routed through {len(chain_items)} IPAdapter(s).")
|
chain_injectors/sd3_ipadapter_injector.py
CHANGED
|
@@ -1,66 +1,66 @@
|
|
| 1 |
-
def inject(assembler, chain_definition, chain_items):
|
| 2 |
-
if not chain_items:
|
| 3 |
-
return
|
| 4 |
-
|
| 5 |
-
ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
|
| 6 |
-
if ksampler_name not in assembler.node_map:
|
| 7 |
-
print(f"Warning: KSampler node '{ksampler_name}' not found for SD3 IPAdapter chain. Skipping.")
|
| 8 |
-
return
|
| 9 |
-
|
| 10 |
-
ksampler_id = assembler.node_map[ksampler_name]
|
| 11 |
-
|
| 12 |
-
if 'model' not in assembler.workflow[ksampler_id]['inputs']:
|
| 13 |
-
print(f"Warning: KSampler node '{ksampler_name}' is missing 'model' input. Skipping SD3 IPAdapter chain.")
|
| 14 |
-
return
|
| 15 |
-
|
| 16 |
-
current_model_connection = assembler.workflow[ksampler_id]['inputs']['model']
|
| 17 |
-
|
| 18 |
-
clip_vision_loader_id = assembler._get_unique_id()
|
| 19 |
-
clip_vision_loader_node = assembler._get_node_template("CLIPVisionLoader")
|
| 20 |
-
clip_vision_loader_node['inputs']['clip_name'] = "sigclip_vision_patch14_384.safetensors"
|
| 21 |
-
assembler.workflow[clip_vision_loader_id] = clip_vision_loader_node
|
| 22 |
-
|
| 23 |
-
ipadapter_loader_id = assembler._get_unique_id()
|
| 24 |
-
ipadapter_loader_node = assembler._get_node_template("IPAdapterSD3Loader")
|
| 25 |
-
ipadapter_loader_node['inputs']['ipadapter'] = "ip-adapter_sd35l_instantx.bin"
|
| 26 |
-
ipadapter_loader_node['inputs']['provider'] = "
|
| 27 |
-
assembler.workflow[ipadapter_loader_id] = ipadapter_loader_node
|
| 28 |
-
|
| 29 |
-
for item_data in chain_items:
|
| 30 |
-
image_loader_id = assembler._get_unique_id()
|
| 31 |
-
image_loader_node = assembler._get_node_template("LoadImage")
|
| 32 |
-
image_loader_node['inputs']['image'] = item_data['image']
|
| 33 |
-
assembler.workflow[image_loader_id] = image_loader_node
|
| 34 |
-
|
| 35 |
-
image_scaler_id = assembler._get_unique_id()
|
| 36 |
-
image_scaler_node = assembler._get_node_template("ImageScaleToTotalPixels")
|
| 37 |
-
image_scaler_node['inputs']['image'] = [image_loader_id, 0]
|
| 38 |
-
image_scaler_node['inputs']['upscale_method'] = 'nearest-exact'
|
| 39 |
-
image_scaler_node['inputs']['megapixels'] = 1.0
|
| 40 |
-
assembler.workflow[image_scaler_id] = image_scaler_node
|
| 41 |
-
|
| 42 |
-
clip_vision_encode_id = assembler._get_unique_id()
|
| 43 |
-
clip_vision_encode_node = assembler._get_node_template("CLIPVisionEncode")
|
| 44 |
-
clip_vision_encode_node['inputs']['crop'] = "center"
|
| 45 |
-
clip_vision_encode_node['inputs']['clip_vision'] = [clip_vision_loader_id, 0]
|
| 46 |
-
clip_vision_encode_node['inputs']['image'] = [image_scaler_id, 0]
|
| 47 |
-
assembler.workflow[clip_vision_encode_id] = clip_vision_encode_node
|
| 48 |
-
|
| 49 |
-
apply_ipa_id = assembler._get_unique_id()
|
| 50 |
-
apply_ipa_node = assembler._get_node_template("ApplyIPAdapterSD3")
|
| 51 |
-
|
| 52 |
-
apply_ipa_node['inputs']['weight'] = item_data.get('weight', 1.0)
|
| 53 |
-
apply_ipa_node['inputs']['start_percent'] = item_data.get('start_percent', 0.0)
|
| 54 |
-
apply_ipa_node['inputs']['end_percent'] = item_data.get('end_percent', 1.0)
|
| 55 |
-
|
| 56 |
-
apply_ipa_node['inputs']['model'] = current_model_connection
|
| 57 |
-
apply_ipa_node['inputs']['ipadapter'] = [ipadapter_loader_id, 0]
|
| 58 |
-
apply_ipa_node['inputs']['image_embed'] = [clip_vision_encode_id, 0]
|
| 59 |
-
|
| 60 |
-
assembler.workflow[apply_ipa_id] = apply_ipa_node
|
| 61 |
-
|
| 62 |
-
current_model_connection = [apply_ipa_id, 0]
|
| 63 |
-
|
| 64 |
-
assembler.workflow[ksampler_id]['inputs']['model'] = current_model_connection
|
| 65 |
-
|
| 66 |
print(f"SD3 IPAdapter injector applied. KSampler model input re-routed through {len(chain_items)} IPAdapter(s).")
|
|
|
|
| 1 |
+
def inject(assembler, chain_definition, chain_items):
|
| 2 |
+
if not chain_items:
|
| 3 |
+
return
|
| 4 |
+
|
| 5 |
+
ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
|
| 6 |
+
if ksampler_name not in assembler.node_map:
|
| 7 |
+
print(f"Warning: KSampler node '{ksampler_name}' not found for SD3 IPAdapter chain. Skipping.")
|
| 8 |
+
return
|
| 9 |
+
|
| 10 |
+
ksampler_id = assembler.node_map[ksampler_name]
|
| 11 |
+
|
| 12 |
+
if 'model' not in assembler.workflow[ksampler_id]['inputs']:
|
| 13 |
+
print(f"Warning: KSampler node '{ksampler_name}' is missing 'model' input. Skipping SD3 IPAdapter chain.")
|
| 14 |
+
return
|
| 15 |
+
|
| 16 |
+
current_model_connection = assembler.workflow[ksampler_id]['inputs']['model']
|
| 17 |
+
|
| 18 |
+
clip_vision_loader_id = assembler._get_unique_id()
|
| 19 |
+
clip_vision_loader_node = assembler._get_node_template("CLIPVisionLoader")
|
| 20 |
+
clip_vision_loader_node['inputs']['clip_name'] = "sigclip_vision_patch14_384.safetensors"
|
| 21 |
+
assembler.workflow[clip_vision_loader_id] = clip_vision_loader_node
|
| 22 |
+
|
| 23 |
+
ipadapter_loader_id = assembler._get_unique_id()
|
| 24 |
+
ipadapter_loader_node = assembler._get_node_template("IPAdapterSD3Loader")
|
| 25 |
+
ipadapter_loader_node['inputs']['ipadapter'] = "ip-adapter_sd35l_instantx.bin"
|
| 26 |
+
ipadapter_loader_node['inputs']['provider'] = "cpu"
|
| 27 |
+
assembler.workflow[ipadapter_loader_id] = ipadapter_loader_node
|
| 28 |
+
|
| 29 |
+
for item_data in chain_items:
|
| 30 |
+
image_loader_id = assembler._get_unique_id()
|
| 31 |
+
image_loader_node = assembler._get_node_template("LoadImage")
|
| 32 |
+
image_loader_node['inputs']['image'] = item_data['image']
|
| 33 |
+
assembler.workflow[image_loader_id] = image_loader_node
|
| 34 |
+
|
| 35 |
+
image_scaler_id = assembler._get_unique_id()
|
| 36 |
+
image_scaler_node = assembler._get_node_template("ImageScaleToTotalPixels")
|
| 37 |
+
image_scaler_node['inputs']['image'] = [image_loader_id, 0]
|
| 38 |
+
image_scaler_node['inputs']['upscale_method'] = 'nearest-exact'
|
| 39 |
+
image_scaler_node['inputs']['megapixels'] = 1.0
|
| 40 |
+
assembler.workflow[image_scaler_id] = image_scaler_node
|
| 41 |
+
|
| 42 |
+
clip_vision_encode_id = assembler._get_unique_id()
|
| 43 |
+
clip_vision_encode_node = assembler._get_node_template("CLIPVisionEncode")
|
| 44 |
+
clip_vision_encode_node['inputs']['crop'] = "center"
|
| 45 |
+
clip_vision_encode_node['inputs']['clip_vision'] = [clip_vision_loader_id, 0]
|
| 46 |
+
clip_vision_encode_node['inputs']['image'] = [image_scaler_id, 0]
|
| 47 |
+
assembler.workflow[clip_vision_encode_id] = clip_vision_encode_node
|
| 48 |
+
|
| 49 |
+
apply_ipa_id = assembler._get_unique_id()
|
| 50 |
+
apply_ipa_node = assembler._get_node_template("ApplyIPAdapterSD3")
|
| 51 |
+
|
| 52 |
+
apply_ipa_node['inputs']['weight'] = item_data.get('weight', 1.0)
|
| 53 |
+
apply_ipa_node['inputs']['start_percent'] = item_data.get('start_percent', 0.0)
|
| 54 |
+
apply_ipa_node['inputs']['end_percent'] = item_data.get('end_percent', 1.0)
|
| 55 |
+
|
| 56 |
+
apply_ipa_node['inputs']['model'] = current_model_connection
|
| 57 |
+
apply_ipa_node['inputs']['ipadapter'] = [ipadapter_loader_id, 0]
|
| 58 |
+
apply_ipa_node['inputs']['image_embed'] = [clip_vision_encode_id, 0]
|
| 59 |
+
|
| 60 |
+
assembler.workflow[apply_ipa_id] = apply_ipa_node
|
| 61 |
+
|
| 62 |
+
current_model_connection = [apply_ipa_id, 0]
|
| 63 |
+
|
| 64 |
+
assembler.workflow[ksampler_id]['inputs']['model'] = current_model_connection
|
| 65 |
+
|
| 66 |
print(f"SD3 IPAdapter injector applied. KSampler model input re-routed through {len(chain_items)} IPAdapter(s).")
|
comfy_integration/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (159 Bytes). View file
|
|
|
comfy_integration/__pycache__/setup.cpython-311.pyc
ADDED
|
Binary file (13 kB). View file
|
|
|
comfy_integration/setup.py
CHANGED
|
@@ -1,98 +1,191 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import sys
|
| 3 |
-
import shutil
|
| 4 |
-
|
| 5 |
-
from core.settings import *
|
| 6 |
-
|
| 7 |
-
def move_and_overwrite(src, dst):
|
| 8 |
-
if os.path.isdir(src):
|
| 9 |
-
if os.path.exists(dst):
|
| 10 |
-
shutil.rmtree(dst)
|
| 11 |
-
shutil.move(src, dst)
|
| 12 |
-
elif os.path.isfile(src):
|
| 13 |
-
if os.path.exists(dst):
|
| 14 |
-
os.remove(dst)
|
| 15 |
-
shutil.move(src, dst)
|
| 16 |
-
|
| 17 |
-
def initialize_comfyui():
|
| 18 |
-
APP_DIR = sys.path[0]
|
| 19 |
-
COMFYUI_TEMP_DIR = "ComfyUI_temp"
|
| 20 |
-
|
| 21 |
-
print("--- Cloning ComfyUI Repository ---")
|
| 22 |
-
if not os.path.exists(COMFYUI_TEMP_DIR):
|
| 23 |
-
os.system(f"git clone https://github.com/comfy-Org/ComfyUI {COMFYUI_TEMP_DIR}")
|
| 24 |
-
print("β
ComfyUI repository cloned.")
|
| 25 |
-
else:
|
| 26 |
-
print("β
ComfyUI repository already exists.")
|
| 27 |
-
|
| 28 |
-
print(f"--- Merging ComfyUI from '{COMFYUI_TEMP_DIR}' to '{APP_DIR}' ---")
|
| 29 |
-
for item in os.listdir(COMFYUI_TEMP_DIR):
|
| 30 |
-
src_path = os.path.join(COMFYUI_TEMP_DIR, item)
|
| 31 |
-
dst_path = os.path.join(APP_DIR, item)
|
| 32 |
-
if item == '.git':
|
| 33 |
-
continue
|
| 34 |
-
move_and_overwrite(src_path, dst_path)
|
| 35 |
-
|
| 36 |
-
try:
|
| 37 |
-
shutil.rmtree(COMFYUI_TEMP_DIR)
|
| 38 |
-
print("β
ComfyUI merged and temporary directory removed.")
|
| 39 |
-
except OSError as e:
|
| 40 |
-
print(f"β οΈ Could not remove temporary directory '{COMFYUI_TEMP_DIR}': {e}")
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
import
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import shutil
|
| 4 |
+
|
| 5 |
+
from core.settings import *
|
| 6 |
+
|
| 7 |
+
def move_and_overwrite(src, dst):
|
| 8 |
+
if os.path.isdir(src):
|
| 9 |
+
if os.path.exists(dst):
|
| 10 |
+
shutil.rmtree(dst)
|
| 11 |
+
shutil.move(src, dst)
|
| 12 |
+
elif os.path.isfile(src):
|
| 13 |
+
if os.path.exists(dst):
|
| 14 |
+
os.remove(dst)
|
| 15 |
+
shutil.move(src, dst)
|
| 16 |
+
|
| 17 |
+
def initialize_comfyui():
|
| 18 |
+
APP_DIR = sys.path[0]
|
| 19 |
+
COMFYUI_TEMP_DIR = "ComfyUI_temp"
|
| 20 |
+
|
| 21 |
+
print("--- Cloning ComfyUI Repository ---")
|
| 22 |
+
if not os.path.exists(COMFYUI_TEMP_DIR):
|
| 23 |
+
os.system(f"git clone https://github.com/comfy-Org/ComfyUI {COMFYUI_TEMP_DIR}")
|
| 24 |
+
print("β
ComfyUI repository cloned.")
|
| 25 |
+
else:
|
| 26 |
+
print("β
ComfyUI repository already exists.")
|
| 27 |
+
|
| 28 |
+
print(f"--- Merging ComfyUI from '{COMFYUI_TEMP_DIR}' to '{APP_DIR}' ---")
|
| 29 |
+
for item in os.listdir(COMFYUI_TEMP_DIR):
|
| 30 |
+
src_path = os.path.join(COMFYUI_TEMP_DIR, item)
|
| 31 |
+
dst_path = os.path.join(APP_DIR, item)
|
| 32 |
+
if item == '.git':
|
| 33 |
+
continue
|
| 34 |
+
move_and_overwrite(src_path, dst_path)
|
| 35 |
+
|
| 36 |
+
try:
|
| 37 |
+
shutil.rmtree(COMFYUI_TEMP_DIR)
|
| 38 |
+
print("β
ComfyUI merged and temporary directory removed.")
|
| 39 |
+
except OSError as e:
|
| 40 |
+
print(f"β οΈ Could not remove temporary directory '{COMFYUI_TEMP_DIR}': {e}")
|
| 41 |
+
|
| 42 |
+
print("--- Cloning third-party extensions for ComfyUI ---")
|
| 43 |
+
|
| 44 |
+
# 1. ComfyUI_IPAdapter_plus
|
| 45 |
+
ipadapter_plus_path = os.path.join(APP_DIR, "custom_nodes", "ComfyUI_IPAdapter_plus")
|
| 46 |
+
if not os.path.exists(ipadapter_plus_path):
|
| 47 |
+
os.system(f"git clone https://github.com/cubiq/ComfyUI_IPAdapter_plus.git {ipadapter_plus_path}")
|
| 48 |
+
print("β
ComfyUI_IPAdapter_plus extension cloned.")
|
| 49 |
+
else:
|
| 50 |
+
print("β
ComfyUI_IPAdapter_plus extension already exists.")
|
| 51 |
+
|
| 52 |
+
# 2. ComfyUI-InstantX-IPAdapter-SD3
|
| 53 |
+
ipadapter_plus_path = os.path.join(APP_DIR, "custom_nodes", "ComfyUI-InstantX-IPAdapter-SD3")
|
| 54 |
+
if not os.path.exists(ipadapter_plus_path):
|
| 55 |
+
os.system(f"git clone https://github.com/Slickytail/ComfyUI-InstantX-IPAdapter-SD3.git {ipadapter_plus_path}")
|
| 56 |
+
print("β
ComfyUI-InstantX-IPAdapter-SD3 extension cloned.")
|
| 57 |
+
else:
|
| 58 |
+
print("β
ComfyUI-InstantX-IPAdapter-SD3 extension already exists.")
|
| 59 |
+
|
| 60 |
+
# 3. ComfyUI-IPAdapter-Flux
|
| 61 |
+
ipadapter_flux_path = os.path.join(APP_DIR, "custom_nodes", "ComfyUI-IPAdapter-Flux")
|
| 62 |
+
if not os.path.exists(ipadapter_flux_path):
|
| 63 |
+
os.system(f"git clone https://github.com/Shakker-Labs/ComfyUI-IPAdapter-Flux.git {ipadapter_flux_path}")
|
| 64 |
+
print("β
ComfyUI-IPAdapter-Flux extension cloned.")
|
| 65 |
+
else:
|
| 66 |
+
print("β
ComfyUI-IPAdapter-Flux extension already exists.")
|
| 67 |
+
|
| 68 |
+
# 4. ComfyUI-Newbie-Nodes
|
| 69 |
+
newbie_nodes_path = os.path.join(APP_DIR, "custom_nodes", "ComfyUI-Newbie-Nodes")
|
| 70 |
+
if not os.path.exists(newbie_nodes_path):
|
| 71 |
+
os.system(f"git clone https://github.com/NewBieAI-Lab/ComfyUI-Newbie-Nodes.git {newbie_nodes_path}")
|
| 72 |
+
print("β
ComfyUI-Newbie-Nodes extension cloned.")
|
| 73 |
+
else:
|
| 74 |
+
print("β
ComfyUI-Newbie-Nodes extension already exists.")
|
| 75 |
+
|
| 76 |
+
# 5. ComfyUI-Anima-LLLite
|
| 77 |
+
anima_controlnet_lllite_nodes_path = os.path.join(APP_DIR, "custom_nodes", "ComfyUI-Anima-LLLite")
|
| 78 |
+
if not os.path.exists(anima_controlnet_lllite_nodes_path):
|
| 79 |
+
os.system(f"git clone https://github.com/kohya-ss/ComfyUI-Anima-LLLite.git {anima_controlnet_lllite_nodes_path}")
|
| 80 |
+
print("β
ComfyUI-Anima-LLLite extension cloned.")
|
| 81 |
+
else:
|
| 82 |
+
print("β
ComfyUI-Anima-LLLite extension already exists.")
|
| 83 |
+
|
| 84 |
+
print(f"β
Current working directory is: {os.getcwd()}")
|
| 85 |
+
|
| 86 |
+
# Ensure torch treats CUDA as unavailable on CPUβonly environment.
|
| 87 |
+
import torch
|
| 88 |
+
torch.cuda.is_available = lambda: False
|
| 89 |
+
# Additional CUDA stubs to prevent calls like torch.cuda.get_device_properties
|
| 90 |
+
class _DummyDeviceProps:
|
| 91 |
+
def __init__(self):
|
| 92 |
+
self.major = 0
|
| 93 |
+
self.minor = 0
|
| 94 |
+
self.total_memory = 0
|
| 95 |
+
def _dummy_get_device_properties(device):
|
| 96 |
+
return _DummyDeviceProps()
|
| 97 |
+
torch.cuda.get_device_properties = _dummy_get_device_properties
|
| 98 |
+
torch.cuda.device_count = lambda: 0
|
| 99 |
+
torch.cuda.current_device = lambda: 0
|
| 100 |
+
torch.cuda.get_device_name = lambda device: "cpu"
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
# Apply patch to the external ComfyUI model_management before import.
|
| 104 |
+
external_model_mgmt_path = os.path.join(APP_DIR, "comfy", "model_management.py")
|
| 105 |
+
if os.path.exists(external_model_mgmt_path):
|
| 106 |
+
try:
|
| 107 |
+
with open(external_model_mgmt_path, "r", encoding="utf-8") as f:
|
| 108 |
+
content = f.read()
|
| 109 |
+
start = content.find("def get_torch_device():")
|
| 110 |
+
if start != -1:
|
| 111 |
+
# Find the end of the function (next def or end of file)
|
| 112 |
+
end = content.find("\ndef ", start + 1)
|
| 113 |
+
if end == -1:
|
| 114 |
+
end = len(content)
|
| 115 |
+
new_func = """def get_torch_device():
|
| 116 |
+
\"\"\"Return appropriate torch device, falling back to CPU when needed.\"\"\"
|
| 117 |
+
global directml_enabled, cpu_state
|
| 118 |
+
if directml_enabled:
|
| 119 |
+
return directml_device
|
| 120 |
+
if cpu_state == CPUState.MPS:
|
| 121 |
+
return torch.device(\"mps\")
|
| 122 |
+
if cpu_state == CPUState.CPU:
|
| 123 |
+
return torch.device(\"cpu\")
|
| 124 |
+
if is_intel_xpu():
|
| 125 |
+
return torch.device(\"xpu\", torch.xpu.current_device())
|
| 126 |
+
if is_ascend_npu():
|
| 127 |
+
return torch.device(\"npu\", torch.npu.current_device())
|
| 128 |
+
if is_mlu():
|
| 129 |
+
return torch.device(\"mlu\", torch.mlu.current_device())
|
| 130 |
+
if torch.cuda.is_available():
|
| 131 |
+
return torch.device(torch.cuda.current_device())
|
| 132 |
+
return torch.device(\"cpu\")
|
| 133 |
+
"""
|
| 134 |
+
patched_content = content[:start] + new_func + content[end:]
|
| 135 |
+
with open(external_model_mgmt_path, "w", encoding="utf-8") as f:
|
| 136 |
+
f.write(patched_content)
|
| 137 |
+
except Exception as e:
|
| 138 |
+
print(f"β οΈ Failed to patch external model_management: {e}")
|
| 139 |
+
|
| 140 |
+
import comfy.model_management as model_mgmt
|
| 141 |
+
# Patch get_torch_device to enforce CPU fallback.
|
| 142 |
+
def _cpu_fallback_get_torch_device():
|
| 143 |
+
"""Return appropriate torch device, falling back to CPU when needed.
|
| 144 |
+
|
| 145 |
+
This patch replaces the original implementation that assumed a CUDA GPU
|
| 146 |
+
and raised a RuntimeError on CPUβonly systems. It respects the existing
|
| 147 |
+
``directml_enabled`` and ``cpu_state`` flags, and falls back to CPU when
|
| 148 |
+
no GPU backend is available.
|
| 149 |
+
"""
|
| 150 |
+
# DirectML path β unchanged.
|
| 151 |
+
if model_mgmt.directml_enabled:
|
| 152 |
+
return model_mgmt.directml_device
|
| 153 |
+
# Apple Silicon / MPS backend.
|
| 154 |
+
if model_mgmt.cpu_state == model_mgmt.CPUState.MPS:
|
| 155 |
+
return torch.device("mps")
|
| 156 |
+
# Explicit CPU request.
|
| 157 |
+
if model_mgmt.cpu_state == model_mgmt.CPUState.CPU:
|
| 158 |
+
return torch.device("cpu")
|
| 159 |
+
# Intel XPU, Ascend NPU, MLU β retain original handling.
|
| 160 |
+
if model_mgmt.is_intel_xpu():
|
| 161 |
+
return torch.device("xpu", torch.xpu.current_device())
|
| 162 |
+
if model_mgmt.is_ascend_npu():
|
| 163 |
+
return torch.device("npu", torch.npu.current_device())
|
| 164 |
+
if model_mgmt.is_mlu():
|
| 165 |
+
return torch.device("mlu", torch.mlu.current_device())
|
| 166 |
+
# CUDA β use if available (will be False due to monkeyβpatch).
|
| 167 |
+
if torch.cuda.is_available():
|
| 168 |
+
return torch.device(torch.cuda.current_device())
|
| 169 |
+
# Default to CPU.
|
| 170 |
+
return torch.device("cpu")
|
| 171 |
+
model_mgmt.get_torch_device = _cpu_fallback_get_torch_device
|
| 172 |
+
import importlib
|
| 173 |
+
importlib.reload(model_mgmt)
|
| 174 |
+
# Override should_use_bf16 to avoid any CUDA device property queries on CPUβonly systems.
|
| 175 |
+
def _safe_should_use_bf16(device, model_params=None, manual_cast=False):
|
| 176 |
+
"""Return False unconditionally β no BF16 support on CPUβonly environment."""
|
| 177 |
+
return False
|
| 178 |
+
model_mgmt.should_use_bf16 = _safe_should_use_bf16
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
print("--- Environment Ready ---")
|
| 182 |
+
|
| 183 |
+
print("β
ComfyUI initialized with default attention mechanism.")
|
| 184 |
+
|
| 185 |
+
for dir_path in CATEGORY_TO_DIR_MAP.values():
|
| 186 |
+
os.makedirs(os.path.join(APP_DIR, dir_path), exist_ok=True)
|
| 187 |
+
|
| 188 |
+
os.makedirs(os.path.join(APP_DIR, INPUT_DIR), exist_ok=True)
|
| 189 |
+
os.makedirs(os.path.join(APP_DIR, OUTPUT_DIR), exist_ok=True)
|
| 190 |
+
|
| 191 |
+
print("β
All required model directories are present.")
|
core/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (146 Bytes). View file
|
|
|
core/__pycache__/settings.cpython-311.pyc
ADDED
|
Binary file (12.7 kB). View file
|
|
|
core/pipelines/base_pipeline.py
CHANGED
|
@@ -1,65 +1,66 @@
|
|
| 1 |
-
from abc import ABC, abstractmethod
|
| 2 |
-
from typing import List, Any, Dict
|
| 3 |
-
import gradio as gr
|
| 4 |
-
import spaces
|
| 5 |
-
import tempfile
|
| 6 |
-
import imageio
|
| 7 |
-
import numpy as np
|
| 8 |
-
import sys
|
| 9 |
-
import os
|
| 10 |
-
|
| 11 |
-
class BasePipeline(ABC):
|
| 12 |
-
def __init__(self):
|
| 13 |
-
from core.model_manager import model_manager
|
| 14 |
-
self.model_manager = model_manager
|
| 15 |
-
|
| 16 |
-
@abstractmethod
|
| 17 |
-
def get_required_models(self, **kwargs) -> List[str]:
|
| 18 |
-
pass
|
| 19 |
-
|
| 20 |
-
@abstractmethod
|
| 21 |
-
def run(self, *args, progress: gr.Progress, **kwargs) -> Any:
|
| 22 |
-
pass
|
| 23 |
-
|
| 24 |
-
def _ensure_models_downloaded(self, progress: gr.Progress, **kwargs):
|
| 25 |
-
"""Ensures model files are downloaded before requesting GPU."""
|
| 26 |
-
required_models = self.get_required_models(**kwargs)
|
| 27 |
-
self.model_manager.ensure_models_downloaded(required_models, progress=progress)
|
| 28 |
-
|
| 29 |
-
def _execute_gpu_logic(self, gpu_function: callable, duration: int, default_duration: int, task_name: str, *args, **kwargs):
|
| 30 |
-
final_duration = default_duration
|
| 31 |
-
try:
|
| 32 |
-
if duration is not None and int(duration) > 0:
|
| 33 |
-
final_duration = int(duration)
|
| 34 |
-
except (ValueError, TypeError):
|
| 35 |
-
print(f"Invalid ZeroGPU duration input for {task_name}. Using default {default_duration}s.")
|
| 36 |
-
pass
|
| 37 |
-
|
| 38 |
-
print(f"Requesting ZeroGPU for {task_name} with duration: {final_duration} seconds.")
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
print(
|
| 48 |
-
print("π¨
|
| 49 |
-
print("
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
| 65 |
return video_path
|
|
|
|
| 1 |
+
from abc import ABC, abstractmethod
|
| 2 |
+
from typing import List, Any, Dict
|
| 3 |
+
import gradio as gr
|
| 4 |
+
import spaces
|
| 5 |
+
import tempfile
|
| 6 |
+
import imageio
|
| 7 |
+
import numpy as np
|
| 8 |
+
import sys
|
| 9 |
+
import os
|
| 10 |
+
|
| 11 |
+
class BasePipeline(ABC):
|
| 12 |
+
def __init__(self):
|
| 13 |
+
from core.model_manager import model_manager
|
| 14 |
+
self.model_manager = model_manager
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def get_required_models(self, **kwargs) -> List[str]:
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
@abstractmethod
|
| 21 |
+
def run(self, *args, progress: gr.Progress, **kwargs) -> Any:
|
| 22 |
+
pass
|
| 23 |
+
|
| 24 |
+
def _ensure_models_downloaded(self, progress: gr.Progress, **kwargs):
|
| 25 |
+
"""Ensures model files are downloaded before requesting GPU."""
|
| 26 |
+
required_models = self.get_required_models(**kwargs)
|
| 27 |
+
self.model_manager.ensure_models_downloaded(required_models, progress=progress)
|
| 28 |
+
|
| 29 |
+
def _execute_gpu_logic(self, gpu_function: callable, duration: int, default_duration: int, task_name: str, *args, **kwargs):
|
| 30 |
+
final_duration = default_duration
|
| 31 |
+
try:
|
| 32 |
+
if duration is not None and int(duration) > 0:
|
| 33 |
+
final_duration = int(duration)
|
| 34 |
+
except (ValueError, TypeError):
|
| 35 |
+
print(f"Invalid ZeroGPU duration input for {task_name}. Using default {default_duration}s.")
|
| 36 |
+
pass
|
| 37 |
+
|
| 38 |
+
print(f"Requesting ZeroGPU for {task_name} with duration: {final_duration} seconds.")
|
| 39 |
+
# Direct call without GPU allocation for CPU execution
|
| 40 |
+
gpu_runner = gpu_function
|
| 41 |
+
|
| 42 |
+
try:
|
| 43 |
+
return gpu_runner(*args, **kwargs)
|
| 44 |
+
except BaseException as e:
|
| 45 |
+
err_msg = str(e)
|
| 46 |
+
if "uncorrectable ECC error" in err_msg or "cudaErrorECCUncorrectable" in err_msg:
|
| 47 |
+
print("\n" + "="*80)
|
| 48 |
+
print(f"π¨ [Fatal GPU Error] Captured uncorrectable ECC error during inference: {err_msg}")
|
| 49 |
+
print("π¨ Terminating process to trigger an automatic container restart...")
|
| 50 |
+
print("="*80 + "\n")
|
| 51 |
+
os._exit(1)
|
| 52 |
+
raise e
|
| 53 |
+
|
| 54 |
+
def _encode_video_from_frames(self, frames_tensor_cpu: 'torch.Tensor', fps: int, progress: gr.Progress) -> str:
|
| 55 |
+
progress(0.9, desc="Encoding video on CPU...")
|
| 56 |
+
frames_np = (frames_tensor_cpu.numpy() * 255.0).astype(np.uint8)
|
| 57 |
+
|
| 58 |
+
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_video_file:
|
| 59 |
+
video_path = temp_video_file.name
|
| 60 |
+
writer = imageio.get_writer(video_path, fps=fps, codec='libx264', quality=8)
|
| 61 |
+
for frame in frames_np:
|
| 62 |
+
writer.append_data(frame)
|
| 63 |
+
writer.close()
|
| 64 |
+
|
| 65 |
+
progress(1.0, desc="Done!")
|
| 66 |
return video_path
|
core/pipelines/sd_image_pipeline.py
CHANGED
|
@@ -6,6 +6,7 @@ import gradio as gr
|
|
| 6 |
from PIL import Image
|
| 7 |
from typing import List, Dict, Any
|
| 8 |
|
|
|
|
| 9 |
from .base_pipeline import BasePipeline
|
| 10 |
from core.settings import *
|
| 11 |
from utils.app_utils import sanitize_prompt
|
|
@@ -26,34 +27,61 @@ class SdImagePipeline(BasePipeline):
|
|
| 26 |
return [model_display_name]
|
| 27 |
|
| 28 |
def _gpu_logic(self, ui_inputs: Dict, loras_string: str, workflow: Dict[str, Any], assembler: WorkflowAssembler, progress=gr.Progress(track_tqdm=True)):
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
| 31 |
progress(0.4, desc="Executing workflow...")
|
| 32 |
-
|
| 33 |
initial_objects = {}
|
| 34 |
-
|
| 35 |
decoded_images_tensor = WorkflowExecutor.execute_workflow(workflow, initial_objects=initial_objects)
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
start_seed = ui_inputs['seed'] if ui_inputs['seed'] != -1 else random.randint(0, 2**64 - 1)
|
| 39 |
for i in range(decoded_images_tensor.shape[0]):
|
| 40 |
img_tensor = decoded_images_tensor[i]
|
| 41 |
pil_image = Image.fromarray((img_tensor.cpu().numpy() * 255.0).astype("uint8"))
|
| 42 |
current_seed = start_seed + i
|
| 43 |
-
|
| 44 |
width_for_meta = ui_inputs.get('width', 'N/A')
|
| 45 |
height_for_meta = ui_inputs.get('height', 'N/A')
|
| 46 |
|
| 47 |
params_string = f"{ui_inputs['positive_prompt']}\nNegative prompt: {ui_inputs['negative_prompt']}\n"
|
| 48 |
-
params_string += f"Steps: {ui_inputs['num_inference_steps']}, Sampler: {ui_inputs['sampler']}, Scheduler: {ui_inputs['scheduler']}, CFG scale: {ui_inputs['guidance_scale']}, Seed: {current_seed}, Size: {width_for_meta}x{height_for_meta}, Base Model: {model_display_name}"
|
| 49 |
-
if ui_inputs['task_type'] != 'txt2img':
|
| 50 |
-
|
| 51 |
-
if
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
| 53 |
pil_image.info = {'parameters': params_string.strip()}
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
def run(self, ui_inputs: Dict, progress):
|
| 59 |
progress(0, desc="Preparing models...")
|
|
@@ -212,46 +240,10 @@ class SdImagePipeline(BasePipeline):
|
|
| 212 |
progress=progress
|
| 213 |
)
|
| 214 |
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
from PIL import PngImagePlugin
|
| 218 |
-
|
| 219 |
-
prompt_json = json.dumps(workflow)
|
| 220 |
-
|
| 221 |
-
out_dir = os.path.abspath(OUTPUT_DIR)
|
| 222 |
-
os.makedirs(out_dir, exist_ok=True)
|
| 223 |
-
|
| 224 |
-
try:
|
| 225 |
-
existing_files = glob.glob(os.path.join(out_dir, "gen_*.png"))
|
| 226 |
-
existing_files.sort(key=os.path.getmtime)
|
| 227 |
-
while len(existing_files) > 50:
|
| 228 |
-
os.remove(existing_files.pop(0))
|
| 229 |
-
except Exception as e:
|
| 230 |
-
print(f"Warning: Failed to cleanup output dir: {e}")
|
| 231 |
-
|
| 232 |
-
final_results = []
|
| 233 |
-
for img in results:
|
| 234 |
-
if not isinstance(img, Image.Image):
|
| 235 |
-
final_results.append(img)
|
| 236 |
-
continue
|
| 237 |
-
|
| 238 |
-
metadata = PngImagePlugin.PngInfo()
|
| 239 |
-
params_string = img.info.get("parameters", "")
|
| 240 |
-
if params_string:
|
| 241 |
-
metadata.add_text("parameters", params_string)
|
| 242 |
-
metadata.add_text("prompt", prompt_json)
|
| 243 |
-
|
| 244 |
-
filename = f"gen_{random.randint(1000000, 9999999)}.png"
|
| 245 |
-
filepath = os.path.join(out_dir, filename)
|
| 246 |
-
img.save(filepath, "PNG", pnginfo=metadata)
|
| 247 |
-
final_results.append(filepath)
|
| 248 |
-
|
| 249 |
-
results = final_results
|
| 250 |
-
|
| 251 |
finally:
|
| 252 |
for temp_file in temp_files_to_clean:
|
| 253 |
if temp_file and os.path.exists(temp_file):
|
| 254 |
os.remove(temp_file)
|
| 255 |
print(f"β
Cleaned up temp file: {temp_file}")
|
| 256 |
-
|
| 257 |
-
return results
|
|
|
|
| 6 |
from PIL import Image
|
| 7 |
from typing import List, Dict, Any
|
| 8 |
|
| 9 |
+
|
| 10 |
from .base_pipeline import BasePipeline
|
| 11 |
from core.settings import *
|
| 12 |
from utils.app_utils import sanitize_prompt
|
|
|
|
| 27 |
return [model_display_name]
|
| 28 |
|
| 29 |
def _gpu_logic(self, ui_inputs: Dict, loras_string: str, workflow: Dict[str, Any], assembler: WorkflowAssembler, progress=gr.Progress(track_tqdm=True)):
|
| 30 |
+
"""Execute the ComfyUI workflow and return the file paths saved by the SaveImage node.
|
| 31 |
+
The original implementation converted the tensor output to PIL images and then saved
|
| 32 |
+
them again, causing duplicate files. Here we rely on the SaveImage node to write the
|
| 33 |
+
images to the output directory and simply return the path(s) it provides.
|
| 34 |
+
"""
|
| 35 |
progress(0.4, desc="Executing workflow...")
|
|
|
|
| 36 |
initial_objects = {}
|
| 37 |
+
# Execute the workflow; it returns image tensor(s) from the VAE Decode node.
|
| 38 |
decoded_images_tensor = WorkflowExecutor.execute_workflow(workflow, initial_objects=initial_objects)
|
| 39 |
+
# If the node returns a tuple/list, take the first element which holds the tensor.
|
| 40 |
+
if isinstance(decoded_images_tensor, (list, tuple)):
|
| 41 |
+
decoded_images_tensor = decoded_images_tensor[0]
|
| 42 |
+
|
| 43 |
+
# Convert tensors to PIL images, embed metadata and save them to the output directory.
|
| 44 |
+
out_dir = os.path.abspath(OUTPUT_DIR)
|
| 45 |
+
os.makedirs(out_dir, exist_ok=True)
|
| 46 |
+
saved_file_paths = []
|
| 47 |
start_seed = ui_inputs['seed'] if ui_inputs['seed'] != -1 else random.randint(0, 2**64 - 1)
|
| 48 |
for i in range(decoded_images_tensor.shape[0]):
|
| 49 |
img_tensor = decoded_images_tensor[i]
|
| 50 |
pil_image = Image.fromarray((img_tensor.cpu().numpy() * 255.0).astype("uint8"))
|
| 51 |
current_seed = start_seed + i
|
| 52 |
+
|
| 53 |
width_for_meta = ui_inputs.get('width', 'N/A')
|
| 54 |
height_for_meta = ui_inputs.get('height', 'N/A')
|
| 55 |
|
| 56 |
params_string = f"{ui_inputs['positive_prompt']}\nNegative prompt: {ui_inputs['negative_prompt']}\n"
|
| 57 |
+
params_string += f"Steps: {ui_inputs['num_inference_steps']}, Sampler: {ui_inputs['sampler']}, Scheduler: {ui_inputs['scheduler']}, CFG scale: {ui_inputs['guidance_scale']}, Seed: {current_seed}, Size: {width_for_meta}x{height_for_meta}, Base Model: {ui_inputs['model_display_name']}"
|
| 58 |
+
if ui_inputs['task_type'] != 'txt2img':
|
| 59 |
+
params_string += f", Denoise: {ui_inputs['denoise']}"
|
| 60 |
+
if ui_inputs.get('clip_skip') and ui_inputs['clip_skip'] != 1:
|
| 61 |
+
params_string += f", Clip skip: {abs(ui_inputs['clip_skip'])}"
|
| 62 |
+
if loras_string:
|
| 63 |
+
params_string += f", {loras_string}"
|
| 64 |
+
|
| 65 |
pil_image.info = {'parameters': params_string.strip()}
|
| 66 |
+
filename = f"gen_{random.randint(1000000, 9999999)}.png"
|
| 67 |
+
filepath = os.path.join(out_dir, filename)
|
| 68 |
+
pil_image.save(filepath, "PNG")
|
| 69 |
+
saved_file_paths.append(filepath)
|
| 70 |
+
|
| 71 |
+
# Deduplicate by file content hash (SHAβ256) to avoid identical images.
|
| 72 |
+
import hashlib
|
| 73 |
+
unique_hashes = set()
|
| 74 |
+
deduped_paths = []
|
| 75 |
+
for p in saved_file_paths:
|
| 76 |
+
try:
|
| 77 |
+
with open(p, "rb") as f:
|
| 78 |
+
h = hashlib.sha256(f.read()).hexdigest()
|
| 79 |
+
if h not in unique_hashes:
|
| 80 |
+
unique_hashes.add(h)
|
| 81 |
+
deduped_paths.append(p)
|
| 82 |
+
except Exception:
|
| 83 |
+
deduped_paths.append(p)
|
| 84 |
+
return deduped_paths
|
| 85 |
|
| 86 |
def run(self, ui_inputs: Dict, progress):
|
| 87 |
progress(0, desc="Preparing models...")
|
|
|
|
| 240 |
progress=progress
|
| 241 |
)
|
| 242 |
|
| 243 |
+
# The workflow already saved images and returned a deduplicated list of file paths.
|
| 244 |
+
return results
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
finally:
|
| 246 |
for temp_file in temp_files_to_clean:
|
| 247 |
if temp_file and os.path.exists(temp_file):
|
| 248 |
os.remove(temp_file)
|
| 249 |
print(f"β
Cleaned up temp file: {temp_file}")
|
|
|
|
|
|
core/pipelines/workflow_executor.py
CHANGED
|
@@ -95,16 +95,17 @@ class WorkflowExecutor:
|
|
| 95 |
result = execution_method(**kwargs)
|
| 96 |
computed_outputs[node_id] = result
|
| 97 |
|
|
|
|
| 98 |
final_node_id = None
|
| 99 |
for node_id in reversed(sorted_node_ids):
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
|
|
|
| 95 |
result = execution_method(**kwargs)
|
| 96 |
computed_outputs[node_id] = result
|
| 97 |
|
| 98 |
+
# Determine the final output. If a SaveImage node exists, use its input image.
|
| 99 |
final_node_id = None
|
| 100 |
for node_id in reversed(sorted_node_ids):
|
| 101 |
+
if workflow[node_id]['class_type'] == 'SaveImage':
|
| 102 |
+
final_node_id = node_id
|
| 103 |
+
break
|
| 104 |
+
if final_node_id:
|
| 105 |
+
save_image_inputs = workflow[final_node_id]['inputs']
|
| 106 |
+
image_source_node_id, image_source_index = save_image_inputs['images']
|
| 107 |
+
return get_value_at_index(computed_outputs[image_source_node_id], image_source_index)
|
| 108 |
+
else:
|
| 109 |
+
# No SaveImage node β return the output of the last node in execution order.
|
| 110 |
+
last_node_id = sorted_node_ids[-1]
|
| 111 |
+
return computed_outputs[last_node_id]
|
core/pipelines/workflow_recipes/_partials/_base_sampler_sd.yaml
CHANGED
|
@@ -1,36 +1,29 @@
|
|
| 1 |
-
nodes:
|
| 2 |
-
pos_prompt:
|
| 3 |
-
class_type: CLIPTextEncode
|
| 4 |
-
title: "CLIP Text Encode (Positive)"
|
| 5 |
-
neg_prompt:
|
| 6 |
-
class_type: CLIPTextEncode
|
| 7 |
-
title: "CLIP Text Encode (Negative)"
|
| 8 |
-
ksampler:
|
| 9 |
-
class_type: KSampler
|
| 10 |
-
title: "KSampler"
|
| 11 |
-
params:
|
| 12 |
-
denoise: 1.0
|
| 13 |
-
vae_decode:
|
| 14 |
-
class_type: VAEDecode
|
| 15 |
-
title: "VAE Decode"
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
seed: "ksampler:seed"
|
| 31 |
-
steps: "ksampler:steps"
|
| 32 |
-
cfg: "ksampler:cfg"
|
| 33 |
-
sampler_name: "ksampler:sampler_name"
|
| 34 |
-
scheduler: "ksampler:scheduler"
|
| 35 |
-
denoise: "ksampler:denoise"
|
| 36 |
-
filename_prefix: "save_image:filename_prefix"
|
|
|
|
| 1 |
+
nodes:
|
| 2 |
+
pos_prompt:
|
| 3 |
+
class_type: CLIPTextEncode
|
| 4 |
+
title: "CLIP Text Encode (Positive)"
|
| 5 |
+
neg_prompt:
|
| 6 |
+
class_type: CLIPTextEncode
|
| 7 |
+
title: "CLIP Text Encode (Negative)"
|
| 8 |
+
ksampler:
|
| 9 |
+
class_type: KSampler
|
| 10 |
+
title: "KSampler"
|
| 11 |
+
params:
|
| 12 |
+
denoise: 1.0
|
| 13 |
+
vae_decode:
|
| 14 |
+
class_type: VAEDecode
|
| 15 |
+
title: "VAE Decode"
|
| 16 |
+
|
| 17 |
+
connections:
|
| 18 |
+
- from: "ksampler:0"
|
| 19 |
+
to: "vae_decode:samples"
|
| 20 |
+
|
| 21 |
+
ui_map:
|
| 22 |
+
positive_prompt: "pos_prompt:text"
|
| 23 |
+
negative_prompt: "neg_prompt:text"
|
| 24 |
+
seed: "ksampler:seed"
|
| 25 |
+
steps: "ksampler:steps"
|
| 26 |
+
cfg: "ksampler:cfg"
|
| 27 |
+
sampler_name: "ksampler:sampler_name"
|
| 28 |
+
scheduler: "ksampler:scheduler"
|
| 29 |
+
denoise: "ksampler:denoise"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core/settings.py
CHANGED
|
@@ -1,207 +1,231 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
def
|
| 90 |
-
if not os.path.exists(
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
MODEL_DEFAULTS_CONFIG = {}
|
|
|
|
| 1 |
+
"""Settings module for the ImageGen Space.
|
| 2 |
+
|
| 3 |
+
The repository contains a directory named ``yaml`` that stores configuration
|
| 4 |
+
files (``model_list.yaml``, ``constants.yaml`` β¦). Unfortunately this directory
|
| 5 |
+
shadows the external **PyYAML** package when ``import yaml`` is performed, leading
|
| 6 |
+
to ``AttributeError: module 'yaml' has no attribute 'safe_load'`` at runtime.
|
| 7 |
+
|
| 8 |
+
To resolve the naming clash we temporarily remove the project root from
|
| 9 |
+
``sys.path`` while importing the real PyYAML library, then restore the original
|
| 10 |
+
search path. This ensures ``yaml.safe_load`` and related helpers are available
|
| 11 |
+
throughout the module without renaming the data directory.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
import sys
|
| 15 |
+
# Preserve the original search path.
|
| 16 |
+
_original_sys_path = sys.path[:]
|
| 17 |
+
# Exclude the ``ImageGen`` project root (which contains the conflicting ``yaml``
|
| 18 |
+
# directory) from the import search. Paths that end with ``ImageGen`` or contain
|
| 19 |
+
# ``/ImageGen/`` are filtered out.
|
| 20 |
+
sys.path = [p for p in sys.path if not (p.endswith('ImageGen') or '/ImageGen/' in p)]
|
| 21 |
+
import yaml as _yaml_lib
|
| 22 |
+
yaml = _yaml_lib
|
| 23 |
+
# Restore the original path for all subsequent imports.
|
| 24 |
+
sys.path = _original_sys_path
|
| 25 |
+
|
| 26 |
+
import os
|
| 27 |
+
from collections import OrderedDict
|
| 28 |
+
|
| 29 |
+
CHECKPOINT_DIR = "models/checkpoints"
|
| 30 |
+
LORA_DIR = "models/loras"
|
| 31 |
+
EMBEDDING_DIR = "models/embeddings"
|
| 32 |
+
CONTROLNET_DIR = "models/controlnet"
|
| 33 |
+
MODEL_PATCHES_DIR = "models/model_patches"
|
| 34 |
+
DIFFUSION_MODELS_DIR = "models/diffusion_models"
|
| 35 |
+
VAE_DIR = "models/vae"
|
| 36 |
+
TEXT_ENCODERS_DIR = "models/text_encoders"
|
| 37 |
+
STYLE_MODELS_DIR = "models/style_models"
|
| 38 |
+
CLIP_VISION_DIR = "models/clip_vision"
|
| 39 |
+
IPADAPTER_DIR = "models/ipadapter"
|
| 40 |
+
IPADAPTER_FLUX_DIR = "models/ipadapter-flux"
|
| 41 |
+
INPUT_DIR = "input"
|
| 42 |
+
OUTPUT_DIR = "output"
|
| 43 |
+
|
| 44 |
+
CATEGORY_TO_DIR_MAP = {
|
| 45 |
+
"diffusion_models": DIFFUSION_MODELS_DIR,
|
| 46 |
+
"text_encoders": TEXT_ENCODERS_DIR,
|
| 47 |
+
"vae": VAE_DIR,
|
| 48 |
+
"checkpoints": CHECKPOINT_DIR,
|
| 49 |
+
"loras": LORA_DIR,
|
| 50 |
+
"controlnet": CONTROLNET_DIR,
|
| 51 |
+
"model_patches": MODEL_PATCHES_DIR,
|
| 52 |
+
"embeddings": EMBEDDING_DIR,
|
| 53 |
+
"style_models": STYLE_MODELS_DIR,
|
| 54 |
+
"clip_vision": CLIP_VISION_DIR,
|
| 55 |
+
"ipadapter": IPADAPTER_DIR,
|
| 56 |
+
"ipadapter-flux": IPADAPTER_FLUX_DIR
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
_PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 60 |
+
_MODEL_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'model_list.yaml')
|
| 61 |
+
_FILE_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'file_list.yaml')
|
| 62 |
+
_IPADAPTER_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'ipadapter.yaml')
|
| 63 |
+
_CONSTANTS_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'constants.yaml')
|
| 64 |
+
_MODEL_ARCHITECTURES_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'model_architectures.yaml')
|
| 65 |
+
_IMAGE_GEN_FEATURES_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'image_gen_features.yaml')
|
| 66 |
+
_MODEL_DEFAULTS_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'model_defaults.yaml')
|
| 67 |
+
|
| 68 |
+
def load_constants_from_yaml(filepath=_CONSTANTS_PATH):
|
| 69 |
+
if not os.path.exists(filepath):
|
| 70 |
+
print(f"Warning: Constants file not found at {filepath}. Using fallback values.")
|
| 71 |
+
return {}
|
| 72 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
| 73 |
+
return yaml.safe_load(f)
|
| 74 |
+
|
| 75 |
+
def load_architectures_config(filepath=_MODEL_ARCHITECTURES_PATH):
|
| 76 |
+
if not os.path.exists(filepath):
|
| 77 |
+
print(f"Warning: Architectures file not found at {filepath}.")
|
| 78 |
+
return {}
|
| 79 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
| 80 |
+
return yaml.safe_load(f)
|
| 81 |
+
|
| 82 |
+
def load_features_config(filepath=_IMAGE_GEN_FEATURES_PATH):
|
| 83 |
+
if not os.path.exists(filepath):
|
| 84 |
+
print(f"Warning: Features file not found at {filepath}.")
|
| 85 |
+
return {}
|
| 86 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
| 87 |
+
return yaml.safe_load(f)
|
| 88 |
+
|
| 89 |
+
def load_model_defaults(filepath=_MODEL_DEFAULTS_PATH):
|
| 90 |
+
if not os.path.exists(filepath):
|
| 91 |
+
print(f"Warning: Model defaults file not found at {filepath}.")
|
| 92 |
+
return {}
|
| 93 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
| 94 |
+
return yaml.safe_load(f)
|
| 95 |
+
|
| 96 |
+
def load_file_download_map(filepath=_FILE_LIST_PATH):
|
| 97 |
+
if not os.path.exists(filepath):
|
| 98 |
+
raise FileNotFoundError(f"The file list (for downloads) was not found at: {filepath}")
|
| 99 |
+
|
| 100 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
| 101 |
+
file_list_data = yaml.safe_load(f)
|
| 102 |
+
|
| 103 |
+
download_info_map = {}
|
| 104 |
+
for category, files in file_list_data.get('file', {}).items():
|
| 105 |
+
if isinstance(files, list):
|
| 106 |
+
for file_info in files:
|
| 107 |
+
if 'filename' in file_info:
|
| 108 |
+
file_info['category'] = category
|
| 109 |
+
download_info_map[file_info['filename']] = file_info
|
| 110 |
+
return download_info_map
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def load_models_from_yaml(model_list_filepath=_MODEL_LIST_PATH, download_map=None):
|
| 114 |
+
if not os.path.exists(model_list_filepath):
|
| 115 |
+
raise FileNotFoundError(f"The model list file was not found at: {model_list_filepath}")
|
| 116 |
+
if download_map is None:
|
| 117 |
+
raise ValueError("download_map must be provided to load_models_from_yaml")
|
| 118 |
+
|
| 119 |
+
with open(model_list_filepath, 'r', encoding='utf-8') as f:
|
| 120 |
+
model_data = yaml.safe_load(f)
|
| 121 |
+
|
| 122 |
+
model_maps = {
|
| 123 |
+
"MODEL_MAP_CHECKPOINT": OrderedDict(),
|
| 124 |
+
"ALL_MODEL_MAP": OrderedDict(),
|
| 125 |
+
}
|
| 126 |
+
category_map_names = {
|
| 127 |
+
"Checkpoint": "MODEL_MAP_CHECKPOINT",
|
| 128 |
+
"Checkpoints": "MODEL_MAP_CHECKPOINT"
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
for category, architectures in model_data.items():
|
| 132 |
+
if category in category_map_names:
|
| 133 |
+
map_name = category_map_names[category]
|
| 134 |
+
if not isinstance(architectures, dict): continue
|
| 135 |
+
|
| 136 |
+
for arch, arch_data in architectures.items():
|
| 137 |
+
if not isinstance(arch_data, dict): continue
|
| 138 |
+
|
| 139 |
+
latent_type = arch_data.get('latent_type', 'latent')
|
| 140 |
+
models = arch_data.get('models', [])
|
| 141 |
+
if not isinstance(models, list): continue
|
| 142 |
+
|
| 143 |
+
for model in models:
|
| 144 |
+
display_name = model['display_name']
|
| 145 |
+
path_or_components = model.get('path') or model.get('components')
|
| 146 |
+
mod_category = model.get('category', None)
|
| 147 |
+
|
| 148 |
+
repo_id = ''
|
| 149 |
+
if isinstance(path_or_components, str):
|
| 150 |
+
download_info = download_map.get(path_or_components, {})
|
| 151 |
+
repo_id = download_info.get('repo_id', '')
|
| 152 |
+
|
| 153 |
+
model_tuple = (
|
| 154 |
+
repo_id,
|
| 155 |
+
path_or_components,
|
| 156 |
+
arch,
|
| 157 |
+
latent_type,
|
| 158 |
+
mod_category
|
| 159 |
+
)
|
| 160 |
+
model_maps[map_name][display_name] = model_tuple
|
| 161 |
+
model_maps["ALL_MODEL_MAP"][display_name] = model_tuple
|
| 162 |
+
|
| 163 |
+
return model_maps
|
| 164 |
+
|
| 165 |
+
try:
|
| 166 |
+
ALL_FILE_DOWNLOAD_MAP = load_file_download_map()
|
| 167 |
+
loaded_maps = load_models_from_yaml(download_map=ALL_FILE_DOWNLOAD_MAP)
|
| 168 |
+
MODEL_MAP_CHECKPOINT = loaded_maps["MODEL_MAP_CHECKPOINT"]
|
| 169 |
+
ALL_MODEL_MAP = loaded_maps["ALL_MODEL_MAP"]
|
| 170 |
+
|
| 171 |
+
category_to_model_type = {
|
| 172 |
+
"diffusion_models": "UNET",
|
| 173 |
+
"text_encoders": "TEXT_ENCODER",
|
| 174 |
+
"vae": "VAE",
|
| 175 |
+
"checkpoints": "SDXL",
|
| 176 |
+
"loras": "LORA",
|
| 177 |
+
"controlnet": "CONTROLNET",
|
| 178 |
+
"model_patches": "MODEL_PATCH",
|
| 179 |
+
"style_models": "STYLE",
|
| 180 |
+
"clip_vision": "CLIP_VISION",
|
| 181 |
+
"ipadapter": "IPADAPTER",
|
| 182 |
+
"ipadapter-flux": "IPADAPTER_FLUX"
|
| 183 |
+
}
|
| 184 |
+
for filename, file_info in ALL_FILE_DOWNLOAD_MAP.items():
|
| 185 |
+
if filename not in ALL_MODEL_MAP:
|
| 186 |
+
category = file_info.get('category')
|
| 187 |
+
model_type = category_to_model_type.get(category, 'UNKNOWN')
|
| 188 |
+
repo_id = file_info.get('repo_id', '')
|
| 189 |
+
ALL_MODEL_MAP[filename] = (repo_id, filename, model_type, None, None)
|
| 190 |
+
|
| 191 |
+
MODEL_TYPE_MAP = {k: v[2] for k, v in ALL_MODEL_MAP.items()}
|
| 192 |
+
|
| 193 |
+
ARCH_CATEGORIES_MAP = {}
|
| 194 |
+
for display_name, info in MODEL_MAP_CHECKPOINT.items():
|
| 195 |
+
arch = info[2]
|
| 196 |
+
cat = info[4] if len(info) > 4 else None
|
| 197 |
+
if arch not in ARCH_CATEGORIES_MAP:
|
| 198 |
+
ARCH_CATEGORIES_MAP[arch] = []
|
| 199 |
+
if cat and cat not in ARCH_CATEGORIES_MAP[arch]:
|
| 200 |
+
ARCH_CATEGORIES_MAP[arch].append(cat)
|
| 201 |
+
|
| 202 |
+
except Exception as e:
|
| 203 |
+
print(f"FATAL: Could not load model configuration from YAML. Error: {e}")
|
| 204 |
+
ALL_FILE_DOWNLOAD_MAP = {}
|
| 205 |
+
MODEL_MAP_CHECKPOINT, ALL_MODEL_MAP = {}, {}
|
| 206 |
+
MODEL_TYPE_MAP = {}
|
| 207 |
+
ARCH_CATEGORIES_MAP = {}
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
try:
|
| 211 |
+
_constants = load_constants_from_yaml()
|
| 212 |
+
MAX_LORAS = _constants.get('MAX_LORAS', 5)
|
| 213 |
+
MAX_EMBEDDINGS = _constants.get('MAX_EMBEDDINGS', 5)
|
| 214 |
+
MAX_CONDITIONINGS = _constants.get('MAX_CONDITIONINGS', 10)
|
| 215 |
+
MAX_CONTROLNETS = _constants.get('MAX_CONTROLNETS', 5)
|
| 216 |
+
MAX_IPADAPTERS = _constants.get('MAX_IPADAPTERS', 5)
|
| 217 |
+
LORA_SOURCE_CHOICES = _constants.get('LORA_SOURCE_CHOICES', ["Civitai", "File"])
|
| 218 |
+
RESOLUTION_MAP = _constants.get('RESOLUTION_MAP', {})
|
| 219 |
+
MULTIPLIERS_MAP = _constants.get('MULTIPLIERS_MAP', {})
|
| 220 |
+
ARCHITECTURES_CONFIG = load_architectures_config()
|
| 221 |
+
FEATURES_CONFIG = load_features_config()
|
| 222 |
+
MODEL_DEFAULTS_CONFIG = load_model_defaults()
|
| 223 |
+
except Exception as e:
|
| 224 |
+
print(f"FATAL: Could not load constants from YAML. Error: {e}")
|
| 225 |
+
MAX_LORAS, MAX_EMBEDDINGS, MAX_CONDITIONINGS, MAX_CONTROLNETS, MAX_IPADAPTERS = 5, 5, 10, 5, 5
|
| 226 |
+
LORA_SOURCE_CHOICES = ["Civitai", "File"]
|
| 227 |
+
RESOLUTION_MAP = {}
|
| 228 |
+
MULTIPLIERS_MAP = {}
|
| 229 |
+
ARCHITECTURES_CONFIG = {}
|
| 230 |
+
FEATURES_CONFIG = {}
|
| 231 |
MODEL_DEFAULTS_CONFIG = {}
|
requirements.txt
CHANGED
|
@@ -1,46 +1,46 @@
|
|
| 1 |
-
comfyui-frontend-package==1.45.20
|
| 2 |
-
comfyui-workflow-templates==0.11.1
|
| 3 |
-
comfyui-embedded-docs==0.5.6
|
| 4 |
-
torch
|
| 5 |
-
torchsde
|
| 6 |
-
torchvision
|
| 7 |
-
torchaudio
|
| 8 |
-
numpy>=1.25.0
|
| 9 |
-
einops
|
| 10 |
-
transformers>=4.50.3
|
| 11 |
-
tokenizers>=0.13.3
|
| 12 |
-
sentencepiece
|
| 13 |
-
safetensors>=0.4.2
|
| 14 |
-
aiohttp>=3.11.8
|
| 15 |
-
yarl>=1.18.0
|
| 16 |
-
pyyaml
|
| 17 |
-
Pillow
|
| 18 |
-
scipy
|
| 19 |
-
tqdm
|
| 20 |
-
psutil
|
| 21 |
-
alembic
|
| 22 |
-
SQLAlchemy>=2.0.0
|
| 23 |
-
filelock
|
| 24 |
-
av>=16.0.0
|
| 25 |
-
comfy-kitchen==0.2.16
|
| 26 |
-
comfy-aimdo==0.4.10
|
| 27 |
-
requests
|
| 28 |
-
simpleeval>=1.0.0
|
| 29 |
-
blake3
|
| 30 |
-
|
| 31 |
-
#non essential dependencies:
|
| 32 |
-
kornia>=0.7.1
|
| 33 |
-
spandrel
|
| 34 |
-
pydantic~=2.0
|
| 35 |
-
pydantic-settings~=2.0
|
| 36 |
-
PyOpenGL>=3.1.8
|
| 37 |
-
comfy-angle
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
diffusers
|
| 41 |
-
protobuf
|
| 42 |
-
insightface
|
| 43 |
-
huggingface-hub
|
| 44 |
-
imageio
|
| 45 |
-
spaces
|
| 46 |
-
sageattention @ https://huggingface.co/RioShiina/Sage-Attention-ZeroGPU-Space-Build/resolve/main/sageattention-2.2.0-cp312-cp312-linux_x86_64.whl
|
|
|
|
| 1 |
+
comfyui-frontend-package==1.45.20
|
| 2 |
+
comfyui-workflow-templates==0.11.1
|
| 3 |
+
comfyui-embedded-docs==0.5.6
|
| 4 |
+
torch
|
| 5 |
+
torchsde
|
| 6 |
+
torchvision
|
| 7 |
+
torchaudio
|
| 8 |
+
numpy>=1.25.0
|
| 9 |
+
einops
|
| 10 |
+
transformers>=4.50.3
|
| 11 |
+
tokenizers>=0.13.3
|
| 12 |
+
sentencepiece
|
| 13 |
+
safetensors>=0.4.2
|
| 14 |
+
aiohttp>=3.11.8
|
| 15 |
+
yarl>=1.18.0
|
| 16 |
+
pyyaml
|
| 17 |
+
Pillow
|
| 18 |
+
scipy
|
| 19 |
+
tqdm
|
| 20 |
+
psutil
|
| 21 |
+
alembic
|
| 22 |
+
SQLAlchemy>=2.0.0
|
| 23 |
+
filelock
|
| 24 |
+
av>=16.0.0
|
| 25 |
+
comfy-kitchen==0.2.16
|
| 26 |
+
comfy-aimdo==0.4.10
|
| 27 |
+
requests
|
| 28 |
+
simpleeval>=1.0.0
|
| 29 |
+
blake3
|
| 30 |
+
|
| 31 |
+
#non essential dependencies:
|
| 32 |
+
kornia>=0.7.1
|
| 33 |
+
spandrel
|
| 34 |
+
pydantic~=2.0
|
| 35 |
+
pydantic-settings~=2.0
|
| 36 |
+
PyOpenGL>=3.1.8
|
| 37 |
+
comfy-angle
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
diffusers
|
| 41 |
+
protobuf
|
| 42 |
+
insightface
|
| 43 |
+
huggingface-hub
|
| 44 |
+
imageio
|
| 45 |
+
spaces
|
| 46 |
+
# sageattention @ https://huggingface.co/RioShiina/Sage-Attention-ZeroGPU-Space-Build/resolve/main/sageattention-2.2.0-cp312-cp312-linux_x86_64.whl
|
ui/layout.py
CHANGED
|
@@ -1,48 +1,131 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
return demo
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import shutil
|
| 3 |
+
import zipfile
|
| 4 |
+
import tempfile
|
| 5 |
+
import gradio as gr
|
| 6 |
+
from core.settings import *
|
| 7 |
+
|
| 8 |
+
from .shared import txt2img_ui, img2img_ui, inpaint_ui, outpaint_ui, hires_fix_ui
|
| 9 |
+
|
| 10 |
+
MAX_DYNAMIC_CONTROLS = 10
|
| 11 |
+
|
| 12 |
+
def build_ui(event_handler_function):
|
| 13 |
+
ui_components = {}
|
| 14 |
+
|
| 15 |
+
# Helper function to load thumbnails from the output folder.
|
| 16 |
+
# It returns a **deduplicated** list of absolute image file paths.
|
| 17 |
+
# Using a set ensures that even if the same filename appears multiple
|
| 18 |
+
# times (e.g., due to accidental duplicate saves), the Gallery tab will
|
| 19 |
+
# only display each image once.
|
| 20 |
+
def load_gallery_images():
|
| 21 |
+
"""Return a deduplicated list of image paths.
|
| 22 |
+
If multiple files share the same stem (e.g., ``img.png`` and ``img.webp``),
|
| 23 |
+
only the first encountered file (based on sorted order) is kept. This prevents
|
| 24 |
+
the Gallery tab from displaying duplicate visual entries that arise from
|
| 25 |
+
different extensions of the same generated image.
|
| 26 |
+
"""
|
| 27 |
+
output_dir = os.path.abspath(OUTPUT_DIR)
|
| 28 |
+
if not os.path.isdir(output_dir):
|
| 29 |
+
return []
|
| 30 |
+
seen_bases = set()
|
| 31 |
+
image_paths = []
|
| 32 |
+
for fname in sorted(os.listdir(output_dir)):
|
| 33 |
+
low = fname.lower()
|
| 34 |
+
if low.endswith((".png", ".jpg", ".jpeg", ".gif", ".webp")):
|
| 35 |
+
base = os.path.splitext(fname)[0]
|
| 36 |
+
if base not in seen_bases:
|
| 37 |
+
seen_bases.add(base)
|
| 38 |
+
image_paths.append(os.path.join(output_dir, fname))
|
| 39 |
+
return image_paths
|
| 40 |
+
|
| 41 |
+
# Clear all images from the gallery folder and return an empty list for the UI.
|
| 42 |
+
def clear_gallery_images():
|
| 43 |
+
"""Remove every file in ``OUTPUT_DIR`` and return an empty list for the gallery.
|
| 44 |
+
This provides a quick way for users to reset the gallery view.
|
| 45 |
+
"""
|
| 46 |
+
output_dir = os.path.abspath(OUTPUT_DIR)
|
| 47 |
+
if os.path.isdir(output_dir):
|
| 48 |
+
for f in os.listdir(output_dir):
|
| 49 |
+
try:
|
| 50 |
+
os.remove(os.path.join(output_dir, f))
|
| 51 |
+
except Exception:
|
| 52 |
+
pass
|
| 53 |
+
return []
|
| 54 |
+
|
| 55 |
+
# Create a zip archive of all images in the gallery for downloading.
|
| 56 |
+
def download_gallery_zip():
|
| 57 |
+
"""Package all files in ``OUTPUT_DIR`` into a temporary zip file.
|
| 58 |
+
Returns the file path which Gradio will serve as a downloadable file.
|
| 59 |
+
"""
|
| 60 |
+
output_dir = os.path.abspath(OUTPUT_DIR)
|
| 61 |
+
tmp_fd, tmp_path = tempfile.mkstemp(suffix=".zip")
|
| 62 |
+
os.close(tmp_fd)
|
| 63 |
+
with zipfile.ZipFile(tmp_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
| 64 |
+
if os.path.isdir(output_dir):
|
| 65 |
+
for fname in sorted(os.listdir(output_dir)):
|
| 66 |
+
file_path = os.path.join(output_dir, fname)
|
| 67 |
+
if os.path.isfile(file_path):
|
| 68 |
+
zf.write(file_path, arcname=fname)
|
| 69 |
+
return tmp_path
|
| 70 |
+
|
| 71 |
+
# All UI components, including tabs, must be created inside the same Gradio Blocks context.
|
| 72 |
+
with gr.Blocks() as demo:
|
| 73 |
+
gr.Markdown("# ImageGen")
|
| 74 |
+
gr.Markdown(
|
| 75 |
+
"This demo is a streamlined version of the [Comfy web UI](https://github.com/RioShiina47/comfy-webui)'s [ImageGen](https://huggingface.co/spaces/RioShiina/ImageGen) functionality. "
|
| 76 |
+
"Other spaces: [ImageGen1](https://huggingface.co/spaces/RioShiina/ImageGen1), "
|
| 77 |
+
"[ImageGen2](https://huggingface.co/spaces/RioShiina/ImageGen2), "
|
| 78 |
+
"[ImageGen3](https://huggingface.co/spaces/RioShiina/ImageGen3), "
|
| 79 |
+
"[ImageGen4](https://huggingface.co/spaces/RioShiina/ImageGen4), "
|
| 80 |
+
"[ImageGen5](https://huggingface.co/spaces/RioShiina/ImageGen5), "
|
| 81 |
+
"[ImageGen6](https://huggingface.co/spaces/RioShiina/ImageGen6), "
|
| 82 |
+
"[ImageGen7](https://huggingface.co/spaces/RioShiina/ImageGen7), "
|
| 83 |
+
"[ImageGen8](https://huggingface.co/spaces/RioShiina/ImageGen8)"
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
# Tabs container β now correctly nested within the Blocks context.
|
| 87 |
+
with gr.Tabs(elem_id="tabs_container") as tabs:
|
| 88 |
+
with gr.TabItem("Txt2Img", id=0):
|
| 89 |
+
ui_components.update(txt2img_ui.create_ui())
|
| 90 |
+
|
| 91 |
+
with gr.TabItem("Img2Img", id=1):
|
| 92 |
+
ui_components.update(img2img_ui.create_ui())
|
| 93 |
+
|
| 94 |
+
with gr.TabItem("Inpaint", id=2):
|
| 95 |
+
ui_components.update(inpaint_ui.create_ui())
|
| 96 |
+
|
| 97 |
+
with gr.TabItem("Outpaint", id=3):
|
| 98 |
+
ui_components.update(outpaint_ui.create_ui())
|
| 99 |
+
|
| 100 |
+
with gr.TabItem("Hires. Fix", id=4):
|
| 101 |
+
ui_components.update(hires_fix_ui.create_ui())
|
| 102 |
+
|
| 103 |
+
# New Gallery tab to display generated images
|
| 104 |
+
with gr.TabItem("Gallery", id=5):
|
| 105 |
+
# Row of control buttons for the gallery.
|
| 106 |
+
with gr.Row():
|
| 107 |
+
refresh_btn = gr.Button("Refresh Gallery", variant="secondary")
|
| 108 |
+
clear_btn = gr.Button("Clear Gallery", variant="secondary")
|
| 109 |
+
download_btn = gr.Button("Download All", variant="secondary")
|
| 110 |
+
# Main gallery component.
|
| 111 |
+
gallery = gr.Gallery(label="Generated Images", show_label=False, columns=4, height="auto", type="filepath")
|
| 112 |
+
ui_components["gallery"] = gallery
|
| 113 |
+
# Hidden file component for zip download.
|
| 114 |
+
download_file = gr.File(label="Download", visible=False)
|
| 115 |
+
# Bind buttons.
|
| 116 |
+
refresh_btn.click(fn=load_gallery_images, outputs=gallery)
|
| 117 |
+
clear_btn.click(fn=clear_gallery_images, outputs=gallery)
|
| 118 |
+
download_btn.click(fn=download_gallery_zip, outputs=download_file)
|
| 119 |
+
|
| 120 |
+
ui_components["tabs"] = tabs
|
| 121 |
+
ui_components["image_gen_tabs"] = tabs
|
| 122 |
+
|
| 123 |
+
gr.Markdown("<div style='text-align: center; margin-top: 20px;'>Made by RioShiina with β€οΈ<br><a href='https://github.com/RioShiina47' target='_blank'>GitHub</a> | <a href='https://huggingface.co/RioShiina' target='_blank'>Hugging Face</a> | <a href='https://civitai.com/user/RioShiina' target='_blank'>Civitai</a></div>")
|
| 124 |
+
|
| 125 |
+
# Load gallery images on startup using the helper defined earlier.
|
| 126 |
+
demo.load(fn=load_gallery_images, inputs=[], outputs=ui_components["gallery"])
|
| 127 |
+
|
| 128 |
+
# Connect event handlers (e.g., button clicks) with the UI components.
|
| 129 |
+
event_handler_function(ui_components, demo)
|
| 130 |
+
|
| 131 |
return demo
|
ui/shared/ui_components.py
CHANGED
|
@@ -1,672 +1,673 @@
|
|
| 1 |
-
import gradio as gr
|
| 2 |
-
from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
|
| 3 |
-
from core.settings import (
|
| 4 |
-
MAX_LORAS, LORA_SOURCE_CHOICES, MAX_EMBEDDINGS, MAX_CONDITIONINGS,
|
| 5 |
-
MAX_CONTROLNETS, MAX_IPADAPTERS, RESOLUTION_MAP, ARCHITECTURES_CONFIG,
|
| 6 |
-
MODEL_MAP_CHECKPOINT, MODEL_TYPE_MAP, FEATURES_CONFIG, ARCH_CATEGORIES_MAP,
|
| 7 |
-
VAE_DIR, MODEL_DEFAULTS_CONFIG
|
| 8 |
-
)
|
| 9 |
-
import yaml
|
| 10 |
-
import os
|
| 11 |
-
from functools import lru_cache
|
| 12 |
-
from utils.app_utils import save_uploaded_file_with_hash
|
| 13 |
-
|
| 14 |
-
default_model_name = list(MODEL_MAP_CHECKPOINT.keys())[0] if MODEL_MAP_CHECKPOINT else None
|
| 15 |
-
default_m_type = MODEL_TYPE_MAP.get(default_model_name, "SDXL") if default_model_name else "SDXL"
|
| 16 |
-
default_architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
|
| 17 |
-
default_arch_model_type = default_architectures_dict.get(default_m_type, {}).get("model_type", default_m_type.lower().replace(" ", "").replace(".", ""))
|
| 18 |
-
default_arch_features = FEATURES_CONFIG.get(default_arch_model_type, FEATURES_CONFIG.get('default', {}))
|
| 19 |
-
default_enabled_chains = default_arch_features.get('enabled_chains', [])
|
| 20 |
-
|
| 21 |
-
default_vals = MODEL_DEFAULTS_CONFIG.get('Default', {})
|
| 22 |
-
DEFAULT_STEPS = default_vals.get('steps', 20)
|
| 23 |
-
DEFAULT_CFG = default_vals.get('cfg', 5.0)
|
| 24 |
-
DEFAULT_SAMPLER = default_vals.get('sampler_name', 'euler')
|
| 25 |
-
DEFAULT_SCHEDULER = default_vals.get('scheduler', 'simple')
|
| 26 |
-
DEFAULT_POS_PROMPT = default_vals.get('positive_prompt', '')
|
| 27 |
-
DEFAULT_NEG_PROMPT = default_vals.get('negative_prompt', '')
|
| 28 |
-
|
| 29 |
-
@lru_cache(maxsize=1)
|
| 30 |
-
def get_ipadapter_config_from_yaml():
|
| 31 |
-
try:
|
| 32 |
-
_PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 33 |
-
_IPADAPTER_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'ipadapter.yaml')
|
| 34 |
-
with open(_IPADAPTER_LIST_PATH, 'r', encoding='utf-8') as f:
|
| 35 |
-
config = yaml.safe_load(f)
|
| 36 |
-
return config
|
| 37 |
-
except Exception as e:
|
| 38 |
-
print(f"Warning: Could not load ipadapter.yaml for UI components: {e}")
|
| 39 |
-
return {}
|
| 40 |
-
|
| 41 |
-
def get_ipadapter_presets(arch="SDXL"):
|
| 42 |
-
config = get_ipadapter_config_from_yaml()
|
| 43 |
-
presets = []
|
| 44 |
-
if config:
|
| 45 |
-
std_presets = config.get("IPAdapter_presets", {}).get(arch, [])
|
| 46 |
-
face_presets = config.get("IPAdapter_FaceID_presets", {}).get(arch, [])
|
| 47 |
-
if std_presets:
|
| 48 |
-
presets.extend(std_presets)
|
| 49 |
-
if face_presets:
|
| 50 |
-
presets.extend(face_presets)
|
| 51 |
-
return presets if presets else ["STANDARD (medium strength)"]
|
| 52 |
-
|
| 53 |
-
def create_model_architecture_filter_ui(prefix):
|
| 54 |
-
components = {}
|
| 55 |
-
ordered_architectures = ARCHITECTURES_CONFIG.get("architecture_order", [])
|
| 56 |
-
choices = ["ALL"] + ordered_architectures
|
| 57 |
-
|
| 58 |
-
components[f'model_arch_{prefix}'] = gr.Radio(
|
| 59 |
-
label="Model Architecture",
|
| 60 |
-
choices=choices,
|
| 61 |
-
value="ALL",
|
| 62 |
-
interactive=True,
|
| 63 |
-
visible=True
|
| 64 |
-
)
|
| 65 |
-
return components
|
| 66 |
-
|
| 67 |
-
def create_category_filter_ui(prefix):
|
| 68 |
-
valid_cats = list(set(cat for cats in ARCH_CATEGORIES_MAP.values() for cat in cats))
|
| 69 |
-
cat_choices = ["ALL"] + sorted(valid_cats)
|
| 70 |
-
|
| 71 |
-
components = {}
|
| 72 |
-
components[f'model_cat_{prefix}'] = gr.Dropdown(
|
| 73 |
-
label="Filter Models",
|
| 74 |
-
choices=cat_choices,
|
| 75 |
-
value="ALL",
|
| 76 |
-
interactive=True,
|
| 77 |
-
scale=1,
|
| 78 |
-
allow_custom_value=True
|
| 79 |
-
)
|
| 80 |
-
return components
|
| 81 |
-
|
| 82 |
-
def create_base_parameter_ui(prefix, defaults=None):
|
| 83 |
-
if defaults is None:
|
| 84 |
-
defaults = {}
|
| 85 |
-
|
| 86 |
-
components = {}
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
components[f'
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
components[f'
|
| 153 |
-
components[f'
|
| 154 |
-
components[f'
|
| 155 |
-
components[f'
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
key('
|
| 175 |
-
key('
|
| 176 |
-
key('
|
| 177 |
-
key('
|
| 178 |
-
key('
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
components[key('
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
key('
|
| 218 |
-
key('
|
| 219 |
-
key('
|
| 220 |
-
key('
|
| 221 |
-
key('
|
| 222 |
-
key('
|
| 223 |
-
key('
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
components[key('
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
key('
|
| 265 |
-
key('
|
| 266 |
-
key('
|
| 267 |
-
key('
|
| 268 |
-
key('
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
components[key('
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
components[key('
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
key('
|
| 338 |
-
key('
|
| 339 |
-
key('
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
components[key('
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
all_ipa_components_flat
|
| 358 |
-
|
| 359 |
-
components[key('
|
| 360 |
-
components[key('
|
| 361 |
-
components[key('
|
| 362 |
-
components[key('
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
key('
|
| 380 |
-
key('
|
| 381 |
-
key('
|
| 382 |
-
key('
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
components[key('
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
key('
|
| 418 |
-
key('
|
| 419 |
-
key('
|
| 420 |
-
key('
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
components[key('
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
key('
|
| 455 |
-
key('
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
components[key('
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
key('
|
| 488 |
-
key('
|
| 489 |
-
key('
|
| 490 |
-
key('
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
components[key('
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
key('
|
| 526 |
-
key('
|
| 527 |
-
key('
|
| 528 |
-
key('
|
| 529 |
-
key('
|
| 530 |
-
key('
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
components[key('
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
components[key('
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
components[key('
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
components[key('
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
components[key('
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
components[key('
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
|
|
|
| 672 |
return components
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from comfy_integration.nodes import SAMPLER_CHOICES, SCHEDULER_CHOICES
|
| 3 |
+
from core.settings import (
|
| 4 |
+
MAX_LORAS, LORA_SOURCE_CHOICES, MAX_EMBEDDINGS, MAX_CONDITIONINGS,
|
| 5 |
+
MAX_CONTROLNETS, MAX_IPADAPTERS, RESOLUTION_MAP, ARCHITECTURES_CONFIG,
|
| 6 |
+
MODEL_MAP_CHECKPOINT, MODEL_TYPE_MAP, FEATURES_CONFIG, ARCH_CATEGORIES_MAP,
|
| 7 |
+
VAE_DIR, MODEL_DEFAULTS_CONFIG
|
| 8 |
+
)
|
| 9 |
+
import yaml
|
| 10 |
+
import os
|
| 11 |
+
from functools import lru_cache
|
| 12 |
+
from utils.app_utils import save_uploaded_file_with_hash
|
| 13 |
+
|
| 14 |
+
default_model_name = list(MODEL_MAP_CHECKPOINT.keys())[0] if MODEL_MAP_CHECKPOINT else None
|
| 15 |
+
default_m_type = MODEL_TYPE_MAP.get(default_model_name, "SDXL") if default_model_name else "SDXL"
|
| 16 |
+
default_architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
|
| 17 |
+
default_arch_model_type = default_architectures_dict.get(default_m_type, {}).get("model_type", default_m_type.lower().replace(" ", "").replace(".", ""))
|
| 18 |
+
default_arch_features = FEATURES_CONFIG.get(default_arch_model_type, FEATURES_CONFIG.get('default', {}))
|
| 19 |
+
default_enabled_chains = default_arch_features.get('enabled_chains', [])
|
| 20 |
+
|
| 21 |
+
default_vals = MODEL_DEFAULTS_CONFIG.get('Default', {})
|
| 22 |
+
DEFAULT_STEPS = default_vals.get('steps', 20)
|
| 23 |
+
DEFAULT_CFG = default_vals.get('cfg', 5.0)
|
| 24 |
+
DEFAULT_SAMPLER = default_vals.get('sampler_name', 'euler')
|
| 25 |
+
DEFAULT_SCHEDULER = default_vals.get('scheduler', 'simple')
|
| 26 |
+
DEFAULT_POS_PROMPT = default_vals.get('positive_prompt', '')
|
| 27 |
+
DEFAULT_NEG_PROMPT = default_vals.get('negative_prompt', '')
|
| 28 |
+
|
| 29 |
+
@lru_cache(maxsize=1)
|
| 30 |
+
def get_ipadapter_config_from_yaml():
|
| 31 |
+
try:
|
| 32 |
+
_PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 33 |
+
_IPADAPTER_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'ipadapter.yaml')
|
| 34 |
+
with open(_IPADAPTER_LIST_PATH, 'r', encoding='utf-8') as f:
|
| 35 |
+
config = yaml.safe_load(f)
|
| 36 |
+
return config
|
| 37 |
+
except Exception as e:
|
| 38 |
+
print(f"Warning: Could not load ipadapter.yaml for UI components: {e}")
|
| 39 |
+
return {}
|
| 40 |
+
|
| 41 |
+
def get_ipadapter_presets(arch="SDXL"):
|
| 42 |
+
config = get_ipadapter_config_from_yaml()
|
| 43 |
+
presets = []
|
| 44 |
+
if config:
|
| 45 |
+
std_presets = config.get("IPAdapter_presets", {}).get(arch, [])
|
| 46 |
+
face_presets = config.get("IPAdapter_FaceID_presets", {}).get(arch, [])
|
| 47 |
+
if std_presets:
|
| 48 |
+
presets.extend(std_presets)
|
| 49 |
+
if face_presets:
|
| 50 |
+
presets.extend(face_presets)
|
| 51 |
+
return presets if presets else ["STANDARD (medium strength)"]
|
| 52 |
+
|
| 53 |
+
def create_model_architecture_filter_ui(prefix):
|
| 54 |
+
components = {}
|
| 55 |
+
ordered_architectures = ARCHITECTURES_CONFIG.get("architecture_order", [])
|
| 56 |
+
choices = ["ALL"] + ordered_architectures
|
| 57 |
+
|
| 58 |
+
components[f'model_arch_{prefix}'] = gr.Radio(
|
| 59 |
+
label="Model Architecture",
|
| 60 |
+
choices=choices,
|
| 61 |
+
value="ALL",
|
| 62 |
+
interactive=True,
|
| 63 |
+
visible=True
|
| 64 |
+
)
|
| 65 |
+
return components
|
| 66 |
+
|
| 67 |
+
def create_category_filter_ui(prefix):
|
| 68 |
+
valid_cats = list(set(cat for cats in ARCH_CATEGORIES_MAP.values() for cat in cats))
|
| 69 |
+
cat_choices = ["ALL"] + sorted(valid_cats)
|
| 70 |
+
|
| 71 |
+
components = {}
|
| 72 |
+
components[f'model_cat_{prefix}'] = gr.Dropdown(
|
| 73 |
+
label="Filter Models",
|
| 74 |
+
choices=cat_choices,
|
| 75 |
+
value="ALL",
|
| 76 |
+
interactive=True,
|
| 77 |
+
scale=1,
|
| 78 |
+
allow_custom_value=True
|
| 79 |
+
)
|
| 80 |
+
return components
|
| 81 |
+
|
| 82 |
+
def create_base_parameter_ui(prefix, defaults=None):
|
| 83 |
+
if defaults is None:
|
| 84 |
+
defaults = {}
|
| 85 |
+
|
| 86 |
+
components = {}
|
| 87 |
+
# Aspect Ratio
|
| 88 |
+
components[f'aspect_ratio_{prefix}'] = gr.Dropdown(
|
| 89 |
+
label="Aspect Ratio",
|
| 90 |
+
choices=list(RESOLUTION_MAP.get('sdxl', {}).keys()),
|
| 91 |
+
value="1:1 (Square)",
|
| 92 |
+
interactive=True,
|
| 93 |
+
allow_custom_value=True
|
| 94 |
+
)
|
| 95 |
+
# Width & Height
|
| 96 |
+
components[f'width_{prefix}'] = gr.Number(label="Width", value=defaults.get('w', 1024), interactive=True)
|
| 97 |
+
components[f'height_{prefix}'] = gr.Number(label="Height", value=defaults.get('h', 1024), interactive=True)
|
| 98 |
+
# Sampler & Scheduler
|
| 99 |
+
components[f'sampler_{prefix}'] = gr.Dropdown(
|
| 100 |
+
label="Sampler",
|
| 101 |
+
choices=SAMPLER_CHOICES,
|
| 102 |
+
value=DEFAULT_SAMPLER if DEFAULT_SAMPLER in SAMPLER_CHOICES else (SAMPLER_CHOICES[0] if SAMPLER_CHOICES else 'euler')
|
| 103 |
+
)
|
| 104 |
+
components[f'scheduler_{prefix}'] = gr.Dropdown(
|
| 105 |
+
label="Scheduler",
|
| 106 |
+
choices=SCHEDULER_CHOICES,
|
| 107 |
+
value=DEFAULT_SCHEDULER if DEFAULT_SCHEDULER in SCHEDULER_CHOICES else (SCHEDULER_CHOICES[0] if SCHEDULER_CHOICES else 'simple')
|
| 108 |
+
)
|
| 109 |
+
# Steps & CFG
|
| 110 |
+
components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=DEFAULT_STEPS)
|
| 111 |
+
components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=DEFAULT_CFG)
|
| 112 |
+
# Seed & Batch Size
|
| 113 |
+
components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
|
| 114 |
+
components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
|
| 115 |
+
# Clip Skip & Guidance (FLUX) & ZeroGPU Duration
|
| 116 |
+
components[f'clip_skip_{prefix}'] = gr.Slider(label="Clip Skip", minimum=1, maximum=2, step=1, value=1, visible=False, interactive=True)
|
| 117 |
+
components[f'guidance_{prefix}'] = gr.Slider(label="Guidance (FLUX)", minimum=1.0, maximum=10.0, step=0.1, value=3.5, visible=False, interactive=True)
|
| 118 |
+
components[f'zero_gpu_{prefix}'] = gr.Number(label="ZeroGPU Duration (s)", value=None, placeholder="Default: 60s, Max: 120s", info="Optional: Set how long to reserve the GPU.")
|
| 119 |
+
|
| 120 |
+
return components
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def create_lora_settings_ui(prefix: str):
|
| 124 |
+
components = {}
|
| 125 |
+
|
| 126 |
+
lora_rows, lora_sources, lora_ids, lora_scales, lora_uploads = [], [], [], [], []
|
| 127 |
+
|
| 128 |
+
with gr.Accordion("LoRA Settings", open=False, visible=('lora' in default_enabled_chains)) as lora_accordion:
|
| 129 |
+
components[f'lora_accordion_{prefix}'] = lora_accordion
|
| 130 |
+
gr.Markdown("π‘ **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button. When downloading from Hugging Face, please use the format: `repo_id/filename.extension` or `repo_id/folder_path/filename.extension` (e.g., `lightx2v/Qwen-Image-Lightning/Qwen-Image-Lightning-4steps-V2.0-bf16.safetensors`).")
|
| 131 |
+
components[f'lora_count_state_{prefix}'] = gr.State(1)
|
| 132 |
+
|
| 133 |
+
# Wrap LoRA controls in a column to ensure proper hierarchical parenting
|
| 134 |
+
with gr.Column() as lora_container:
|
| 135 |
+
for i in range(MAX_LORAS):
|
| 136 |
+
with gr.Row() as row:
|
| 137 |
+
source = gr.Dropdown(label=f"LoRA Source {i+1}", choices=LORA_SOURCE_CHOICES, value=LORA_SOURCE_CHOICES[0], scale=1)
|
| 138 |
+
lora_id = gr.Textbox(label="Civitai Version ID / HF file / Upload File", scale=2, type="text")
|
| 139 |
+
scale = gr.Slider(label=f"Scale", minimum=0.0, maximum=2.0, step=0.05, value=1.0, scale=1)
|
| 140 |
+
upload = gr.UploadButton(label="Upload", file_types=[".safetensors"], scale=1)
|
| 141 |
+
|
| 142 |
+
lora_rows.append(row)
|
| 143 |
+
lora_sources.append(source)
|
| 144 |
+
lora_ids.append(lora_id)
|
| 145 |
+
lora_scales.append(scale)
|
| 146 |
+
lora_uploads.append(upload)
|
| 147 |
+
|
| 148 |
+
with gr.Row():
|
| 149 |
+
components[f'add_lora_button_{prefix}'] = gr.Button("Add LoRA", variant="secondary")
|
| 150 |
+
components[f'delete_lora_button_{prefix}'] = gr.Button("Remove LoRA", variant="secondary", visible=False)
|
| 151 |
+
|
| 152 |
+
components[f'lora_rows_{prefix}'] = lora_rows
|
| 153 |
+
components[f'lora_sources_{prefix}'] = lora_sources
|
| 154 |
+
components[f'lora_ids_{prefix}'] = lora_ids
|
| 155 |
+
components[f'lora_scales_{prefix}'] = lora_scales
|
| 156 |
+
components[f'lora_uploads_{prefix}'] = lora_uploads
|
| 157 |
+
|
| 158 |
+
all_lora_components_flat = []
|
| 159 |
+
for i in range(MAX_LORAS):
|
| 160 |
+
all_lora_components_flat.extend([lora_sources[i], lora_ids[i], lora_scales[i], lora_uploads[i]])
|
| 161 |
+
components[f'all_lora_components_flat_{prefix}'] = all_lora_components_flat
|
| 162 |
+
|
| 163 |
+
return components
|
| 164 |
+
|
| 165 |
+
def create_controlnet_ui(prefix: str, max_units=MAX_CONTROLNETS):
|
| 166 |
+
components = {}
|
| 167 |
+
key = lambda name: f"{name}_{prefix}"
|
| 168 |
+
|
| 169 |
+
with gr.Accordion("ControlNet Settings", open=False, visible=('controlnet' in default_enabled_chains)) as accordion:
|
| 170 |
+
components[key('controlnet_accordion')] = accordion
|
| 171 |
+
|
| 172 |
+
cn_rows, images, series, types, strengths, filepaths = [], [], [], [], [], []
|
| 173 |
+
components.update({
|
| 174 |
+
key('controlnet_rows'): cn_rows,
|
| 175 |
+
key('controlnet_images'): images,
|
| 176 |
+
key('controlnet_series'): series,
|
| 177 |
+
key('controlnet_types'): types,
|
| 178 |
+
key('controlnet_strengths'): strengths,
|
| 179 |
+
key('controlnet_filepaths'): filepaths
|
| 180 |
+
})
|
| 181 |
+
|
| 182 |
+
for i in range(max_units):
|
| 183 |
+
with gr.Row(visible=(i < 1)) as row:
|
| 184 |
+
with gr.Column(scale=1):
|
| 185 |
+
images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
|
| 186 |
+
with gr.Column(scale=2):
|
| 187 |
+
types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
|
| 188 |
+
series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
|
| 189 |
+
strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
|
| 190 |
+
filepaths.append(gr.State(None))
|
| 191 |
+
cn_rows.append(row)
|
| 192 |
+
|
| 193 |
+
with gr.Row():
|
| 194 |
+
components[key('add_controlnet_button')] = gr.Button("β Add ControlNet")
|
| 195 |
+
components[key('delete_controlnet_button')] = gr.Button("β Delete ControlNet", visible=False)
|
| 196 |
+
components[key('controlnet_count_state')] = gr.State(1)
|
| 197 |
+
|
| 198 |
+
all_cn_components_flat = []
|
| 199 |
+
for i in range(max_units):
|
| 200 |
+
all_cn_components_flat.extend([
|
| 201 |
+
images[i], types[i], series[i], strengths[i], filepaths[i]
|
| 202 |
+
])
|
| 203 |
+
components[key('all_controlnet_components_flat')] = all_cn_components_flat
|
| 204 |
+
|
| 205 |
+
return components
|
| 206 |
+
|
| 207 |
+
def create_anima_controlnet_lllite_ui(prefix: str, max_units=MAX_CONTROLNETS):
|
| 208 |
+
components = {}
|
| 209 |
+
key = lambda name: f"{name}_{prefix}"
|
| 210 |
+
|
| 211 |
+
with gr.Accordion("Anima ControlNet Lllite Settings", open=False, visible=('anima_controlnet_lllite' in default_enabled_chains)) as accordion:
|
| 212 |
+
components[key('anima_controlnet_lllite_accordion')] = accordion
|
| 213 |
+
gr.Markdown("π‘ **Tip:** Processed using the [kohya-ss/ComfyUI-Anima-LLLite](https://github.com/kohya-ss/ComfyUI-Anima-LLLite) node.")
|
| 214 |
+
|
| 215 |
+
cn_rows, images, series, types, strengths, filepaths, start_percents, end_percents = [], [], [], [], [], [], [], []
|
| 216 |
+
components.update({
|
| 217 |
+
key('anima_controlnet_lllite_rows'): cn_rows,
|
| 218 |
+
key('anima_controlnet_lllite_images'): images,
|
| 219 |
+
key('anima_controlnet_lllite_series'): series,
|
| 220 |
+
key('anima_controlnet_lllite_types'): types,
|
| 221 |
+
key('anima_controlnet_lllite_strengths'): strengths,
|
| 222 |
+
key('anima_controlnet_lllite_filepaths'): filepaths,
|
| 223 |
+
key('anima_controlnet_lllite_start_percents'): start_percents,
|
| 224 |
+
key('anima_controlnet_lllite_end_percents'): end_percents
|
| 225 |
+
})
|
| 226 |
+
|
| 227 |
+
for i in range(max_units):
|
| 228 |
+
with gr.Row(visible=(i < 1)) as row:
|
| 229 |
+
with gr.Column(scale=1):
|
| 230 |
+
images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
|
| 231 |
+
with gr.Column(scale=2):
|
| 232 |
+
types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
|
| 233 |
+
series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
|
| 234 |
+
strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
|
| 235 |
+
with gr.Row(visible=False):
|
| 236 |
+
start_percents.append(gr.State(0.0))
|
| 237 |
+
end_percents.append(gr.State(1.0))
|
| 238 |
+
filepaths.append(gr.State(None))
|
| 239 |
+
cn_rows.append(row)
|
| 240 |
+
|
| 241 |
+
with gr.Row():
|
| 242 |
+
components[key('add_anima_controlnet_lllite_button')] = gr.Button("β Add Lllite")
|
| 243 |
+
components[key('delete_anima_controlnet_lllite_button')] = gr.Button("β Delete Lllite", visible=False)
|
| 244 |
+
components[key('anima_controlnet_lllite_count_state')] = gr.State(1)
|
| 245 |
+
|
| 246 |
+
all_cn_components_flat = []
|
| 247 |
+
for i in range(max_units):
|
| 248 |
+
all_cn_components_flat.extend([
|
| 249 |
+
images[i], types[i], series[i], strengths[i], filepaths[i], start_percents[i], end_percents[i]
|
| 250 |
+
])
|
| 251 |
+
components[key('all_anima_controlnet_lllite_components_flat')] = all_cn_components_flat
|
| 252 |
+
|
| 253 |
+
return components
|
| 254 |
+
|
| 255 |
+
def create_diffsynth_controlnet_ui(prefix: str, max_units=MAX_CONTROLNETS):
|
| 256 |
+
components = {}
|
| 257 |
+
key = lambda name: f"{name}_{prefix}"
|
| 258 |
+
|
| 259 |
+
with gr.Accordion("DiffSynth ControlNet Settings", open=False, visible=('controlnet_model_patch' in default_enabled_chains)) as accordion:
|
| 260 |
+
components[key('diffsynth_controlnet_accordion')] = accordion
|
| 261 |
+
|
| 262 |
+
cn_rows, images, series, types, strengths, filepaths = [], [], [], [], [], []
|
| 263 |
+
components.update({
|
| 264 |
+
key('diffsynth_controlnet_rows'): cn_rows,
|
| 265 |
+
key('diffsynth_controlnet_images'): images,
|
| 266 |
+
key('diffsynth_controlnet_series'): series,
|
| 267 |
+
key('diffsynth_controlnet_types'): types,
|
| 268 |
+
key('diffsynth_controlnet_strengths'): strengths,
|
| 269 |
+
key('diffsynth_controlnet_filepaths'): filepaths
|
| 270 |
+
})
|
| 271 |
+
|
| 272 |
+
for i in range(max_units):
|
| 273 |
+
with gr.Row(visible=(i < 1)) as row:
|
| 274 |
+
with gr.Column(scale=1):
|
| 275 |
+
images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
|
| 276 |
+
with gr.Column(scale=2):
|
| 277 |
+
types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
|
| 278 |
+
series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
|
| 279 |
+
strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
|
| 280 |
+
filepaths.append(gr.State(None))
|
| 281 |
+
cn_rows.append(row)
|
| 282 |
+
|
| 283 |
+
with gr.Row():
|
| 284 |
+
components[key('add_diffsynth_controlnet_button')] = gr.Button("β Add DiffSynth ControlNet")
|
| 285 |
+
components[key('delete_diffsynth_controlnet_button')] = gr.Button("β Delete DiffSynth ControlNet", visible=False)
|
| 286 |
+
components[key('diffsynth_controlnet_count_state')] = gr.State(1)
|
| 287 |
+
|
| 288 |
+
all_cn_components_flat = []
|
| 289 |
+
for i in range(max_units):
|
| 290 |
+
all_cn_components_flat.extend([
|
| 291 |
+
images[i], types[i], series[i], strengths[i], filepaths[i]
|
| 292 |
+
])
|
| 293 |
+
components[key('all_diffsynth_controlnet_components_flat')] = all_cn_components_flat
|
| 294 |
+
|
| 295 |
+
return components
|
| 296 |
+
|
| 297 |
+
def create_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
|
| 298 |
+
components = {}
|
| 299 |
+
key = lambda name: f"{name}_{prefix}"
|
| 300 |
+
|
| 301 |
+
sdxl_presets = get_ipadapter_presets("SDXL")
|
| 302 |
+
default_preset = sdxl_presets[0] if sdxl_presets else None
|
| 303 |
+
|
| 304 |
+
with gr.Accordion("IPAdapter Settings", open=False, visible=('ipadapter' in default_enabled_chains)) as accordion:
|
| 305 |
+
components[key('ipadapter_accordion')] = accordion
|
| 306 |
+
gr.Markdown("π‘ **Tip:** Processed using the [cubiq/ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) node.")
|
| 307 |
+
|
| 308 |
+
with gr.Row():
|
| 309 |
+
components[key('ipadapter_final_preset')] = gr.Dropdown(
|
| 310 |
+
label="Preset (for all images)",
|
| 311 |
+
choices=sdxl_presets,
|
| 312 |
+
value=default_preset,
|
| 313 |
+
interactive=True,
|
| 314 |
+
allow_custom_value=True
|
| 315 |
+
)
|
| 316 |
+
components[key('ipadapter_embeds_scaling')] = gr.Dropdown(
|
| 317 |
+
label="Embeds Scaling",
|
| 318 |
+
choices=['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],
|
| 319 |
+
value='V only',
|
| 320 |
+
interactive=True
|
| 321 |
+
)
|
| 322 |
+
|
| 323 |
+
with gr.Row():
|
| 324 |
+
components[key('ipadapter_combine_method')] = gr.Dropdown(
|
| 325 |
+
label="Combine Method",
|
| 326 |
+
choices=["concat", "add", "subtract", "average", "norm average", "max", "min"],
|
| 327 |
+
value="concat",
|
| 328 |
+
interactive=True
|
| 329 |
+
)
|
| 330 |
+
components[key('ipadapter_final_weight')] = gr.Slider(label="Final Weight", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True)
|
| 331 |
+
components[key('ipadapter_final_lora_strength')] = gr.Slider(label="Final LoRA Strength", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True, visible=False)
|
| 332 |
+
|
| 333 |
+
gr.Markdown("---")
|
| 334 |
+
|
| 335 |
+
ipa_rows, images, weights, lora_strengths = [], [], [], []
|
| 336 |
+
components.update({
|
| 337 |
+
key('ipadapter_rows'): ipa_rows,
|
| 338 |
+
key('ipadapter_images'): images,
|
| 339 |
+
key('ipadapter_weights'): weights,
|
| 340 |
+
key('ipadapter_lora_strengths'): lora_strengths
|
| 341 |
+
})
|
| 342 |
+
|
| 343 |
+
for i in range(max_units):
|
| 344 |
+
with gr.Row(visible=(i < 1)) as row:
|
| 345 |
+
with gr.Column(scale=1):
|
| 346 |
+
images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
|
| 347 |
+
with gr.Column(scale=2):
|
| 348 |
+
weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
|
| 349 |
+
lora_strengths.append(gr.Slider(label="LoRA Strength", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True, visible=False))
|
| 350 |
+
ipa_rows.append(row)
|
| 351 |
+
|
| 352 |
+
with gr.Row():
|
| 353 |
+
components[key('add_ipadapter_button')] = gr.Button("β Add IPAdapter")
|
| 354 |
+
components[key('delete_ipadapter_button')] = gr.Button("β Delete IPAdapter", visible=False)
|
| 355 |
+
components[key('ipadapter_count_state')] = gr.State(1)
|
| 356 |
+
|
| 357 |
+
all_ipa_components_flat = images + weights + lora_strengths
|
| 358 |
+
all_ipa_components_flat += [
|
| 359 |
+
components[key('ipadapter_final_preset')],
|
| 360 |
+
components[key('ipadapter_final_weight')],
|
| 361 |
+
components[key('ipadapter_final_lora_strength')],
|
| 362 |
+
components[key('ipadapter_embeds_scaling')],
|
| 363 |
+
components[key('ipadapter_combine_method')],
|
| 364 |
+
]
|
| 365 |
+
components[key('all_ipadapter_components_flat')] = all_ipa_components_flat
|
| 366 |
+
|
| 367 |
+
return components
|
| 368 |
+
|
| 369 |
+
def create_flux1_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
|
| 370 |
+
components = {}
|
| 371 |
+
key = lambda name: f"{name}_{prefix}"
|
| 372 |
+
|
| 373 |
+
with gr.Accordion("IPAdapter Settings (FLUX.1)", open=False, visible=('flux1_ipadapter' in default_enabled_chains)) as accordion:
|
| 374 |
+
components[key('flux1_ipadapter_accordion')] = accordion
|
| 375 |
+
gr.Markdown("π‘ **Tip:** Processed using the [Shakker-Labs/ComfyUI-IPAdapter-Flux](https://github.com/Shakker-Labs/ComfyUI-IPAdapter-Flux) node.")
|
| 376 |
+
|
| 377 |
+
ipa_rows, images, weights, start_percents, end_percents = [], [], [], [], []
|
| 378 |
+
components.update({
|
| 379 |
+
key('flux1_ipadapter_rows'): ipa_rows,
|
| 380 |
+
key('flux1_ipadapter_images'): images,
|
| 381 |
+
key('flux1_ipadapter_weights'): weights,
|
| 382 |
+
key('flux1_ipadapter_start_percents'): start_percents,
|
| 383 |
+
key('flux1_ipadapter_end_percents'): end_percents,
|
| 384 |
+
})
|
| 385 |
+
|
| 386 |
+
for i in range(max_units):
|
| 387 |
+
with gr.Row(visible=(i < 1)) as row:
|
| 388 |
+
with gr.Column(scale=1):
|
| 389 |
+
images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
|
| 390 |
+
with gr.Column(scale=2):
|
| 391 |
+
weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True))
|
| 392 |
+
with gr.Row():
|
| 393 |
+
start_percents.append(gr.Slider(label="Start At", minimum=0.0, maximum=1.0, step=0.01, value=0.0, interactive=True))
|
| 394 |
+
end_percents.append(gr.Slider(label="End At", minimum=0.0, maximum=1.0, step=0.01, value=0.6, interactive=True))
|
| 395 |
+
ipa_rows.append(row)
|
| 396 |
+
|
| 397 |
+
with gr.Row():
|
| 398 |
+
components[key('add_flux1_ipadapter_button')] = gr.Button("β Add IPAdapter (FLUX)")
|
| 399 |
+
components[key('delete_flux1_ipadapter_button')] = gr.Button("β Delete IPAdapter (FLUX)", visible=False)
|
| 400 |
+
components[key('flux1_ipadapter_count_state')] = gr.State(1)
|
| 401 |
+
|
| 402 |
+
all_flux1_ipa_components_flat = images + weights + start_percents + end_percents
|
| 403 |
+
components[key('all_flux1_ipadapter_components_flat')] = all_flux1_ipa_components_flat
|
| 404 |
+
|
| 405 |
+
return components
|
| 406 |
+
|
| 407 |
+
def create_sd3_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
|
| 408 |
+
components = {}
|
| 409 |
+
key = lambda name: f"{name}_{prefix}"
|
| 410 |
+
|
| 411 |
+
with gr.Accordion("IPAdapter Settings (SD3)", open=False, visible=('sd3_ipadapter' in default_enabled_chains)) as accordion:
|
| 412 |
+
components[key('sd3_ipadapter_accordion')] = accordion
|
| 413 |
+
gr.Markdown("π‘ **Tip:** Processed using the [Slickytail/ComfyUI-InstantX-IPAdapter-SD3](https://github.com/Slickytail/ComfyUI-InstantX-IPAdapter-SD3) node.")
|
| 414 |
+
|
| 415 |
+
ipa_rows, images, weights, start_percents, end_percents = [], [], [], [], []
|
| 416 |
+
components.update({
|
| 417 |
+
key('sd3_ipadapter_rows'): ipa_rows,
|
| 418 |
+
key('sd3_ipadapter_images'): images,
|
| 419 |
+
key('sd3_ipadapter_weights'): weights,
|
| 420 |
+
key('sd3_ipadapter_start_percents'): start_percents,
|
| 421 |
+
key('sd3_ipadapter_end_percents'): end_percents,
|
| 422 |
+
})
|
| 423 |
+
|
| 424 |
+
for i in range(max_units):
|
| 425 |
+
with gr.Row(visible=(i < 1)) as row:
|
| 426 |
+
with gr.Column(scale=1):
|
| 427 |
+
images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
|
| 428 |
+
with gr.Column(scale=2):
|
| 429 |
+
weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=0.5, interactive=True))
|
| 430 |
+
with gr.Row():
|
| 431 |
+
start_percents.append(gr.Slider(label="Start At", minimum=0.0, maximum=1.0, step=0.01, value=0.0, interactive=True))
|
| 432 |
+
end_percents.append(gr.Slider(label="End At", minimum=0.0, maximum=1.0, step=0.01, value=1.0, interactive=True))
|
| 433 |
+
ipa_rows.append(row)
|
| 434 |
+
|
| 435 |
+
with gr.Row():
|
| 436 |
+
components[key('add_sd3_ipadapter_button')] = gr.Button("β Add IPAdapter (SD3)")
|
| 437 |
+
components[key('delete_sd3_ipadapter_button')] = gr.Button("β Delete IPAdapter (SD3)", visible=False)
|
| 438 |
+
components[key('sd3_ipadapter_count_state')] = gr.State(1)
|
| 439 |
+
|
| 440 |
+
all_sd3_ipa_components_flat = images + weights + start_percents + end_percents
|
| 441 |
+
components[key('all_sd3_ipadapter_components_flat')] = all_sd3_ipa_components_flat
|
| 442 |
+
|
| 443 |
+
return components
|
| 444 |
+
|
| 445 |
+
def create_style_ui(prefix: str):
|
| 446 |
+
components = {}
|
| 447 |
+
key = lambda name: f"{name}_{prefix}"
|
| 448 |
+
|
| 449 |
+
with gr.Accordion("Style Settings (FLUX.1)", open=False, visible=('style' in default_enabled_chains)) as accordion:
|
| 450 |
+
components[key('style_accordion')] = accordion
|
| 451 |
+
|
| 452 |
+
style_rows, images, strengths = [], [], []
|
| 453 |
+
components.update({
|
| 454 |
+
key('style_rows'): style_rows,
|
| 455 |
+
key('style_images'): images,
|
| 456 |
+
key('style_strengths'): strengths
|
| 457 |
+
})
|
| 458 |
+
|
| 459 |
+
for i in range(5):
|
| 460 |
+
with gr.Row(visible=(i < 1)) as row:
|
| 461 |
+
with gr.Column(scale=1):
|
| 462 |
+
images.append(gr.Image(label=f"Style Image {i+1}", type="pil", sources=["upload"], height=256))
|
| 463 |
+
with gr.Column(scale=2):
|
| 464 |
+
strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
|
| 465 |
+
style_rows.append(row)
|
| 466 |
+
|
| 467 |
+
with gr.Row():
|
| 468 |
+
components[key('add_style_button')] = gr.Button("β Add Style (FLUX)")
|
| 469 |
+
components[key('delete_style_button')] = gr.Button("β Delete Style (FLUX)", visible=False)
|
| 470 |
+
components[key('style_count_state')] = gr.State(1)
|
| 471 |
+
|
| 472 |
+
all_style_components_flat = images + strengths
|
| 473 |
+
components[key('all_style_components_flat')] = all_style_components_flat
|
| 474 |
+
|
| 475 |
+
return components
|
| 476 |
+
|
| 477 |
+
def create_embedding_ui(prefix: str):
|
| 478 |
+
components = {}
|
| 479 |
+
key = lambda name: f"{name}_{prefix}"
|
| 480 |
+
|
| 481 |
+
with gr.Accordion("Embedding Settings", open=False, visible=('embedding' in default_enabled_chains)) as accordion:
|
| 482 |
+
components[key('embedding_accordion')] = accordion
|
| 483 |
+
gr.Markdown("π‘ **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button. For example, entering the Version ID 456 will automatically save the file as \"civitai_456.safetensors\", and you will need to manually enter `embedding:civitai_456` in either your prompt or negative prompt to activate it.When downloading from Hugging Face, please use the format: repo_id/filename.extension or repo_id/folder_path/filename.extension (e.g., ilikebigturtles/lazypos/lazypos.safetensors or ilikebigturtles/lazyneg/lazyneg.safetensors). For Hugging Face files, you will need to enter embedding:filename (e.g., entering embedding:lazypos in your positive prompt, or embedding:lazyneg in your negative prompt) to activate it.")
|
| 484 |
+
|
| 485 |
+
embedding_rows, sources, ids, files, upload_buttons = [], [], [], [], []
|
| 486 |
+
components.update({
|
| 487 |
+
key('embedding_rows'): embedding_rows,
|
| 488 |
+
key('embeddings_sources'): sources,
|
| 489 |
+
key('embeddings_ids'): ids,
|
| 490 |
+
key('embeddings_files'): files,
|
| 491 |
+
key('embeddings_uploads'): upload_buttons
|
| 492 |
+
})
|
| 493 |
+
|
| 494 |
+
for i in range(MAX_EMBEDDINGS):
|
| 495 |
+
with gr.Row(visible=(i < 1)) as row:
|
| 496 |
+
sources.append(gr.Dropdown(label=f"Embedding Source {i+1}", choices=LORA_SOURCE_CHOICES, value="Civitai", scale=1, interactive=True))
|
| 497 |
+
ids.append(gr.Textbox(label="Civitai Version ID / HF file / Upload File", scale=3, interactive=True, type="text"))
|
| 498 |
+
upload_btn = gr.UploadButton("Upload", file_types=[".safetensors"], scale=1)
|
| 499 |
+
files.append(gr.State(None))
|
| 500 |
+
upload_buttons.append(upload_btn)
|
| 501 |
+
embedding_rows.append(row)
|
| 502 |
+
|
| 503 |
+
with gr.Row():
|
| 504 |
+
components[key('add_embedding_button')] = gr.Button("β Add Embedding")
|
| 505 |
+
components[key('delete_embedding_button')] = gr.Button("β Delete Embedding", visible=False)
|
| 506 |
+
components[key('embedding_count_state')] = gr.State(1)
|
| 507 |
+
|
| 508 |
+
all_embedding_components_flat = []
|
| 509 |
+
for i in range(MAX_EMBEDDINGS):
|
| 510 |
+
all_embedding_components_flat.extend([sources[i], ids[i], files[i]])
|
| 511 |
+
components[key('all_embedding_components_flat')] = all_embedding_components_flat
|
| 512 |
+
|
| 513 |
+
return components
|
| 514 |
+
|
| 515 |
+
def create_conditioning_ui(prefix: str):
|
| 516 |
+
components = {}
|
| 517 |
+
key = lambda name: f"{name}_{prefix}"
|
| 518 |
+
|
| 519 |
+
with gr.Accordion("Conditioning Settings", open=False, visible=('conditioning' in default_enabled_chains)) as accordion:
|
| 520 |
+
components[key('conditioning_accordion')] = accordion
|
| 521 |
+
gr.Markdown("π‘ **Tip:** Define rectangular areas and assign specific prompts to them. Coordinates (X, Y) start from the top-left corner.")
|
| 522 |
+
|
| 523 |
+
cond_rows, prompts, widths, heights, xs, ys, strengths = [], [], [], [], [], [], []
|
| 524 |
+
components.update({
|
| 525 |
+
key('conditioning_rows'): cond_rows,
|
| 526 |
+
key('conditioning_prompts'): prompts,
|
| 527 |
+
key('conditioning_widths'): widths,
|
| 528 |
+
key('conditioning_heights'): heights,
|
| 529 |
+
key('conditioning_xs'): xs,
|
| 530 |
+
key('conditioning_ys'): ys,
|
| 531 |
+
key('conditioning_strengths'): strengths
|
| 532 |
+
})
|
| 533 |
+
|
| 534 |
+
for i in range(MAX_CONDITIONINGS):
|
| 535 |
+
with gr.Column(visible=(i < 1)) as row_wrapper:
|
| 536 |
+
prompts.append(gr.Textbox(label=f"Area Prompt {i+1}", lines=2, interactive=True))
|
| 537 |
+
with gr.Row():
|
| 538 |
+
xs.append(gr.Number(label="X", value=0, interactive=True, step=8, scale=1))
|
| 539 |
+
ys.append(gr.Number(label="Y", value=0, interactive=True, step=8, scale=1))
|
| 540 |
+
widths.append(gr.Number(label="Width", value=512, interactive=True, step=8, scale=1))
|
| 541 |
+
heights.append(gr.Number(label="Height", value=512, interactive=True, step=8, scale=1))
|
| 542 |
+
strengths.append(gr.Slider(label="Strength", minimum=0.1, maximum=2.0, step=0.05, value=1.0, interactive=True, scale=2))
|
| 543 |
+
cond_rows.append(row_wrapper)
|
| 544 |
+
|
| 545 |
+
with gr.Row():
|
| 546 |
+
components[key('add_conditioning_button')] = gr.Button("β Add Area")
|
| 547 |
+
components[key('delete_conditioning_button')] = gr.Button("β Delete Area", visible=False)
|
| 548 |
+
components[key('conditioning_count_state')] = gr.State(1)
|
| 549 |
+
|
| 550 |
+
all_cond_components_flat = prompts + widths + heights + xs + ys + strengths
|
| 551 |
+
components[key('all_conditioning_components_flat')] = all_cond_components_flat
|
| 552 |
+
|
| 553 |
+
return components
|
| 554 |
+
|
| 555 |
+
def on_vae_upload(file_obj):
|
| 556 |
+
if not file_obj:
|
| 557 |
+
return gr.update(), gr.update(), None
|
| 558 |
+
|
| 559 |
+
hashed_filename = save_uploaded_file_with_hash(file_obj, VAE_DIR)
|
| 560 |
+
return hashed_filename, "File", file_obj
|
| 561 |
+
|
| 562 |
+
def create_vae_override_ui(prefix: str):
|
| 563 |
+
components = {}
|
| 564 |
+
key = lambda name: f"{name}_{prefix}"
|
| 565 |
+
source_choices = ["None"] + LORA_SOURCE_CHOICES
|
| 566 |
+
|
| 567 |
+
with gr.Accordion("VAE Settings (Override)", open=False, visible=('vae' in default_enabled_chains)) as vae_accordion:
|
| 568 |
+
components[key('vae_accordion')] = vae_accordion
|
| 569 |
+
gr.Markdown("π‘ **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button. When downloading from Hugging Face, please use the format: `repo_id/filename.extension` or `repo_id/folder_path/filename.extension` (e.g., `madebyollin/sdxl-vae-fp16-fix/sdxl_vae.safetensors`).")
|
| 570 |
+
with gr.Row():
|
| 571 |
+
components[key('vae_source')] = gr.Dropdown(
|
| 572 |
+
label="VAE Source",
|
| 573 |
+
choices=source_choices,
|
| 574 |
+
value="None",
|
| 575 |
+
scale=1,
|
| 576 |
+
interactive=True
|
| 577 |
+
)
|
| 578 |
+
components[key('vae_id')] = gr.Textbox(
|
| 579 |
+
label="Civitai Version ID / HF file / Upload File",
|
| 580 |
+
scale=3,
|
| 581 |
+
interactive=True,
|
| 582 |
+
type="text"
|
| 583 |
+
)
|
| 584 |
+
upload_btn = gr.UploadButton(
|
| 585 |
+
"Upload",
|
| 586 |
+
file_types=[".safetensors"],
|
| 587 |
+
scale=1
|
| 588 |
+
)
|
| 589 |
+
components[key('vae_upload_button')] = upload_btn
|
| 590 |
+
components[key('vae_file')] = gr.State(None)
|
| 591 |
+
|
| 592 |
+
upload_btn.upload(
|
| 593 |
+
fn=on_vae_upload,
|
| 594 |
+
inputs=[upload_btn],
|
| 595 |
+
outputs=[components[key('vae_id')], components[key('vae_source')], components[key('vae_file')]]
|
| 596 |
+
)
|
| 597 |
+
|
| 598 |
+
return components
|
| 599 |
+
|
| 600 |
+
def create_reference_latent_ui(prefix: str, max_units=10):
|
| 601 |
+
components = {}
|
| 602 |
+
key = lambda name: f"{name}_{prefix}"
|
| 603 |
+
|
| 604 |
+
with gr.Accordion("Reference Edit Settings", open=False, visible=('reference_latent' in default_enabled_chains)) as ref_accordion:
|
| 605 |
+
components[key('reference_latent_accordion')] = ref_accordion
|
| 606 |
+
gr.Markdown("π‘ **Tip:** For multimodal models, this feature enables powerful editing and combining capabilities. In txt2img mode, adding a single reference image performs an **Image Edit**, while adding multiple images performs an **Image Combine**.")
|
| 607 |
+
|
| 608 |
+
ref_image_groups = []
|
| 609 |
+
ref_image_inputs = []
|
| 610 |
+
with gr.Row():
|
| 611 |
+
for i in range(max_units):
|
| 612 |
+
with gr.Column(visible=(i < 1), min_width=160) as img_col:
|
| 613 |
+
img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
|
| 614 |
+
ref_image_groups.append(img_col)
|
| 615 |
+
ref_image_inputs.append(img_comp)
|
| 616 |
+
|
| 617 |
+
components[key('reference_latent_rows')] = ref_image_groups
|
| 618 |
+
components[key('reference_latent_images')] = ref_image_inputs
|
| 619 |
+
|
| 620 |
+
with gr.Row():
|
| 621 |
+
components[key('add_reference_latent_button')] = gr.Button("β Add Reference Image")
|
| 622 |
+
components[key('delete_reference_latent_button')] = gr.Button("β Delete Reference Image", visible=False)
|
| 623 |
+
components[key('reference_latent_count_state')] = gr.State(1)
|
| 624 |
+
|
| 625 |
+
components[key('all_reference_latent_components_flat')] = ref_image_inputs
|
| 626 |
+
|
| 627 |
+
return components
|
| 628 |
+
|
| 629 |
+
def create_hidream_o1_reference_ui(prefix: str, max_units=10):
|
| 630 |
+
components = {}
|
| 631 |
+
key = lambda name: f"{name}_{prefix}"
|
| 632 |
+
|
| 633 |
+
with gr.Accordion("HiDream-O1 Reference Edit Settings", open=False, visible=('hidream_o1_reference' in default_enabled_chains)) as ref_accordion:
|
| 634 |
+
components[key('hidream_o1_reference_accordion')] = ref_accordion
|
| 635 |
+
gr.Markdown("π‘ **Tip:** Please use **HiDream-O1-Image-Dev** (HiDream-O1-Image will time out), and set the resolution to **4.0MP** (e.g., 2048x2048). In txt2img mode, adding a single reference image performs an **Image Edit**, while adding multiple images performs an **Image Combine**.")
|
| 636 |
+
|
| 637 |
+
ref_image_groups = []
|
| 638 |
+
ref_image_inputs = []
|
| 639 |
+
with gr.Row():
|
| 640 |
+
for i in range(max_units):
|
| 641 |
+
with gr.Column(visible=(i < 1), min_width=160) as img_col:
|
| 642 |
+
img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
|
| 643 |
+
ref_image_groups.append(img_col)
|
| 644 |
+
ref_image_inputs.append(img_comp)
|
| 645 |
+
|
| 646 |
+
components[key('hidream_o1_reference_rows')] = ref_image_groups
|
| 647 |
+
components[key('hidream_o1_reference_images')] = ref_image_inputs
|
| 648 |
+
|
| 649 |
+
with gr.Row():
|
| 650 |
+
components[key('add_hidream_o1_reference_button')] = gr.Button("β Add Reference Image")
|
| 651 |
+
components[key('delete_hidream_o1_reference_button')] = gr.Button("β Delete Reference Image", visible=False)
|
| 652 |
+
components[key('hidream_o1_reference_count_state')] = gr.State(1)
|
| 653 |
+
|
| 654 |
+
components[key('all_hidream_o1_reference_components_flat')] = ref_image_inputs
|
| 655 |
+
|
| 656 |
+
return components
|
| 657 |
+
|
| 658 |
+
def create_pid_ui(prefix: str):
|
| 659 |
+
components = {}
|
| 660 |
+
key = lambda name: f"{name}_{prefix}"
|
| 661 |
+
|
| 662 |
+
with gr.Accordion("PiD Settings", open=False, visible=('pid' in default_enabled_chains)) as pid_accordion:
|
| 663 |
+
components[key('pid_accordion')] = pid_accordion
|
| 664 |
+
gr.Markdown("π‘ **Tip:** Use PiD (Pixel Diffusion Decoder) instead of the VAE Decoder for 4x decoding.")
|
| 665 |
+
with gr.Row():
|
| 666 |
+
components[key('pid_settings')] = gr.Dropdown(
|
| 667 |
+
label="PiD Mode",
|
| 668 |
+
choices=["OFF", "ON"],
|
| 669 |
+
value="OFF",
|
| 670 |
+
interactive=True
|
| 671 |
+
)
|
| 672 |
+
|
| 673 |
return components
|
utils/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (147 Bytes). View file
|
|
|
utils/__pycache__/app_utils.cpython-311.pyc
ADDED
|
Binary file (37.1 kB). View file
|
|
|