Fábrica de banners
Un banner ancho con espacio negativo deliberado para un titular. La relación de
Showcase — Fábrica de banners
Un banner ancho con espacio negativo deliberado para un titular. La relación de
aspecto es un parámetro (size para OpenAI, aspect_ratio para Imagen) y rehace la
composición más de lo que imaginarías — una escena hecha para un cuadrado no es la
escena hecha para un banner 16:9.
Córrelo
bash bootstrap-secrets.sh # reads ../../../../.env, writes secrets/
docker compose up --build # default: PROVIDER=openai
Abre http://localhost:3000. Gemini: PROVIDER=gemini docker compose up --build.
Qué hay aquí
backend/ai_openai.py—size="1536x1024"ancho más una pista de composición.backend/ai_gemini.py— Imagen conaspect_ratio="16:9".frontend/app/page.tsx— caja de sujeto; renderiza el banner.
Detenlo
docker compose down
Ejecútalo en tu máquina
Descarga el proyecto como ZIP y córrelo con Docker. Levanta un backend FastAPI y un frontend Next.js en localhost:3000.
unzip banner-maker.zip
cd banner-maker
bash bootstrap-secrets.sh # one-time: pulls API keys into ./secrets
docker compose up --build # default provider: openai
# or: PROVIDER=gemini docker compose up --build
Escribe algo, elige un proveedor y ejecuta el mismo código de Código contra la API real. Requiere iniciar sesión.
Los mismos módulos que ejecuta el botón Run. El proyecto completo (frontend, Dockerfile, compose) está en el ZIP, pestaña README.
backend/ai_openai.py
"""Showcase 3 (OpenAI): banner maker.
Same generation, a wide aspect ratio and a composition hint that leaves room for
a headline. Aspect ratio is a parameter (`size`), and it changes the result more
than people expect — a scene composed for a square is not the same scene
composed for a banner.
"""
from openai import OpenAI
_client = OpenAI()
_MODEL = "gpt-image-1"
_STYLE = ("A wide website banner illustration: {subject}. Balanced composition "
"with clear negative space on one side for a headline. Modern, clean, "
"high quality.")
def run(subject: str) -> str:
s = subject.strip()
if not s:
return "Describe the banner subject (e.g. 'a mountain sunrise for a travel blog')."
response = _client.images.generate(model=_MODEL, prompt=_STYLE.format(subject=s), size="1536x1024")
return f"data:image/png;base64,{response.data[0].b64_json}"
backend/ai_gemini.py
"""Showcase 3 (Gemini): banner maker via the image model.
Same wide-composition recipe; aspect ratio is expressed in the prompt (the
generate_content image path doesn't take Imagen's aspect_ratio config).
"""
import base64
import os
from google import genai
from google.genai import types
_client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
_MODEL = "gemini-2.5-flash-image"
_STYLE = ("A wide 16:9 website banner illustration: {subject}. Balanced composition "
"with clear negative space on one side for a headline. Modern, clean, "
"high quality.")
def _generate(prompt: str) -> str:
response = _client.models.generate_content(
model=_MODEL, contents=prompt,
config=types.GenerateContentConfig(response_modalities=["TEXT", "IMAGE"]),
)
for part in response.candidates[0].content.parts:
if getattr(part, "inline_data", None) and part.inline_data.data:
b64 = base64.b64encode(part.inline_data.data).decode()
return f"data:image/png;base64,{b64}"
return "No image was generated — try a different subject."
def run(subject: str) -> str:
s = subject.strip()
if not s:
return "Describe the banner subject (e.g. 'a mountain sunrise for a travel blog')."
return _generate(_STYLE.format(subject=s))
Archivos del proyecto
.gitignoreREADME.es.mdREADME.mdbackend/Dockerfilebackend/ai_gemini.pybackend/ai_openai.pybackend/main.pybackend/requirements.txtbootstrap-secrets.shdocker-compose.ymlfrontend/Dockerfilefrontend/app/layout.tsxfrontend/app/page.tsxfrontend/next.config.tsfrontend/package.jsonfrontend/tsconfig.json