tomiconic commited on
Commit
db73293
Β·
verified Β·
1 Parent(s): cd72379

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +340 -239
app.py CHANGED
@@ -4,14 +4,15 @@ import spaces
4
  from diffusers import StableDiffusionXLPipeline, DPMSolverMultistepScheduler
5
  from huggingface_hub import hf_hub_download
6
  import random
 
7
 
8
- # ── Model ─────────────────────────────────────────────────────────────────────
9
- MODEL_REPO = "cyberdelia/CyberRealisticPony"
10
- MODEL_FILE = "CyberRealisticPony_V16.0_FP16.safetensors"
11
- PONY_POS = "score_9, score_8_up, score_7_up, "
12
- PONY_NEG = "score_6, score_5, score_4, "
13
 
14
- print("Downloading model...")
15
  local_path = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE)
16
  print("Loading pipeline...")
17
  pipe = StableDiffusionXLPipeline.from_single_file(local_path, torch_dtype=torch.float16)
@@ -21,85 +22,196 @@ pipe.scheduler = DPMSolverMultistepScheduler.from_config(
21
  pipe.enable_attention_slicing()
22
  print("Ready.")
23
 
24
- # ── Style presets (Fooocus-style) ─────────────────────────────────────────────
25
- STYLES = {
26
- "None": {
27
- "pos": "",
28
- "neg": "",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  },
30
- "πŸ“Έ Photorealistic": {
31
- "pos": "RAW photo, photorealistic, hyperrealistic, 8k uhd, dslr, high quality, film grain, Fujifilm XT3, sharp focus, detailed skin texture, natural lighting, ",
32
- "neg": "painting, illustration, cartoon, anime, render, cgi, drawing, ",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  },
34
  "🎬 Cinematic": {
35
- "pos": "cinematic shot, movie scene, dramatic lighting, shallow depth of field, anamorphic lens, film grain, color graded, professional cinematography, epic composition, ",
36
- "neg": "static, flat lighting, amateur, snapshot, ",
37
  },
38
  "πŸ–ΌοΈ Portrait": {
39
- "pos": "professional portrait, studio lighting, bokeh background, sharp eyes, detailed face, perfect skin, 85mm lens, f/1.8, headshot, ",
40
- "neg": "wide angle, fish eye, distorted face, bad eyes, cropped head, ",
41
  },
42
  "πŸŒ† Neon City": {
43
- "pos": "cyberpunk city, neon lights, rain reflections on pavement, night scene, atmospheric fog, vibrant colors, futuristic architecture, blade runner aesthetic, ",
44
- "neg": "daytime, rural, nature, warm tones, bright sunshine, ",
45
  },
46
- "🎨 Oil Painting": {
47
- "pos": "oil painting, impressionist, visible brushstrokes, canvas texture, artistic masterpiece, museum quality, rich colors, painted by a master, ",
48
- "neg": "photo, photograph, digital art, flat colors, ",
49
  },
50
- "✨ Fantasy Epic": {
51
- "pos": "fantasy art, epic scene, magical atmosphere, dramatic clouds, volumetric lighting, highly detailed, concept art, artstation, 4k, ",
52
- "neg": "modern, contemporary, mundane, boring, flat, ",
53
  },
54
  }
55
 
56
- # ── LoRA presets ──────────────────────────────────────────────────────────────
57
  LORAS = {
58
  "None": None,
59
- "βœ‹ Perfect Hands": {
60
  "repo": "WolfAether21/PONY-DIFFUSION-SDXL-LORA",
61
  "file": "Perfect Hands v2.safetensors",
62
- "default_strength": 0.7,
63
  },
64
- "πŸ” Detail Enhancer": {
65
  "repo": "WolfAether21/PONY-DIFFUSION-SDXL-LORA",
66
  "file": "SDXL Detail.safetensors",
67
- "default_strength": 0.6,
68
  },
69
  }
70
 
71
  # ── Generation ────────────────────────────────────────────────────────────────
72
  @spaces.GPU(duration=180)
73
- def generate(prompt, negative_prompt, style, lora_name, lora_strength,
74
- width, height, steps, guidance, seed, randomize):
75
 
76
- if not prompt.strip():
77
  raise gr.Error("Please enter a prompt.")
78
 
79
  if randomize:
80
  seed = random.randint(0, 2**32 - 1)
81
  seed = int(seed)
82
 
83
- # Build final prompt with style
84
- style_data = STYLES.get(style, STYLES["None"])
85
- final_pos = PONY_POS + style_data["pos"] + prompt.strip()
86
- final_neg = PONY_NEG + style_data["neg"] + negative_prompt.strip()
 
 
87
 
88
- # Move to GPU
89
  pipe.to("cuda")
90
 
91
- # Load LoRA if selected
92
  lora_loaded = False
93
  lora_data = LORAS.get(lora_name)
94
  if lora_data:
95
  try:
96
- lora_path = hf_hub_download(repo_id=lora_data["repo"], filename=lora_data["file"])
97
- pipe.load_lora_weights(lora_path)
98
  pipe.fuse_lora(lora_scale=float(lora_strength))
99
  lora_loaded = True
100
- print(f"LoRA loaded: {lora_name} @ {lora_strength}")
101
  except Exception as e:
102
- print(f"LoRA load failed, continuing without: {e}")
103
 
104
  generator = torch.Generator(device="cpu").manual_seed(seed)
105
 
@@ -111,367 +223,356 @@ def generate(prompt, negative_prompt, style, lora_name, lora_strength,
111
  num_inference_steps=int(steps),
112
  guidance_scale=float(guidance),
113
  generator=generator,
114
- clip_skip=2,
115
  )
116
 
117
- # Unload LoRA and free GPU
118
  if lora_loaded:
119
  pipe.unfuse_lora()
120
  pipe.unload_lora_weights()
121
 
122
  pipe.to("cpu")
123
 
124
- return result.images[0], seed
 
 
 
 
125
 
126
  # ── CSS ───────────────────────────────────────────────────────────────────────
127
  css = """
128
  * { box-sizing: border-box; margin: 0; padding: 0; }
129
 
130
  body, .gradio-container {
131
- background: #080810 !important;
132
  font-family: 'Inter', system-ui, -apple-system, sans-serif !important;
133
- max-width: 480px !important;
134
  margin: 0 auto !important;
135
- padding: 10px !important;
136
  }
137
 
138
- /* ── Top bar ── */
139
  .topbar {
140
  display: flex;
141
  align-items: center;
142
  justify-content: space-between;
143
- padding: 12px 4px;
144
- margin-bottom: 10px;
145
  }
146
  .topbar-title {
147
- color: #fff;
148
- font-size: 1.0em;
149
  font-weight: 800;
150
  letter-spacing: -0.3px;
151
  }
152
- .topbar-badge {
153
- background: #1aff7a;
154
- color: #000;
 
155
  font-size: 0.6em;
156
- font-weight: 900;
157
- padding: 3px 10px;
158
  border-radius: 20px;
159
- letter-spacing: 1px;
160
  text-transform: uppercase;
161
  }
162
 
163
- /* ── Output image ── */
164
- .img-output {
165
- background: #0e0e18;
166
- border: 1px solid #1c1c2e;
167
- border-radius: 18px;
168
  overflow: hidden;
169
- margin-bottom: 10px;
170
- aspect-ratio: 3/4;
 
171
  display: flex;
172
  align-items: center;
173
  justify-content: center;
174
- position: relative;
175
  }
176
- .img-output img {
177
  width: 100% !important;
178
- height: 100% !important;
179
- object-fit: contain;
180
- border-radius: 18px;
181
  }
182
- /* Empty state placeholder */
183
- .img-output .empty {
184
- color: #2a2a3e;
185
- font-size: 3em;
 
 
 
 
 
 
 
 
 
 
 
186
  }
187
 
188
- /* ── Prompt area ── */
189
- .prompt-wrap {
190
- background: #0e0e18;
191
- border: 1px solid #1c1c2e;
192
  border-radius: 14px;
193
- padding: 12px;
194
  margin-bottom: 8px;
195
- transition: border-color 0.2s;
196
- }
197
- .prompt-wrap:focus-within {
198
- border-color: #5533aa;
199
  }
200
- .prompt-label {
201
- color: #44334a;
202
- font-size: 0.65em;
203
- font-weight: 700;
204
  text-transform: uppercase;
205
- letter-spacing: 1.5px;
206
- margin-bottom: 6px;
207
  }
208
 
 
209
  textarea {
210
  background: transparent !important;
211
  border: none !important;
212
- border-radius: 0 !important;
213
- color: #c8b8e8 !important;
214
- font-size: 14px !important;
215
- line-height: 1.5 !important;
216
  padding: 0 !important;
217
  resize: none !important;
218
  box-shadow: none !important;
219
  width: 100% !important;
 
220
  }
 
221
  textarea:focus {
222
  outline: none !important;
223
  box-shadow: none !important;
224
- border: none !important;
225
  }
226
- textarea::placeholder { color: #2e2640 !important; }
227
 
228
  /* ── Style pills ── */
229
- .style-pills {
230
- margin-bottom: 8px;
231
- }
232
- .style-pills .gr-radio-group {
233
  display: flex !important;
234
  flex-wrap: wrap !important;
235
  gap: 6px !important;
236
  }
237
- .style-pills label {
238
- background: #0e0e18 !important;
239
- border: 1px solid #1c1c2e !important;
240
- border-radius: 20px !important;
241
- color: #7766aa !important;
242
- cursor: pointer !important;
243
- font-size: 0.78em !important;
244
  font-weight: 600 !important;
245
  padding: 6px 14px !important;
246
- transition: all 0.15s !important;
 
247
  white-space: nowrap !important;
248
  }
249
- .style-pills label:has(input:checked) {
250
- background: #1e0e3e !important;
251
- border-color: #6633cc !important;
252
- color: #cc99ff !important;
 
253
  }
254
- .style-pills input[type=radio] { display: none !important; }
255
 
256
  /* ── Accordion ── */
257
  .gradio-accordion {
258
- background: #0e0e18 !important;
259
- border: 1px solid #1c1c2e !important;
260
  border-radius: 14px !important;
261
  margin-bottom: 8px !important;
262
  overflow: hidden !important;
263
  }
264
- .gradio-accordion > .label-wrap {
265
- padding: 12px 16px !important;
266
- color: #7766aa !important;
267
- font-size: 0.78em !important;
268
  font-weight: 700 !important;
269
  text-transform: uppercase !important;
270
- letter-spacing: 1px !important;
271
- }
272
- .gradio-accordion > .label-wrap:hover {
273
- color: #aa88ff !important;
274
  }
275
 
276
  /* ── Sliders ── */
277
  .gradio-slider {
278
- padding: 4px 0 !important;
279
  background: transparent !important;
280
  border: none !important;
 
281
  }
282
  input[type=range] {
283
- accent-color: #6633cc !important;
284
  width: 100% !important;
285
- height: 3px !important;
286
  }
287
- .gradio-slider span {
288
- color: #7766aa !important;
289
- font-size: 0.75em !important;
290
  font-weight: 600 !important;
291
  }
292
 
293
- /* ── Number input ── */
294
  input[type=number] {
295
  background: #0a0a14 !important;
296
- border: 1px solid #1c1c2e !important;
297
  border-radius: 10px !important;
298
- color: #aa88ff !important;
299
  font-size: 13px !important;
300
  padding: 8px 10px !important;
301
- width: 100% !important;
302
  }
303
 
304
  /* ── Checkbox ── */
305
- input[type=checkbox] {
306
- accent-color: #6633cc !important;
307
- width: 16px !important;
308
- height: 16px !important;
309
- }
310
  .gradio-checkbox label span {
311
- color: #7766aa !important;
312
- font-size: 0.78em !important;
313
  font-weight: 600 !important;
314
  }
315
 
316
- /* ── Dropdown (LoRA) ── */
317
  .gradio-dropdown {
318
  background: #0a0a14 !important;
319
- border: 1px solid #1c1c2e !important;
320
  border-radius: 10px !important;
321
  }
322
- .gradio-dropdown select, .gradio-dropdown input {
323
- background: transparent !important;
324
- color: #aa88ff !important;
325
- font-size: 13px !important;
326
- }
327
 
328
- /* ── Seed row ── */
329
- .seed-row {
330
- display: flex;
331
- align-items: center;
332
- gap: 10px;
333
- margin-top: 4px;
 
 
 
 
 
 
334
  }
335
- .seed-out input {
336
- background: #0a0a14 !important;
337
- border: 1px solid #111120 !important;
338
- border-radius: 8px !important;
339
- color: #332255 !important;
340
- font-size: 0.75em !important;
341
- text-align: center !important;
342
- padding: 6px !important;
343
  }
344
 
345
  /* ── Generate button ── */
346
  .gen-btn button {
347
- background: linear-gradient(135deg, #5522aa 0%, #3a1580 100%) !important;
348
- border: 1px solid #7744cc !important;
349
  border-radius: 14px !important;
350
  color: #fff !important;
351
- font-size: 0.95em !important;
352
- font-weight: 800 !important;
353
- padding: 16px !important;
354
  width: 100% !important;
355
- letter-spacing: 1.5px !important;
356
  text-transform: uppercase !important;
357
- box-shadow: 0 4px 20px #5522aa44 !important;
358
- transition: all 0.15s !important;
359
- margin-top: 4px !important;
360
  }
361
  .gen-btn button:hover {
362
- box-shadow: 0 6px 28px #5522aa88 !important;
363
  transform: translateY(-1px) !important;
364
  }
365
  .gen-btn button:active {
366
  transform: scale(0.98) !important;
367
- box-shadow: 0 2px 10px #5522aa33 !important;
368
  }
369
 
370
- /* ── Labels ── */
371
- label > span:first-child {
372
- color: #55446a !important;
373
- font-size: 0.72em !important;
374
- font-weight: 700 !important;
375
- text-transform: uppercase !important;
376
- letter-spacing: 1px !important;
377
- }
378
-
379
- footer, .built-with, .svelte-1ipelgc { display: none !important; }
380
  """
381
 
382
  # ── UI ────────────────────────────────────────────────────────────────────────
383
  with gr.Blocks(css=css, title="ImageGen") as demo:
384
 
385
- # Top bar
386
  gr.HTML("""
387
  <div class="topbar">
388
- <span class="topbar-title">🐴 CyberRealistic Pony</span>
389
- <span class="topbar-badge">⚑ GPU</span>
390
  </div>
391
  """)
392
 
393
  # Output
394
  output_image = gr.Image(
395
- show_label=False,
396
- type="pil",
397
- height=440,
398
- elem_classes="img-output",
399
  )
400
-
401
- # Seed used (small, below image)
402
  used_seed = gr.Number(
403
- label="Last seed",
404
- interactive=False,
405
- elem_classes="seed-out",
406
  )
407
 
408
- # Prompt
409
- gr.HTML('<div class="prompt-label">Prompt</div>')
410
  prompt = gr.Textbox(
411
  show_label=False,
412
- placeholder="describe your image...",
413
  lines=3,
414
- elem_classes="prompt-wrap",
415
  )
 
416
 
417
- # Negative
418
- gr.HTML('<div class="prompt-label" style="margin-top:8px">Negative</div>')
419
- negative_prompt = gr.Textbox(
420
- show_label=False,
421
- value=(
422
- "(worst quality:1.2), (low quality:1.2), (normal quality:1.2), "
423
- "lowres, bad anatomy, bad hands, signature, watermarks, "
424
- "ugly, imperfect eyes, skewed eyes, unnatural face, "
425
- "unnatural body, error, extra limb, missing limbs"
426
- ),
427
- lines=2,
428
- elem_classes="prompt-wrap",
429
- )
430
-
431
- # Style presets
432
- gr.HTML('<div class="prompt-label" style="margin-top:8px">Style</div>')
433
  style = gr.Radio(
434
  choices=list(STYLES.keys()),
435
- value="None",
436
  show_label=False,
437
- elem_classes="style-pills",
 
 
 
 
 
 
438
  )
439
 
440
- # Advanced settings accordion
441
- with gr.Accordion("βš™οΈ Advanced Settings", open=False):
442
- gr.HTML('<div style="height:8px"></div>')
 
 
 
 
 
 
 
 
 
443
 
444
  with gr.Row():
445
- width = gr.Slider(512, 896, value=832, step=64, label="Width")
446
- height = gr.Slider(512, 1152, value=1024, step=64, label="Height")
447
 
448
  steps = gr.Slider(20, 60, value=30, step=1, label="Steps")
449
- guidance = gr.Slider(1.0, 12.0, value=5.0, step=0.5, label="CFG Scale")
450
 
451
  with gr.Row():
452
- seed = gr.Number(label="Seed", value=42, precision=0,
453
- minimum=0, maximum=2**32-1, scale=3)
454
- randomize_seed = gr.Checkbox(label="Random seed", value=True, scale=1)
 
 
 
 
 
 
 
455
 
456
  # LoRA accordion
457
  with gr.Accordion("🎨 LoRA", open=False):
458
- gr.HTML('<div style="height:8px"></div>')
459
- gr.HTML('<div style="color:#554466;font-size:0.72em;margin-bottom:10px;">Add a LoRA to tweak style or fix common issues like hands.</div>')
460
- lora_name = gr.Dropdown(
461
- choices=list(LORAS.keys()),
462
- value="None",
463
- label="LoRA",
464
- )
465
  lora_strength = gr.Slider(0.1, 1.0, value=0.7, step=0.05, label="LoRA Strength")
466
 
467
- # Generate
468
- generate_btn = gr.Button("Generate ✦", variant="primary", size="lg", elem_classes="gen-btn")
469
 
470
  generate_btn.click(
471
  fn=generate,
472
- inputs=[prompt, negative_prompt, style, lora_name, lora_strength,
473
- width, height, steps, guidance, seed, randomize_seed],
474
- outputs=[output_image, used_seed],
 
 
475
  )
476
 
477
  demo.launch()
 
4
  from diffusers import StableDiffusionXLPipeline, DPMSolverMultistepScheduler
5
  from huggingface_hub import hf_hub_download
6
  import random
7
+ import re
8
 
9
+ # ── Model β€” CyberIllustrious V8 ───────────────────────────────────────────────
10
+ MODEL_REPO = "cyberdelia/latest_sdxl_models"
11
+ MODEL_FILE = "CyberIllustrious_V8.0alt.safetensors"
12
+ IL_POS = "masterpiece, best quality, very aesthetic, absurdres, "
13
+ IL_NEG = "worst quality, low quality, bad quality, ugly, "
14
 
15
+ print("Downloading CyberIllustrious V8...")
16
  local_path = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE)
17
  print("Loading pipeline...")
18
  pipe = StableDiffusionXLPipeline.from_single_file(local_path, torch_dtype=torch.float16)
 
22
  pipe.enable_attention_slicing()
23
  print("Ready.")
24
 
25
+ # ── Smart prompt expansion (Fooocus-style) ─────────────────────────────────────
26
+ # Detects scene type and expands with the right vocabulary.
27
+ # Specific unique details (numbers, colours, states) get attention weights.
28
+
29
+ SCENE_TAGS = {
30
+ "portrait": {
31
+ "keywords": ["woman", "man", "girl", "boy", "person", "face", "portrait",
32
+ "lady", "guy", "model", "character"],
33
+ "pos": "sharp focus on face, detailed eyes, professional photography, "
34
+ "85mm lens, f/1.8 bokeh, skin texture, natural lighting, ",
35
+ "neg": "wide angle distortion, bad eyes, asymmetric face, blurry face, ",
36
+ },
37
+ "architecture": {
38
+ "keywords": ["building", "house", "tower", "city", "street", "bridge",
39
+ "castle", "cathedral", "skyscraper", "facade", "structure"],
40
+ "pos": "architectural photography, sharp geometry, detailed textures, "
41
+ "golden hour lighting, wide angle lens, high resolution, ",
42
+ "neg": "distorted perspective, blurry, watermarks, ",
43
+ },
44
+ "landscape": {
45
+ "keywords": ["mountain", "forest", "ocean", "beach", "valley", "sky",
46
+ "field", "river", "lake", "landscape", "nature", "countryside"],
47
+ "pos": "landscape photography, epic vista, golden hour, volumetric light, "
48
+ "sharp foreground, atmospheric perspective, 16mm lens, ",
49
+ "neg": "oversaturated, blurry horizon, flat lighting, ",
50
  },
51
+ "interior": {
52
+ "keywords": ["room", "interior", "bedroom", "kitchen", "office",
53
+ "living room", "hallway", "bathroom", "studio", "cafe"],
54
+ "pos": "interior photography, ambient lighting, detailed surfaces, "
55
+ "realistic materials, depth of field, architectural digest style, ",
56
+ "neg": "fisheye distortion, dark, muddy colours, ",
57
+ },
58
+ "cinematic": {
59
+ "keywords": ["cinematic", "movie", "scene", "dramatic", "epic",
60
+ "action", "night", "rain", "fog", "smoke"],
61
+ "pos": "cinematic shot, anamorphic lens, film grain, color graded, "
62
+ "dramatic lighting, shallow depth of field, movie still, ",
63
+ "neg": "flat lighting, amateur, snapshot, overexposed, ",
64
+ },
65
+ }
66
+
67
+ # Things that are specific and easy to lose β€” boost their weight
68
+ SPECIFIC_PATTERNS = [
69
+ r'\bone\b', r'\btwo\b', r'\bthree\b', r'\bsingle\b',
70
+ r'\bopen\b', r'\bclosed\b', r'\bbroken\b', r'\bempty\b',
71
+ r'\bright\b', r'\bleft\b', r'\btop\b', r'\bbottom\b',
72
+ r'rainbow', r'red ', r'blue ', r'green ', r'yellow ',
73
+ r'purple ', r'pink ', r'black ', r'white ', r'golden ',
74
+ ]
75
+
76
+ def detect_scene(prompt_lower):
77
+ scores = {}
78
+ for scene, data in SCENE_TAGS.items():
79
+ score = sum(1 for kw in data["keywords"] if kw in prompt_lower)
80
+ if score > 0:
81
+ scores[scene] = score
82
+ if not scores:
83
+ return None
84
+ return max(scores, key=scores.get)
85
+
86
+ def boost_specific_details(prompt):
87
+ """
88
+ Wraps specific/unique details in attention weights so the model
89
+ doesn't gloss over them. e.g. 'one window open' -> '(one window open:1.4)'
90
+ """
91
+ boosted = prompt
92
+
93
+ # Find phrases containing specific words and wrap them
94
+ specific_words = [
95
+ r'\b(one|single)\s+\w+(\s+\w+)?', # "one window", "single door open"
96
+ r'\b(open|closed|broken|cracked)\s+\w+', # "open window", "broken glass"
97
+ r'\b\w+\s+(rainbow|aurora|lightning)\b', # "rainbow over", "lightning bolt"
98
+ r'\b(rainbow|aurora|lightning)\b',
99
+ r'\b(left|right)\s+\w+', # "left side", "right hand"
100
+ ]
101
+
102
+ for pattern in specific_words:
103
+ def wrap(m):
104
+ return f"({m.group(0)}:1.4)"
105
+ boosted = re.sub(pattern, wrap, boosted, flags=re.IGNORECASE)
106
+
107
+ return boosted
108
+
109
+ def expand_prompt(raw_prompt, style_choice):
110
+ """
111
+ Takes a short natural language prompt and expands it Fooocus-style.
112
+ Returns (expanded_positive, extra_negative)
113
+ """
114
+ prompt_lower = raw_prompt.lower()
115
+
116
+ # Detect scene
117
+ scene = detect_scene(prompt_lower)
118
+
119
+ # Start with quality prefix (added later outside this fn)
120
+ extra_pos = ""
121
+ extra_neg = ""
122
+
123
+ # Add scene vocabulary
124
+ if scene and style_choice == "Auto":
125
+ extra_pos += SCENE_TAGS[scene]["pos"]
126
+ extra_neg += SCENE_TAGS[scene]["neg"]
127
+
128
+ # Boost specific details in the original prompt
129
+ weighted_prompt = boost_specific_details(raw_prompt.strip())
130
+
131
+ # Add general realism boosters if no style override
132
+ if style_choice == "Auto":
133
+ extra_pos += "highly detailed, sharp focus, realistic, high resolution, "
134
+
135
+ return weighted_prompt, extra_pos, extra_neg
136
+
137
+
138
+ # ── Style presets ─────────────────────────────────────────────────────────────
139
+ STYLES = {
140
+ "Auto": {"pos": "", "neg": ""},
141
+ "πŸ“Έ Photo": {
142
+ "pos": "RAW photo, photorealistic, DSLR, 8k uhd, film grain, Fujifilm XT3, ",
143
+ "neg": "painting, illustration, cartoon, anime, cgi, ",
144
  },
145
  "🎬 Cinematic": {
146
+ "pos": "cinematic, movie still, anamorphic, film grain, color graded, dramatic lighting, ",
147
+ "neg": "flat, amateur, snapshot, overexposed, ",
148
  },
149
  "πŸ–ΌοΈ Portrait": {
150
+ "pos": "portrait, studio lighting, 85mm lens, bokeh, sharp eyes, detailed skin, ",
151
+ "neg": "wide angle, distorted, bad eyes, cropped, ",
152
  },
153
  "πŸŒ† Neon City": {
154
+ "pos": "cyberpunk, neon lights, rain reflections, night scene, blade runner aesthetic, ",
155
+ "neg": "daytime, rural, warm tones, ",
156
  },
157
+ "✨ Fantasy": {
158
+ "pos": "fantasy art, epic, magical atmosphere, volumetric lighting, concept art, artstation, ",
159
+ "neg": "modern, mundane, flat, ",
160
  },
161
+ "🎨 Painterly": {
162
+ "pos": "oil painting, impressionist, visible brushstrokes, canvas texture, museum quality, ",
163
+ "neg": "photo, digital art, flat colours, ",
164
  },
165
  }
166
 
167
+ # ── LoRAs ─────────────────────────────────────────────────────────────────────
168
  LORAS = {
169
  "None": None,
170
+ "βœ‹ Better Hands": {
171
  "repo": "WolfAether21/PONY-DIFFUSION-SDXL-LORA",
172
  "file": "Perfect Hands v2.safetensors",
173
+ "strength": 0.7,
174
  },
175
+ "πŸ” More Detail": {
176
  "repo": "WolfAether21/PONY-DIFFUSION-SDXL-LORA",
177
  "file": "SDXL Detail.safetensors",
178
+ "strength": 0.6,
179
  },
180
  }
181
 
182
  # ── Generation ────────────────────────────────────────────────────────────────
183
  @spaces.GPU(duration=180)
184
+ def generate(raw_prompt, negative_prompt, style, lora_name, lora_strength,
185
+ width, height, steps, guidance, seed, randomize, show_expanded):
186
 
187
+ if not raw_prompt.strip():
188
  raise gr.Error("Please enter a prompt.")
189
 
190
  if randomize:
191
  seed = random.randint(0, 2**32 - 1)
192
  seed = int(seed)
193
 
194
+ # ── Smart expansion ──
195
+ weighted_prompt, scene_pos, scene_neg = expand_prompt(raw_prompt, style)
196
+ style_data = STYLES.get(style, STYLES["Auto"])
197
+
198
+ final_pos = IL_POS + style_data["pos"] + scene_pos + weighted_prompt
199
+ final_neg = IL_NEG + style_data["neg"] + scene_neg + negative_prompt.strip()
200
 
201
+ # ── Move to GPU ──
202
  pipe.to("cuda")
203
 
204
+ # ── Load LoRA ──
205
  lora_loaded = False
206
  lora_data = LORAS.get(lora_name)
207
  if lora_data:
208
  try:
209
+ lp = hf_hub_download(repo_id=lora_data["repo"], filename=lora_data["file"])
210
+ pipe.load_lora_weights(lp)
211
  pipe.fuse_lora(lora_scale=float(lora_strength))
212
  lora_loaded = True
 
213
  except Exception as e:
214
+ print(f"LoRA failed, skipping: {e}")
215
 
216
  generator = torch.Generator(device="cpu").manual_seed(seed)
217
 
 
223
  num_inference_steps=int(steps),
224
  guidance_scale=float(guidance),
225
  generator=generator,
226
+ clip_skip=1,
227
  )
228
 
 
229
  if lora_loaded:
230
  pipe.unfuse_lora()
231
  pipe.unload_lora_weights()
232
 
233
  pipe.to("cpu")
234
 
235
+ # Show expanded prompt if toggled on
236
+ expanded_text = f"**Sent to model:**\n{final_pos}" if show_expanded else ""
237
+
238
+ return result.images[0], seed, expanded_text
239
+
240
 
241
  # ── CSS ───────────────────────────────────────────────────────────────────────
242
  css = """
243
  * { box-sizing: border-box; margin: 0; padding: 0; }
244
 
245
  body, .gradio-container {
246
+ background: #07070e !important;
247
  font-family: 'Inter', system-ui, -apple-system, sans-serif !important;
248
+ max-width: 500px !important;
249
  margin: 0 auto !important;
250
+ padding: 8px !important;
251
  }
252
 
253
+ /* ── Topbar ── */
254
  .topbar {
255
  display: flex;
256
  align-items: center;
257
  justify-content: space-between;
258
+ padding: 10px 2px 14px;
 
259
  }
260
  .topbar-title {
261
+ color: #e8e0ff;
262
+ font-size: 0.95em;
263
  font-weight: 800;
264
  letter-spacing: -0.3px;
265
  }
266
+ .gpu-pill {
267
+ background: #1aff7a18;
268
+ border: 1px solid #1aff7a44;
269
+ color: #1aff7a;
270
  font-size: 0.6em;
271
+ font-weight: 800;
272
+ padding: 4px 12px;
273
  border-radius: 20px;
274
+ letter-spacing: 1.5px;
275
  text-transform: uppercase;
276
  }
277
 
278
+ /* ── Image output ── */
279
+ .img-out {
280
+ background: #0d0d1a;
281
+ border: 1px solid #16162a;
282
+ border-radius: 20px;
283
  overflow: hidden;
284
+ margin-bottom: 8px;
285
+ min-height: 380px;
286
+ position: relative;
287
  display: flex;
288
  align-items: center;
289
  justify-content: center;
 
290
  }
291
+ .img-out img {
292
  width: 100% !important;
293
+ border-radius: 20px;
294
+ display: block;
 
295
  }
296
+
297
+ /* ── Seed pill under image ── */
298
+ .seed-pill {
299
+ text-align: center;
300
+ margin-bottom: 12px;
301
+ }
302
+ .seed-pill input[type=number] {
303
+ background: transparent !important;
304
+ border: none !important;
305
+ color: #2e2848 !important;
306
+ font-size: 0.7em !important;
307
+ text-align: center !important;
308
+ padding: 0 !important;
309
+ width: 100% !important;
310
+ pointer-events: none;
311
  }
312
 
313
+ /* ── Card ── */
314
+ .card {
315
+ background: #0d0d1a;
316
+ border: 1px solid #16162a;
317
  border-radius: 14px;
318
+ padding: 14px;
319
  margin-bottom: 8px;
 
 
 
 
320
  }
321
+ .card-label {
322
+ color: #3d3060;
323
+ font-size: 0.62em;
324
+ font-weight: 800;
325
  text-transform: uppercase;
326
+ letter-spacing: 2px;
327
+ margin-bottom: 8px;
328
  }
329
 
330
+ /* ── Prompt textarea ── */
331
  textarea {
332
  background: transparent !important;
333
  border: none !important;
334
+ color: #c8b8f0 !important;
335
+ font-size: 15px !important;
336
+ line-height: 1.6 !important;
 
337
  padding: 0 !important;
338
  resize: none !important;
339
  box-shadow: none !important;
340
  width: 100% !important;
341
+ outline: none !important;
342
  }
343
+ textarea::placeholder { color: #252038 !important; }
344
  textarea:focus {
345
  outline: none !important;
346
  box-shadow: none !important;
 
347
  }
 
348
 
349
  /* ── Style pills ── */
350
+ .style-wrap .gr-radio {
 
 
 
351
  display: flex !important;
352
  flex-wrap: wrap !important;
353
  gap: 6px !important;
354
  }
355
+ .style-wrap label {
356
+ background: #0d0d1a !important;
357
+ border: 1px solid #1a1a2e !important;
358
+ border-radius: 30px !important;
359
+ color: #4a3a6a !important;
360
+ font-size: 0.75em !important;
 
361
  font-weight: 600 !important;
362
  padding: 6px 14px !important;
363
+ cursor: pointer !important;
364
+ transition: all 0.15s ease !important;
365
  white-space: nowrap !important;
366
  }
367
+ .style-wrap label:has(input:checked) {
368
+ background: #18083a !important;
369
+ border-color: #7744ee !important;
370
+ color: #bb99ff !important;
371
+ box-shadow: 0 0 10px #7744ee33 !important;
372
  }
373
+ .style-wrap input[type=radio] { display: none !important; }
374
 
375
  /* ── Accordion ── */
376
  .gradio-accordion {
377
+ background: #0d0d1a !important;
378
+ border: 1px solid #16162a !important;
379
  border-radius: 14px !important;
380
  margin-bottom: 8px !important;
381
  overflow: hidden !important;
382
  }
383
+ .gradio-accordion .label-wrap button {
384
+ color: #4a3a6a !important;
385
+ font-size: 0.72em !important;
 
386
  font-weight: 700 !important;
387
  text-transform: uppercase !important;
388
+ letter-spacing: 1.5px !important;
389
+ padding: 12px 16px !important;
 
 
390
  }
391
 
392
  /* ── Sliders ── */
393
  .gradio-slider {
 
394
  background: transparent !important;
395
  border: none !important;
396
+ padding: 4px 0 10px !important;
397
  }
398
  input[type=range] {
399
+ accent-color: #6633bb !important;
400
  width: 100% !important;
 
401
  }
402
+ .gradio-slider .wrap {
403
+ color: #6644aa !important;
404
+ font-size: 0.72em !important;
405
  font-weight: 600 !important;
406
  }
407
 
408
+ /* ── Number inputs ── */
409
  input[type=number] {
410
  background: #0a0a14 !important;
411
+ border: 1px solid #18182a !important;
412
  border-radius: 10px !important;
413
+ color: #9977cc !important;
414
  font-size: 13px !important;
415
  padding: 8px 10px !important;
 
416
  }
417
 
418
  /* ── Checkbox ── */
419
+ input[type=checkbox] { accent-color: #6633bb !important; }
 
 
 
 
420
  .gradio-checkbox label span {
421
+ color: #4a3a6a !important;
422
+ font-size: 0.75em !important;
423
  font-weight: 600 !important;
424
  }
425
 
426
+ /* ── Dropdown ── */
427
  .gradio-dropdown {
428
  background: #0a0a14 !important;
429
+ border: 1px solid #18182a !important;
430
  border-radius: 10px !important;
431
  }
 
 
 
 
 
432
 
433
+ /* ── Expanded prompt box ── */
434
+ .expanded-box {
435
+ background: #080814;
436
+ border: 1px solid #111122;
437
+ border-radius: 10px;
438
+ padding: 10px 12px;
439
+ color: #332255;
440
+ font-size: 0.7em;
441
+ line-height: 1.6;
442
+ font-family: monospace;
443
+ word-break: break-word;
444
+ min-height: 32px;
445
  }
446
+
447
+ /* ── Labels ── */
448
+ label > span:first-child {
449
+ color: #3a2d55 !important;
450
+ font-size: 0.7em !important;
451
+ font-weight: 700 !important;
452
+ text-transform: uppercase !important;
453
+ letter-spacing: 1px !important;
454
  }
455
 
456
  /* ── Generate button ── */
457
  .gen-btn button {
458
+ background: linear-gradient(135deg, #4a1aaa 0%, #2d0e77 100%) !important;
459
+ border: 1px solid #6633cc !important;
460
  border-radius: 14px !important;
461
  color: #fff !important;
462
+ font-size: 0.88em !important;
463
+ font-weight: 900 !important;
464
+ padding: 17px !important;
465
  width: 100% !important;
466
+ letter-spacing: 2px !important;
467
  text-transform: uppercase !important;
468
+ box-shadow: 0 4px 24px #4a1aaa55 !important;
469
+ transition: all 0.15s ease !important;
470
+ margin-top: 6px !important;
471
  }
472
  .gen-btn button:hover {
473
+ box-shadow: 0 6px 32px #4a1aaa99 !important;
474
  transform: translateY(-1px) !important;
475
  }
476
  .gen-btn button:active {
477
  transform: scale(0.98) !important;
478
+ box-shadow: 0 2px 12px #4a1aaa33 !important;
479
  }
480
 
481
+ footer, .built-with { display: none !important; }
 
 
 
 
 
 
 
 
 
482
  """
483
 
484
  # ── UI ────────────────────────────────────────────────────────────────────────
485
  with gr.Blocks(css=css, title="ImageGen") as demo:
486
 
 
487
  gr.HTML("""
488
  <div class="topbar">
489
+ <span class="topbar-title">CyberIllustrious</span>
490
+ <span class="gpu-pill">⚑ ZeroGPU</span>
491
  </div>
492
  """)
493
 
494
  # Output
495
  output_image = gr.Image(
496
+ show_label=False, type="pil",
497
+ height=460, elem_classes="img-out",
 
 
498
  )
 
 
499
  used_seed = gr.Number(
500
+ label="seed", interactive=False,
501
+ elem_classes="seed-pill",
 
502
  )
503
 
504
+ # Prompt card
505
+ gr.HTML('<div class="card"><div class="card-label">Prompt</div>')
506
  prompt = gr.Textbox(
507
  show_label=False,
508
+ placeholder="describe anything β€” short or long, it gets expanded automatically...",
509
  lines=3,
 
510
  )
511
+ gr.HTML('</div>')
512
 
513
+ # Style pills
514
+ gr.HTML('<div class="card-label" style="padding:4px 2px 8px;color:#3d3060;font-size:0.62em;font-weight:800;text-transform:uppercase;letter-spacing:2px;">Style</div>')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
  style = gr.Radio(
516
  choices=list(STYLES.keys()),
517
+ value="Auto",
518
  show_label=False,
519
+ elem_classes="style-wrap",
520
+ )
521
+
522
+ # Generate
523
+ generate_btn = gr.Button(
524
+ "Generate ✦", variant="primary",
525
+ size="lg", elem_classes="gen-btn",
526
  )
527
 
528
+ # Advanced accordion
529
+ with gr.Accordion("βš™οΈ Settings", open=False):
530
+ gr.HTML('<div style="height:6px"></div>')
531
+
532
+ negative_prompt = gr.Textbox(
533
+ label="Negative Prompt",
534
+ value=(
535
+ "worst quality, low quality, bad anatomy, bad hands, "
536
+ "signature, watermarks, ugly, blurry, deformed"
537
+ ),
538
+ lines=2,
539
+ )
540
 
541
  with gr.Row():
542
+ width = gr.Slider(512, 1024, value=832, step=64, label="Width")
543
+ height = gr.Slider(512, 1216, value=1216, step=64, label="Height")
544
 
545
  steps = gr.Slider(20, 60, value=30, step=1, label="Steps")
546
+ guidance = gr.Slider(1.0, 10.0, value=5.0, step=0.5, label="CFG Scale")
547
 
548
  with gr.Row():
549
+ seed = gr.Number(
550
+ label="Seed", value=42, precision=0,
551
+ minimum=0, maximum=2**32-1, scale=3,
552
+ )
553
+ randomize = gr.Checkbox(label="Random seed", value=True, scale=1)
554
+
555
+ show_expanded = gr.Checkbox(
556
+ label="Show expanded prompt (debug)",
557
+ value=False,
558
+ )
559
 
560
  # LoRA accordion
561
  with gr.Accordion("🎨 LoRA", open=False):
562
+ gr.HTML('<div style="height:6px"></div>')
563
+ lora_name = gr.Dropdown(choices=list(LORAS.keys()), value="None", label="LoRA preset")
 
 
 
 
 
564
  lora_strength = gr.Slider(0.1, 1.0, value=0.7, step=0.05, label="LoRA Strength")
565
 
566
+ # Expanded prompt debug output
567
+ expanded_out = gr.Markdown(elem_classes="expanded-box")
568
 
569
  generate_btn.click(
570
  fn=generate,
571
+ inputs=[
572
+ prompt, negative_prompt, style, lora_name, lora_strength,
573
+ width, height, steps, guidance, seed, randomize, show_expanded,
574
+ ],
575
+ outputs=[output_image, used_seed, expanded_out],
576
  )
577
 
578
  demo.launch()