import { useEffect, useState, useCallback } from "react"; import { BarChart3, Coins, Cpu, Database, Hash, TrendingUp, } from "lucide-react"; import { api } from "@/lib/api"; import type { AnalyticsResponse, AnalyticsDailyEntry, AnalyticsModelEntry } from "@/lib/api"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; const PERIODS = [ { label: "7d", days: 7 }, { label: "30d", days: 30 }, { label: "90d", days: 90 }, ] as const; const CHART_HEIGHT_PX = 160; function formatTokens(n: number): string { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`; return String(n); } function formatCost(n: number): string { if (n < 0.01) return `$${n.toFixed(4)}`; return `$${n.toFixed(2)}`; } /** Pick the best cost value: actual > estimated > 0 */ function bestCost(entry: { estimated_cost: number; actual_cost?: number }): number { if (entry.actual_cost && entry.actual_cost > 0) return entry.actual_cost; return entry.estimated_cost; } function formatDate(day: string): string { try { const d = new Date(day + "T00:00:00"); return d.toLocaleDateString(undefined, { month: "short", day: "numeric" }); } catch { return day; } } function SummaryCard({ icon: Icon, label, value, sub, }: { icon: React.ComponentType<{ className?: string }>; label: string; value: string; sub?: string; }) { return ( {label}
{value}
{sub &&

{sub}

}
); } function TokenBarChart({ daily }: { daily: AnalyticsDailyEntry[] }) { if (daily.length === 0) return null; const maxTokens = Math.max(...daily.map((d) => d.input_tokens + d.output_tokens), 1); return (
Daily Token Usage
Input
Output
{daily.map((d) => { const total = d.input_tokens + d.output_tokens; const inputH = Math.round((d.input_tokens / maxTokens) * CHART_HEIGHT_PX); const outputH = Math.round((d.output_tokens / maxTokens) * CHART_HEIGHT_PX); const cacheReadPct = d.cache_read_tokens > 0 ? Math.round((d.cache_read_tokens / (d.input_tokens + d.cache_read_tokens)) * 100) : 0; return (
{/* Tooltip */}
{formatDate(d.day)}
Input: {formatTokens(d.input_tokens)}
Output: {formatTokens(d.output_tokens)}
{cacheReadPct > 0 &&
Cache hit: {cacheReadPct}%
}
Total: {formatTokens(total)}
{bestCost(d) > 0 &&
Cost: {formatCost(bestCost(d))}
}
{/* Input bar */}
0 ? 1 : 0) }} /> {/* Output bar */}
0 ? 1 : 0) }} />
); })}
{/* X-axis labels */}
{daily.length > 0 ? formatDate(daily[0].day) : ""} {daily.length > 2 && ( {formatDate(daily[Math.floor(daily.length / 2)].day)} )} {daily.length > 1 ? formatDate(daily[daily.length - 1].day) : ""}
); } function DailyTable({ daily }: { daily: AnalyticsDailyEntry[] }) { if (daily.length === 0) return null; const sorted = [...daily].reverse(); return (
Daily Breakdown
{sorted.map((d) => { const cost = bestCost(d); const cacheHitPct = d.cache_read_tokens > 0 && d.input_tokens > 0 ? Math.round((d.cache_read_tokens / d.input_tokens) * 100) : 0; return ( ); })}
Date Sessions Input Output Cache Hit Cost
{formatDate(d.day)} {d.sessions} {formatTokens(d.input_tokens)} {formatTokens(d.output_tokens)} {cacheHitPct > 0 ? `${cacheHitPct}%` : "—"} {cost > 0 ? formatCost(cost) : "—"}
); } function ModelTable({ models }: { models: AnalyticsModelEntry[] }) { if (models.length === 0) return null; const sorted = [...models].sort( (a, b) => b.input_tokens + b.output_tokens - (a.input_tokens + a.output_tokens), ); return (
Per-Model Breakdown
{sorted.map((m) => ( ))}
Model Sessions Tokens Cost
{m.model} {m.sessions} {formatTokens(m.input_tokens)} {" / "} {formatTokens(m.output_tokens)} {m.estimated_cost > 0 ? formatCost(m.estimated_cost) : "—"}
); } export default function AnalyticsPage() { const [days, setDays] = useState(30); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const load = useCallback(() => { setLoading(true); setError(null); api .getAnalytics(days) .then(setData) .catch((err) => setError(String(err))) .finally(() => setLoading(false)); }, [days]); useEffect(() => { load(); }, [load]); return (
{/* Period selector */}
Period: {PERIODS.map((p) => ( ))}
{loading && !data && (
)} {error && (

{error}

)} {data && ( <> {/* Summary cards — matches hermes's token model */}
0 ? `${Math.round((data.totals.total_cache_read / (data.totals.total_input + data.totals.total_cache_read)) * 100)}%` : "—"} sub={`${formatTokens(data.totals.total_cache_read)} tokens from cache`} /> 0 ? data.totals.total_actual_cost : data.totals.total_estimated_cost )} sub={data.totals.total_actual_cost > 0 ? "actual" : `estimated · last ${days}d`} />
{/* Bar chart */} {/* Tables */} )} {data && data.daily.length === 0 && data.by_model.length === 0 && (

No usage data for this period

Start a session to see analytics here

)}
); }