CreaRack-SL

UI Híbrida Oráculo de EL — Caja de Búsqueda del Workspace

UI Híbrida Oráculo de EL — Caja de Búsqueda del Workspace

PR #51 · 2026-05-19 · Cierra Fase 1 del plan Oráculo
Archivos: src/components/shell/SearchInline.tsx, src/styles/globals.css

Qué es

La caja de búsqueda global del Workspace (SearchInline) evoluciona de un buscador fuzzy local a un sistema híbrido que combina dos modos de consulta:

ModoMotorLatenciaCuándo se activa
SearchFuse.js (local, /search-index.json)<10 msQueries cortas, sin interrogación
OraclePOST /api/oraculo/ask → Gemma 4 + grafo Bibliotecario~1-3 sQueries que parecen preguntas

La heurística de detección automática (shouldAskOracle) evalúa en modo auto (por defecto): si la query tiene ≥4 palabras, termina en ?/¿, o empieza por palabra interrogativa (qué/cómo/dónde/why/what…). Umbral mínimo de 8 chars para no disparar Gemma 4 con términos cortos.

Componentes de la feature

Detección híbrida (shouldAskOracle)

function shouldAskOracle(query: string, mode: SearchMode): boolean {
  if (mode === 'search') return false;
  const q = query.trim();
  if (q.length < ORACLE_MIN_CHARS) return false;  // 8 chars
  if (mode === 'oracle') return true;
  // auto: heurística
  if (q.endsWith('?') || q.endsWith('¿')) return true;
  if (q.split(/\s+/).length >= 4) return true;
  if (INTERROGATIVE_RE.test(q)) return true;
  return false;
}

Toggle manual de modo

Tres chips en el header del dropdown:

  • Auto (default) — el sistema decide según la heurística.
  • Buscar — solo Fuse, nunca Oráculo.
  • Preguntar — fuerza Oráculo cuando hay query.

Debounce + AbortController

ORACLE_DEBOUNCE_MS = 450. Cada keystroke cancela el request anterior via AbortController. Evita tormenta de llamadas a Gemma 4 mientras el usuario sigue escribiendo. Los AbortError se descartan silenciosamente.

Render markdown

Usa marked (GFM habilitado, breaks: false) para renderizar la respuesta del Oráculo como HTML. Sin DOMPurify: el corpus es interno y la respuesta viene constrained por JSON schema. Mismo patrón que NoteEditor del workspace.

Chips de fuentes clickables

sourceToHref(path) mapea rutas del grafo Bibliotecario a URLs navegables:

  • src/content/wiki/<slug>.md → /wiki/<slug> (chip con link)
  • Otros paths → chip informativo sin link

UI/UX

  • Caja ampliada de 240 px → 360 px min-width para invitar a preguntas largas.
  • Loader inline: “El Oráculo está pensando…”
  • Mensaje de error user-friendly si el endpoint falla.
  • Metadata de respuesta: nº de fuentes y duración en ms.

Estilos nuevos (globals.css)

14 clases nuevas para la sección Oráculo + 5 clases de refactor de inline styles preexistentes:

GrupoClases
Toggle de modo.search-inline-mode-toggle, .search-inline-mode-chip, .search-inline-mode-chip.active
Respuesta Oráculo.search-inline-oracle, .search-inline-oracle-answer, .search-inline-oracle-loading, .search-inline-oracle-error, .search-inline-oracle-meta
Fuentes.search-inline-oracle-sources, .search-inline-oracle-chip, .search-inline-oracle-chip.linkable
Refactor.search-inline-result-main, .search-inline-result-title, .search-inline-result-path, .search-inline-result-enter, .search-inline-history-term, .search-inline-empty

Todos los estilos usan tokens CSS existentes (--overlay-3, --accent, --text-sm, --fg-dim, --border-soft, --danger, --font-mono). Sin valores hardcodeados salvo el fallback #2962ff del --accent.

Flujo de datos

Usuario escribe query
        │
        ▼
shouldAskOracle(query, mode)?
   ├─ No  → Fuse.js (local) → lista de páginas
   └─ Sí  → debounce 450ms
                │
                ▼
        AbortController nuevo
                │
                ▼
        POST /api/oraculo/ask
        { question: query }
                │
                ▼
        OracleResponse {
          answer: string,      ← marked.parse() → HTML
          sources: [{title, path, relevance}],
          model: "gemma-4-...",
          chunks_used: N,
          duration_ms: N
        }
                │
                ▼
        Render: respuesta MD + chips de fuentes

Estado del plan Oráculo

FaseDescripciónPREstado
Fase 1Endpoint /api/oraculo/ask (backend RAG)#50✅
Fase 1UI híbrida caja de búsqueda#51✅
Fase 2TBD—pendiente

Decisiones técnicas

  • Sin DOMPurify: decisión consciente. El HTML renderizado viene de Gemma 4 sobre corpus propio, constrained por schema. Si el corpus se abriera a contenido externo, revaluar.
  • marked sin sanitize: ver nota anterior. Consistente con NoteEditor.
  • Debounce 450 ms: balance entre respuesta ágil y no saturar Gemma 4. Ajustable en ORACLE_DEBOUNCE_MS.
  • Min 8 chars: evita disparar el LLM por búsquedas como “racks” o “ip” que son claramente fuzzy.

Véase también

  • [[entity—workspace—component—search-inline]]
  • [[feature—workspace—oraculo-endpoint]]