Hermes Bot commited on
Commit
749e96c
Β·
2 Parent(s): 211ab441d4eb4e

Merge remote changes, keep our modifications for metadata

Browse files
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
- print("--- [Runtime Patch] sageattention package found. Applying patch... ---")
27
- model_management.sage_attention_enabled = lambda: True
28
- model_management.pytorch_attention_enabled = lambda: False
29
-
30
- SAGE_PATCH_APPLIED = True
31
- return "βœ… Successfully enabled SageAttention."
32
- except ImportError:
33
- SAGE_PATCH_APPLIED = False
34
- msg = "--- [Runtime Patch] ⚠️ sageattention package not found. Continuing with default attention. ---"
35
- print(msg)
36
- return msg
37
- except Exception as e:
38
- SAGE_PATCH_APPLIED = False
39
- msg = f"--- [Runtime Patch] ❌ An error occurred while applying SageAttention patch: {e} ---"
40
- print(msg)
41
- return msg
42
-
43
- @spaces.GPU
44
- def dummy_gpu_for_startup():
45
- try:
46
- print("--- [GPU Startup] Dummy function for startup check initiated. ---")
47
- patch_result = apply_sage_attention_patch()
48
- print(f"--- [GPU Startup] {patch_result} ---")
49
- print("--- [GPU Startup] Startup check passed. ---")
50
- return "Startup check passed."
51
- except BaseException as e:
52
- err_msg = str(e)
53
- if "uncorrectable ECC error" in err_msg or "cudaErrorECCUncorrectable" in err_msg:
54
- print("\n" + "="*80)
55
- print(f"🚨 [Fatal GPU Error] Captured uncorrectable ECC error during inference: {err_msg}")
56
- print("🚨 Terminating process to trigger an automatic container restart...")
57
- print("="*80 + "\n")
58
- os._exit(1)
59
- raise e
60
-
61
-
62
- def main():
63
- from comfy_integration import setup as setup_comfyui
64
- from utils.app_utils import load_ipadapter_presets
65
-
66
- print("--- [Setup] Starting ComfyUI initialization ---")
67
- setup_comfyui.initialize_comfyui()
68
-
69
- print("--- [Setup] Applying SageAttention Runtime Patch ---")
70
- patch_result = apply_sage_attention_patch()
71
- print(f"--- [Setup] {patch_result} ---")
72
-
73
- print("--- [Setup] Reloading site-packages to detect newly installed packages... ---")
74
- try:
75
- site.main()
76
- print("--- [Setup] βœ… Site-packages reloaded. ---")
77
- except Exception as e:
78
- print(f"--- [Setup] ⚠️ Warning: Could not fully reload site-packages: {e} ---")
79
-
80
- print("--- Initiating GPU Startup Check & SageAttention Patch Verification ---")
81
- try:
82
- dummy_gpu_for_startup()
83
- except Exception as e:
84
- print(f"--- [GPU Startup] ⚠️ Warning: Startup check failed: {e} ---")
85
-
86
- print("--- Starting Application Setup ---")
87
-
88
- print("--- Loading IPAdapter presets ---")
89
- load_ipadapter_presets()
90
- print("--- βœ… IPAdapter setup complete. ---")
91
-
92
-
93
- print("--- Environment configured. Proceeding with module imports. ---")
94
- from ui.layout import build_ui
95
- from ui.events import attach_event_handlers
96
-
97
- print(f"βœ… Working directory is stable: {os.getcwd()}")
98
-
99
- demo = build_ui(attach_event_handlers)
100
-
101
- print("--- Launching Gradio Interface ---")
102
- demo.queue().launch(server_name="0.0.0.0", server_port=7860)
103
-
104
-
105
- if __name__ == "__main__":
 
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'] = "cuda"
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'] = "cuda"
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
- print("--- Cloning third-party extensions for ComfyUI ---")
44
-
45
- # 1. ComfyUI_IPAdapter_plus
46
- ipadapter_plus_path = os.path.join(APP_DIR, "custom_nodes", "ComfyUI_IPAdapter_plus")
47
- if not os.path.exists(ipadapter_plus_path):
48
- os.system(f"git clone https://github.com/cubiq/ComfyUI_IPAdapter_plus.git {ipadapter_plus_path}")
49
- print("βœ… ComfyUI_IPAdapter_plus extension cloned.")
50
- else:
51
- print("βœ… ComfyUI_IPAdapter_plus extension already exists.")
52
-
53
- # 2. ComfyUI-InstantX-IPAdapter-SD3
54
- ipadapter_plus_path = os.path.join(APP_DIR, "custom_nodes", "ComfyUI-InstantX-IPAdapter-SD3")
55
- if not os.path.exists(ipadapter_plus_path):
56
- os.system(f"git clone https://github.com/Slickytail/ComfyUI-InstantX-IPAdapter-SD3.git {ipadapter_plus_path}")
57
- print("βœ… ComfyUI-InstantX-IPAdapter-SD3 extension cloned.")
58
- else:
59
- print("βœ… ComfyUI-InstantX-IPAdapter-SD3 extension already exists.")
60
-
61
- # 3. ComfyUI-IPAdapter-Flux
62
- ipadapter_flux_path = os.path.join(APP_DIR, "custom_nodes", "ComfyUI-IPAdapter-Flux")
63
- if not os.path.exists(ipadapter_flux_path):
64
- os.system(f"git clone https://github.com/Shakker-Labs/ComfyUI-IPAdapter-Flux.git {ipadapter_flux_path}")
65
- print("βœ… ComfyUI-IPAdapter-Flux extension cloned.")
66
- else:
67
- print("βœ… ComfyUI-IPAdapter-Flux extension already exists.")
68
-
69
- # 4. ComfyUI-Newbie-Nodes
70
- newbie_nodes_path = os.path.join(APP_DIR, "custom_nodes", "ComfyUI-Newbie-Nodes")
71
- if not os.path.exists(newbie_nodes_path):
72
- os.system(f"git clone https://github.com/NewBieAI-Lab/ComfyUI-Newbie-Nodes.git {newbie_nodes_path}")
73
- print("βœ… ComfyUI-Newbie-Nodes extension cloned.")
74
- else:
75
- print("βœ… ComfyUI-Newbie-Nodes extension already exists.")
76
-
77
- # 5. ComfyUI-Anima-LLLite
78
- anima_controlnet_lllite_nodes_path = os.path.join(APP_DIR, "custom_nodes", "ComfyUI-Anima-LLLite")
79
- if not os.path.exists(anima_controlnet_lllite_nodes_path):
80
- os.system(f"git clone https://github.com/kohya-ss/ComfyUI-Anima-LLLite.git {anima_controlnet_lllite_nodes_path}")
81
- print("βœ… ComfyUI-Anima-LLLite extension cloned.")
82
- else:
83
- print("βœ… ComfyUI-Anima-LLLite extension already exists.")
84
-
85
- print(f"βœ… Current working directory is: {os.getcwd()}")
86
-
87
- import comfy.model_management
88
- print("--- Environment Ready ---")
89
-
90
- print("βœ… ComfyUI initialized with default attention mechanism.")
91
-
92
- for dir_path in CATEGORY_TO_DIR_MAP.values():
93
- os.makedirs(os.path.join(APP_DIR, dir_path), exist_ok=True)
94
-
95
- os.makedirs(os.path.join(APP_DIR, INPUT_DIR), exist_ok=True)
96
- os.makedirs(os.path.join(APP_DIR, OUTPUT_DIR), exist_ok=True)
97
-
98
- print("βœ… All required model directories are present.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- gpu_runner = spaces.GPU(duration=final_duration)(gpu_function)
40
-
41
- try:
42
- return gpu_runner(*args, **kwargs)
43
- except BaseException as e:
44
- err_msg = str(e)
45
- if "uncorrectable ECC error" in err_msg or "cudaErrorECCUncorrectable" in err_msg:
46
- print("\n" + "="*80)
47
- print(f"🚨 [Fatal GPU Error] Captured uncorrectable ECC error during inference: {err_msg}")
48
- print("🚨 Terminating process to trigger an automatic container restart...")
49
- print("="*80 + "\n")
50
- os._exit(1)
51
- raise e
52
-
53
- def _encode_video_from_frames(self, frames_tensor_cpu: 'torch.Tensor', fps: int, progress: gr.Progress) -> str:
54
- progress(0.9, desc="Encoding video on CPU...")
55
- frames_np = (frames_tensor_cpu.numpy() * 255.0).astype(np.uint8)
56
-
57
- with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_video_file:
58
- video_path = temp_video_file.name
59
- writer = imageio.get_writer(video_path, fps=fps, codec='libx264', quality=8)
60
- for frame in frames_np:
61
- writer.append_data(frame)
62
- writer.close()
63
-
64
- progress(1.0, desc="Done!")
 
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
- model_display_name = ui_inputs['model_display_name']
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
- output_images = []
 
 
 
 
 
 
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': params_string += f", Denoise: {ui_inputs['denoise']}"
50
- if ui_inputs.get('clip_skip') and ui_inputs['clip_skip'] != 1: params_string += f", Clip skip: {abs(ui_inputs['clip_skip'])}"
51
- if loras_string: params_string += f", {loras_string}"
52
-
 
 
 
53
  pil_image.info = {'parameters': params_string.strip()}
54
- output_images.append(pil_image)
55
-
56
- return output_images
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- import json
216
- import glob
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
- if workflow[node_id]['class_type'] == 'SaveImage':
101
- final_node_id = node_id
102
- break
103
-
104
- if not final_node_id:
105
- raise RuntimeError("Workflow does not contain a 'SaveImage' node as the output.")
106
-
107
- save_image_inputs = workflow[final_node_id]['inputs']
108
- image_source_node_id, image_source_index = save_image_inputs['images']
109
-
110
- return get_value_at_index(computed_outputs[image_source_node_id], image_source_index)
 
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
- save_image:
17
- class_type: SaveImage
18
- title: "Save Image"
19
- params: {}
20
-
21
- connections:
22
- - from: "ksampler:0"
23
- to: "vae_decode:samples"
24
- - from: "vae_decode:0"
25
- to: "save_image:images"
26
-
27
- ui_map:
28
- positive_prompt: "pos_prompt:text"
29
- negative_prompt: "neg_prompt:text"
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
- import yaml
2
- import os
3
- from collections import OrderedDict
4
-
5
- CHECKPOINT_DIR = "models/checkpoints"
6
- LORA_DIR = "models/loras"
7
- EMBEDDING_DIR = "models/embeddings"
8
- CONTROLNET_DIR = "models/controlnet"
9
- MODEL_PATCHES_DIR = "models/model_patches"
10
- DIFFUSION_MODELS_DIR = "models/diffusion_models"
11
- VAE_DIR = "models/vae"
12
- TEXT_ENCODERS_DIR = "models/text_encoders"
13
- STYLE_MODELS_DIR = "models/style_models"
14
- CLIP_VISION_DIR = "models/clip_vision"
15
- IPADAPTER_DIR = "models/ipadapter"
16
- IPADAPTER_FLUX_DIR = "models/ipadapter-flux"
17
- INPUT_DIR = "input"
18
- OUTPUT_DIR = "output"
19
-
20
- CATEGORY_TO_DIR_MAP = {
21
- "diffusion_models": DIFFUSION_MODELS_DIR,
22
- "text_encoders": TEXT_ENCODERS_DIR,
23
- "vae": VAE_DIR,
24
- "checkpoints": CHECKPOINT_DIR,
25
- "loras": LORA_DIR,
26
- "controlnet": CONTROLNET_DIR,
27
- "model_patches": MODEL_PATCHES_DIR,
28
- "embeddings": EMBEDDING_DIR,
29
- "style_models": STYLE_MODELS_DIR,
30
- "clip_vision": CLIP_VISION_DIR,
31
- "ipadapter": IPADAPTER_DIR,
32
- "ipadapter-flux": IPADAPTER_FLUX_DIR
33
- }
34
-
35
- _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
36
- _MODEL_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'model_list.yaml')
37
- _FILE_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'file_list.yaml')
38
- _IPADAPTER_LIST_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'ipadapter.yaml')
39
- _CONSTANTS_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'constants.yaml')
40
- _MODEL_ARCHITECTURES_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'model_architectures.yaml')
41
- _IMAGE_GEN_FEATURES_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'image_gen_features.yaml')
42
- _MODEL_DEFAULTS_PATH = os.path.join(_PROJECT_ROOT, 'yaml', 'model_defaults.yaml')
43
-
44
- def load_constants_from_yaml(filepath=_CONSTANTS_PATH):
45
- if not os.path.exists(filepath):
46
- print(f"Warning: Constants file not found at {filepath}. Using fallback values.")
47
- return {}
48
- with open(filepath, 'r', encoding='utf-8') as f:
49
- return yaml.safe_load(f)
50
-
51
- def load_architectures_config(filepath=_MODEL_ARCHITECTURES_PATH):
52
- if not os.path.exists(filepath):
53
- print(f"Warning: Architectures file not found at {filepath}.")
54
- return {}
55
- with open(filepath, 'r', encoding='utf-8') as f:
56
- return yaml.safe_load(f)
57
-
58
- def load_features_config(filepath=_IMAGE_GEN_FEATURES_PATH):
59
- if not os.path.exists(filepath):
60
- print(f"Warning: Features file not found at {filepath}.")
61
- return {}
62
- with open(filepath, 'r', encoding='utf-8') as f:
63
- return yaml.safe_load(f)
64
-
65
- def load_model_defaults(filepath=_MODEL_DEFAULTS_PATH):
66
- if not os.path.exists(filepath):
67
- print(f"Warning: Model defaults file not found at {filepath}.")
68
- return {}
69
- with open(filepath, 'r', encoding='utf-8') as f:
70
- return yaml.safe_load(f)
71
-
72
- def load_file_download_map(filepath=_FILE_LIST_PATH):
73
- if not os.path.exists(filepath):
74
- raise FileNotFoundError(f"The file list (for downloads) was not found at: {filepath}")
75
-
76
- with open(filepath, 'r', encoding='utf-8') as f:
77
- file_list_data = yaml.safe_load(f)
78
-
79
- download_info_map = {}
80
- for category, files in file_list_data.get('file', {}).items():
81
- if isinstance(files, list):
82
- for file_info in files:
83
- if 'filename' in file_info:
84
- file_info['category'] = category
85
- download_info_map[file_info['filename']] = file_info
86
- return download_info_map
87
-
88
-
89
- def load_models_from_yaml(model_list_filepath=_MODEL_LIST_PATH, download_map=None):
90
- if not os.path.exists(model_list_filepath):
91
- raise FileNotFoundError(f"The model list file was not found at: {model_list_filepath}")
92
- if download_map is None:
93
- raise ValueError("download_map must be provided to load_models_from_yaml")
94
-
95
- with open(model_list_filepath, 'r', encoding='utf-8') as f:
96
- model_data = yaml.safe_load(f)
97
-
98
- model_maps = {
99
- "MODEL_MAP_CHECKPOINT": OrderedDict(),
100
- "ALL_MODEL_MAP": OrderedDict(),
101
- }
102
- category_map_names = {
103
- "Checkpoint": "MODEL_MAP_CHECKPOINT",
104
- "Checkpoints": "MODEL_MAP_CHECKPOINT"
105
- }
106
-
107
- for category, architectures in model_data.items():
108
- if category in category_map_names:
109
- map_name = category_map_names[category]
110
- if not isinstance(architectures, dict): continue
111
-
112
- for arch, arch_data in architectures.items():
113
- if not isinstance(arch_data, dict): continue
114
-
115
- latent_type = arch_data.get('latent_type', 'latent')
116
- models = arch_data.get('models', [])
117
- if not isinstance(models, list): continue
118
-
119
- for model in models:
120
- display_name = model['display_name']
121
- path_or_components = model.get('path') or model.get('components')
122
- mod_category = model.get('category', None)
123
-
124
- repo_id = ''
125
- if isinstance(path_or_components, str):
126
- download_info = download_map.get(path_or_components, {})
127
- repo_id = download_info.get('repo_id', '')
128
-
129
- model_tuple = (
130
- repo_id,
131
- path_or_components,
132
- arch,
133
- latent_type,
134
- mod_category
135
- )
136
- model_maps[map_name][display_name] = model_tuple
137
- model_maps["ALL_MODEL_MAP"][display_name] = model_tuple
138
-
139
- return model_maps
140
-
141
- try:
142
- ALL_FILE_DOWNLOAD_MAP = load_file_download_map()
143
- loaded_maps = load_models_from_yaml(download_map=ALL_FILE_DOWNLOAD_MAP)
144
- MODEL_MAP_CHECKPOINT = loaded_maps["MODEL_MAP_CHECKPOINT"]
145
- ALL_MODEL_MAP = loaded_maps["ALL_MODEL_MAP"]
146
-
147
- category_to_model_type = {
148
- "diffusion_models": "UNET",
149
- "text_encoders": "TEXT_ENCODER",
150
- "vae": "VAE",
151
- "checkpoints": "SDXL",
152
- "loras": "LORA",
153
- "controlnet": "CONTROLNET",
154
- "model_patches": "MODEL_PATCH",
155
- "style_models": "STYLE",
156
- "clip_vision": "CLIP_VISION",
157
- "ipadapter": "IPADAPTER",
158
- "ipadapter-flux": "IPADAPTER_FLUX"
159
- }
160
- for filename, file_info in ALL_FILE_DOWNLOAD_MAP.items():
161
- if filename not in ALL_MODEL_MAP:
162
- category = file_info.get('category')
163
- model_type = category_to_model_type.get(category, 'UNKNOWN')
164
- repo_id = file_info.get('repo_id', '')
165
- ALL_MODEL_MAP[filename] = (repo_id, filename, model_type, None, None)
166
-
167
- MODEL_TYPE_MAP = {k: v[2] for k, v in ALL_MODEL_MAP.items()}
168
-
169
- ARCH_CATEGORIES_MAP = {}
170
- for display_name, info in MODEL_MAP_CHECKPOINT.items():
171
- arch = info[2]
172
- cat = info[4] if len(info) > 4 else None
173
- if arch not in ARCH_CATEGORIES_MAP:
174
- ARCH_CATEGORIES_MAP[arch] = []
175
- if cat and cat not in ARCH_CATEGORIES_MAP[arch]:
176
- ARCH_CATEGORIES_MAP[arch].append(cat)
177
-
178
- except Exception as e:
179
- print(f"FATAL: Could not load model configuration from YAML. Error: {e}")
180
- ALL_FILE_DOWNLOAD_MAP = {}
181
- MODEL_MAP_CHECKPOINT, ALL_MODEL_MAP = {}, {}
182
- MODEL_TYPE_MAP = {}
183
- ARCH_CATEGORIES_MAP = {}
184
-
185
-
186
- try:
187
- _constants = load_constants_from_yaml()
188
- MAX_LORAS = _constants.get('MAX_LORAS', 5)
189
- MAX_EMBEDDINGS = _constants.get('MAX_EMBEDDINGS', 5)
190
- MAX_CONDITIONINGS = _constants.get('MAX_CONDITIONINGS', 10)
191
- MAX_CONTROLNETS = _constants.get('MAX_CONTROLNETS', 5)
192
- MAX_IPADAPTERS = _constants.get('MAX_IPADAPTERS', 5)
193
- LORA_SOURCE_CHOICES = _constants.get('LORA_SOURCE_CHOICES', ["Civitai", "File"])
194
- RESOLUTION_MAP = _constants.get('RESOLUTION_MAP', {})
195
- MULTIPLIERS_MAP = _constants.get('MULTIPLIERS_MAP', {})
196
- ARCHITECTURES_CONFIG = load_architectures_config()
197
- FEATURES_CONFIG = load_features_config()
198
- MODEL_DEFAULTS_CONFIG = load_model_defaults()
199
- except Exception as e:
200
- print(f"FATAL: Could not load constants from YAML. Error: {e}")
201
- MAX_LORAS, MAX_EMBEDDINGS, MAX_CONDITIONINGS, MAX_CONTROLNETS, MAX_IPADAPTERS = 5, 5, 10, 5, 5
202
- LORA_SOURCE_CHOICES = ["Civitai", "File"]
203
- RESOLUTION_MAP = {}
204
- MULTIPLIERS_MAP = {}
205
- ARCHITECTURES_CONFIG = {}
206
- FEATURES_CONFIG = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 gradio as gr
3
- from core.settings import *
4
-
5
- from .shared import txt2img_ui, img2img_ui, inpaint_ui, outpaint_ui, hires_fix_ui
6
-
7
- MAX_DYNAMIC_CONTROLS = 10
8
-
9
- def build_ui(event_handler_function):
10
- ui_components = {}
11
-
12
- with gr.Blocks() as demo:
13
- gr.Markdown("# ImageGen")
14
- gr.Markdown(
15
- "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. "
16
- "Other spaces: [ImageGen1](https://huggingface.co/spaces/RioShiina/ImageGen1), "
17
- "[ImageGen2](https://huggingface.co/spaces/RioShiina/ImageGen2), "
18
- "[ImageGen3](https://huggingface.co/spaces/RioShiina/ImageGen3), "
19
- "[ImageGen4](https://huggingface.co/spaces/RioShiina/ImageGen4), "
20
- "[ImageGen5](https://huggingface.co/spaces/RioShiina/ImageGen5), "
21
- "[ImageGen6](https://huggingface.co/spaces/RioShiina/ImageGen6), "
22
- "[ImageGen7](https://huggingface.co/spaces/RioShiina/ImageGen7), "
23
- "[ImageGen8](https://huggingface.co/spaces/RioShiina/ImageGen8)"
24
- )
25
- with gr.Tabs(elem_id="tabs_container") as tabs:
26
- with gr.TabItem("Txt2Img", id=0):
27
- ui_components.update(txt2img_ui.create_ui())
28
-
29
- with gr.TabItem("Img2Img", id=1):
30
- ui_components.update(img2img_ui.create_ui())
31
-
32
- with gr.TabItem("Inpaint", id=2):
33
- ui_components.update(inpaint_ui.create_ui())
34
-
35
- with gr.TabItem("Outpaint", id=3):
36
- ui_components.update(outpaint_ui.create_ui())
37
-
38
- with gr.TabItem("Hires. Fix", id=4):
39
- ui_components.update(hires_fix_ui.create_ui())
40
-
41
- ui_components["tabs"] = tabs
42
- ui_components["image_gen_tabs"] = tabs
43
-
44
- 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>")
45
-
46
- event_handler_function(ui_components, demo)
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
- with gr.Row():
89
- components[f'aspect_ratio_{prefix}'] = gr.Dropdown(
90
- label="Aspect Ratio",
91
- choices=list(RESOLUTION_MAP.get('sdxl', {}).keys()),
92
- value="1:1 (Square)",
93
- interactive=True,
94
- allow_custom_value=True
95
- )
96
- with gr.Row():
97
- components[f'width_{prefix}'] = gr.Number(label="Width", value=defaults.get('w', 1024), interactive=True)
98
- components[f'height_{prefix}'] = gr.Number(label="Height", value=defaults.get('h', 1024), interactive=True)
99
- with gr.Row():
100
- components[f'sampler_{prefix}'] = gr.Dropdown(
101
- label="Sampler",
102
- choices=SAMPLER_CHOICES,
103
- value=DEFAULT_SAMPLER if DEFAULT_SAMPLER in SAMPLER_CHOICES else (SAMPLER_CHOICES[0] if SAMPLER_CHOICES else 'euler')
104
- )
105
- components[f'scheduler_{prefix}'] = gr.Dropdown(
106
- label="Scheduler",
107
- choices=SCHEDULER_CHOICES,
108
- value=DEFAULT_SCHEDULER if DEFAULT_SCHEDULER in SCHEDULER_CHOICES else (SCHEDULER_CHOICES[0] if SCHEDULER_CHOICES else 'simple')
109
- )
110
- with gr.Row():
111
- components[f'steps_{prefix}'] = gr.Slider(label="Steps", minimum=1, maximum=100, step=1, value=DEFAULT_STEPS)
112
- components[f'cfg_{prefix}'] = gr.Slider(label="CFG Scale", minimum=1.0, maximum=20.0, step=0.1, value=DEFAULT_CFG)
113
- with gr.Row():
114
- components[f'seed_{prefix}'] = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
115
- components[f'batch_size_{prefix}'] = gr.Slider(label="Batch Size", minimum=1, maximum=16, step=1, value=1)
116
- with gr.Row():
117
- components[f'clip_skip_{prefix}'] = gr.Slider(label="Clip Skip", minimum=1, maximum=2, step=1, value=1, visible=False, interactive=True)
118
- 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)
119
- 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.")
120
-
121
- return components
122
-
123
-
124
- def create_lora_settings_ui(prefix: str):
125
- components = {}
126
-
127
- lora_rows, lora_sources, lora_ids, lora_scales, lora_uploads = [], [], [], [], []
128
-
129
- with gr.Accordion("LoRA Settings", open=False, visible=('lora' in default_enabled_chains)) as lora_accordion:
130
- components[f'lora_accordion_{prefix}'] = lora_accordion
131
- 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`).")
132
- components[f'lora_count_state_{prefix}'] = gr.State(1)
133
-
134
- for i in range(MAX_LORAS):
135
- with gr.Row(visible=i==0) as row:
136
- source = gr.Dropdown(label=f"LoRA Source {i+1}", choices=LORA_SOURCE_CHOICES, value=LORA_SOURCE_CHOICES[0], scale=1)
137
- lora_id = gr.Textbox(label="Civitai Version ID / HF file / Upload File", scale=2, type="text")
138
- scale = gr.Slider(label=f"Scale", minimum=0.0, maximum=2.0, step=0.05, value=1.0, scale=1)
139
- upload = gr.UploadButton(label="Upload", file_types=[".safetensors"], scale=1)
140
-
141
- lora_rows.append(row)
142
- lora_sources.append(source)
143
- lora_ids.append(lora_id)
144
- lora_scales.append(scale)
145
- lora_uploads.append(upload)
146
-
147
- with gr.Row():
148
- components[f'add_lora_button_{prefix}'] = gr.Button("Add LoRA", variant="secondary")
149
- components[f'delete_lora_button_{prefix}'] = gr.Button("Remove LoRA", variant="secondary", visible=False)
150
-
151
- components[f'lora_rows_{prefix}'] = lora_rows
152
- components[f'lora_sources_{prefix}'] = lora_sources
153
- components[f'lora_ids_{prefix}'] = lora_ids
154
- components[f'lora_scales_{prefix}'] = lora_scales
155
- components[f'lora_uploads_{prefix}'] = lora_uploads
156
-
157
- all_lora_components_flat = []
158
- for i in range(MAX_LORAS):
159
- all_lora_components_flat.extend([lora_sources[i], lora_ids[i], lora_scales[i], lora_uploads[i]])
160
- components[f'all_lora_components_flat_{prefix}'] = all_lora_components_flat
161
-
162
- return components
163
-
164
- def create_controlnet_ui(prefix: str, max_units=MAX_CONTROLNETS):
165
- components = {}
166
- key = lambda name: f"{name}_{prefix}"
167
-
168
- with gr.Accordion("ControlNet Settings", open=False, visible=('controlnet' in default_enabled_chains)) as accordion:
169
- components[key('controlnet_accordion')] = accordion
170
-
171
- cn_rows, images, series, types, strengths, filepaths = [], [], [], [], [], []
172
- components.update({
173
- key('controlnet_rows'): cn_rows,
174
- key('controlnet_images'): images,
175
- key('controlnet_series'): series,
176
- key('controlnet_types'): types,
177
- key('controlnet_strengths'): strengths,
178
- key('controlnet_filepaths'): filepaths
179
- })
180
-
181
- for i in range(max_units):
182
- with gr.Row(visible=(i < 1)) as row:
183
- with gr.Column(scale=1):
184
- images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
185
- with gr.Column(scale=2):
186
- types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
187
- series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
188
- strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
189
- filepaths.append(gr.State(None))
190
- cn_rows.append(row)
191
-
192
- with gr.Row():
193
- components[key('add_controlnet_button')] = gr.Button("✚ Add ControlNet")
194
- components[key('delete_controlnet_button')] = gr.Button("βž– Delete ControlNet", visible=False)
195
- components[key('controlnet_count_state')] = gr.State(1)
196
-
197
- all_cn_components_flat = []
198
- for i in range(max_units):
199
- all_cn_components_flat.extend([
200
- images[i], types[i], series[i], strengths[i], filepaths[i]
201
- ])
202
- components[key('all_controlnet_components_flat')] = all_cn_components_flat
203
-
204
- return components
205
-
206
- def create_anima_controlnet_lllite_ui(prefix: str, max_units=MAX_CONTROLNETS):
207
- components = {}
208
- key = lambda name: f"{name}_{prefix}"
209
-
210
- with gr.Accordion("Anima ControlNet Lllite Settings", open=False, visible=('anima_controlnet_lllite' in default_enabled_chains)) as accordion:
211
- components[key('anima_controlnet_lllite_accordion')] = accordion
212
- gr.Markdown("πŸ’‘ **Tip:** Processed using the [kohya-ss/ComfyUI-Anima-LLLite](https://github.com/kohya-ss/ComfyUI-Anima-LLLite) node.")
213
-
214
- cn_rows, images, series, types, strengths, filepaths, start_percents, end_percents = [], [], [], [], [], [], [], []
215
- components.update({
216
- key('anima_controlnet_lllite_rows'): cn_rows,
217
- key('anima_controlnet_lllite_images'): images,
218
- key('anima_controlnet_lllite_series'): series,
219
- key('anima_controlnet_lllite_types'): types,
220
- key('anima_controlnet_lllite_strengths'): strengths,
221
- key('anima_controlnet_lllite_filepaths'): filepaths,
222
- key('anima_controlnet_lllite_start_percents'): start_percents,
223
- key('anima_controlnet_lllite_end_percents'): end_percents
224
- })
225
-
226
- for i in range(max_units):
227
- with gr.Row(visible=(i < 1)) as row:
228
- with gr.Column(scale=1):
229
- images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
230
- with gr.Column(scale=2):
231
- types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
232
- series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
233
- strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
234
- with gr.Row(visible=False):
235
- start_percents.append(gr.State(0.0))
236
- end_percents.append(gr.State(1.0))
237
- filepaths.append(gr.State(None))
238
- cn_rows.append(row)
239
-
240
- with gr.Row():
241
- components[key('add_anima_controlnet_lllite_button')] = gr.Button("✚ Add Lllite")
242
- components[key('delete_anima_controlnet_lllite_button')] = gr.Button("βž– Delete Lllite", visible=False)
243
- components[key('anima_controlnet_lllite_count_state')] = gr.State(1)
244
-
245
- all_cn_components_flat = []
246
- for i in range(max_units):
247
- all_cn_components_flat.extend([
248
- images[i], types[i], series[i], strengths[i], filepaths[i], start_percents[i], end_percents[i]
249
- ])
250
- components[key('all_anima_controlnet_lllite_components_flat')] = all_cn_components_flat
251
-
252
- return components
253
-
254
- def create_diffsynth_controlnet_ui(prefix: str, max_units=MAX_CONTROLNETS):
255
- components = {}
256
- key = lambda name: f"{name}_{prefix}"
257
-
258
- with gr.Accordion("DiffSynth ControlNet Settings", open=False, visible=('controlnet_model_patch' in default_enabled_chains)) as accordion:
259
- components[key('diffsynth_controlnet_accordion')] = accordion
260
-
261
- cn_rows, images, series, types, strengths, filepaths = [], [], [], [], [], []
262
- components.update({
263
- key('diffsynth_controlnet_rows'): cn_rows,
264
- key('diffsynth_controlnet_images'): images,
265
- key('diffsynth_controlnet_series'): series,
266
- key('diffsynth_controlnet_types'): types,
267
- key('diffsynth_controlnet_strengths'): strengths,
268
- key('diffsynth_controlnet_filepaths'): filepaths
269
- })
270
-
271
- for i in range(max_units):
272
- with gr.Row(visible=(i < 1)) as row:
273
- with gr.Column(scale=1):
274
- images.append(gr.Image(label=f"Control Image {i+1}", type="pil", sources=["upload"], height=256))
275
- with gr.Column(scale=2):
276
- types.append(gr.Dropdown(label="Type", choices=[], interactive=True, allow_custom_value=True))
277
- series.append(gr.Dropdown(label="Series", choices=[], interactive=True, allow_custom_value=True))
278
- strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
279
- filepaths.append(gr.State(None))
280
- cn_rows.append(row)
281
-
282
- with gr.Row():
283
- components[key('add_diffsynth_controlnet_button')] = gr.Button("✚ Add DiffSynth ControlNet")
284
- components[key('delete_diffsynth_controlnet_button')] = gr.Button("βž– Delete DiffSynth ControlNet", visible=False)
285
- components[key('diffsynth_controlnet_count_state')] = gr.State(1)
286
-
287
- all_cn_components_flat = []
288
- for i in range(max_units):
289
- all_cn_components_flat.extend([
290
- images[i], types[i], series[i], strengths[i], filepaths[i]
291
- ])
292
- components[key('all_diffsynth_controlnet_components_flat')] = all_cn_components_flat
293
-
294
- return components
295
-
296
- def create_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
297
- components = {}
298
- key = lambda name: f"{name}_{prefix}"
299
-
300
- sdxl_presets = get_ipadapter_presets("SDXL")
301
- default_preset = sdxl_presets[0] if sdxl_presets else None
302
-
303
- with gr.Accordion("IPAdapter Settings", open=False, visible=('ipadapter' in default_enabled_chains)) as accordion:
304
- components[key('ipadapter_accordion')] = accordion
305
- gr.Markdown("πŸ’‘ **Tip:** Processed using the [cubiq/ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) node.")
306
-
307
- with gr.Row():
308
- components[key('ipadapter_final_preset')] = gr.Dropdown(
309
- label="Preset (for all images)",
310
- choices=sdxl_presets,
311
- value=default_preset,
312
- interactive=True,
313
- allow_custom_value=True
314
- )
315
- components[key('ipadapter_embeds_scaling')] = gr.Dropdown(
316
- label="Embeds Scaling",
317
- choices=['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],
318
- value='V only',
319
- interactive=True
320
- )
321
-
322
- with gr.Row():
323
- components[key('ipadapter_combine_method')] = gr.Dropdown(
324
- label="Combine Method",
325
- choices=["concat", "add", "subtract", "average", "norm average", "max", "min"],
326
- value="concat",
327
- interactive=True
328
- )
329
- components[key('ipadapter_final_weight')] = gr.Slider(label="Final Weight", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True)
330
- 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)
331
-
332
- gr.Markdown("---")
333
-
334
- ipa_rows, images, weights, lora_strengths = [], [], [], []
335
- components.update({
336
- key('ipadapter_rows'): ipa_rows,
337
- key('ipadapter_images'): images,
338
- key('ipadapter_weights'): weights,
339
- key('ipadapter_lora_strengths'): lora_strengths
340
- })
341
-
342
- for i in range(max_units):
343
- with gr.Row(visible=(i < 1)) as row:
344
- with gr.Column(scale=1):
345
- images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
346
- with gr.Column(scale=2):
347
- weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
348
- lora_strengths.append(gr.Slider(label="LoRA Strength", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True, visible=False))
349
- ipa_rows.append(row)
350
-
351
- with gr.Row():
352
- components[key('add_ipadapter_button')] = gr.Button("✚ Add IPAdapter")
353
- components[key('delete_ipadapter_button')] = gr.Button("βž– Delete IPAdapter", visible=False)
354
- components[key('ipadapter_count_state')] = gr.State(1)
355
-
356
- all_ipa_components_flat = images + weights + lora_strengths
357
- all_ipa_components_flat += [
358
- components[key('ipadapter_final_preset')],
359
- components[key('ipadapter_final_weight')],
360
- components[key('ipadapter_final_lora_strength')],
361
- components[key('ipadapter_embeds_scaling')],
362
- components[key('ipadapter_combine_method')],
363
- ]
364
- components[key('all_ipadapter_components_flat')] = all_ipa_components_flat
365
-
366
- return components
367
-
368
- def create_flux1_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
369
- components = {}
370
- key = lambda name: f"{name}_{prefix}"
371
-
372
- with gr.Accordion("IPAdapter Settings (FLUX.1)", open=False, visible=('flux1_ipadapter' in default_enabled_chains)) as accordion:
373
- components[key('flux1_ipadapter_accordion')] = accordion
374
- gr.Markdown("πŸ’‘ **Tip:** Processed using the [Shakker-Labs/ComfyUI-IPAdapter-Flux](https://github.com/Shakker-Labs/ComfyUI-IPAdapter-Flux) node.")
375
-
376
- ipa_rows, images, weights, start_percents, end_percents = [], [], [], [], []
377
- components.update({
378
- key('flux1_ipadapter_rows'): ipa_rows,
379
- key('flux1_ipadapter_images'): images,
380
- key('flux1_ipadapter_weights'): weights,
381
- key('flux1_ipadapter_start_percents'): start_percents,
382
- key('flux1_ipadapter_end_percents'): end_percents,
383
- })
384
-
385
- for i in range(max_units):
386
- with gr.Row(visible=(i < 1)) as row:
387
- with gr.Column(scale=1):
388
- images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
389
- with gr.Column(scale=2):
390
- weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=0.6, interactive=True))
391
- with gr.Row():
392
- start_percents.append(gr.Slider(label="Start At", minimum=0.0, maximum=1.0, step=0.01, value=0.0, interactive=True))
393
- end_percents.append(gr.Slider(label="End At", minimum=0.0, maximum=1.0, step=0.01, value=0.6, interactive=True))
394
- ipa_rows.append(row)
395
-
396
- with gr.Row():
397
- components[key('add_flux1_ipadapter_button')] = gr.Button("✚ Add IPAdapter (FLUX)")
398
- components[key('delete_flux1_ipadapter_button')] = gr.Button("βž– Delete IPAdapter (FLUX)", visible=False)
399
- components[key('flux1_ipadapter_count_state')] = gr.State(1)
400
-
401
- all_flux1_ipa_components_flat = images + weights + start_percents + end_percents
402
- components[key('all_flux1_ipadapter_components_flat')] = all_flux1_ipa_components_flat
403
-
404
- return components
405
-
406
- def create_sd3_ipadapter_ui(prefix: str, max_units=MAX_IPADAPTERS):
407
- components = {}
408
- key = lambda name: f"{name}_{prefix}"
409
-
410
- with gr.Accordion("IPAdapter Settings (SD3)", open=False, visible=('sd3_ipadapter' in default_enabled_chains)) as accordion:
411
- components[key('sd3_ipadapter_accordion')] = accordion
412
- gr.Markdown("πŸ’‘ **Tip:** Processed using the [Slickytail/ComfyUI-InstantX-IPAdapter-SD3](https://github.com/Slickytail/ComfyUI-InstantX-IPAdapter-SD3) node.")
413
-
414
- ipa_rows, images, weights, start_percents, end_percents = [], [], [], [], []
415
- components.update({
416
- key('sd3_ipadapter_rows'): ipa_rows,
417
- key('sd3_ipadapter_images'): images,
418
- key('sd3_ipadapter_weights'): weights,
419
- key('sd3_ipadapter_start_percents'): start_percents,
420
- key('sd3_ipadapter_end_percents'): end_percents,
421
- })
422
-
423
- for i in range(max_units):
424
- with gr.Row(visible=(i < 1)) as row:
425
- with gr.Column(scale=1):
426
- images.append(gr.Image(label=f"IPAdapter Image {i+1}", type="pil", sources=["upload"], height=256))
427
- with gr.Column(scale=2):
428
- weights.append(gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=0.5, interactive=True))
429
- with gr.Row():
430
- start_percents.append(gr.Slider(label="Start At", minimum=0.0, maximum=1.0, step=0.01, value=0.0, interactive=True))
431
- end_percents.append(gr.Slider(label="End At", minimum=0.0, maximum=1.0, step=0.01, value=1.0, interactive=True))
432
- ipa_rows.append(row)
433
-
434
- with gr.Row():
435
- components[key('add_sd3_ipadapter_button')] = gr.Button("✚ Add IPAdapter (SD3)")
436
- components[key('delete_sd3_ipadapter_button')] = gr.Button("βž– Delete IPAdapter (SD3)", visible=False)
437
- components[key('sd3_ipadapter_count_state')] = gr.State(1)
438
-
439
- all_sd3_ipa_components_flat = images + weights + start_percents + end_percents
440
- components[key('all_sd3_ipadapter_components_flat')] = all_sd3_ipa_components_flat
441
-
442
- return components
443
-
444
- def create_style_ui(prefix: str):
445
- components = {}
446
- key = lambda name: f"{name}_{prefix}"
447
-
448
- with gr.Accordion("Style Settings (FLUX.1)", open=False, visible=('style' in default_enabled_chains)) as accordion:
449
- components[key('style_accordion')] = accordion
450
-
451
- style_rows, images, strengths = [], [], []
452
- components.update({
453
- key('style_rows'): style_rows,
454
- key('style_images'): images,
455
- key('style_strengths'): strengths
456
- })
457
-
458
- for i in range(5):
459
- with gr.Row(visible=(i < 1)) as row:
460
- with gr.Column(scale=1):
461
- images.append(gr.Image(label=f"Style Image {i+1}", type="pil", sources=["upload"], height=256))
462
- with gr.Column(scale=2):
463
- strengths.append(gr.Slider(label="Strength", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True))
464
- style_rows.append(row)
465
-
466
- with gr.Row():
467
- components[key('add_style_button')] = gr.Button("✚ Add Style (FLUX)")
468
- components[key('delete_style_button')] = gr.Button("βž– Delete Style (FLUX)", visible=False)
469
- components[key('style_count_state')] = gr.State(1)
470
-
471
- all_style_components_flat = images + strengths
472
- components[key('all_style_components_flat')] = all_style_components_flat
473
-
474
- return components
475
-
476
- def create_embedding_ui(prefix: str):
477
- components = {}
478
- key = lambda name: f"{name}_{prefix}"
479
-
480
- with gr.Accordion("Embedding Settings", open=False, visible=('embedding' in default_enabled_chains)) as accordion:
481
- components[key('embedding_accordion')] = accordion
482
- 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.")
483
-
484
- embedding_rows, sources, ids, files, upload_buttons = [], [], [], [], []
485
- components.update({
486
- key('embedding_rows'): embedding_rows,
487
- key('embeddings_sources'): sources,
488
- key('embeddings_ids'): ids,
489
- key('embeddings_files'): files,
490
- key('embeddings_uploads'): upload_buttons
491
- })
492
-
493
- for i in range(MAX_EMBEDDINGS):
494
- with gr.Row(visible=(i < 1)) as row:
495
- sources.append(gr.Dropdown(label=f"Embedding Source {i+1}", choices=LORA_SOURCE_CHOICES, value="Civitai", scale=1, interactive=True))
496
- ids.append(gr.Textbox(label="Civitai Version ID / HF file / Upload File", scale=3, interactive=True, type="text"))
497
- upload_btn = gr.UploadButton("Upload", file_types=[".safetensors"], scale=1)
498
- files.append(gr.State(None))
499
- upload_buttons.append(upload_btn)
500
- embedding_rows.append(row)
501
-
502
- with gr.Row():
503
- components[key('add_embedding_button')] = gr.Button("✚ Add Embedding")
504
- components[key('delete_embedding_button')] = gr.Button("βž– Delete Embedding", visible=False)
505
- components[key('embedding_count_state')] = gr.State(1)
506
-
507
- all_embedding_components_flat = []
508
- for i in range(MAX_EMBEDDINGS):
509
- all_embedding_components_flat.extend([sources[i], ids[i], files[i]])
510
- components[key('all_embedding_components_flat')] = all_embedding_components_flat
511
-
512
- return components
513
-
514
- def create_conditioning_ui(prefix: str):
515
- components = {}
516
- key = lambda name: f"{name}_{prefix}"
517
-
518
- with gr.Accordion("Conditioning Settings", open=False, visible=('conditioning' in default_enabled_chains)) as accordion:
519
- components[key('conditioning_accordion')] = accordion
520
- gr.Markdown("πŸ’‘ **Tip:** Define rectangular areas and assign specific prompts to them. Coordinates (X, Y) start from the top-left corner.")
521
-
522
- cond_rows, prompts, widths, heights, xs, ys, strengths = [], [], [], [], [], [], []
523
- components.update({
524
- key('conditioning_rows'): cond_rows,
525
- key('conditioning_prompts'): prompts,
526
- key('conditioning_widths'): widths,
527
- key('conditioning_heights'): heights,
528
- key('conditioning_xs'): xs,
529
- key('conditioning_ys'): ys,
530
- key('conditioning_strengths'): strengths
531
- })
532
-
533
- for i in range(MAX_CONDITIONINGS):
534
- with gr.Column(visible=(i < 1)) as row_wrapper:
535
- prompts.append(gr.Textbox(label=f"Area Prompt {i+1}", lines=2, interactive=True))
536
- with gr.Row():
537
- xs.append(gr.Number(label="X", value=0, interactive=True, step=8, scale=1))
538
- ys.append(gr.Number(label="Y", value=0, interactive=True, step=8, scale=1))
539
- widths.append(gr.Number(label="Width", value=512, interactive=True, step=8, scale=1))
540
- heights.append(gr.Number(label="Height", value=512, interactive=True, step=8, scale=1))
541
- strengths.append(gr.Slider(label="Strength", minimum=0.1, maximum=2.0, step=0.05, value=1.0, interactive=True, scale=2))
542
- cond_rows.append(row_wrapper)
543
-
544
- with gr.Row():
545
- components[key('add_conditioning_button')] = gr.Button("✚ Add Area")
546
- components[key('delete_conditioning_button')] = gr.Button("βž– Delete Area", visible=False)
547
- components[key('conditioning_count_state')] = gr.State(1)
548
-
549
- all_cond_components_flat = prompts + widths + heights + xs + ys + strengths
550
- components[key('all_conditioning_components_flat')] = all_cond_components_flat
551
-
552
- return components
553
-
554
- def on_vae_upload(file_obj):
555
- if not file_obj:
556
- return gr.update(), gr.update(), None
557
-
558
- hashed_filename = save_uploaded_file_with_hash(file_obj, VAE_DIR)
559
- return hashed_filename, "File", file_obj
560
-
561
- def create_vae_override_ui(prefix: str):
562
- components = {}
563
- key = lambda name: f"{name}_{prefix}"
564
- source_choices = ["None"] + LORA_SOURCE_CHOICES
565
-
566
- with gr.Accordion("VAE Settings (Override)", open=False, visible=('vae' in default_enabled_chains)) as vae_accordion:
567
- components[key('vae_accordion')] = vae_accordion
568
- 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`).")
569
- with gr.Row():
570
- components[key('vae_source')] = gr.Dropdown(
571
- label="VAE Source",
572
- choices=source_choices,
573
- value="None",
574
- scale=1,
575
- interactive=True
576
- )
577
- components[key('vae_id')] = gr.Textbox(
578
- label="Civitai Version ID / HF file / Upload File",
579
- scale=3,
580
- interactive=True,
581
- type="text"
582
- )
583
- upload_btn = gr.UploadButton(
584
- "Upload",
585
- file_types=[".safetensors"],
586
- scale=1
587
- )
588
- components[key('vae_upload_button')] = upload_btn
589
- components[key('vae_file')] = gr.State(None)
590
-
591
- upload_btn.upload(
592
- fn=on_vae_upload,
593
- inputs=[upload_btn],
594
- outputs=[components[key('vae_id')], components[key('vae_source')], components[key('vae_file')]]
595
- )
596
-
597
- return components
598
-
599
- def create_reference_latent_ui(prefix: str, max_units=10):
600
- components = {}
601
- key = lambda name: f"{name}_{prefix}"
602
-
603
- with gr.Accordion("Reference Edit Settings", open=False, visible=('reference_latent' in default_enabled_chains)) as ref_accordion:
604
- components[key('reference_latent_accordion')] = ref_accordion
605
- 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**.")
606
-
607
- ref_image_groups = []
608
- ref_image_inputs = []
609
- with gr.Row():
610
- for i in range(max_units):
611
- with gr.Column(visible=(i < 1), min_width=160) as img_col:
612
- img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
613
- ref_image_groups.append(img_col)
614
- ref_image_inputs.append(img_comp)
615
-
616
- components[key('reference_latent_rows')] = ref_image_groups
617
- components[key('reference_latent_images')] = ref_image_inputs
618
-
619
- with gr.Row():
620
- components[key('add_reference_latent_button')] = gr.Button("✚ Add Reference Image")
621
- components[key('delete_reference_latent_button')] = gr.Button("βž– Delete Reference Image", visible=False)
622
- components[key('reference_latent_count_state')] = gr.State(1)
623
-
624
- components[key('all_reference_latent_components_flat')] = ref_image_inputs
625
-
626
- return components
627
-
628
- def create_hidream_o1_reference_ui(prefix: str, max_units=10):
629
- components = {}
630
- key = lambda name: f"{name}_{prefix}"
631
-
632
- with gr.Accordion("HiDream-O1 Reference Edit Settings", open=False, visible=('hidream_o1_reference' in default_enabled_chains)) as ref_accordion:
633
- components[key('hidream_o1_reference_accordion')] = ref_accordion
634
- 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**.")
635
-
636
- ref_image_groups = []
637
- ref_image_inputs = []
638
- with gr.Row():
639
- for i in range(max_units):
640
- with gr.Column(visible=(i < 1), min_width=160) as img_col:
641
- img_comp = gr.Image(type="pil", label=f"Ref. {i+1}", sources=["upload"], height=150)
642
- ref_image_groups.append(img_col)
643
- ref_image_inputs.append(img_comp)
644
-
645
- components[key('hidream_o1_reference_rows')] = ref_image_groups
646
- components[key('hidream_o1_reference_images')] = ref_image_inputs
647
-
648
- with gr.Row():
649
- components[key('add_hidream_o1_reference_button')] = gr.Button("✚ Add Reference Image")
650
- components[key('delete_hidream_o1_reference_button')] = gr.Button("βž– Delete Reference Image", visible=False)
651
- components[key('hidream_o1_reference_count_state')] = gr.State(1)
652
-
653
- components[key('all_hidream_o1_reference_components_flat')] = ref_image_inputs
654
-
655
- return components
656
-
657
- def create_pid_ui(prefix: str):
658
- components = {}
659
- key = lambda name: f"{name}_{prefix}"
660
-
661
- with gr.Accordion("PiD Settings", open=False, visible=('pid' in default_enabled_chains)) as pid_accordion:
662
- components[key('pid_accordion')] = pid_accordion
663
- gr.Markdown("πŸ’‘ **Tip:** Use PiD (Pixel Diffusion Decoder) instead of the VAE Decoder for 4x decoding.")
664
- with gr.Row():
665
- components[key('pid_settings')] = gr.Dropdown(
666
- label="PiD Mode",
667
- choices=["OFF", "ON"],
668
- value="OFF",
669
- interactive=True
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