--- id: "ET-LLM-007" title: "Especificación Frontend del Módulo LLM Agent" type: "Technical Specification" status: "Done" priority: "Alta" epic: "OQI-007" project: "trading-platform" version: "1.0.0" created_date: "2025-12-15" updated_date: "2026-01-25" --- # ET-LLM-007: Especificación Frontend del Módulo LLM Agent **Épica:** OQI-007 - LLM Strategy Agent **Versión:** 1.0 **Fecha:** 2025-12-15 **Estado:** Implementado **Prioridad:** P1 - Alto --- ## Resumen Esta especificación define la arquitectura, componentes y servicios del frontend para el módulo LLM Strategy Agent. El frontend proporciona una interfaz conversacional interactiva que permite a los usuarios comunicarse con el agente LLM para análisis de trading, consultas estratégicas y visualización de señales en tiempo real. --- ## Arquitectura General ``` ┌──────────────────────────────────────────────────────────────────────┐ │ FRONTEND (React) │ │ Port: 3000 │ ├──────────────────────────────────────────────────────────────────────┤ │ │ │ ┌────────────────────┐ ┌────────────────────┐ ┌────────────────┐ │ │ │ Pages │ │ Components │ │ Services │ │ │ ├────────────────────┤ ├────────────────────┤ ├────────────────┤ │ │ │ • AssistantPage │ │ • ChatWindow │ │ • llmAgentSvc │ │ │ │ • ChatPage │ │ • ChatMessage │ │ • llmProviders │ │ │ │ • StrategyPage │ │ • ChatInput │ │ • tokenService │ │ │ │ • AnalysisPage │ │ • SignalCard │ │ • storageService│ │ │ │ │ │ • ToolCallCard │ │ │ │ │ │ │ │ • MessageList │ │ │ │ │ │ │ │ • SidebarConv │ │ │ │ │ │ │ │ • LoadingSpinner │ │ │ │ │ │ │ │ • ErrorBoundary │ │ │ │ │ └────────────────────┘ └────────────────────┘ └────────────────┘ │ │ │ │ │ │ │ └──────────────────────┼──────────────────────┘ │ │ │ │ │ ┌────────────────────────▼──────────────────────────┐ │ │ │ Zustand Store │ │ │ │ ┌─────────────┐ ┌───────────┐ ┌─────────────┐ │ │ │ │ │ chatStore │ │ uiStore │ │ signalStore │ │ │ │ │ └─────────────┘ └───────────┘ └─────────────┘ │ │ │ └────────────────────┬───────────────────────────────┘ │ │ │ │ │ ┌────────────────────▼──────────────────────────────┐ │ │ │ Query Client (React Query) │ │ │ │ Caching, Sincronización, Invalidación │ │ │ └────────────────────┬───────────────────────────────┘ │ │ │ │ └──────────────────────────────┼───────────────────────────────────────┘ │ ┌───────────────────┼───────────────────┐ │ │ │ ┌──────────▼────────┐ ┌───────▼────────┐ ┌──────▼──────────┐ │ LLM Agent API │ │ Backend API │ │ WebSocket │ │ :3085 │ │ :3080 │ │ :3085/ws │ │ │ │ │ │ │ │ POST /chat │ │ GET /profiles │ │ message:send │ │ GET /history │ │ GET /signals │ │ agent:stream │ │ POST /strategies │ │ POST /orders │ │ agent:complete │ │ DELETE /convs │ │ GET /portfolio │ │ error │ │ │ │ │ │ │ └───────────────────┘ └────────────────┘ └─────────────────┘ ``` --- ## Stack Tecnológico Frontend ```yaml Core: - React: 18.2.0 - TypeScript: 5.3.0 - Vite: 6.2.0 - React Router: 6.18.0 State Management: - Zustand: 4.4.7 - React Query (TanStack Query): 5.14.0 Styling: - Tailwind CSS: 3.3.0 - Headless UI: 1.7.0 - Radix UI: 1.0.0 Components & UI: - React Markdown: 8.0.0 - Highlight.js: 11.8.0 - Recharts: 2.10.0 (para gráficos simples) - lightweight-charts: 4.1.1 (para charts de trading) Utilities: - axios: 1.6.0 - date-fns: 2.30.0 - clsx: 2.0.0 - zustand: 4.4.7 Testing: - Vitest: 1.0.0 - @testing-library/react: 14.1.0 - @testing-library/user-event: 14.5.0 Dev Tools: - @types/react: 18.2.0 - @types/node: 20.9.0 - ESLint: 8.50.0 - Prettier: 3.0.0 ``` --- ## Estructura de Directorios Frontend ``` apps/frontend/src/ ├── pages/ │ ├── AssistantPage.tsx # Página principal del asistente │ ├── ChatPage.tsx # Página de chat conversacional │ ├── StrategyPage.tsx # Análisis de estrategias │ └── AnalysisPage.tsx # Análisis detallados │ ├── modules/ │ └── llm-agent/ │ ├── components/ │ │ ├── ChatWindow.tsx │ │ ├── ChatMessage.tsx │ │ ├── ChatInput.tsx │ │ ├── SignalCard.tsx │ │ ├── ToolCallCard.tsx │ │ ├── MessageList.tsx │ │ ├── SidebarConversations.tsx │ │ ├── LoadingIndicator.tsx │ │ └── ErrorBoundary.tsx │ │ │ ├── hooks/ │ │ ├── useChat.ts │ │ ├── useLlmAgent.ts │ │ ├── useSignals.ts │ │ ├── useWebSocket.ts │ │ └── useMessageStream.ts │ │ │ ├── services/ │ │ ├── llmAgentService.ts # Cliente API LLM Agent │ │ ├── websocketService.ts # Conexión WS │ │ ├── llmProviderService.ts # Adaptadores OpenAI/Claude │ │ └── tokenService.ts # Conteo de tokens │ │ │ ├── stores/ │ │ ├── chatStore.ts # Zustand chat store │ │ ├── uiStore.ts # UI state │ │ └── signalStore.ts # Señales y análisis │ │ │ ├── types/ │ │ ├── index.ts │ │ ├── chat.types.ts │ │ ├── signal.types.ts │ │ └── api.types.ts │ │ │ ├── utils/ │ │ ├── messageFormatters.ts │ │ ├── tokenCounter.ts │ │ ├── errorHandlers.ts │ │ └── conversationHelpers.ts │ │ │ ├── hooks.test.ts │ ├── services.test.ts │ └── components.test.tsx │ ├── shared/ │ ├── components/ │ │ ├── Button.tsx │ │ ├── Card.tsx │ │ ├── Modal.tsx │ │ └── Spinner.tsx │ │ │ └── hooks/ │ └── useWindowSize.ts │ ├── stores/ │ └── authStore.ts # Estado de autenticación global │ ├── services/ │ ├── api.ts # Configuración axios │ ├── authService.ts # JWT, login, logout │ └── storageService.ts # LocalStorage helpers │ ├── App.tsx ├── main.tsx ├── index.css └── types.d.ts ``` --- ## Componentes Principales ### 1. ChatWindow Component **Ubicación:** `apps/frontend/src/modules/llm-agent/components/ChatWindow.tsx` **Responsabilidad:** Contenedor principal que orquesta el chat conversacional ```typescript interface ChatWindowProps { conversationId: string; onClose?: () => void; initialMessage?: string; theme?: 'light' | 'dark'; } export function ChatWindow({ conversationId, onClose, initialMessage, theme = 'light' }: ChatWindowProps) { // State management const { messages, isLoading, streamingContent } = useChatStore(); const { connected, connect, disconnect } = useWebSocket(); // Lifecycle useEffect(() => { // Conectar y cargar historial }, [conversationId]); // Handlers const handleSendMessage = (content: string) => { // Enviar mensaje vía WS }; const handleCancel = () => { // Cancelar generación }; return (
); } ``` **Features:** - Soporte para múltiples conversaciones - Auto-scroll a últimos mensajes - Indicador de conexión - Tema claro/oscuro - Responsive design (mobile-first) --- ### 2. ChatMessage Component **Ubicación:** `apps/frontend/src/modules/llm-agent/components/ChatMessage.tsx` **Responsabilidad:** Renderizar mensaje individual con soporte para markdown, código y tool calls ```typescript interface ChatMessageProps { message: Message; isStreaming?: boolean; onFeedback?: (rating: number, comment?: string) => void; } export function ChatMessage({ message, isStreaming, onFeedback }: ChatMessageProps) { const [showFeedback, setShowFeedback] = useState(false); const [feedback, setFeedback] = useState(); return (
{message.role === 'user' ? : }
{/* Renderizar contenido */} {/* Tool calls si existen */} {message.toolCalls && (
{message.toolCalls.map((tool) => ( ))}
)} {/* Signals si existen */} {message.signals && (
{message.signals.map((signal) => ( ))}
)} {/* Feedback */} {message.role === 'assistant' && ( )}
{formatTime(message.createdAt)} {message.tokensInput && ( {message.tokensInput + (message.tokensOutput || 0)} tokens )}
); } ``` **Features:** - Renderizado seguro de Markdown - Highlight de código con Highlight.js - Soporte para LaTeX matemático - Feedback rating (thumbs up/down + comentario) - Copyable code blocks - Display de tokens consumidos --- ### 3. ChatInput Component **Ubicación:** `apps/frontend/src/modules/llm-agent/components/ChatInput.tsx` **Responsabilidad:** Input de usuario con soporte para multi-line, attachments y comandos ```typescript interface ChatInputProps { onSend: (content: string, files?: File[]) => void; onCancel?: () => void; isLoading?: boolean; disabled?: boolean; maxLength?: number; placeholder?: string; } export function ChatInput({ onSend, onCancel, isLoading, disabled, maxLength = 4000, placeholder = 'Escribe tu consulta aquí...' }: ChatInputProps) { const [content, setContent] = useState(''); const [files, setFiles] = useState([]); const textareaRef = useRef(null); // Auto-resize textarea useEffect(() => { if (textareaRef.current) { textareaRef.current.style.height = 'auto'; textareaRef.current.style.height = Math.min(textareaRef.current.scrollHeight, 200) + 'px'; } }, [content]); const handleSend = () => { if (!content.trim()) return; onSend(content, files); setContent(''); setFiles([]); }; const handleKeyDown = (e: KeyboardEvent) => { // Enviar con Ctrl+Enter o Cmd+Enter if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); handleSend(); } }; return (
{/* File attachments preview */} {files.length > 0 && (
{files.map((file) => ( setFiles(f => f.filter(x => x !== file))} /> ))}
)}
{/* Attach files */}