async function renderCost() {
const content = document.getElementById('pageContent');
content.innerHTML = `
`;
try {
const data = await api.getCost();
const entries = data.entries || [];
const totals = data.daily_totals || {};
const alerts = data.free_tier_alerts || [];
const totalCost = entries.reduce((s, e) => s + (e.cost || 0), 0);
const totalTokens = entries.reduce((s, e) => s + (e.tokens || 0), 0);
const days = Object.keys(totals).length;
document.getElementById('costStats').innerHTML = `
💰
$${totalCost.toFixed(4)}
Total Cost
🔤
${totalTokens.toLocaleString()}
Total Tokens
${alerts.length > 0 ? '⚠' : '✓'}
${alerts.length}
Free Tier Alerts
`;
const entriesContainer = document.getElementById('costEntries');
if (entries.length === 0) {
entriesContainer.innerHTML = '';
} else {
entriesContainer.innerHTML = `
| Time | Agent | Model | Tokens | Cost |
${entries.slice(-20).reverse().map(e => `
| ${formatDate(e.timestamp)} |
${e.agent} |
${e.model} |
${(e.tokens || 0).toLocaleString()} |
$${(e.cost || 0).toFixed(6)} |
`).join('')}
`;
}
if (alerts.length > 0) {
const header = document.querySelector('.page-header');
header.insertAdjacentHTML('afterend', `
âš
Free Tier Alerts
${alerts.map(a => `
${escapeHtml(a)}
`).join('')}
`);
}
// Build agent chart
const agentTotals = {};
entries.forEach(e => {
const a = e.agent || 'unknown';
agentTotals[a] = (agentTotals[a] || 0) + (e.tokens || 0);
});
const agentLabels = Object.keys(agentTotals);
const agentData = Object.values(agentTotals);
if (entries.length > 0) {
new Chart(document.getElementById('agentChart'), {
type: 'doughnut',
data: { labels: agentLabels, datasets: [{ data: agentData, backgroundColor: ['rgba(108,92,231,0.8)', 'rgba(0,212,170,0.8)', 'rgba(69,170,242,0.8)', 'rgba(255,165,2,0.8)'] }] },
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: getComputedStyle(document.documentElement).getPropertyValue('--text-secondary').trim(), font: { size: 11 } } } } }
});
const timeTotals = {};
entries.forEach(e => {
const day = e.timestamp ? e.timestamp.slice(0, 10) : 'unknown';
timeTotals[day] = (timeTotals[day] || 0) + (e.tokens || 0);
});
const timeLabels = Object.keys(timeTotals).sort();
const timeData = timeLabels.map(d => timeTotals[d]);
new Chart(document.getElementById('timeChart'), {
type: 'line',
data: { labels: timeLabels, datasets: [{ label: 'Tokens', data: timeData, borderColor: 'rgba(108,92,231,0.8)', backgroundColor: 'rgba(108,92,231,0.1)', fill: true, tension: 0.4, pointRadius: 3 }] },
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { ticks: { color: getComputedStyle(document.documentElement).getPropertyValue('--text-muted').trim(), font: { size: 10 } } }, y: { ticks: { color: getComputedStyle(document.documentElement).getPropertyValue('--text-muted').trim(), font: { size: 10 } } } } }
});
}
} catch (err) {
document.getElementById('costStats').innerHTML = `âš
${escapeHtml(err.message)}
`;
}
}
async function recordTestCost() {
showModal('Record Cost Entry', `
`, `
`);
}
async function submitCostRecord() {
try {
await api.recordCost({
agent: document.getElementById('rcAgent').value,
model: document.getElementById('rcModel').value,
tokens: parseInt(document.getElementById('rcTokens').value) || 0,
cost: parseFloat(document.getElementById('rcCost').value) || 0,
});
closeModal();
showToast('Cost recorded', 'success');
renderCost();
} catch (err) {
showToast(`Error: ${err.message}`, 'error');
}
}