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 --- GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", "") if not GOOGLE_API_KEY: raise ValueError("GOOGLE_API_KEY environment variable not set.") client = genai.Client( api_key=os.environ.get("GEMINI_API_KEY"), ) GEMINI_MODEL_NAME = 'gemini-2.5-flash-image-preview' 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.""" if hasattr(response, 'candidates') and response.candidates: for candidate in response.candidates: if hasattr(candidate, 'content') and hasattr(candidate.content, 'parts') and candidate.content.parts: for part in candidate.content.parts: if hasattr(part, 'inline_data') and hasattr(part.inline_data, 'data'): return part.inline_data.data return None 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 Google Gemini.""" try: progress(0.2, desc="๐ŸŽจ ์ค€๋น„ ์ค‘...") contents = [prompt] if image_path: input_image = Image.open(image_path) contents.append(input_image) progress(0.5, desc="โœจ ์ƒ์„ฑ ์ค‘...") response = client.models.generate_content( model=GEMINI_MODEL_NAME, contents=contents, ) progress(0.8, desc="๐Ÿ–ผ๏ธ ๋งˆ๋ฌด๋ฆฌ ์ค‘...") image_data = _extract_image_data_from_response(response) if not image_data: raise ValueError("No image data found in the model response.") # Save the generated image to a temporary file to return its path pil_image = Image.open(BytesIO(image_data)) with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmpfile: pil_image.save(tmpfile.name) progress(1.0, desc="โœ… ์™„๋ฃŒ!") return tmpfile.name except Exception as 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="๐ŸŽจ ์ด๋ฏธ์ง€ ์ค€๋น„ ์ค‘...") contents = [Image.open(image_path[0]) for image_path in images] contents.append(prompt) progress(0.5, desc="โœจ ์ƒ์„ฑ ์ค‘...") response = client.models.generate_content( model=GEMINI_MODEL_NAME, contents=contents, ) progress(0.8, desc="๐Ÿ–ผ๏ธ ๋งˆ๋ฌด๋ฆฌ ์ค‘...") image_data = _extract_image_data_from_response(response) if not image_data: raise ValueError("No image data found in the model response.") pil_image = Image.open(BytesIO(image_data)) with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmpfile: pil_image.save(tmpfile.name) progress(1.0, desc="โœ… ์™„๋ฃŒ!") return tmpfile.name except Exception as 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-container { background: #f9fafb; border-radius: 0.5rem; padding: 1rem; margin-top: 1rem; } .dark .examples-container { 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 2.5 Flash Image Preview๋กœ ๊ตฌ๋™๋˜๋Š” AI ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ

''') # 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, elem_classes="examples-container" ) 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 2.5 Flash

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