๐ Real Nano Banana
Google Gemini API๋ก ๊ตฌ๋๋๋ AI ์ด๋ฏธ์ง ๋ถ์๊ธฐ
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('''
Google Gemini API๋ก ๊ตฌ๋๋๋ AI ์ด๋ฏธ์ง ๋ถ์๊ธฐ
โ ๏ธ ์ฐธ๊ณ : Gemini API๋ ์ด๋ฏธ์ง ์์ฑ์ด ์๋ ๋ถ์์ ์ ๊ณตํฉ๋๋ค. ์ค์ ์ด๋ฏธ์ง ์์ฑ์ ์ํ์๋ฉด DALL-E, Midjourney, Stable Diffusion ๋ฑ์ ์ฌ์ฉํ์ธ์.
๐ ์ด ์คํ์ด์ค๋ Hugging Face PRO ์ฌ์ฉ์ ์ ์ฉ์ ๋๋ค. PRO ๊ตฌ๋ ํ๊ธฐ
๐ก ํ ์คํธ ์ค๋ช ๋ง ์ํ์๋ฉด ๋น์๋์ธ์
''') 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('Made with ๐ by Hugging Face PRO | Powered by Google Gemini API