nano-banana / app.py
multimodalart's picture
Update app.py
b5b30fd verified
raw
history blame
6.76 kB
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("GOOGLE_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 unified_image_generator(
prompt: str,
images: Optional[List[str]] = None,
oauth_token: Optional[gr.OAuthToken] = None
) -> str:
"""
Handles all image generation tasks based on the number of input images.
- 0 images: Text-to-image
- 1+ images: Image-to-image (single or multi-modal)
"""
if not verify_pro_status(oauth_token):
raise gr.Error("Access Denied. This service is for PRO users only.")
try:
# Dynamically build the 'contents' list for the API
contents = []
if images:
# If there are images, open them and add to contents
for image_path in images:
print(image_path)
contents.append(Image.open(image_path[0]))
# Always add the prompt to the contents
contents.append(prompt)
response = client.models.generate_content(
model=GEMINI_MODEL_NAME,
contents=contents,
)
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)
return tmpfile.name
except Exception as e:
raise gr.Error(f"Image generation failed: {e}")
# --- Gradio App UI ---
css = '''
#sub_title{margin-top: -35px !important}
.tab-wrapper{margin-bottom: -33px !important}
.tabitem{padding: 0px !important}
.fillable{max-width: 980px !important}
.dark .progress-text {color: white}
.logo-dark{display: none}
.dark .logo-dark{display: block !important}
.dark .logo-light{display: none}
'''
with gr.Blocks(theme=gr.themes.Citrus(), css=css) as demo:
gr.HTML('''
<img class="logo-dark" src='https://huggingface.co/spaces/multimodalart/nano-banana/resolve/main/nano_banana_pros.png' style='margin: 0 auto; max-width: 500px' />
<img class="logo-light" src='https://huggingface.co/spaces/multimodalart/nano-banana/resolve/main/nano_banana_pros_light.png' style='margin: 0 auto; max-width: 500px' />
''')
gr.HTML("<h3 style='text-align:center'>Hugging Face PRO users can use Google's Nano Banana (Gemini 2.5 Flash Image Preview) on this Space. <a href='https://huggingface.co/pro?source=nana_banana' target='_blank'>Subscribe to PRO</a></h3>", elem_id="sub_title")
pro_message = gr.Markdown(visible=False)
main_interface = gr.Column(visible=False)
with main_interface:
with gr.Row():
with gr.Column(scale=1):
image_input_gallery = gr.Gallery(
label="Upload one or more images here. Leave empty for text-to-image",
file_types=["image"],
height="auto"
)
prompt_input = gr.Textbox(
label="Prompt",
placeholder="Turns this photo into a masterpiece"
)
generate_button = gr.Button("Generate", variant="primary")
with gr.Column(scale=1):
output_image = gr.Image(label="Output", interactive=False, elem_id="output", type="filepath")
use_image_button = gr.Button("♻️ Use this Image for Next Edit")
gr.Markdown("## Thank you for being a PRO! 🤗")
login_button = gr.LoginButton()
# --- Event Handlers (SIMPLIFIED) ---
generate_button.click(
unified_image_generator,
inputs=[prompt_input, image_input_gallery], # Inputs are now just the prompt and the single gallery
outputs=[output_image],
)
use_image_button.click(
lambda img_path: [img_path] if img_path else None,
inputs=[output_image],
outputs=[image_input_gallery]
)
# --- Access Control Logic (UNCHANGED) ---
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 = (
"## ✨ Exclusive Access for PRO Users\n\n"
"Thank you for your interest! This feature is available exclusively for our Hugging Face **PRO** members.\n\n"
"To unlock this and many other benefits, please consider upgrading your account.\n\n"
"### [**Become a PRO Member Today!**](https://huggingface.co/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()