Notas de reunión
Pega una audio URL de una reunión o nota de voz y obtén un resumen de una línea más
Showcase — Notas de reunión
Pega una audio URL de una reunión o nota de voz y obtén un resumen de una línea más puntos de acción. Es la forma de la mayoría de las features de audio reales: speech to text, luego un modelo de texto hace el razonamiento. OpenAI lo hace en dos pasos (transcribir, luego resumir); Gemini entiende audio de forma nativa y lo hace en una sola llamada — ambos aterrizan en el mismo lugar.
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— transcribe, luego resume con un modelo de chat.backend/ai_gemini.py— una sola llamada multimodal: audio adentro, resumen afuera.frontend/app/page.tsx— caja de URL + resumen y transcripción.
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 meeting-notes.zip
cd meeting-notes
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): audio to meeting notes.
Two steps chained: transcribe the audio, then summarize the transcript into a
one-line recap plus action items. This is the shape of most real audio features
— speech to text, then an ordinary text model does the thinking on the words.
"""
import urllib.request
from openai import OpenAI
_client = OpenAI()
_STT_MODEL = "gpt-4o-mini-transcribe"
_CHAT_MODEL = "gpt-5.4-nano"
def run(audio_url: str) -> str:
url = audio_url.strip()
if not url.startswith("http"):
return "Paste a public audio URL (http/https) of a meeting or voice note."
data = urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})).read()
name = url.split("/")[-1].split("?")[0] or "audio.mp3"
transcript = _client.audio.transcriptions.create(model=_STT_MODEL, file=(name, data)).text
summary = _client.responses.create(
model=_CHAT_MODEL,
instructions=(
"Summarize this meeting transcript: first one sentence of overall "
"summary, then a bulleted list of action items with an owner in "
"parentheses if one is named. Be concise."
),
input=[{"role": "user", "content": transcript}],
)
return f"{summary.output_text}\n\n---\nTranscript:\n{transcript}"
backend/ai_gemini.py
"""Showcase 3 (Gemini): audio to meeting notes.
Gemini understands audio natively, so this is ONE call: hand it the audio and
ask for the summary directly. (OpenAI does it in two steps — transcribe, then
summarize — which is the more common pattern; both land in the same place.)
"""
import os
import urllib.request
from google import genai
from google.genai import types
_client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
_MODEL = "gemini-3.1-flash-lite"
_MIME = {"mp3": "audio/mpeg", "wav": "audio/wav", "m4a": "audio/mp4", "ogg": "audio/ogg", "flac": "audio/flac"}
def _mime(url: str) -> str:
ext = url.lower().split("?")[0].rsplit(".", 1)[-1]
return _MIME.get(ext, "audio/mpeg")
def run(audio_url: str) -> str:
url = audio_url.strip()
if not url.startswith("http"):
return "Paste a public audio URL (http/https) of a meeting or voice note."
data = urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})).read()
response = _client.models.generate_content(
model=_MODEL,
contents=[
types.Part.from_bytes(data=data, mime_type=_mime(url)),
"Summarize this meeting audio: first one sentence of overall summary, "
"then a bulleted list of action items with an owner in parentheses if "
"one is named. Be concise.",
],
)
return response.text or ""
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