Course EN
← back to chapter

Rewrite tone

Paste any message (email, Slack, commit message), pick a tone, get it

Showcase — Rewrite tone

Paste any message (email, Slack, commit message), pick a tone, get it rewritten with the same facts and a different voice. Still one API call.

Run

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

Open http://localhost:3000.

To run against Gemini instead:

PROVIDER=gemini docker compose up --build

What's where

  • backend/ai_openai.py — the OpenAI call
  • backend/ai_gemini.py — the Gemini call
  • backend/main.py — identical FastAPI loader; reads PROVIDER and dispatches
  • frontend/app/page.tsx — textarea + tone <select> + result
  • docker-compose.yml — two services, secrets mounted from ./secrets/

The frontend encodes <tone>\n---\n<message> into the single-string input the backend expects, which is how the def run(input: str) -> str contract stays the same across all three showcases in this week.

Stop

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

unzip rewrite-tone.zip
cd rewrite-tone
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 1 - Showcase 2 (OpenAI): rewrite a message in a chosen tone.

The frontend encodes the input as "<tone>\\n---\\n<message>" so the
def run(input: str) -> str contract stays the same as the other showcases.
"""
from openai import OpenAI

_client = OpenAI()


def run(payload: str) -> str:
    tone, _, message = payload.partition("\n---\n")
    tone = (tone or "neutral").strip().lower()
    message = (message or payload).strip()
    prompt = (
        f"Rewrite the following message in a {tone} tone. Preserve every fact, "
        "name, date, and number. Do not add or remove information. Return only "
        "the rewritten message, no preamble."
        f"\n\n---\n{message}\n---"
    )
    # Cap the response so the model can't run away on us. 2048 tokens
    # is roughly 6k characters — comfortably more than any reasonable
    # rewrite of an 8k-char input.
    response = _client.responses.create(
        model="gpt-5.4-nano", input=prompt, max_output_tokens=2048,
    )
    return response.output_text

backend/ai_gemini.py

"""Week 1 - Showcase 2 (Gemini): rewrite a message in a chosen tone."""
import os

from google import genai
from google.genai import types

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


def run(payload: str) -> str:
    tone, _, message = payload.partition("\n---\n")
    tone = (tone or "neutral").strip().lower()
    message = (message or payload).strip()
    prompt = (
        f"Rewrite the following message in a {tone} tone. Preserve every fact, "
        "name, date, and number. Do not add or remove information. Return only "
        "the rewritten message, no preamble."
        f"\n\n---\n{message}\n---"
    )
    # Cap the response so the model can't run away on us. 2048 tokens
    # is roughly 6k characters — comfortably more than any reasonable
    # rewrite of an 8k-char input.
    response = _client.models.generate_content(
        model="gemini-3.1-flash-lite", contents=prompt,
        config=types.GenerateContentConfig(max_output_tokens=2048),
    )
    return response.text

Archivos del proyecto

  • .gitignore
  • 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