seawolf2357 commited on
Commit
cbdb263
ยท
verified ยท
1 Parent(s): 51ab74c

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +676 -0
app.py ADDED
@@ -0,0 +1,676 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ===== CRITICAL: Import spaces FIRST before any CUDA operations =====
2
+ try:
3
+ import spaces
4
+ HF_SPACES = True
5
+ except ImportError:
6
+ # If running locally, create a dummy decorator
7
+ def spaces_gpu_decorator(duration=60):
8
+ def decorator(func):
9
+ return func
10
+ return decorator
11
+ spaces = type('spaces', (), {'GPU': spaces_gpu_decorator})()
12
+ HF_SPACES = False
13
+ print("Warning: Running without Hugging Face Spaces GPU allocation")
14
+
15
+ # ===== Now import other libraries =====
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 DiffusionPipeline
28
+ from PIL import Image
29
+
30
+ # ===== OpenAI ์„ค์ • =====
31
+ from openai import OpenAI
32
+
33
+ # Add error handling for API key
34
+ try:
35
+ client = OpenAI(api_key=os.getenv("LLM_API"))
36
+ except Exception as e:
37
+ print(f"Warning: OpenAI client initialization failed: {e}")
38
+ client = None
39
+
40
+ # ===== ํ”„๋กฌํ”„ํŠธ ์ฆ๊ฐ•์šฉ ์Šคํƒ€์ผ ํ”„๋ฆฌ์…‹ =====
41
+ STYLE_PRESETS = {
42
+ "None": "",
43
+ "Realistic Photo": "photorealistic, 8k, ultra-detailed, cinematic lighting, realistic skin texture",
44
+ "Oil Painting": "oil painting, rich brush strokes, canvas texture, baroque lighting",
45
+ "Comic Book": "comic book style, bold ink outlines, cel shading, vibrant colors",
46
+ "Watercolor": "watercolor illustration, soft gradients, splatter effect, pastel palette",
47
+ }
48
+
49
+ # ===== ์ €์žฅ ํด๋” =====
50
+ SAVE_DIR = "saved_images"
51
+ if not os.path.exists(SAVE_DIR):
52
+ os.makedirs(SAVE_DIR, exist_ok=True)
53
+
54
+ # ===== ๋””๋ฐ”์ด์Šค & ๋ชจ๋ธ ๋กœ๋“œ =====
55
+ device = "cuda" if torch.cuda.is_available() else "cpu"
56
+ print(f"Using device: {device}")
57
+
58
+ repo_id = "black-forest-labs/FLUX.1-dev"
59
+ adapter_id = "seawolf2357/kim-korea"
60
+
61
+ # Add error handling for model loading
62
+ try:
63
+ pipeline = DiffusionPipeline.from_pretrained(repo_id, torch_dtype=torch.bfloat16)
64
+ pipeline.load_lora_weights(adapter_id)
65
+ pipeline = pipeline.to(device)
66
+ print("Model loaded successfully")
67
+ except Exception as e:
68
+ print(f"Error loading model: {e}")
69
+ pipeline = None
70
+
71
+ MAX_SEED = np.iinfo(np.int32).max
72
+ MAX_IMAGE_SIZE = 1024
73
+
74
+ # ===== ํ•œ๊ธ€ ์—ฌ๋ถ€ ํŒ๋ณ„ =====
75
+ HANGUL_RE = re.compile(r"[\u3131-\u318E\uAC00-\uD7A3]+")
76
+
77
+ def is_korean(text: str) -> bool:
78
+ return bool(HANGUL_RE.search(text))
79
+
80
+ # ===== ๋ฒˆ์—ญ & ์ฆ๊ฐ• ํ•จ์ˆ˜ =====
81
+
82
+ def openai_translate(text: str, retries: int = 3) -> str:
83
+ """ํ•œ๊ธ€์„ ์˜์–ด๋กœ ๋ฒˆ์—ญ (OpenAI GPT-4o-mini ์‚ฌ์šฉ). ์˜์–ด ์ž…๋ ฅ์ด๋ฉด ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜."""
84
+ if not is_korean(text):
85
+ return text
86
+
87
+ if client is None:
88
+ print("Warning: OpenAI client not available, returning original text")
89
+ return text
90
+
91
+ for attempt in range(retries):
92
+ try:
93
+ res = client.chat.completions.create(
94
+ model="gpt-4o-mini",
95
+ messages=[
96
+ {
97
+ "role": "system",
98
+ "content": "Translate the following Korean prompt into concise, descriptive English suitable for an image generation model. Keep the meaning, do not add new concepts."
99
+ },
100
+ {"role": "user", "content": text}
101
+ ],
102
+ temperature=0.3,
103
+ max_tokens=256,
104
+ )
105
+ return res.choices[0].message.content.strip()
106
+ except Exception as e:
107
+ print(f"[translate] attempt {attempt + 1} failed: {e}")
108
+ time.sleep(2)
109
+ return text # ๋ฒˆ์—ญ ์‹คํŒจ ์‹œ ์›๋ฌธ ๊ทธ๋Œ€๋กœ
110
+
111
+ def enhance_prompt(text: str, retries: int = 3) -> str:
112
+ """OpenAI๋ฅผ ํ†ตํ•ด ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ฆ๊ฐ•ํ•˜์—ฌ ๊ณ ํ’ˆ์งˆ ์ด๋ฏธ์ง€ ์ƒ์„ฑ์„ ์œ„ํ•œ ์ƒ์„ธํ•œ ์„ค๋ช…์œผ๋กœ ๋ณ€ํ™˜."""
113
+ if client is None:
114
+ print("Warning: OpenAI client not available, returning original text")
115
+ return text
116
+
117
+ for attempt in range(retries):
118
+ try:
119
+ res = client.chat.completions.create(
120
+ model="gpt-4o-mini",
121
+ messages=[
122
+ {
123
+ "role": "system",
124
+ "content": """You are an expert prompt engineer for image generation models. Enhance the given prompt to create high-quality, detailed images.
125
+
126
+ Guidelines:
127
+ - Add specific visual details (lighting, composition, colors, textures)
128
+ - Include technical photography terms (depth of field, focal length, etc.)
129
+ - Add atmosphere and mood descriptors
130
+ - Specify image quality terms (4K, ultra-detailed, professional, etc.)
131
+ - Keep the core subject and meaning intact
132
+ - Make it comprehensive but not overly long
133
+ - Focus on visual elements that will improve image generation quality
134
+
135
+ Example:
136
+ Input: "A man giving a speech"
137
+ 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"
138
+ """
139
+ },
140
+ {"role": "user", "content": f"Enhance this prompt for high-quality image generation: {text}"}
141
+ ],
142
+ temperature=0.7,
143
+ max_tokens=512,
144
+ )
145
+ return res.choices[0].message.content.strip()
146
+ except Exception as e:
147
+ print(f"[enhance] attempt {attempt + 1} failed: {e}")
148
+ time.sleep(2)
149
+ return text # ์ฆ๊ฐ• ์‹คํŒจ ์‹œ ์›๋ฌธ ๊ทธ๋Œ€๋กœ
150
+
151
+ def prepare_prompt(user_prompt: str, style_key: str, enhance_prompt_enabled: bool = False) -> str:
152
+ """ํ•œ๊ธ€์ด๋ฉด ๋ฒˆ์—ญํ•˜๊ณ , ํ”„๋กฌํ”„ํŠธ ์ฆ๊ฐ• ์˜ต์…˜์ด ํ™œ์„ฑํ™”๋˜๋ฉด ์ฆ๊ฐ•ํ•˜๊ณ , ์„ ํƒํ•œ ์Šคํƒ€์ผ ํ”„๋ฆฌ์…‹์„ ๋ถ™์—ฌ์„œ ์ตœ์ข… ํ”„๋กฌํ”„ํŠธ๋ฅผ ๋งŒ๋“ ๋‹ค."""
153
+ # 1. ๋ฒˆ์—ญ (ํ•œ๊ธ€์ธ ๊ฒฝ์šฐ)
154
+ prompt_en = openai_translate(user_prompt)
155
+
156
+ # 2. ํ”„๋กฌํ”„ํŠธ ์ฆ๊ฐ• (ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ)
157
+ if enhance_prompt_enabled:
158
+ prompt_en = enhance_prompt(prompt_en)
159
+ print(f"Enhanced prompt: {prompt_en}")
160
+
161
+ # 3. ์Šคํƒ€์ผ ํ”„๋ฆฌ์…‹ ์ ์šฉ
162
+ style_suffix = STYLE_PRESETS.get(style_key, "")
163
+ if style_suffix:
164
+ final_prompt = f"{prompt_en}, {style_suffix}"
165
+ else:
166
+ final_prompt = prompt_en
167
+
168
+ return final_prompt
169
+
170
+ # ===== ์ด๋ฏธ์ง€ ์ €์žฅ =====
171
+
172
+ def save_generated_image(image: Image.Image, prompt: str) -> str:
173
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
174
+ unique_id = str(uuid.uuid4())[:8]
175
+ filename = f"{timestamp}_{unique_id}.png"
176
+ filepath = os.path.join(SAVE_DIR, filename)
177
+ image.save(filepath)
178
+
179
+ # ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ €์žฅ
180
+ metadata_file = os.path.join(SAVE_DIR, "metadata.txt")
181
+ with open(metadata_file, "a", encoding="utf-8") as f:
182
+ f.write(f"{filename}|{prompt}|{timestamp}\n")
183
+ return filepath
184
+
185
+ # ===== Diffusion ํ˜ธ์ถœ =====
186
+
187
+ def run_pipeline(prompt: str, seed: int, width: int, height: int, guidance_scale: float, num_steps: int, lora_scale: float):
188
+ if pipeline is None:
189
+ raise ValueError("Model pipeline not loaded")
190
+
191
+ generator = torch.Generator(device=device).manual_seed(int(seed))
192
+ result = pipeline(
193
+ prompt=prompt,
194
+ guidance_scale=guidance_scale,
195
+ num_inference_steps=num_steps,
196
+ width=width,
197
+ height=height,
198
+ generator=generator,
199
+ joint_attention_kwargs={"scale": lora_scale},
200
+ ).images[0]
201
+ return result
202
+
203
+ # ===== Gradio inference ๋ž˜ํผ =====
204
+
205
+ @spaces.GPU(duration=60)
206
+ def generate_image(
207
+ user_prompt: str,
208
+ style_key: str,
209
+ enhance_prompt_enabled: bool = False,
210
+ seed: int = 42,
211
+ randomize_seed: bool = True,
212
+ width: int = 1024,
213
+ height: int = 768,
214
+ guidance_scale: float = 3.5,
215
+ num_inference_steps: int = 30,
216
+ lora_scale: float = 1.0,
217
+ progress=None,
218
+ ):
219
+ try:
220
+ if randomize_seed:
221
+ seed = random.randint(0, MAX_SEED)
222
+
223
+ # 1) ๋ฒˆ์—ญ + ์ฆ๊ฐ•
224
+ final_prompt = prepare_prompt(user_prompt, style_key, enhance_prompt_enabled)
225
+ print(f"Final prompt: {final_prompt}")
226
+
227
+ # 2) ํŒŒ์ดํ”„๋ผ์ธ ํ˜ธ์ถœ
228
+ image = run_pipeline(final_prompt, seed, width, height, guidance_scale, num_inference_steps, lora_scale)
229
+
230
+ # 3) ์ €์žฅ
231
+ save_generated_image(image, final_prompt)
232
+
233
+ return image, seed
234
+
235
+ except Exception as e:
236
+ print(f"Error generating image: {e}")
237
+ # Return a placeholder or error message
238
+ error_image = Image.new('RGB', (width, height), color='red')
239
+ return error_image, seed
240
+
241
+ # ===== ์˜ˆ์‹œ ํ”„๋กฌํ”„ํŠธ (ํ•œ๊ตญ์–ด/์˜์–ด ํ˜ผ์šฉ ํ—ˆ์šฉ) =====
242
+
243
+ examples = [
244
+ "Mr. KIM์ด ๋‘ ์†์œผ๋กœ 'Fighting!' ํ˜„์ˆ˜๋ง‰์„ ๋“ค๊ณ  ์žˆ๋Š” ๋ชจ์Šต, ์• ๊ตญ์‹ฌ๊ณผ ๊ตญ๊ฐ€ ๋ฐœ์ „์— ๋Œ€ํ•œ ์˜์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ๋‹ค.",
245
+ "Mr. KIM์ด ์–‘ํŒ”์„ ๋“ค์–ด ์˜ฌ๋ฆฌ๋ฉฐ ์Šน๋ฆฌ์˜ ํ‘œ์ •์œผ๋กœ ํ™˜ํ˜ธํ•˜๋Š” ๋ชจ์Šต, ์Šน๋ฆฌ์™€ ๋ฏธ๋ž˜์— ๋Œ€ํ•œ ํฌ๋ง์„ ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ๋‹ค.",
246
+ "Mr. KIM์ด ์šด๋™๋ณต์„ ์ž…๊ณ  ๊ณต์›์—์„œ ์กฐ๊น…ํ•˜๋Š” ๋ชจ์Šต, ๊ฑด๊ฐ•ํ•œ ์ƒํ™œ์Šต๊ด€๊ณผ ํ™œ๊ธฐ์ฐฌ ๋ฆฌ๋”์‹ญ์„ ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ๋‹ค.",
247
+ "Mr. KIM์ด ๋ถ๋น„๋Š” ๊ฑฐ๋ฆฌ์—์„œ ์—ฌ์„ฑ ์‹œ๋ฏผ๋“ค๊ณผ ๋”ฐ๋œปํ•˜๊ฒŒ ์•…์ˆ˜ํ•˜๋Š” ๋ชจ์Šต, ์—ฌ์„ฑ ์œ ๊ถŒ์ž๋“ค์— ๋Œ€ํ•œ ์ง„์ •ํ•œ ๊ด€์‹ฌ๊ณผ ์†Œํ†ต์„ ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ๋‹ค.",
248
+ "Mr. KIM์ด ์„ ๊ฑฐ ์œ ์„ธ์žฅ์—์„œ ์ง€ํ‰์„ ์„ ํ–ฅํ•ด ์†๊ฐ€๋ฝ์œผ๋กœ ๊ฐ€๋ฆฌํ‚ค๋ฉฐ ์˜๊ฐ์„ ์ฃผ๋Š” ์ œ์Šค์ฒ˜๋ฅผ ์ทจํ•˜๊ณ  ์žˆ๊ณ , ์—ฌ์„ฑ๋“ค๊ณผ ์•„์ด๋“ค์ด ๋ฐ•์ˆ˜๋ฅผ ์น˜๊ณ  ์žˆ๋‹ค.",
249
+ "Mr. KIM์ด ์ง€์—ญ ํ–‰์‚ฌ์— ์ฐธ์—ฌํ•˜์—ฌ ์—ด์ •์ ์œผ๋กœ ์‘์›ํ•˜๋Š” ์—ฌ์„ฑ ์ง€์ง€์ž๋“ค์—๊ฒŒ ๋‘˜๋Ÿฌ์‹ธ์—ฌ ์žˆ๋Š” ๋ชจ์Šต.",
250
+ "Mr. KIM visiting a local market, engaging in friendly conversation with female vendors and shopkeepers.",
251
+ "Mr. KIM walking through a university campus, discussing education policies with female students and professors.",
252
+ "Mr. KIM delivering a powerful speech in front of a large crowd with confident gestures and determined expression.",
253
+ "Mr. KIM in a dynamic interview setting, passionately outlining his visions for the future.",
254
+ "Mr. KIM preparing for an important debate, surrounded by paperwork, looking focused and resolute.",
255
+ ]
256
+
257
+ # ===== ์ปค์Šคํ…€ CSS (์ง„ํ•œ ๋ถ‰์€์ƒ‰ ๊ณ ๊ธ‰ ๋””์ž์ธ) =====
258
+ custom_css = """
259
+ :root {
260
+ --color-primary: #8F1A3A;
261
+ --color-secondary: #FF4B4B;
262
+ --color-accent: #B91C3C;
263
+ --color-dark-red: #7F1D1D;
264
+ --color-gold: #D4AF37;
265
+ --background-fill-primary: linear-gradient(135deg, #450A0A 0%, #7F1D1D 25%, #991B1B 50%, #B91C3C 75%, #DC2626 100%);
266
+ --glass-bg: rgba(255, 255, 255, 0.1);
267
+ --glass-border: rgba(255, 255, 255, 0.2);
268
+ --shadow-primary: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
269
+ --shadow-secondary: 0 10px 25px -3px rgba(0, 0, 0, 0.3);
270
+ --shadow-accent: 0 4px 15px rgba(185, 28, 60, 0.4);
271
+ }
272
+
273
+ /* ์ „์ฒด ๋ฐฐ๊ฒฝ */
274
+ footer {visibility: hidden;}
275
+ .gradio-container {
276
+ background: var(--background-fill-primary) !important;
277
+ min-height: 100vh;
278
+ position: relative;
279
+ }
280
+
281
+ /* ๋ฐฐ๊ฒฝ์— ํŒจํ„ด ์ถ”๊ฐ€ */
282
+ .gradio-container::before {
283
+ content: '';
284
+ position: absolute;
285
+ top: 0;
286
+ left: 0;
287
+ right: 0;
288
+ bottom: 0;
289
+ background-image:
290
+ radial-gradient(circle at 25% 25%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
291
+ radial-gradient(circle at 75% 75%, rgba(212, 175, 55, 0.1) 0%, transparent 50%);
292
+ pointer-events: none;
293
+ z-index: 1;
294
+ }
295
+
296
+ /* ๋ฉ”์ธ ์ฝ˜ํ…์ธ  z-index */
297
+ .gradio-container > * {
298
+ position: relative;
299
+ z-index: 2;
300
+ }
301
+
302
+ /* ํƒ€์ดํ‹€ ์Šคํƒ€์ผ */
303
+ .title {
304
+ color: #FFFFFF !important;
305
+ font-size: 3.5rem !important;
306
+ font-weight: 800 !important;
307
+ text-align: center;
308
+ margin: 2rem 0;
309
+ font-family: 'Playfair Display', serif;
310
+ text-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
311
+ background: linear-gradient(135deg, #FFFFFF 0%, var(--color-gold) 50%, #FFFFFF 100%);
312
+ -webkit-background-clip: text;
313
+ -webkit-text-fill-color: transparent;
314
+ background-clip: text;
315
+ }
316
+
317
+ .subtitle {
318
+ color: #F3F4F6 !important;
319
+ font-size: 1.3rem !important;
320
+ text-align: center;
321
+ margin-bottom: 2rem;
322
+ font-style: italic;
323
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
324
+ }
325
+
326
+ .collection-link {
327
+ text-align: center;
328
+ margin-bottom: 2rem;
329
+ font-size: 1.1rem;
330
+ }
331
+
332
+ .collection-link a {
333
+ color: var(--color-gold);
334
+ text-decoration: none;
335
+ transition: all 0.3s ease;
336
+ font-weight: 600;
337
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
338
+ }
339
+
340
+ .collection-link a:hover {
341
+ color: #F59E0B;
342
+ text-shadow: 0 4px 8px rgba(245, 158, 11, 0.5);
343
+ }
344
+
345
+ /* ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด 3D ๋ฐ•์Šค ์Šคํƒ€์ผ */
346
+ .model-description {
347
+ background: linear-gradient(135deg, var(--glass-bg) 0%, rgba(255, 255, 255, 0.05) 100%);
348
+ backdrop-filter: blur(20px);
349
+ -webkit-backdrop-filter: blur(20px);
350
+ border: 1px solid var(--glass-border);
351
+ border-radius: 20px;
352
+ padding: 30px;
353
+ margin: 25px 0;
354
+ box-shadow:
355
+ var(--shadow-primary),
356
+ inset 0 1px 0 rgba(255, 255, 255, 0.2),
357
+ 0 0 30px rgba(185, 28, 60, 0.2);
358
+ position: relative;
359
+ overflow: hidden;
360
+ transform: translateZ(0);
361
+ }
362
+
363
+ .model-description::before {
364
+ content: '';
365
+ position: absolute;
366
+ top: 0;
367
+ left: 0;
368
+ right: 0;
369
+ height: 1px;
370
+ background: linear-gradient(90deg, transparent, var(--color-gold), transparent);
371
+ }
372
+
373
+ .model-description p {
374
+ color: #F9FAFB !important;
375
+ font-size: 1.1rem;
376
+ line-height: 1.6;
377
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
378
+ }
379
+
380
+ /* ๋ฒ„ํŠผ ์Šคํƒ€์ผ */
381
+ button.primary {
382
+ background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%) !important;
383
+ color: #FFFFFF !important;
384
+ border: none !important;
385
+ box-shadow:
386
+ var(--shadow-accent),
387
+ inset 0 1px 0 rgba(255, 255, 255, 0.2) !important;
388
+ transition: all 0.3s ease !important;
389
+ font-weight: 600 !important;
390
+ text-transform: uppercase !important;
391
+ letter-spacing: 0.5px !important;
392
+ }
393
+
394
+ button.primary:hover {
395
+ transform: translateY(-3px) !important;
396
+ box-shadow:
397
+ 0 20px 40px rgba(185, 28, 60, 0.4),
398
+ inset 0 1px 0 rgba(255, 255, 255, 0.2) !important;
399
+ background: linear-gradient(135deg, var(--color-accent) 0%, #DC2626 100%) !important;
400
+ }
401
+
402
+ /* ์ž…๋ ฅ ์ปจํ…Œ์ด๋„ˆ */
403
+ .input-container {
404
+ background: linear-gradient(135deg, var(--glass-bg) 0%, rgba(255, 255, 255, 0.08) 100%);
405
+ backdrop-filter: blur(25px);
406
+ -webkit-backdrop-filter: blur(25px);
407
+ border: 1px solid var(--glass-border);
408
+ border-radius: 20px;
409
+ padding: 25px;
410
+ margin-bottom: 2rem;
411
+ box-shadow: var(--shadow-secondary);
412
+ position: relative;
413
+ overflow: hidden;
414
+ }
415
+
416
+ .input-container::before {
417
+ content: '';
418
+ position: absolute;
419
+ top: 0;
420
+ left: 0;
421
+ right: 0;
422
+ height: 2px;
423
+ background: linear-gradient(90deg, var(--color-primary), var(--color-gold), var(--color-primary));
424
+ }
425
+
426
+ /* ๊ณ ๊ธ‰ ์„ค์ • */
427
+ .advanced-settings {
428
+ background: linear-gradient(135deg, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.1) 100%);
429
+ backdrop-filter: blur(15px);
430
+ -webkit-backdrop-filter: blur(15px);
431
+ border: 1px solid rgba(255, 255, 255, 0.1);
432
+ border-radius: 16px;
433
+ padding: 20px;
434
+ margin-top: 1.5rem;
435
+ box-shadow: var(--shadow-secondary);
436
+ }
437
+
438
+ /* ์˜ˆ์‹œ ์˜์—ญ */
439
+ .example-region {
440
+ background: linear-gradient(135deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.05) 100%);
441
+ backdrop-filter: blur(20px);
442
+ -webkit-backdrop-filter: blur(20px);
443
+ border: 1px solid rgba(255, 255, 255, 0.15);
444
+ border-radius: 16px;
445
+ padding: 20px;
446
+ margin-top: 1.5rem;
447
+ box-shadow: var(--shadow-secondary);
448
+ }
449
+
450
+ /* ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ์นธ ํฌ๊ธฐ 2๋ฐฐ ์ฆ๊ฐ€ + ์Šคํƒ€์ผ */
451
+ .large-prompt textarea {
452
+ min-height: 120px !important;
453
+ font-size: 16px !important;
454
+ line-height: 1.5 !important;
455
+ background: rgba(0, 0, 0, 0.3) !important;
456
+ border: 2px solid rgba(255, 255, 255, 0.2) !important;
457
+ border-radius: 12px !important;
458
+ color: #FFFFFF !important;
459
+ backdrop-filter: blur(10px) !important;
460
+ transition: all 0.3s ease !important;
461
+ }
462
+
463
+ .large-prompt textarea:focus {
464
+ border-color: var(--color-gold) !important;
465
+ box-shadow: 0 0 20px rgba(212, 175, 55, 0.3) !important;
466
+ }
467
+
468
+ .large-prompt textarea::placeholder {
469
+ color: rgba(255, 255, 255, 0.6) !important;
470
+ }
471
+
472
+ /* ์ƒ์„ฑ ๋ฒ„ํŠผ ์ž‘๊ฒŒ ๋งŒ๋“ค๊ธฐ + 3D ํšจ๊ณผ */
473
+ .small-generate-btn {
474
+ max-width: 120px !important;
475
+ height: 45px !important;
476
+ font-size: 14px !important;
477
+ padding: 10px 20px !important;
478
+ border-radius: 12px !important;
479
+ position: relative !important;
480
+ overflow: hidden !important;
481
+ }
482
+
483
+ .small-generate-btn::before {
484
+ content: '';
485
+ position: absolute;
486
+ top: 0;
487
+ left: -100%;
488
+ width: 100%;
489
+ height: 100%;
490
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
491
+ transition: left 0.5s ease;
492
+ }
493
+
494
+ .small-generate-btn:hover::before {
495
+ left: 100%;
496
+ }
497
+
498
+ /* ํ”„๋กฌํ”„ํŠธ ์ฆ๊ฐ• ์„น์…˜ ์Šคํƒ€์ผ */
499
+ .prompt-enhance-section {
500
+ background: linear-gradient(135deg, rgba(212, 175, 55, 0.15) 0%, rgba(212, 175, 55, 0.05) 100%);
501
+ backdrop-filter: blur(15px);
502
+ -webkit-backdrop-filter: blur(15px);
503
+ border: 1px solid rgba(212, 175, 55, 0.3);
504
+ border-radius: 15px;
505
+ padding: 20px;
506
+ margin-top: 15px;
507
+ box-shadow:
508
+ 0 8px 25px rgba(0, 0, 0, 0.2),
509
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
510
+ position: relative;
511
+ }
512
+
513
+ .prompt-enhance-section::before {
514
+ content: '';
515
+ position: absolute;
516
+ top: 0;
517
+ left: 20px;
518
+ right: 20px;
519
+ height: 1px;
520
+ background: linear-gradient(90deg, transparent, var(--color-gold), transparent);
521
+ }
522
+
523
+ /* ์Šคํƒ€์ผ ํ”„๋ฆฌ์…‹ ์„น์…˜ */
524
+ .style-preset-section {
525
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
526
+ backdrop-filter: blur(15px);
527
+ -webkit-backdrop-filter: blur(15px);
528
+ border: 1px solid rgba(255, 255, 255, 0.2);
529
+ border-radius: 15px;
530
+ padding: 20px;
531
+ margin-top: 15px;
532
+ box-shadow: var(--shadow-secondary);
533
+ }
534
+
535
+ /* ๋ผ๋ฒจ ํ…์ŠคํŠธ ์ƒ‰์ƒ */
536
+ label {
537
+ color: #F9FAFB !important;
538
+ font-weight: 600 !important;
539
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5) !important;
540
+ }
541
+
542
+ /* ๋ผ๋””์˜ค ๋ฒ„ํŠผ ๋ฐ ์ฒดํฌ๋ฐ•์Šค ์Šคํƒ€์ผ */
543
+ input[type="radio"], input[type="checkbox"] {
544
+ accent-color: var(--color-gold) !important;
545
+ }
546
+
547
+ /* ์Šฌ๋ผ์ด๋” ์Šคํƒ€์ผ */
548
+ input[type="range"] {
549
+ accent-color: var(--color-gold) !important;
550
+ }
551
+
552
+ /* ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ */
553
+ .image-container {
554
+ border-radius: 16px !important;
555
+ overflow: hidden !important;
556
+ box-shadow: var(--shadow-primary) !important;
557
+ background: rgba(0, 0, 0, 0.3) !important;
558
+ backdrop-filter: blur(10px) !important;
559
+ }
560
+
561
+ /* ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ถ”๊ฐ€ */
562
+ @keyframes shimmer {
563
+ 0% { transform: translateX(-100%); }
564
+ 100% { transform: translateX(100%); }
565
+ }
566
+
567
+ .model-description, .input-container, .prompt-enhance-section, .style-preset-section {
568
+ animation: fadeInUp 0.6s ease-out;
569
+ }
570
+
571
+ @keyframes fadeInUp {
572
+ from {
573
+ opacity: 0;
574
+ transform: translateY(30px);
575
+ }
576
+ to {
577
+ opacity: 1;
578
+ transform: translateY(0);
579
+ }
580
+ }
581
+ """
582
+
583
+ # ===== Gradio UI =====
584
+ def create_interface():
585
+ with gr.Blocks(css=custom_css, analytics_enabled=False) as demo:
586
+ gr.HTML('<div class="title">Mr. KIM in KOREA</div>')
587
+ gr.HTML('<div class="collection-link"><a href="https://huggingface.co/collections/openfree/painting-art-ai-681453484ec15ef5978bbeb1" target="_blank">Visit the LoRA Model Collection</a></div>')
588
+
589
+ with gr.Group(elem_classes="model-description"):
590
+ gr.HTML("""
591
+ <p>
592
+ ๋ณธ ๋ชจ๋ธ์€ ์—ฐ๊ตฌ ๋ชฉ์ ์œผ๋กœ ํŠน์ •์ธ์˜ ์–ผ๊ตด๊ณผ ์™ธ๋ชจ๋ฅผ ํ•™์Šตํ•œ LoRA ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค.<br>
593
+ ๋ชฉ์ ์™ธ์˜ ์šฉ๋„๋กœ ๋ฌด๋‹จ ์‚ฌ์šฉ ์•Š๋„๋ก ์œ ์˜ํ•ด ์ฃผ์„ธ์š”.<br>
594
+ (์˜ˆ์‹œ prompt ์‚ฌ์šฉ ์‹œ ๋ฐ˜๋“œ์‹œ 'kim'์„ ํฌํ•จํ•˜์—ฌ์•ผ ์ตœ์ ์˜ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)
595
+ </p>
596
+ """)
597
+
598
+ # ===== ๋ฉ”์ธ ์ž…๋ ฅ =====
599
+ with gr.Column():
600
+ with gr.Row(elem_classes="input-container"):
601
+ with gr.Column(scale=4):
602
+ user_prompt = gr.Text(
603
+ label="Prompt",
604
+ max_lines=5,
605
+ value=examples[0],
606
+ elem_classes="large-prompt"
607
+ )
608
+ with gr.Column(scale=1):
609
+ run_button = gr.Button(
610
+ "์ƒ์„ฑ",
611
+ variant="primary",
612
+ elem_classes="small-generate-btn"
613
+ )
614
+
615
+ # ํ”„๋กฌํ”„ํŠธ ์ฆ๊ฐ• ์˜ต์…˜ (์ƒ์„ฑ ๋ฒ„ํŠผ ์•„๋ž˜)
616
+ with gr.Group(elem_classes="prompt-enhance-section"):
617
+ enhance_prompt_checkbox = gr.Checkbox(
618
+ label="๐Ÿš€ ํ”„๋กฌํ”„ํŠธ ์ฆ๊ฐ• (AI๋กœ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž๋™์œผ๋กœ ๊ฐœ์„ ํ•˜์—ฌ ๊ณ ํ’ˆ์งˆ ์ด๋ฏธ์ง€ ์ƒ์„ฑ)",
619
+ value=False,
620
+ info="OpenAI API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž…๋ ฅํ•œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ๋”์šฑ ์ƒ์„ธํ•˜๊ณ  ๊ณ ํ’ˆ์งˆ์˜ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ์ž๋™์œผ๋กœ ์ฆ๊ฐ•ํ•ฉ๋‹ˆ๋‹ค."
621
+ )
622
+
623
+ # ์Šคํƒ€์ผ ํ”„๋ฆฌ์…‹ ์„น์…˜
624
+ with gr.Group(elem_classes="style-preset-section"):
625
+ style_select = gr.Radio(
626
+ label="๐ŸŽจ Style Preset",
627
+ choices=list(STYLE_PRESETS.keys()),
628
+ value="None",
629
+ interactive=True
630
+ )
631
+
632
+ result_image = gr.Image(label="Generated Image")
633
+ seed_output = gr.Number(label="Seed")
634
+
635
+ # ===== ๊ณ ๊ธ‰ ์„ค์ • =====
636
+ with gr.Accordion("Advanced Settings", open=False, elem_classes="advanced-settings"):
637
+ seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=42)
638
+ randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
639
+ with gr.Row():
640
+ width = gr.Slider(label="Width", minimum=256, maximum=MAX_IMAGE_SIZE, step=32, value=1024)
641
+ height = gr.Slider(label="Height", minimum=256, maximum=MAX_IMAGE_SIZE, step=32, value=768)
642
+ with gr.Row():
643
+ guidance_scale = gr.Slider(label="Guidance scale", minimum=0.0, maximum=10.0, step=0.1, value=3.5)
644
+ num_inference_steps = gr.Slider(label="Inference steps", minimum=1, maximum=50, step=1, value=30)
645
+ lora_scale = gr.Slider(label="LoRA scale", minimum=0.0, maximum=1.0, step=0.1, value=1.0)
646
+
647
+ # ===== ์˜ˆ์‹œ ์˜์—ญ =====
648
+ with gr.Group(elem_classes="example-region"):
649
+ gr.Markdown("### Examples")
650
+ gr.Examples(examples=examples, inputs=user_prompt, cache_examples=False)
651
+
652
+ # ===== ์ด๋ฒคํŠธ =====
653
+ run_button.click(
654
+ fn=generate_image,
655
+ inputs=[
656
+ user_prompt,
657
+ style_select,
658
+ enhance_prompt_checkbox,
659
+ seed,
660
+ randomize_seed,
661
+ width,
662
+ height,
663
+ guidance_scale,
664
+ num_inference_steps,
665
+ lora_scale,
666
+ ],
667
+ outputs=[result_image, seed_output],
668
+ )
669
+
670
+ return demo
671
+
672
+ # ===== ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ =====
673
+ if __name__ == "__main__":
674
+ demo = create_interface()
675
+ demo.queue()
676
+ demo.launch()