Spaces:
Running
Running
import gradio as gr | |
import requests | |
import os | |
import base64 | |
import json | |
from io import BytesIO | |
from PIL import Image | |
# --- Configuration --- | |
# Your Hugging Face Space URL. Set this in your Space secrets for best practice. | |
YOUR_SITE_URL = os.environ.get("YOUR_SITE_URL", "https://huggingface.co/spaces/broadfield-dev/nano-banana") | |
YOUR_SITE_NAME = "Gemini 2.5 Image Editor Demo" | |
# Fallback API key from Hugging Face secrets | |
FALLBACK_API_KEY = os.environ.get("OPENROUTER_API_KEY") | |
def edit_image(image, prompt, api_key): | |
""" | |
Sends an image and a prompt to the OpenRouter API and returns the edited image. | |
""" | |
final_api_key = api_key if api_key else FALLBACK_API_KEY | |
if not final_api_key: | |
raise gr.Error("API Key is required. Please enter your OpenRouter API key or set it in the Space secrets.") | |
if image is None: | |
raise gr.Error("An input image is required. Please upload an image.") | |
# Convert the input PIL image to a base64 string | |
buffered = BytesIO() | |
image.save(buffered, format="PNG") | |
img_base64 = base64.b64encode(buffered.getvalue()).decode("utf-8") | |
headers = { | |
"Authorization": f"Bearer {final_api_key}", | |
"Content-Type": "application/json", | |
"HTTP-Referer": YOUR_SITE_URL, | |
"X-Title": YOUR_SITE_NAME, | |
} | |
payload = { | |
# *** CORRECTED MODEL NAME *** | |
"model": "google/gemini-2.5-flash-image-preview:free", | |
"messages": [ | |
{ | |
"role": "user", | |
"content": [ | |
{"type": "text", "text": prompt}, | |
{ | |
"type": "image_url", | |
"image_url": {"url": f"data:image/png;base64,{img_base64}"}, | |
}, | |
], | |
} | |
], | |
"max_tokens": 2048, | |
} | |
try: | |
response = requests.post( | |
"https://openrouter.ai/api/v1/chat/completions", | |
headers=headers, | |
data=json.dumps(payload) | |
) | |
response.raise_for_status() | |
result_data = response.json() | |
# --- *** CORRECTED RESPONSE PARSING LOGIC *** --- | |
# The image is in the 'images' list within the message, not 'content'. | |
message = result_data.get('choices', [{}])[0].get('message', {}) | |
if message and 'images' in message and message['images']: | |
# Get the first image from the 'images' list | |
image_data = message['images'][0] | |
base64_string = image_data.get('image_url', {}).get('url', '') | |
if base64_string and ',' in base64_string: | |
# Remove the "data:image/png;base64," prefix | |
base64_content = base64_string.split(',')[1] | |
# Decode the base64 string and create a PIL image | |
img_bytes = base64.b64decode(base64_content) | |
edited_image = Image.open(BytesIO(img_bytes)) | |
return edited_image | |
else: | |
raise gr.Error(f"API returned an invalid image format. Response: {json.dumps(result_data, indent=2)}") | |
else: | |
raise gr.Error(f"API did not return an image. Full Response: {json.dumps(result_data, indent=2)}") | |
except requests.exceptions.HTTPError as err: | |
error_body = err.response.text | |
if err.response.status_code == 401: | |
raise gr.Error("Authentication failed. Check your OpenRouter API key.") | |
elif err.response.status_code == 429: | |
raise gr.Error("Rate limit exceeded or insufficient credits. Check your OpenRouter account.") | |
else: | |
raise gr.Error(f"An API error occurred: {error_body}") | |
except Exception as e: | |
# Catch any other unexpected errors, like JSON parsing failures | |
raise gr.Error(f"An unexpected error occurred: {str(e)}") | |
# --- Gradio Interface --- | |
with gr.Blocks(theme=gr.themes.Soft()) as iface: | |
gr.Markdown( | |
""" | |
# Image Editing with Google Gemini 2.5 Flash via OpenRouter | |
OpenRouter API key included, but may run out of free inference, so you can use your own key. | |
Try the chatbot version here (BYOK) | |
[https://huggingface.co/spaces/broadfield-dev/nano-banana-chat](https://huggingface.co/spaces/broadfield-dev/nano-banana-chat) | |
Upload an image, describe the edit you want. | |
""" | |
) | |
with gr.Row(): | |
with gr.Column(scale=1): | |
image_input = gr.Image(type="pil", label="Upload Image") | |
prompt_input = gr.Textbox(label="Prompt", placeholder="e.g., 'make the background black and white' or 'add sunglasses to the person'") | |
api_key_input = gr.Textbox( | |
label="OpenRouter API Key (optional)", | |
placeholder="Enter your key here (uses Space secret as fallback)", | |
type="password" | |
) | |
submit_button = gr.Button("Generate Image", variant="primary") | |
with gr.Column(scale=1): | |
image_output = gr.Image(label="Edited Image") | |
submit_button.click( | |
fn=edit_image, | |
inputs=[image_input, prompt_input, api_key_input], | |
outputs=image_output | |
) | |
iface.launch(ssr_mode=False) |