Course EN
← back to chapter

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.pysize="1536x1024" ancho más una pista de composición.
  • backend/ai_gemini.py — Imagen con aspect_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.

Descargar banner-maker.zip

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

  • .gitignore
  • README.es.md
  • README.md
  • backend/Dockerfile
  • backend/ai_gemini.py
  • backend/ai_openai.py
  • backend/main.py
  • backend/requirements.txt
  • bootstrap-secrets.sh
  • docker-compose.yml
  • frontend/Dockerfile
  • frontend/app/layout.tsx
  • frontend/app/page.tsx
  • frontend/next.config.ts
  • frontend/package.json
  • frontend/tsconfig.json