RioShiina commited on
Commit
5628af4
·
1 Parent(s): 3ecf4c3

Add SD1.5 architecture; rename to Animated T2I with LoRAs

Browse files
Files changed (4) hide show
  1. .gitattributes +4 -35
  2. README.md +9 -9
  3. app.py +483 -436
  4. requirements.txt +32 -10
.gitattributes CHANGED
@@ -1,35 +1,4 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ /web/assets/** linguist-generated
2
+ /web/** linguist-vendored
3
+ comfy_api_nodes/apis/__init__.py linguist-generated
4
+ comfy/text_encoders/t5_pile_tokenizer/tokenizer.model filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -1,9 +1,9 @@
1
- ---
2
- title: Animated SDXL T2I with LoRAs
3
- emoji: 🖼
4
- colorFrom: purple
5
- colorTo: red
6
- sdk: gradio
7
- app_file: app.py
8
- pinned: true
9
- ---
 
1
+ ---
2
+ title: Animated T2I with LoRAs
3
+ emoji: 🖼
4
+ colorFrom: purple
5
+ colorTo: red
6
+ sdk: gradio
7
+ app_file: app.py
8
+ pinned: true
9
+ ---
app.py CHANGED
@@ -1,506 +1,553 @@
1
- import spaces
2
- import gradio as gr
3
- import numpy as np
4
- import PIL.Image
5
- from PIL import Image, PngImagePlugin
6
  import random
7
- from diffusers import StableDiffusionXLPipeline, EulerAncestralDiscreteScheduler, EulerDiscreteScheduler, DPMSolverMultistepScheduler, DDIMScheduler, UniPCMultistepScheduler, HeunDiscreteScheduler, LMSDiscreteScheduler
 
8
  import torch
9
- from compel import Compel, ReturnedEmbeddingsType
 
 
 
 
 
10
  import requests
11
- import os
12
  import re
13
- import gc
14
- from huggingface_hub import hf_hub_download, snapshot_download
15
- import time
16
 
17
- # This dummy function is required to pass the Hugging Face Spaces startup check for GPU apps.
18
  @spaces.GPU(duration=60)
19
  def dummy_gpu_for_startup():
20
  print("Dummy function for startup check executed. This is normal.")
21
  return "Startup check passed."
22
 
23
- # --- Constants ---
24
- MAX_LORAS = 5
25
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
26
- MAX_SEED = np.iinfo(np.int64).max
27
- MAX_IMAGE_SIZE = 1216
28
- SAMPLER_MAP = {
29
- "Euler a": EulerAncestralDiscreteScheduler,
30
- "Euler": EulerDiscreteScheduler,
31
- "DPM++ 2M Karras": DPMSolverMultistepScheduler,
32
- "DDIM": DDIMScheduler,
33
- "UniPC": UniPCMultistepScheduler,
34
- "Heun": HeunDiscreteScheduler,
35
- "LMS": LMSDiscreteScheduler,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  }
37
- SCHEDULE_TYPE_MAP = ["Default", "Karras", "Uniform", "SGM Uniform"]
38
- DEFAULT_SCHEDULE_TYPE = "Default"
39
- DEFAULT_SAMPLER = "Euler a"
40
- DEFAULT_NEGATIVE_PROMPT = "monochrome, (low quality, worst quality:1.2), 3d, watermark, signature, ugly, poorly drawn,"
41
- DOWNLOAD_DIR = "/tmp/loras"
42
- os.makedirs(DOWNLOAD_DIR, exist_ok=True)
43
-
44
- # --- Model Lists ---
45
- MODEL_LIST = [
46
- "dhead/wai-nsfw-illustrious-sdxl-v140-sdxl",
47
- "Laxhar/noobai-XL-Vpred-1.0",
48
- "John6666/hassaku-xl-illustrious-v30-sdxl",
49
- "RedRayz/hikari_noob_v-pred_1.2.2",
50
- "bluepen5805/noob_v_pencil-XL",
51
- "Laxhar/noobai-XL-1.1"
52
- ]
53
-
54
- # --- List of V-Prediction Models ---
55
- V_PREDICTION_MODELS = [
56
- "Laxhar/noobai-XL-Vpred-1.0",
57
- "RedRayz/hikari_noob_v-pred_1.2.2",
58
- "bluepen5805/noob_v_pencil-XL"
59
- ]
60
-
61
- # --- Dictionary for single-file models now stores the filename ---
62
- SINGLE_FILE_MODELS = {
63
- "bluepen5805/noob_v_pencil-XL": "noob_v_pencil-XL-v3.0.0.safetensors"
64
  }
65
-
66
- # --- Model Hash to Name Mapping ---
67
- HASH_TO_MODEL_MAP = {
68
- "bdb59bac77": "dhead/wai-nsfw-illustrious-sdxl-v140-sdxl",
69
- "ea349eeae8": "Laxhar/noobai-XL-Vpred-1.0",
70
- "b4fb5f829a": "John6666/hassaku-xl-illustrious-v30-sdxl",
71
- "6681e8e4b1": "Laxhar/noobai-XL-1.1",
72
- "90b7911a78": "bluepen5805/noob_v_pencil-XL",
73
- "874170688a": "RedRayz/hikari_noob_v-pred_1.2.2"
74
  }
75
 
76
- def download_all_base_models_on_startup():
77
- """Downloads all base models listed in MODEL_LIST when the app starts."""
78
- print("--- Starting pre-download of all base models ---")
79
- for model_name in MODEL_LIST:
80
- try:
81
- print(f"Downloading: {model_name}...")
82
- start_time = time.time()
83
- # Handle single-file models
84
- if model_name in SINGLE_FILE_MODELS:
85
- filename = SINGLE_FILE_MODELS[model_name]
86
- hf_hub_download(repo_id=model_name, filename=filename)
87
- # Handle standard diffusers models
88
- else:
89
- snapshot_download(repo_id=model_name, ignore_patterns=["*.onnx", "*.flax"])
90
- end_time = time.time()
91
- print(f"✅ Successfully downloaded {model_name} in {end_time - start_time:.2f} seconds.")
92
- except Exception as e:
93
- print(f"❌ Failed to download {model_name}: {e}")
94
- finally:
95
- # Clean up to conserve memory
96
- gc.collect()
97
- if torch.cuda.is_available():
98
- torch.cuda.empty_cache()
99
- print("--- Finished pre-downloading all base models ---")
100
-
101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  def get_civitai_file_info(version_id):
103
- """Gets the file metadata for a model version via the Civitai API."""
104
  api_url = f"https://civitai.com/api/v1/model-versions/{version_id}"
105
  try:
106
- response = requests.get(api_url)
107
- response.raise_for_status()
108
- data = response.json()
109
  for file_data in data.get('files', []):
110
- if file_data['name'].endswith('.safetensors'):
111
- return file_data
112
- if data.get('files'):
113
- return data['files'][0]
114
- return None
115
- except Exception as e:
116
- print(f"Could not get file info from Civitai API: {e}")
117
- return None
118
 
119
- def download_file(url, save_path, api_key=None, progress=None, desc=""):
120
- """Downloads a file, skipping if it already exists."""
121
- if os.path.exists(save_path):
122
- return f"File already exists: {os.path.basename(save_path)}"
123
-
124
- headers = {}
125
- if api_key and api_key.strip():
126
- headers['Authorization'] = f'Bearer {api_key}'
 
 
127
 
 
 
 
128
  try:
129
  if progress: progress(0, desc=desc)
130
- response = requests.get(url, stream=True, headers=headers)
131
- response.raise_for_status()
132
-
133
  total_size = int(response.headers.get('content-length', 0))
134
-
135
  with open(save_path, "wb") as f:
136
  downloaded = 0
137
  for chunk in response.iter_content(chunk_size=8192):
138
  f.write(chunk)
139
- if progress and total_size > 0:
140
- downloaded += len(chunk)
141
- progress(downloaded / total_size, desc=desc)
142
-
143
  return f"Successfully downloaded: {os.path.basename(save_path)}"
144
  except Exception as e:
145
  if os.path.exists(save_path): os.remove(save_path)
146
  return f"Download failed for {os.path.basename(save_path)}: {e}"
147
 
148
- def process_long_prompt(compel_proc, prompt, negative_prompt=""):
149
- try:
150
- conditioning, pooled = compel_proc([prompt, negative_prompt])
151
- return conditioning, pooled
152
- except Exception:
153
- return None, None
154
-
155
- def pre_download_loras(civitai_api_key, *lora_data, progress=gr.Progress(track_tqdm=True)):
156
- civitai_ids = lora_data[0::2]
157
- status_log = []
158
-
159
- active_lora_ids = [cid for cid in civitai_ids if cid and cid.strip()]
160
- if not active_lora_ids:
161
- return "No LoRA IDs provided to download."
162
-
163
- for i, civitai_id in enumerate(active_lora_ids):
164
- version_id = civitai_id.strip()
165
- progress(i / len(active_lora_ids), desc=f"Getting URL for LoRA ID: {version_id}")
166
-
167
- local_lora_path = os.path.join(DOWNLOAD_DIR, f"civitai_{version_id}.safetensors")
168
-
169
- file_info = get_civitai_file_info(version_id)
170
- if not file_info:
171
- status_log.append(f"* LoRA ID {version_id}: Could not get file info from Civitai.")
172
- continue
173
-
174
- download_url = file_info.get('downloadUrl')
175
- if not download_url:
176
- status_log.append(f"* LoRA ID {version_id}: Could not get download link.")
177
- continue
178
-
179
- status = download_file(
180
- download_url,
181
- local_lora_path,
182
- api_key=civitai_api_key,
183
- progress=progress,
184
- desc=f"Downloading LoRA ID: {version_id}"
185
- )
186
- status_log.append(f"* LoRA ID {version_id}: {status}")
187
-
188
- return "\n".join(status_log)
189
-
190
- def _infer_logic(base_model_name, prompt, negative_prompt, seed, batch_size, width, height, guidance_scale, num_inference_steps,
191
- sampler, schedule_type,
192
- civitai_api_key,
193
- *lora_data,
194
- progress=gr.Progress(track_tqdm=True)):
195
 
196
- pipe = None
197
- try:
198
- progress(0, desc=f"Loading model: {base_model_name}")
199
-
200
- if base_model_name in SINGLE_FILE_MODELS:
201
- filename = SINGLE_FILE_MODELS[base_model_name]
202
- print(f"Loading single file: {filename} from repo: {base_model_name}")
203
- local_path = hf_hub_download(repo_id=base_model_name, filename=filename)
204
- pipe = StableDiffusionXLPipeline.from_single_file(
205
- local_path,
206
- torch_dtype=torch.float16,
207
- use_safetensors=True
208
- )
209
- else:
210
- print(f"Loading diffusers model: {base_model_name}")
211
- pipe = StableDiffusionXLPipeline.from_pretrained(
212
- base_model_name,
213
- torch_dtype=torch.float16,
214
- use_safetensors=True
215
- )
216
- pipe.to(device)
217
-
218
- batch_size = int(batch_size)
219
- seed = int(seed)
 
 
 
220
 
221
- pipe.unload_lora_weights()
 
222
 
223
- scheduler_class = SAMPLER_MAP.get(sampler, EulerAncestralDiscreteScheduler)
224
- scheduler_config = pipe.scheduler.config
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
226
- if base_model_name in V_PREDICTION_MODELS:
227
- scheduler_config['prediction_type'] = 'v_prediction'
228
- else:
229
- scheduler_config['prediction_type'] = 'epsilon'
230
-
231
- scheduler_kwargs = {}
232
- if schedule_type == "Default" and sampler == "DPM++ 2M Karras":
233
- scheduler_kwargs['use_karras_sigmas'] = True
234
- elif schedule_type == "Karras":
235
- scheduler_kwargs['use_karras_sigmas'] = True
236
- elif schedule_type == "Uniform":
237
- scheduler_kwargs['use_karras_sigmas'] = False
238
- elif schedule_type == "SGM Uniform":
239
- scheduler_kwargs['algorithm_type'] = 'sgm_uniform'
240
-
241
- pipe.scheduler = scheduler_class.from_config(scheduler_config, **scheduler_kwargs)
242
 
243
- compel_type = ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED
244
- compel = Compel(tokenizer=[pipe.tokenizer, pipe.tokenizer_2], text_encoder=[pipe.text_encoder, pipe.text_encoder_2],
245
- returned_embeddings_type=compel_type, requires_pooled=[False, True], truncate_long_prompts=False)
 
 
 
246
 
247
- civitai_ids, lora_scales = lora_data[0::2], lora_data[1::2]
248
- lora_params = list(zip(civitai_ids, lora_scales))
249
- active_loras, active_lora_names_for_meta = [], []
250
-
251
- for i, (civitai_id, lora_scale) in enumerate(lora_params):
252
- if civitai_id and civitai_id.strip() and lora_scale > 0:
253
- version_id = civitai_id.strip()
254
- local_lora_path = os.path.join(DOWNLOAD_DIR, f"civitai_{version_id}.safetensors")
255
-
256
- if not os.path.exists(local_lora_path):
257
- file_info = get_civitai_file_info(version_id)
258
- if not file_info:
259
- print(f"Could not get file info for Civitai ID {version_id}, skipping.")
260
- continue
261
-
262
- download_url = file_info.get('downloadUrl')
263
- if download_url:
264
- download_file(download_url, local_lora_path, api_key=civitai_api_key, progress=progress, desc=f"Downloading LoRA ID {version_id}")
265
- else:
266
- print(f"Could not get download link for Civitai ID {version_id} during inference, skipping."); continue
267
-
268
- if not os.path.exists(local_lora_path): print(f"LoRA file for ID {version_id} not found, skipping."); continue
269
-
270
- adapter_name = f"lora_{i+1}"
271
- progress((i * 0.1) + 0.05, desc=f"Loading LoRA (ID: {version_id})")
272
- pipe.load_lora_weights(local_lora_path, adapter_name=adapter_name)
273
- active_loras.append((adapter_name, lora_scale))
274
- active_lora_names_for_meta.append(f"LoRA {i+1} (ID: {version_id}, Weight: {lora_scale})")
275
-
276
- if active_loras:
277
- adapter_names, adapter_weights = zip(*active_loras); pipe.set_adapters(list(adapter_names), list(adapter_weights))
278
 
279
- conditioning, pooled = process_long_prompt(compel, prompt, negative_prompt)
280
 
281
- pipe_args = {
282
- "guidance_scale": guidance_scale,
283
- "num_inference_steps": num_inference_steps,
284
- "width": width,
285
- "height": height,
286
- }
287
-
288
- output_images = []
289
- loras_string = f"LoRAs: [{', '.join(active_lora_names_for_meta)}]" if active_lora_names_for_meta else ""
 
 
 
290
 
291
- for i in range(batch_size):
292
- progress(i / batch_size, desc=f"Generating image {i+1}/{batch_size}")
 
 
 
293
 
294
- if i == 0 and seed != -1:
295
- current_seed = seed
296
- else:
297
- current_seed = random.randint(0, MAX_SEED)
298
-
299
- generator = torch.Generator(device=device).manual_seed(current_seed)
300
- pipe_args["generator"] = generator
301
-
302
- if conditioning is not None:
303
- image = pipe(prompt_embeds=conditioning[0:1], pooled_prompt_embeds=pooled[0:1], negative_prompt_embeds=conditioning[1:2], negative_pooled_prompt_embeds=pooled[1:2], **pipe_args).images[0]
304
- else:
305
- image = pipe(prompt=prompt, negative_prompt=negative_prompt, **pipe_args).images[0]
306
 
307
- params_string = f"{prompt}\nNegative prompt: {negative_prompt}\n"
308
- params_string += f"Steps: {num_inference_steps}, Sampler: {sampler}, Schedule type: {schedule_type}, CFG scale: {guidance_scale}, Seed: {current_seed}, Size: {width}x{height}, Base Model: {base_model_name}, {loras_string}".strip()
309
- image.info = {'parameters': params_string}
310
- output_images.append(image)
311
-
312
- return output_images
313
-
314
- except Exception as e:
315
- print(f"An error occurred during generation: {e}"); raise gr.Error(f"Generation failed: {e}")
316
- finally:
317
- if pipe is not None:
318
- pipe.disable_lora()
319
- del pipe
320
- gc.collect()
321
- if torch.cuda.is_available():
322
- torch.cuda.empty_cache()
323
 
324
- def infer(base_model_name, prompt, negative_prompt, seed, batch_size, width, height, guidance_scale, num_inference_steps,
325
- sampler, schedule_type,
326
- civitai_api_key,
327
- zero_gpu_duration,
328
- *lora_data,
329
- progress=gr.Progress(track_tqdm=True)):
330
-
331
- duration = 60
332
- if zero_gpu_duration and int(zero_gpu_duration) > 0:
333
- duration = int(zero_gpu_duration)
334
 
335
- print(f"Using ZeroGPU duration: {duration} seconds")
 
 
 
 
 
 
 
 
 
 
336
 
337
- decorated_infer_logic = spaces.GPU(duration=duration)(_infer_logic)
338
-
339
- return decorated_infer_logic(
340
- base_model_name, prompt, negative_prompt, seed, batch_size, width, height, guidance_scale, num_inference_steps,
341
- sampler, schedule_type, civitai_api_key, *lora_data, progress=progress
342
- )
343
 
 
344
  def _parse_parameters(params_text):
345
- data = {'lora_ids': [''] * MAX_LORAS, 'lora_scales': [0.0] * MAX_LORAS}
346
- lines = params_text.strip().split('\n')
347
- data['prompt'] = lines[0]
348
  data['negative_prompt'] = lines[1].replace("Negative prompt:", "").strip() if len(lines) > 1 and lines[1].startswith("Negative prompt:") else ""
349
- params_line = lines[2] if len(lines) > 2 else ""
350
-
351
- def find_param(key, default, cast_type=str):
352
- match = re.search(fr"\b{key}: ([^,]+?)(,|$)", params_line)
353
- if match:
354
- try:
355
- return cast_type(match.group(1).strip())
356
- except (ValueError, TypeError):
357
- return default
358
- return default
359
-
360
- data['steps'] = find_param("Steps", 28, int)
361
- data['sampler'] = find_param("Sampler", DEFAULT_SAMPLER)
362
- data['schedule_type'] = find_param("Schedule type", DEFAULT_SCHEDULE_TYPE)
363
- data['cfg_scale'] = find_param("CFG scale", 7.0, float)
364
- data['seed'] = find_param("Seed", -1, int)
365
- data['base_model'] = find_param("Base Model", MODEL_LIST[0])
366
- data['model_hash'] = find_param("Model hash", None)
367
-
368
- size_match = re.search(r"Size: (\d+)x(\d+)", params_line); data['width'], data['height'] = (int(size_match.group(1)), int(size_match.group(2))) if size_match else (1024, 1024)
369
- if loras_match := re.search(r"LoRAs: \[(.+?)\]", params_line):
370
- for i, (lora_id, lora_scale) in enumerate(re.findall(r"ID: (\d+), Weight: ([\d.]+)", loras_match.group(1))):
371
- if i < MAX_LORAS: data['lora_ids'][i] = lora_id; data['lora_scales'][i] = float(lora_scale)
372
  return data
373
 
374
  def get_png_info(image):
375
- if image is None: return "", "", "Please upload an image first."
376
- params = image.info.get('parameters', None)
377
- if not params: return "", "", "No metadata found in the image."
378
- try:
379
- parsed_data = _parse_parameters(params)
380
- lines = params.strip().split('\n')
381
- other_params_text = lines[2] if len(lines) > 2 else ""
382
- other_params_display = "\n".join([p.strip() for p in other_params_text.split(',')])
383
-
384
- return parsed_data.get('prompt', ''), parsed_data.get('negative_prompt', ''), other_params_display
385
- except Exception as e:
386
- return "", "", f"Error parsing metadata: {e}\n\nRaw metadata:\n{params}"
387
-
388
- def send_info_to_txt2img(image):
389
- if image is None or not (params := image.info.get('parameters', '')):
390
- return [gr.update()] * (12 + MAX_LORAS * 2 + 1)
391
-
392
- data = _parse_parameters(params)
393
-
394
- model_from_hash = HASH_TO_MODEL_MAP.get(data.get('model_hash'))
395
- final_base_model = model_from_hash if model_from_hash else data.get('base_model', MODEL_LIST[0])
396
-
397
- sampler_from_png = data.get('sampler', DEFAULT_SAMPLER)
398
- final_sampler = sampler_from_png if sampler_from_png in SAMPLER_MAP else DEFAULT_SAMPLER
399
-
400
- schedule_from_png = data.get('schedule_type', DEFAULT_SCHEDULE_TYPE)
401
- final_schedule_type = schedule_from_png if schedule_from_png in SCHEDULE_TYPE_MAP else DEFAULT_SCHEDULE_TYPE
402
-
403
- updates = [final_base_model, data['prompt'], data['negative_prompt'], data['seed'], gr.update(), gr.update(), data['width'], data['height'],
404
- data['cfg_scale'], data['steps'], final_sampler, final_schedule_type]
405
 
406
- for i in range(MAX_LORAS): updates.extend([data['lora_ids'][i], data['lora_scales'][i]])
407
- updates.append(gr.Tabs(selected=0))
408
  return updates
409
 
410
- # --- Execute model download on startup ---
411
- download_all_base_models_on_startup()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
 
 
 
413
 
 
414
  with gr.Blocks(css="#col-container {margin: 0 auto; max-width: 1024px;}") as demo:
415
- gr.Markdown("# Animated SDXL T2I with LoRAs")
416
  with gr.Tabs(elem_id="tabs_container") as tabs:
417
  with gr.TabItem("txt2img", id=0):
418
- gr.Markdown("<div style='background-color: #282828; color: #a0aec0; padding: 10px; border-radius: 5px; margin-bottom: 15px;'>💡 <b>Tip:</b> Pre-downloading LoRAs before 'Run' can maximize ZeroGPU time.</div>")
419
- with gr.Column(elem_id="col-container"):
420
- with gr.Row():
421
- with gr.Column(scale=3):
422
- base_model_name = gr.Dropdown(label="Base Model", choices=MODEL_LIST, value="Laxhar/noobai-XL-Vpred-1.0")
423
- with gr.Column(scale=1):
424
- predownload_lora_button = gr.Button("Pre-download LoRAs")
425
- run_button = gr.Button("Run", variant="primary")
426
-
427
- predownload_status = gr.Markdown("")
428
- prompt = gr.Text(label="Prompt", lines=3, placeholder="Enter your prompt")
429
- negative_prompt = gr.Text(label="Negative prompt", lines=3, placeholder="Enter a negative prompt", value=DEFAULT_NEGATIVE_PROMPT)
430
-
431
- # --- UI Layout ---
432
- with gr.Row():
433
- with gr.Column(scale=2):
434
- with gr.Row():
435
- width = gr.Slider(label="Width", minimum=256, maximum=MAX_IMAGE_SIZE, step=32, value=1024)
436
- height = gr.Slider(label="Height", minimum=256, maximum=MAX_IMAGE_SIZE, step=32, value=1024)
437
- with gr.Row():
438
- sampler = gr.Dropdown(label="Sampling method", choices=list(SAMPLER_MAP.keys()), value=DEFAULT_SAMPLER)
439
- schedule_type = gr.Dropdown(label="Schedule type", choices=SCHEDULE_TYPE_MAP, value=DEFAULT_SCHEDULE_TYPE)
440
- with gr.Row():
441
- guidance_scale = gr.Slider(label="CFG Scale", minimum=0.0, maximum=20.0, step=0.1, value=7)
442
- num_inference_steps = gr.Slider(label="Sampling steps", minimum=1, maximum=50, step=1, value=28)
443
-
444
- with gr.Column(scale=1):
445
- result = gr.Gallery(label="Result", show_label=False, elem_id="result_gallery", columns=2, object_fit="contain", height="auto")
446
-
447
- with gr.Row():
448
- seed = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
449
- batch_size = gr.Slider(label="Batch size", minimum=1, maximum=8, step=1, value=1)
450
- zero_gpu_duration = gr.Number(
451
- label="ZeroGPU Duration (s)",
452
- value=None,
453
- placeholder="Default: 60s",
454
- info="Optional: Leave empty for default (60s), max to 120"
455
- )
456
-
457
- with gr.Accordion("LoRA Settings", open=False):
458
- gr.Markdown("⚠️ **Responsible Use Notice:** Please avoid excessive, rapid, or automated (scripted) use of the pre-download LoRA feature. Overt misuse may lead to service disruption. Thank you for your cooperation.")
459
- civitai_api_key = gr.Textbox(label="Optional Civitai API Key", info="Get from your Civitai account settings...", placeholder="Enter your Civitai API Key here", type="password", show_label=True)
460
- gr.Markdown("Find the Model Version ID in the LoRA page URL (e.g., `modelVersionId=12345`) and fill it in below.")
461
- lora_rows, lora_civitai_id_inputs, lora_scale_inputs = [], [], []
462
- for i in range(MAX_LORAS):
463
- with gr.Row(visible=(i == 0)) as row:
464
- lora_civitai_id = gr.Textbox(label=f"LoRA {i+1} - Civitai Model Version ID", placeholder="e.g.: 1834914")
465
- lora_scale = gr.Slider(label=f"Weight {i+1}", minimum=0.0, maximum=2.0, step=0.05, value=0.0)
466
- lora_rows.append(row); lora_civitai_id_inputs.append(lora_civitai_id); lora_scale_inputs.append(lora_scale)
467
- with gr.Row():
468
- add_lora_button = gr.Button("✚ Add LoRA", variant="secondary")
469
- lora_count_state = gr.State(value=1)
470
- all_lora_inputs = [item for pair in zip(lora_civitai_id_inputs, lora_scale_inputs) for item in pair]
471
-
472
  with gr.TabItem("PNG Info", id=1):
473
- with gr.Column(elem_id="col-container"):
474
- gr.Markdown("Upload a generated image to view its generation data.")
475
- info_image_input = gr.Image(type="pil", label="Upload Image")
 
 
476
  with gr.Row():
477
- info_get_button = gr.Button("Get Info", variant="secondary")
478
- send_to_txt2img_button = gr.Button("Send to txt2img", variant="primary")
 
 
479
  gr.Markdown("### Positive Prompt"); info_prompt_output = gr.Textbox(lines=3, interactive=False, show_label=False)
480
  gr.Markdown("### Negative Prompt"); info_neg_prompt_output = gr.Textbox(lines=3, interactive=False, show_label=False)
481
  gr.Markdown("### Other Parameters"); info_params_output = gr.Textbox(lines=5, interactive=False, show_label=False)
 
482
 
483
- gr.Markdown("<div style='text-align: center; margin-top: 20px;'>Made by <a href='https://civitai.com/user/RioShiina'>RioShiina</a> with ❤</div>")
484
-
485
- def add_lora_row(current_count):
486
- current_count = int(current_count)
487
- if current_count < MAX_LORAS:
488
- updates = {lora_count_state: current_count + 1, lora_rows[current_count]: gr.Row(visible=True)}
489
- if current_count + 1 == MAX_LORAS: updates[add_lora_button] = gr.Button(visible=False)
490
- return updates
491
- return {lora_count_state: current_count}
492
-
493
- add_lora_button.click(fn=add_lora_row, inputs=[lora_count_state], outputs=[lora_count_state, add_lora_button] + lora_rows)
494
-
495
- predownload_lora_button.click(fn=pre_download_loras, inputs=[civitai_api_key, *all_lora_inputs], outputs=[predownload_status])
496
 
497
- run_button.click(fn=infer,
498
- inputs=[base_model_name, prompt, negative_prompt, seed, batch_size, width, height, guidance_scale, num_inference_steps, sampler, schedule_type, civitai_api_key, zero_gpu_duration, *all_lora_inputs],
499
- outputs=[result])
 
 
 
 
500
 
501
- info_get_button.click(fn=get_png_info, inputs=[info_image_input], outputs=[info_prompt_output, info_neg_prompt_output, info_params_output])
 
 
502
 
503
- txt2img_outputs = [base_model_name, prompt, negative_prompt, seed, batch_size, zero_gpu_duration, width, height, guidance_scale, num_inference_steps, sampler, schedule_type, *all_lora_inputs, tabs]
504
- send_to_txt2img_button.click(fn=send_info_to_txt2img, inputs=[info_image_input], outputs=txt2img_outputs)
505
-
506
- demo.queue().launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
 
 
 
 
2
  import random
3
+ import sys
4
+ from typing import Sequence, Mapping, Any, Union
5
  import torch
6
+ import gradio as gr
7
+ from PIL import Image
8
+ from huggingface_hub import hf_hub_download
9
+ import spaces
10
+ from comfy import model_management # We need to import this early
11
+ import gc
12
  import requests
 
13
  import re
14
+ import hashlib
15
+ import shutil
 
16
 
17
+ # --- Startup Dummy Function ---
18
  @spaces.GPU(duration=60)
19
  def dummy_gpu_for_startup():
20
  print("Dummy function for startup check executed. This is normal.")
21
  return "Startup check passed."
22
 
23
+ # --- ComfyUI Backend Setup ---
24
+ def find_path(name: str, path: str = None) -> str:
25
+ if path is None: path = os.getcwd()
26
+ if name in os.listdir(path): return os.path.join(path, name)
27
+ parent_directory = os.path.dirname(path)
28
+ if parent_directory == path: return None
29
+ return find_path(name, parent_directory)
30
+
31
+ def add_comfyui_directory_to_sys_path() -> None:
32
+ comfyui_path = find_path("ComfyUI")
33
+ if comfyui_path and os.path.isdir(comfyui_path):
34
+ sys.path.append(comfyui_path)
35
+ print(f"'{comfyui_path}' added to sys.path")
36
+
37
+ def add_extra_model_paths() -> None:
38
+ try: from main import load_extra_path_config
39
+ except ImportError: from utils.extra_config import load_extra_path_config
40
+ extra_model_paths = find_path("extra_model_paths.yaml")
41
+ if extra_model_paths: load_extra_path_config(extra_model_paths)
42
+ else: print("Could not find extra_model_paths.yaml")
43
+
44
+ add_comfyui_directory_to_sys_path()
45
+ add_extra_model_paths()
46
+
47
+ # Monkey-patch for Sage Attention
48
+ print("Attempting to monkey-patch ComfyUI for Sage Attention...")
49
+ try:
50
+ model_management.sage_attention_enabled = lambda: True
51
+ model_management.pytorch_attention_enabled = lambda: False
52
+ print("Successfully monkey-patched model_management for Sage Attention.")
53
+ except Exception as e:
54
+ print(f"An error occurred during monkey-patching: {e}")
55
+
56
+ # --- Constants & Configuration ---
57
+ CHECKPOINT_DIR = "models/checkpoints"
58
+ LORA_DIR = "models/loras"
59
+ os.makedirs(CHECKPOINT_DIR, exist_ok=True)
60
+ os.makedirs(LORA_DIR, exist_ok=True)
61
+
62
+ # --- Model Definitions with Hashes ---
63
+ # Format: {Display Name: (Repo ID, Filename, Type, Hash)}
64
+ MODEL_MAP_ILLUSTRIOUS = {
65
+ "Laxhar/noobai-XL-Vpred-1.0": ("Laxhar/noobai-XL-Vpred-1.0", "NoobAI-XL-Vpred-v1.0.safetensors", "SDXL", "ea349eeae8"),
66
+ "Laxhar/noobai-XL-1.1": ("Laxhar/noobai-XL-1.1", "NoobAI-XL-v1.1.safetensors", "SDXL", "6681e8e4b1"),
67
+ "WAI0731/wai-nsfw-illustrious-sdxl-v140": ("Ine007/waiNSFWIllustrious_v140", "waiNSFWIllustrious_v140.safetensors", "SDXL", "bdb59bac77"),
68
+ "Ikena/hassaku-xl-illustrious-v30": ("misri/hassakuXLIllustrious_v30", "hassakuXLIllustrious_v30.safetensors", "SDXL", "b4fb5f829a"),
69
+ "bluepen5805/noob_v_pencil-XL": ("bluepen5805/noob_v_pencil-XL", "noob_v_pencil-XL-v3.0.0.safetensors", "SDXL", "90b7911a78"),
70
+ "RedRayz/hikari_noob_v-pred_1.2.2": ("RedRayz/hikari_noob_v-pred_1.2.2", "Hikari_Noob_v-pred_1.2.2.safetensors", "SDXL", "874170688a"),
71
  }
72
+ MODEL_MAP_ANIMAGINE = {
73
+ "cagliostrolab/animagine-xl-4.0": ("cagliostrolab/animagine-xl-4.0", "animagine-xl-4.0.safetensors", "SDXL", "6327eca98b"),
74
+ "cagliostrolab/animagine-xl-3.1": ("cagliostrolab/animagine-xl-3.1", "animagine-xl-3.1.safetensors", "SDXL", "e3c47aedb0"),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  }
76
+ MODEL_MAP_PONY = {
77
+ "PurpleSmartAI/Pony_Diffusion_V6_XL": ("LyliaEngine/Pony_Diffusion_V6_XL", "ponyDiffusionV6XL_v6StartWithThisOne.safetensors", "SDXL", "67ab2fd8ec"),
78
+ }
79
+ MODEL_MAP_SD15 = {
80
+ "Yuno779/anything-v3": ("ckpt/anything-v3.0", "Anything-V3.0-pruned.safetensors", "SD1.5", "ddd565f806"),
 
 
 
 
81
  }
82
 
83
+ # --- Combined Maps for Global Lookup ---
84
+ ALL_MODEL_MAP = {**MODEL_MAP_ILLUSTRIOUS, **MODEL_MAP_ANIMAGINE, **MODEL_MAP_PONY, **MODEL_MAP_SD15}
85
+ MODEL_TYPE_MAP = {k: v[2] for k, v in ALL_MODEL_MAP.items()}
86
+ DISPLAY_NAME_TO_HASH_MAP = {k: v[3] for k, v in ALL_MODEL_MAP.items()}
87
+ HASH_TO_DISPLAY_NAME_MAP = {v[3]: k for k, v in ALL_MODEL_MAP.items()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
+ # --- UI Defaults ---
90
+ DEFAULT_NEGATIVE_PROMPT = "monochrome, (low quality, worst quality:1.2), 3d, watermark, signature, ugly, poorly drawn,"
91
+ MAX_LORAS = 5
92
+ LORA_SOURCE_CHOICES = ["Civitai", "TensorArt", "Custom URL", "File"]
93
+
94
+ def get_value_at_index(obj: Union[Sequence, Mapping], index: int) -> Any:
95
+ try: return obj[index]
96
+ except (KeyError, IndexError):
97
+ try: return obj["result"][index]
98
+ except (KeyError, IndexError): return None
99
+
100
+ def import_custom_nodes() -> None:
101
+ import asyncio, execution, server
102
+ from nodes import init_extra_nodes
103
+ loop = asyncio.new_event_loop()
104
+ asyncio.set_event_loop(loop)
105
+ server_instance = server.PromptServer(loop)
106
+ execution.PromptQueue(server_instance)
107
+ loop.run_until_complete(init_extra_nodes())
108
+
109
+ # --- Import ComfyUI Nodes & Get Choices ---
110
+ from nodes import CheckpointLoaderSimple, EmptyLatentImage, KSampler, VAEDecode, SaveImage, NODE_CLASS_MAPPINGS
111
+ import_custom_nodes()
112
+ CLIPTextEncodeSDXL = NODE_CLASS_MAPPINGS['CLIPTextEncodeSDXL']
113
+ CLIPTextEncode = NODE_CLASS_MAPPINGS['CLIPTextEncode']
114
+ LoraLoader = NODE_CLASS_MAPPINGS['LoraLoader']
115
+ CLIPSetLastLayer = NODE_CLASS_MAPPINGS['CLIPSetLastLayer']
116
+ try:
117
+ SAMPLER_CHOICES = KSampler.INPUT_TYPES()["required"]["sampler_name"][0]
118
+ SCHEDULER_CHOICES = KSampler.INPUT_TYPES()["required"]["scheduler"][0]
119
+ except Exception:
120
+ SAMPLER_CHOICES = ['euler', 'dpmpp_2m_sde_gpu']
121
+ SCHEDULER_CHOICES = ['normal', 'karras']
122
+
123
+ # --- Instantiate Node Objects ---
124
+ checkpointloadersimple = CheckpointLoaderSimple(); cliptextencodesdxl = CLIPTextEncodeSDXL()
125
+ cliptextencode_sd15 = CLIPTextEncode(); emptylatentimage = EmptyLatentImage()
126
+ ksampler = KSampler(); vaedecode = VAEDecode(); saveimage = SaveImage(); loraloader = LoraLoader()
127
+ clipsetlastlayer = CLIPSetLastLayer()
128
+
129
+ # --- LoRA & File Utils ---
130
  def get_civitai_file_info(version_id):
 
131
  api_url = f"https://civitai.com/api/v1/model-versions/{version_id}"
132
  try:
133
+ response = requests.get(api_url, timeout=10); response.raise_for_status(); data = response.json()
 
 
134
  for file_data in data.get('files', []):
135
+ if file_data.get('type') == 'Model' and file_data['name'].endswith('.safetensors'): return file_data
136
+ if data.get('files'): return data['files'][0]
137
+ except Exception: return None
 
 
 
 
 
138
 
139
+ def get_tensorart_file_info(model_id):
140
+ api_url = f"https://tensor.art/api/v1/models/{model_id}"
141
+ try:
142
+ response = requests.get(api_url, timeout=10); response.raise_for_status(); data = response.json()
143
+ model_versions = data.get('modelVersions', [])
144
+ if not model_versions: return None
145
+ for file_data in model_versions[0].get('files', []):
146
+ if file_data['name'].endswith('.safetensors'): return file_data
147
+ return model_versions[0]['files'][0] if model_versions[0].get('files') else None
148
+ except Exception: return None
149
 
150
+ def download_file(url, save_path, api_key=None, progress=None, desc=""):
151
+ if os.path.exists(save_path): return f"File already exists: {os.path.basename(save_path)}"
152
+ headers = {'Authorization': f'Bearer {api_key}'} if api_key and api_key.strip() else {}
153
  try:
154
  if progress: progress(0, desc=desc)
155
+ response = requests.get(url, stream=True, headers=headers, timeout=15); response.raise_for_status()
 
 
156
  total_size = int(response.headers.get('content-length', 0))
 
157
  with open(save_path, "wb") as f:
158
  downloaded = 0
159
  for chunk in response.iter_content(chunk_size=8192):
160
  f.write(chunk)
161
+ if progress and total_size > 0: downloaded += len(chunk); progress(downloaded / total_size, desc=desc)
 
 
 
162
  return f"Successfully downloaded: {os.path.basename(save_path)}"
163
  except Exception as e:
164
  if os.path.exists(save_path): os.remove(save_path)
165
  return f"Download failed for {os.path.basename(save_path)}: {e}"
166
 
167
+ def get_lora_path(source, id_or_url, civitai_key, tensorart_key, progress):
168
+ if not id_or_url or not id_or_url.strip(): return None, "No ID/URL provided."
169
+ if source == "Civitai":
170
+ version_id = id_or_url.strip(); local_path = os.path.join(LORA_DIR, f"civitai_{version_id}.safetensors"); file_info, api_key_to_use = get_civitai_file_info(version_id), civitai_key; source_name = f"Civitai ID {version_id}"
171
+ elif source == "TensorArt":
172
+ model_id = id_or_url.strip(); local_path = os.path.join(LORA_DIR, f"tensorart_{model_id}.safetensors"); file_info, api_key_to_use = get_tensorart_file_info(model_id), tensorart_key; source_name = f"TensorArt ID {model_id}"
173
+ elif source == "Custom URL":
174
+ url = id_or_url.strip(); url_hash = hashlib.md5(url.encode()).hexdigest(); local_path = os.path.join(LORA_DIR, f"custom_{url_hash}.safetensors"); file_info, api_key_to_use = {'downloadUrl': url}, None; source_name = f"URL {url[:30]}..."
175
+ else: return None, "Invalid source."
176
+ if os.path.exists(local_path): return local_path, "File already exists."
177
+ if not file_info or not file_info.get('downloadUrl'): return None, f"Could not get download link for {source_name}."
178
+ status = download_file(file_info['downloadUrl'], local_path, api_key_to_use, progress=progress, desc=f"Downloading {source_name}")
179
+ return (local_path, status) if "Successfully" in status else (None, status)
180
+
181
+ def pre_download_loras(civitai_api_key, tensorart_api_key, *lora_data, progress=gr.Progress(track_tqdm=True)):
182
+ sources, ids, _, files = lora_data[0::4], lora_data[1::4], lora_data[2::4], lora_data[3::4]
183
+ active_loras = [(s, i) for s, i, f in zip(sources, ids, files) if s in ["Civitai", "TensorArt", "Custom URL"] and i and i.strip() and f is None]
184
+ if not active_loras: return "No remote LoRAs specified for pre-downloading."
185
+ log = [f"* {s} ID {i}: {get_lora_path(s, i, civitai_api_key, tensorart_api_key, progress)[1]}" for s, i in active_loras]
186
+ return "\n".join(log)
187
+
188
+ # --- Model Management & Core Logic ---
189
+ current_loaded_model_name = None; loaded_checkpoint_tuple = None
190
+ def load_model(model_display_name: str, progress=gr.Progress()):
191
+ global current_loaded_model_name, loaded_checkpoint_tuple
192
+ if model_display_name == current_loaded_model_name and loaded_checkpoint_tuple: return loaded_checkpoint_tuple
193
+ if loaded_checkpoint_tuple: model_management.unload_all_models(); loaded_checkpoint_tuple = None; gc.collect(); torch.cuda.empty_cache()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
 
195
+ repo_id, filename, _, _ = ALL_MODEL_MAP[model_display_name]
196
+ local_file_path = os.path.join(CHECKPOINT_DIR, filename)
197
+
198
+ if not os.path.exists(local_file_path):
199
+ progress(0, desc=f"Downloading model: {model_display_name}")
200
+ hf_hub_download(repo_id=repo_id, filename=filename, local_dir=CHECKPOINT_DIR, local_dir_use_symlinks=False)
201
+
202
+ progress(0.5, desc=f"Loading '{filename}'")
203
+ MODEL_TUPLE = checkpointloadersimple.load_checkpoint(ckpt_name=filename)
204
+ model_management.load_models_gpu([get_value_at_index(MODEL_TUPLE, 0)])
205
+ current_loaded_model_name = model_display_name; loaded_checkpoint_tuple = MODEL_TUPLE
206
+ progress(1.0, desc="Model loaded"); return loaded_checkpoint_tuple
207
+
208
+ def _generate_image_logic(model_display_name: str, positive_prompt: str, negative_prompt: str,
209
+ seed: int, batch_size: int, width: int, height: int, guidance_scale: float, num_inference_steps: int,
210
+ sampler_name: str, scheduler: str, civitai_api_key: str, tensorart_api_key: str, *lora_data,
211
+ progress=gr.Progress(track_tqdm=True)):
212
+ output_images = []
213
+ is_sd15 = MODEL_TYPE_MAP.get(model_display_name) == "SD1.5"
214
+ clip_skip = 1
215
+ if is_sd15 and len(lora_data) > MAX_LORAS * 4:
216
+ clip_skip = int(lora_data[-1])
217
+ lora_data = lora_data[:-1]
218
+
219
+ with torch.inference_mode():
220
+ model_tuple = load_model(model_display_name, progress)
221
+ model, clip, vae = (get_value_at_index(model_tuple, i) for i in range(3))
222
 
223
+ if is_sd15:
224
+ clip = get_value_at_index(clipsetlastlayer.set_last_layer(clip=clip, stop_at_clip_layer=-clip_skip), 0)
225
 
226
+ active_loras_for_meta = []
227
+ sources, ids, scales, files = lora_data[0::4], lora_data[1::4], lora_data[2::4], lora_data[3::4]
228
+ for i, (source, lora_id, scale, custom_file) in enumerate(zip(sources, ids, scales, files)):
229
+ if scale > 0:
230
+ lora_filename = None
231
+ if custom_file:
232
+ lora_filename = os.path.basename(custom_file.name)
233
+ shutil.copy(custom_file.name, LORA_DIR)
234
+ elif lora_id and lora_id.strip():
235
+ local_path, _ = get_lora_path(source, lora_id, civitai_api_key, tensorart_api_key, progress)
236
+ if local_path: lora_filename = os.path.basename(local_path)
237
+
238
+ if lora_filename:
239
+ lora_tuple = loraloader.load_lora(model=model, clip=clip, lora_name=lora_filename, strength_model=scale, strength_clip=scale)
240
+ model, clip = get_value_at_index(lora_tuple, 0), get_value_at_index(lora_tuple, 1)
241
+ active_loras_for_meta.append(f"{source} {lora_id}:{scale}")
242
 
243
+ loras_string = f"LoRAs: [{', '.join(active_loras_for_meta)}]" if active_loras_for_meta else ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
 
245
+ if is_sd15:
246
+ pos_cond = cliptextencode_sd15.encode(text=positive_prompt, clip=clip)
247
+ neg_cond = cliptextencode_sd15.encode(text=negative_prompt, clip=clip)
248
+ else:
249
+ pos_cond = cliptextencodesdxl.encode(width=width, height=height, text_g=positive_prompt, text_l=positive_prompt, clip=clip, target_width=width, target_height=height, crop_w=0, crop_h=0)
250
+ neg_cond = cliptextencodesdxl.encode(width=width, height=height, text_g=negative_prompt, text_l=negative_prompt, clip=clip, target_width=width, target_height=height, crop_w=0, crop_h=0)
251
 
252
+ start_seed = seed if seed != -1 else random.randint(0, 2**64 - 1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
+ latent = emptylatentimage.generate(width=width, height=height, batch_size=batch_size)
255
 
256
+ sampled = ksampler.sample(
257
+ seed=start_seed,
258
+ steps=num_inference_steps,
259
+ cfg=guidance_scale,
260
+ sampler_name=sampler_name,
261
+ scheduler=scheduler,
262
+ denoise=1.0,
263
+ model=model,
264
+ positive=get_value_at_index(pos_cond, 0),
265
+ negative=get_value_at_index(neg_cond, 0),
266
+ latent_image=get_value_at_index(latent, 0)
267
+ )
268
 
269
+ decoded_images_tensor = get_value_at_index(vaedecode.decode(samples=get_value_at_index(sampled, 0), vae=vae), 0)
270
+
271
+ for i in range(decoded_images_tensor.shape[0]):
272
+ img_tensor = decoded_images_tensor[i]
273
+ pil_image = Image.fromarray((img_tensor.cpu().numpy() * 255.0).astype("uint8"))
274
 
275
+ current_seed = start_seed + i
 
 
 
 
 
 
 
 
 
 
 
276
 
277
+ model_hash = DISPLAY_NAME_TO_HASH_MAP.get(model_display_name, "N/A")
278
+ params_string = f"{positive_prompt}\nNegative prompt: {negative_prompt}\n"
279
+ params_string += f"Steps: {num_inference_steps}, Sampler: {sampler_name}, Scheduler: {scheduler}, CFG scale: {guidance_scale}, Seed: {current_seed}, Size: {width}x{height}, Base Model: {model_display_name}, Model hash: {model_hash}"
280
+ if is_sd15: params_string += f", Clip skip: {clip_skip}"
281
+ params_string += f", {loras_string}"
282
+ pil_image.info = {'parameters': params_string.strip()}
 
 
 
 
 
 
 
 
 
 
283
 
284
+ output_images.append(pil_image)
285
+
286
+ return output_images
 
 
 
 
 
 
 
287
 
288
+ def generate_image_wrapper(*args, **kwargs):
289
+ logic_args_list = list(args[:11])
290
+ zero_gpu_duration = args[11]
291
+ logic_args_list.extend(args[12:])
292
+ duration = 60
293
+ try:
294
+ if zero_gpu_duration and int(zero_gpu_duration) > 0:
295
+ duration = int(zero_gpu_duration)
296
+ except (ValueError, TypeError):
297
+ pass
298
+ return spaces.GPU(duration=duration)(_generate_image_logic)(*logic_args_list, **kwargs)
299
 
 
 
 
 
 
 
300
 
301
+ # --- PNG Info & UI Logic ---
302
  def _parse_parameters(params_text):
303
+ data = {}; lines = params_text.strip().split('\n'); data['prompt'] = lines[0]
 
 
304
  data['negative_prompt'] = lines[1].replace("Negative prompt:", "").strip() if len(lines) > 1 and lines[1].startswith("Negative prompt:") else ""
305
+ params_line = '\n'.join(lines[2:])
306
+ def find_param(key, default, cast_type=str):
307
+ match = re.search(fr"\b{key}: ([^,]+?)(,|$|\n)", params_line)
308
+ return cast_type(match.group(1).strip()) if match else default
309
+ data['steps'] = find_param("Steps", 28, int); data['sampler'] = find_param("Sampler", SAMPLER_CHOICES[0], str)
310
+ data['scheduler'] = find_param("Scheduler", SCHEDULER_CHOICES[0], str); data['cfg_scale'] = find_param("CFG scale", 7.5, float)
311
+ data['seed'] = find_param("Seed", -1, int); data['clip_skip'] = find_param("Clip skip", 1, int)
312
+ data['base_model'] = find_param("Base Model", list(ALL_MODEL_MAP.keys())[0], str); data['model_hash'] = find_param("Model hash", None, str)
313
+ size_match = re.search(r"Size: (\d+)x(\d+)", params_line)
314
+ data['width'], data['height'] = (int(size_match.group(1)), int(size_match.group(2))) if size_match else (1024, 1024)
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  return data
316
 
317
  def get_png_info(image):
318
+ if not image or not (params := image.info.get('parameters')): return "", "", "No metadata found in the image."
319
+ parsed_data = _parse_parameters(params)
320
+ other_params_text = "\n".join([p.strip() for p in '\n'.join(params.strip().split('\n')[2:]).split(',')])
321
+ return parsed_data.get('prompt', ''), parsed_data.get('negative_prompt', ''), other_params_text
322
+
323
+ def apply_data_to_ui(data, target_tab):
324
+ final_sampler = data.get('sampler') if data.get('sampler') in SAMPLER_CHOICES else SAMPLER_CHOICES[0]
325
+ default_scheduler = 'normal' if 'normal' in SCHEDULER_CHOICES else SCHEDULER_CHOICES[0]
326
+ final_scheduler = data.get('scheduler') if data.get('scheduler') in SCHEDULER_CHOICES else default_scheduler
327
+
328
+ updates = {}
329
+ base_model_name = data.get('base_model')
330
+
331
+ if target_tab == "Illustrious":
332
+ if base_model_name in MODEL_MAP_ILLUSTRIOUS:
333
+ updates.update({base_model_name_input_illustrious: base_model_name})
334
+ updates.update({prompt_illustrious: data['prompt'], negative_prompt_illustrious: data['negative_prompt'], seed_illustrious: data['seed'], width_illustrious: data['width'], height_illustrious: data['height'], guidance_scale_illustrious: data['cfg_scale'], num_inference_steps_illustrious: data['steps'], sampler_illustrious: final_sampler, schedule_type_illustrious: final_scheduler, model_tabs: gr.Tabs(selected=0)})
335
+ elif target_tab == "Animagine":
336
+ if base_model_name in MODEL_MAP_ANIMAGINE:
337
+ updates.update({base_model_name_input_animagine: base_model_name})
338
+ updates.update({prompt_animagine: data['prompt'], negative_prompt_animagine: data['negative_prompt'], seed_animagine: data['seed'], width_animagine: data['width'], height_animagine: data['height'], guidance_scale_animagine: data['cfg_scale'], num_inference_steps_animagine: data['steps'], sampler_animagine: final_sampler, schedule_type_animagine: final_scheduler, model_tabs: gr.Tabs(selected=1)})
339
+ elif target_tab == "Pony":
340
+ if base_model_name in MODEL_MAP_PONY:
341
+ updates.update({base_model_name_input_pony: base_model_name})
342
+ updates.update({prompt_pony: data['prompt'], negative_prompt_pony: data['negative_prompt'], seed_pony: data['seed'], width_pony: data['width'], height_pony: data['height'], guidance_scale_pony: data['cfg_scale'], num_inference_steps_pony: data['steps'], sampler_pony: final_sampler, schedule_type_pony: final_scheduler, model_tabs: gr.Tabs(selected=2)})
343
+ elif target_tab == "SD1.5":
344
+ if base_model_name in MODEL_MAP_SD15:
345
+ updates.update({base_model_name_input_sd15: base_model_name})
346
+ updates.update({prompt_sd15: data['prompt'], negative_prompt_sd15: data['negative_prompt'], seed_sd15: data['seed'], width_sd15: data['width'], height_sd15: data['height'], guidance_scale_sd15: data['cfg_scale'], num_inference_steps_sd15: data['steps'], sampler_sd15: final_sampler, schedule_type_sd15: final_scheduler, clip_skip_sd15: data.get('clip_skip', 1), model_tabs: gr.Tabs(selected=3)})
 
347
 
348
+ updates[tabs] = gr.Tabs(selected=0)
 
349
  return updates
350
 
351
+ def send_info_to_tab(image, target_tab):
352
+ if not image or not image.info.get('parameters', ''): return {comp: gr.update() for comp in all_ui_components}
353
+ data = _parse_parameters(image.info['parameters'])
354
+ return apply_data_to_ui(data, target_tab)
355
+
356
+ def send_info_by_hash(image):
357
+ if not image or not image.info.get('parameters', ''): return {comp: gr.update() for comp in all_ui_components}
358
+ data = _parse_parameters(image.info['parameters'])
359
+ model_hash = data.get('model_hash')
360
+ display_name = HASH_TO_DISPLAY_NAME_MAP.get(model_hash)
361
+
362
+ if not display_name:
363
+ raise gr.Error("Model hash not found in this app's model list. The original model name from the PNG will be used if it exists in the target tab.")
364
+
365
+ if display_name in MODEL_MAP_ILLUSTRIOUS: target_tab = "Illustrious"
366
+ elif display_name in MODEL_MAP_ANIMAGINE: target_tab = "Animagine"
367
+ elif display_name in MODEL_MAP_PONY: target_tab = "Pony"
368
+ elif display_name in MODEL_MAP_SD15: target_tab = "SD1.5"
369
+ else:
370
+ raise gr.Error("Cannot determine the correct tab for this model.")
371
+
372
+ data['base_model'] = display_name
373
+ return apply_data_to_ui(data, target_tab)
374
+
375
+ # --- UI Generation Functions ---
376
+ def create_lora_settings_ui():
377
+ with gr.Accordion("LoRA Settings", open=False):
378
+ gr.Markdown("⚠️ **Responsible Use Notice:** Please avoid excessive, rapid, or automated (scripted) use of the pre-download LoRA feature. Overt misuse may lead to service disruption. Thank you for your cooperation.")
379
+ gr.Markdown("For LoRAs that require login to download, you may need to enter the corresponding API Key.")
380
+ with gr.Row():
381
+ civitai_api_key = gr.Textbox(label="Civitai API Key", placeholder="Enter your Civitai API Key", type="password", scale=1)
382
+ tensorart_api_key = gr.Textbox(label="TensorArt API Key", placeholder="Enter your TensorArt API Key", type="password", scale=1)
383
+ gr.Markdown("---")
384
+ gr.Markdown("For each LoRA, choose a source, provide an ID/URL, or upload a file.")
385
+ gr.Markdown("""
386
+ <div style='background-color: #282828; color: #a0aec0; padding: 10px; border-radius: 5px; margin-top: 10px; margin-bottom: 15px;'>
387
+ <b>Input Examples:</b>
388
+ <ul>
389
+ <li><b>Civitai:</b> Enter the <b>Model Version ID</b>, not the Model ID. Example: <code>133755</code> (Found in the URL, e.g., <code>civitai.com/models/122136?modelVersionId=<b>133755</b></code>)</li>
390
+ <li><b>TensorArt:</b> Enter the <b>Model ID</b>. Example: <code>706684852832599558</code> (Found in the URL, e.g., <code>tensor.art/models/<b>706684852832599558</b></code>)</li>
391
+ <li><b>Custom URL:</b> Provide a direct download link to a <code>.safetensors</code> file. Example: <code>https://huggingface.co/path/to/your/lora.safetensors</code></li>
392
+ <li><b>File:</b> Use the "Upload" button. The source will be set automatically.</li>
393
+ </ul>
394
+ </div>
395
+ """)
396
+ gr.Markdown("""
397
+ <div style='background-color: #282828; color: #a0aec0; padding: 10px; border-radius: 5px; margin-bottom: 15px;'>
398
+ <b>Notice:</b>
399
+ <ul style='margin-bottom: 0;'>
400
+ <li>With Gradio, the page may become unresponsive until a file is fully uploaded. Please be patient and wait for the process to complete.</li>
401
+ </ul>
402
+ </div>
403
+ """)
404
+ lora_rows, sources, ids, scales, uploads = [], [], [], [], []
405
+ for i in range(MAX_LORAS):
406
+ with gr.Row(visible=(i == 0)) as row:
407
+ source = gr.Dropdown(label=f"LoRA {i+1} Source", choices=LORA_SOURCE_CHOICES, value="Civitai", scale=1)
408
+ lora_id = gr.Textbox(label="ID / URL / File", placeholder="e.g.: 133755", scale=2)
409
+ scale = gr.Slider(label="Weight", minimum=0.0, maximum=2.0, step=0.05, value=0.0, scale=2)
410
+ upload = gr.UploadButton("Upload", file_types=[".safetensors"], scale=1)
411
+ lora_rows.append(row); sources.append(source); ids.append(lora_id); scales.append(scale); uploads.append(upload)
412
+ upload.upload(fn=lambda f: (os.path.basename(f.name), "File") if f else (gr.update(), gr.update()), inputs=[upload], outputs=[lora_id, source])
413
+ with gr.Row(): add_button = gr.Button("✚ Add LoRA"); delete_button = gr.Button("➖ Delete LoRA", visible=False)
414
+ count_state = gr.State(value=1)
415
+ all_components = [item for sublist in zip(sources, ids, scales, uploads) for item in sublist]
416
+ return (civitai_api_key, tensorart_api_key, lora_rows, sources, ids, scales, uploads, add_button, delete_button, count_state, all_components)
417
+
418
+ def download_all_models_on_startup():
419
+ """Downloads all base models listed in ALL_MODEL_MAP when the app starts."""
420
+ print("--- Starting pre-download of all base models ---")
421
+ for model_display_name, model_info in ALL_MODEL_MAP.items():
422
+ repo_id, filename, _, _ = model_info
423
+ local_file_path = os.path.join(CHECKPOINT_DIR, filename)
424
+
425
+ if os.path.exists(local_file_path):
426
+ print(f"✅ Model '{filename}' already exists. Skipping download.")
427
+ continue
428
+
429
+ try:
430
+ print(f"Downloading: {model_display_name} ({filename})...")
431
+ hf_hub_download(
432
+ repo_id=repo_id,
433
+ filename=filename,
434
+ local_dir=CHECKPOINT_DIR,
435
+ local_dir_use_symlinks=False
436
+ )
437
+ print(f"✅ Successfully downloaded {filename}.")
438
+ except Exception as e:
439
+ print(f"❌ Failed to download {filename} from {repo_id}: {e}")
440
+ print("--- Finished pre-downloading all base models ---")
441
 
442
+ # --- Execute model download on startup ---
443
+ download_all_models_on_startup()
444
 
445
+ # --- Gradio UI ---
446
  with gr.Blocks(css="#col-container {margin: 0 auto; max-width: 1024px;}") as demo:
447
+ gr.Markdown("# Animated T2I with LoRAs")
448
  with gr.Tabs(elem_id="tabs_container") as tabs:
449
  with gr.TabItem("txt2img", id=0):
450
+ with gr.Tabs() as model_tabs:
451
+ for tab_name, model_map, defaults in [
452
+ ("Illustrious", MODEL_MAP_ILLUSTRIOUS, {'w': 1024, 'h': 1024, 'cs_vis': False, 'cs_val': 1}),
453
+ ("Animagine", MODEL_MAP_ANIMAGINE, {'w': 1024, 'h': 1024, 'cs_vis': False, 'cs_val': 1}),
454
+ ("Pony", MODEL_MAP_PONY, {'w': 1024, 'h': 1024, 'cs_vis': False, 'cs_val': 1}),
455
+ ("SD1.5", MODEL_MAP_SD15, {'w': 512, 'h': 768, 'cs_vis': True, 'cs_val': 1})
456
+ ]:
457
+ with gr.TabItem(tab_name):
458
+ gr.Markdown("💡 **Tip:** Pre-downloading LoRAs before 'Run' can maximize ZeroGPU time.")
459
+ with gr.Column():
460
+ with gr.Row():
461
+ base_model = gr.Dropdown(label="Base Model", choices=list(model_map.keys()), value=list(model_map.keys())[0], scale=3)
462
+ with gr.Column(scale=1): predownload_lora = gr.Button("Pre-download LoRAs"); run = gr.Button("Run", variant="primary")
463
+ predownload_status = gr.Markdown("")
464
+ prompt = gr.Text(label="Prompt", lines=3, placeholder="Enter your prompt")
465
+ neg_prompt = gr.Text(label="Negative prompt", lines=3, value=DEFAULT_NEGATIVE_PROMPT)
466
+ with gr.Row():
467
+ with gr.Column(scale=2):
468
+ with gr.Row(): width = gr.Slider(label="Width", minimum=256, maximum=2048, step=64, value=defaults['w']); height = gr.Slider(label="Height", minimum=256, maximum=2048, step=64, value=defaults['h'])
469
+ with gr.Row():
470
+ sampler = gr.Dropdown(label="Sampling method", choices=SAMPLER_CHOICES, value=SAMPLER_CHOICES[0])
471
+ default_scheduler = 'normal' if 'normal' in SCHEDULER_CHOICES else SCHEDULER_CHOICES[0]
472
+ scheduler = gr.Dropdown(label="Scheduler", choices=SCHEDULER_CHOICES, value=default_scheduler)
473
+ with gr.Row(): cfg = gr.Slider(label="CFG Scale", minimum=0.0, maximum=20.0, step=0.1, value=7.5); steps = gr.Slider(label="Sampling steps", minimum=1, maximum=50, step=1, value=28)
474
+ with gr.Column(scale=1): result = gr.Gallery(label="Result", show_label=False, columns=2, object_fit="contain", height="auto")
475
+ with gr.Row():
476
+ seed = gr.Number(label="Seed (-1 for random)", value=-1, precision=0)
477
+ batch_size = gr.Slider(label="Batch size", minimum=1, maximum=8, step=1, value=1)
478
+ clip_skip = gr.Slider(label="Clip Skip", minimum=1, maximum=2, step=1, value=defaults['cs_val'], visible=defaults['cs_vis'])
479
+ zero_gpu = gr.Number(label="ZeroGPU Duration (s)", value=None, placeholder="Default: 60s", info="Optional: Leave empty for default (60s), max to 120")
480
+ lora_settings = create_lora_settings_ui()
481
+
482
+ # Assign specific variables for event handlers
483
+ if tab_name == "Illustrious":
484
+ base_model_name_input_illustrious, prompt_illustrious, negative_prompt_illustrious, seed_illustrious, batch_size_illustrious, width_illustrious, height_illustrious, guidance_scale_illustrious, num_inference_steps_illustrious, sampler_illustrious, schedule_type_illustrious, zero_gpu_duration_illustrious, result_illustrious = base_model, prompt, neg_prompt, seed, batch_size, width, height, cfg, steps, sampler, scheduler, zero_gpu, result
485
+ civitai_api_key_illustrious, tensorart_api_key_illustrious, lora_rows_illustrious, _, lora_id_inputs_illustrious, lora_scale_inputs_illustrious, _, add_lora_button_illustrious, delete_lora_button_illustrious, lora_count_state_illustrious, all_lora_components_flat_illustrious = lora_settings
486
+ predownload_lora_button_illustrious, run_button_illustrious, predownload_status_illustrious = predownload_lora, run, predownload_status
487
+ elif tab_name == "Animagine":
488
+ base_model_name_input_animagine, prompt_animagine, negative_prompt_animagine, seed_animagine, batch_size_animagine, width_animagine, height_animagine, guidance_scale_animagine, num_inference_steps_animagine, sampler_animagine, schedule_type_animagine, zero_gpu_duration_animagine, result_animagine = base_model, prompt, neg_prompt, seed, batch_size, width, height, cfg, steps, sampler, scheduler, zero_gpu, result
489
+ civitai_api_key_animagine, tensorart_api_key_animagine, lora_rows_animagine, _, lora_id_inputs_animagine, lora_scale_inputs_animagine, _, add_lora_button_animagine, delete_lora_button_animagine, lora_count_state_animagine, all_lora_components_flat_animagine = lora_settings
490
+ predownload_lora_button_animagine, run_button_animagine, predownload_status_animagine = predownload_lora, run, predownload_status
491
+ elif tab_name == "Pony":
492
+ base_model_name_input_pony, prompt_pony, negative_prompt_pony, seed_pony, batch_size_pony, width_pony, height_pony, guidance_scale_pony, num_inference_steps_pony, sampler_pony, schedule_type_pony, zero_gpu_duration_pony, result_pony = base_model, prompt, neg_prompt, seed, batch_size, width, height, cfg, steps, sampler, scheduler, zero_gpu, result
493
+ civitai_api_key_pony, tensorart_api_key_pony, lora_rows_pony, _, lora_id_inputs_pony, lora_scale_inputs_pony, _, add_lora_button_pony, delete_lora_button_pony, lora_count_state_pony, all_lora_components_flat_pony = lora_settings
494
+ predownload_lora_button_pony, run_button_pony, predownload_status_pony = predownload_lora, run, predownload_status
495
+ elif tab_name == "SD1.5":
496
+ base_model_name_input_sd15, prompt_sd15, negative_prompt_sd15, seed_sd15, batch_size_sd15, width_sd15, height_sd15, guidance_scale_sd15, num_inference_steps_sd15, sampler_sd15, schedule_type_sd15, clip_skip_sd15, zero_gpu_duration_sd15, result_sd15 = base_model, prompt, neg_prompt, seed, batch_size, width, height, cfg, steps, sampler, scheduler, clip_skip, zero_gpu, result
497
+ civitai_api_key_sd15, tensorart_api_key_sd15, lora_rows_sd15, _, lora_id_inputs_sd15, lora_scale_inputs_sd15, _, add_lora_button_sd15, delete_lora_button_sd15, lora_count_state_sd15, all_lora_components_flat_sd15 = lora_settings
498
+ predownload_lora_button_sd15, run_button_sd15, predownload_status_sd15 = predownload_lora, run, predownload_status
 
 
 
 
 
499
  with gr.TabItem("PNG Info", id=1):
500
+ with gr.Column():
501
+ info_image_input = gr.Image(type="pil", label="Upload Image", height=512)
502
+ with gr.Row():
503
+ info_get_button = gr.Button("Get Info")
504
+ send_by_hash_button = gr.Button("Send to txt2img by Model Hash", variant="primary")
505
  with gr.Row():
506
+ send_to_illustrious_button = gr.Button("Send to Illustrious")
507
+ send_to_animagine_button = gr.Button("Send to Animagine")
508
+ send_to_pony_button = gr.Button("Send to Pony")
509
+ send_to_sd15_button = gr.Button("Send to SD1.5")
510
  gr.Markdown("### Positive Prompt"); info_prompt_output = gr.Textbox(lines=3, interactive=False, show_label=False)
511
  gr.Markdown("### Negative Prompt"); info_neg_prompt_output = gr.Textbox(lines=3, interactive=False, show_label=False)
512
  gr.Markdown("### Other Parameters"); info_params_output = gr.Textbox(lines=5, interactive=False, show_label=False)
513
+ gr.Markdown("<div style='text-align: center; margin-top: 20px;'>Made by <a href='https://civitai.com/user/RioShiina'>RioShiina</a> with ❤️</div>")
514
 
515
+ # --- Event Handlers ---
516
+ def create_lora_event_handlers(lora_rows, count_state, add_button, del_button, lora_ids, lora_scales):
517
+ def add_lora_row(c): return {count_state: c+1, lora_rows[c]: gr.update(visible=True), del_button: gr.update(visible=True), add_button: gr.update(visible=c+1 < MAX_LORAS)}
518
+ def del_lora_row(c): c-=1; return {count_state: c, lora_rows[c]: gr.update(visible=False), lora_ids[c]: "", lora_scales[c]: 0.0, add_button: gr.update(visible=True), del_button: gr.update(visible=c > 1)}
519
+ add_button.click(add_lora_row, [count_state], [count_state, add_button, del_button, *lora_rows])
520
+ del_button.click(del_lora_row, [count_state], [count_state, add_button, del_button, *lora_rows, *lora_ids, *lora_scales])
521
+
522
+ create_lora_event_handlers(lora_rows_illustrious, lora_count_state_illustrious, add_lora_button_illustrious, delete_lora_button_illustrious, lora_id_inputs_illustrious, lora_scale_inputs_illustrious)
523
+ predownload_lora_button_illustrious.click(lambda: "⏳ Downloading...", None, [predownload_status_illustrious]).then(pre_download_loras, [civitai_api_key_illustrious, tensorart_api_key_illustrious, *all_lora_components_flat_illustrious], [predownload_status_illustrious])
524
+ run_button_illustrious.click(generate_image_wrapper, [base_model_name_input_illustrious, prompt_illustrious, negative_prompt_illustrious, seed_illustrious, batch_size_illustrious, width_illustrious, height_illustrious, guidance_scale_illustrious, num_inference_steps_illustrious, sampler_illustrious, schedule_type_illustrious, zero_gpu_duration_illustrious, civitai_api_key_illustrious, tensorart_api_key_illustrious, *all_lora_components_flat_illustrious], [result_illustrious])
 
 
 
525
 
526
+ create_lora_event_handlers(lora_rows_animagine, lora_count_state_animagine, add_lora_button_animagine, delete_lora_button_animagine, lora_id_inputs_animagine, lora_scale_inputs_animagine)
527
+ predownload_lora_button_animagine.click(lambda: "⏳ Downloading...", None, [predownload_status_animagine]).then(pre_download_loras, [civitai_api_key_animagine, tensorart_api_key_animagine, *all_lora_components_flat_animagine], [predownload_status_animagine])
528
+ run_button_animagine.click(generate_image_wrapper, [base_model_name_input_animagine, prompt_animagine, negative_prompt_animagine, seed_animagine, batch_size_animagine, width_animagine, height_animagine, guidance_scale_animagine, num_inference_steps_animagine, sampler_animagine, schedule_type_animagine, zero_gpu_duration_animagine, civitai_api_key_animagine, tensorart_api_key_animagine, *all_lora_components_flat_animagine], [result_animagine])
529
+
530
+ create_lora_event_handlers(lora_rows_pony, lora_count_state_pony, add_lora_button_pony, delete_lora_button_pony, lora_id_inputs_pony, lora_scale_inputs_pony)
531
+ predownload_lora_button_pony.click(lambda: "⏳ Downloading...", None, [predownload_status_pony]).then(pre_download_loras, [civitai_api_key_pony, tensorart_api_key_pony, *all_lora_components_flat_pony], [predownload_status_pony])
532
+ run_button_pony.click(generate_image_wrapper, [base_model_name_input_pony, prompt_pony, negative_prompt_pony, seed_pony, batch_size_pony, width_pony, height_pony, guidance_scale_pony, num_inference_steps_pony, sampler_pony, schedule_type_pony, zero_gpu_duration_pony, civitai_api_key_pony, tensorart_api_key_pony, *all_lora_components_flat_pony], [result_pony])
533
 
534
+ create_lora_event_handlers(lora_rows_sd15, lora_count_state_sd15, add_lora_button_sd15, delete_lora_button_sd15, lora_id_inputs_sd15, lora_scale_inputs_sd15)
535
+ predownload_lora_button_sd15.click(lambda: "⏳ Downloading...", None, [predownload_status_sd15]).then(pre_download_loras, [civitai_api_key_sd15, tensorart_api_key_sd15, *all_lora_components_flat_sd15], [predownload_status_sd15])
536
+ run_button_sd15.click(generate_image_wrapper, [base_model_name_input_sd15, prompt_sd15, negative_prompt_sd15, seed_sd15, batch_size_sd15, width_sd15, height_sd15, guidance_scale_sd15, num_inference_steps_sd15, sampler_sd15, schedule_type_sd15, zero_gpu_duration_sd15, civitai_api_key_sd15, tensorart_api_key_sd15, *all_lora_components_flat_sd15, clip_skip_sd15], [result_sd15])
537
 
538
+ info_get_button.click(get_png_info, [info_image_input], [info_prompt_output, info_neg_prompt_output, info_params_output])
539
+ all_ui_components = [
540
+ base_model_name_input_illustrious, prompt_illustrious, negative_prompt_illustrious, seed_illustrious, width_illustrious, height_illustrious, guidance_scale_illustrious, num_inference_steps_illustrious, sampler_illustrious, schedule_type_illustrious,
541
+ base_model_name_input_animagine, prompt_animagine, negative_prompt_animagine, seed_animagine, width_animagine, height_animagine, guidance_scale_animagine, num_inference_steps_animagine, sampler_animagine, schedule_type_animagine,
542
+ base_model_name_input_pony, prompt_pony, negative_prompt_pony, seed_pony, width_pony, height_pony, guidance_scale_pony, num_inference_steps_pony, sampler_pony, schedule_type_pony,
543
+ base_model_name_input_sd15, prompt_sd15, negative_prompt_sd15, seed_sd15, width_sd15, height_sd15, guidance_scale_sd15, num_inference_steps_sd15, sampler_sd15, schedule_type_sd15, clip_skip_sd15,
544
+ tabs, model_tabs
545
+ ]
546
+ send_to_illustrious_button.click(lambda img: send_info_to_tab(img, "Illustrious"), [info_image_input], all_ui_components)
547
+ send_to_animagine_button.click(lambda img: send_info_to_tab(img, "Animagine"), [info_image_input], all_ui_components)
548
+ send_to_pony_button.click(lambda img: send_info_to_tab(img, "Pony"), [info_image_input], all_ui_components)
549
+ send_to_sd15_button.click(lambda img: send_info_to_tab(img, "SD1.5"), [info_image_input], all_ui_components)
550
+ send_by_hash_button.click(send_info_by_hash, [info_image_input], all_ui_components)
551
+
552
+ if __name__ == "__main__":
553
+ demo.queue().launch()
requirements.txt CHANGED
@@ -1,11 +1,33 @@
1
- accelerate
2
- diffusers
3
- invisible_watermark
4
  torch
5
- transformers
6
- xformers
7
- compel
8
- pydantic==2.10.6
9
- gradio==5.12.0
10
- requests
11
- peft
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ comfyui-frontend-package==1.23.4
2
+ comfyui-workflow-templates==0.1.59
3
+ comfyui-embedded-docs==0.2.6
4
  torch
5
+ torchsde
6
+ torchvision
7
+ torchaudio
8
+ numpy>=1.25.0
9
+ einops
10
+ transformers>=4.37.2
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
23
+
24
+ #non essential dependencies:
25
+ kornia>=0.7.1
26
+ spandrel
27
+ soundfile
28
+ av>=14.2.0
29
+ pydantic~=2.0
30
+ pydantic-settings~=2.0
31
+
32
+ triton
33
+ sageattention