import { useEffect, useState, useCallback } from "react"; import { BarChart3, Cpu, Hash, TrendingUp, Zap, } 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"; import { useI18n } from "@/i18n"; const PERIODS = [ { label: "7d", days: 7 }, { label: "30d", days: 30 }, { label: "90d", days: 90 }, ] as const; const CHART_HEIGHT_PX = 160; /** Compute total prompt tokens (input + cache_read + cache_write). */ function getPromptTokens(d: { input_tokens: number; cache_read_tokens?: number; cache_write_tokens?: number }): number { return d.input_tokens + (d.cache_read_tokens ?? 0) + (d.cache_write_tokens ?? 0); } 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 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[] }) { const { t } = useI18n(); if (daily.length === 0) return null; const maxTokens = Math.max(...daily.map((d) => getPromptTokens(d) + d.output_tokens), 1); return (
{t.analytics.dailyTokenUsage}
{t.analytics.prompt}
{t.analytics.output}
{daily.map((d) => { const promptTokens = getPromptTokens(d); const total = promptTokens + d.output_tokens; const inputH = Math.round((promptTokens / maxTokens) * CHART_HEIGHT_PX); const outputH = Math.round((d.output_tokens / maxTokens) * CHART_HEIGHT_PX); return (
{/* Tooltip */}
{formatDate(d.day)}
{t.analytics.prompt}: {formatTokens(promptTokens)}
{t.analytics.output}: {formatTokens(d.output_tokens)}
{t.analytics.total}: {formatTokens(total)}
{/* 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[] }) { const { t } = useI18n(); if (daily.length === 0) return null; const sorted = [...daily].reverse(); return (
{t.analytics.dailyBreakdown}
{sorted.map((d) => { const promptTokens = getPromptTokens(d); return ( ); })}
{t.analytics.date} {t.sessions.title} {t.analytics.prompt} {t.analytics.output}
{formatDate(d.day)} {d.sessions} {formatTokens(promptTokens)} {formatTokens(d.output_tokens)}
); } function ModelTable({ models }: { models: AnalyticsModelEntry[] }) { const { t } = useI18n(); if (models.length === 0) return null; const sorted = [...models].sort( (a, b) => (getPromptTokens(b) + b.output_tokens) - (getPromptTokens(a) + a.output_tokens), ); return (
{t.analytics.perModelBreakdown}
{sorted.map((m) => ( ))}
{t.analytics.model} {t.sessions.title} {t.analytics.tokens}
{m.model} {m.sessions} {formatTokens(getPromptTokens(m))} {" / "} {formatTokens(m.output_tokens)}
); } 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 { t } = useI18n(); 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 */}
{t.analytics.period} {PERIODS.map((p) => ( ))}
{loading && !data && (
)} {error && (

{error}

)} {data && ( <> {/* Summary cards */}
sum + d.sessions, 0))} sub={t.analytics.acrossModels.replace("{count}", String(data.by_model.length))} /> {(() => { const promptSent = (data.totals.total_input ?? 0) + (data.totals.total_cache_read ?? 0); const rate = promptSent > 0 ? `${((data.totals.total_cache_read ?? 0) / promptSent * 100).toFixed(0)}%` : "—"; return ( ); })()}
{/* Bar chart */} {/* Tables */} )} {data && data.daily.length === 0 && data.by_model.length === 0 && (

{t.analytics.noUsageData}

{t.analytics.startSession}

)}
); }