7.1 KiB
Plan: Chat lateral con tools sobre todo el kanban
Objetivo
Panel de chat a la derecha (Mantine AppShell.Aside) que conversa con un LLM y manipula el kanban via tool-calling: crear/renombrar/mover/borrar columnas y tarjetas, consultar metricas (tiempo en columna, historial), explicar el board, etc.
Arquitectura
+---------------------------+----------------+
| Board (DnD) | Chat Aside |
| | |
| Cols / Cards | msg msg msg |
| | [input...] |
+---------------------------+----------------+
| |
v v
/api/... /api/chat (SSE)
\ /
v v
kanban backend (Go)
|
+-- llamadas internas a las
mismas funciones del backend
(no HTTP loopback)
- No HTTP loopback: el endpoint
/api/chatejecuta los tools llamando directamente adb.*(mismas funciones que usan los handlers HTTP). Cero overhead, cero auth-loop. - SSE streaming: respuestas +
tool_usedeltas streamed al frontend para UX viva. - State sharing: el chat ve y modifica el mismo
operations.dbque la UI. Tras una mutacion via tool, el frontend hacereload()del board para reflejar cambios.
Backend Go
Nuevo paquete apps/kanban/chat/
| Archivo | Responsabilidad |
|---|---|
chat.go |
Endpoint POST /api/chat con SSE. Loop: send msgs+tools a Claude API, recibe tool_use, ejecuta, reinyecta tool_result, hasta end_turn. |
tools.go |
Catalogo de tools: nombre, JSON schema, dispatch a db.*. |
claude.go |
Cliente Claude API (HTTP directo, sin SDK Go porque no hay oficial). Usa infra.HttpPostJSON + streaming manual. |
Tool catalog (mapeo 1:1 con la API REST + extras)
| Tool name | Input | Output | DB call |
|---|---|---|---|
list_board |
{} |
{columns,cards} con time_in_column_ms |
ListColumns + ListCardsWithTime |
create_column |
{name:string} |
Column |
CreateColumn |
rename_column |
{id:string, name:string} |
{} |
UpdateColumn |
delete_column |
{id:string} |
{} |
DeleteColumn |
reorder_columns |
{ids:string[]} |
{} |
ReorderColumns |
create_card |
{column_id, requester?, title, description?} |
Card |
CreateCard |
update_card |
{id, requester?, title?, description?} |
{} |
UpdateCard |
delete_card |
{id} |
{} |
DeleteCard |
move_card |
{id, column_id, position?} |
{} |
MoveCard (calcula ordered_ids si solo position) |
card_history |
{id} |
[{column_name,duration_ms,...}] |
CardHistory |
find_cards |
{query?:string, column_id?:string, requester?:string} |
Card[] |
filtro en memoria sobre ListCardsWithTime |
column_stats |
{column_id} |
{count, total_time_ms, avg_time_ms, oldest_card_id} |
derivado |
bulk_create_cards |
{column_id, cards:[{requester?,title,description?}]} |
Card[] |
loop CreateCard |
Tools puros (sin escritura) marcados read_only en metadata para mostrar badge "solo lectura" en UI.
Configuracion
- Env vars:
ANTHROPIC_API_KEY(obligatoria),KANBAN_CHAT_MODEL(default:claude-sonnet-4-6),KANBAN_CHAT_SYSTEM(opcional, override del system prompt). - Sistema prompt incluido en el binario:
Eres asistente del tablero kanban. Antes de modificar, llama
list_boardpara ver el estado. Cuando el usuario nombre tarjetas o columnas, resuelve elidconfind_cards/list_board. Confirma cambios destructivos antes de borrar. - Coste: prompt-caching del system + tools schema (5 min TTL) → llamadas siguientes baratas.
SSE protocol al frontend
event: text
data: "Voy a mover la tarjeta..."
event: tool_use
data: {"id":"toolu_01","name":"move_card","input":{...}}
event: tool_result
data: {"tool_use_id":"toolu_01","ok":true,"result":{...}}
event: text
data: "Hecho. Ahora esta en Doing."
event: done
data: {"stop_reason":"end_turn","usage":{"input_tokens":1234,"output_tokens":56}}
Frontend
Layout
AppShell con aside={{ width: 360, breakpoint: "md" }}. Toggle con icon en header (IconMessageChatbot) — colapsable para no robar espacio en monitores chicos.
Componentes nuevos
| Componente | Funcion |
|---|---|
ChatPanel.tsx |
Lista de mensajes + input. Usa EventSource para SSE. |
ChatMessage.tsx |
Renderiza turn: texto markdown, tool_use cards (nombre+input pretty-printed), tool_result chips. |
useKanbanChat.ts |
Hook: estado de turnos, persistencia en localStorage, trigger de onBoardChange (reload) tras cada tool_result exitoso. |
Markdown
react-markdown + remark-gfm para tablas/listas en respuestas del LLM.
Botones rapidos en cada tarjeta/columna
ActionIcon "Preguntar al chat sobre esto" inyecta contexto:
Sobre la tarjeta
{title}(id{id}): ...
Persistencia chat
Conversaciones por sesion en localStorage (clave kanban_chat_v1). NO se persiste en SQLite por ahora — es state efimero. Si en el futuro se quiere historico → tabla chat_messages en operations.db.
Seguridad
- Tools de borrado (
delete_column,delete_card) requieren confirmacion explicita en el system prompt. Si el LLM las invoca sin confirmar, el frontend abre modal de confirmacion antes de pasar el resultado al loop. ANTHROPIC_API_KEYsolo en backend, NUNCA expuesta al frontend.
Hitos (orden de ejecucion sugerido)
- Tools.go + tests: catalogo + dispatch puro Go, sin LLM. Probar con curl manual.
- Endpoint
/api/chatno streaming: request → response sincrono. Validar Claude API + tool loop. - Streaming SSE.
- ChatPanel UI +
useKanbanChat. - Toggle aside + layout responsive.
- Confirmacion de tools destructivos.
- Botones contextuales en cards/columns.
Funciones del registry a delegar a fn-constructor (registry-first)
Antes de codear el chat, crear estas primitivas reutilizables:
| ID | Lenguaje | Proposito |
|---|---|---|
claude_messages_call_go_infra |
Go | Wrapper sobre POST https://api.anthropic.com/v1/messages con tools, system, prompt-caching. Impure, error_type. |
claude_messages_stream_go_infra |
Go | Idem pero con SSE streaming, retorna canal de eventos. Impure. |
sse_writer_go_infra |
Go | Helper para escribir SSE eventos en http.ResponseWriter (set headers, flusher, format event:/data:). Pure factory. |
Estas tres son utiles en cualquier app que necesite chat o LLM tools — no se quedan en el kanban.
Decisiones pendientes (preguntar al usuario antes de codear)
- Modelo:
claude-opus-4-7(mas capaz, mas caro) vsclaude-sonnet-4-6(default razonable) vsclaude-haiku-4-5(rapido, barato). - Streaming: empezar simple (request/response sincrono) o ir directo a SSE.
- Persistencia chat: solo
localStorageo tambien tabla enoperations.dbpara historico cross-session. - Confirmaciones destructivas: bloqueo en frontend o solo via prompt? Si bloqueo, ¿qué define "destructivo"?
- Limite de turnos por sesion: para evitar tool-loops infinitos, cap (ej. 20 iteraciones por mensaje del usuario).