aiqtech commited on
Commit
c6a04f8
ยท
verified ยท
1 Parent(s): 4153d6e

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +501 -0
app.py ADDED
@@ -0,0 +1,501 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from google import genai
3
+ from google.genai import types
4
+ import os
5
+ from typing import Optional, List
6
+ from huggingface_hub import whoami
7
+ from PIL import Image
8
+ from io import BytesIO
9
+ import tempfile
10
+
11
+ # --- Google Gemini API Configuration ---
12
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", "")
13
+ if not GOOGLE_API_KEY:
14
+ raise ValueError("GOOGLE_API_KEY environment variable not set.")
15
+
16
+ client = genai.Client(
17
+ api_key=os.environ.get("GEMINI_API_KEY"),
18
+ )
19
+
20
+ GEMINI_MODEL_NAME = 'gemini-2.5-flash-image-preview'
21
+
22
+ def verify_pro_status(token: Optional[gr.OAuthToken]) -> bool:
23
+ """Verifies if the user is a Hugging Face PRO user or part of an enterprise org."""
24
+ if not token:
25
+ return False
26
+ try:
27
+ user_info = whoami(token=token.token)
28
+ if user_info.get("isPro", False):
29
+ return True
30
+ orgs = user_info.get("orgs", [])
31
+ if any(org.get("isEnterprise", False) for org in orgs):
32
+ return True
33
+ return False
34
+ except Exception as e:
35
+ print(f"Could not verify user's PRO/Enterprise status: {e}")
36
+ return False
37
+
38
+ def _extract_image_data_from_response(response) -> Optional[bytes]:
39
+ """Helper to extract image data from the model's response."""
40
+ if hasattr(response, 'candidates') and response.candidates:
41
+ for candidate in response.candidates:
42
+ if hasattr(candidate, 'content') and hasattr(candidate.content, 'parts') and candidate.content.parts:
43
+ for part in candidate.content.parts:
44
+ if hasattr(part, 'inline_data') and hasattr(part.inline_data, 'data'):
45
+ return part.inline_data.data
46
+ return None
47
+
48
+ def run_single_image_logic(prompt: str, image_path: Optional[str] = None, progress=gr.Progress()) -> str:
49
+ """Handles text-to-image or single image-to-image using Google Gemini."""
50
+ try:
51
+ progress(0.2, desc="๐ŸŽจ ์ค€๋น„ ์ค‘...")
52
+ contents = [prompt]
53
+ if image_path:
54
+ input_image = Image.open(image_path)
55
+ contents.append(input_image)
56
+
57
+ progress(0.5, desc="โœจ ์ƒ์„ฑ ์ค‘...")
58
+ response = client.models.generate_content(
59
+ model=GEMINI_MODEL_NAME,
60
+ contents=contents,
61
+ )
62
+
63
+ progress(0.8, desc="๐Ÿ–ผ๏ธ ๋งˆ๋ฌด๋ฆฌ ์ค‘...")
64
+ image_data = _extract_image_data_from_response(response)
65
+
66
+ if not image_data:
67
+ raise ValueError("No image data found in the model response.")
68
+
69
+ # Save the generated image to a temporary file to return its path
70
+ pil_image = Image.open(BytesIO(image_data))
71
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmpfile:
72
+ pil_image.save(tmpfile.name)
73
+ progress(1.0, desc="โœ… ์™„๋ฃŒ!")
74
+ return tmpfile.name
75
+
76
+ except Exception as e:
77
+ raise gr.Error(f"์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ: {e}")
78
+
79
+
80
+ def run_multi_image_logic(prompt: str, images: List[str], progress=gr.Progress()) -> str:
81
+ """
82
+ Handles multi-image editing by sending a list of images and a prompt.
83
+ """
84
+ if not images:
85
+ raise gr.Error("'์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€' ํƒญ์—์„œ ์ตœ์†Œ ํ•œ ๊ฐœ์˜ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.")
86
+
87
+ try:
88
+ progress(0.2, desc="๐ŸŽจ ์ด๋ฏธ์ง€ ์ค€๋น„ ์ค‘...")
89
+ contents = [Image.open(image_path[0]) for image_path in images]
90
+ contents.append(prompt)
91
+
92
+ progress(0.5, desc="โœจ ์ƒ์„ฑ ์ค‘...")
93
+ response = client.models.generate_content(
94
+ model=GEMINI_MODEL_NAME,
95
+ contents=contents,
96
+ )
97
+
98
+ progress(0.8, desc="๐Ÿ–ผ๏ธ ๋งˆ๋ฌด๋ฆฌ ์ค‘...")
99
+ image_data = _extract_image_data_from_response(response)
100
+
101
+ if not image_data:
102
+ raise ValueError("No image data found in the model response.")
103
+
104
+ pil_image = Image.open(BytesIO(image_data))
105
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmpfile:
106
+ pil_image.save(tmpfile.name)
107
+ progress(1.0, desc="โœ… ์™„๋ฃŒ!")
108
+ return tmpfile.name
109
+
110
+ except Exception as e:
111
+ raise gr.Error(f"์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ: {e}")
112
+
113
+
114
+ # --- Gradio App UI ---
115
+ css = '''
116
+ /* Header Styling */
117
+ .main-header {
118
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
119
+ padding: 2rem;
120
+ border-radius: 1rem;
121
+ margin-bottom: 2rem;
122
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
123
+ }
124
+
125
+ .header-title {
126
+ font-size: 2.5rem !important;
127
+ font-weight: bold;
128
+ color: white;
129
+ text-align: center;
130
+ margin: 0 !important;
131
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
132
+ }
133
+
134
+ .header-subtitle {
135
+ color: rgba(255,255,255,0.9);
136
+ text-align: center;
137
+ margin-top: 0.5rem !important;
138
+ font-size: 1.1rem;
139
+ }
140
+
141
+ /* Card Styling */
142
+ .card {
143
+ background: white;
144
+ border-radius: 1rem;
145
+ padding: 1.5rem;
146
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
147
+ border: 1px solid rgba(0,0,0,0.05);
148
+ }
149
+
150
+ .dark .card {
151
+ background: #1f2937;
152
+ border: 1px solid #374151;
153
+ }
154
+
155
+ /* Tab Styling */
156
+ .tabs {
157
+ border-radius: 0.5rem;
158
+ overflow: hidden;
159
+ margin-bottom: 1rem;
160
+ }
161
+
162
+ .tabitem {
163
+ padding: 1rem !important;
164
+ }
165
+
166
+ button.selected {
167
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
168
+ color: white !important;
169
+ }
170
+
171
+ /* Button Styling */
172
+ .generate-btn {
173
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
174
+ border: none !important;
175
+ color: white !important;
176
+ font-size: 1.1rem !important;
177
+ font-weight: 600 !important;
178
+ padding: 0.8rem 2rem !important;
179
+ border-radius: 0.5rem !important;
180
+ cursor: pointer !important;
181
+ transition: all 0.3s ease !important;
182
+ width: 100% !important;
183
+ margin-top: 1rem !important;
184
+ }
185
+
186
+ .generate-btn:hover {
187
+ transform: translateY(-2px) !important;
188
+ box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4) !important;
189
+ }
190
+
191
+ .use-btn {
192
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
193
+ border: none !important;
194
+ color: white !important;
195
+ font-weight: 600 !important;
196
+ padding: 0.6rem 1.5rem !important;
197
+ border-radius: 0.5rem !important;
198
+ cursor: pointer !important;
199
+ transition: all 0.3s ease !important;
200
+ width: 100% !important;
201
+ }
202
+
203
+ .use-btn:hover {
204
+ transform: translateY(-1px) !important;
205
+ box-shadow: 0 5px 15px rgba(16, 185, 129, 0.4) !important;
206
+ }
207
+
208
+ /* Input Styling */
209
+ .prompt-input textarea {
210
+ border-radius: 0.5rem !important;
211
+ border: 2px solid #e5e7eb !important;
212
+ padding: 0.8rem !important;
213
+ font-size: 1rem !important;
214
+ transition: border-color 0.3s ease !important;
215
+ }
216
+
217
+ .prompt-input textarea:focus {
218
+ border-color: #667eea !important;
219
+ outline: none !important;
220
+ }
221
+
222
+ .dark .prompt-input textarea {
223
+ border-color: #374151 !important;
224
+ background: #1f2937 !important;
225
+ }
226
+
227
+ /* Image Output Styling */
228
+ #output {
229
+ border-radius: 0.5rem !important;
230
+ overflow: hidden !important;
231
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1) !important;
232
+ }
233
+
234
+ /* Progress Bar Styling */
235
+ .progress-bar {
236
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
237
+ }
238
+
239
+ /* Examples Styling */
240
+ .examples-container {
241
+ background: #f9fafb;
242
+ border-radius: 0.5rem;
243
+ padding: 1rem;
244
+ margin-top: 1rem;
245
+ }
246
+
247
+ .dark .examples-container {
248
+ background: #1f2937;
249
+ }
250
+
251
+ /* Pro Message Styling */
252
+ .pro-message {
253
+ background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
254
+ border-radius: 1rem;
255
+ padding: 2rem;
256
+ text-align: center;
257
+ border: 2px solid #f59e0b;
258
+ }
259
+
260
+ .dark .pro-message {
261
+ background: linear-gradient(135deg, #7c2d12 0%, #92400e 100%);
262
+ border-color: #f59e0b;
263
+ }
264
+
265
+ /* Emoji Animations */
266
+ @keyframes bounce {
267
+ 0%, 100% { transform: translateY(0); }
268
+ 50% { transform: translateY(-10px); }
269
+ }
270
+
271
+ .emoji-icon {
272
+ display: inline-block;
273
+ animation: bounce 2s infinite;
274
+ }
275
+
276
+ /* Responsive Design */
277
+ @media (max-width: 768px) {
278
+ .header-title {
279
+ font-size: 2rem !important;
280
+ }
281
+
282
+ .main-container {
283
+ padding: 1rem !important;
284
+ }
285
+ }
286
+ '''
287
+
288
+ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
289
+ # Header
290
+ gr.HTML('''
291
+ <div class="main-header">
292
+ <h1 class="header-title">
293
+ ๐ŸŒ Real Nano Banana
294
+ </h1>
295
+ <p class="header-subtitle">
296
+ Google Gemini 2.5 Flash Image Preview๋กœ ๊ตฌ๋™๋˜๋Š” AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ
297
+ </p>
298
+ </div>
299
+ ''')
300
+
301
+ # Pro User Notice
302
+ gr.HTML('''
303
+ <div style="background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
304
+ border-radius: 0.5rem; padding: 1rem; margin-bottom: 1.5rem;
305
+ border-left: 4px solid #f59e0b;">
306
+ <p style="margin: 0; color: #92400e; font-weight: 600;">
307
+ ๐ŸŒŸ ์ด ์ŠคํŽ˜์ด์Šค๋Š” Hugging Face PRO ์‚ฌ์šฉ์ž ์ „์šฉ์ž…๋‹ˆ๋‹ค.
308
+ <a href="https://huggingface.co/pro" target="_blank"
309
+ style="color: #dc2626; text-decoration: underline;">
310
+ PRO ๊ตฌ๋…ํ•˜๊ธฐ
311
+ </a>
312
+ </p>
313
+ </div>
314
+ ''')
315
+
316
+ pro_message = gr.Markdown(visible=False)
317
+ main_interface = gr.Column(visible=False, elem_classes="main-container")
318
+
319
+ with main_interface:
320
+ with gr.Row():
321
+ with gr.Column(scale=1):
322
+ gr.HTML('<div class="card">')
323
+
324
+ # Mode Selection
325
+ gr.HTML('<h3 style="margin-top: 0;">๐Ÿ“ธ ๋ชจ๋“œ ์„ ํƒ</h3>')
326
+ active_tab_state = gr.State(value="single")
327
+
328
+ with gr.Tabs(elem_classes="tabs") as tabs:
329
+ with gr.TabItem("๐Ÿ–ผ๏ธ ๋‹จ์ผ ์ด๋ฏธ์ง€", id="single") as single_tab:
330
+ image_input = gr.Image(
331
+ type="filepath",
332
+ label="์ž…๋ ฅ ์ด๋ฏธ์ง€",
333
+ elem_classes="image-input"
334
+ )
335
+ gr.HTML('''
336
+ <p style="text-align: center; color: #6b7280; font-size: 0.9rem; margin-top: 0.5rem;">
337
+ ๐Ÿ’ก ํ…์ŠคํŠธโ†’์ด๋ฏธ์ง€ ์ƒ์„ฑ์€ ๋น„์›Œ๋‘์„ธ์š”
338
+ </p>
339
+ ''')
340
+
341
+ with gr.TabItem("๐ŸŽจ ๋‹ค์ค‘ ์ด๋ฏธ์ง€", id="multiple") as multi_tab:
342
+ gallery_input = gr.Gallery(
343
+ label="์ž…๋ ฅ ์ด๋ฏธ์ง€๋“ค",
344
+ file_types=["image"],
345
+ elem_classes="gallery-input"
346
+ )
347
+ gr.HTML('''
348
+ <p style="text-align: center; color: #6b7280; font-size: 0.9rem; margin-top: 0.5rem;">
349
+ ๐Ÿ’ก ์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€๋ฅผ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญํ•˜์„ธ์š”
350
+ </p>
351
+ ''')
352
+
353
+ # Prompt Input
354
+ gr.HTML('<h3>โœ๏ธ ํ”„๋กฌํ”„ํŠธ</h3>')
355
+ prompt_input = gr.Textbox(
356
+ label="",
357
+ info="AI์—๊ฒŒ ์›ํ•˜๋Š” ๊ฒƒ์„ ์„ค๋ช…ํ•˜์„ธ์š”",
358
+ placeholder="์˜ˆ: ๋ง›์žˆ์–ด ๋ณด์ด๋Š” ํ”ผ์ž, ์šฐ์ฃผ๋ฅผ ๋ฐฐ๊ฒฝ์œผ๋กœ ํ•œ ๊ณ ์–‘์ด, ๋ฏธ๋ž˜์ ์ธ ๋„์‹œ ํ’๊ฒฝ...",
359
+ lines=3,
360
+ elem_classes="prompt-input"
361
+ )
362
+
363
+ # Generate Button
364
+ generate_button = gr.Button(
365
+ "๐Ÿš€ ์ƒ์„ฑํ•˜๊ธฐ",
366
+ variant="primary",
367
+ elem_classes="generate-btn"
368
+ )
369
+
370
+ # Examples
371
+ with gr.Accordion("๐Ÿ’ก ์˜ˆ์ œ ํ”„๋กฌํ”„ํŠธ", open=False):
372
+ gr.Examples(
373
+ examples=[
374
+ ["์น˜์ฆˆ๊ฐ€ ๋Š˜์–ด๋‚˜๋Š” ๋ง›์žˆ์–ด ๋ณด์ด๋Š” ํ”ผ์ž"],
375
+ ["์šฐ์ฃผ๋ณต์„ ์ž…์€ ๊ณ ์–‘์ด๊ฐ€ ๋‹ฌ ํ‘œ๋ฉด์„ ๊ฑท๊ณ  ์žˆ๋Š” ๋ชจ์Šต"],
376
+ ["๋„ค์˜จ ๋ถˆ๋น›์ด ๋น›๋‚˜๋Š” ์‚ฌ์ด๋ฒ„ํŽ‘ํฌ ๋„์‹œ์˜ ์•ผ๊ฒฝ"],
377
+ ["๋ด„๋‚  ๋ฒš๊ฝƒ์ด ๋งŒ๊ฐœํ•œ ์ผ๋ณธ ์ •์›"],
378
+ ["ํŒํƒ€์ง€ ์„ธ๊ณ„์˜ ๋งˆ๋ฒ•์‚ฌ ํƒ‘"],
379
+ ],
380
+ inputs=prompt_input,
381
+ elem_classes="examples-container"
382
+ )
383
+
384
+ gr.HTML('</div>')
385
+
386
+ with gr.Column(scale=1):
387
+ gr.HTML('<div class="card">')
388
+ gr.HTML('<h3 style="margin-top: 0;">๐ŸŽจ ์ƒ์„ฑ ๊ฒฐ๊ณผ</h3>')
389
+
390
+ output_image = gr.Image(
391
+ label="",
392
+ interactive=False,
393
+ elem_id="output"
394
+ )
395
+
396
+ use_image_button = gr.Button(
397
+ "โ™ป๏ธ ์ด ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์Œ ํŽธ์ง‘์— ์‚ฌ์šฉ",
398
+ elem_classes="use-btn",
399
+ visible=False
400
+ )
401
+
402
+ # Tips
403
+ gr.HTML('''
404
+ <div style="background: #f0f9ff; border-radius: 0.5rem; padding: 1rem; margin-top: 1rem;">
405
+ <h4 style="margin-top: 0; color: #0369a1;">๐Ÿ’ก ํŒ</h4>
406
+ <ul style="margin: 0; padding-left: 1.5rem; color: #0c4a6e;">
407
+ <li>๊ตฌ์ฒด์ ์ด๊ณ  ์ƒ์„ธํ•œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”</li>
408
+ <li>์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๋ฅผ ์žฌ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ˜๋ณต์ ์œผ๋กœ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค</li>
409
+ <li>๋‹ค์ค‘ ์ด๋ฏธ์ง€ ๋ชจ๋“œ๋กœ ์—ฌ๋Ÿฌ ์ฐธ์กฐ ์ด๋ฏธ์ง€๋ฅผ ๊ฒฐํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค</li>
410
+ </ul>
411
+ </div>
412
+ ''')
413
+
414
+ gr.HTML('</div>')
415
+
416
+ # Footer
417
+ gr.HTML('''
418
+ <div style="text-align: center; margin-top: 2rem; padding: 1rem;
419
+ border-top: 1px solid #e5e7eb;">
420
+ <p style="color: #6b7280;">
421
+ Made with ๐Ÿ’œ by Hugging Face PRO | Powered by Google Gemini 2.5 Flash
422
+ </p>
423
+ </div>
424
+ ''')
425
+
426
+ login_button = gr.LoginButton()
427
+
428
+ # --- Event Handlers ---
429
+ def unified_generator(
430
+ prompt: str,
431
+ single_image: Optional[str],
432
+ multi_images: Optional[List[str]],
433
+ active_tab: str,
434
+ oauth_token: Optional[gr.OAuthToken] = None,
435
+ ):
436
+ if not verify_pro_status(oauth_token):
437
+ raise gr.Error("์•ก์„ธ์Šค ๊ฑฐ๋ถ€: ์ด ์„œ๋น„์Šค๋Š” PRO ์‚ฌ์šฉ์ž ์ „์šฉ์ž…๋‹ˆ๋‹ค.")
438
+ if not prompt:
439
+ raise gr.Error("ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.")
440
+ if active_tab == "multiple" and multi_images:
441
+ result = run_multi_image_logic(prompt, multi_images)
442
+ else:
443
+ result = run_single_image_logic(prompt, single_image)
444
+ return result, gr.update(visible=True)
445
+
446
+ single_tab.select(lambda: "single", None, active_tab_state)
447
+ multi_tab.select(lambda: "multiple", None, active_tab_state)
448
+
449
+ generate_button.click(
450
+ unified_generator,
451
+ inputs=[prompt_input, image_input, gallery_input, active_tab_state],
452
+ outputs=[output_image, use_image_button],
453
+ )
454
+
455
+ use_image_button.click(
456
+ lambda img: (img, gr.update(visible=False)),
457
+ inputs=[output_image],
458
+ outputs=[image_input, use_image_button]
459
+ )
460
+
461
+ # --- Access Control Logic ---
462
+ def control_access(
463
+ profile: Optional[gr.OAuthProfile] = None,
464
+ oauth_token: Optional[gr.OAuthToken] = None
465
+ ):
466
+ if not profile:
467
+ return gr.update(visible=False), gr.update(visible=False)
468
+ if verify_pro_status(oauth_token):
469
+ return gr.update(visible=True), gr.update(visible=False)
470
+ else:
471
+ message = '''
472
+ <div class="pro-message">
473
+ <h2>โœจ PRO ์‚ฌ์šฉ์ž ์ „์šฉ ๊ธฐ๋Šฅ</h2>
474
+ <p style="font-size: 1.1rem; margin: 1rem 0;">
475
+ ์ด ๊ฐ•๋ ฅํ•œ AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋„๊ตฌ๋Š” Hugging Face <strong>PRO</strong> ๋ฉค๋ฒ„ ์ „์šฉ์ž…๋‹ˆ๋‹ค.
476
+ </p>
477
+ <p style="margin: 1rem 0;">
478
+ PRO ๊ตฌ๋…์œผ๋กœ ๋‹ค์Œ์„ ๋ˆ„๋ฆฌ์„ธ์š”:
479
+ </p>
480
+ <ul style="text-align: left; display: inline-block; margin: 1rem 0;">
481
+ <li>๐Ÿš€ Google Gemini 2.5 Flash ๋ฌด์ œํ•œ ์•ก์„ธ์Šค</li>
482
+ <li>โšก ๋น ๋ฅธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ</li>
483
+ <li>๐ŸŽจ ๊ณ ํ’ˆ์งˆ ๊ฒฐ๊ณผ๋ฌผ</li>
484
+ <li>๐Ÿ”ง ๋‹ค์ค‘ ์ด๋ฏธ์ง€ ํŽธ์ง‘ ๊ธฐ๋Šฅ</li>
485
+ </ul>
486
+ <a href="https://huggingface.co/pro" target="_blank"
487
+ style="display: inline-block; margin-top: 1rem; padding: 1rem 2rem;
488
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
489
+ color: white; text-decoration: none; border-radius: 0.5rem;
490
+ font-weight: bold; font-size: 1.1rem;">
491
+ ๐ŸŒŸ ์ง€๊ธˆ PRO ๋ฉค๋ฒ„ ๋˜๊ธฐ
492
+ </a>
493
+ </div>
494
+ '''
495
+ return gr.update(visible=False), gr.update(visible=True, value=message)
496
+
497
+ demo.load(control_access, inputs=None, outputs=[main_interface, pro_message])
498
+
499
+ if __name__ == "__main__":
500
+ demo.queue(max_size=None, default_concurrency_limit=None)
501
+ demo.launch()