// Costs Page Module class CostsPage { constructor() { this.costData = null; this.init(); } async init() { // Load data await Promise.all([ this.loadCostStats(), this.loadCostsChart(), this.loadBudgetTracking(), this.loadPricingTable() ]); // Setup event listeners this.setupEventListeners(); } async loadCostStats() { try { const data = await window.api.get('/usage/summary'); this.costData = { totalCost: data.total_cost, todayCost: data.today_cost, weekCost: data.total_cost * 0.4, // Placeholder for weekly logic monthCost: data.total_cost, avgDailyCost: data.total_cost / 30, // Simplified costTrend: 5.2, budgetUsed: Math.min(Math.round((data.total_cost / 100) * 100), 100), // Assuming $100 budget projectedMonthEnd: data.today_cost * 30, cacheReadTokens: data.total_cache_read_tokens || 0, cacheWriteTokens: data.total_cache_write_tokens || 0, totalTokens: data.total_tokens || 0, }; this.renderCostStats(); } catch (error) { console.error('Error loading cost stats:', error); } } renderCostStats() { const container = document.getElementById('cost-stats'); if (!container) return; const cacheHitRate = this.costData.totalTokens > 0 ? ((this.costData.cacheReadTokens / this.costData.totalTokens) * 100).toFixed(1) : '0.0'; container.innerHTML = `
${window.api.formatCurrency(this.costData.totalCost)}
Total Cost
${window.api.formatCurrency(this.costData.todayCost)} today
${window.api.formatCurrency(this.costData.monthCost)}
This Month
${window.api.formatCurrency(this.costData.avgDailyCost)}/day avg
${cacheHitRate}%
Cache Hit Rate
${window.api.formatNumber(this.costData.cacheReadTokens)} cached tokens
${this.costData.budgetUsed}%
Budget Used
$${this.costData.projectedMonthEnd.toFixed(2)} projected
`; } async loadCostsChart() { try { const cm = window.chartManager || await window.waitForChartManager(); if (!cm) { this.showEmptyChart('costs-chart', 'Chart system unavailable'); return; } const data = await window.api.get('/usage/providers'); if (!data || data.length === 0) { this.showEmptyChart('costs-chart', 'No provider spending data yet'); return; } const chartData = { labels: data.map(item => item.provider), datasets: [{ label: 'Cost ($)', data: data.map(item => item.cost), color: '#fe8019' }] }; cm.createBarChart('costs-chart', chartData); } catch (error) { console.error('Error loading costs chart:', error); this.showEmptyChart('costs-chart', 'Failed to load spending data'); } } showEmptyChart(canvasId, message) { const canvas = document.getElementById(canvasId); if (!canvas) return; const container = canvas.closest('.chart-container'); if (container) { canvas.style.display = 'none'; let msg = container.querySelector('.empty-chart-msg'); if (!msg) { msg = document.createElement('div'); msg.className = 'empty-chart-msg'; msg.style.cssText = 'display:flex;align-items:center;justify-content:center;height:200px;color:var(--fg4);font-size:0.9rem;'; container.appendChild(msg); } msg.textContent = message; } } async loadBudgetTracking() { const container = document.getElementById('budget-progress'); if (!container) return; try { const providers = await window.api.get('/providers'); container.innerHTML = providers.filter(p => p.id !== 'ollama').map(provider => { const used = provider.low_credit_threshold; // Not quite right but using available fields const balance = provider.credit_balance; const percentage = balance > 0 ? Math.max(0, Math.min(100, (1 - (used / balance)) * 100)) : 0; return `
${provider.name} Balance ${window.api.formatCurrency(balance)}
`; }).join(''); } catch (error) { console.error('Error loading budgets:', error); } } async loadPricingTable() { try { const data = await window.api.get('/models'); this.renderPricingTable(data); } catch (error) { console.error('Error loading pricing data:', error); } } renderPricingTable(data) { const tableBody = document.querySelector('#pricing-table tbody'); if (!tableBody) return; tableBody.innerHTML = data.map(row => { const cacheRead = row.cache_read_cost != null ? `${window.api.formatCurrency(row.cache_read_cost)} / 1M` : '--'; const cacheWrite = row.cache_write_cost != null ? `${window.api.formatCurrency(row.cache_write_cost)} / 1M` : '--'; return ` ${row.provider.toUpperCase()} ${row.id} ${window.api.formatCurrency(row.prompt_cost)} / 1M ${window.api.formatCurrency(row.completion_cost)} / 1M ${cacheRead} ${cacheWrite} `; }).join(''); } setupEventListeners() { // ... } refresh() { this.loadCostStats(); this.loadCostsChart(); this.loadBudgetTracking(); this.loadPricingTable(); } } window.initCosts = async () => { window.costsPage = new CostsPage(); };