const App = { currentSection: 'dashboard', ws: null, wsReconnectAttempts: 0, wsReconnectTimer: null, _initialized: false, _lastAgentName: '', _executeDropzone: null, _pipelineDropzone: null, sectionTitles: { dashboard: 'Dashboard', agents: 'Agentes', tasks: 'Tarefas', schedules: 'Agendamentos', pipelines: 'Pipelines', webhooks: 'Webhooks', terminal: 'Terminal', history: 'Histórico', settings: 'Configurações', }, sections: ['dashboard', 'agents', 'tasks', 'schedules', 'pipelines', 'webhooks', 'terminal', 'history', 'settings'], init() { if (App._initialized) return; App._initialized = true; const savedTheme = localStorage.getItem('theme') || 'dark'; document.documentElement.setAttribute('data-theme', savedTheme); App.setupNavigation(); App.setupWebSocket(); App.setupEventListeners(); App.setupKeyboardShortcuts(); App._executeDropzone = Utils.initDropzone('execute-dropzone', 'execute-files', 'execute-file-list'); App._pipelineDropzone = Utils.initDropzone('pipeline-execute-dropzone', 'pipeline-execute-files', 'pipeline-execute-file-list'); const initialSection = location.hash.replace('#', '') || 'dashboard'; App.navigateTo(App.sections.includes(initialSection) ? initialSection : 'dashboard'); App.startPeriodicRefresh(); window.addEventListener('hashchange', () => { const section = location.hash.replace('#', '') || 'dashboard'; if (App.sections.includes(section)) App.navigateTo(section); }); const themeToggle = document.getElementById('theme-toggle'); if (themeToggle) { themeToggle.addEventListener('click', () => { const current = document.documentElement.getAttribute('data-theme') || 'dark'; const next = current === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', next); localStorage.setItem('theme', next); Utils.refreshIcons(); }); } if (typeof NotificationsUI !== 'undefined') NotificationsUI.init(); if ('Notification' in window && Notification.permission === 'default') { Notification.requestPermission(); } Utils.refreshIcons(); }, setupNavigation() { document.querySelectorAll('.sidebar-nav-link[data-section]').forEach((link) => { link.addEventListener('click', (e) => { e.preventDefault(); App.navigateTo(link.dataset.section); }); }); const refreshBtn = document.getElementById('refresh-activity-btn'); if (refreshBtn) { refreshBtn.addEventListener('click', () => DashboardUI.load()); } }, navigateTo(section) { if (location.hash !== `#${section}`) { history.pushState(null, '', `#${section}`); } document.querySelectorAll('.section').forEach((el) => { const isActive = el.id === section; el.classList.toggle('active', isActive); el.hidden = !isActive; }); document.querySelectorAll('.sidebar-nav-item').forEach((item) => { const link = item.querySelector('.sidebar-nav-link'); item.classList.toggle('active', link && link.dataset.section === section); }); const titleEl = document.getElementById('header-title'); if (titleEl) titleEl.textContent = App.sectionTitles[section] || section; App.currentSection = section; App._loadSection(section); }, async _loadSection(section) { try { switch (section) { case 'dashboard': await DashboardUI.load(); break; case 'agents': await AgentsUI.load(); break; case 'tasks': await TasksUI.load(); break; case 'schedules': await SchedulesUI.load(); break; case 'pipelines': await PipelinesUI.load(); break; case 'webhooks': await WebhooksUI.load(); break; case 'history': await HistoryUI.load(); break; case 'settings': await SettingsUI.load(); break; } } catch (err) { Toast.error(`Erro ao carregar seção: ${err.message}`); } }, setupWebSocket() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const clientId = API.clientId; const url = `${protocol}//${window.location.host}?clientId=${clientId}`; try { App.ws = new WebSocket(url); App.ws.onopen = () => { App.updateWsStatus('connected'); App.wsReconnectAttempts = 0; if (App.wsReconnectTimer) { clearTimeout(App.wsReconnectTimer); App.wsReconnectTimer = null; } }; App.ws.onclose = () => { App.updateWsStatus('disconnected'); App._scheduleWsReconnect(); }; App.ws.onerror = () => { App.updateWsStatus('error'); }; App.ws.onmessage = (event) => { try { const data = JSON.parse(event.data); App.handleWsMessage(data); } catch { // } }; } catch { App.updateWsStatus('error'); App._scheduleWsReconnect(); } }, _scheduleWsReconnect() { const delay = Math.min(1000 * Math.pow(2, App.wsReconnectAttempts), 30000); App.wsReconnectAttempts++; App.wsReconnectTimer = setTimeout(() => { App.setupWebSocket(); }, delay); }, handleWsMessage(data) { switch (data.type) { case 'connected': break; case 'execution_output': { Terminal.stopProcessing(); const evtType = data.data?.type || 'chunk'; const content = data.data?.content || ''; if (!content) break; if (evtType === 'tool') { Terminal.addLine(`▸ ${content}`, 'tool', data.executionId); } else if (evtType === 'turn') { Terminal.addLine(`── ${content} ──`, 'turn', data.executionId); } else if (evtType === 'system') { Terminal.addLine(content, 'system', data.executionId); } else if (evtType === 'stderr') { Terminal.addLine(content, 'stderr', data.executionId); } else { Terminal.addLine(content, 'default', data.executionId); } App._updateActiveBadge(); break; } case 'execution_complete': { Terminal.stopProcessing(); const result = data.data?.result || ''; if (result) { Terminal.addLine(result, 'success', data.executionId); } else { Terminal.addLine('Execução concluída (sem resultado textual).', 'info', data.executionId); } if (data.data?.stderr) { Terminal.addLine(data.data.stderr, 'error', data.executionId); } const costUsd = data.data?.costUsd || 0; const numTurns = data.data?.numTurns || 0; if (costUsd > 0) { Terminal.addLine(`Custo: $${costUsd.toFixed(4)} | Turnos: ${numTurns}`, 'info', data.executionId); } const sessionId = data.data?.sessionId || ''; if (sessionId && data.agentId) { if (Terminal.getChatSession()?.sessionId === sessionId || !Terminal.getChatSession()) { const agentName = App._lastAgentName || 'Agente'; Terminal.enableChat(data.agentId, agentName, sessionId); } if (Terminal.getChatSession()) { Terminal.updateSessionId(sessionId); } } if (typeof NotificationsUI !== 'undefined') { NotificationsUI.loadCount(); NotificationsUI.showBrowserNotification('Execução concluída', data.agentName || 'Agente'); } Toast.success('Execução concluída'); App.refreshCurrentSection(); App._updateActiveBadge(); break; } case 'execution_error': Terminal.stopProcessing(); Terminal.addLine(data.data?.error || 'Erro na execução', 'error', data.executionId); if (typeof NotificationsUI !== 'undefined') { NotificationsUI.loadCount(); NotificationsUI.showBrowserNotification('Execução falhou', data.agentName || 'Agente'); } Toast.error(`Erro na execução: ${data.data?.error || 'desconhecido'}`); App._updateActiveBadge(); break; case 'execution_retry': Terminal.stopProcessing(); Terminal.addLine( `Retry ${data.attempt || '?'}/${data.maxRetries || '?'} — próxima tentativa em ${data.nextRetryIn || '?'}s. Motivo: ${data.reason || 'erro na execução'}`, 'warning', data.executionId ); break; case 'pipeline_step_output': { Terminal.stopProcessing(); const stepEvtType = data.data?.type || 'chunk'; const stepContent = data.data?.content || ''; if (!stepContent) break; if (stepEvtType === 'tool') { Terminal.addLine(`▸ ${stepContent}`, 'tool', data.executionId); } else if (stepEvtType === 'turn') { Terminal.addLine(`── ${stepContent} ──`, 'turn', data.executionId); } else if (stepEvtType === 'system') { Terminal.addLine(stepContent, 'system', data.executionId); } else if (stepEvtType === 'stderr') { Terminal.addLine(stepContent, 'stderr', data.executionId); } else { Terminal.addLine(stepContent, 'default', data.executionId); } break; } case 'pipeline_step_start': Terminal.stopProcessing(); if (data.resumed) Terminal.addLine('(retomando execução anterior)', 'system'); Terminal.addLine(`Pipeline passo ${data.stepIndex + 1}/${data.totalSteps}: Executando agente "${data.agentName}"...`, 'system'); Terminal.startProcessing(data.agentName); break; case 'pipeline_step_complete': Terminal.stopProcessing(); Terminal.addLine(`Passo ${data.stepIndex + 1} concluído.`, 'info'); Terminal.addLine(data.result || '(sem output)', 'default'); break; case 'pipeline_complete': Terminal.stopProcessing(); Terminal.addLine('Pipeline concluído com sucesso.', 'success'); if (data.lastSessionId && data.lastAgentId) { Terminal.enableChat(data.lastAgentId, data.lastAgentName || 'Agente', data.lastSessionId); } Toast.success('Pipeline concluído'); App.refreshCurrentSection(); break; case 'pipeline_error': Terminal.stopProcessing(); Terminal.addLine(`Erro no passo ${data.stepIndex + 1}: ${data.error}`, 'error'); Toast.error('Erro no pipeline'); break; case 'pipeline_approval_required': Terminal.stopProcessing(); Terminal.addLine(`Passo ${data.stepIndex + 1} requer aprovação antes de executar.`, 'system'); if (data.previousOutput) { Terminal.addLine(`Output do passo anterior:\n${data.previousOutput.slice(0, 1000)}`, 'info'); } App._showApprovalNotification(data.pipelineId, data.stepIndex, data.agentName); Toast.warning('Pipeline aguardando aprovação'); break; case 'pipeline_rejected': Terminal.stopProcessing(); Terminal.addLine(`Pipeline rejeitado no passo ${data.stepIndex + 1}.`, 'error'); App._hideApprovalNotification(); Toast.info('Pipeline rejeitado'); App.refreshCurrentSection(); break; case 'pipeline_status': break; case 'report_generated': if (data.reportFile) { Terminal.addLine(`📄 Relatório gerado: ${data.reportFile}`, 'info'); App._openReport(data.reportFile); } break; } }, async _openReport(filename) { try { const data = await API.request('GET', `/reports/${encodeURIComponent(filename)}`); if (!data || !data.content) return; const modal = document.getElementById('execution-detail-modal-overlay'); const title = document.getElementById('execution-detail-title'); const content = document.getElementById('execution-detail-content'); if (!modal || !title || !content) return; title.textContent = 'Relatório de Execução'; content.innerHTML = `
${Utils.escapeHtml(data.content)}
`; Utils.refreshIcons(content); document.getElementById('report-copy-btn')?.addEventListener('click', () => { navigator.clipboard.writeText(data.content).then(() => Toast.success('Relatório copiado')); }); document.getElementById('report-download-btn')?.addEventListener('click', () => { const blob = new Blob([data.content], { type: 'text/markdown' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); Toast.success('Download iniciado'); }); Modal.open('execution-detail-modal-overlay'); } catch (e) {} }, _showApprovalNotification(pipelineId, stepIndex, agentName) { const container = document.getElementById('approval-notification'); if (!container) return; container.innerHTML = `
Aprovação necessária Passo ${stepIndex + 1} (${Utils.escapeHtml(agentName) || 'agente'}) aguardando autorização
`; container.hidden = false; container.dataset.pipelineId = pipelineId; Utils.refreshIcons(container); document.getElementById('approval-approve-btn')?.addEventListener('click', () => { App._handleApproval(pipelineId, true); }); document.getElementById('approval-reject-btn')?.addEventListener('click', () => { App._handleApproval(pipelineId, false); }); }, _hideApprovalNotification() { const container = document.getElementById('approval-notification'); if (container) { container.hidden = true; container.innerHTML = ''; } }, async _handleApproval(pipelineId, approve) { try { if (approve) { await API.pipelines.approve(pipelineId); Terminal.addLine('Passo aprovado. Continuando pipeline...', 'success'); Toast.success('Passo aprovado'); } else { await API.pipelines.reject(pipelineId); Terminal.addLine('Pipeline rejeitado pelo usuário.', 'error'); Toast.info('Pipeline rejeitado'); } App._hideApprovalNotification(); } catch (err) { Toast.error(`Erro: ${err.message}`); } }, updateWsStatus(status) { const indicator = document.getElementById('ws-indicator'); const label = document.getElementById('ws-label'); const terminalDot = document.getElementById('terminal-ws-dot'); const terminalLabel = document.getElementById('terminal-ws-label'); const wsBadge = document.getElementById('system-ws-status-badge'); const labels = { connected: 'Conectado', disconnected: 'Desconectado', error: 'Erro de conexão', }; const cssClass = { connected: 'ws-indicator--connected', disconnected: 'ws-indicator--disconnected', error: 'ws-indicator--error', }; const badgeClass = { connected: 'badge--green', disconnected: 'badge--red', error: 'badge--red', }; const displayLabel = labels[status] || status; const dotClass = cssClass[status] || 'ws-indicator--disconnected'; [indicator, terminalDot].forEach((el) => { if (!el) return; el.className = `ws-indicator ${dotClass}`; }); [label, terminalLabel].forEach((el) => { if (el) el.textContent = displayLabel; }); if (wsBadge) { wsBadge.textContent = displayLabel; wsBadge.className = `badge ${badgeClass[status] || 'badge--red'}`; } }, setupEventListeners() { const on = (id, event, handler) => { const el = document.getElementById(id); if (el) el.addEventListener(event, handler); }; on('new-agent-btn', 'click', () => AgentsUI.openCreateModal()); on('agents-empty-new-btn', 'click', () => AgentsUI.openCreateModal()); on('import-agent-btn', 'click', () => AgentsUI.openImportModal()); on('agent-form-submit', 'click', (e) => { e.preventDefault(); AgentsUI.save(); }); on('agent-form', 'submit', (e) => { e.preventDefault(); AgentsUI.save(); }); on('import-confirm-btn', 'click', () => AgentsUI.importAgent()); on('execute-form-submit', 'click', (e) => { e.preventDefault(); App._handleExecute(); }); on('execute-form', 'submit', (e) => { e.preventDefault(); App._handleExecute(); }); on('execute-saved-task', 'change', (e) => { const taskId = e.target.value; if (!taskId) return; const task = (AgentsUI._savedTasksCache || []).find((t) => t.id === taskId); if (!task) return; const taskEl = document.getElementById('execute-task-desc'); if (taskEl) { const parts = [task.name]; if (task.description) parts.push(task.description); taskEl.value = parts.join('\n\n'); } }); on('tasks-new-btn', 'click', () => TasksUI.openCreateModal()); on('tasks-empty-new-btn', 'click', () => TasksUI.openCreateModal()); on('schedules-new-btn', 'click', () => SchedulesUI.openCreateModal()); on('schedule-form-submit', 'click', (e) => { e.preventDefault(); SchedulesUI.save(); }); on('schedule-form', 'submit', (e) => { e.preventDefault(); SchedulesUI.save(); }); on('webhooks-new-btn', 'click', () => WebhooksUI.openCreateModal()); on('webhook-form-submit', 'click', (e) => { e.preventDefault(); WebhooksUI.save(); }); on('webhook-target-type', 'change', (e) => { WebhooksUI._updateTargetSelect(e.target.value); }); on('pipelines-new-btn', 'click', () => PipelinesUI.openCreateModal()); on('pipeline-form-submit', 'click', (e) => { e.preventDefault(); PipelinesUI.save(); }); on('pipeline-add-step-btn', 'click', () => PipelinesUI.addStep()); on('pipeline-execute-submit', 'click', () => PipelinesUI._executeFromModal()); on('terminal-clear-btn', 'click', () => { Terminal.clear(); Terminal.disableChat(); }); on('terminal-send-btn', 'click', () => App._sendChatMessage()); on('terminal-input', 'keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); App._sendChatMessage(); } }); on('export-copy-btn', 'click', () => App._copyExportJson()); on('system-status-btn', 'click', () => App.navigateTo('dashboard')); on('terminal-execution-select', 'change', (e) => { Terminal.setExecutionFilter(e.target.value || null); }); on('settings-form', 'submit', (e) => { e.preventDefault(); SettingsUI.save(); }); on('agents-search', 'input', () => { AgentsUI.filter( document.getElementById('agents-search')?.value, document.getElementById('agents-filter-status')?.value ); }); on('agents-filter-status', 'change', () => { AgentsUI.filter( document.getElementById('agents-search')?.value, document.getElementById('agents-filter-status')?.value ); }); on('tasks-search', 'input', () => { TasksUI.filter( document.getElementById('tasks-search')?.value, document.getElementById('tasks-filter-category')?.value ); }); on('tasks-filter-category', 'change', () => { TasksUI.filter( document.getElementById('tasks-search')?.value, document.getElementById('tasks-filter-category')?.value ); }); on('schedules-search', 'input', () => { SchedulesUI.filter( document.getElementById('schedules-search')?.value, document.getElementById('schedules-filter-status')?.value ); }); on('schedules-filter-status', 'change', () => { SchedulesUI.filter( document.getElementById('schedules-search')?.value, document.getElementById('schedules-filter-status')?.value ); }); on('webhooks-search', 'input', () => { WebhooksUI.filter(document.getElementById('webhooks-search')?.value); }); on('pipelines-search', 'input', () => { PipelinesUI.filter(document.getElementById('pipelines-search')?.value); }); on('history-search', 'input', () => { HistoryUI.filter( document.getElementById('history-search')?.value, document.getElementById('history-filter-type')?.value, document.getElementById('history-filter-status')?.value ); }); on('history-filter-type', 'change', () => { HistoryUI.filter( document.getElementById('history-search')?.value, document.getElementById('history-filter-type')?.value, document.getElementById('history-filter-status')?.value ); }); on('history-filter-status', 'change', () => { HistoryUI.filter( document.getElementById('history-search')?.value, document.getElementById('history-filter-type')?.value, document.getElementById('history-filter-status')?.value ); }); on('history-clear-btn', 'click', () => HistoryUI.clearHistory()); document.getElementById('agents-grid')?.addEventListener('click', (e) => { const btn = e.target.closest('[data-action]'); if (!btn) return; const { action, id } = btn.dataset; switch (action) { case 'execute': AgentsUI.execute(id); break; case 'edit': AgentsUI.openEditModal(id); break; case 'export': AgentsUI.export(id); break; case 'delete': AgentsUI.delete(id); break; case 'duplicate': AgentsUI.duplicate(id); break; case 'versions': AgentsUI.openVersionsModal(id); break; } }); document.getElementById('tasks-grid')?.addEventListener('click', (e) => { const btn = e.target.closest('[data-action]'); if (!btn) return; const { action, id } = btn.dataset; switch (action) { case 'execute-task': TasksUI.execute(id); break; case 'edit-task': TasksUI.openEditModal(id); break; case 'delete-task': TasksUI.delete(id); break; } }); document.getElementById('schedules-tbody')?.addEventListener('click', (e) => { const btn = e.target.closest('[data-action]'); if (!btn) return; const { action, id } = btn.dataset; switch (action) { case 'edit-schedule': SchedulesUI.openEditModal(id); break; case 'delete-schedule': SchedulesUI.delete(id); break; } }); document.getElementById('schedules-history')?.addEventListener('click', (e) => { const btn = e.target.closest('[data-action]'); if (!btn) return; const { action, id } = btn.dataset; if (action === 'view-schedule-exec') HistoryUI.viewDetail(id); }); document.getElementById('pipelines-grid')?.addEventListener('click', (e) => { if (e.target.closest('#pipelines-empty-new-btn')) { PipelinesUI.openCreateModal(); return; } const btn = e.target.closest('[data-action]'); if (!btn) return; const { action, id } = btn.dataset; switch (action) { case 'execute-pipeline': PipelinesUI.execute(id); break; case 'edit-pipeline': PipelinesUI.openEditModal(id); break; case 'flow-pipeline': FlowEditor.open(id); break; case 'delete-pipeline': PipelinesUI.delete(id); break; } }); document.getElementById('history-list')?.addEventListener('click', (e) => { const btn = e.target.closest('[data-action]'); if (!btn) return; const { action, id } = btn.dataset; switch (action) { case 'view-execution': HistoryUI.viewDetail(id); break; case 'delete-execution': HistoryUI.deleteExecution(id); break; case 'retry': HistoryUI.retryExecution(id); break; case 'resume-pipeline': HistoryUI.resumePipeline(id); break; } }); document.getElementById('webhooks-list')?.addEventListener('click', (e) => { const btn = e.target.closest('[data-action]'); if (!btn) return; const { action, id, url } = btn.dataset; switch (action) { case 'toggle-webhook': WebhooksUI.toggleActive(id); break; case 'delete-webhook': WebhooksUI.delete(id); break; case 'copy-webhook-url': WebhooksUI.copyUrl(url); break; case 'copy-webhook-curl': WebhooksUI.copyCurl(id); break; case 'edit-webhook': WebhooksUI.openEditModal(id); break; case 'test-webhook': WebhooksUI.test(id); break; } }); document.getElementById('pipeline-steps-container')?.addEventListener('click', (e) => { const btn = e.target.closest('[data-step-action]'); if (!btn) return; const stepAction = btn.dataset.stepAction; const stepIndex = parseInt(btn.dataset.stepIndex, 10); switch (stepAction) { case 'move-up': PipelinesUI.moveStep(stepIndex, -1); break; case 'move-down': PipelinesUI.moveStep(stepIndex, 1); break; case 'remove': PipelinesUI.removeStep(stepIndex); break; case 'toggle-mode': PipelinesUI.toggleMode(stepIndex); break; } }); document.addEventListener('click', (e) => { const template = e.target.closest('[data-template]'); if (template) { const taskEl = document.getElementById('execute-task-desc'); if (taskEl) taskEl.value = template.dataset.template; return; } const cronPreset = e.target.closest('[data-cron]'); if (cronPreset) { const cronEl = document.getElementById('schedule-cron'); if (cronEl) cronEl.value = cronPreset.dataset.cron; return; } }); App._setupTagsInput(); }, _setupTagsInput() { const input = document.getElementById('agent-tags-input'); const chips = document.getElementById('agent-tags-chips'); const hidden = document.getElementById('agent-tags'); if (!input || !chips || !hidden) return; const getTags = () => { try { return JSON.parse(hidden.value || '[]'); } catch { return []; } }; const setTags = (tags) => { hidden.value = JSON.stringify(tags); chips.innerHTML = tags.map((t) => ` ${Utils.escapeHtml(t)} `).join(''); }; input.addEventListener('keydown', (e) => { if (e.key !== 'Enter' && e.key !== ',') return; e.preventDefault(); const value = input.value.trim().replace(/,$/, ''); if (!value) return; const tags = getTags(); if (!tags.includes(value)) { tags.push(value); setTags(tags); } input.value = ''; }); chips.addEventListener('click', (e) => { const removeBtn = e.target.closest('.tag-remove'); if (!removeBtn) return; const tag = removeBtn.dataset.tag; const tags = getTags().filter((t) => t !== tag); setTags(tags); }); }, async _handleExecute() { const agentId = document.getElementById('execute-agent-select')?.value || document.getElementById('execute-agent-id')?.value; if (!agentId) { Toast.warning('Selecione um agente para executar'); return; } const task = document.getElementById('execute-task-desc')?.value.trim(); if (!task) { Toast.warning('Descreva a tarefa a ser executada'); return; } const instructions = document.getElementById('execute-instructions')?.value.trim() || ''; try { const selectEl = document.getElementById('execute-agent-select'); const agentName = selectEl?.selectedOptions[0]?.text || 'Agente'; let contextFiles = null; const dropzone = App._executeDropzone; if (dropzone && dropzone.getFiles().length > 0) { Toast.info('Fazendo upload dos arquivos...'); const uploadResult = await API.uploads.send(dropzone.getFiles()); contextFiles = uploadResult.files; } Terminal.disableChat(); App._lastAgentName = agentName; await API.agents.execute(agentId, task, instructions, contextFiles); if (dropzone) dropzone.reset(); Modal.close('execute-modal-overlay'); App.navigateTo('terminal'); Toast.info('Execução iniciada'); Terminal.startProcessing(agentName); } catch (err) { Toast.error(`Erro ao iniciar execução: ${err.message}`); } }, async _sendChatMessage() { const session = Terminal.getChatSession(); if (!session) return; const input = document.getElementById('terminal-input'); const message = input?.value.trim(); if (!message) return; input.value = ''; Terminal.addLine(`❯ ${message}`, 'user-message', null); try { await API.agents.continue(session.agentId, session.sessionId, message); Terminal.startProcessing(session.agentName); } catch (err) { Terminal.addLine(`Erro: ${err.message}`, 'error'); Toast.error(`Erro ao continuar conversa: ${err.message}`); } }, async _copyExportJson() { const jsonEl = document.getElementById('export-code-content'); if (!jsonEl) return; try { await navigator.clipboard.writeText(jsonEl.textContent); Toast.success('JSON copiado para a área de transferência'); } catch { Toast.error('Não foi possível copiar o JSON'); } }, setupKeyboardShortcuts() { document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { Modal.closeAll(); return; } const isInInput = ['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement?.tagName); if (isInInput) return; if (e.key === 'n' || e.key === 'N') { if (App.currentSection === 'agents') { AgentsUI.openCreateModal(); } } if (!e.ctrlKey && !e.metaKey && !e.altKey) { const sectionKeys = { '1': 'dashboard', '2': 'agents', '3': 'tasks', '4': 'schedules', '5': 'pipelines', '6': 'terminal', '7': 'history', '8': 'webhooks', '9': 'settings', }; if (sectionKeys[e.key]) { e.preventDefault(); App.navigateTo(sectionKeys[e.key]); } } }); }, async refreshCurrentSection() { await App._loadSection(App.currentSection); }, async _updateActiveBadge() { try { const active = await API.system.activeExecutions(); const count = Array.isArray(active) ? active.length : 0; const badge = document.getElementById('active-executions-badge'); const countEl = document.getElementById('active-executions-count'); if (countEl) countEl.textContent = count; if (badge) badge.style.display = count > 0 ? 'flex' : 'none'; const terminalSelect = document.getElementById('terminal-execution-select'); if (terminalSelect && Array.isArray(active)) { const existing = new Set( Array.from(terminalSelect.options).map((o) => o.value).filter(Boolean) ); active.forEach((exec) => { const execId = exec.executionId || exec.id; if (!existing.has(execId)) { const option = document.createElement('option'); option.value = execId; const agentName = (exec.agentConfig && exec.agentConfig.agent_name) || exec.agentId || 'Agente'; option.textContent = `${agentName} — ${new Date(exec.startedAt).toLocaleTimeString('pt-BR')}`; terminalSelect.appendChild(option); } }); } } catch { // } }, startPeriodicRefresh() { setInterval(async () => { await App._updateActiveBadge(); if (App.currentSection === 'dashboard') { await DashboardUI.load(); } }, 30000); }, }; document.addEventListener('DOMContentLoaded', () => App.init()); window.App = App;