/* global React, Icon, API */ const { useState: useExtraState, useEffect: useExtraEffect } = React; function studioData() { return window.MOCK || {}; } function refreshStudio() { return window.STUDIO && window.STUDIO.refresh ? window.STUDIO.refresh() : Promise.resolve(); } const studioToast = window.studioToast || function(m, t) { if (window.STUDIO?.toast) window.STUDIO.toast(m, t); }; const studioForm = window.studioForm || function(c) { return window.STUDIO?.form ? window.STUDIO.form(c) : Promise.resolve(null); }; const studioConfirm = window.studioConfirm || function(c) { return window.STUDIO?.confirm ? window.STUDIO.confirm(c) : Promise.resolve(false); }; function agentName(id) { const agent = (studioData().agents || []).find(item => String(item.id) === String(id)); return agent ? agent.name : '--'; } function statusTag(value) { if (['online', 'open', 'ok', 'finished'].includes(value)) return 'ok'; if (['connecting', 'qr-pendente', 'in_progress', 'warn', 'pausado', 'not_found'].includes(value)) return 'warn'; if (['unanswered', 'offline', 'expired', 'bad', 'error'].includes(value)) return 'bad'; return ''; } function channelStatusInfo(instanceName) { const inst = (studioData().instances || []).find(item => item.id === instanceName || item.name === instanceName || item.raw?.name === instanceName); return whatsappInstanceUiState(inst, instanceName); } function channelStatus(instanceName) { return channelStatusInfo(instanceName).status; } function firstAgentId() { return (studioData().agents || [])[0]?.id || null; } function assistantInitialAgentId(agents) { const stored = window.localStorage?.getItem('sm_assistant_focus_agent_id'); if (stored && agents.some(agent => String(agent.id) === String(stored))) return String(stored); return String(agents[0]?.id || ''); } const TESTER_FLOW_STEPS = [ 'Preparando cenários', 'Lendo configuração', 'Verificando materiais', 'Enviando perguntas', 'Analisando respostas', 'Gerando diagnóstico', 'Sugerindo melhorias', ]; const OPTIMIZER_FLOW_STEPS = [ 'Analisando configuração', 'Revisando testes', 'Verificando conhecimento', 'Identificando falhas', 'Gerando melhorias', 'Comparando versões', 'Preparando histórico', ]; function useAssistantFlow(active, labels) { const [current, setCurrent] = useExtraState(0); useExtraEffect(() => { if (!active) { setCurrent(0); return undefined; } setCurrent(0); const timer = window.setInterval(() => { setCurrent(prev => Math.min(prev + 1, labels.length - 1)); }, 950); return () => window.clearInterval(timer); }, [active, labels.length]); return labels.map((label, index) => ({ label, status: active ? (index < current ? 'done' : index === current ? 'running' : 'pending') : 'pending', })); } function flowState(labels, active, finished, runningSteps) { if (active) return runningSteps; return labels.map(label => ({ label, status: finished ? 'done' : 'pending' })); } function fieldNames(table) { const fields = table?.schema_json?.fields; return Array.isArray(fields) ? fields.map(f => f.name || f.id).filter(Boolean) : []; } async function runAction(fn) { try { await fn(); await refreshStudio(); } catch (err) { studioToast(err.message || String(err), 'bad'); } } function PageTitle({ eyebrow, title, sub, meta, action }) { return (
{eyebrow}

{title}

{sub}

{action || meta}
); } function MiniKpi({ label, value, sub }) { return (
{label}
{value}
{sub &&
{sub}
}
); } function EmptyRow({ cols, text }) { return {text}; } function prettyPageValue(value) { return window.prettyStudioValue ? window.prettyStudioValue(value) : String(value == null ? '' : value); } function truncateText(value, max = 220) { const text = typeof value === 'object' && value !== null ? prettyPageValue(value) : String(value == null ? '' : value); return text.length > max ? text.slice(0, max - 1).trimEnd() + '...' : text; } function pickDisplayColumns(columns) { const priority = ['conversation_id', 'agent_id', 'agent_name', 'customer_phone', 'customer_name', 'event', 'summary', 'created_at']; const picked = priority.filter(col => columns.includes(col)); columns.forEach(col => { if (picked.length < 6 && !picked.includes(col)) picked.push(col); }); return picked.slice(0, 6); } function recordCellClass(col) { return /summary|transcript|metadata|message|content/i.test(col) ? 'record-long' : ''; } const ECOSYSTEM_STEPS = [ ['tables', 'Dados'], ['knowledge_bases', 'Conhecimento'], ['agents', 'Agentes'], ['squads', 'Squads'], ['tools', 'Ferramentas'], ['channels', 'Canais'], ['teams', 'Time humano'], ['tested', 'Teste'], ['published', 'Produção'], ]; function ecosystemRoute(id) { if (id === 'knowledge_bases') return 'kb'; if (id === 'teams') return 'team'; if (id === 'published' || id === 'tested') return 'agents'; if (id === 'attendance') return 'team'; return id; } function ecosystemStateLabel(percent) { if (percent >= 85) return 'Pronto para produção assistida'; if (percent >= 55) return 'Setup em validação'; if (percent >= 25) return 'Fundação em montagem'; return 'Começo guiado'; } window.EcosystemPage = function EcosystemPage({ setPage, onOpenAgent }) { const [status, setStatus] = useExtraState(null); const [loading, setLoading] = useExtraState(true); const [error, setError] = useExtraState(''); const data = studioData(); const agents = data.agents || []; const progress = status?.setup_progress || {}; const readyCount = ECOSYSTEM_STEPS.filter(([id]) => !!progress[id]).length; const percent = Math.round((readyCount / ECOSYSTEM_STEPS.length) * 100); async function load() { setLoading(true); setError(''); try { setStatus(await API.getEcosystemStatus()); } catch (err) { setError(err.message || String(err)); setStatus(null); } finally { setLoading(false); } } useExtraEffect(() => { load(); }, []); const recs = status?.recommendations || []; const stateLabel = ecosystemStateLabel(percent); const publishedCount = agents.filter(agent => agent.isPublished).length; const openTicketCount = (data.tickets || []).filter(ticket => ['unanswered', 'in_progress'].includes(ticket.rawStatus || ticket.raw?.status)).length; const publicConversationCount = (data.conversations || []).filter(conv => conv.channel === 'public' || conv.raw?.state?.source === 'public_test' || String(conv.number || '').startsWith('pub-')).length; return ( <> } />
{error &&
Falha ao carregar mapa. Atualize novamente ou verifique o backend. {error}
}
Saúde operacional

{agents.length ? stateLabel : 'Nenhum agente criado'}

{agents.length ? `${readyCount} de ${ECOSYSTEM_STEPS.length} etapas gerais estão prontas. A leitura principal agora é por agente, sem painel genérico misturado com cards antigos.` : 'Crie o primeiro bot para liberar o mapa por agente, testes internos, link público e checklist de publicação.'}

Agentes{agents.length}
Publicados{publishedCount}
Filas abertas{openTicketCount}
Testes públicos{publicConversationCount}
{!agents.length ? (
Crie o primeiro agente para montar o ecossistema.

Depois disso o mapa passa a mostrar canais, conhecimento, ferramentas, filas, atendimentos, testes, publicação e pendências por agente.

) : (
{agents.map(agent => { const agentId = String(agent.id); const agentConvs = (data.conversations || []).filter(conv => String(conv.agentId || conv.agent_id || conv.raw?.agent_id || '') === agentId); const publicConvs = agentConvs.filter(conv => conv.channel === 'public' || conv.raw?.state?.source === 'public_test' || String(conv.number || '').startsWith('pub-')); const agentTickets = (data.tickets || []).filter(ticket => String(ticket.raw?.agent_id || ticket.agent_id || '') === agentId); const openTickets = agentTickets.filter(ticket => ['unanswered', 'in_progress'].includes(ticket.rawStatus || ticket.raw?.status)); const agentRuns = (data.runs || []).filter(run => String(run.agent_id || '') === agentId); const testRuns = agentRuns.filter(run => run.is_test || ['agent_test', 'sandbox', 'system_assistant_tester'].includes(run.input?.source)); const agentKbs = (data.knowledge || []).filter(kb => String(kb.agent_id || '') === agentId); const agentTools = (data.tools || []).filter(tool => String(tool.agent_id || '') === agentId); const agentChannels = (data.channels || []).filter(channel => String(channel.agent_id || '') === agentId || channel.instance_name === agent.channel || channel.instance_name === agent.raw?.instance_token); const agentRecs = recs.filter(item => String(item.agent_id || '') === agentId); const channelState = agent.statusTag || statusTag(agent.status); const pendingItems = [ !(agent.kbSources || agentKbs.length) && 'Adicionar conhecimento', !agentTools.length && 'Ativar ferramentas', !agentChannels.length && 'Vincular canal', !testRuns.length && 'Rodar teste', !agent.isPublished && 'Publicar quando pronto', ...agentRecs.map(item => item.message), ].filter(Boolean).slice(0, 4); return (
{(agent.name || '?')[0].toUpperCase()}
{agent.name}{agent.model || 'modelo não informado'}
Status{agent.setupLabel || 'Rascunho'}
Canais{agentChannels.length || (agent.channel && agent.channel !== '--' ? 1 : 0)} · {agent.statusLabel || 'sem conexão'}
Conhecimento{agent.kbSources || agentKbs.length} base(s)
Ferramentas{agent.tools || agentTools.length} ativa(s)
Filas{openTickets.length} abertas
Atendimentos{agentConvs.length} threads
Testes{testRuns.length} internos · {publicConvs.length} públicos
Publicação{agent.isPublished ? 'Publicado' : 'Rascunho'}
Canais {agentChannels.length ? agentChannels.slice(0, 3).map(channel => ) : }
Pendências {pendingItems.length ? pendingItems.map((item, idx) => ) :
Sem pendências críticas
}
); })}
)}
Recomendações do sistema
Priorizadas a partir das configurações atuais.
{recs.map((item, idx) => ( ))} {!recs.length &&
Nenhuma recomendação pendente.Continue monitorando conversas e testes antes de escalar o uso.
}
); }; const PATCH_LABELS = { description: 'Descrição', system_prompt: 'Prompt do agente', negative_rules: 'Regras negativas', conversation_goals: 'Objetivos da conversa', final_goal: 'Objetivo final', handoff_config: 'Repasse humano', }; function patchPreview(value) { if (Array.isArray(value)) return value.map(item => typeof item === 'string' ? item : prettyPageValue(item)).join('\n'); if (value && typeof value === 'object') return prettyPageValue(value); return String(value || ''); } function currentPatchPreview(value) { if (value == null || value === '') return '(vazio)'; if (typeof value === 'string') return value; return prettyPageValue(value); } function assistantFriendlyError(err) { const raw = String(err?.message || err || ''); if (/list.*mapping|object is not a mapping/i.test(raw)) return 'O Otimizador recebeu uma resposta em formato inesperado. Gere a prévia novamente; o sistema agora normaliza esse retorno antes de aplicar.'; if (/internal server error/i.test(raw)) return 'O assistente encontrou uma falha interna. Corrija o diagnóstico do agente e tente novamente.'; if (/timeout|timed out/i.test(raw)) return 'O modelo demorou para responder. Tente novamente em alguns instantes.'; if (/api|key|chave|unauthorized|401/i.test(raw)) return 'A chave do modelo não respondeu corretamente. Verifique as credenciais dos assistentes.'; return raw || 'Não foi possível concluir a ação agora.'; } function testerMessageText(message) { if (message == null || message === '') return ''; if (typeof message === 'string') return message; if (typeof message.content === 'string') return message.content; if (typeof message.message === 'string') return message.message; if (typeof message.text === 'string') return message.text; return prettyPageValue(message); } function testerScenarioMessages(item) { const scenario = item?.scenario || {}; const input = item?.input || {}; const candidates = [ scenario.messages, item?.messages, input.messages, scenario.message, scenario.input, input.message, item?.message, item?.prompt, ]; const raw = candidates.find(value => Array.isArray(value) ? value.length : value); const list = Array.isArray(raw) ? raw : [raw]; return list.map(testerMessageText).filter(Boolean); } function asList(value) { if (Array.isArray(value)) return value.filter(item => item !== null && item !== undefined && item !== ''); if (value === null || value === undefined || value === '') return []; if (typeof value === 'object') { const itemKeys = ['problem', 'finding', 'summary', 'recommendation', 'reason', 'name', 'passed', 'scenario', 'status', 'evidence', 'message', 'value']; if (itemKeys.some(key => Object.prototype.hasOwnProperty.call(value, key))) return [value]; return Object.entries(value) .filter(([, item]) => item !== null && item !== undefined && item !== '') .map(([key, item]) => { if (item && typeof item === 'object' && !Array.isArray(item)) return { name: key, ...item }; return { name: key, value: item }; }); } return [value]; } function reportItemText(item) { if (item === null || item === undefined || item === '') return ''; if (typeof item === 'boolean') return item ? 'Confirmado.' : ''; if (typeof item === 'string' || typeof item === 'number') return String(item); if (typeof item !== 'object') return String(item); if (Object.prototype.hasOwnProperty.call(item, 'value') && item.name) return `${item.name}: ${testerMessageText(item.value)}`; const label = item.problem || item.finding || item.summary || item.recommendation || item.reason || item.name || item.status || item.evidence || item.message; const context = item.scenario || item.category || item.severity; if (context && label && context !== label) return `${context}: ${label}`; if (label) return String(label); const fields = ['what', 'impact', 'expected_behavior', 'tester_message', 'agent_reply_summary', 'rating'] .map(key => item[key] ? `${key.replace(/_/g, ' ')}: ${testerMessageText(item[key])}` : '') .filter(Boolean); return fields.join('\n'); } function issueText(item) { if (!item || typeof item !== 'object') return reportItemText(item); const severity = item.severity || item.priority || 'médio'; const problem = item.problem || item.finding || item.message || item.evidence || 'problema identificado'; const scenario = item.scenario ? `${item.scenario}: ` : ''; return `${severity} - ${scenario}${problem}`; } function scenarioEvaluationText(item) { if (!item || typeof item !== 'object') return reportItemText(item); const lines = [ `${item.scenario || item.name || 'Cenário'}: ${item.finding || item.problem || item.status || item.evidence || 'avaliado'}`, ]; if (item.tester_message) lines.push(`Pergunta: ${item.tester_message}`); if (item.agent_reply_summary) lines.push(`Resposta: ${item.agent_reply_summary}`); if (item.rating) lines.push(`Avaliação: ${item.rating}`); if (item.expected_behavior) lines.push(`Esperado: ${item.expected_behavior}`); return lines.join('\n'); } function scoreValue(value) { const parsed = Number.parseFloat(String(value ?? 0).replace('%', '')); if (!Number.isFinite(parsed)) return 0; return Math.max(0, Math.min(100, Math.round(parsed))); } function criterionInfo(item) { if (!item || typeof item !== 'object') return { label: reportItemText(item), passed: null }; const explicitPassed = Object.prototype.hasOwnProperty.call(item, 'passed') ? item.passed : item.value; return { label: item.name || item.label || item.criterion || reportItemText(item), passed: explicitPassed === false || item.status === 'failed' || item.ok === false ? false : true, }; } function cleanReportText(value) { const text = String(value || '').trim(); if (!text) return ''; if (/^(true|false|null|undefined|\[object object\])$/i.test(text)) return ''; return text; } function reportItems(items, emptyFallback) { const list = asList(items).map(reportItemText).map(cleanReportText).filter(Boolean); return list.length ? list : emptyFallback ? [emptyFallback] : []; } function preserveReportItems(items) { return asList(items) .map(item => typeof item === 'boolean' ? '' : reportItemText(item)) .map(cleanReportText) .filter(Boolean); } function recommendationPriority(text, tone) { const value = String(text || '').toLowerCase(); if (tone === 'positive' || tone === 'preserve') return ''; if (/(timeout|sil[eê]ncio|falha|cr[ií]tico|risco|n[aã]o respondeu|ausente|kb|base de conhecimento|invent)/.test(value)) return 'Alta'; if (/(implementar|adicionar|corrigir|validar|reduzir|refinar|ajustar|treinar)/.test(value)) return 'Média'; return tone === 'next' ? 'Baixa' : ''; } function reportPriorityClass(priority) { if (priority === 'Alta' || priority === 'Crítico') return 'high'; if (priority === 'Média') return 'medium'; if (priority === 'Baixa') return 'low'; return 'neutral'; } function reportToneIcon(tone) { if (tone === 'positive' || tone === 'preserve') return 'check'; if (tone === 'risk') return 'x'; if (tone === 'next') return 'arrow'; if (tone === 'knowledge') return 'kb'; if (tone === 'recommendation') return 'tools'; return 'logs'; } function reportItemParts(text, tone) { let clean = String(text || '').replace(/\r\n/g, '\n').trim(); let priority = ''; const prefix = clean.match(/^(cr[ií]tico|alta|m[eé]dia|media|m[eé]dio|medio|baixa)\s*[-:]\s*/i); if (prefix) { priority = /cr/i.test(prefix[1]) ? 'Crítico' : /alt/i.test(prefix[1]) ? 'Alta' : /m[eé]dia|media|m[eé]dio|medio/i.test(prefix[1]) ? 'Média' : 'Baixa'; clean = clean.slice(prefix[0].length).trim(); } const suffix = clean.match(/\s+[-–—:]?\s*(cr[ií]tico|alta|m[eé]dia|media|m[eé]dio|medio|baixa)$/i); if (suffix && clean.length > suffix[0].length + 12) { priority = priority || (/cr/i.test(suffix[1]) ? 'Crítico' : /alt/i.test(suffix[1]) ? 'Alta' : /m[eé]dia|media|m[eé]dio|medio/i.test(suffix[1]) ? 'Média' : 'Baixa'); clean = clean.slice(0, clean.length - suffix[0].length).trim(); } priority = priority || recommendationPriority(clean, tone); const lines = clean.split('\n').map(line => line.trim()).filter(Boolean); let title = lines.shift() || clean || 'Item'; const body = []; const colon = title.indexOf(':'); if (colon > 0 && colon < 42) { const before = title.slice(0, colon).trim(); const after = title.slice(colon + 1).trim(); title = before; if (after) body.push(after); } body.push(...lines); return { title, body, priority }; } function scoreBand(score) { if (score >= 85) return { label: 'Excelente', tone: 'positive', readiness: 'Pronto para uso assistido' }; if (score >= 70) return { label: 'Bom', tone: 'positive', readiness: 'Pronto com pequenos ajustes' }; if (score >= 50) return { label: 'Precisa de ajustes', tone: 'warn', readiness: 'Revisão recomendada' }; return { label: 'Crítico', tone: 'risk', readiness: 'Não publicar sem correção' }; } function testerHistoryStatusClass(status) { const value = String(status || '').toLowerCase(); if (['completed', 'done', 'ok'].includes(value)) return 'ok'; if (['started', 'running', 'pending'].includes(value)) return 'warn'; if (['failed', 'error'].includes(value)) return 'bad'; return ''; } function testerHistoryDate(value) { return window.fmtDate ? window.fmtDate(value) : (value || '--'); } function testerHistoryScore(item) { const score = scoreValue(item?.score); return item?.score == null ? '--' : `${score}%`; } function testerHistoryMessages(detail) { const direct = Array.isArray(detail?.messages) ? detail.messages : []; if (direct.length) return direct; return asList(detail?.output?.transcripts).flatMap((item, idx) => { const scenario = item?.scenario || {}; const label = scenario.name || `Cenário ${idx + 1}`; return [ scenario.message && { role: 'user', content: scenario.message, scenario: label, latency_ms: item.latency_ms }, item.reply && { role: 'assistant', content: item.reply, scenario: label, latency_ms: item.latency_ms }, item.error && { role: 'system', content: item.error, scenario: label, latency_ms: item.latency_ms }, ].filter(Boolean); }); } window.SystemAssistantsPage = function SystemAssistantsPage({ onOpenAgent, setPage }) { const data = studioData(); const agents = data.agents || []; const [tab, setTab] = useExtraState('tester'); const [agentId, setAgentId] = useExtraState(() => assistantInitialAgentId(agents)); const [testerMode, setTesterMode] = useExtraState('quick'); const [testerObjective, setTesterObjective] = useExtraState(''); const [testerLoading, setTesterLoading] = useExtraState(false); const [testerReport, setTesterReport] = useExtraState(null); const [optimizerLoading, setOptimizerLoading] = useExtraState(false); const [optimizerPreview, setOptimizerPreview] = useExtraState(null); const [selectedSections, setSelectedSections] = useExtraState({}); const [agentVersions, setAgentVersions] = useExtraState([]); const [assistantError, setAssistantError] = useExtraState(''); const [testerHistory, setTesterHistory] = useExtraState({ items: [], total: 0, limit: 6, offset: 0 }); const [testerHistoryLimit, setTesterHistoryLimit] = useExtraState(6); const [testerHistoryLoading, setTesterHistoryLoading] = useExtraState(false); const [testerHistoryOpen, setTesterHistoryOpen] = useExtraState(true); const [testerHistoryDetail, setTesterHistoryDetail] = useExtraState(null); const [testerHistoryDetailLoading, setTesterHistoryDetailLoading] = useExtraState(false); const selectedAgent = agents.find(agent => String(agent.id) === String(agentId)); const testerFlowRunning = useAssistantFlow(testerLoading, TESTER_FLOW_STEPS); const optimizerFlowRunning = useAssistantFlow(optimizerLoading, OPTIMIZER_FLOW_STEPS); const testerFlowSteps = flowState(TESTER_FLOW_STEPS, testerLoading, !!testerReport, testerFlowRunning); const optimizerFlowSteps = flowState(OPTIMIZER_FLOW_STEPS, optimizerLoading, !!optimizerPreview, optimizerFlowRunning); useExtraEffect(() => { if (!agentId && agents[0]?.id) setAgentId(assistantInitialAgentId(agents)); }, [agents.length]); useExtraEffect(() => { if (agentId) window.localStorage?.setItem('sm_assistant_focus_agent_id', String(agentId)); }, [agentId]); useExtraEffect(() => { if (!agentId) { setAgentVersions([]); return undefined; } let alive = true; API.getAgentVersions(Number(agentId)) .then(rows => { if (alive) setAgentVersions(rows || []); }) .catch(() => { if (alive) setAgentVersions([]); }); return () => { alive = false; }; }, [agentId]); async function loadTesterHistory(limit = testerHistoryLimit) { if (!agentId) { setTesterHistory({ items: [], total: 0, limit, offset: 0 }); return { items: [], total: 0, limit, offset: 0 }; } setTesterHistoryLoading(true); try { const data = await API.getSystemTesterHistory({ agent_id: Number(agentId), limit, offset: 0 }); setTesterHistory(data || { items: [], total: 0, limit, offset: 0 }); return data; } catch (err) { setTesterHistory({ items: [], total: 0, limit, offset: 0 }); return null; } finally { setTesterHistoryLoading(false); } } useExtraEffect(() => { let alive = true; if (!agentId || tab !== 'tester') return undefined; setTesterHistoryLoading(true); API.getSystemTesterHistory({ agent_id: Number(agentId), limit: testerHistoryLimit, offset: 0 }) .then(data => { if (alive) setTesterHistory(data || { items: [], total: 0, limit: testerHistoryLimit, offset: 0 }); }) .catch(() => { if (alive) setTesterHistory({ items: [], total: 0, limit: testerHistoryLimit, offset: 0 }); }) .finally(() => { if (alive) setTesterHistoryLoading(false); }); return () => { alive = false; }; }, [agentId, tab, testerHistoryLimit]); async function refreshVersions() { if (!agentId) return []; const rows = await API.getAgentVersions(Number(agentId)); setAgentVersions(rows || []); return rows || []; } async function openTesterHistoryDetail(item) { if (!item?.id) return; setTesterHistoryDetailLoading(true); try { const detail = await API.getSystemTesterHistoryDetail(item.id); setTesterHistoryDetail(detail); } catch (err) { studioToast(err.message || String(err), 'bad'); } finally { setTesterHistoryDetailLoading(false); } } function loadMoreTesterHistory() { setTesterHistoryLimit(value => Math.min(value + 6, 48)); } async function runTester() { if (!agentId) return studioToast('Selecione um agente para testar.', 'bad'); setTesterLoading(true); setTesterReport(null); setAssistantError(''); try { const res = await API.runSystemTester({ agent_id: Number(agentId), mode: testerMode, objective: testerObjective }); setTesterReport(res); setTesterHistoryLimit(6); loadTesterHistory(6).catch(() => {}); studioToast('Teste interno concluído.'); } catch (err) { const friendly = assistantFriendlyError(err); setAssistantError(friendly); studioToast(friendly, 'bad'); } finally { setTesterLoading(false); } } async function previewOptimizer(report = testerReport) { if (!agentId) return studioToast('Selecione um agente para otimizar.', 'bad'); setOptimizerLoading(true); setOptimizerPreview(null); setAssistantError(''); try { const res = await API.previewSystemOptimizer({ agent_id: Number(agentId), tester_report: report || null }); const sections = {}; Object.keys(res.patch || {}).forEach(key => { sections[key] = true; }); setSelectedSections(sections); setOptimizerPreview(res); setAgentVersions(res.versions || agentVersions); setTab('optimizer'); studioToast('Prévia de melhoria gerada.'); } catch (err) { const friendly = assistantFriendlyError(err); setAssistantError(friendly); studioToast(friendly, 'bad'); } finally { setOptimizerLoading(false); } } async function applyOptimizer() { if (!optimizerPreview?.patch) return; const sections = Object.keys(selectedSections).filter(key => selectedSections[key]); if (!sections.length) return studioToast('Selecione ao menos uma seção para aplicar.', 'bad'); const ok = await studioConfirm({ title: 'Aplicar melhorias no agente?', message: 'As seções selecionadas serão salvas no agente. Se ele estiver publicado, revise o comportamento antes de novos atendimentos reais.', confirmLabel: 'Aplicar melhorias', danger: false, }); if (!ok) return; try { const res = await API.applySystemOptimizer({ agent_id: Number(agentId), patch: optimizerPreview.patch, sections }); await refreshStudio(); setAgentVersions(res.versions || await refreshVersions()); studioToast(`Melhorias aplicadas: ${(res.applied || []).length}`); if (onOpenAgent) onOpenAgent(Number(agentId), 'instructions'); } catch (err) { const friendly = assistantFriendlyError(err); setAssistantError(friendly); studioToast(friendly, 'bad'); } } async function restoreVersion(version) { if (!version?.id || !agentId) return; const ok = await studioConfirm({ title: 'Restaurar versão do agente?', message: `Restaurar a versão "${version.label || version.id}"? O estado atual será salvo antes da restauração.`, confirmLabel: 'Restaurar', danger: false, }); if (!ok) return; try { const res = await API.restoreAgentVersion(Number(agentId), version.id); await refreshStudio(); await refreshVersions(); studioToast(`Versão restaurada: ${(res.restored || []).length} seção(ões).`); if (onOpenAgent) onOpenAgent(Number(agentId), 'instructions'); } catch (err) { const friendly = assistantFriendlyError(err); setAssistantError(friendly); studioToast(friendly, 'bad'); } } const patchEntries = Object.entries(optimizerPreview?.patch || {}); const testerScore = scoreValue(testerReport?.score); const assistantCards = [ { id: 'tester', label: 'Testador de Agente', status: testerLoading ? 'testando' : testerReport ? `${testerScore}% no último teste` : 'pronto', detail: 'Executa cenários automáticos contra o agente selecionado.' }, { id: 'optimizer', label: 'Otimizador de Agente', status: optimizerLoading ? 'gerando' : optimizerPreview ? 'prévia gerada' : 'aguardando', detail: 'Propõe melhorias revisáveis antes de salvar.' }, ]; const testerBand = scoreBand(testerScore); const testerReportSections = testerReport ? [ { key: 'strengths', title: 'Forças', icon: 'check', tone: 'positive', items: testerReport.strengths, empty: 'Nenhuma força específica registrada.' }, { key: 'issues', title: 'Problemas', icon: 'x', tone: 'risk', items: asList(testerReport.issues).map(issueText), empty: 'Nenhum problema crítico registrado.' }, { key: 'improvements', title: 'Melhorias', icon: 'arrow', tone: 'improvement', items: testerReport.improvements, empty: 'Nenhuma melhoria objetiva registrada.' }, { key: 'next_tests', title: 'Próximos testes', icon: 'refresh', tone: 'next', items: testerReport.next_tests, empty: 'Nenhum próximo teste sugerido.' }, { key: 'kb', title: 'Uso da KB', icon: 'kb', tone: 'knowledge', items: testerReport.kb_findings, empty: 'A base de conhecimento não trouxe achados específicos neste teste.' }, { key: 'tone', title: 'Tom e escopo', icon: 'assistants', tone: 'neutral', items: testerReport.tone_findings, empty: 'Nenhuma observação específica sobre tom e escopo.' }, { key: 'scenarios', title: 'Avaliação por cenário', icon: 'logs', tone: 'scenario', items: asList(testerReport.scenario_evaluations).map(scenarioEvaluationText), empty: 'Nenhuma avaliação por cenário registrada.' }, { key: 'preserve', title: 'O que preservar', icon: 'check', tone: 'preserve', items: preserveReportItems(testerReport.preserve), empty: 'Nenhum ponto específico informado para preservação.' }, { key: 'optimizer', title: 'Recomendações para o Otimizador', icon: 'tools', tone: 'recommendation', items: testerReport.optimizer_recommendations, empty: 'Nenhuma recomendação específica para o Otimizador.' }, ] : []; return ( <> } />
{assistantError &&
{assistantError}Se persistir, rode o teste automático ou confira as credenciais do modelo.
}
Operação assistida

Dois assistentes internos, um fluxo de melhoria.

Use o Testador para auditar um agente real e o Otimizador para aplicar somente ajustes revisados. O Guia do Sistema está disponível na barra superior.

{assistantCards.map(card => ( ))}
{tab === 'tester' && (
Testador de Agente
Roda cenários internos, avalia respostas, ferramentas, KBs e pontos de melhoria.