Files
GopherGate/static/js/pages/costs.js

184 lines
6.8 KiB
JavaScript

// 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
};
this.renderCostStats();
} catch (error) {
console.error('Error loading cost stats:', error);
}
}
renderCostStats() {
const container = document.getElementById('cost-stats');
if (!container) return;
container.innerHTML = `
<div class="stat-card">
<div class="stat-icon warning">
<i class="fas fa-dollar-sign"></i>
</div>
<div class="stat-content">
<div class="stat-value">${window.api.formatCurrency(this.costData.totalCost)}</div>
<div class="stat-label">Total Cost</div>
<div class="stat-change positive">
<i class="fas fa-arrow-up"></i>
${window.api.formatCurrency(this.costData.todayCost)} today
</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon success">
<i class="fas fa-calendar-alt"></i>
</div>
<div class="stat-content">
<div class="stat-value">${window.api.formatCurrency(this.costData.monthCost)}</div>
<div class="stat-label">This Month</div>
<div class="stat-change">
<i class="fas fa-chart-line"></i>
${window.api.formatCurrency(this.costData.avgDailyCost)}/day avg
</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon danger">
<i class="fas fa-piggy-bank"></i>
</div>
<div class="stat-content">
<div class="stat-value">${this.costData.budgetUsed}%</div>
<div class="stat-label">Budget Used</div>
<div class="stat-change">
$${this.costData.projectedMonthEnd.toFixed(2)} projected
</div>
</div>
</div>
`;
}
async loadCostsChart() {
try {
const data = await window.api.get('/usage/providers');
const chartData = {
labels: data.map(item => item.provider),
datasets: [{
label: 'Cost ($)',
data: data.map(item => item.cost),
color: '#fe8019'
}]
};
window.chartManager.createBarChart('costs-chart', chartData);
} catch (error) {
console.error('Error loading costs chart:', error);
}
}
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 `
<div class="budget-item" style="margin-bottom: 1.5rem;">
<div class="budget-header" style="display: flex; justify-content: space-between; margin-bottom: 0.5rem;">
<span class="budget-name" style="font-weight: 600;">${provider.name} Balance</span>
<span class="budget-amount">${window.api.formatCurrency(balance)}</span>
</div>
<div class="progress-bar" style="height: 8px; background: var(--bg2); border-radius: 4px; overflow: hidden;">
<div class="progress-fill" style="width: ${percentage}%; height: 100%; background: ${balance < used ? 'var(--red)' : 'var(--green)'};"></div>
</div>
<div class="budget-footer" style="display: flex; justify-content: space-between; margin-top: 0.5rem; font-size: 0.75rem; color: var(--fg4);">
<span>Threshold: ${window.api.formatCurrency(used)}</span>
<span>Status: ${balance < used ? 'LOW' : 'Healthy'}</span>
</div>
</div>
`;
}).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 => `
<tr>
<td><span class="badge-client">${row.provider.toUpperCase()}</span></td>
<td><code class="code-sm">${row.id}</code></td>
<td>${window.api.formatCurrency(row.prompt_cost)} / 1M</td>
<td>${window.api.formatCurrency(row.completion_cost)} / 1M</td>
<td>Now</td>
</tr>
`).join('');
}
setupEventListeners() {
// ...
}
refresh() {
this.loadCostStats();
this.loadCostsChart();
this.loadBudgetTracking();
this.loadPricingTable();
}
}
window.initCosts = async () => {
window.costsPage = new CostsPage();
};