Spaces:
Paused
Paused
| import gradio as gr | |
| import replicate | |
| import os | |
| from PIL import Image | |
| import requests | |
| from io import BytesIO | |
| import time | |
| import tempfile | |
| import base64 | |
| # Set up Replicate API key from environment variable | |
| os.environ['REPLICATE_API_TOKEN'] = os.getenv('REPLICATE_API_TOKEN') | |
| def upload_image_to_hosting(image): | |
| """ | |
| Upload image to multiple hosting services with fallback | |
| """ | |
| # Method 1: Try imgbb.com (most reliable) | |
| try: | |
| buffered = BytesIO() | |
| image.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', | |
| 'image': img_base64, | |
| } | |
| ) | |
| if response.status_code == 200: | |
| data = response.json() | |
| if data.get('success'): | |
| return data['data']['url'] | |
| except: | |
| pass | |
| # Method 2: Try 0x0.st (simple and reliable) | |
| try: | |
| buffered = BytesIO() | |
| image.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: | |
| return response.text.strip() | |
| except: | |
| pass | |
| # Method 3: Fallback to base64 | |
| buffered = BytesIO() | |
| image.save(buffered, format="PNG") | |
| buffered.seek(0) | |
| img_base64 = base64.b64encode(buffered.getvalue()).decode() | |
| return f"data:image/png;base64,{img_base64}" | |
| def process_images(prompt, image1, image2=None): | |
| """ | |
| Process with Nano Banana - works with or without images | |
| """ | |
| if not os.getenv('REPLICATE_API_TOKEN'): | |
| return None, "Please set REPLICATE_API_TOKEN" | |
| try: | |
| # Prepare input for Nano Banana model | |
| model_input = { | |
| "prompt": prompt | |
| } | |
| # Only add image_input if images are provided | |
| if image1 or image2: | |
| image_urls = [] | |
| if image1: | |
| url1 = upload_image_to_hosting(image1) | |
| image_urls.append(url1) | |
| if image2: | |
| url2 = upload_image_to_hosting(image2) | |
| image_urls.append(url2) | |
| model_input["image_input"] = image_urls | |
| status_msg = "✨ Generated with style transfer!" | |
| else: | |
| # No images - text-only generation with Nano Banana | |
| status_msg = "✨ Generated from text!" | |
| # Run Nano Banana model (it should handle both cases) | |
| output = replicate.run( | |
| "google/nano-banana", | |
| input=model_input | |
| ) | |
| if output is None: | |
| return None, "No output received" | |
| # Get the generated image | |
| try: | |
| if hasattr(output, 'read'): | |
| img_data = output.read() | |
| img = Image.open(BytesIO(img_data)) | |
| return img, status_msg | |
| except: | |
| pass | |
| try: | |
| if hasattr(output, 'url'): | |
| output_url = output.url() | |
| response = requests.get(output_url, timeout=30) | |
| if response.status_code == 200: | |
| img = Image.open(BytesIO(response.content)) | |
| return img, status_msg | |
| except: | |
| pass | |
| output_url = None | |
| if isinstance(output, str): | |
| output_url = output | |
| elif isinstance(output, list) and len(output) > 0: | |
| output_url = output[0] | |
| if output_url: | |
| response = requests.get(output_url, timeout=30) | |
| if response.status_code == 200: | |
| img = Image.open(BytesIO(response.content)) | |
| return img, status_msg | |
| return None, "Could not process output" | |
| except Exception as e: | |
| error_msg = str(e) | |
| if "image_input" in error_msg.lower(): | |
| # If the model requires images, provide a helpful message | |
| return None, "Note: This model may require at least one image. Try uploading an image or check the error: " + error_msg[:100] | |
| return None, f"Error: {error_msg[:100]}" | |
| # Enhanced CSS with modern, minimal design | |
| css = """ | |
| .gradio-container { | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| min-height: 100vh; | |
| } | |
| .header-container { | |
| background: linear-gradient(135deg, #ffd93d 0%, #ffb347 100%); | |
| padding: 2.5rem; | |
| border-radius: 24px; | |
| margin-bottom: 2.5rem; | |
| box-shadow: 0 20px 60px rgba(255, 179, 71, 0.25); | |
| } | |
| .logo-text { | |
| font-size: 3.5rem; | |
| font-weight: 900; | |
| color: #2d3436; | |
| text-align: center; | |
| margin: 0; | |
| letter-spacing: -2px; | |
| } | |
| .subtitle { | |
| color: #2d3436; | |
| text-align: center; | |
| font-size: 1rem; | |
| margin-top: 0.5rem; | |
| opacity: 0.8; | |
| } | |
| .mode-indicator { | |
| background: rgba(255, 255, 255, 0.3); | |
| backdrop-filter: blur(10px); | |
| border-radius: 12px; | |
| padding: 0.5rem 1rem; | |
| margin-top: 1rem; | |
| text-align: center; | |
| font-weight: 600; | |
| color: #2d3436; | |
| } | |
| .main-content { | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(20px); | |
| border-radius: 24px; | |
| padding: 2.5rem; | |
| box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); | |
| } | |
| .gr-button-primary { | |
| background: linear-gradient(135deg, #ffd93d 0%, #ffb347 100%) !important; | |
| border: none !important; | |
| color: #2d3436 !important; | |
| font-weight: 700 !important; | |
| font-size: 1.1rem !important; | |
| padding: 1.2rem 2rem !important; | |
| border-radius: 14px !important; | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| width: 100%; | |
| margin-top: 1rem !important; | |
| } | |
| .gr-button-primary:hover { | |
| transform: translateY(-3px) !important; | |
| box-shadow: 0 15px 40px rgba(255, 179, 71, 0.35) !important; | |
| } | |
| .gr-input, .gr-textarea { | |
| background: #ffffff !important; | |
| border: 2px solid #e1e8ed !important; | |
| border-radius: 14px !important; | |
| color: #2d3436 !important; | |
| font-size: 1rem !important; | |
| padding: 0.8rem 1rem !important; | |
| } | |
| .gr-input:focus, .gr-textarea:focus { | |
| border-color: #ffd93d !important; | |
| box-shadow: 0 0 0 4px rgba(255, 217, 61, 0.15) !important; | |
| } | |
| .gr-form { | |
| background: transparent !important; | |
| border: none !important; | |
| } | |
| .gr-panel { | |
| background: #ffffff !important; | |
| border: 2px solid #e1e8ed !important; | |
| border-radius: 16px !important; | |
| padding: 1.5rem !important; | |
| } | |
| .gr-box { | |
| border-radius: 14px !important; | |
| border-color: #e1e8ed !important; | |
| } | |
| label { | |
| color: #636e72 !important; | |
| font-weight: 600 !important; | |
| font-size: 0.85rem !important; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| margin-bottom: 0.5rem !important; | |
| } | |
| .status-text { | |
| font-family: 'SF Mono', 'Monaco', monospace; | |
| color: #00b894; | |
| font-size: 0.9rem; | |
| } | |
| .image-container { | |
| border-radius: 14px !important; | |
| overflow: hidden; | |
| border: 2px solid #e1e8ed !important; | |
| background: #fafbfc !important; | |
| } | |
| footer { | |
| display: none !important; | |
| } | |
| /* Equal sizing for all image containers */ | |
| .image-upload { | |
| min-height: 200px !important; | |
| max-height: 200px !important; | |
| } | |
| .output-image { | |
| min-height: 420px !important; | |
| max-height: 420px !important; | |
| } | |
| /* Ensure consistent spacing */ | |
| .gr-row { | |
| gap: 1rem !important; | |
| } | |
| .gr-column { | |
| gap: 1rem !important; | |
| } | |
| .info-box { | |
| background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%); | |
| border-radius: 12px; | |
| padding: 1rem; | |
| margin-bottom: 1rem; | |
| border-left: 4px solid #2196f3; | |
| } | |
| """ | |
| with gr.Blocks(css=css, theme=gr.themes.Base()) as demo: | |
| with gr.Column(elem_classes="header-container"): | |
| gr.HTML(""" | |
| <h1 class="logo-text">🍌 Free Nano Banana</h1> | |
| <p class="subtitle">AI-Powered Image Generation & Style Transfer</p> | |
| <div class="mode-indicator"> | |
| 💡 Works with or without images - Just describe what you want! | |
| </div> | |
| <div style="display: flex; justify-content: center; align-items: center; gap: 10px; margin-top: 20px;"> | |
| <a href="https://huggingface.co/spaces/ginigen/Nano-Banana-PRO" target="_blank"> | |
| <img src="https://img.shields.io/static/v1?label=NANO%20BANANA&message=PRO&color=%230000ff&labelColor=%23800080&logo=HUGGINGFACE&logoColor=white&style=for-the-badge" alt="badge"> | |
| </a> | |
| <a href="https://huggingface.co/spaces/openfree/Nano-Banana-Upscale" target="_blank"> | |
| <img src="https://img.shields.io/static/v1?label=NANO%20BANANA&message=UPSCALE&color=%230000ff&labelColor=%23800080&logo=GOOGLE&logoColor=white&style=for-the-badge" alt="Nano Banana Upscale"> | |
| </a> | |
| <a href="https://huggingface.co/spaces/aiqtech/Nano-Banana-API" target="_blank"> | |
| <img src="https://img.shields.io/static/v1?label=NANO%20BANANA&message=API&color=%230000ff&labelColor=%23800080&logo=GOOGLE&logoColor=white&style=for-the-badge" alt="Nano Banana API"> | |
| </a> | |
| <a href="https://huggingface.co/spaces/ginigen/Nano-Banana-Video" target="_blank"> | |
| <img src="https://img.shields.io/static/v1?label=NANO%20BANANA&message=VIDEO&color=%230000ff&labelColor=%23800080&logo=GOOGLE&logoColor=white&style=for-the-badge" alt="Nano Banana VIDEO"> | |
| </a> | |
| <a href="https://discord.gg/openfreeai" target="_blank"> | |
| <img src="https://img.shields.io/static/v1?label=Discord&message=Openfree%20AI&color=%230000ff&labelColor=%23800080&logo=discord&logoColor=white&style=for-the-badge" alt="Discord Openfree AI"> | |
| </a> | |
| </div> | |
| """) | |
| with gr.Column(elem_classes="main-content"): | |
| # Info box | |
| gr.HTML(""" | |
| <div class="info-box"> | |
| <strong>How to use Nano Banana:</strong><br> | |
| • <b>Text Generation:</b> Just enter a prompt - no images needed!<br> | |
| • <b>Style Transfer:</b> Add images to apply specific styles<br> | |
| • The powerful Nano Banana model handles both modes seamlessly | |
| </div> | |
| """) | |
| with gr.Row(equal_height=True): | |
| # Left Column - Inputs | |
| with gr.Column(scale=1): | |
| prompt = gr.Textbox( | |
| label="Prompt / Style Description", | |
| placeholder="Describe what you want to generate...", | |
| lines=3, | |
| value="A beautiful banana-themed paradise with golden sunset", | |
| elem_classes="prompt-input" | |
| ) | |
| with gr.Row(equal_height=True): | |
| image1 = gr.Image( | |
| label="Primary Image (Optional)", | |
| type="pil", | |
| height=200, | |
| elem_classes="image-container image-upload" | |
| ) | |
| image2 = gr.Image( | |
| label="Secondary Image (Optional)", | |
| type="pil", | |
| height=200, | |
| elem_classes="image-container image-upload" | |
| ) | |
| generate_btn = gr.Button( | |
| "Generate Magic ✨", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| # Right Column - Output | |
| with gr.Column(scale=1): | |
| output_image = gr.Image( | |
| label="Generated Result", | |
| type="pil", | |
| height=420, | |
| elem_classes="image-container output-image" | |
| ) | |
| status = gr.Textbox( | |
| label="Status", | |
| interactive=False, | |
| lines=1, | |
| elem_classes="status-text", | |
| value="Ready to generate with Nano Banana..." | |
| ) | |
| # Event handler | |
| generate_btn.click( | |
| fn=process_images, | |
| inputs=[prompt, image1, image2], | |
| outputs=[output_image, status] | |
| ) | |
| # Examples for text-only generation | |
| gr.Examples( | |
| examples=[ | |
| ["A majestic banana kingdom floating in the clouds", None, None], | |
| ["Cyberpunk banana city with neon lights", None, None], | |
| ["Ancient banana temple in a mystical forest", None, None], | |
| ["Banana spaceship exploring the cosmos", None, None], | |
| ["Make the sheets in the style of the logo. Make the scene natural.", None, None], | |
| ], | |
| inputs=[prompt, image1, image2], | |
| label="Text Generation Examples (No Images Needed!)" | |
| ) | |
| # Launch | |
| if __name__ == "__main__": | |
| demo.launch( | |
| share=True, | |
| server_name="0.0.0.0", | |
| server_port=7860 | |
| ) |