import gradio as gr from google import genai from google.genai import types import os from typing import Optional, List from huggingface_hub import whoami from PIL import Image from io import BytesIO import tempfile # --- Google Gemini API Configuration --- # Use GEMINI_API_KEY if available, otherwise fall back to GOOGLE_API_KEY GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY") API_KEY = GEMINI_API_KEY or GOOGLE_API_KEY if not API_KEY: raise ValueError("Neither GEMINI_API_KEY nor GOOGLE_API_KEY environment variable is set.") client = genai.Client( api_key=API_KEY, ) # Note: Gemini models don't directly generate images - they analyze/describe them # For image generation, you'd need to use a different API like Imagen # This code is updated to work with text generation about images GEMINI_MODEL_NAME = 'gemini-2.0-flash-exp' # Updated model name def verify_pro_status(token: Optional[gr.OAuthToken]) -> bool: """Verifies if the user is a Hugging Face PRO user or part of an enterprise org.""" if not token: return False try: user_info = whoami(token=token.token) if user_info.get("isPro", False): return True orgs = user_info.get("orgs", []) if any(org.get("isEnterprise", False) for org in orgs): return True return False except Exception as e: print(f"Could not verify user's PRO/Enterprise status: {e}") return False def _extract_image_data_from_response(response) -> Optional[bytes]: """Helper to extract image data from the model's response.""" # Debug: Print response structure print(f"Response type: {type(response)}") # Note: Gemini doesn't generate images directly # This would need to be replaced with actual image generation API # For now, return None to indicate no image was generated if hasattr(response, 'text'): print(f"Response text: {response.text[:200] if response.text else 'Empty'}") return None def run_single_image_logic(prompt: str, image_path: Optional[str] = None, progress=gr.Progress()) -> str: """Handles text or image analysis using Google Gemini.""" try: progress(0.2, desc="๐ŸŽจ ์ค€๋น„ ์ค‘...") contents = [] if image_path: # Image analysis input_image = Image.open(image_path) contents.append(input_image) contents.append(f"Describe what you see and how it could be modified based on: {prompt}") else: # Text-only prompt contents.append(f"Describe an image concept for: {prompt}") progress(0.5, desc="โœจ ์ƒ์„ฑ ์ค‘...") # Remove the generation_config parameter that's causing the error # Use the simpler API call format try: response = client.models.generate_content( model=GEMINI_MODEL_NAME, contents=contents, ) except Exception as api_error: # Fallback: try with just the contents as a simple string/list print(f"First attempt failed: {api_error}") if image_path: # For image input, we need to handle it differently # The API might expect a different format raise gr.Error("์ด๋ฏธ์ง€ ์ž…๋ ฅ์€ ํ˜„์žฌ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Gemini API๋Š” ์ด๋ฏธ์ง€ ์ƒ์„ฑ์ด ์•„๋‹Œ ๋ถ„์„์šฉ์ž…๋‹ˆ๋‹ค.") else: # For text-only, try a simpler approach response = client.models.generate_content( model=GEMINI_MODEL_NAME, contents=prompt ) progress(0.8, desc="๐Ÿ–ผ๏ธ ๋งˆ๋ฌด๋ฆฌ ์ค‘...") # Since Gemini doesn't generate images, we'll need to handle this differently # For demonstration, create a placeholder or use a different service if hasattr(response, 'text') and response.text: # Return the text response for now # In production, you'd call an actual image generation API here description = response.text # Create a placeholder image with the description from PIL import Image, ImageDraw, ImageFont img = Image.new('RGB', (512, 512), color='white') draw = ImageDraw.Draw(img) # Add text to explain the situation text = "Gemini API๋Š” ์ด๋ฏธ์ง€ ์ƒ์„ฑ์„ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.\n\n" text += "์ƒ์„ฑ๋œ ์„ค๋ช…:\n" + description[:200] + "..." # Simple text wrapping y_position = 50 for line in text.split('\n'): draw.text((20, y_position), line, fill='black') y_position += 30 # Save the placeholder image with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmpfile: img.save(tmpfile.name) progress(1.0, desc="โœ… ์™„๋ฃŒ!") return tmpfile.name else: raise ValueError("API ์‘๋‹ต์„ ๋ฐ›์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.") except Exception as e: print(f"Error details: {e}") print(f"Error type: {type(e)}") raise gr.Error(f"์ฒ˜๋ฆฌ ์‹คํŒจ: {e}") def run_multi_image_logic(prompt: str, images: List[str], progress=gr.Progress()) -> str: """ Handles multi-image analysis. """ if not images: raise gr.Error("'์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€' ํƒญ์—์„œ ์ตœ์†Œ ํ•œ ๊ฐœ์˜ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.") try: progress(0.2, desc="๐ŸŽจ ์ด๋ฏธ์ง€ ์ค€๋น„ ์ค‘...") contents = [] for image_path in images: if isinstance(image_path, (list, tuple)): image_path = image_path[0] contents.append(Image.open(image_path)) contents.append(f"Analyze these images based on: {prompt}") progress(0.5, desc="โœจ ๋ถ„์„ ์ค‘...") # Simple API call without generation_config response = client.models.generate_content( model=GEMINI_MODEL_NAME, contents=contents, ) progress(0.8, desc="๐Ÿ–ผ๏ธ ๋งˆ๋ฌด๋ฆฌ ์ค‘...") # Create a result image with the analysis if hasattr(response, 'text') and response.text: from PIL import Image, ImageDraw img = Image.new('RGB', (512, 512), color='white') draw = ImageDraw.Draw(img) text = "๋‹ค์ค‘ ์ด๋ฏธ์ง€ ๋ถ„์„ ๊ฒฐ๊ณผ:\n\n" text += response.text[:300] + "..." y_position = 50 for line in text.split('\n'): draw.text((20, y_position), line, fill='black') y_position += 30 with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmpfile: img.save(tmpfile.name) progress(1.0, desc="โœ… ์™„๋ฃŒ!") return tmpfile.name else: raise ValueError("API ์‘๋‹ต์„ ๋ฐ›์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.") except Exception as e: print(f"Multi-image error details: {e}") raise gr.Error(f"์ฒ˜๋ฆฌ ์‹คํŒจ: {e}") # --- Gradio App UI --- css = ''' /* Header Styling */ .main-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 2rem; border-radius: 1rem; margin-bottom: 2rem; box-shadow: 0 10px 30px rgba(0,0,0,0.1); } .header-title { font-size: 2.5rem !important; font-weight: bold; color: white; text-align: center; margin: 0 !important; text-shadow: 2px 2px 4px rgba(0,0,0,0.2); } .header-subtitle { color: rgba(255,255,255,0.9); text-align: center; margin-top: 0.5rem !important; font-size: 1.1rem; } /* Card Styling */ .card { background: white; border-radius: 1rem; padding: 1.5rem; box-shadow: 0 4px 6px rgba(0,0,0,0.1); border: 1px solid rgba(0,0,0,0.05); } .dark .card { background: #1f2937; border: 1px solid #374151; } /* Tab Styling */ .tabs { border-radius: 0.5rem; overflow: hidden; margin-bottom: 1rem; } .tabitem { padding: 1rem !important; } button.selected { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; color: white !important; } /* Button Styling */ .generate-btn { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; border: none !important; color: white !important; font-size: 1.1rem !important; font-weight: 600 !important; padding: 0.8rem 2rem !important; border-radius: 0.5rem !important; cursor: pointer !important; transition: all 0.3s ease !important; width: 100% !important; margin-top: 1rem !important; } .generate-btn:hover { transform: translateY(-2px) !important; box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4) !important; } .use-btn { background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important; border: none !important; color: white !important; font-weight: 600 !important; padding: 0.6rem 1.5rem !important; border-radius: 0.5rem !important; cursor: pointer !important; transition: all 0.3s ease !important; width: 100% !important; } .use-btn:hover { transform: translateY(-1px) !important; box-shadow: 0 5px 15px rgba(16, 185, 129, 0.4) !important; } /* Input Styling */ .prompt-input textarea { border-radius: 0.5rem !important; border: 2px solid #e5e7eb !important; padding: 0.8rem !important; font-size: 1rem !important; transition: border-color 0.3s ease !important; } .prompt-input textarea:focus { border-color: #667eea !important; outline: none !important; } .dark .prompt-input textarea { border-color: #374151 !important; background: #1f2937 !important; } /* Image Output Styling */ #output { border-radius: 0.5rem !important; overflow: hidden !important; box-shadow: 0 4px 6px rgba(0,0,0,0.1) !important; } /* Progress Bar Styling */ .progress-bar { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; } /* Examples Styling */ .examples { background: #f9fafb; border-radius: 0.5rem; padding: 1rem; margin-top: 1rem; } .dark .examples { background: #1f2937; } /* Pro Message Styling */ .pro-message { background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); border-radius: 1rem; padding: 2rem; text-align: center; border: 2px solid #f59e0b; } .dark .pro-message { background: linear-gradient(135deg, #7c2d12 0%, #92400e 100%); border-color: #f59e0b; } /* Emoji Animations */ @keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } } .emoji-icon { display: inline-block; animation: bounce 2s infinite; } /* Responsive Design */ @media (max-width: 768px) { .header-title { font-size: 2rem !important; } .main-container { padding: 1rem !important; } } ''' with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo: # Header gr.HTML('''

๐ŸŒ Real Nano Banana

Google Gemini API๋กœ ๊ตฌ๋™๋˜๋Š” AI ์ด๋ฏธ์ง€ ๋ถ„์„๊ธฐ

''') # Important Notice gr.HTML('''

โš ๏ธ ์ฐธ๊ณ : Gemini API๋Š” ์ด๋ฏธ์ง€ ์ƒ์„ฑ์ด ์•„๋‹Œ ๋ถ„์„์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ์ด๋ฏธ์ง€ ์ƒ์„ฑ์„ ์›ํ•˜์‹œ๋ฉด DALL-E, Midjourney, Stable Diffusion ๋“ฑ์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

''') # Pro User Notice gr.HTML('''

๐ŸŒŸ ์ด ์ŠคํŽ˜์ด์Šค๋Š” Hugging Face PRO ์‚ฌ์šฉ์ž ์ „์šฉ์ž…๋‹ˆ๋‹ค. PRO ๊ตฌ๋…ํ•˜๊ธฐ

''') pro_message = gr.Markdown(visible=False) main_interface = gr.Column(visible=False, elem_classes="main-container") with main_interface: with gr.Row(): with gr.Column(scale=1): gr.HTML('
') # Mode Selection gr.HTML('

๐Ÿ“ธ ๋ชจ๋“œ ์„ ํƒ

') active_tab_state = gr.State(value="single") with gr.Tabs(elem_classes="tabs") as tabs: with gr.TabItem("๐Ÿ–ผ๏ธ ๋‹จ์ผ ์ด๋ฏธ์ง€", id="single") as single_tab: image_input = gr.Image( type="filepath", label="์ž…๋ ฅ ์ด๋ฏธ์ง€", elem_classes="image-input" ) gr.HTML('''

๐Ÿ’ก ํ…์ŠคํŠธ ์„ค๋ช…๋งŒ ์›ํ•˜์‹œ๋ฉด ๋น„์›Œ๋‘์„ธ์š”

''') with gr.TabItem("๐ŸŽจ ๋‹ค์ค‘ ์ด๋ฏธ์ง€", id="multiple") as multi_tab: gallery_input = gr.Gallery( label="์ž…๋ ฅ ์ด๋ฏธ์ง€๋“ค", file_types=["image"], elem_classes="gallery-input" ) gr.HTML('''

๐Ÿ’ก ์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€๋ฅผ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญํ•˜์„ธ์š”

''') # Prompt Input gr.HTML('

โœ๏ธ ํ”„๋กฌํ”„ํŠธ

') prompt_input = gr.Textbox( label="", info="AI์—๊ฒŒ ์›ํ•˜๋Š” ๊ฒƒ์„ ์„ค๋ช…ํ•˜์„ธ์š”", placeholder="์˜ˆ: ์ด ์ด๋ฏธ์ง€๋ฅผ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”, ๋ฌด์—‡์ด ๋ณด์ด๋‚˜์š”?, ์ด๋ฏธ์ง€๋ฅผ ์–ด๋–ป๊ฒŒ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?", lines=3, elem_classes="prompt-input" ) # Generate Button generate_button = gr.Button( "๐Ÿš€ ๋ถ„์„ํ•˜๊ธฐ", variant="primary", elem_classes="generate-btn" ) # Examples with gr.Accordion("๐Ÿ’ก ์˜ˆ์ œ ํ”„๋กฌํ”„ํŠธ", open=False): gr.Examples( examples=[ ["์ด ์ด๋ฏธ์ง€์—์„œ ๋ฌด์—‡์ด ๋ณด์ด๋‚˜์š”?"], ["์ด๋ฏธ์ง€์˜ ์ƒ‰์ƒ๊ณผ ๊ตฌ์„ฑ์„ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”"], ["์ด ์žฅ๋ฉด์„ ๋” ๊ทน์ ์œผ๋กœ ๋งŒ๋“ค๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ์š”?"], ["์ด๋ฏธ์ง€์˜ ๋ถ„์œ„๊ธฐ์™€ ๊ฐ์ •์„ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”"], ["๊ธฐ์ˆ ์ ์ธ ๊ด€์ ์—์„œ ์ด ์ด๋ฏธ์ง€๋ฅผ ํ‰๊ฐ€ํ•ด์ฃผ์„ธ์š”"], ], inputs=prompt_input ) gr.HTML('
') with gr.Column(scale=1): gr.HTML('
') gr.HTML('

๐ŸŽจ ๋ถ„์„ ๊ฒฐ๊ณผ

') output_image = gr.Image( label="", interactive=False, elem_id="output" ) use_image_button = gr.Button( "โ™ป๏ธ ์ด ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์Œ ๋ถ„์„์— ์‚ฌ์šฉ", elem_classes="use-btn", visible=False ) # Tips gr.HTML('''

๐Ÿ’ก ํŒ

''') gr.HTML('
') # Footer gr.HTML('''

Made with ๐Ÿ’œ by Hugging Face PRO | Powered by Google Gemini API

''') login_button = gr.LoginButton() # --- Event Handlers --- def unified_generator( prompt: str, single_image: Optional[str], multi_images: Optional[List[str]], active_tab: str, oauth_token: Optional[gr.OAuthToken] = None, ): if not verify_pro_status(oauth_token): raise gr.Error("์•ก์„ธ์Šค ๊ฑฐ๋ถ€: ์ด ์„œ๋น„์Šค๋Š” PRO ์‚ฌ์šฉ์ž ์ „์šฉ์ž…๋‹ˆ๋‹ค.") if not prompt: raise gr.Error("ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.") if active_tab == "multiple" and multi_images: result = run_multi_image_logic(prompt, multi_images) else: result = run_single_image_logic(prompt, single_image) return result, gr.update(visible=True) single_tab.select(lambda: "single", None, active_tab_state) multi_tab.select(lambda: "multiple", None, active_tab_state) generate_button.click( unified_generator, inputs=[prompt_input, image_input, gallery_input, active_tab_state], outputs=[output_image, use_image_button], ) use_image_button.click( lambda img: (img, gr.update(visible=False)), inputs=[output_image], outputs=[image_input, use_image_button] ) # --- Access Control Logic --- def control_access( profile: Optional[gr.OAuthProfile] = None, oauth_token: Optional[gr.OAuthToken] = None ): if not profile: return gr.update(visible=False), gr.update(visible=False) if verify_pro_status(oauth_token): return gr.update(visible=True), gr.update(visible=False) else: message = '''

โœจ PRO ์‚ฌ์šฉ์ž ์ „์šฉ ๊ธฐ๋Šฅ

์ด ๊ฐ•๋ ฅํ•œ AI ์ด๋ฏธ์ง€ ๋ถ„์„ ๋„๊ตฌ๋Š” Hugging Face PRO ๋ฉค๋ฒ„ ์ „์šฉ์ž…๋‹ˆ๋‹ค.

PRO ๊ตฌ๋…์œผ๋กœ ๋‹ค์Œ์„ ๋ˆ„๋ฆฌ์„ธ์š”:

๐ŸŒŸ ์ง€๊ธˆ PRO ๋ฉค๋ฒ„ ๋˜๊ธฐ
''' return gr.update(visible=False), gr.update(visible=True, value=message) demo.load(control_access, inputs=None, outputs=[main_interface, pro_message]) if __name__ == "__main__": demo.queue(max_size=None, default_concurrency_limit=None) demo.launch()