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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +111 -193
app.py CHANGED
@@ -2,18 +2,29 @@ import gradio as gr
2
  import torch
3
  import spaces
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)
19
  pipe.scheduler = DPMSolverMultistepScheduler.from_config(
@@ -22,145 +33,76 @@ pipe.scheduler = DPMSolverMultistepScheduler.from_config(
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
 
@@ -191,12 +133,13 @@ def generate(raw_prompt, negative_prompt, style, lora_name, lora_strength,
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")
@@ -206,7 +149,11 @@ def generate(raw_prompt, negative_prompt, style, lora_name, lora_strength,
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
@@ -232,11 +179,10 @@ def generate(raw_prompt, negative_prompt, style, lora_name, lora_strength,
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 = """
@@ -250,7 +196,6 @@ body, .gradio-container {
250
  padding: 8px !important;
251
  }
252
 
253
- /* ── Topbar ── */
254
  .topbar {
255
  display: flex;
256
  align-items: center;
@@ -261,7 +206,6 @@ body, .gradio-container {
261
  color: #e8e0ff;
262
  font-size: 0.95em;
263
  font-weight: 800;
264
- letter-spacing: -0.3px;
265
  }
266
  .gpu-pill {
267
  background: #1aff7a18;
@@ -275,7 +219,6 @@ body, .gradio-container {
275
  text-transform: uppercase;
276
  }
277
 
278
- /* ── Image output ── */
279
  .img-out {
280
  background: #0d0d1a;
281
  border: 1px solid #16162a;
@@ -283,7 +226,6 @@ body, .gradio-container {
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;
@@ -294,23 +236,16 @@ body, .gradio-container {
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;
@@ -327,7 +262,6 @@ body, .gradio-container {
327
  margin-bottom: 8px;
328
  }
329
 
330
- /* ── Prompt textarea ── */
331
  textarea {
332
  background: transparent !important;
333
  border: none !important;
@@ -344,9 +278,9 @@ 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;
@@ -372,7 +306,6 @@ textarea:focus {
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;
@@ -389,7 +322,6 @@ textarea:focus {
389
  padding: 12px 16px !important;
390
  }
391
 
392
- /* ── Sliders ── */
393
  .gradio-slider {
394
  background: transparent !important;
395
  border: none !important;
@@ -399,13 +331,7 @@ 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;
@@ -415,7 +341,6 @@ input[type=number] {
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;
@@ -423,37 +348,34 @@ input[type=checkbox] { accent-color: #6633bb !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;
@@ -475,7 +397,6 @@ label > span:first-child {
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; }
@@ -491,7 +412,6 @@ with gr.Blocks(css=css, title="ImageGen") as demo:
491
  </div>
492
  """)
493
 
494
- # Output
495
  output_image = gr.Image(
496
  show_label=False, type="pil",
497
  height=460, elem_classes="img-out",
@@ -501,16 +421,14 @@ with gr.Blocks(css=css, title="ImageGen") as demo:
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()),
@@ -519,13 +437,17 @@ with gr.Blocks(css=css, title="ImageGen") as demo:
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
 
@@ -542,7 +464,7 @@ with gr.Blocks(css=css, title="ImageGen") as demo:
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():
@@ -553,19 +475,15 @@ with gr.Blocks(css=css, title="ImageGen") as demo:
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=[
 
2
  import torch
3
  import spaces
4
  from diffusers import StableDiffusionXLPipeline, DPMSolverMultistepScheduler
5
+ from huggingface_hub import hf_hub_download, InferenceClient
6
  import random
7
+ import os
8
 
9
+ # ── HF Inference client (prompt expansion LLM) ────────────────────────────────
10
+ HF_TOKEN = os.environ.get("HF_TOKEN", None)
11
+ llm_client = InferenceClient(
12
+ model="mistralai/Mistral-7B-Instruct-v0.3",
13
+ token=HF_TOKEN,
14
+ )
15
+
16
+ # ── Image model β€” CyberIllustrious ────────────────────────────────────────────
17
  MODEL_REPO = "cyberdelia/latest_sdxl_models"
18
  MODEL_FILE = "CyberIllustrious_V8.0alt.safetensors"
19
  IL_POS = "masterpiece, best quality, very aesthetic, absurdres, "
20
  IL_NEG = "worst quality, low quality, bad quality, ugly, "
21
 
22
+ print("Downloading CyberIllustrious...")
23
+ local_path = hf_hub_download(
24
+ repo_id=MODEL_REPO,
25
+ filename=MODEL_FILE,
26
+ token=HF_TOKEN,
27
+ )
28
  print("Loading pipeline...")
29
  pipe = StableDiffusionXLPipeline.from_single_file(local_path, torch_dtype=torch.float16)
30
  pipe.scheduler = DPMSolverMultistepScheduler.from_config(
 
33
  pipe.enable_attention_slicing()
34
  print("Ready.")
35
 
36
+ # ── LLM prompt expansion ──────────────────────────────────────────────────────
37
+ EXPANSION_SYSTEM = """You are an expert Stable Diffusion prompt engineer specialising in photorealistic and cinematic image generation.
38
+
39
+ Your job: take a short user description and rewrite it as a detailed, accurate image generation prompt.
40
+
41
+ Rules:
42
+ - PRESERVE every specific detail from the input β€” if they say "one window open", "rainbow", "red door", those MUST appear
43
+ - Wrap unique/specific details in attention weights like (one window open:1.4) or (rainbow:1.3)
44
+ - Add: lighting description, camera/lens style, atmosphere, material textures, composition
45
+ - Add quality boosters appropriate to the scene
46
+ - Do NOT add people unless the user mentioned people
47
+ - Do NOT change the subject or invent things not implied
48
+ - Return ONLY the final prompt β€” no explanation, no preamble, no quotes
49
+ - Keep it under 120 words
50
+ - Use comma-separated tags and phrases, not full sentences"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
+ def expand_prompt_llm(raw_prompt, style):
53
+ """Use Mistral to expand the user's short prompt Fooocus-style."""
54
+ if not raw_prompt.strip():
55
+ return ""
56
+
57
+ style_hint = f" The desired style is: {style}." if style != "Auto" else ""
58
+
59
+ user_msg = f"Expand this into a detailed image generation prompt:{style_hint}\n\n{raw_prompt.strip()}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
+ try:
62
+ response = llm_client.chat_completion(
63
+ messages=[
64
+ {"role": "system", "content": EXPANSION_SYSTEM},
65
+ {"role": "user", "content": user_msg},
66
+ ],
67
+ max_tokens=200,
68
+ temperature=0.7,
69
+ )
70
+ expanded = response.choices[0].message.content.strip()
71
+ # Clean up any accidental quotes or preamble
72
+ expanded = expanded.strip('"').strip("'")
73
+ if expanded.lower().startswith("prompt:"):
74
+ expanded = expanded[7:].strip()
75
+ return expanded
76
+ except Exception as e:
77
+ print(f"LLM expansion failed, using raw prompt: {e}")
78
+ return raw_prompt.strip()
79
 
80
  # ── Style presets ─────────────────────────────────────────────────────────────
81
  STYLES = {
82
+ "Auto": {"pos": "", "neg": ""},
83
+ "πŸ“Έ Photo": {
84
+ "pos": "RAW photo, photorealistic, DSLR, 8k uhd, film grain, Fujifilm XT3, sharp focus, ",
85
+ "neg": "painting, illustration, cartoon, anime, cgi, render, ",
86
  },
87
  "🎬 Cinematic": {
88
+ "pos": "cinematic movie still, anamorphic lens, film grain, color graded, dramatic lighting, ",
89
+ "neg": "flat lighting, amateur, snapshot, overexposed, ",
90
  },
91
+ "πŸ–ΌοΈ Portrait": {
92
+ "pos": "professional portrait, studio lighting, 85mm lens, bokeh, sharp eyes, skin texture, ",
93
+ "neg": "wide angle distortion, bad eyes, cropped head, ",
94
  },
95
  "πŸŒ† Neon City": {
96
+ "pos": "cyberpunk city, neon lights, rain reflections, night scene, blade runner aesthetic, ",
97
+ "neg": "daytime, rural, nature, warm tones, ",
98
  },
99
+ "✨ Fantasy": {
100
  "pos": "fantasy art, epic, magical atmosphere, volumetric lighting, concept art, artstation, ",
101
  "neg": "modern, mundane, flat, ",
102
  },
103
+ "🎨 Painterly": {
104
  "pos": "oil painting, impressionist, visible brushstrokes, canvas texture, museum quality, ",
105
+ "neg": "photo, digital flat art, ",
106
  },
107
  }
108
 
 
133
  seed = random.randint(0, 2**32 - 1)
134
  seed = int(seed)
135
 
136
+ # ── LLM expansion ──
137
+ expanded = expand_prompt_llm(raw_prompt, style)
 
138
 
139
+ # ── Build final prompt ──
140
+ style_data = STYLES.get(style, STYLES["Auto"])
141
+ final_pos = IL_POS + style_data["pos"] + expanded
142
+ final_neg = IL_NEG + style_data["neg"] + negative_prompt.strip()
143
 
144
  # ── Move to GPU ──
145
  pipe.to("cuda")
 
149
  lora_data = LORAS.get(lora_name)
150
  if lora_data:
151
  try:
152
+ lp = hf_hub_download(
153
+ repo_id=lora_data["repo"],
154
+ filename=lora_data["file"],
155
+ token=HF_TOKEN,
156
+ )
157
  pipe.load_lora_weights(lp)
158
  pipe.fuse_lora(lora_scale=float(lora_strength))
159
  lora_loaded = True
 
179
 
180
  pipe.to("cpu")
181
 
182
+ # ── Debug output ──
183
+ debug_text = f"**Expanded prompt sent to model:**\n\n{final_pos}" if show_expanded else ""
 
 
184
 
185
+ return result.images[0], seed, debug_text
186
 
187
  # ── CSS ───────────────────────────────────────────────────────────────────────
188
  css = """
 
196
  padding: 8px !important;
197
  }
198
 
 
199
  .topbar {
200
  display: flex;
201
  align-items: center;
 
206
  color: #e8e0ff;
207
  font-size: 0.95em;
208
  font-weight: 800;
 
209
  }
210
  .gpu-pill {
211
  background: #1aff7a18;
 
219
  text-transform: uppercase;
220
  }
221
 
 
222
  .img-out {
223
  background: #0d0d1a;
224
  border: 1px solid #16162a;
 
226
  overflow: hidden;
227
  margin-bottom: 8px;
228
  min-height: 380px;
 
229
  display: flex;
230
  align-items: center;
231
  justify-content: center;
 
236
  display: block;
237
  }
238
 
 
 
 
 
 
239
  .seed-pill input[type=number] {
240
  background: transparent !important;
241
  border: none !important;
242
  color: #2e2848 !important;
243
  font-size: 0.7em !important;
244
  text-align: center !important;
245
+ padding: 2px !important;
246
  width: 100% !important;
 
247
  }
248
 
 
249
  .card {
250
  background: #0d0d1a;
251
  border: 1px solid #16162a;
 
262
  margin-bottom: 8px;
263
  }
264
 
 
265
  textarea {
266
  background: transparent !important;
267
  border: none !important;
 
278
  textarea:focus {
279
  outline: none !important;
280
  box-shadow: none !important;
281
+ border: none !important;
282
  }
283
 
 
284
  .style-wrap .gr-radio {
285
  display: flex !important;
286
  flex-wrap: wrap !important;
 
306
  }
307
  .style-wrap input[type=radio] { display: none !important; }
308
 
 
309
  .gradio-accordion {
310
  background: #0d0d1a !important;
311
  border: 1px solid #16162a !important;
 
322
  padding: 12px 16px !important;
323
  }
324
 
 
325
  .gradio-slider {
326
  background: transparent !important;
327
  border: none !important;
 
331
  accent-color: #6633bb !important;
332
  width: 100% !important;
333
  }
 
 
 
 
 
334
 
 
335
  input[type=number] {
336
  background: #0a0a14 !important;
337
  border: 1px solid #18182a !important;
 
341
  padding: 8px 10px !important;
342
  }
343
 
 
344
  input[type=checkbox] { accent-color: #6633bb !important; }
345
  .gradio-checkbox label span {
346
  color: #4a3a6a !important;
 
348
  font-weight: 600 !important;
349
  }
350
 
 
351
  .gradio-dropdown {
352
  background: #0a0a14 !important;
353
  border: 1px solid #18182a !important;
354
  border-radius: 10px !important;
355
  }
356
 
357
+ label > span:first-child {
358
+ color: #3a2d55 !important;
359
+ font-size: 0.7em !important;
360
+ font-weight: 700 !important;
361
+ text-transform: uppercase !important;
362
+ letter-spacing: 1px !important;
363
+ }
364
+
365
+ /* Expanded prompt debug box */
366
+ .debug-box {
367
  background: #080814;
368
  border: 1px solid #111122;
369
  border-radius: 10px;
370
  padding: 10px 12px;
371
+ color: #443366;
372
  font-size: 0.7em;
373
+ line-height: 1.7;
374
  font-family: monospace;
375
  word-break: break-word;
376
+ margin-bottom: 8px;
 
 
 
 
 
 
 
 
 
377
  }
378
 
 
379
  .gen-btn button {
380
  background: linear-gradient(135deg, #4a1aaa 0%, #2d0e77 100%) !important;
381
  border: 1px solid #6633cc !important;
 
397
  }
398
  .gen-btn button:active {
399
  transform: scale(0.98) !important;
 
400
  }
401
 
402
  footer, .built-with { display: none !important; }
 
412
  </div>
413
  """)
414
 
 
415
  output_image = gr.Image(
416
  show_label=False, type="pil",
417
  height=460, elem_classes="img-out",
 
421
  elem_classes="seed-pill",
422
  )
423
 
424
+ gr.HTML('<div class="card"><div class="card-label">✦ Prompt β€” write anything, short or long</div>')
 
425
  prompt = gr.Textbox(
426
  show_label=False,
427
+ placeholder="building with rainbow and one window open...",
428
  lines=3,
429
  )
430
  gr.HTML('</div>')
431
 
 
432
  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>')
433
  style = gr.Radio(
434
  choices=list(STYLES.keys()),
 
437
  elem_classes="style-wrap",
438
  )
439
 
 
440
  generate_btn = gr.Button(
441
  "Generate ✦", variant="primary",
442
  size="lg", elem_classes="gen-btn",
443
  )
444
 
445
+ expanded_out = gr.Markdown(
446
+ value="",
447
+ elem_classes="debug-box",
448
+ visible=True,
449
+ )
450
+
451
  with gr.Accordion("βš™οΈ Settings", open=False):
452
  gr.HTML('<div style="height:6px"></div>')
453
 
 
464
  width = gr.Slider(512, 1024, value=832, step=64, label="Width")
465
  height = gr.Slider(512, 1216, value=1216, step=64, label="Height")
466
 
467
+ steps = gr.Slider(20, 60, value=30, step=1, label="Steps")
468
  guidance = gr.Slider(1.0, 10.0, value=5.0, step=0.5, label="CFG Scale")
469
 
470
  with gr.Row():
 
475
  randomize = gr.Checkbox(label="Random seed", value=True, scale=1)
476
 
477
  show_expanded = gr.Checkbox(
478
+ label="Show expanded prompt (see what the LLM wrote)",
479
+ value=True,
480
  )
481
 
 
482
  with gr.Accordion("🎨 LoRA", open=False):
483
  gr.HTML('<div style="height:6px"></div>')
484
  lora_name = gr.Dropdown(choices=list(LORAS.keys()), value="None", label="LoRA preset")
485
  lora_strength = gr.Slider(0.1, 1.0, value=0.7, step=0.05, label="LoRA Strength")
486
 
 
 
 
487
  generate_btn.click(
488
  fn=generate,
489
  inputs=[