aiqtech commited on
Commit
5ad5226
ยท
verified ยท
1 Parent(s): 73ebb76

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +122 -181
app.py CHANGED
@@ -1,187 +1,144 @@
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, ImageDraw, ImageFont
 
8
  from io import BytesIO
9
  import tempfile
10
 
11
- # --- Google Gemini API Configuration ---
12
- # Use GEMINI_API_KEY if available, otherwise fall back to GOOGLE_API_KEY
13
- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
14
- GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
15
 
16
- API_KEY = GEMINI_API_KEY or GOOGLE_API_KEY
 
17
 
18
- if not API_KEY:
19
- raise ValueError("Neither GEMINI_API_KEY nor GOOGLE_API_KEY environment variable is set.")
20
 
21
- client = genai.Client(
22
- api_key=API_KEY,
23
- )
24
-
25
- # Note: Gemini models don't directly generate images - they analyze/describe them
26
- # For image generation, you'd need to use a different API like Imagen
27
- # This code is updated to work with text generation about images
28
- GEMINI_MODEL_NAME = 'gemini-2.0-flash-exp' # Updated model name
29
-
30
- def verify_pro_status(token: Optional[gr.OAuthToken]) -> bool:
31
- """Verifies if the user is a Hugging Face PRO user or part of an enterprise org."""
32
  if not token:
33
  return False
34
  try:
35
  user_info = whoami(token=token.token)
36
- if user_info.get("isPro", False):
37
- return True
38
- orgs = user_info.get("orgs", [])
39
- if any(org.get("isEnterprise", False) for org in orgs):
40
- return True
41
- return False
42
  except Exception as e:
43
- print(f"Could not verify user's PRO/Enterprise status: {e}")
44
  return False
45
 
46
- def _extract_image_data_from_response(response) -> Optional[bytes]:
47
- """Helper to extract image data from the model's response."""
48
- # Debug: Print response structure
49
- print(f"Response type: {type(response)}")
50
-
51
- # Note: Gemini doesn't generate images directly
52
- # This would need to be replaced with actual image generation API
53
- # For now, return None to indicate no image was generated
54
-
55
- if hasattr(response, 'text'):
56
- print(f"Response text: {response.text[:200] if response.text else 'Empty'}")
57
-
58
- return None
59
-
60
  def run_single_image_logic(prompt: str, image_path: Optional[str] = None, progress=gr.Progress()) -> str:
61
- """Handles text or image analysis using Google Gemini."""
62
  try:
63
  progress(0.2, desc="๐ŸŽจ ์ค€๋น„ ์ค‘...")
64
 
65
- contents = []
 
 
 
 
 
66
  if image_path:
67
- # Image analysis
68
- input_image = Image.open(image_path)
69
- contents.append(input_image)
70
- contents.append(f"Describe what you see and how it could be modified based on: {prompt}")
71
- else:
72
- # Text-only prompt
73
- contents.append(f"Describe an image concept for: {prompt}")
74
-
75
  progress(0.5, desc="โœจ ์ƒ์„ฑ ์ค‘...")
76
 
77
- # Remove the generation_config parameter that's causing the error
78
- # Use the simpler API call format
79
- try:
80
- response = client.models.generate_content(
81
- model=GEMINI_MODEL_NAME,
82
- contents=contents,
83
- )
84
- except Exception as api_error:
85
- # Fallback: try with just the contents as a simple string/list
86
- print(f"First attempt failed: {api_error}")
87
- if image_path:
88
- # For image input, we need to handle it differently
89
- # The API might expect a different format
90
- raise gr.Error("์ด๋ฏธ์ง€ ์ž…๋ ฅ์€ ํ˜„์žฌ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Gemini API๋Š” ์ด๋ฏธ์ง€ ์ƒ์„ฑ์ด ์•„๋‹Œ ๋ถ„์„์šฉ์ž…๋‹ˆ๋‹ค.")
91
- else:
92
- # For text-only, try a simpler approach
93
- response = client.models.generate_content(
94
- model=GEMINI_MODEL_NAME,
95
- contents=prompt
96
- )
97
 
98
  progress(0.8, desc="๐Ÿ–ผ๏ธ ๋งˆ๋ฌด๋ฆฌ ์ค‘...")
99
 
100
- # Since Gemini doesn't generate images, we'll need to handle this differently
101
- # For demonstration, create a placeholder or use a different service
102
- if hasattr(response, 'text') and response.text:
103
- # Return the text response for now
104
- # In production, you'd call an actual image generation API here
105
- description = response.text
106
-
107
- # Create a placeholder image with the description
108
- img = Image.new('RGB', (512, 512), color='white')
109
- draw = ImageDraw.Draw(img)
110
-
111
- # Add text to explain the situation
112
- text = "Gemini API๋Š” ์ด๋ฏธ์ง€ ์ƒ์„ฑ์„ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.\n\n"
113
- text += "์ƒ์„ฑ๋œ ์„ค๋ช…:\n" + description[:200] + "..."
114
 
115
- # Simple text wrapping
116
- y_position = 50
117
- for line in text.split('\n'):
118
- draw.text((20, y_position), line, fill='black')
119
- y_position += 30
120
 
121
- # Save the placeholder image
 
122
  with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmpfile:
123
  img.save(tmpfile.name)
124
  progress(1.0, desc="โœ… ์™„๋ฃŒ!")
125
  return tmpfile.name
126
  else:
127
- raise ValueError("API ์‘๋‹ต์„ ๋ฐ›์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.")
128
 
129
  except Exception as e:
130
  print(f"Error details: {e}")
131
  print(f"Error type: {type(e)}")
132
- raise gr.Error(f"์ฒ˜๋ฆฌ ์‹คํŒจ: {e}")
133
-
134
 
135
  def run_multi_image_logic(prompt: str, images: List[str], progress=gr.Progress()) -> str:
136
  """
137
- Handles multi-image analysis.
138
  """
139
  if not images:
140
  raise gr.Error("'์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€' ํƒญ์—์„œ ์ตœ์†Œ ํ•œ ๊ฐœ์˜ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.")
141
 
142
  try:
143
  progress(0.2, desc="๐ŸŽจ ์ด๋ฏธ์ง€ ์ค€๋น„ ์ค‘...")
144
- contents = []
145
- for image_path in images:
146
- if isinstance(image_path, (list, tuple)):
147
- image_path = image_path[0]
148
- contents.append(Image.open(image_path))
149
- contents.append(f"Analyze these images based on: {prompt}")
150
-
151
- progress(0.5, desc="โœจ ๋ถ„์„ ์ค‘...")
152
 
153
- # Simple API call without generation_config
154
- response = client.models.generate_content(
155
- model=GEMINI_MODEL_NAME,
156
- contents=contents,
 
 
 
 
 
 
 
 
157
  )
158
 
159
  progress(0.8, desc="๐Ÿ–ผ๏ธ ๋งˆ๋ฌด๋ฆฌ ์ค‘...")
160
 
161
- # Create a result image with the analysis
162
- if hasattr(response, 'text') and response.text:
163
- img = Image.new('RGB', (512, 512), color='white')
164
- draw = ImageDraw.Draw(img)
165
-
166
- text = "๋‹ค์ค‘ ์ด๋ฏธ์ง€ ๋ถ„์„ ๊ฒฐ๊ณผ:\n\n"
167
- text += response.text[:300] + "..."
 
 
 
 
168
 
169
- y_position = 50
170
- for line in text.split('\n'):
171
- draw.text((20, y_position), line, fill='black')
172
- y_position += 30
173
 
 
 
174
  with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmpfile:
175
  img.save(tmpfile.name)
176
  progress(1.0, desc="โœ… ์™„๋ฃŒ!")
177
  return tmpfile.name
178
  else:
179
- raise ValueError("API ์‘๋‹ต์„ ๋ฐ›์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.")
180
 
181
  except Exception as e:
182
  print(f"Multi-image error details: {e}")
183
- raise gr.Error(f"์ฒ˜๋ฆฌ ์‹คํŒจ: {e}")
184
-
185
 
186
  # --- Gradio App UI ---
187
  css = '''
@@ -320,8 +277,8 @@ button.selected {
320
  background: #1f2937;
321
  }
322
 
323
- /* Pro Message Styling */
324
- .pro-message {
325
  background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
326
  border-radius: 1rem;
327
  padding: 2rem;
@@ -329,7 +286,7 @@ button.selected {
329
  border: 2px solid #f59e0b;
330
  }
331
 
332
- .dark .pro-message {
333
  background: linear-gradient(135deg, #7c2d12 0%, #92400e 100%);
334
  border-color: #f59e0b;
335
  }
@@ -365,38 +322,23 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
365
  ๐ŸŒ Real Nano Banana
366
  </h1>
367
  <p class="header-subtitle">
368
- Google Gemini API๋กœ ๊ตฌ๋™๋˜๋Š” AI ์ด๋ฏธ์ง€ ๋ถ„์„๊ธฐ
369
  </p>
370
  </div>
371
  ''')
372
 
373
- # Important Notice
374
  gr.HTML('''
375
- <div style="background: #fef2f2; border-radius: 0.5rem; padding: 1rem; margin-bottom: 1.5rem;
376
- border-left: 4px solid #ef4444;">
377
- <p style="margin: 0; color: #991b1b; font-weight: 600;">
378
- โš ๏ธ ์ฐธ๊ณ : Gemini API๋Š” ์ด๋ฏธ์ง€ ์ƒ์„ฑ์ด ์•„๋‹Œ ๋ถ„์„์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
379
- ์‹ค์ œ ์ด๋ฏธ์ง€ ์ƒ์„ฑ์„ ์›ํ•˜์‹œ๋ฉด DALL-E, Midjourney, Stable Diffusion ๋“ฑ์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
380
- </p>
381
- </div>
382
- ''')
383
-
384
- # Pro User Notice
385
- gr.HTML('''
386
- <div style="background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
387
  border-radius: 0.5rem; padding: 1rem; margin-bottom: 1.5rem;
388
- border-left: 4px solid #f59e0b;">
389
- <p style="margin: 0; color: #92400e; font-weight: 600;">
390
- ๐ŸŒŸ ์ด ์ŠคํŽ˜์ด์Šค๋Š” Hugging Face PRO ์‚ฌ์šฉ์ž ์ „์šฉ์ž…๋‹ˆ๋‹ค.
391
- <a href="https://huggingface.co/pro" target="_blank"
392
- style="color: #dc2626; text-decoration: underline;">
393
- PRO ๊ตฌ๋…ํ•˜๊ธฐ
394
- </a>
395
  </p>
396
  </div>
397
  ''')
398
 
399
- pro_message = gr.Markdown(visible=False)
400
  main_interface = gr.Column(visible=False, elem_classes="main-container")
401
 
402
  with main_interface:
@@ -412,12 +354,12 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
412
  with gr.TabItem("๐Ÿ–ผ๏ธ ๋‹จ์ผ ์ด๋ฏธ์ง€", id="single") as single_tab:
413
  image_input = gr.Image(
414
  type="filepath",
415
- label="์ž…๋ ฅ ์ด๋ฏธ์ง€",
416
  elem_classes="image-input"
417
  )
418
  gr.HTML('''
419
  <p style="text-align: center; color: #6b7280; font-size: 0.9rem; margin-top: 0.5rem;">
420
- ๐Ÿ’ก ํ…์ŠคํŠธ ์„ค๋ช…๋งŒ ์›ํ•˜์‹œ๋ฉด ๋น„์›Œ๋‘์„ธ์š”
421
  </p>
422
  ''')
423
 
@@ -437,15 +379,15 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
437
  gr.HTML('<h3>โœ๏ธ ํ”„๋กฌํ”„ํŠธ</h3>')
438
  prompt_input = gr.Textbox(
439
  label="",
440
- info="AI์—๊ฒŒ ์›ํ•˜๋Š” ๊ฒƒ์„ ์„ค๋ช…ํ•˜์„ธ์š”",
441
- placeholder="์˜ˆ: ์ด ์ด๋ฏธ์ง€๋ฅผ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”, ๋ฌด์—‡์ด ๋ณด์ด๋‚˜์š”?, ์ด๋ฏธ์ง€๋ฅผ ์–ด๋–ป๊ฒŒ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?",
442
  lines=3,
443
  elem_classes="prompt-input"
444
  )
445
 
446
  # Generate Button
447
  generate_button = gr.Button(
448
- "๐Ÿš€ ๋ถ„์„ํ•˜๊ธฐ",
449
  variant="primary",
450
  elem_classes="generate-btn"
451
  )
@@ -454,11 +396,13 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
454
  with gr.Accordion("๐Ÿ’ก ์˜ˆ์ œ ํ”„๋กฌํ”„ํŠธ", open=False):
455
  gr.Examples(
456
  examples=[
457
- ["์ด ์ด๋ฏธ์ง€์—์„œ ๋ฌด์—‡์ด ๋ณด์ด๋‚˜์š”?"],
458
- ["์ด๋ฏธ์ง€์˜ ์ƒ‰์ƒ๊ณผ ๊ตฌ์„ฑ์„ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”"],
459
- ["์ด ์žฅ๋ฉด์„ ๋” ๊ทน์ ์œผ๋กœ ๋งŒ๋“ค๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ์š”?"],
460
- ["์ด๋ฏธ์ง€์˜ ๋ถ„์œ„๊ธฐ์™€ ๊ฐ์ •์„ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”"],
461
- ["๊ธฐ์ˆ ์ ์ธ ๊ด€์ ์—์„œ ์ด ์ด๋ฏธ์ง€๋ฅผ ํ‰๊ฐ€ํ•ด์ฃผ์„ธ์š”"],
 
 
462
  ],
463
  inputs=prompt_input
464
  )
@@ -467,7 +411,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
467
 
468
  with gr.Column(scale=1):
469
  gr.HTML('<div class="card">')
470
- gr.HTML('<h3 style="margin-top: 0;">๐ŸŽจ ๋ถ„์„ ๊ฒฐ๊ณผ</h3>')
471
 
472
  output_image = gr.Image(
473
  label="",
@@ -476,7 +420,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
476
  )
477
 
478
  use_image_button = gr.Button(
479
- "โ™ป๏ธ ์ด ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์Œ ๋ถ„์„์— ์‚ฌ์šฉ",
480
  elem_classes="use-btn",
481
  visible=False
482
  )
@@ -487,8 +431,9 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
487
  <h4 style="margin-top: 0; color: #0369a1;">๐Ÿ’ก ํŒ</h4>
488
  <ul style="margin: 0; padding-left: 1.5rem; color: #0c4a6e;">
489
  <li>๊ตฌ์ฒด์ ์ด๊ณ  ์ƒ์„ธํ•œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”</li>
490
- <li>์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜์—ฌ AI ๋ถ„์„์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค</li>
491
- <li>๋‹ค์ค‘ ์ด๋ฏธ์ง€ ๋ชจ๋“œ๋กœ ์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€๋ฅผ ๋น„๊ต ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค</li>
 
492
  </ul>
493
  </div>
494
  ''')
@@ -500,7 +445,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
500
  <div style="text-align: center; margin-top: 2rem; padding: 1rem;
501
  border-top: 1px solid #e5e7eb;">
502
  <p style="color: #6b7280;">
503
- Made with ๐Ÿ’œ by Hugging Face PRO | Powered by Google Gemini API
504
  </p>
505
  </div>
506
  ''')
@@ -515,8 +460,8 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
515
  active_tab: str,
516
  oauth_token: Optional[gr.OAuthToken] = None,
517
  ):
518
- if not verify_pro_status(oauth_token):
519
- raise gr.Error("์•ก์„ธ์Šค ๊ฑฐ๋ถ€: ์ด ์„œ๋น„์Šค๋Š” PRO ์‚ฌ์šฉ์ž ์ „์šฉ์ž…๋‹ˆ๋‹ค.")
520
  if not prompt:
521
  raise gr.Error("ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.")
522
  if active_tab == "multiple" and multi_images:
@@ -547,36 +492,32 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
547
  ):
548
  if not profile:
549
  return gr.update(visible=False), gr.update(visible=False)
550
- if verify_pro_status(oauth_token):
551
  return gr.update(visible=True), gr.update(visible=False)
552
  else:
553
  message = '''
554
- <div class="pro-message">
555
- <h2>โœจ PRO ์‚ฌ์šฉ์ž ์ „์šฉ ๊ธฐ๋Šฅ</h2>
556
  <p style="font-size: 1.1rem; margin: 1rem 0;">
557
- ์ด ๊ฐ•๋ ฅํ•œ AI ์ด๋ฏธ์ง€ ๋ถ„์„ ๋„๊ตฌ๋Š” Hugging Face <strong>PRO</strong> ๋ฉค๋ฒ„ ์ „์šฉ์ž…๋‹ˆ๋‹ค.
558
  </p>
559
  <p style="margin: 1rem 0;">
560
- PRO ๊ตฌ๋…์œผ๋กœ ๋‹ค์Œ์„ ๋ˆ„๋ฆฌ์„ธ์š”:
561
  </p>
562
  <ul style="text-align: left; display: inline-block; margin: 1rem 0;">
563
- <li>๐Ÿš€ Google Gemini API ๋ฌด์ œํ•œ ์•ก์„ธ์Šค</li>
564
- <li>โšก ๋น ๋ฅธ ์ด๋ฏธ์ง€ ๋ถ„์„</li>
565
- <li>๐ŸŽจ ์ƒ์„ธํ•œ ์ด๋ฏธ์ง€ ์„ค๋ช…</li>
566
- <li>๐Ÿ”ง ๋‹ค์ค‘ ์ด๋ฏธ์ง€ ๋น„๊ต ๋ถ„์„</li>
567
  </ul>
568
- <a href="https://huggingface.co/pro" target="_blank"
569
- style="display: inline-block; margin-top: 1rem; padding: 1rem 2rem;
570
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
571
- color: white; text-decoration: none; border-radius: 0.5rem;
572
- font-weight: bold; font-size: 1.1rem;">
573
- ๐ŸŒŸ ์ง€๊ธˆ PRO ๋ฉค๋ฒ„ ๋˜๊ธฐ
574
- </a>
575
  </div>
576
  '''
577
  return gr.update(visible=False), gr.update(visible=True, value=message)
578
 
579
- demo.load(control_access, inputs=None, outputs=[main_interface, pro_message])
580
 
581
  if __name__ == "__main__":
582
  demo.queue(max_size=None, default_concurrency_limit=None)
 
1
  import gradio as gr
2
+ import replicate
 
3
  import os
4
  from typing import Optional, List
5
  from huggingface_hub import whoami
6
+ from PIL import Image
7
+ import requests
8
  from io import BytesIO
9
  import tempfile
10
 
11
+ # --- Replicate API Configuration ---
12
+ REPLICATE_API_TOKEN = os.getenv("REPLICATE_API_TOKEN")
 
 
13
 
14
+ if not REPLICATE_API_TOKEN:
15
+ raise ValueError("REPLICATE_API_TOKEN environment variable is not set.")
16
 
17
+ # Initialize Replicate client
18
+ os.environ["REPLICATE_API_TOKEN"] = REPLICATE_API_TOKEN
19
 
20
+ def verify_login_status(token: Optional[gr.OAuthToken]) -> bool:
21
+ """Verifies if the user is logged in to Hugging Face."""
 
 
 
 
 
 
 
 
 
22
  if not token:
23
  return False
24
  try:
25
  user_info = whoami(token=token.token)
26
+ return True if user_info else False
 
 
 
 
 
27
  except Exception as e:
28
+ print(f"Could not verify user's login status: {e}")
29
  return False
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  def run_single_image_logic(prompt: str, image_path: Optional[str] = None, progress=gr.Progress()) -> str:
32
+ """Handles text-to-image or single image-to-image using Replicate's Nano Banana."""
33
  try:
34
  progress(0.2, desc="๐ŸŽจ ์ค€๋น„ ์ค‘...")
35
 
36
+ # Prepare input for Replicate API
37
+ input_data = {
38
+ "prompt": prompt
39
+ }
40
+
41
+ # If there's an input image, add it to the input
42
  if image_path:
43
+ # Upload the local image file and get a URL
44
+ # For Replicate, we need to provide URLs, not local paths
45
+ # We'll read the file and create a temporary URL or use file directly
46
+ input_data["image_input"] = [image_path]
47
+
 
 
 
48
  progress(0.5, desc="โœจ ์ƒ์„ฑ ์ค‘...")
49
 
50
+ # Run the model on Replicate
51
+ output = replicate.run(
52
+ "google/nano-banana",
53
+ input=input_data
54
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
  progress(0.8, desc="๐Ÿ–ผ๏ธ ๋งˆ๋ฌด๋ฆฌ ์ค‘...")
57
 
58
+ # Handle the output
59
+ if output:
60
+ # If output is a FileOutput object, get the URL
61
+ if hasattr(output, 'url'):
62
+ image_url = output.url()
63
+ elif isinstance(output, str):
64
+ image_url = output
65
+ elif isinstance(output, list) and len(output) > 0:
66
+ image_url = output[0]
67
+ else:
68
+ raise ValueError("Unexpected output format from Replicate")
 
 
 
69
 
70
+ # Download the image from URL
71
+ response = requests.get(image_url)
72
+ response.raise_for_status()
 
 
73
 
74
+ # Save to temporary file
75
+ img = Image.open(BytesIO(response.content))
76
  with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmpfile:
77
  img.save(tmpfile.name)
78
  progress(1.0, desc="โœ… ์™„๋ฃŒ!")
79
  return tmpfile.name
80
  else:
81
+ raise ValueError("No output received from Replicate API")
82
 
83
  except Exception as e:
84
  print(f"Error details: {e}")
85
  print(f"Error type: {type(e)}")
86
+ raise gr.Error(f"์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ: {e}")
 
87
 
88
  def run_multi_image_logic(prompt: str, images: List[str], progress=gr.Progress()) -> str:
89
  """
90
+ Handles multi-image editing by sending a list of images and a prompt.
91
  """
92
  if not images:
93
  raise gr.Error("'์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€' ํƒญ์—์„œ ์ตœ์†Œ ํ•œ ๊ฐœ์˜ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.")
94
 
95
  try:
96
  progress(0.2, desc="๐ŸŽจ ์ด๋ฏธ์ง€ ์ค€๋น„ ์ค‘...")
 
 
 
 
 
 
 
 
97
 
98
+ # Prepare input for Replicate API with multiple images
99
+ input_data = {
100
+ "prompt": prompt,
101
+ "image_input": images # Pass the list of image paths directly
102
+ }
103
+
104
+ progress(0.5, desc="โœจ ์ƒ์„ฑ ์ค‘...")
105
+
106
+ # Run the model on Replicate
107
+ output = replicate.run(
108
+ "google/nano-banana",
109
+ input=input_data
110
  )
111
 
112
  progress(0.8, desc="๐Ÿ–ผ๏ธ ๋งˆ๋ฌด๋ฆฌ ์ค‘...")
113
 
114
+ # Handle the output
115
+ if output:
116
+ # If output is a FileOutput object, get the URL
117
+ if hasattr(output, 'url'):
118
+ image_url = output.url()
119
+ elif isinstance(output, str):
120
+ image_url = output
121
+ elif isinstance(output, list) and len(output) > 0:
122
+ image_url = output[0]
123
+ else:
124
+ raise ValueError("Unexpected output format from Replicate")
125
 
126
+ # Download the image from URL
127
+ response = requests.get(image_url)
128
+ response.raise_for_status()
 
129
 
130
+ # Save to temporary file
131
+ img = Image.open(BytesIO(response.content))
132
  with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmpfile:
133
  img.save(tmpfile.name)
134
  progress(1.0, desc="โœ… ์™„๋ฃŒ!")
135
  return tmpfile.name
136
  else:
137
+ raise ValueError("No output received from Replicate API")
138
 
139
  except Exception as e:
140
  print(f"Multi-image error details: {e}")
141
+ raise gr.Error(f"์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ: {e}")
 
142
 
143
  # --- Gradio App UI ---
144
  css = '''
 
277
  background: #1f2937;
278
  }
279
 
280
+ /* Login Message Styling */
281
+ .login-message {
282
  background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
283
  border-radius: 1rem;
284
  padding: 2rem;
 
286
  border: 2px solid #f59e0b;
287
  }
288
 
289
+ .dark .login-message {
290
  background: linear-gradient(135deg, #7c2d12 0%, #92400e 100%);
291
  border-color: #f59e0b;
292
  }
 
322
  ๐ŸŒ Real Nano Banana
323
  </h1>
324
  <p class="header-subtitle">
325
+ Google Nano Banana๋กœ ๊ตฌ๋™๋˜๋Š” AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ
326
  </p>
327
  </div>
328
  ''')
329
 
330
+ # Login Notice
331
  gr.HTML('''
332
+ <div style="background: linear-gradient(135deg, #e0f2fe 0%, #bae6fd 100%);
 
 
 
 
 
 
 
 
 
 
 
333
  border-radius: 0.5rem; padding: 1rem; margin-bottom: 1.5rem;
334
+ border-left: 4px solid #0284c7;">
335
+ <p style="margin: 0; color: #075985; font-weight: 600;">
336
+ ๐Ÿ” ์ด ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Hugging Face ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•ด์ฃผ์„ธ์š”.
 
 
 
 
337
  </p>
338
  </div>
339
  ''')
340
 
341
+ login_message = gr.Markdown(visible=False)
342
  main_interface = gr.Column(visible=False, elem_classes="main-container")
343
 
344
  with main_interface:
 
354
  with gr.TabItem("๐Ÿ–ผ๏ธ ๋‹จ์ผ ์ด๋ฏธ์ง€", id="single") as single_tab:
355
  image_input = gr.Image(
356
  type="filepath",
357
+ label="์ž…๋ ฅ ์ด๋ฏธ์ง€ (์„ ํƒ์‚ฌํ•ญ)",
358
  elem_classes="image-input"
359
  )
360
  gr.HTML('''
361
  <p style="text-align: center; color: #6b7280; font-size: 0.9rem; margin-top: 0.5rem;">
362
+ ๐Ÿ’ก ํ…์ŠคํŠธโ†’์ด๋ฏธ์ง€ ์ƒ์„ฑ์€ ๋น„์›Œ๋‘์„ธ์š”
363
  </p>
364
  ''')
365
 
 
379
  gr.HTML('<h3>โœ๏ธ ํ”„๋กฌํ”„ํŠธ</h3>')
380
  prompt_input = gr.Textbox(
381
  label="",
382
+ info="AI์—๊ฒŒ ์›ํ•˜๋Š” ์ด๋ฏธ์ง€๋ฅผ ์„ค๋ช…ํ•˜์„ธ์š”",
383
+ placeholder="์˜ˆ: ๋ง›์žˆ์–ด ๋ณด์ด๋Š” ํ”ผ์ž, ์šฐ์ฃผ๋ฅผ ๋ฐฐ๊ฒฝ์œผ๋กœ ํ•œ ๊ณ ์–‘์ด, ๋ฏธ๋ž˜์ ์ธ ๋„์‹œ ํ’๊ฒฝ...",
384
  lines=3,
385
  elem_classes="prompt-input"
386
  )
387
 
388
  # Generate Button
389
  generate_button = gr.Button(
390
+ "๐Ÿš€ ์ƒ์„ฑํ•˜๊ธฐ",
391
  variant="primary",
392
  elem_classes="generate-btn"
393
  )
 
396
  with gr.Accordion("๐Ÿ’ก ์˜ˆ์ œ ํ”„๋กฌํ”„ํŠธ", open=False):
397
  gr.Examples(
398
  examples=[
399
+ ["A delicious looking pizza with melting cheese"],
400
+ ["A cat in a spacesuit walking on the moon surface"],
401
+ ["Cyberpunk city at night with neon lights"],
402
+ ["Japanese garden with cherry blossoms in spring"],
403
+ ["Fantasy wizard tower in a magical world"],
404
+ ["Make the scene more dramatic and cinematic"],
405
+ ["Transform this into a watercolor painting style"],
406
  ],
407
  inputs=prompt_input
408
  )
 
411
 
412
  with gr.Column(scale=1):
413
  gr.HTML('<div class="card">')
414
+ gr.HTML('<h3 style="margin-top: 0;">๐ŸŽจ ์ƒ์„ฑ ๊ฒฐ๊ณผ</h3>')
415
 
416
  output_image = gr.Image(
417
  label="",
 
420
  )
421
 
422
  use_image_button = gr.Button(
423
+ "โ™ป๏ธ ์ด ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์Œ ํŽธ์ง‘์— ์‚ฌ์šฉ",
424
  elem_classes="use-btn",
425
  visible=False
426
  )
 
431
  <h4 style="margin-top: 0; color: #0369a1;">๐Ÿ’ก ํŒ</h4>
432
  <ul style="margin: 0; padding-left: 1.5rem; color: #0c4a6e;">
433
  <li>๊ตฌ์ฒด์ ์ด๊ณ  ์ƒ์„ธํ•œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”</li>
434
+ <li>์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๋ฅผ ์žฌ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ˜๋ณต์ ์œผ๋กœ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค</li>
435
+ <li>๋‹ค์ค‘ ์ด๋ฏธ์ง€ ๋ชจ๋“œ๋กœ ์—ฌ๋Ÿฌ ์ฐธ์กฐ ์ด๋ฏธ์ง€๋ฅผ ๊ฒฐํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค</li>
436
+ <li>์˜์–ด ํ”„๋กฌํ”„ํŠธ๊ฐ€ ๋” ์ข‹์€ ๊ฒฐ๊ณผ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค</li>
437
  </ul>
438
  </div>
439
  ''')
 
445
  <div style="text-align: center; margin-top: 2rem; padding: 1rem;
446
  border-top: 1px solid #e5e7eb;">
447
  <p style="color: #6b7280;">
448
+ Made with ๐Ÿ’œ using Replicate API | Powered by Google Nano Banana
449
  </p>
450
  </div>
451
  ''')
 
460
  active_tab: str,
461
  oauth_token: Optional[gr.OAuthToken] = None,
462
  ):
463
+ if not verify_login_status(oauth_token):
464
+ raise gr.Error("๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ƒ๋‹จ์˜ 'Sign in with Hugging Face' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด์ฃผ์„ธ์š”.")
465
  if not prompt:
466
  raise gr.Error("ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.")
467
  if active_tab == "multiple" and multi_images:
 
492
  ):
493
  if not profile:
494
  return gr.update(visible=False), gr.update(visible=False)
495
+ if verify_login_status(oauth_token):
496
  return gr.update(visible=True), gr.update(visible=False)
497
  else:
498
  message = '''
499
+ <div class="login-message">
500
+ <h2>๐Ÿ” ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค</h2>
501
  <p style="font-size: 1.1rem; margin: 1rem 0;">
502
+ ์ด AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Hugging Face ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•ด์ฃผ์„ธ์š”.
503
  </p>
504
  <p style="margin: 1rem 0;">
505
+ ๋กœ๊ทธ์ธํ•˜๋ฉด ๋‹ค์Œ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉ๏ฟฝ๏ฟฝ๏ฟฝ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
506
  </p>
507
  <ul style="text-align: left; display: inline-block; margin: 1rem 0;">
508
+ <li>๐Ÿš€ Google Nano Banana๋ฅผ ํ†ตํ•œ ๊ณ ํ’ˆ์งˆ ์ด๋ฏธ์ง€ ์ƒ์„ฑ</li>
509
+ <li>โšก ๋น ๋ฅธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฐ ํŽธ์ง‘</li>
510
+ <li>๐ŸŽจ ํ…์ŠคํŠธ๋ฅผ ์ด๋ฏธ์ง€๋กœ ๋ณ€ํ™˜</li>
511
+ <li>๐Ÿ”ง ๋‹ค์ค‘ ์ด๋ฏธ์ง€ ํŽธ์ง‘ ๋ฐ ๊ฒฐํ•ฉ</li>
512
  </ul>
513
+ <p style="margin-top: 1.5rem; font-weight: bold;">
514
+ ์ƒ๋‹จ์˜ "Sign in with Hugging Face" ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ ์‹œ์ž‘ํ•˜์„ธ์š”!
515
+ </p>
 
 
 
 
516
  </div>
517
  '''
518
  return gr.update(visible=False), gr.update(visible=True, value=message)
519
 
520
+ demo.load(control_access, inputs=None, outputs=[main_interface, login_message])
521
 
522
  if __name__ == "__main__":
523
  demo.queue(max_size=None, default_concurrency_limit=None)