/* ============================================================ FLOWSHIELD — Asesor IA (chat por facción) ============================================================ */ const ADVISOR_EXAMPLES = { redteam: [ 'Quiero hacer un pentest autorizado para escanear vulnerabilidades de una web.', 'Quiero sacar la contraseña a partir de un hash que he obtenido.', 'Quiero hacer fingerprinting de un servicio y detectar vulnerabilidades en los servicios expuestos.', 'Voy a auditar una API REST con autorización, ¿qué orden sigo?', 'Tengo el puerto 22 abierto, ¿cómo audito las credenciales SSH de forma autorizada?', 'Quiero enumerar subdominios y la superficie de un dominio antes de atacar.', 'Sospecho de inyección SQL en un parámetro, ¿cómo lo confirmo y exploto?', 'Voy a auditar un WordPress, ¿por dónde empiezo?', ], blueteam: [ 'Voy a bastionar un servidor Ubuntu con Nginx, ¿cuál es el plan?', 'Quiero endurecer un Windows Server según CIS, ¿qué perfil aplico?', 'Voy a montar una base de datos PostgreSQL, ¿qué puntos críticos reviso?', '¿Cómo verifico que mi remediación cerró los fallos del último escaneo?', 'Necesito un plan de hardening para un host Docker expuesto.', 'Quiero priorizar las reglas CIS fallidas de mayor impacto, ¿cómo lo enfoco?', ], }; function AiAdvisor({ faction, onOpenModule }) { const { useState, useEffect, useRef } = React; const [messages, setMessages] = useState([]); // {role, content, modules?} const [convId, setConvId] = useState(null); const [input, setInput] = useState(''); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const [exIdx, setExIdx] = useState(0); const scrollRef = useRef(null); const examples = ADVISOR_EXAMPLES[faction] || ADVISOR_EXAMPLES.redteam; useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [messages, busy]); // Rota los ejemplos mientras el chat está vacío. useEffect(() => { if (messages.length > 0) return; const t = setInterval(() => setExIdx(i => (i + 1) % examples.length), 3800); return () => clearInterval(t); }, [messages.length, examples.length]); const send = async () => { const text = input.trim(); if (!text || busy) return; setError(''); setInput(''); setMessages(m => [...m, { role: 'user', content: text }]); setBusy(true); try { const r = await window.fsApi('/api/assistant/chat', { method: 'POST', body: { faction, conversation_id: convId, message: text }, }); setConvId(r.conversation_id || convId); setMessages(m => [...m, { role: 'assistant', content: r.reply || '', modules: r.referenced_modules || [], workflow: r.workflow || [] }]); } catch (e) { const msg = (e && e.message) ? e.message : 'No se pudo contactar con el asesor.'; setError(msg); } finally { setBusy(false); } }; const onKey = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }; // Limpia la última línea "MÓDULOS: ..." del texto mostrado (se renderiza como chips). const cleanText = (t) => (t || '').replace(/(?:^|\n)\s*M[ÓO]DULOS\s*:.*$/i, '').trim(); const downloadWorkflow = (m) => { const wf = m.workflow || []; const esc = (s) => String(s || '').replace(/[&<>]/g, c => ({ '&':'&','<':'<','>':'>' }[c])); const steps = wf.map(s => ( '
' + esc(cleanText(m.content)) + ''; const blob = new Blob([html], { type: 'text/html' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'flowshield-workflow-' + faction + (convId ? '-' + convId : '') + '.html'; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000); }; return (