π Real Nano Banana
AI Image Generator powered by Google Nano Banana
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 import base64 # --- 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 upload_image_to_hosting(image_path: str) -> str: """ Upload image to hosting service and return URL. Using multiple fallback methods for reliability. """ # Open the image img = Image.open(image_path) # Method 1: Try imgbb.com (most reliable) try: buffered = BytesIO() img.save(buffered, format="PNG") buffered.seek(0) img_base64 = base64.b64encode(buffered.getvalue()).decode() response = requests.post( "https://api.imgbb.com/1/upload", data={ 'key': '6d207e02198a847aa98d0a2a901485a5', # Free API key 'image': img_base64, } ) if response.status_code == 200: data = response.json() if data.get('success'): return data['data']['url'] except Exception as e: print(f"imgbb upload failed: {e}") # Method 2: Try 0x0.st (simple and reliable) try: buffered = BytesIO() img.save(buffered, format="PNG") buffered.seek(0) files = {'file': ('image.png', buffered, 'image/png')} response = requests.post("https://0x0.st", files=files) if response.status_code == 200: url = response.text.strip() if url.startswith('http'): return url except Exception as e: print(f"0x0.st upload failed: {e}") # Method 3: Fallback to data URI (last resort) buffered = BytesIO() img.save(buffered, format="PNG") buffered.seek(0) img_base64 = base64.b64encode(buffered.getvalue()).decode() return f"data:image/png;base64,{img_base64}" def image_to_data_uri(image_path: str) -> str: """Convert local image file to data URI format (kept for backwards compatibility).""" with open(image_path, "rb") as img_file: img_data = img_file.read() img_base64 = base64.b64encode(img_data).decode('utf-8') # Get the image format img = Image.open(image_path) img_format = img.format.lower() if img.format else 'png' # Create data URI data_uri = f"data:image/{img_format};base64,{img_base64}" return data_uri def process_output(output, progress=gr.Progress()) -> str: """Process the output from Replicate API and return a local file path.""" try: # Check if output has a url attribute (FileObject) if hasattr(output, 'url'): # If url is a method, call it; if it's a property, just access it image_url = output.url() if callable(output.url) else output.url # If output is already a string URL elif isinstance(output, str): image_url = output # If output is a list of URLs elif isinstance(output, list) and len(output) > 0: # Check first item in list first_item = output[0] if hasattr(first_item, 'url'): image_url = first_item.url() if callable(first_item.url) else first_item.url else: image_url = first_item else: raise ValueError(f"Unexpected output format from Replicate: {type(output)}") # 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="β Complete!") return tmpfile.name except Exception as e: print(f"Error processing output: {e}") raise ValueError(f"Failed to process output: {str(e)}") 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="π¨ Preparing...") # Prepare input for Replicate API input_data = { "prompt": prompt } # If there's an input image, upload it to get a proper URL if image_path: progress(0.3, desc="π€ Uploading image...") # Upload to hosting service for proper URL image_url = upload_image_to_hosting(image_path) if image_url.startswith('http'): print(f"Image uploaded successfully: {image_url[:50]}...") else: print("Using data URI fallback") input_data["image_input"] = [image_url] progress(0.5, desc="β¨ Generating...") # Run the model on Replicate # Note: Replace "google/nano-banana" with actual model name if it doesn't exist # Examples of real models: "stability-ai/stable-diffusion", "tencentarc/photomaker", etc. output = replicate.run( "google/nano-banana", # This might need to be changed to a real model input=input_data ) progress(0.8, desc="πΌοΈ Finalizing...") # Handle the output - output is already a URL string or FileObject if output: return process_output(output, progress) else: raise ValueError("No output received from Replicate API") except replicate.exceptions.ModelError as e: print(f"Replicate Model Error: {e}") error_msg = str(e) if "does not exist" in error_msg.lower() or "not found" in error_msg.lower(): raise gr.Error("The specified model 'google/nano-banana' was not found. Please check the model name and ensure your Replicate API token has access.") else: raise gr.Error(f"Model error: {error_msg[:200]}") except Exception as e: print(f"Error details: {e}") print(f"Error type: {type(e)}") if 'output' in locals(): print(f"Output value: {output}") print(f"Output type: {type(output)}") raise gr.Error(f"Image generation failed: {str(e)[:200]}") 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. Note: Since the actual model might not support multiple images, we'll process only the first image or combine them. """ if not images: raise gr.Error("Please upload at least one image in the 'Multiple Images' tab.") try: progress(0.2, desc="π¨ Preparing images...") # For now, we'll use only the first image since the model might not support multiple # You can modify this based on the actual model's capabilities image_path = images[0] if isinstance(image_path, (list, tuple)): image_path = image_path[0] progress(0.3, desc="π€ Uploading image...") image_url = upload_image_to_hosting(image_path) if image_url.startswith('http'): print(f"Image uploaded successfully: {image_url[:50]}...") else: print("Using data URI fallback") # Prepare input for Replicate API # Using single image format since model might not support multiple input_data = { "prompt": prompt, "image_input": [image_url] # Send as array with single image } progress(0.5, desc="β¨ Generating...") # Run the model on Replicate # Note: Replace "google/nano-banana" with actual model name # Examples of real models: "stability-ai/stable-diffusion", "tencentarc/photomaker", etc. output = replicate.run( "google/nano-banana", # This might need to be changed to a real model input=input_data ) progress(0.8, desc="πΌοΈ Finalizing...") # Handle the output using the process_output function if output: return process_output(output, progress) else: raise ValueError("No output received from Replicate API") except replicate.exceptions.ModelError as e: print(f"Replicate Model Error: {e}") error_msg = str(e) if "does not exist" in error_msg.lower() or "not found" in error_msg.lower(): raise gr.Error("The specified model 'google/nano-banana' was not found. Please check the model name.") elif "no image content" in error_msg.lower(): raise gr.Error("Failed to process images. The model may not support the provided image format or multiple images.") else: raise gr.Error(f"Model error: {error_msg[:200]}") except Exception as e: print(f"Multi-image error details: {e}") print(f"Input data sent: {input_data if 'input_data' in locals() else 'Not set'}") print(f"Output value: {output if 'output' in locals() else 'Not set'}") raise gr.Error(f"Image generation failed: {str(e)[:200]}") # --- 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('''
''') # μ¬κΈ°μ λ«λ λ°μ΄ν μΆκ° # Login Notice gr.HTML('''π Please sign in with your Hugging Face account to use this service.
π‘ Leave empty for text-to-image generation
''') with gr.TabItem("π¨ Multiple Images", id="multiple") as multi_tab: gallery_input = gr.Gallery( label="Input Images (Max 2 images)", file_types=["image"], elem_classes="gallery-input" ) gr.HTML('''π‘ Upload up to 2 images for combination/editing
''') # Prompt Input gr.HTML('Made with π using Replicate API | Powered by Google Nano Banana