Skip to content

Latest commit

 

History

History
78 lines (54 loc) · 5.48 KB

File metadata and controls

78 lines (54 loc) · 5.48 KB

Arquitectura y decisiones

Mapa de componentes

extension/
  manifest.json      MV3. Permisos: sidePanel, storage, tabs. host_permissions: https://api.exa.ai/*
  background.js      Service worker. Proxy autenticado: único que conoce la API key y toca la red.
  sidepanel.{html,css,js}   El panel: controles, query, estimador, render, polling de research.
  options.{html,css,js}     Guardado de la API key en chrome.storage.local.
  icons/             Iconos PNG generados por make_icons.py (stdlib pura).
  make_icons.py      Helper de build, no se carga en el navegador.

No hay build step ni dependencias de terceros. Se carga tal cual en chrome://extensions.

Modelo de seguridad

  • La API key vive solo en chrome.storage.local y solo la lee el service worker (getApiKey). El panel nunca la ve: manda { type, method, path, body } y el worker inyecta la cabecera x-api-key.
  • El worker valida la ruta contra una allowlist (/search, /contents, /findSimilar, /research/v0/tasks). El panel no puede forzar una URL arbitraria.
  • No hay content scripts. La key nunca entra en una página web.
  • Único destino de red: api.exa.ai. Los favicons salen del campo favicon que ya da Exa o de un monograma local, sin llamar a servicios externos.

El motor: por qué busca por tema

El primitivo de partida era findSimilar(url), pero sobre una URL concreta devuelve los vecinos más cercanos, que para un repo de nicho son el propio proyecto y sus forks. Prueba real sobre github.com/joshcirre/instruckt-laravel: de 7 resultados, 3 eran la propia URL (releases y PRs) y el resto forks de instruckt.

La solución es buscar por tema con /search, derivando un query de la página.

Describir-luego-buscar (resolveQuery en sidepanel.js)

  1. titleQuery() intenta sacar una descripción del título de la pestaña. En GitHub quita el prefijo owner/repo: y los adornos "GitHub - " / " · GitHub". Si solo queda owner/repo o vacío, devuelve null. Nunca usa el nombre del repo como query (eso devolvía el propio proyecto: el bug original era el fallback "instruckt open source alternative").
  2. Si titleQuery devuelve null (repo sin "About"), se pide a Exa un resumen de función de la URL con /contents + summary (lee el README). Cuesta $0.001 extra y se suma al coste real mostrado.
  3. Con el query resuelto se llama a /search con la category detectada.
  4. filterSource() quita de los resultados la propia URL y, en GitHub, el mismo repo.

Validado en vivo: el resumen derivado de joshcirre/instruckt (sin descripción) produce alternativas reales (agent-ui-annotation, agentation, pi-annotate, ...), cero instruckt, por $0.008.

Mapeo de nivel a tipo de /search

Nivel type contents Coste aprox (10 resultados)
1 Rápida fast (ninguno) $0.007
2 Contexto auto highlights $0.007
3 Completa auto highlights + text $0.007
4 Profunda deep highlights + summary $0.012 + $0.001 por resultado
5 Deep research endpoint /research n/a variable, pide confirmación

El nivel 5 no usa /search: crea una tarea en /research/v0/tasks (modelo exa-research) con instrucciones de "encuentra alternativas, no el mismo proyecto", y hace polling (cada 2.5s, máx 40 intentos) hasta completed. Nunca se lanza solo al abrir el panel.

Coste

  • El estimador (computeEstimate) calcula a partir del tipo del nivel y el nº de resultados, y suma summaries en el nivel 4.
  • Tras cada llamada se muestra el coste real (costDollars.total), sumando el paso /contents cuando se usó.
  • Si la respuesta trae perRequestPrices/perPagePrices, se guardan en chrome.storage.local y recalibran el estimado para la próxima vez.

Categoría y filtro de origen

  • detectCategory(url): arxiv.org da research paper, github.com da github, el resto general. Override manual en el desplegable. setCategory() es el punto único de verdad que mantiene estado, desplegable y badge en sync.
  • "Ocultar la página de origen" filtra en cliente (la propia URL y, en GitHub, el mismo owner/repo). No se usa excludeSourceDomain de la API porque en GitHub o arXiv excluiría todo el dominio, que es justo donde están los similares.

Estados de UI

Sin key (pide configurar), analizando (cuando hace falta /contents), skeletons de carga, lista de resultados, vacío y error con reintento. Tokens de diseño con claro y oscuro automático (color-scheme y prefers-color-scheme).

Limitaciones conocidas y futuro

  • La forma exacta de la respuesta de /research se parsea de forma defensiva pero no se ha exprimido en vivo a fondo.
  • Para empresas, el tema sale de la meta-descripción de la home; si es pobre, el query lo será. Se podría leer el contenido con /contents también para no-GitHub.
  • Posible conmutador "Más como esta URL" (findSimilar) frente a "Alternativas por tema" (/search), útil para papers de arXiv donde findSimilar por embedding de la página entera a veces va mejor.
  • El query no es editable; se podría exponer un campo para afinarlo a mano.

Desarrollo y pruebas

  • Sin build. Edita y pulsa recargar (↻) en chrome://extensions.
  • Iconos: python3 extension/make_icons.py (stdlib pura, regenera los PNG).
  • Prueba en vivo contra la API real sin abrir Chrome: scripts/exa_smoke.py, que inyecta la key desde el vault de Phase. Ver cabecera del script.
  • Vista previa de la UI sin Chrome: se sirve extension/sidepanel.html con un mock de chrome.* (ver historial de la sesión); útil para iterar el diseño en claro y oscuro.