Spaces:
Heartsync
/
Running on Zero

seawolf2357 commited on
Commit
7f2d83c
ยท
verified ยท
1 Parent(s): f8c8bc0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +177 -287
app.py CHANGED
@@ -16,30 +16,17 @@ except ImportError:
16
  import random
17
  import os
18
  import uuid
19
- import re
20
- import time
21
  from datetime import datetime
22
 
23
  import gradio as gr
24
  import numpy as np
25
- import requests
26
  import torch
27
  from diffusers import StableDiffusionXLPipeline
28
  from diffusers import EulerAncestralDiscreteScheduler
29
  from compel import Compel, ReturnedEmbeddingsType
30
  from PIL import Image
31
 
32
- # ===== OpenAI ์„ค์ • =====
33
- from openai import OpenAI
34
-
35
- # Add error handling for API key
36
- try:
37
- client = OpenAI(api_key=os.getenv("LLM_API"))
38
- except Exception as e:
39
- print(f"Warning: OpenAI client initialization failed: {e}")
40
- client = None
41
-
42
- # ===== ํ”„๋กฌํ”„ํŠธ ์ฆ๊ฐ•์šฉ ์Šคํƒ€์ผ ํ”„๋ฆฌ์…‹ =====
43
  STYLE_PRESETS = {
44
  "None": "",
45
  "Realistic Photo": "photorealistic, 8k, ultra-detailed, cinematic lighting, realistic skin texture",
@@ -48,12 +35,46 @@ STYLE_PRESETS = {
48
  "Watercolor": "watercolor illustration, soft gradients, splatter effect, pastel palette",
49
  }
50
 
51
- # ===== ์ €์žฅ ํด๋” =====
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  SAVE_DIR = "saved_images"
53
  if not os.path.exists(SAVE_DIR):
54
  os.makedirs(SAVE_DIR, exist_ok=True)
55
 
56
- # ===== ๋””๋ฐ”์ด์Šค & ๋ชจ๋ธ ๋กœ๋“œ =====
57
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
58
  print(f"Using device: {device}")
59
 
@@ -94,104 +115,21 @@ except Exception as e:
94
  MAX_SEED = np.iinfo(np.int32).max
95
  MAX_IMAGE_SIZE = 1216
96
 
97
- # ===== ํ•œ๊ธ€ ์—ฌ๋ถ€ ํŒ๋ณ„ =====
98
- HANGUL_RE = re.compile(r"[\u3131-\u318E\uAC00-\uD7A3]+")
99
-
100
- def is_korean(text: str) -> bool:
101
- return bool(HANGUL_RE.search(text))
102
-
103
- # ===== ๋ฒˆ์—ญ & ์ฆ๊ฐ• ํ•จ์ˆ˜ =====
104
-
105
- def openai_translate(text: str, retries: int = 3) -> str:
106
- """ํ•œ๊ธ€์„ ์˜์–ด๋กœ ๋ฒˆ์—ญ (OpenAI GPT-4o-mini ์‚ฌ์šฉ). ์˜์–ด ์ž…๋ ฅ์ด๋ฉด ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜."""
107
- if not is_korean(text):
108
- return text
109
-
110
- if client is None:
111
- print("Warning: OpenAI client not available, returning original text")
112
- return text
113
-
114
- for attempt in range(retries):
115
- try:
116
- res = client.chat.completions.create(
117
- model="gpt-4o-mini",
118
- messages=[
119
- {
120
- "role": "system",
121
- "content": "Translate the following Korean prompt into concise, descriptive English suitable for an image generation model. Keep the meaning, do not add new concepts."
122
- },
123
- {"role": "user", "content": text}
124
- ],
125
- temperature=0.3,
126
- max_tokens=256,
127
- )
128
- return res.choices[0].message.content.strip()
129
- except Exception as e:
130
- print(f"[translate] attempt {attempt + 1} failed: {e}")
131
- time.sleep(2)
132
- return text # ๋ฒˆ์—ญ ์‹คํŒจ ์‹œ ์›๋ฌธ ๊ทธ๋Œ€๋กœ
133
-
134
- def enhance_prompt(text: str, retries: int = 3) -> str:
135
- """OpenAI๋ฅผ ํ†ตํ•ด ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ฆ๊ฐ•ํ•˜์—ฌ ๊ณ ํ’ˆ์งˆ ์ด๋ฏธ์ง€ ์ƒ์„ฑ์„ ์œ„ํ•œ ์ƒ์„ธํ•œ ์„ค๋ช…์œผ๋กœ ๋ณ€ํ™˜."""
136
- if client is None:
137
- print("Warning: OpenAI client not available, returning original text")
138
- return text
139
-
140
- for attempt in range(retries):
141
- try:
142
- res = client.chat.completions.create(
143
- model="gpt-4o-mini",
144
- messages=[
145
- {
146
- "role": "system",
147
- "content": """You are an expert prompt engineer for image generation models. Enhance the given prompt to create high-quality, detailed images.
148
-
149
- Guidelines:
150
- - Add specific visual details (lighting, composition, colors, textures)
151
- - Include technical photography terms (depth of field, focal length, etc.)
152
- - Add atmosphere and mood descriptors
153
- - Specify image quality terms (4K, ultra-detailed, professional, etc.)
154
- - Keep the core subject and meaning intact
155
- - Make it comprehensive but not overly long
156
- - Focus on visual elements that will improve image generation quality
157
-
158
- Example:
159
- Input: "A man giving a speech"
160
- Output: "A professional man giving an inspiring speech at a podium, dramatic lighting with warm spotlights, confident posture and gestures, high-resolution 4K photography, sharp focus, cinematic composition, bokeh background with audience silhouettes, professional event setting, detailed facial expressions, realistic skin texture"
161
- """
162
- },
163
- {"role": "user", "content": f"Enhance this prompt for high-quality image generation: {text}"}
164
- ],
165
- temperature=0.7,
166
- max_tokens=512,
167
- )
168
- return res.choices[0].message.content.strip()
169
- except Exception as e:
170
- print(f"[enhance] attempt {attempt + 1} failed: {e}")
171
- time.sleep(2)
172
- return text # ์ฆ๊ฐ• ์‹คํŒจ ์‹œ ์›๋ฌธ ๊ทธ๋Œ€๋กœ
173
-
174
- def prepare_prompt(user_prompt: str, style_key: str, enhance_prompt_enabled: bool = False) -> str:
175
- """ํ•œ๊ธ€์ด๋ฉด ๋ฒˆ์—ญํ•˜๊ณ , ํ”„๋กฌํ”„ํŠธ ์ฆ๊ฐ• ์˜ต์…˜์ด ํ™œ์„ฑํ™”๋˜๋ฉด ์ฆ๊ฐ•ํ•˜๊ณ , ์„ ํƒํ•œ ์Šคํƒ€์ผ ํ”„๋ฆฌ์…‹์„ ๋ถ™์—ฌ์„œ ์ตœ์ข… ํ”„๋กฌํ”„ํŠธ๋ฅผ ๋งŒ๋“ ๋‹ค."""
176
- # 1. ๋ฒˆ์—ญ (ํ•œ๊ธ€์ธ ๊ฒฝ์šฐ)
177
- prompt_en = openai_translate(user_prompt)
178
-
179
- # 2. ํ”„๋กฌํ”„ํŠธ ์ฆ๊ฐ• (ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ)
180
- if enhance_prompt_enabled:
181
- prompt_en = enhance_prompt(prompt_en)
182
- print(f"Enhanced prompt: {prompt_en}")
183
-
184
- # 3. ์Šคํƒ€์ผ ํ”„๋ฆฌ์…‹ ์ ์šฉ
185
  style_suffix = STYLE_PRESETS.get(style_key, "")
186
  if style_suffix:
187
- final_prompt = f"{prompt_en}, {style_suffix}"
188
  else:
189
- final_prompt = prompt_en
190
 
191
  return final_prompt
192
 
193
- # ===== ์ด๋ฏธ์ง€ ์ €์žฅ =====
 
 
194
 
 
195
  def save_generated_image(image: Image.Image, prompt: str) -> str:
196
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
197
  unique_id = str(uuid.uuid4())[:8]
@@ -199,7 +137,7 @@ def save_generated_image(image: Image.Image, prompt: str) -> str:
199
  filepath = os.path.join(SAVE_DIR, filename)
200
  image.save(filepath)
201
 
202
- # ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ €์žฅ
203
  metadata_file = os.path.join(SAVE_DIR, "metadata.txt")
204
  with open(metadata_file, "a", encoding="utf-8") as f:
205
  f.write(f"{filename}|{prompt}|{timestamp}\n")
@@ -215,8 +153,7 @@ def process_long_prompt(prompt, negative_prompt=""):
215
  print(f"Long prompt processing failed: {e}, falling back to standard processing")
216
  return None, None
217
 
218
- # ===== Diffusion ํ˜ธ์ถœ =====
219
-
220
  def run_pipeline(prompt: str, negative_prompt: str, seed: int, width: int, height: int, guidance_scale: float, num_steps: int):
221
  if pipeline is None:
222
  raise ValueError("Model pipeline not loaded")
@@ -264,13 +201,11 @@ def run_pipeline(prompt: str, negative_prompt: str, seed: int, width: int, heigh
264
  error_img = Image.new('RGB', (width, height), color=(0, 0, 0))
265
  return error_img
266
 
267
- # ===== Gradio inference ๋ž˜ํผ =====
268
-
269
  @spaces.GPU(duration=60)
270
  def generate_image(
271
  user_prompt: str,
272
  style_key: str,
273
- enhance_prompt_enabled: bool = False,
274
  negative_prompt: str = "nsfw, (low quality, worst quality:1.2), very displeasing, 3d, watermark, signature, ugly, poorly drawn",
275
  seed: int = 42,
276
  randomize_seed: bool = True,
@@ -284,14 +219,14 @@ def generate_image(
284
  if randomize_seed:
285
  seed = random.randint(0, MAX_SEED)
286
 
287
- # 1) ๋ฒˆ์—ญ + ์ฆ๊ฐ•
288
- final_prompt = prepare_prompt(user_prompt, style_key, enhance_prompt_enabled)
289
  print(f"Final prompt: {final_prompt}")
290
 
291
- # 2) ํŒŒ์ดํ”„๋ผ์ธ ํ˜ธ์ถœ
292
  image = run_pipeline(final_prompt, negative_prompt, seed, width, height, guidance_scale, num_inference_steps)
293
 
294
- # 3) ์ €์žฅ
295
  save_generated_image(image, final_prompt)
296
 
297
  return image, seed
@@ -302,56 +237,41 @@ def generate_image(
302
  error_image = Image.new('RGB', (width, height), color='red')
303
  return error_image, seed
304
 
305
- # ===== ์˜ˆ์‹œ ํ”„๋กฌํ”„ํŠธ (ํ•œ๊ตญ์–ด/์˜์–ด ํ˜ผ์šฉ ํ—ˆ์šฉ) =====
306
-
307
- examples = [
308
- "์•„๋ฆ„๋‹ค์šด ํ•œ๊ตญ ์—ฌ์„ฑ์ด ์ „ํ†ต ํ•œ๋ณต์„ ์ž…๊ณ  ๊ฒฝ๋ณต๊ถ ์•ž์—์„œ ๋ฏธ์†Œ ์ง“๋Š” ๋ชจ์Šต",
309
- "ํ˜„๋Œ€์ ์ธ ์„œ์šธ ๋„์‹œ ํ’๊ฒฝ๊ณผ ๋‚จ์‚ฐํƒ€์›Œ๊ฐ€ ๋ณด์ด๋Š” ์•ผ๊ฒฝ",
310
- "ํ•œ๊ตญ์˜ ๋ด„, ๋ฒš๊ฝƒ์ด ๋งŒ๊ฐœํ•œ ๊ณต์›์—์„œ ์‚ฐ์ฑ…ํ•˜๋Š” ์‚ฌ๋žŒ๋“ค",
311
- "์ „ํ†ต ํ•œ์˜ฅ๋งˆ์„์˜ ๊ณ ์ฆˆ๋„‰ํ•œ ์˜คํ›„ ํ’๊ฒฝ",
312
- "K-pop ์•„์ด๋Œ ์ฝ˜์„œํŠธ์˜ ํ™”๋ คํ•œ ๋ฌด๋Œ€์™€ ์—ด์ •์ ์ธ ํŒฌ๋“ค",
313
- "์„œ์šธ์˜ ๋ฒˆํ™”ํ•œ ๋ช…๋™ ๊ฑฐ๋ฆฌ, ์‡ผํ•‘ํ•˜๋Š” ์‚ฌ๋žŒ๋“ค๊ณผ ๋„ค์˜จ์‚ฌ์ธ",
314
- "Beautiful Korean woman in traditional hanbok dress standing in front of Gyeongbokgung Palace",
315
- "Modern Seoul cityscape at night with N Seoul Tower illuminated",
316
- "Korean spring scenery with cherry blossoms in full bloom",
317
- "Traditional Korean tea ceremony in a peaceful hanok setting",
318
- ]
319
-
320
- # ===== ์ปค์Šคํ…€ CSS (์ง„ํ•œ ๋ถ‰์€์ƒ‰ ๊ณ ๊ธ‰ ๋””์ž์ธ) =====
321
  custom_css = """
322
  :root {
323
- --color-primary: #E91E63;
324
- --color-secondary: #FCE4EC;
325
- --color-accent: #F8BBD9;
326
- --color-rose: #F06292;
327
- --color-gold: #FFB74D;
328
- --color-warm-gray: #F5F5F5;
329
- --color-dark-gray: #424242;
330
- --background-primary: linear-gradient(135deg, #FAFAFA 0%, #F5F5F5 50%, #EEEEEE 100%);
331
- --background-accent: linear-gradient(135deg, #FCE4EC 0%, #F8BBD9 100%);
332
- --text-primary: #212121;
333
- --text-secondary: #757575;
334
- --shadow-soft: 0 4px 20px rgba(0, 0, 0, 0.08);
335
- --shadow-medium: 0 8px 30px rgba(0, 0, 0, 0.12);
336
- --border-radius: 16px;
337
- }
338
-
339
- /* ์ „์ฒด ๋ฐฐ๊ฒฝ */
340
  footer {visibility: hidden;}
341
  .gradio-container {
342
  background: var(--background-primary) !important;
343
  min-height: 100vh;
344
- font-family: 'Inter', 'Noto Sans KR', sans-serif;
345
  }
346
 
347
- /* ํƒ€์ดํ‹€ ์Šคํƒ€์ผ */
348
  .title {
349
  color: var(--text-primary) !important;
350
  font-size: 3rem !important;
351
  font-weight: 700 !important;
352
  text-align: center;
353
  margin: 2rem 0;
354
- background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-rose) 50%, var(--color-gold) 100%);
355
  -webkit-background-clip: text;
356
  -webkit-text-fill-color: transparent;
357
  background-clip: text;
@@ -366,29 +286,10 @@ footer {visibility: hidden;}
366
  font-weight: 400;
367
  }
368
 
369
- .collection-link {
370
- text-align: center;
371
- margin-bottom: 2rem;
372
- font-size: 1rem;
373
- }
374
-
375
- .collection-link a {
376
- color: var(--color-primary);
377
- text-decoration: none;
378
- transition: all 0.3s ease;
379
- font-weight: 500;
380
- border-bottom: 1px solid transparent;
381
- }
382
-
383
- .collection-link a:hover {
384
- color: var(--color-rose);
385
- border-bottom-color: var(--color-rose);
386
- }
387
-
388
- /* ์‹ฌํ”Œํ•œ ์นด๋“œ ์Šคํƒ€์ผ */
389
  .model-description {
390
- background: rgba(255, 255, 255, 0.9);
391
- border: 1px solid rgba(233, 30, 99, 0.1);
392
  border-radius: var(--border-radius);
393
  padding: 2rem;
394
  margin: 1.5rem 0;
@@ -404,28 +305,46 @@ footer {visibility: hidden;}
404
  margin: 0;
405
  }
406
 
407
- /* ๋ฒ„ํŠผ ์Šคํƒ€์ผ */
408
  button.primary {
409
  background: var(--background-accent) !important;
410
- color: var(--color-primary) !important;
411
- border: 1px solid var(--color-accent) !important;
412
- border-radius: 12px !important;
413
  box-shadow: var(--shadow-soft) !important;
414
- transition: all 0.2s ease !important;
415
  font-weight: 600 !important;
416
  font-size: 0.95rem !important;
417
  }
418
 
419
  button.primary:hover {
420
- background: linear-gradient(135deg, var(--color-accent) 0%, var(--color-secondary) 100%) !important;
421
- transform: translateY(-1px) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  box-shadow: var(--shadow-medium) !important;
 
423
  }
424
 
425
- /* ์ž…๋ ฅ ์ปจํ…Œ์ด๋„ˆ */
426
  .input-container {
427
- background: rgba(255, 255, 255, 0.8);
428
- border: 1px solid rgba(233, 30, 99, 0.15);
429
  border-radius: var(--border-radius);
430
  padding: 1.5rem;
431
  margin-bottom: 1.5rem;
@@ -434,10 +353,10 @@ button.primary:hover {
434
  -webkit-backdrop-filter: blur(10px);
435
  }
436
 
437
- /* ๊ณ ๊ธ‰ ์„ค์ • */
438
  .advanced-settings {
439
- background: rgba(255, 255, 255, 0.6);
440
- border: 1px solid rgba(233, 30, 99, 0.1);
441
  border-radius: var(--border-radius);
442
  padding: 1.5rem;
443
  margin-top: 1rem;
@@ -446,24 +365,24 @@ button.primary:hover {
446
  -webkit-backdrop-filter: blur(8px);
447
  }
448
 
449
- /* ์˜ˆ์‹œ ์˜์—ญ */
450
- .example-region {
451
- background: rgba(252, 228, 236, 0.3);
452
- border: 1px solid rgba(233, 30, 99, 0.15);
453
  border-radius: var(--border-radius);
454
- padding: 1.5rem;
455
  margin-top: 1rem;
456
  box-shadow: var(--shadow-soft);
457
  }
458
 
459
- /* ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ์นธ ์Šคํƒ€์ผ */
460
  .large-prompt textarea {
461
  min-height: 120px !important;
462
  font-size: 15px !important;
463
  line-height: 1.5 !important;
464
  background: rgba(255, 255, 255, 0.9) !important;
465
- border: 2px solid rgba(233, 30, 99, 0.2) !important;
466
- border-radius: 12px !important;
467
  color: var(--text-primary) !important;
468
  transition: all 0.3s ease !important;
469
  padding: 1rem !important;
@@ -471,7 +390,7 @@ button.primary:hover {
471
 
472
  .large-prompt textarea:focus {
473
  border-color: var(--color-primary) !important;
474
- box-shadow: 0 0 0 3px rgba(233, 30, 99, 0.1) !important;
475
  outline: none !important;
476
  }
477
 
@@ -480,58 +399,31 @@ button.primary:hover {
480
  font-style: italic;
481
  }
482
 
483
- /* ์ƒ์„ฑ ๋ฒ„ํŠผ */
484
  .small-generate-btn {
485
  max-width: 140px !important;
486
  height: 48px !important;
487
  font-size: 15px !important;
488
  padding: 12px 24px !important;
489
- border-radius: 12px !important;
490
  font-weight: 600 !important;
491
  }
492
 
493
- /* ํ”„๋กฌํ”„ํŠธ ์ฆ๊ฐ• ์„น์…˜ */
494
- .prompt-enhance-section {
495
- background: linear-gradient(135deg, rgba(255, 183, 77, 0.1) 0%, rgba(252, 228, 236, 0.2) 100%);
496
- border: 1px solid rgba(255, 183, 77, 0.3);
497
- border-radius: var(--border-radius);
498
- padding: 1.2rem;
499
- margin-top: 1rem;
500
- box-shadow: var(--shadow-soft);
501
- }
502
-
503
- /* ์Šคํƒ€์ผ ํ”„๋ฆฌ์…‹ ์„น์…˜ */
504
- .style-preset-section {
505
- background: linear-gradient(135deg, rgba(248, 187, 217, 0.15) 0%, rgba(252, 228, 236, 0.2) 100%);
506
- border: 1px solid rgba(233, 30, 99, 0.2);
507
- border-radius: var(--border-radius);
508
- padding: 1.2rem;
509
- margin-top: 1rem;
510
- box-shadow: var(--shadow-soft);
511
- }
512
-
513
- /* ๋ผ๋ฒจ ํ…์ŠคํŠธ */
514
  label {
515
  color: var(--text-primary) !important;
516
  font-weight: 600 !important;
517
  font-size: 0.95rem !important;
518
  }
519
 
520
- /* ์ •๋ณด ํ…์ŠคํŠธ */
521
  .gr-info, .gr-textbox-info {
522
  color: var(--text-secondary) !important;
523
  font-size: 0.85rem !important;
524
  line-height: 1.4 !important;
525
  }
526
 
527
- /* ์˜ˆ์‹œ ๋งˆํฌ๋‹ค์šด */
528
- .example-region h3 {
529
- color: var(--text-primary) !important;
530
- font-weight: 600 !important;
531
- margin-bottom: 1rem !important;
532
- }
533
-
534
- /* ํผ ์š”์†Œ๋“ค */
535
  input[type="radio"], input[type="checkbox"] {
536
  accent-color: var(--color-primary) !important;
537
  }
@@ -540,36 +432,36 @@ input[type="range"] {
540
  accent-color: var(--color-primary) !important;
541
  }
542
 
543
- /* ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ */
544
  .image-container {
545
  border-radius: var(--border-radius) !important;
546
  overflow: hidden !important;
547
  box-shadow: var(--shadow-medium) !important;
548
  background: rgba(255, 255, 255, 0.9) !important;
549
- border: 1px solid rgba(233, 30, 99, 0.1) !important;
550
  }
551
 
552
- /* ์Šฌ๋ผ์ด๋” ์ปจํ…Œ์ด๋„ˆ ์Šคํƒ€์ผ๋ง */
553
  .gr-slider {
554
  margin: 0.5rem 0 !important;
555
  }
556
 
557
- /* ์•„์ฝ”๋””์–ธ ์Šคํƒ€์ผ */
558
  .gr-accordion {
559
- border: 1px solid rgba(233, 30, 99, 0.15) !important;
560
  border-radius: var(--border-radius) !important;
561
- background: rgba(255, 255, 255, 0.7) !important;
562
  }
563
 
564
  .gr-accordion-header {
565
  background: var(--background-accent) !important;
566
- color: var(--color-primary) !important;
567
  font-weight: 600 !important;
568
  border-radius: var(--border-radius) var(--border-radius) 0 0 !important;
569
  }
570
 
571
- /* ๋ถ€๋“œ๋Ÿฌ์šด ์• ๋‹ˆ๋ฉ”์ด์…˜ */
572
- .model-description, .input-container, .prompt-enhance-section, .style-preset-section {
573
  animation: fadeInUp 0.4s ease-out;
574
  }
575
 
@@ -584,39 +476,39 @@ input[type="range"] {
584
  }
585
  }
586
 
587
- /* ์ „์ฒด์ ์ธ ํ…์ŠคํŠธ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ */
588
  * {
589
  -webkit-font-smoothing: antialiased;
590
  -moz-osx-font-smoothing: grayscale;
591
  }
592
 
593
- /* ๋“œ๋กญ๋‹ค์šด ๋ฐ ์…€๋ ‰ํŠธ ์Šคํƒ€์ผ */
594
  select, .gr-dropdown {
595
  background: rgba(255, 255, 255, 0.9) !important;
596
- border: 1px solid rgba(233, 30, 99, 0.2) !important;
597
- border-radius: 8px !important;
598
  color: var(--text-primary) !important;
599
  }
600
 
601
- /* ์ฒดํฌ๋ฐ•์Šค์™€ ๋ผ๋””์˜ค ๋ฒ„ํŠผ ๊ฐœ์„  */
602
  .gr-checkbox, .gr-radio {
603
  background: transparent !important;
604
  }
605
 
606
- /* ์ „์ฒด ์ปจํ…Œ์ด๋„ˆ ์—ฌ๋ฐฑ ์กฐ์ • */
607
  .gr-container {
608
  max-width: 1200px !important;
609
  margin: 0 auto !important;
610
  padding: 2rem 1rem !important;
611
  }
612
 
613
- /* ๋ชจ๋ฐ”์ผ ๋ฐ˜์‘ํ˜• */
614
  @media (max-width: 768px) {
615
  .title {
616
  font-size: 2.2rem !important;
617
  }
618
 
619
- .model-description, .input-container, .advanced-settings, .example-region {
620
  padding: 1rem !important;
621
  margin: 1rem 0 !important;
622
  }
@@ -634,78 +526,70 @@ def create_interface():
634
  with gr.Group(elem_classes="model-description"):
635
  gr.HTML("""
636
  <p>
637
- <strong>๊ณ ํ’ˆ์งˆ ์ด๋ฏธ์ง€ ์ƒ์„ฑ AI</strong><br>
638
- <small style="opacity: 0.8;">StableDiffusionXL ๊ธฐ๋ฐ˜์˜ ๊ณ ๊ธ‰ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค. ํ•œ๊ตญ์–ด ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž๋™์œผ๋กœ ๋ฒˆ์—ญํ•˜๋ฉฐ, ๊ธด ํ”„๋กฌํ”„ํŠธ๋„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.</small><br><br>
639
  """)
640
 
641
- # ===== ๋ฉ”์ธ ์ž…๋ ฅ =====
642
  with gr.Column():
643
  with gr.Row(elem_classes="input-container"):
644
- with gr.Column(scale=4):
645
  user_prompt = gr.Text(
646
- label="Prompt (ํ”„๋กฌํ”„ํŠธ)",
647
  max_lines=5,
648
- value=examples[0],
649
  elem_classes="large-prompt",
650
- placeholder="Enter your image description here... (์ด๋ฏธ์ง€ ์„ค๋ช…์„ ์ž…๋ ฅํ•˜์„ธ์š”...)"
651
  )
652
  with gr.Column(scale=1):
653
- run_button = gr.Button(
654
- "Generate (์ƒ์„ฑ)",
655
- variant="primary",
656
- elem_classes="small-generate-btn"
657
- )
658
-
659
- # ํ”„๋กฌํ”„ํŠธ ์ฆ๊ฐ• ์˜ต์…˜ (์ƒ์„ฑ ๋ฒ„ํŠผ ์•„๋ž˜)
660
- with gr.Group(elem_classes="prompt-enhance-section"):
661
- enhance_prompt_checkbox = gr.Checkbox(
662
- label="๐Ÿš€ Prompt Enhancement (ํ”„๋กฌํ”„ํŠธ ์ฆ๊ฐ•)",
663
- value=False,
664
- info="Automatically improve your prompt using OpenAI API for high-quality image generation (OpenAI API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ณ ํ’ˆ์งˆ ์ด๋ฏธ์ง€ ์ƒ์„ฑ์„ ์œ„ํ•ด ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž๋™์œผ๋กœ ๊ฐœ์„ ํ•ฉ๋‹ˆ๋‹ค)"
665
- )
666
 
667
- # ์Šคํƒ€์ผ ํ”„๋ฆฌ์…‹ ์„น์…˜
668
  with gr.Group(elem_classes="style-preset-section"):
669
  style_select = gr.Radio(
670
- label="๐ŸŽจ Style Preset (์Šคํƒ€์ผ ํ”„๋ฆฌ์…‹)",
671
  choices=list(STYLE_PRESETS.keys()),
672
  value="None",
673
  interactive=True
674
  )
675
 
676
- result_image = gr.Image(label="Generated Image (์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€)")
677
- seed_output = gr.Number(label="Seed (์‹œ๋“œ๊ฐ’)")
678
 
679
- # ===== ๊ณ ๊ธ‰ ์„ค์ • =====
680
- with gr.Accordion("Advanced Settings (๊ณ ๊ธ‰ ์„ค์ •)", open=False, elem_classes="advanced-settings"):
681
  negative_prompt = gr.Text(
682
- label="Negative prompt (๋„ค๊ฑฐํ‹ฐ๋ธŒ ํ”„๋กฌํ”„ํŠธ)",
683
  max_lines=1,
684
  placeholder="Enter a negative prompt",
685
  value="nsfw, (low quality, worst quality:1.2), very displeasing, 3d, watermark, signature, ugly, poorly drawn"
686
  )
687
 
688
- seed = gr.Slider(label="Seed (์‹œ๋“œ๊ฐ’)", minimum=0, maximum=MAX_SEED, step=1, value=42)
689
- randomize_seed = gr.Checkbox(label="Randomize seed (์‹œ๋“œ๊ฐ’ ๋ฌด์ž‘์œ„)", value=True)
690
  with gr.Row():
691
- width = gr.Slider(label="Width (๊ฐ€๋กœ)", minimum=256, maximum=MAX_IMAGE_SIZE, step=32, value=1024)
692
- height = gr.Slider(label="Height (์„ธ๋กœ)", minimum=256, maximum=MAX_IMAGE_SIZE, step=32, value=1024)
693
  with gr.Row():
694
- guidance_scale = gr.Slider(label="Guidance scale (๊ฐ€์ด๋˜์Šค ์Šค์ผ€์ผ)", minimum=0.0, maximum=20.0, step=0.1, value=7.0)
695
- num_inference_steps = gr.Slider(label="Inference steps (์ถ”๋ก  ๋‹จ๊ณ„)", minimum=1, maximum=50, step=1, value=28)
696
-
697
- # ===== ์˜ˆ์‹œ ์˜์—ญ =====
698
- with gr.Group(elem_classes="example-region"):
699
- gr.Markdown("### Examples (์˜ˆ์‹œ)")
700
- gr.Examples(examples=examples, inputs=user_prompt, cache_examples=False)
701
 
702
- # ===== ์ด๋ฒคํŠธ =====
703
  run_button.click(
704
  fn=generate_image,
705
  inputs=[
706
  user_prompt,
707
  style_select,
708
- enhance_prompt_checkbox,
709
  negative_prompt,
710
  seed,
711
  randomize_seed,
@@ -716,10 +600,16 @@ def create_interface():
716
  ],
717
  outputs=[result_image, seed_output],
718
  )
 
 
 
 
 
 
719
 
720
  return demo
721
 
722
- # ===== ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ =====
723
  if __name__ == "__main__":
724
  demo = create_interface()
725
  demo.queue()
 
16
  import random
17
  import os
18
  import uuid
 
 
19
  from datetime import datetime
20
 
21
  import gradio as gr
22
  import numpy as np
 
23
  import torch
24
  from diffusers import StableDiffusionXLPipeline
25
  from diffusers import EulerAncestralDiscreteScheduler
26
  from compel import Compel, ReturnedEmbeddingsType
27
  from PIL import Image
28
 
29
+ # ===== Style presets =====
 
 
 
 
 
 
 
 
 
 
30
  STYLE_PRESETS = {
31
  "None": "",
32
  "Realistic Photo": "photorealistic, 8k, ultra-detailed, cinematic lighting, realistic skin texture",
 
35
  "Watercolor": "watercolor illustration, soft gradients, splatter effect, pastel palette",
36
  }
37
 
38
+ # ===== Random prompt examples =====
39
+ prompt_examples = [
40
+ "The shy college girl, with glasses and a tight plaid skirt, nervously approaches her professor",
41
+ "Her skirt rose a little higher with each gentle push, a soft blush of blush spreading across her cheeks as she felt the satisfying warmth of his breath on her cheek.",
42
+ "a girl in a school uniform having her skirt pulled up by a boy, and then being fucked",
43
+ "Moody mature anime scene of two lovers fuck under neon rain, sensual atmosphere",
44
+ "Moody mature anime scene of two lovers kissing under neon rain, sensual atmosphere",
45
+ "The girl sits on the boy's lap by the window, his hands resting on her waist. She is unbuttoning his shirt, her expression focused and intense.",
46
+ "A girl with long, black hair is sleeping on her desk in the classroom. Her skirt has ridden up, revealing her thighs, and a trail of drool escapes her slightly parted lips.",
47
+ "The waves rolled gently, a slow, sweet kiss of the lip, a slow, slow build of anticipation as their toes bumped gently โ€“ a slow, sweet kiss of the lip, a promise of more to come.",
48
+ "Her elegant silk gown swayed gracefully as she approached him, the delicate fabric brushing against her legs. A warm blush spread across her cheeks as she felt his breath on her face.",
49
+ "Her white blouse and light cotton skirt rose a little higher with each gentle push, a soft blush spreading across her cheeks as she felt the satisfying warmth of his breath on her cheek.",
50
+ "A woman in a business suit having her skirt lifted by a man, and then being sexually assaulted.",
51
+ "The older woman sits on the man's lap by the fireplace, his hands resting on her hips. She is unbuttoning his vest, her expression focused and intense. He takes control of the situation as she finishes unbuttoning his shirt, pushing her onto her back and begins to have sex with her.",
52
+ "There is a woman with long black hair. Her face features alluring eyes and full lips, with a slender figure adorned in black lace lingerie. She lies on the bed, loosening her lingerie strap with one hand while seductively glancing downward.",
53
+ "In a dimly lit room, the same woman teases with her dark, flowing hair, now covering her voluptuous breasts, while a black garter belt accentuates her thighs. She sits on the sofa, leaning back, lifting one leg to expose her most private areas through the sheer lingerie.",
54
+ "A woman with glasses, lying on the bed in just her bra, spreads her legs wide, revealing all! She wears a sultry expression, gazing directly at the viewer with her brown eyes, her short black hair cascading over the pillow. Her slim figure, accentuated by the lacy lingerie, exudes a seductive aura.",
55
+ "A soft focus on the girl's face, eyes closed, biting her lip, as her roommate performs oral pleasure, the experienced woman's hair cascading between her thighs.",
56
+ "A woman in a blue hanbok sits on a wooden floor, her legs folded beneath her, gazing out of a window, the sunlight highlighting the graceful lines of her clothing.",
57
+ "The couple, immersed in a wooden outdoor bath, share an intimate moment, her wet kimono clinging to her curves, his hands exploring her body beneath the water's surface.",
58
+ "A steamy shower scene, the twins embrace under the warm water, their soapy hands gliding over each other's curves, their passion intensifying as they explore uncharted territories.",
59
+ "The teacher, with a firm grip, pins the student against the blackboard, her skirt hiked up, exposing her delicate lace panties. Their heavy breathing echoes in the quiet room as they share an intense, intimate moment.",
60
+ "After hours, the girl sits on top of the teacher's lap, riding him on the classroom floor, her hair cascading over her face as she moves with increasing intensity, their bodies glistening with sweat.",
61
+ "In the dimly lit dorm room, the roommates lay entangled in a passionate embrace, their naked bodies glistening with sweat, as the experienced woman teaches her lover the art of kissing and touching.",
62
+ "The once-innocent student, now confident, takes charge, straddling her lover on the couch, their bare skin illuminated by the warm glow of the sunset through the window.",
63
+ "A close-up of the secretary's hand unzipping her boss's dress shirt, her fingers gently caressing his chest, their eyes locked in a heated embrace in the supply closet.",
64
+ "The secretary, in a tight pencil skirt and silk blouse, leans back on the boss's desk, her legs wrapped around his waist, her blouse unbuttoned, revealing her lace bra, as he passionately kisses her, his hands exploring her body.",
65
+ "On the living room couch, one twin sits astride her sister's lap, their lips locked in a passionate kiss, their hands tangled in each other's hair, unraveling a new level of intimacy.",
66
+ "In a dimly lit chamber, the dominant woman, dressed in a leather corset and thigh-high boots, stands tall, her hand gripping her submissive partner's hair, his eyes closed in submission as she instructs him to please her.",
67
+ "The dominant, in a sheer lace bodysuit, sits on a throne-like chair, her legs spread, as the submissive, on his knees, worships her with his tongue, his hands bound behind his back.",
68
+ "A traditional Japanese onsen, with steam rising, a young woman in a colorful kimono kneels on a tatami mat, her back to the viewer, as her male partner, also in a kimono, gently unties her obi, revealing her bare back.",
69
+ "In a serene outdoor setting, the woman, in a vibrant summer kimono, sits on a bench, her legs slightly spread, her partner kneeling before her, his hands gently caressing her exposed thigh.",
70
+ ]
71
+
72
+ # ===== Save directory =====
73
  SAVE_DIR = "saved_images"
74
  if not os.path.exists(SAVE_DIR):
75
  os.makedirs(SAVE_DIR, exist_ok=True)
76
 
77
+ # ===== Device & model loading =====
78
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
79
  print(f"Using device: {device}")
80
 
 
115
  MAX_SEED = np.iinfo(np.int32).max
116
  MAX_IMAGE_SIZE = 1216
117
 
118
+ def prepare_prompt(user_prompt: str, style_key: str) -> str:
119
+ """Apply style preset to prompt."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  style_suffix = STYLE_PRESETS.get(style_key, "")
121
  if style_suffix:
122
+ final_prompt = f"{user_prompt}, {style_suffix}"
123
  else:
124
+ final_prompt = user_prompt
125
 
126
  return final_prompt
127
 
128
+ def get_random_prompt():
129
+ """Get a random prompt from the examples list."""
130
+ return random.choice(prompt_examples)
131
 
132
+ # ===== Image saving =====
133
  def save_generated_image(image: Image.Image, prompt: str) -> str:
134
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
135
  unique_id = str(uuid.uuid4())[:8]
 
137
  filepath = os.path.join(SAVE_DIR, filename)
138
  image.save(filepath)
139
 
140
+ # Save metadata
141
  metadata_file = os.path.join(SAVE_DIR, "metadata.txt")
142
  with open(metadata_file, "a", encoding="utf-8") as f:
143
  f.write(f"{filename}|{prompt}|{timestamp}\n")
 
153
  print(f"Long prompt processing failed: {e}, falling back to standard processing")
154
  return None, None
155
 
156
+ # ===== Diffusion call =====
 
157
  def run_pipeline(prompt: str, negative_prompt: str, seed: int, width: int, height: int, guidance_scale: float, num_steps: int):
158
  if pipeline is None:
159
  raise ValueError("Model pipeline not loaded")
 
201
  error_img = Image.new('RGB', (width, height), color=(0, 0, 0))
202
  return error_img
203
 
204
+ # ===== Gradio inference wrapper =====
 
205
  @spaces.GPU(duration=60)
206
  def generate_image(
207
  user_prompt: str,
208
  style_key: str,
 
209
  negative_prompt: str = "nsfw, (low quality, worst quality:1.2), very displeasing, 3d, watermark, signature, ugly, poorly drawn",
210
  seed: int = 42,
211
  randomize_seed: bool = True,
 
219
  if randomize_seed:
220
  seed = random.randint(0, MAX_SEED)
221
 
222
+ # Apply style preset
223
+ final_prompt = prepare_prompt(user_prompt, style_key)
224
  print(f"Final prompt: {final_prompt}")
225
 
226
+ # Generate image
227
  image = run_pipeline(final_prompt, negative_prompt, seed, width, height, guidance_scale, num_inference_steps)
228
 
229
+ # Save image
230
  save_generated_image(image, final_prompt)
231
 
232
  return image, seed
 
237
  error_image = Image.new('RGB', (width, height), color='red')
238
  return error_image, seed
239
 
240
+ # ===== Custom CSS (Pastel Gradient Design) =====
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  custom_css = """
242
  :root {
243
+ --color-primary: #A8E6CF;
244
+ --color-secondary: #FFD3A5;
245
+ --color-accent: #FD9BB8;
246
+ --color-purple: #C5A3F0;
247
+ --color-blue: #A8D8F0;
248
+ --color-warm-gray: #F8F9FA;
249
+ --color-dark-gray: #495057;
250
+ --background-primary: linear-gradient(135deg, #FFE5F1 0%, #E5F3FF 25%, #F0E5FF 50%, #E5FFE5 75%, #FFF5E5 100%);
251
+ --background-accent: linear-gradient(135deg, #FFD3E8 0%, #D3E8FF 50%, #E8D3FF 100%);
252
+ --text-primary: #2D3748;
253
+ --text-secondary: #718096;
254
+ --shadow-soft: 0 4px 20px rgba(168, 230, 207, 0.15);
255
+ --shadow-medium: 0 8px 30px rgba(168, 230, 207, 0.25);
256
+ --border-radius: 20px;
257
+ }
258
+
259
+ /* Global background */
260
  footer {visibility: hidden;}
261
  .gradio-container {
262
  background: var(--background-primary) !important;
263
  min-height: 100vh;
264
+ font-family: 'Inter', sans-serif;
265
  }
266
 
267
+ /* Title styles */
268
  .title {
269
  color: var(--text-primary) !important;
270
  font-size: 3rem !important;
271
  font-weight: 700 !important;
272
  text-align: center;
273
  margin: 2rem 0;
274
+ background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 50%, var(--color-purple) 100%);
275
  -webkit-background-clip: text;
276
  -webkit-text-fill-color: transparent;
277
  background-clip: text;
 
286
  font-weight: 400;
287
  }
288
 
289
+ /* Simple card styles */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  .model-description {
291
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(248, 187, 208, 0.1) 100%);
292
+ border: 1px solid rgba(168, 230, 207, 0.3);
293
  border-radius: var(--border-radius);
294
  padding: 2rem;
295
  margin: 1.5rem 0;
 
305
  margin: 0;
306
  }
307
 
308
+ /* Button styles */
309
  button.primary {
310
  background: var(--background-accent) !important;
311
+ color: var(--text-primary) !important;
312
+ border: 1px solid var(--color-primary) !important;
313
+ border-radius: 15px !important;
314
  box-shadow: var(--shadow-soft) !important;
315
+ transition: all 0.3s ease !important;
316
  font-weight: 600 !important;
317
  font-size: 0.95rem !important;
318
  }
319
 
320
  button.primary:hover {
321
+ background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-secondary) 100%) !important;
322
+ transform: translateY(-2px) !important;
323
+ box-shadow: var(--shadow-medium) !important;
324
+ }
325
+
326
+ /* Random button specific styles */
327
+ .random-btn {
328
+ background: linear-gradient(135deg, var(--color-accent) 0%, var(--color-purple) 100%) !important;
329
+ color: white !important;
330
+ border: none !important;
331
+ border-radius: 15px !important;
332
+ padding: 12px 24px !important;
333
+ font-weight: 600 !important;
334
+ box-shadow: var(--shadow-soft) !important;
335
+ transition: all 0.3s ease !important;
336
+ }
337
+
338
+ .random-btn:hover {
339
+ transform: translateY(-2px) !important;
340
  box-shadow: var(--shadow-medium) !important;
341
+ background: linear-gradient(135deg, var(--color-purple) 0%, var(--color-blue) 100%) !important;
342
  }
343
 
344
+ /* Input container */
345
  .input-container {
346
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(168, 230, 207, 0.1) 100%);
347
+ border: 1px solid rgba(168, 230, 207, 0.3);
348
  border-radius: var(--border-radius);
349
  padding: 1.5rem;
350
  margin-bottom: 1.5rem;
 
353
  -webkit-backdrop-filter: blur(10px);
354
  }
355
 
356
+ /* Advanced settings */
357
  .advanced-settings {
358
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.8) 0%, rgba(197, 163, 240, 0.1) 100%);
359
+ border: 1px solid rgba(168, 216, 240, 0.3);
360
  border-radius: var(--border-radius);
361
  padding: 1.5rem;
362
  margin-top: 1rem;
 
365
  -webkit-backdrop-filter: blur(8px);
366
  }
367
 
368
+ /* Style preset section */
369
+ .style-preset-section {
370
+ background: linear-gradient(135deg, rgba(255, 211, 232, 0.3) 0%, rgba(211, 232, 255, 0.3) 100%);
371
+ border: 1px solid rgba(168, 230, 207, 0.3);
372
  border-radius: var(--border-radius);
373
+ padding: 1.2rem;
374
  margin-top: 1rem;
375
  box-shadow: var(--shadow-soft);
376
  }
377
 
378
+ /* Prompt input styles */
379
  .large-prompt textarea {
380
  min-height: 120px !important;
381
  font-size: 15px !important;
382
  line-height: 1.5 !important;
383
  background: rgba(255, 255, 255, 0.9) !important;
384
+ border: 2px solid rgba(168, 230, 207, 0.4) !important;
385
+ border-radius: 15px !important;
386
  color: var(--text-primary) !important;
387
  transition: all 0.3s ease !important;
388
  padding: 1rem !important;
 
390
 
391
  .large-prompt textarea:focus {
392
  border-color: var(--color-primary) !important;
393
+ box-shadow: 0 0 0 3px rgba(168, 230, 207, 0.2) !important;
394
  outline: none !important;
395
  }
396
 
 
399
  font-style: italic;
400
  }
401
 
402
+ /* Generate button */
403
  .small-generate-btn {
404
  max-width: 140px !important;
405
  height: 48px !important;
406
  font-size: 15px !important;
407
  padding: 12px 24px !important;
408
+ border-radius: 15px !important;
409
  font-weight: 600 !important;
410
  }
411
 
412
+ /* Labels */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
  label {
414
  color: var(--text-primary) !important;
415
  font-weight: 600 !important;
416
  font-size: 0.95rem !important;
417
  }
418
 
419
+ /* Info text */
420
  .gr-info, .gr-textbox-info {
421
  color: var(--text-secondary) !important;
422
  font-size: 0.85rem !important;
423
  line-height: 1.4 !important;
424
  }
425
 
426
+ /* Form elements */
 
 
 
 
 
 
 
427
  input[type="radio"], input[type="checkbox"] {
428
  accent-color: var(--color-primary) !important;
429
  }
 
432
  accent-color: var(--color-primary) !important;
433
  }
434
 
435
+ /* Result image container */
436
  .image-container {
437
  border-radius: var(--border-radius) !important;
438
  overflow: hidden !important;
439
  box-shadow: var(--shadow-medium) !important;
440
  background: rgba(255, 255, 255, 0.9) !important;
441
+ border: 1px solid rgba(168, 230, 207, 0.2) !important;
442
  }
443
 
444
+ /* Slider containers */
445
  .gr-slider {
446
  margin: 0.5rem 0 !important;
447
  }
448
 
449
+ /* Accordion styles */
450
  .gr-accordion {
451
+ border: 1px solid rgba(168, 230, 207, 0.3) !important;
452
  border-radius: var(--border-radius) !important;
453
+ background: rgba(255, 255, 255, 0.8) !important;
454
  }
455
 
456
  .gr-accordion-header {
457
  background: var(--background-accent) !important;
458
+ color: var(--text-primary) !important;
459
  font-weight: 600 !important;
460
  border-radius: var(--border-radius) var(--border-radius) 0 0 !important;
461
  }
462
 
463
+ /* Smooth animations */
464
+ .model-description, .input-container, .style-preset-section {
465
  animation: fadeInUp 0.4s ease-out;
466
  }
467
 
 
476
  }
477
  }
478
 
479
+ /* Improved text readability */
480
  * {
481
  -webkit-font-smoothing: antialiased;
482
  -moz-osx-font-smoothing: grayscale;
483
  }
484
 
485
+ /* Dropdown and select styles */
486
  select, .gr-dropdown {
487
  background: rgba(255, 255, 255, 0.9) !important;
488
+ border: 1px solid rgba(168, 230, 207, 0.3) !important;
489
+ border-radius: 10px !important;
490
  color: var(--text-primary) !important;
491
  }
492
 
493
+ /* Checkbox and radio button improvements */
494
  .gr-checkbox, .gr-radio {
495
  background: transparent !important;
496
  }
497
 
498
+ /* Overall container margin adjustment */
499
  .gr-container {
500
  max-width: 1200px !important;
501
  margin: 0 auto !important;
502
  padding: 2rem 1rem !important;
503
  }
504
 
505
+ /* Mobile responsive */
506
  @media (max-width: 768px) {
507
  .title {
508
  font-size: 2.2rem !important;
509
  }
510
 
511
+ .model-description, .input-container, .advanced-settings {
512
  padding: 1rem !important;
513
  margin: 1rem 0 !important;
514
  }
 
526
  with gr.Group(elem_classes="model-description"):
527
  gr.HTML("""
528
  <p>
529
+ <strong>AI Image Generator</strong><br>
530
+ <small style="opacity: 0.8;">High-quality image generation powered by StableDiffusionXL. Supports long prompts and various artistic styles.</small><br><br>
531
  """)
532
 
533
+ # ===== Main input =====
534
  with gr.Column():
535
  with gr.Row(elem_classes="input-container"):
536
+ with gr.Column(scale=3):
537
  user_prompt = gr.Text(
538
+ label="Prompt",
539
  max_lines=5,
540
+ value="",
541
  elem_classes="large-prompt",
542
+ placeholder="Enter your image description here..."
543
  )
544
  with gr.Column(scale=1):
545
+ with gr.Row():
546
+ run_button = gr.Button(
547
+ "Generate",
548
+ variant="primary",
549
+ elem_classes="small-generate-btn"
550
+ )
551
+ with gr.Row():
552
+ random_button = gr.Button(
553
+ "๐ŸŽฒ Random",
554
+ elem_classes="random-btn"
555
+ )
 
 
556
 
557
+ # Style preset section
558
  with gr.Group(elem_classes="style-preset-section"):
559
  style_select = gr.Radio(
560
+ label="๐ŸŽจ Style Preset",
561
  choices=list(STYLE_PRESETS.keys()),
562
  value="None",
563
  interactive=True
564
  )
565
 
566
+ result_image = gr.Image(label="Generated Image")
567
+ seed_output = gr.Number(label="Seed")
568
 
569
+ # ===== Advanced settings =====
570
+ with gr.Accordion("Advanced Settings", open=False, elem_classes="advanced-settings"):
571
  negative_prompt = gr.Text(
572
+ label="Negative prompt",
573
  max_lines=1,
574
  placeholder="Enter a negative prompt",
575
  value="nsfw, (low quality, worst quality:1.2), very displeasing, 3d, watermark, signature, ugly, poorly drawn"
576
  )
577
 
578
+ seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=42)
579
+ randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
580
  with gr.Row():
581
+ width = gr.Slider(label="Width", minimum=256, maximum=MAX_IMAGE_SIZE, step=32, value=1024)
582
+ height = gr.Slider(label="Height", minimum=256, maximum=MAX_IMAGE_SIZE, step=32, value=1024)
583
  with gr.Row():
584
+ guidance_scale = gr.Slider(label="Guidance scale", minimum=0.0, maximum=20.0, step=0.1, value=7.0)
585
+ num_inference_steps = gr.Slider(label="Inference steps", minimum=1, maximum=50, step=1, value=28)
 
 
 
 
 
586
 
587
+ # ===== Events =====
588
  run_button.click(
589
  fn=generate_image,
590
  inputs=[
591
  user_prompt,
592
  style_select,
 
593
  negative_prompt,
594
  seed,
595
  randomize_seed,
 
600
  ],
601
  outputs=[result_image, seed_output],
602
  )
603
+
604
+ # Random button event
605
+ random_button.click(
606
+ fn=get_random_prompt,
607
+ outputs=user_prompt
608
+ )
609
 
610
  return demo
611
 
612
+ # ===== Application execution =====
613
  if __name__ == "__main__":
614
  demo = create_interface()
615
  demo.queue()