Volver a la wiki

{“status”: “active”, “tags”: [“workspace”, “oraculo”, “search”, “chat”, “multi-turn”, “gemini”, “cloudflare”, “workers”, “react”, “ux”], “sources”: [{“type”: “code”, “ref”: “src/components/shell/SearchInline.tsx”, “last_seen”: “2026-05-19”}, {“type”: “code”, “ref”: “functions/api/oraculo/ask.ts”, “last_seen”: “2026-05-19”}, {“type”: “code”, “ref”: “functions/api/mcp/handlers/archivo-core.ts”, “last_seen”: “2026-05-19”}, {“type”: “code”, “ref”: “src/styles/globals.css”, “last_seen”: “2026-05-19”}, {“type”: “commit”, “ref”: “a1da9a06f735c6ed19f3d9bb5caa0c42b2d313bc”}], “related”: [“entity—workspace—endpoint—oraculo-ask”, “workspace—que-es-workspace”], “content”: ”## Resumen\n\nEl Oráculo de EL es la capa de búsqueda semántica del Workspace de EsfericLabs. Con la iteración s72 (#52) el Oráculo pasa de ser un sistema one-shot (pregunta → respuesta única) a un chat conversacional multi-turno, manteniendo el historial de la sesión en estado React efímero — sin persistencia en D1.\n\nLa audiencia del Oráculo es el propio equipo (Edu / Dani / Txell): resuelve preguntas sobre CreaRack Pro, el Workspace, Claude-method y los ADRs del grafo Bibliotecario.\n\n---\n\n## Arquitectura del flujo multi-turno\n\n\nUsuario escribe pregunta (Enter)\n │\n ▼\nSearchInline.tsx\n ├── shouldAskOracle(q, mode)?\n │ SÍ → askOracle(question)\n │ ├── Append turn {role:'user'} al estado local\n │ ├── POST /api/oraculo/ask { question, history: turnos previos }\n │ └── Append turn {role:'assistant', ...metadatos}\n │\n └── NO → navega al hit Fuse (comportamiento legacy)\n\n\nEl historial vive exclusivamente en el estado React de SearchInline. Al cerrar la pestaña o pulsar “Nueva conversación” se descarta. No hay tabla D1 ni cookie para los turnos.\n\n---\n\n## Backend (archivo-core.ts + ask.ts)\n\n### buildOraclePrompts — nueva firma\n\ntypescript\nfunction buildOraclePrompts(\n question: string,\n chunks: ChunkMatch[],\n lang: 'en' | 'es',\n history?: TutorMessage[], // ← NUEVO (s72)\n): { systemPrompt: string; userPrompt: string }\n\n\nCuando history tiene turnos, los serializa en un bloque CONVERSACIÓN HASTA AHORA (ES) / CONVERSATION SO FAR (EN) que se añade al userPrompt tras los fragmentos del grafo. La serialización asigna roles Usuario/Oráculo o User/Oracle según el idioma detectado.\n\nEl systemPrompt se extiende con una sección MULTI-TURN que instruye al modelo:\n- Reconocer referencias anafóricas a respuestas anteriores (“amplía sobre eso”, “y por qué”, “explica el último punto”).\n- Mantener tono e idioma consistentes con los turnos previos.\n\nEste patrón es idéntico al modo Tutor (CNS Network Tutor), reutilizando TutorMessage como tipo compartido.\n\n### ask.ts — sanitización y cap de tokens\n\ntypescript\nconst MAX_HISTORY_MESSAGES = 12;\n\n\nEl handler lee body.history, filtra entradas inválidas (rol incorrecto, contenido vacío) y trunca a los 12 mensajes más recientes antes de pasarlos a synthesizeAnswer. El cliente puede enviar más, pero el backend siempre aplica el cap. El objetivo es limitar inflación de tokens sin perder contexto útil (el modelo se centra en lo reciente).\n\n### synthesizeAnswer — rama oracle actualizada\n\ntypescript\n// Antes (one-shot):\nbuildOraclePrompts(question, chunks, detectedLang)\n\n// Después (multi-turn):\nbuildOraclePrompts(question, chunks, detectedLang, history)\n\n\n---\n\n## Frontend (SearchInline.tsx)\n\n### Estado refactorizado\n\n| Estado anterior | Estado nuevo | Propósito |\n|---|---|---|\n| oracleAnswer: OracleResponse \\| null | turns: Turn[] | Historial completo de la conversación |\n| oracleLoading: boolean | pending: boolean | Petición en vuelo |\n| oracleError: string \\| null | (eliminado) | Errores ahora son turns con error: true |\n\n### Tipo Turn\n\ntypescript\ntype Turn =\n | { role: 'user'; content: string }\n | {\n role: 'assistant';\n content: string;\n sources: OracleSource[];\n chunksUsed: number;\n durationMs: number;\n error?: boolean; // turno de error — excluido del history enviado al backend\n };\n\n\nLos turnos con error: true se renderizan con clase is-error y no se incluyen en el payload history del siguiente POST.\n\n### Trigger de envío\n\n- Enter en la caja principal: si shouldAskOracle(q, mode) → llama askOracle(q). Si no → navega al resultado Fuse.\n- Enter en la caja de seguimiento (follow-up): siempre envía al Oráculo si hay texto y no hay petición pendiente.\n- Se elimina el debounce de 450 ms del sistema one-shot.\n\n### Modos de visibilidad\n\n\ninChat = turns.length > 0 || pending\n\ninChat = true → Fuse oculto, caja de follow-up visible\ninChat = false → Comportamiento legacy (Fuse + oracle one-shot si shouldAskOracle)\n\n\n### Auto-scroll y foco\n\n- Tras cada turno nuevo, turnsScrollRef hace scroll al fondo con un setTimeout(30ms) para esperar el re-render.\n- Tras recibir respuesta assistant, el foco pasa automáticamente a la caja de follow-up.\n\n### Botón “Nueva conversación”\n\nAparece en el header del dropdown cuando inChat. Al pulsarlo:\n1. Aborta el AbortController de la petición en vuelo.\n2. Resetea turns, pending, followUpQ, q, results.\n3. Devuelve el foco al inputRef principal.\n\n---\n\n## CSS (globals.css)\n\nClases nuevas introducidas en s72:\n\n| Clase | Propósito |\n|---|---|\n| .search-inline-oracle-chat | Contenedor scrollable de turnos. max-height: 380px; overflow-y: auto |\n| .search-inline-oracle-turn | Un turno individual (user o assistant). border-bottom entre turnos |\n| .search-inline-oracle-turn.user | Fondo var(--overlay-1) para distinguir turnos del usuario |\n| .search-inline-oracle-turn.is-error | Fondo color-mix(danger 8%) para errores |\n| .search-inline-oracle-role | Label “Tú” / “Oráculo” — uppercase, 11px, var(--fg-dim) |\n| .search-inline-oracle-user-text | Texto del turno usuario — white-space: pre-wrap |\n| .search-inline-oracle-meta-inline | Metadatos inline junto al role (fuentes, ms) |\n| .search-inline-oracle-followup | Contenedor de la caja de seguimiento |\n| .search-inline-oracle-followup-input | Input de follow-up con estados :focus y :disabled |\n| .search-inline-new-convo | Botón pill “Nueva conversación” — margin-left: auto en el toggle |\n\nLos tokens CSS (--overlay-1, --border-soft, --accent, --danger, etc.) son los ya existentes en el sistema de diseño del Workspace.\n\n---\n\n## Decisiones de diseño\n\n| Decisión | Razón |\n|---|---|\n| Sin persistencia D1 | La conversación es exploratoria. No hay valor en recuperar el historial entre sesiones para el caso de uso del equipo |\n| Cap a 12 turnos en el backend | Anti-inflación de tokens con Gemini. El backend impone el límite aunque el cliente mande más |\n| Errores como turnos is-error | Mantiene el flujo visual del chat sin romper el estado. El usuario ve qué falló sin perder la conversación |\n| Fuse oculto durante chat activo | Evitar mezclar resultados de búsqueda con el chat en progreso |\n| Enter sin debounce | Comportamiento esperado en una UI tipo chat. El debounce era específico del modo one-shot anti-tormenta |\n| Reutilización de TutorMessage | Mismo patrón que CNS Network Tutor — coherencia interna del sistema |\n\n---\n\n## Véase también\n\n- [[entity—workspace—endpoint—oraculo-ask]] — Especificación del endpoint /api/oraculo/ask con la shape history\n- [[workspace—que-es-workspace]] — Visión general del Workspace de EsfericLabs donde vive esta feature”}

Subir