/* global React */ const { useState: useStudioState, useEffect: useStudioEffect, useCallback: useStudioCallback } = React; const API = { async apiFetch(url, opts = {}) { const res = await fetch(url, opts); const text = await res.text(); let data = null; if (text) { try { data = JSON.parse(text); } catch (e) { data = { detail: text }; } } if (!res.ok) { const detail = data && data.detail; const message = detail && typeof detail === 'object' ? detail.message : detail; const err = new Error(message || (data && data.message) || text || res.statusText); err.detail = (data && data.detail) || text; err.status = res.status; throw err; } return data; }, request(url, opts = {}) { return this.apiFetch(url, opts); }, get(url) { return this.apiFetch(url); }, post(url, body) { return this.apiFetch(url, jsonOpts('POST', body)); }, put(url, body) { return this.apiFetch(url, jsonOpts('PUT', body)); }, del(url) { return this.apiFetch(url, { method: 'DELETE' }); }, getAgents() { return this.get('/api/agents'); }, getEcosystemStatus() { return this.get('/api/ecosystem-status'); }, systemGuideChat(data) { return this.post('/api/system-assistants/guide/chat', data); }, runSystemTester(data) { return this.post('/api/system-assistants/tester/run', data); }, getSystemTesterHistory(params = {}) { const query = new URLSearchParams(); if (params.agent_id) query.set('agent_id', params.agent_id); if (params.limit) query.set('limit', params.limit); if (params.offset) query.set('offset', params.offset); const suffix = query.toString() ? `?${query.toString()}` : ''; return this.get(`/api/system-assistants/tester/history${suffix}`); }, getSystemTesterHistoryDetail(runId) { return this.get(`/api/system-assistants/tester/history/${runId}`); }, previewSystemOptimizer(data) { return this.post('/api/system-assistants/optimizer/preview', data); }, applySystemOptimizer(data) { return this.post('/api/system-assistants/optimizer/apply', data); }, getAgentVersions(id) { return this.get(`/api/agents/${id}/versions`); }, restoreAgentVersion(id, versionId) { return this.post(`/api/agents/${id}/versions/${versionId}/restore`, {}); }, createAgent(data) { return this.post('/api/agents', data); }, updateAgent(id, data) { return this.put(`/api/agents/${id}`, data); }, deleteAgent(id) { return this.del(`/api/agents/${id}`); }, connectAgent(id) { return this.post(`/api/agents/${id}/connect`, {}); }, disconnectAgent(id) { return this.post(`/api/agents/${id}/disconnect`, {}); }, getQr(id) { return this.connectAgent(id); }, getAgentReadiness(id) { return this.get(`/api/agents/${id}/readiness`); }, getAgentPublicTestLink(id) { return this.get(`/api/agents/${id}/public-test-link`); }, publishAgent(id) { return this.post(`/api/agents/${id}/publish`, {}); }, unpublishAgent(id) { return this.post(`/api/agents/${id}/unpublish`, {}); }, testAgentMessage(id, data) { return this.post(`/api/agents/${id}/test-message`, data); }, getAgentKbHealth(id) { return this.get(`/api/agents/${id}/kb-health`); }, async testAgentChat(id, data) { try { return await this.post(`/api/agents/${id}/test-chat`, data); } catch (err) { if (err.status === 404) { return await this.post(`/api/site-agents/${id}/chat`, { ...data, metadata: { ...(data?.metadata || {}), source: 'agent_test_fallback' }, }); } throw err; } }, getAgentTestSessions(agentId) { return this.get(`/api/agents/${agentId}/test-sessions`); }, createAgentTestSession(agentId, data) { return this.post(`/api/agents/${agentId}/test-sessions`, data); }, getAgentTestSession(agentId, sessionId) { return this.get(`/api/agents/${agentId}/test-sessions/${encodeURIComponent(sessionId)}`); }, deleteAgentTestSessions(agentId) { return this.del(`/api/agents/${agentId}/test-sessions`); }, deleteAgentTestSession(agentId, sessionId) { return this.del(`/api/agents/${agentId}/test-sessions/${encodeURIComponent(sessionId)}`); }, getWhatsappGroups(agentId) { return this.get(`/api/agents/${agentId}/whatsapp/groups`); }, updateWhatsappGroups(agentId, data) { return this.put(`/api/agents/${agentId}/whatsapp/groups`, data); }, getWhatsappTargets(agentId, query = '') { const suffix = query ? `?q=${encodeURIComponent(query)}` : ''; return this.get(`/api/agents/${agentId}/whatsapp/targets${suffix}`); }, sendFirstMessage(agentId, data) { return this.post(`/api/agents/${agentId}/first-message`, data); }, getChannels() { return this.get('/api/channels'); }, createChannel(data) { return this.post('/api/channels', data); }, updateChannel(id, data) { return this.put(`/api/channels/${id}`, data); }, deleteChannel(id) { return this.del(`/api/channels/${id}`); }, connectChannel(id) { return this.post(`/api/channels/${id}/connect`, {}); }, disconnectChannel(id) { return this.post(`/api/channels/${id}/disconnect`, {}); }, getKbSources(agentId) { return this.get(`/api/agents/${agentId}/kb-sources`); }, getKbSource(agentId, sourceId) { return this.get(`/api/agents/${agentId}/kb-sources/${sourceId}`); }, addKbSource(agentId, data) { return this.post(`/api/agents/${agentId}/kb-sources`, data); }, reindex(agentId) { return this.post(`/api/agents/${agentId}/kb-reindex`, {}); }, getKnowledgeBases(agentId) { return this.get('/api/knowledge-bases' + (agentId ? `?agent_id=${agentId}` : '')); }, createKnowledgeBase(data) { return this.post('/api/knowledge-bases', data); }, updateKnowledgeBase(id, data) { return this.put(`/api/knowledge-bases/${id}`, data); }, deleteKnowledgeBase(id) { return this.del(`/api/knowledge-bases/${id}`); }, getKnowledgeBaseSources(id) { return this.get(`/api/knowledge-bases/${id}/sources`); }, testKnowledgeBase(id, data) { return this.post(`/api/knowledge-bases/${id}/test-query`, data); }, getTools(agentId) { return this.get('/api/tools' + (agentId ? `?agent_id=${agentId}` : '')); }, createTool(data) { return this.post('/api/tools', data); }, updateTool(id, data) { return this.put(`/api/tools/${id}`, data); }, deleteTool(id) { return this.del(`/api/tools/${id}`); }, getConversations(agentId) { return this.get(agentId ? `/api/agents/${agentId}/conversations` : '/api/conversations'); }, getConversationDetail(convId) { return this.get(`/api/conversations/${convId}`); }, deleteConversation(id) { return this.del(`/api/conversations/${id}`); }, deleteAgentConversations(agentId) { return this.del(`/api/agents/${agentId}/conversations`); }, pauseAgent(convId) { return this.post(`/api/conversations/${convId}/pause-agent`, {}); }, resumeAgent(convId) { return this.post(`/api/conversations/${convId}/resume-agent`, {}); }, sendHumanMessage(convId, text) { return this.post(`/api/conversations/${convId}/human-message`, { message: text }); }, getTables() { return this.get('/api/data-tables'); }, createTable(data) { return this.post('/api/data-tables', data); }, updateTable(id, data) { return this.put(`/api/data-tables/${id}`, data); }, deleteTable(id) { return this.del(`/api/data-tables/${id}`); }, getTableDetail(id) { return this.get(`/api/data-tables/${id}`); }, addColumn(tableId, data) { return this.post(`/api/data-tables/${tableId}/columns`, data); }, addRecord(tableId, data) { return this.post(`/api/data-tables/${tableId}/records`, data); }, updateRecord(id, data) { return this.put(`/api/table-records/${id}`, data); }, deleteRecord(id) { return this.del(`/api/table-records/${id}`); }, getSquads() { return this.get('/api/squads'); }, createSquad(data) { return this.post('/api/squads', data); }, updateSquad(id, data) { return this.put(`/api/squads/${id}`, data); }, deleteSquad(id) { return this.del(`/api/squads/${id}`); }, getSquadMembers(squadId) { return this.get(`/api/squads/${squadId}`); }, addMember(squadId, data) { return this.post(`/api/squads/${squadId}/members`, data); }, updateMember(id, data) { return this.put(`/api/squad-members/${id}`, data); }, deleteMember(id) { return this.del(`/api/squad-members/${id}`); }, squadBuilderPreview(squadId, data) { return this.post(`/api/squads/${squadId}/agent-builder/preview`, data); }, getTickets() { return this.get('/api/tickets'); }, updateTicket(id, data) { return this.put(`/api/tickets/${id}`, data); }, deleteTicket(id) { return this.del(`/api/tickets/${id}`); }, getAttendants() { return this.get('/api/crm/attendants'); }, createAttendant(data) { return this.post('/api/crm/attendants', data); }, updateAttendant(id, data) { return this.put(`/api/crm/attendants/${id}`, data); }, deleteAttendant(id) { return this.del(`/api/crm/attendants/${id}`); }, getTags() { return this.get('/api/crm/tags'); }, createTag(data) { return this.post('/api/crm/tags', data); }, updateTag(id, data) { return this.put(`/api/crm/tags/${id}`, data); }, deleteTag(id) { return this.del(`/api/crm/tags/${id}`); }, getTeams() { return this.get('/api/crm/teams'); }, createTeam(data) { return this.post('/api/crm/teams', data); }, updateTeam(id, data) { return this.put(`/api/crm/teams/${id}`, data); }, getAgentRuns(agentId) { return this.get(`/api/agents/${agentId}/runs`); }, getDiagnostics() { return this.get('/api/diagnostics'); }, agentBuilderPreview(data) { return this.post('/api/agent-builder/preview', data); }, getConditionalPrompts(agentId) { return this.get(`/api/agents/${agentId}/conditional-prompts`); }, createConditional(agentId, data) { return this.post(`/api/agents/${agentId}/conditional-prompts`, data); }, updateConditional(id, data) { return this.put(`/api/conditional-prompts/${id}`, data); }, deleteConditional(id) { return this.del(`/api/conditional-prompts/${id}`); }, }; function jsonOpts(method, body) { return { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) }; } function asArray(value) { if (Array.isArray(value)) return value; if (value && Array.isArray(value.value)) return value.value; return []; } function parseDate(value) { if (!value) return null; const text = String(value); const normalized = text.includes('T') && !/[zZ]|[+-]\d{2}:?\d{2}$/.test(text) ? text + 'Z' : text; const date = new Date(normalized); return Number.isNaN(date.getTime()) ? null : date; } function fmtTime(value) { const date = parseDate(value); return date ? date.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }) : '--:--'; } function fmtShortDate(value) { const date = parseDate(value); return date ? date.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' }) : '--'; } function latestPreview(preview) { return String(preview || '').split('\n').filter(Boolean).pop()?.replace(/^(user|assistant|human|system):\s*/i, '') || ''; } function conversationChannelLabelValue(channel) { if (channel === 'site') return 'Site'; if (channel === 'public') return 'Chat Compartilhado'; if (channel === 'agent_test') return 'Teste interno'; return 'WhatsApp'; } function statusLabel(status) { const map = { unanswered: 'Aberto', in_progress: 'Em andamento', finished: 'Resolvido', closed: 'Fechado', deleted: 'Excluido' }; return map[status] || status || 'Sem status'; } function friendlyWhatsAppStatus(value) { const raw = String(value || '').trim(); const key = raw.toLowerCase(); const map = { open: 'Conectado', close: 'Desconectado', closed: 'Desconectado', offline: 'Desconectado', connecting: 'Reconectando...', not_found: 'Sem conexão', missing: 'Sem conexão', }; return map[key] || raw || 'Sem status'; } function whatsappInstanceUiState(inst, fallbackName = '') { if (!inst) { return { status: 'not_found', tag: 'warn', label: 'Sem conexão', name: fallbackName || '--', rawStatus: 'not_found' }; } const raw = inst.raw || inst; const rawStatus = raw.connectionStatus || inst.connectionStatus || inst.status || ''; const name = raw.name || inst.name || inst.id || fallbackName || '--'; if (raw.needsLogoutReset || inst.needsLogoutReset) { return { status: 'expired', tag: 'bad', label: 'Sessão expirada', name, rawStatus }; } if (rawStatus === 'open' || rawStatus === 'online') { return { status: 'online', tag: 'ok', label: 'Conectado', name, rawStatus }; } if (rawStatus === 'connecting') { return { status: 'connecting', tag: 'warn', label: 'Reconectando...', name, rawStatus }; } if (rawStatus === 'not_found') { return { status: 'not_found', tag: 'warn', label: 'Sem conexão', name, rawStatus }; } return { status: 'offline', tag: 'bad', label: friendlyWhatsAppStatus(rawStatus || 'offline'), name, rawStatus }; } function setupStatusLabel(status) { const map = { draft: 'Rascunho', configuring: 'Configurando', testing: 'Em teste', ready: 'Pronto para publicar', production: 'Em produção', paused: 'Pausado', }; return map[status] || status || 'Rascunho'; } function instanceStatusFor(instances, token) { const inst = instances.find(item => item.name === token); const ui = whatsappInstanceUiState(inst, token); return { status: ui.status, statusLabel: ui.label, statusTag: ui.tag, channel: ui.name }; } function transformStudio(raw) { const agentsRaw = asArray(raw.agents); const diag = raw.diagnostics || {}; const instancesRaw = asArray(diag.instances); const conversationsRaw = asArray(diag.conversations); const runsRaw = asArray(diag.runs); const ticketsRaw = asArray(raw.tickets); const toolsRaw = asArray(raw.tools); const knowledgeRaw = asArray(raw.knowledge); const agents = agentsRaw.map(agent => { const inst = instanceStatusFor(instancesRaw, agent.instance_token); const agentRuns = runsRaw.filter(run => run.agent_id === agent.id); const latencyItems = agentRuns.map(run => Number(run.latency_ms || 0)).filter(Boolean); return { id: agent.id, name: agent.name || 'Agente sem nome', publicSlug: agent.public_slug || '', role: agent.description || 'Atendimento multicanal', model: agent.ai_model || 'gpt-4o-mini', conversations: conversationsRaw.filter(c => c.agent_id === agent.id).length, tickets: ticketsRaw.filter(t => t.agent_id === agent.id).length, runs: agentRuns.length, status: inst.status, statusLabel: inst.statusLabel, statusTag: inst.statusTag, setupStatus: agent.setup_status || (agent.is_published ? 'production' : 'draft'), setupLabel: setupStatusLabel(agent.setup_status || (agent.is_published ? 'production' : 'draft')), isPublished: !!agent.is_published, setupProgress: agent.setup_progress || {}, sharedChatConfig: agent.shared_chat_config || null, kbSources: knowledgeRaw.filter(k => k.agent_id === agent.id).length, tools: toolsRaw.filter(t => t.agent_id === agent.id).length, latency: latencyItems.length ? Math.round(latencyItems.reduce((a, b) => a + b, 0) / latencyItems.length) : 0, channel: inst.channel, raw: agent, }; }); const instances = instancesRaw.map(item => { const ui = whatsappInstanceUiState(item, item.name); return { id: item.name, name: item.name || 'Instância', number: ui.label, status: ui.status, statusLabel: ui.label, statusTag: ui.tag, uptime: item.registered ? 'registrada' : 'não registrada', queue: Number(item.messages || 0), raw: item, }; }); const conversations = conversationsRaw.map(c => { const ticket = c.ticket ? `T-${c.ticket.id}` : null; const handoff = (c.state && c.state.human_handoff) || {}; const paused = Boolean(ticket) || Boolean(handoff.paused || handoff.requested || handoff.ticket_status === 'in_progress'); const source = c.channel || c.source || c.state?.source || ''; const phone = String(c.customer_phone || ''); const channel = ['public_test', 'public', 'shared_chat', 'shared_chat_demo', 'shared_chat_contact'].includes(source) || phone.startsWith('pub-') ? 'public' : source || (phone.startsWith('site-') ? 'site' : 'whatsapp'); return { id: c.id, name: c.customer_name || c.customer_phone || 'Contato', number: c.customer_phone || '--', agent: c.agent_name || `Agente #${c.agent_id || '-'}`, agentId: c.agent_id, channel, channelLabel: conversationChannelLabelValue(channel), last: latestPreview(c.preview), time: fmtTime(c.updated_at || c.created_at), unread: 0, paused, ticket, raw: c, }; }); const tickets = ticketsRaw.map(t => ({ source: t.metadata?.source || '', requestType: t.metadata?.request_type || '', origin: t.metadata?.origin || '', id: `T-${t.id}`, realId: t.id, client: t.customer_name || t.customer_phone || 'Cliente', team: t.team_name || '--', attendant: t.assigned_attendant_name || '--', status: statusLabel(t.status), rawStatus: t.status, tags: asArray(t.tags).map(tag => tag.name || tag), summary: t.summary || 'Sem resumo.', updated: fmtShortDate(t.updated_at || t.created_at), priority: t.status === 'unanswered' ? 'alta' : 'media', raw: t, })); const handoffQueue = tickets .filter(t => ['Aberto', 'Em andamento'].includes(t.status)) .slice(0, 8) .map(t => ({ id: t.id, name: t.client, number: t.raw.customer_phone || '', reason: t.summary.slice(0, 90), wait: t.updated, agent: t.raw.agent_name || '--' })); return { agents, instances, handoffQueue, tickets, conversations, thread: [], channels: asArray(raw.channels), knowledge: knowledgeRaw, tables: asArray(raw.tables), squads: asArray(raw.squads), tools: toolsRaw, teams: asArray(raw.teams), attendants: asArray(raw.attendants), tags: asArray(raw.tags), runs: runsRaw, events: asArray(diag.events), diagnostics: diag, }; } function useStudioData() { const [state, setState] = useStudioState({ data: window.MOCK, loading: true, error: null }); const refresh = useStudioCallback(async () => { try { const [agents, diagnostics, channels, knowledge, tables, squads, tools, tickets, teams, attendants, tags] = await Promise.all([ API.get('/api/agents'), API.get('/api/diagnostics'), API.get('/api/channels'), API.get('/api/knowledge-bases'), API.get('/api/data-tables'), API.get('/api/squads'), API.get('/api/tools'), API.get('/api/tickets'), API.get('/api/crm/teams'), API.get('/api/crm/attendants'), API.get('/api/crm/tags'), ]); const data = transformStudio({ agents, diagnostics, channels, knowledge, tables, squads, tools, tickets, teams, attendants, tags }); window.MOCK = data; setState({ data, loading: false, error: null }); return data; } catch (error) { setState(prev => ({ ...prev, loading: false, error: error.message || String(error) })); throw error; } }, []); useStudioEffect(() => { refresh().catch(() => {}); const timer = setInterval(() => refresh().catch(() => {}), 10000); return () => clearInterval(timer); }, [refresh]); return { ...state, refresh }; } window.API = API; window.useStudioData = useStudioData; window.fmtTime = fmtTime; window.fmtShortDate = fmtShortDate; window.statusLabel = statusLabel; window.friendlyWhatsAppStatus = friendlyWhatsAppStatus; window.whatsappInstanceUiState = whatsappInstanceUiState; window.setupStatusLabel = setupStatusLabel;