const HistoryUI = {
executions: [],
total: 0,
page: 0,
pageSize: 20,
_currentSearch: '',
_currentType: '',
_currentStatus: '',
_exportListenerAdded: false,
async load() {
if (!HistoryUI._exportListenerAdded) {
HistoryUI._exportListenerAdded = true;
const exportBtn = document.getElementById('history-export-csv');
if (exportBtn) {
exportBtn.addEventListener('click', () => API.executions.exportCsv());
}
}
const params = { limit: HistoryUI.pageSize, offset: HistoryUI.page * HistoryUI.pageSize };
if (HistoryUI._currentType) params.type = HistoryUI._currentType;
if (HistoryUI._currentStatus) params.status = HistoryUI._currentStatus;
if (HistoryUI._currentSearch) params.search = HistoryUI._currentSearch;
try {
const data = await API.executions.history(params);
HistoryUI.executions = data.items || [];
HistoryUI.total = data.total || 0;
HistoryUI.render();
HistoryUI._renderPagination();
} catch (err) {
Toast.error(`Erro ao carregar histórico: ${err.message}`);
}
},
render() {
const container = document.getElementById('history-list');
if (!container) return;
if (HistoryUI.executions.length === 0) {
container.innerHTML = `
Nenhuma execução encontrada
O histórico de execuções aparecerá aqui.
`;
Utils.refreshIcons(container);
return;
}
container.innerHTML = HistoryUI.executions.map((exec) => HistoryUI._renderCard(exec)).join('');
Utils.refreshIcons(container);
},
_renderCard(exec) {
const typeBadge = exec.type === 'pipeline'
? 'Pipeline'
: 'Agente';
const statusBadge = HistoryUI._statusBadge(exec.status);
const name = exec.type === 'pipeline'
? (exec.pipelineName || 'Pipeline')
: (exec.agentName || 'Agente');
const taskRaw = exec.type === 'pipeline'
? (exec.input || '')
: (exec.task || '');
const task = taskRaw.length > 150 ? taskRaw.slice(0, 150) + '…' : taskRaw;
const date = HistoryUI._formatDate(exec.startedAt);
const duration = HistoryUI._formatDuration(exec.startedAt, exec.endedAt);
const cost = exec.costUsd || exec.totalCostUsd || 0;
const costHtml = cost > 0
? `$${cost.toFixed(4)}`
: '';
return `
${Utils.escapeHtml(task)}
${date}
${duration}
${costHtml}
${(exec.status === 'error' || exec.status === 'canceled') ? `
` : ''}
`;
},
_renderPagination() {
const container = document.getElementById('history-pagination');
if (!container) return;
const totalPages = Math.ceil(HistoryUI.total / HistoryUI.pageSize);
if (totalPages <= 1) {
container.innerHTML = '';
return;
}
const hasPrev = HistoryUI.page > 0;
const hasNext = HistoryUI.page < totalPages - 1;
const start = HistoryUI.page * HistoryUI.pageSize + 1;
const end = Math.min((HistoryUI.page + 1) * HistoryUI.pageSize, HistoryUI.total);
container.innerHTML = `
`;
Utils.refreshIcons(container);
document.getElementById('history-prev-btn')?.addEventListener('click', () => {
HistoryUI.page--;
HistoryUI.load();
});
document.getElementById('history-next-btn')?.addEventListener('click', () => {
HistoryUI.page++;
HistoryUI.load();
});
},
filter(search, type, status) {
HistoryUI._currentSearch = search || '';
HistoryUI._currentType = type || '';
HistoryUI._currentStatus = status || '';
HistoryUI.page = 0;
HistoryUI.load();
},
async viewDetail(id) {
try {
const exec = await API.executions.get(id);
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;
const name = exec.type === 'pipeline'
? (exec.pipelineName || 'Pipeline')
: (exec.agentName || 'Agente');
title.textContent = name;
content.innerHTML = exec.type === 'pipeline'
? HistoryUI._renderPipelineDetail(exec)
: HistoryUI._renderAgentDetail(exec);
Modal.open('execution-detail-modal-overlay');
Utils.refreshIcons(content);
content.querySelector('[data-action="download-result-md"]')?.addEventListener('click', () => {
HistoryUI._downloadResultMd(exec);
});
content.querySelectorAll('.pipeline-step-prompt-toggle').forEach((btn) => {
btn.addEventListener('click', () => {
const stepCard = btn.closest('.pipeline-step-detail');
const promptBody = stepCard?.querySelector('.pipeline-step-prompt-body');
if (!promptBody) return;
const isHidden = promptBody.hidden;
promptBody.hidden = !isHidden;
btn.setAttribute('aria-expanded', String(isHidden));
});
});
} catch (err) {
Toast.error(`Erro ao carregar execução: ${err.message}`);
}
},
_renderAgentDetail(exec) {
const duration = HistoryUI._formatDuration(exec.startedAt, exec.endedAt);
const startDate = HistoryUI._formatDate(exec.startedAt);
const endDate = exec.endedAt ? HistoryUI._formatDate(exec.endedAt) : '—';
const resultBlock = exec.result
? `${Utils.escapeHtml(exec.result)}
`
: '';
const errorBlock = exec.error
? `${Utils.escapeHtml(exec.error)}
`
: '';
return `
${exec.result ? `
` : ''}
${exec.task ? `
Tarefa
${Utils.escapeHtml(exec.task)}
` : ''}
${resultBlock ? `
Resultado
${resultBlock}
` : ''}
${errorBlock ? `
Erro
${errorBlock}
` : ''}
`;
},
_renderPipelineDetail(exec) {
const duration = HistoryUI._formatDuration(exec.startedAt, exec.endedAt);
const startDate = HistoryUI._formatDate(exec.startedAt);
const endDate = exec.endedAt ? HistoryUI._formatDate(exec.endedAt) : '—';
const steps = Array.isArray(exec.steps) ? exec.steps : [];
const stepsHtml = steps.map((step, index) => {
const stepDuration = HistoryUI._formatDuration(step.startedAt, step.endedAt);
const isLast = index === steps.length - 1;
return `
${step.stepIndex + 1}
${isLast ? '' : '
'}
${step.prompt ? `
${Utils.escapeHtml(step.prompt)}
` : ''}
${step.result ? `
Resultado
${Utils.escapeHtml(step.result)}
` : ''}
${step.status === 'error' ? `
Passo falhou.
` : ''}
`;
}).join('');
const hasResults = steps.some(s => s.result);
return `
${hasResults ? `
` : ''}
${exec.input ? `
Input Inicial
${Utils.escapeHtml(exec.input)}
` : ''}
${steps.length > 0 ? `
Passos do Pipeline
${stepsHtml}
` : ''}
${exec.error ? `
Erro
${Utils.escapeHtml(exec.error)}
` : ''}
`;
},
_downloadResultMd(exec) {
let md = '';
const name = exec.type === 'pipeline'
? (exec.pipelineName || 'Pipeline')
: (exec.agentName || 'Agente');
if (exec.type === 'pipeline') {
md += `# ${name}\n\n`;
const steps = Array.isArray(exec.steps) ? exec.steps : [];
steps.forEach((step, i) => {
md += `## Passo ${i + 1} — ${step.agentName || 'Agente'}\n\n`;
if (step.result) md += `${step.result}\n\n`;
});
} else {
md += exec.result || '';
}
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
const filename = `${slug}-${new Date(exec.startedAt || Date.now()).toISOString().slice(0, 10)}.md`;
const blob = new Blob([md], { 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');
},
async retryExecution(id) {
try {
await API.executions.retry(id);
Toast.success('Execução reiniciada');
App.navigateTo('terminal');
} catch (err) {
Toast.error(`Erro ao reexecutar: ${err.message}`);
}
},
async deleteExecution(id) {
const confirmed = await Modal.confirm(
'Excluir execução',
'Tem certeza que deseja excluir esta execução do histórico? Esta ação não pode ser desfeita.'
);
if (!confirmed) return;
try {
await API.executions.delete(id);
Toast.success('Execução excluída do histórico');
await HistoryUI.load();
} catch (err) {
Toast.error(`Erro ao excluir execução: ${err.message}`);
}
},
async clearHistory() {
const confirmed = await Modal.confirm(
'Limpar histórico',
'Tem certeza que deseja excluir todo o histórico de execuções? Esta ação não pode ser desfeita.'
);
if (!confirmed) return;
try {
await API.executions.clearAll();
Toast.success('Histórico limpo com sucesso');
HistoryUI.page = 0;
await HistoryUI.load();
} catch (err) {
Toast.error(`Erro ao limpar histórico: ${err.message}`);
}
},
_statusBadge(status) {
const map = {
running: ['badge-running', 'Em execução'],
completed: ['badge-active', 'Concluído'],
error: ['badge-error', 'Erro'],
awaiting_approval: ['badge-warning', 'Aguardando'],
rejected: ['badge-error', 'Rejeitado'],
canceled: ['badge-inactive', 'Cancelado'],
};
const [cls, label] = map[status] || ['badge-inactive', status || 'Desconhecido'];
return `${label}`;
},
_formatDuration(start, end) {
if (!start) return '—';
const startMs = new Date(start).getTime();
const endMs = end ? new Date(end).getTime() : Date.now();
const totalSeconds = Math.floor((endMs - startMs) / 1000);
if (totalSeconds < 0) return '—';
if (totalSeconds < 60) return `${totalSeconds}s`;
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
if (hours > 0) return `${hours}h ${minutes}m`;
return `${minutes}m ${seconds}s`;
},
_formatDate(iso) {
if (!iso) return '—';
const date = new Date(iso);
return date.toLocaleString('pt-BR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
},
};
window.HistoryUI = HistoryUI;