Spaces:
Running
on
Zero
Running
on
Zero
Update app.py
Browse files
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 |
-
# =====
|
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 |
-
|
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"{
|
188 |
else:
|
189 |
-
final_prompt =
|
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 |
-
#
|
288 |
-
final_prompt = prepare_prompt(user_prompt, style_key
|
289 |
print(f"Final prompt: {final_prompt}")
|
290 |
|
291 |
-
#
|
292 |
image = run_pipeline(final_prompt, negative_prompt, seed, width, height, guidance_scale, num_inference_steps)
|
293 |
|
294 |
-
#
|
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: #
|
324 |
-
--color-secondary: #
|
325 |
-
--color-accent: #
|
326 |
-
--color-
|
327 |
-
--color-
|
328 |
-
--color-warm-gray: #
|
329 |
-
--color-dark-gray: #
|
330 |
-
--background-primary: linear-gradient(135deg, #
|
331 |
-
--background-accent: linear-gradient(135deg, #
|
332 |
-
--text-primary: #
|
333 |
-
--text-secondary: #
|
334 |
-
--shadow-soft: 0 4px 20px rgba(
|
335 |
-
--shadow-medium: 0 8px 30px rgba(
|
336 |
-
--border-radius:
|
337 |
-
}
|
338 |
-
|
339 |
-
/*
|
340 |
footer {visibility: hidden;}
|
341 |
.gradio-container {
|
342 |
background: var(--background-primary) !important;
|
343 |
min-height: 100vh;
|
344 |
-
font-family: 'Inter',
|
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-
|
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 |
-
|
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(
|
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(--
|
411 |
-
border: 1px solid var(--color-
|
412 |
-
border-radius:
|
413 |
box-shadow: var(--shadow-soft) !important;
|
414 |
-
transition: all 0.
|
415 |
font-weight: 600 !important;
|
416 |
font-size: 0.95rem !important;
|
417 |
}
|
418 |
|
419 |
button.primary:hover {
|
420 |
-
background: linear-gradient(135deg, var(--color-
|
421 |
-
transform: translateY(-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
422 |
box-shadow: var(--shadow-medium) !important;
|
|
|
423 |
}
|
424 |
|
425 |
-
/*
|
426 |
.input-container {
|
427 |
-
background: rgba(255, 255, 255, 0.
|
428 |
-
border: 1px solid rgba(
|
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.
|
440 |
-
border: 1px solid rgba(
|
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 |
-
.
|
451 |
-
background: rgba(
|
452 |
-
border: 1px solid rgba(
|
453 |
border-radius: var(--border-radius);
|
454 |
-
padding: 1.
|
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(
|
466 |
-
border-radius:
|
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(
|
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:
|
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(
|
550 |
}
|
551 |
|
552 |
-
/*
|
553 |
.gr-slider {
|
554 |
margin: 0.5rem 0 !important;
|
555 |
}
|
556 |
|
557 |
-
/*
|
558 |
.gr-accordion {
|
559 |
-
border: 1px solid rgba(
|
560 |
border-radius: var(--border-radius) !important;
|
561 |
-
background: rgba(255, 255, 255, 0.
|
562 |
}
|
563 |
|
564 |
.gr-accordion-header {
|
565 |
background: var(--background-accent) !important;
|
566 |
-
color: var(--
|
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, .
|
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(
|
597 |
-
border-radius:
|
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
|
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
|
638 |
-
<small style="opacity: 0.8;">
|
639 |
""")
|
640 |
|
641 |
-
# =====
|
642 |
with gr.Column():
|
643 |
with gr.Row(elem_classes="input-container"):
|
644 |
-
with gr.Column(scale=
|
645 |
user_prompt = gr.Text(
|
646 |
-
label="Prompt
|
647 |
max_lines=5,
|
648 |
-
value=
|
649 |
elem_classes="large-prompt",
|
650 |
-
placeholder="Enter your image description here...
|
651 |
)
|
652 |
with gr.Column(scale=1):
|
653 |
-
|
654 |
-
|
655 |
-
|
656 |
-
|
657 |
-
|
658 |
-
|
659 |
-
|
660 |
-
|
661 |
-
|
662 |
-
|
663 |
-
|
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
|
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
|
689 |
-
randomize_seed = gr.Checkbox(label="Randomize seed
|
690 |
with gr.Row():
|
691 |
-
width = gr.Slider(label="Width
|
692 |
-
height = gr.Slider(label="Height
|
693 |
with gr.Row():
|
694 |
-
guidance_scale = gr.Slider(label="Guidance scale
|
695 |
-
num_inference_steps = gr.Slider(label="Inference steps
|
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()
|