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('''

๐ŸŒ Real Nano Banana

Google Nano Banana๋กœ ๊ตฌ๋™๋˜๋Š” AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ

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

๐Ÿ” ์ด ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Hugging Face ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•ด์ฃผ์„ธ์š”.

''') login_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=[ ["A delicious looking pizza with melting cheese"], ["A cat in a spacesuit walking on the moon surface"], ["Cyberpunk city at night with neon lights"], ["Japanese garden with cherry blossoms in spring"], ["Fantasy wizard tower in a magical world"], ["Make the scene more dramatic and cinematic"], ["Transform this into a watercolor painting style"], ], 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 ๐Ÿ’œ using Replicate API | Powered by Google Nano Banana

''') 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_login_status(oauth_token): raise gr.Error("๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ƒ๋‹จ์˜ 'Sign in with Hugging Face' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด์ฃผ์„ธ์š”.") 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_login_status(oauth_token): return gr.update(visible=True), gr.update(visible=False) else: message = '''

๐Ÿ” ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค

์ด AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Hugging Face ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•ด์ฃผ์„ธ์š”.

๋กœ๊ทธ์ธํ•˜๋ฉด ๋‹ค์Œ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

์ƒ๋‹จ์˜ "Sign in with Hugging Face" ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ ์‹œ์ž‘ํ•˜์„ธ์š”!

''' return gr.update(visible=False), gr.update(visible=True, value=message) demo.load(control_access, inputs=None, outputs=[main_interface, login_message]) if __name__ == "__main__": demo.queue(max_size=None, default_concurrency_limit=None) demo.launch()