Skip to content

Añadiendo un resumen IA a los posts del blog con la Chrome Summarizer API

Published:

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-nano en chrome://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:

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).

Resumen IA generado en este blog con la Chrome Summarizer API

Algunos detalles de UX que merecen atención:

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.


Next Post
WebPerf Snippets: progressive disclosure en Claude Skills