gemini-cost-tracker
Version:
CLI tool to display token usage and costs for Gemini and Vertex AI
146 lines • 6.98 kB
JavaScript
import chalk from 'chalk';
import { formatDate } from '../../utils/dateHelper.js';
export class ChartFormatter {
format(data) {
let output = '';
// Header
output += chalk.bold.blue('📊 Cost Report - Chart View\n');
output += chalk.gray(`Period: ${formatDate(data.period.start)} to ${formatDate(data.period.end)}\n\n`);
// Daily cost chart
const dailyCosts = this.calculateDailyCosts(data);
if (Object.keys(dailyCosts).length > 1) {
output += this.renderDailyCostChart(dailyCosts, data.summary.currency);
output += '\n\n';
}
// Service comparison chart
const serviceBreakdown = this.calculateServiceBreakdown(data);
if (Object.keys(serviceBreakdown).length > 1) {
output += this.renderServiceChart(serviceBreakdown, data.summary.currency);
output += '\n\n';
}
// Model comparison chart
const modelBreakdown = this.calculateModelBreakdown(data);
if (Object.keys(modelBreakdown).length > 1) {
output += this.renderModelChart(modelBreakdown, data.summary.currency);
output += '\n\n';
}
// Token usage trend
const dailyTokens = this.calculateDailyTokens(data);
if (Object.keys(dailyTokens).length > 1) {
output += this.renderTokenTrendChart(dailyTokens);
output += '\n\n';
}
// Summary
output += chalk.bold.green('📈 Summary:\n');
output += `Total Cost: ${chalk.bold(data.summary.totalCost.toFixed(4))} ${data.summary.currency}\n`;
output += `Total Tokens: ${chalk.bold((data.summary.totalInputTokens + data.summary.totalOutputTokens).toLocaleString())}\n`;
output += `Average Cost per 1K Tokens: ${chalk.bold((data.summary.totalCost / ((data.summary.totalInputTokens + data.summary.totalOutputTokens) / 1000)).toFixed(6))} ${data.summary.currency}\n`;
return output;
}
calculateDailyCosts(data) {
const dailyCosts = {};
for (const detail of data.details) {
const dateKey = formatDate(detail.date);
if (!dailyCosts[dateKey]) {
dailyCosts[dateKey] = 0;
}
dailyCosts[dateKey] += detail.cost.totalCost;
}
return dailyCosts;
}
calculateServiceBreakdown(data) {
const breakdown = {};
for (const detail of data.details) {
if (!breakdown[detail.service]) {
breakdown[detail.service] = 0;
}
breakdown[detail.service] += detail.cost.totalCost;
}
return breakdown;
}
calculateModelBreakdown(data) {
const breakdown = {};
for (const detail of data.details) {
if (!breakdown[detail.model]) {
breakdown[detail.model] = 0;
}
breakdown[detail.model] += detail.cost.totalCost;
}
return breakdown;
}
calculateDailyTokens(data) {
const dailyTokens = {};
for (const detail of data.details) {
const dateKey = formatDate(detail.date);
if (!dailyTokens[dateKey]) {
dailyTokens[dateKey] = { input: 0, output: 0 };
}
dailyTokens[dateKey].input += detail.usage.inputTokens;
dailyTokens[dateKey].output += detail.usage.outputTokens;
}
return dailyTokens;
}
renderDailyCostChart(dailyCosts, currency) {
let output = chalk.bold('💰 Daily Cost Trend\n');
const dates = Object.keys(dailyCosts).sort();
const maxCost = Math.max(...Object.values(dailyCosts));
const maxBarLength = 50;
for (const date of dates) {
const cost = dailyCosts[date];
const barLength = Math.round((cost / maxCost) * maxBarLength);
const bar = '█'.repeat(barLength);
const spaces = ' '.repeat(Math.max(0, maxBarLength - barLength));
output += `${date} │${chalk.blue(bar)}${spaces}│ ${cost.toFixed(4)} ${currency}\n`;
}
return output;
}
renderServiceChart(serviceBreakdown, currency) {
let output = chalk.bold('🔧 Service Cost Comparison\n');
const services = Object.entries(serviceBreakdown).sort(([, a], [, b]) => b - a);
const maxCost = Math.max(...Object.values(serviceBreakdown));
const maxBarLength = 40;
for (const [service, cost] of services) {
const barLength = Math.round((cost / maxCost) * maxBarLength);
const serviceColor = service === 'gemini' ? chalk.green : chalk.blue;
const bar = '█'.repeat(barLength);
const spaces = ' '.repeat(Math.max(0, maxBarLength - barLength));
output += `${serviceColor(service.padEnd(10))} │${chalk.yellow(bar)}${spaces}│ ${cost.toFixed(4)} ${currency}\n`;
}
return output;
}
renderModelChart(modelBreakdown, currency) {
let output = chalk.bold('🤖 Model Cost Comparison\n');
const models = Object.entries(modelBreakdown).sort(([, a], [, b]) => b - a);
const maxCost = Math.max(...Object.values(modelBreakdown));
const maxBarLength = 35;
for (const [model, cost] of models) {
const barLength = Math.round((cost / maxCost) * maxBarLength);
const bar = '█'.repeat(barLength);
const spaces = ' '.repeat(Math.max(0, maxBarLength - barLength));
output += `${chalk.cyan(model.padEnd(18))} │${chalk.magenta(bar)}${spaces}│ ${cost.toFixed(4)} ${currency}\n`;
}
return output;
}
renderTokenTrendChart(dailyTokens) {
let output = chalk.bold('📊 Daily Token Usage Trend\n');
const dates = Object.keys(dailyTokens).sort();
const maxTokens = Math.max(...dates.map((date) => dailyTokens[date].input + dailyTokens[date].output));
const maxBarLength = 45;
for (const date of dates) {
const tokens = dailyTokens[date];
const totalTokens = tokens.input + tokens.output;
const inputRatio = tokens.input / totalTokens;
// const outputRatio = tokens.output / totalTokens;
const totalBarLength = Math.round((totalTokens / maxTokens) * maxBarLength);
const inputBarLength = Math.round(totalBarLength * inputRatio);
const outputBarLength = totalBarLength - inputBarLength;
const inputBar = '█'.repeat(inputBarLength);
const outputBar = '█'.repeat(outputBarLength);
const spaces = ' '.repeat(Math.max(0, maxBarLength - totalBarLength));
output += `${date} │${chalk.green(inputBar)}${chalk.red(outputBar)}${spaces}│ ${totalTokens.toLocaleString()}\n`;
}
output += `\nLegend: ${chalk.green('█')} Input Tokens ${chalk.red('█')} Output Tokens\n`;
return output;
}
}
//# sourceMappingURL=chartFormatter.js.map