Course EN
← back to chapter

Reescritura en vivo

Pega cualquier párrafo de borrador — un correo, un mensaje de Slack, una nota

Reescritura en vivo

Pega cualquier párrafo de borrador — un correo, un mensaje de Slack, una nota de commit — y observa cómo llega en streaming una versión más apretada. Los mismos datos, menos palabras, mejor flujo. La salida en streaming hace que el "segundo borrador" se sienta inmediato.

Córrelo

bash bootstrap-secrets.sh              # reads ../../../../.env, writes secrets/
docker compose up --build              # default: PROVIDER=openai

Abre http://localhost:3000.

Para correrlo contra Gemini en su lugar:

PROVIDER=gemini docker compose up --build

Qué hay aquí

  • backend/ai_openai.py — emite en streaming una reescritura del párrafo de entrada
  • backend/ai_gemini.py — el mismo prompt, otro SDK
  • backend/main.py — FastAPI: /api/ai (final) + /api/ai/stream (SSE)
  • frontend/app/page.tsx — textarea + panel de resultado en streaming

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 live-rewrite.zip

unzip live-rewrite.zip
cd live-rewrite
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

"""Week 3 - Showcase 3 (OpenAI): live-rewrite.

Takes a draft paragraph and streams back a clearer, tighter version
that keeps every fact intact. Useful for editing messages, commit
notes, doc blurbs — anywhere the user wants a faster second draft.

`run_stream` yields tokens (so the local UI sees a live edit happening);
`run` accumulates for the website's non-streaming contract.
"""
from typing import Iterator

from openai import OpenAI

_client = OpenAI()


def _prompt(draft: str) -> str:
    return (
        "Rewrite the following paragraph for clarity, brevity, and natural "
        "flow. Keep every fact and intent intact. Return only the rewritten "
        "paragraph; no commentary, no headings, no preamble."
        f"\n\n---\n{draft}\n---"
    )


def run_stream(draft: str) -> Iterator[str]:
    # Cap output length so the rewrite can't run away on us. 2048
    # tokens (~6k chars) is well over any reasonable rewrite of an
    # 8k-char input paragraph.
    stream = _client.responses.create(
        model="gpt-5.4-nano", input=_prompt(draft), stream=True,
        max_output_tokens=2048,
    )
    for event in stream:
        if event.type == "response.output_text.delta":
            yield event.delta


def run(draft: str) -> str:
    return "".join(run_stream(draft))

backend/ai_gemini.py

"""Week 3 - Showcase 3 (Gemini): live-rewrite.

Same prompt and contract as ai_openai.py; different SDK shape.
"""
import os
from typing import Iterator

from google import genai
from google.genai import types

_client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])


def _prompt(draft: str) -> str:
    return (
        "Rewrite the following paragraph for clarity, brevity, and natural "
        "flow. Keep every fact and intent intact. Return only the rewritten "
        "paragraph; no commentary, no headings, no preamble."
        f"\n\n---\n{draft}\n---"
    )


def run_stream(draft: str) -> Iterator[str]:
    # Cap output length so the rewrite can't run away on us. 2048
    # tokens (~6k chars) is well over any reasonable rewrite of an
    # 8k-char input paragraph.
    stream = _client.models.generate_content_stream(
        model="gemini-3.1-flash-lite", contents=_prompt(draft),
        config=types.GenerateContentConfig(max_output_tokens=2048),
    )
    for chunk in stream:
        if chunk.text:
            yield chunk.text


def run(draft: str) -> str:
    return "".join(run_stream(draft))

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