Chat Tutor multi-turn — hilo conversacional en el Help Widget
Chat Tutor multi-turn — hilo conversacional en el Help Widget
Resumen
El modo IT Tutor del Help Widget pasa de ser un sistema de pregunta-respuesta única a un chat con historial conversacional (s55). El usuario puede encadenar preguntas de seguimiento y el modelo mantiene el contexto entre mensajes, comportándose como un tutor de IT interactivo.
La funcionalidad convive con el modo Help existente (búsqueda contextual en la wiki de la app), que no recibe historial y mantiene su flujo de respuesta única.
Decisiones de diseño (consensuadas con Edu)
| Decisión | Valor |
|---|---|
| Persistencia del hilo | Efímera — se limpia al cerrar el panel o al cambiar de modo |
| Límite de historial | Últimos 10 turnos enviados al modelo (recorte en cliente) |
| Streaming | Sin streaming (MVP) — respuesta completa de golpe |
| Arranque en modo Help | Sin cambios — historial no aplica |
| “New chat” | Botón visible solo cuando hasChat === true |
Arquitectura
Frontend — alpine-components.js (helpWidget)
chatMessages[] ← array de turnos { role, content, htmlContent }
ask() ← bifurca lógica tutor vs help
newChat() ← limpia hilo (botón "New chat")
_renderChat() ← innerHTML directo en #help-chat-list (patrón CSP)
_stripHtmlTags(html) ← extrae texto plano del HTML para enviar al modelo
hasChat (getter) ← tutorMode && chatMessages.length > 0
Patrón CSP: Alpine CSP build no soporta x-html. El renderizado de burbujas se hace mediante innerHTML directo en #help-chat-list desde _renderChat(), igual que openArticle() y el bloque help-answer-body del modo Help.
Flujo tutor ask():
- Push del turno
useral arraychatMessagesantes del fetch → aparece en la UI inmediatamente. - POST a
/api/help/askcon{ mode: 'tutor', question, history: chatMessages.slice(-10) }. - Push de la respuesta
assistantal array. _renderChat()actualiza el DOM.- El mensaje de “Thinking…” se muestra durante
loadingy desaparece en el re-render final.
Template — base.html
<!-- Bloque exclusivo tutorMode -->
<template x-if="tutorMode">
<div class="help-chat">
<div class="help-chat-bar">
<span class="help-chat-mode-badge">IT Tutor chat</span>
<button x-show="hasChat" @click.stop="newChat()">New chat</button>
</div>
<div class="help-chat-list" id="help-chat-list"></div>
</div>
</template>
<!-- Bloques help pasan a !tutorMode -->
<template x-if="!tutorMode && loading"> … </template>
<template x-if="!tutorMode && hasAnswer"> … </template>
<template x-if="!tutorMode && hasError"> … </template>
El badge help-answer-mode-badge (s53) queda eliminado — dead code reemplazado por el badge help-chat-mode-badge dentro del hilo.
Backend — core/api_help.py
El proxy /api/help/ask extrae history del payload solo cuando mode == 'tutor' y lo reenvía al MCP workspace:
history = payload.get("history") if mode == "tutor" else None
# history se pasa a _mcp_call("bib_ask", {..., "history": history})
El recorte a 10 turnos se aplica en cliente (chatMessages.slice(-10)). El backend pasa el array sin modificar al workspace.
El workspace (commit 7406e35) introduce TutorMessage y modifica buildTutorPrompts / synthesizeAnswer / handleAsk para consumir el historial.
Estilos — help.css
Nuevas clases introducidas:
| Clase | Propósito |
|---|---|
.help-chat | Contenedor columna del hilo (gap 8px) |
.help-chat-bar | Barra superior: badge + botón “New chat” |
.help-chat-mode-badge | Badge “IT Tutor chat” (pill naranja, --warning) |
.help-chat-list | Lista scrollable (max-height: 420px, overflow-y: auto) |
.help-chat-msg | Burbuja base (padding, border-radius, font-size) |
.help-chat-msg-user | Burbuja usuario: alineada derecha, fondo accent translúcido |
.help-chat-msg-assistant | Burbuja asistente: alineada izquierda, card-bg |
.help-chat-msg-loading | Estado “Thinking…”: opacity: 0.7, cursiva |
.help-chat-msg-content | Wrapper interno con estilos para <p>, <code>, <pre> |
Eliminada: .help-answer-mode-badge (s53 — obsoleta).
Lifecycle del hilo
toggleTutor() ──→ chatMessages = [] (cambio de modo)
close(true) ──→ chatMessages = [] (cierre del panel)
newChat() ──→ chatMessages = [] (acción explícita del usuario)
El flag localStorage.helpTutorMode persiste entre páginas, pero el hilo nunca persiste — es efímero por diseño.
Getters eliminados / renombrados
| s53 | s55 |
|---|---|
isTutorAnswer → !!(answer && answer.mode==='tutor') | Eliminado (dead code) |
| — | hasChat → tutorMode && chatMessages.length > 0 |
Limitaciones conocidas (MVP)
- Sin streaming: el usuario espera la respuesta completa antes de verla.
- El historial se trunca a 10 turnos en cliente; conversaciones largas pierden contexto antiguo.
_renderChat()reescribe el DOM completo en cada turno (no diff incremental). Aceptable con listas cortas.- Sin persistencia cross-session: refrescar la página limpia el hilo aunque
tutorModeesté activo.
Véase también
- [[entity—core—endpoint—help-ask]]
- [[feature—help—it-tutor-mode]]
- [[concept—help—help-widget]]
- [[entity—core—endpoint—help-wiki]]
- [[entity—core—endpoint—help-article]]