|
|
import gradio as gr |
|
|
import base64 |
|
|
import mimetypes |
|
|
import os |
|
|
import google-genai as genai |
|
|
from google-genai import types |
|
|
from PIL import Image |
|
|
import io |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
API_KEY = os.environ.get("GEMINI_API_KEY") |
|
|
client = genai.Client(api_key=API_KEY) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_image(prompt, negative_prompt, resolution): |
|
|
|
|
|
if not API_KEY: |
|
|
return None, "❌ API Key GEMINI_API_KEY não configurada no HuggingFace." |
|
|
|
|
|
try: |
|
|
|
|
|
full_prompt = prompt |
|
|
if negative_prompt: |
|
|
full_prompt += f"\n\nEvitar: {negative_prompt}" |
|
|
|
|
|
contents = [ |
|
|
types.Content( |
|
|
role="user", |
|
|
parts=[types.Part.from_text(text=full_prompt)], |
|
|
) |
|
|
] |
|
|
|
|
|
|
|
|
image_res = {"1K": "1K", "2K": "2K", "4K": "4K"}.get(resolution, "1K") |
|
|
|
|
|
config = types.GenerateContentConfig( |
|
|
response_modalities=["IMAGE", "TEXT"], |
|
|
image_config=types.ImageConfig(image_size=image_res), |
|
|
tools=[types.Tool(googleSearch=types.GoogleSearch())], |
|
|
) |
|
|
|
|
|
|
|
|
for chunk in client.models.generate_content_stream( |
|
|
model="gemini-3-pro-image-preview", |
|
|
contents=contents, |
|
|
config=config, |
|
|
): |
|
|
if ( |
|
|
chunk.candidates |
|
|
and chunk.candidates[0].content |
|
|
and chunk.candidates[0].content.parts |
|
|
): |
|
|
|
|
|
part = chunk.candidates[0].content.parts[0] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if hasattr(part, "inline_data") and part.inline_data: |
|
|
mime = part.inline_data.mime_type |
|
|
if mime and mime.startswith("image"): |
|
|
data = part.inline_data.data |
|
|
img = Image.open(io.BytesIO(data)) |
|
|
return img, "✅ Imagem gerada com sucesso!" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if hasattr(part, "image") and part.image: |
|
|
try: |
|
|
img = Image.open(io.BytesIO(part.image)) |
|
|
return img, "✅ Imagem gerada (fallback image)." |
|
|
except: |
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if hasattr(part, "blob") and part.blob: |
|
|
try: |
|
|
img = Image.open(io.BytesIO(part.blob)) |
|
|
return img, "✅ Imagem gerada (fallback blob)." |
|
|
except: |
|
|
pass |
|
|
|
|
|
return None, "❌ O modelo respondeu, mas não retornou imagem." |
|
|
|
|
|
except Exception as e: |
|
|
return None, f"❌ Erro: {str(e)}" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
examples = [ |
|
|
[ |
|
|
"Cinematic portrait of a woman with red hair, soft light, 85mm, ultra realistic, 8k", |
|
|
"blurry, distorted, ugly, low quality", |
|
|
"1K", |
|
|
], |
|
|
[ |
|
|
"Cyberpunk futuristic city, neon rain, flying cars, ultrarealistic, night mood", |
|
|
"daylight, cartoon, lowres", |
|
|
"2K", |
|
|
], |
|
|
[ |
|
|
"Mystical forest, god rays, fog, moss rocks, photorealistic nature", |
|
|
"urban, artificial", |
|
|
"1K", |
|
|
], |
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks() as demo: |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<style> |
|
|
.gradio-container { |
|
|
font-family: 'Inter','Manrope',sans-serif; |
|
|
} |
|
|
.title { |
|
|
text-align: center; |
|
|
font-size: 2.6em; |
|
|
font-weight: 800; |
|
|
margin-bottom: 0.3em; |
|
|
background: linear-gradient(135deg,#39FF14 0%,#00CC11 100%); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
} |
|
|
.subtitle { |
|
|
text-align: center; |
|
|
font-size: 1.1em; |
|
|
color:#6b7280; |
|
|
margin-bottom: 2em; |
|
|
} |
|
|
</style> |
|
|
""") |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div style='text-align:center;margin-bottom:30px;'> |
|
|
<h1 class='title'>🎨 Gerador Ultra-Realista (Nano Banana Pro)</h1> |
|
|
<p class='subtitle'>Gemini 3 Pro Image Preview — Imagens de nível profissional</p> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
|
|
|
|
|
|
with gr.Column(): |
|
|
|
|
|
prompt = gr.Textbox( |
|
|
label="📝 Prompt", |
|
|
lines=5, |
|
|
placeholder="Descreva a imagem desejada com detalhes..." |
|
|
) |
|
|
|
|
|
negative_prompt = gr.Textbox( |
|
|
label="🚫 Negative Prompt", |
|
|
value="blurry, distorted, ugly, deformed", |
|
|
lines=3 |
|
|
) |
|
|
|
|
|
resolution = gr.Dropdown( |
|
|
label="📐 Resolução", |
|
|
choices=["1K", "2K", "4K"], |
|
|
value="1K" |
|
|
) |
|
|
|
|
|
btn = gr.Button("✨ Gerar imagem", variant="primary") |
|
|
|
|
|
|
|
|
with gr.Column(): |
|
|
|
|
|
output_image = gr.Image( |
|
|
type="pil", |
|
|
height=600, |
|
|
label="Imagem Gerada" |
|
|
) |
|
|
|
|
|
output_text = gr.Textbox( |
|
|
label="Status", |
|
|
lines=5, |
|
|
interactive=False |
|
|
) |
|
|
|
|
|
|
|
|
gr.Markdown("### 📚 Exemplos") |
|
|
gr.Examples( |
|
|
examples=examples, |
|
|
inputs=[prompt, negative_prompt, resolution] |
|
|
) |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div style='text-align:center;margin-top:50px;padding:20px;border-top:1px solid #e5e7eb;'> |
|
|
<strong>Leicam · Tech</strong><br> |
|
|
<span style='color:#9ca3af;font-size:12px;'>© 2025 Todos os direitos reservados.</span> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
btn.click( |
|
|
fn=generate_image, |
|
|
inputs=[prompt, negative_prompt, resolution], |
|
|
outputs=[output_image, output_text], |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch() |
|
|
|