En ocasiones me encuentro con páginas que cargan modelos de IA externos para funcionalidades que el navegador podría resolver de forma nativa. La Chrome Summarizer API permite generar resúmenes de texto directamente en el dispositivo, sin peticiones a servidores externos, sin coste por token y con total privacidad.
En este artículo explico cómo la he implementado en este blog: el botón “Resumen IA” que ves justo encima de este texto.
Qué es la Chrome Summarizer API
La Summarizer API forma parte de las Chrome Built-in AI APIs, un conjunto de APIs que exponen modelos de lenguaje que corren localmente en el navegador usando Gemini Nano.
A diferencia de integrar la API de OpenAI o similar, aquí no hay petición de red: el modelo vive en el dispositivo del usuario o usuaria. El primer uso puede requerir descargar el modelo (unos cientos de MB), pero las siguientes ejecuciones son instantáneas.
La API está disponible en Chrome 131+ y necesita activar la flag
#summarizer-api-for-gemini-nanoenchrome://flags.
Detección de soporte
Lo primero es comprobar si el navegador soporta la API. Esto es progressive enhancement puro: si no hay soporte, no se renderiza nada.
// ❌ Bad: asumir que la API está disponible
const summarizer = await Summarizer.create();
// ✅ Good: comprobar disponibilidad primero
if (!("Summarizer" in window)) return;
const availability = await Summarizer.availability({ outputLanguage: "es" });
if (availability === "unavailable") return;
availability() devuelve tres valores posibles:
"available": el modelo está descargado y listo"downloadable": necesita descargarse antes del primer uso"unavailable": no soportado en este dispositivo o navegador
Crear el summarizer
const summarizer = await Summarizer.create({
type: "key-points", // tl;dr | teaser | headline | key-points
format: "markdown",
length: "medium",
outputLanguage: "es",
sharedContext:
"Technical blog post about web performance and frontend development",
});
El parámetro outputLanguage es importante: sin él, el navegador muestra un warning en consola pidiendo que se especifique.
Si el modelo necesita descargarse, create() acepta un callback monitor para mostrar el progreso:
const summarizer = await Summarizer.create({
// ...opciones,
monitor(m) {
m.addEventListener("downloadprogress", e => {
const pct = Math.round((e.loaded / e.total) * 100);
console.log(`Descargando modelo: ${pct}%`);
});
},
});
Streaming para mejor UX
La API soporta streaming, lo que permite mostrar el resumen según se genera en lugar de esperar al resultado completo. Los chunks son incrementales (deltas), así que hay que acumularlos:
// ❌ Bad: tomar el último chunk como resultado completo
for await (const chunk of stream) {
setSummary(chunk); // pierde todo excepto el último delta
}
// ✅ Good: acumular los deltas
let result = "";
for await (const chunk of stream) {
result += chunk;
setSummary(result); // actualizar UI progresivamente
}
Gestionar el límite de tokens
El modelo tiene un límite de tokens de entrada (inputQuota). Para artículos largos, hay que verificarlo antes de resumir y truncar si es necesario:
const inputUsage = await summarizer.measureInputUsage(markdown);
let textToSummarize = markdown;
if (inputUsage > summarizer.inputQuota) {
const ratio = summarizer.inputQuota / inputUsage;
// 0.9 de margen para no llegar justo al límite
textToSummarize = markdown.slice(
0,
Math.floor(markdown.length * ratio * 0.9)
);
}
En la implementación aviso con un mensaje cuando el resumen es parcial.
Markdown como fuente de datos
En lugar de extraer texto plano del DOM, el componente hace fetch del markdown en bruto en el momento en que quien navega solicita el resumen:
<!-- PostDetails.astro: solo una URL corta en el payload de hidratación -->
<AISummary client:idle markdownUrl={markdownUrl} lang={lang} />
// Fetch happens only when the user requests the summary
const markdown = await fetch(markdownUrl).then(r => r.text());
Sin coste en la carga inicial, el archivo lo sirve la CDN cacheado. Y el modelo recibe el markdown estructurado en lugar de texto plano, lo que mejora la calidad del resumen.
Exponer el markdown para agentes IA
El endpoint que ya necesitamos para el componente sirve también para que agentes IA accedan al contenido en formato estructurado sin parsear el HTML:
// src/pages/posts/[slug].md.ts
export const GET: APIRoute = ({ props }) => {
return new Response(props.body, {
headers: { "Content-Type": "text/markdown; charset=utf-8" },
});
};
Cada post tiene su versión markdown en /posts/slug.md. En el <head> de cada post incluyo el link de descubrimiento para que los agentes puedan encontrarlo:
<link rel="alternate" type="text/markdown" href="/posts/slug.md" />
Caso real: este blog
La implementación completa usa React con Astro (client:idle para no bloquear la carga inicial).
Algunos detalles de UX que merecen atención:
- Caché del resumen: una vez generado, ocultar y volver a mostrar el panel no regenera el resumen
- Sin mensaje de descarga si el modelo ya está disponible: solo se activa el
monitorsiavailability === "downloadable" - Reintento en caso de error: el estado de error muestra un botón para regenerar el resumen
Conclusión
La Chrome Summarizer API es un ejemplo de cómo las APIs de IA nativas del navegador pueden mejorar la experiencia sin añadir dependencias externas, sin coste y respetando la privacidad. La funcionalidad solo aparece donde tiene soporte real: progressive enhancement aplicado a IA.