Oráculo de EL — UI híbrida de búsqueda y respuesta sintetizada (s72)
Oráculo de EL — UI híbrida de búsqueda y respuesta sintetizada (s72)
Resumen
El commit s72 (PR #51, cableado con PR #50) extiende el componente SearchInline del shell del Workspace para convertirlo en una caja de búsqueda híbrida: mantiene la búsqueda fuzzy local (Fuse.js) y añade un nuevo modo Oráculo que lanza una pregunta al endpoint POST /api/oraculo/ask, recibe una respuesta sintetizada por Gemma 4 con grounding sobre el grafo completo del Bibliotecario, y la renderiza con Markdown en un panel inline dentro del dropdown de búsqueda.
Cierra la Fase 1 del plan Oráculo de EL.
Modos de búsqueda
| Modo | Trigger | Backend | Latencia típica |
|---|---|---|---|
| search | Siempre (manual o auto cuando la query parece término corto) | Fuse.js local sobre /search-index.json | ~0 ms (local) |
| oracle | Auto cuando la query “parece pregunta”; o forzado por chip | POST /api/oraculo/ask → Gemma 4 + grafo Bibliotecario | 1–3 s |
| auto | (default) Delega a shouldAskOracle() | Cualquiera de los dos según heurística | variable |
Heurística shouldAskOracle()
Una query activa el modo Oráculo si cualquiera de estas condiciones se cumple (y tiene ≥ 8 caracteres):
- Termina en
?o¿. - Tiene ≥ 4 palabras.
- Empieza por palabra interrogativa: qué / cómo / dónde / por qué / cuál / quién / cuándo / what / how / where / why / which / who / when (regex case-insensitive, acepta tildes y sin tilde).
El mínimo de 8 caracteres evita disparar llamadas al modelo para queries cortas como "racks" o "api".
Toggle manual (chips)
En el header del dropdown aparecen tres chips: Auto · Buscar · Preguntar.
Auto(default): aplicashouldAskOracle().Buscar: fuerza siempre Fuse, nunca llama al Oráculo.Preguntar: fuerza siempre el Oráculo (si la query tiene ≥ 8 chars).
El modo se guarda en state local de React; no persiste entre sesiones (intencional en Fase 1).
Flujo de red del Oráculo
Usuario teclea → debounce 450 ms → shouldAskOracle() → true
→ AbortController nuevo (cancela request anterior si existe)
→ POST /api/oraculo/ask { question: <query> }
→ Responde OracleResponse { answer, sources[], model, chunks_used, duration_ms }
→ marked.parse(answer) → dangerouslySetInnerHTML
→ Chips de fuentes (linkables si source_path ↔ /wiki/<slug>)
Cancelación: cada vez que el usuario sigue tecleando antes de los 450 ms, el timer se limpia y el AbortController del request anterior se llama con .abort(). Evita tormenta de llamadas a Gemma 4.
Interfaz OracleResponse
interface OracleResponse {
answer: string; // Markdown libre generado por Gemma 4
sources: OracleSource[]; // Fragmentos del grafo usados como grounding
model: string; // e.g. "gemma-4-26b-a4b-it"
chunks_used: number;
duration_ms: number;
}
interface OracleSource {
title: string;
path: string | null; // Ruta en el repo (puede ser null)
chunkTitle: string;
relevance: number;
}
Renderizado de la respuesta
- La respuesta llega como Markdown generado por el modelo.
- Se parsea con
marked(GFM habilitado,breaks: false), mismo patrón queNoteEditor. - Sin DOMPurify: el corpus es interno y el modelo está constrained; no hay riesgo XSS real en este contexto.
- Estilos propios en
.search-inline-oracle-answercon soporte de: párrafos, listas,codeinline, bloquespre > code, negrita, enlaces.
Chips de fuentes
La función sourceToHref(path) intenta mapear el source_path devuelto por el Oráculo a una URL navegable del Workspace:
Patrón de path | URL generada |
|---|---|
src/content/wiki/<slug>.md | /wiki/<slug> |
wiki/<slug>.md | /wiki/<slug> |
| Cualquier otro | null (chip informativo sin link) |
Las fuentes con link se renderizan como <a> con clase search-inline-oracle-chip linkable; las demás como <span>.
Cambios CSS (globals.css)
min-widthde la caja: 240 px → 360 px para invitar a preguntas largas.- Clases nuevas añadidas bajo tokens existentes (
--overlay-3/4,--accent,--fg,--fg-dim,--text-sm/xs,--border-soft,--danger):.search-inline-mode-toggle/.search-inline-mode-chip/.search-inline-mode-chip.active.search-inline-oracle/*-meta/*-loading/*-error/*-answer/*-sources/*-chip/*-chip.linkable.search-inline-result-main/*-title/*-path/*-enter.search-inline-history-term/.search-inline-empty
- Refactorización: todos los
style={{ ... }}inline deSearchInlineeliminados y convertidos a clases CSS.
Estado y roadmap
| Fase | Estado | Descripción |
|---|---|---|
| Fase 1 | ✅ Completada (s72) | UI híbrida + cable con /api/oraculo/ask |
| Fase 2 | ⬜ Pendiente | Corpus completo (todas las secciones del grafo) |
| Fase 3 | ⬜ Pendiente | Iteración de prompt según feedback de uso real |
Dependencias añadidas
| Paquete | Uso |
|---|---|
marked | Parse de Markdown en respuestas del Oráculo. Ya usado en NoteEditor. |
Véase también
- [[entity—workspace—component—search-inline]]
- [[workspace—agentes-ia]]
- [[workspace—que-es-workspace]]
- [[workspace—dashboard]]