๐ Real Nano Banana
Google Nano Banana๋ก ๊ตฌ๋๋๋ AI ์ด๋ฏธ์ง ์์ฑ๊ธฐ
import gradio as gr import replicate import os from typing import Optional, List from huggingface_hub import whoami from PIL import Image import requests from io import BytesIO import tempfile # --- Replicate API Configuration --- REPLICATE_API_TOKEN = os.getenv("REPLICATE_API_TOKEN") if not REPLICATE_API_TOKEN: raise ValueError("REPLICATE_API_TOKEN environment variable is not set.") # Initialize Replicate client os.environ["REPLICATE_API_TOKEN"] = REPLICATE_API_TOKEN def verify_login_status(token: Optional[gr.OAuthToken]) -> bool: """Verifies if the user is logged in to Hugging Face.""" if not token: return False try: user_info = whoami(token=token.token) return True if user_info else False except Exception as e: print(f"Could not verify user's login status: {e}") return False def run_single_image_logic(prompt: str, image_path: Optional[str] = None, progress=gr.Progress()) -> str: """Handles text-to-image or single image-to-image using Replicate's Nano Banana.""" try: progress(0.2, desc="๐จ ์ค๋น ์ค...") # Prepare input for Replicate API input_data = { "prompt": prompt } # If there's an input image, add it to the input if image_path: # Upload the local image file and get a URL # For Replicate, we need to provide URLs, not local paths # We'll read the file and create a temporary URL or use file directly input_data["image_input"] = [image_path] progress(0.5, desc="โจ ์์ฑ ์ค...") # Run the model on Replicate output = replicate.run( "google/nano-banana", input=input_data ) progress(0.8, desc="๐ผ๏ธ ๋ง๋ฌด๋ฆฌ ์ค...") # Handle the output if output: # If output is a FileOutput object, get the URL if hasattr(output, 'url'): image_url = output.url() elif isinstance(output, str): image_url = output elif isinstance(output, list) and len(output) > 0: image_url = output[0] else: raise ValueError("Unexpected output format from Replicate") # Download the image from URL response = requests.get(image_url) response.raise_for_status() # Save to temporary file img = Image.open(BytesIO(response.content)) with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmpfile: img.save(tmpfile.name) progress(1.0, desc="โ ์๋ฃ!") return tmpfile.name else: raise ValueError("No output received from Replicate 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 editing by sending a list of images and a prompt. """ if not images: raise gr.Error("'์ฌ๋ฌ ์ด๋ฏธ์ง' ํญ์์ ์ต์ ํ ๊ฐ์ ์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํด์ฃผ์ธ์.") try: progress(0.2, desc="๐จ ์ด๋ฏธ์ง ์ค๋น ์ค...") # Prepare input for Replicate API with multiple images input_data = { "prompt": prompt, "image_input": images # Pass the list of image paths directly } progress(0.5, desc="โจ ์์ฑ ์ค...") # Run the model on Replicate output = replicate.run( "google/nano-banana", input=input_data ) progress(0.8, desc="๐ผ๏ธ ๋ง๋ฌด๋ฆฌ ์ค...") # Handle the output if output: # If output is a FileOutput object, get the URL if hasattr(output, 'url'): image_url = output.url() elif isinstance(output, str): image_url = output elif isinstance(output, list) and len(output) > 0: image_url = output[0] else: raise ValueError("Unexpected output format from Replicate") # Download the image from URL response = requests.get(image_url) response.raise_for_status() # Save to temporary file img = Image.open(BytesIO(response.content)) with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmpfile: img.save(tmpfile.name) progress(1.0, desc="โ ์๋ฃ!") return tmpfile.name else: raise ValueError("No output received from Replicate 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; } /* Login Message Styling */ .login-message { background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); border-radius: 1rem; padding: 2rem; text-align: center; border: 2px solid #f59e0b; } .dark .login-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 Nano Banana๋ก ๊ตฌ๋๋๋ AI ์ด๋ฏธ์ง ์์ฑ๊ธฐ
๐ ์ด ์๋น์ค๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด Hugging Face ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๐ก ํ ์คํธโ์ด๋ฏธ์ง ์์ฑ์ ๋น์๋์ธ์
''') 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 ๐ using Replicate API | Powered by Google Nano Banana