"use client"; import { useState, useEffect, useMemo } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import Card from "./Card"; import Badge from "./Badge"; import { CardSkeleton } from "./Loading"; function SortIcon({ field, currentSort, currentOrder }) { if (currentSort !== field) return ; return {currentOrder === "asc" ? "↑" : "↓"}; } function MiniBarGraph({ data, colorClass = "bg-primary" }) { const max = Math.max(...data, 1); return (
{data.slice(-9).map((val, i) => (
))}
); } export default function UsageStats() { const router = useRouter(); const searchParams = useSearchParams(); const sortBy = searchParams.get("sortBy") || "rawModel"; const sortOrder = searchParams.get("sortOrder") || "asc"; const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [autoRefresh, setAutoRefresh] = useState(false); const toggleSort = (field) => { const params = new URLSearchParams(searchParams.toString()); if (sortBy === field) { params.set("sortOrder", sortOrder === "asc" ? "desc" : "asc"); } else { params.set("sortBy", field); params.set("sortOrder", "asc"); } router.replace(`?${params.toString()}`, { scroll: false }); }; const sortData = (dataMap) => { return Object.entries(dataMap || {}) .map(([key, data]) => ({ ...data, key, totalTokens: (data.promptTokens || 0) + (data.completionTokens || 0) })) .sort((a, b) => { let valA = a[sortBy]; let valB = b[sortBy]; // Handle case-insensitive sorting for strings if (typeof valA === "string") valA = valA.toLowerCase(); if (typeof valB === "string") valB = valB.toLowerCase(); if (valA < valB) return sortOrder === "asc" ? -1 : 1; if (valA > valB) return sortOrder === "asc" ? 1 : -1; return 0; }); }; const sortedModels = useMemo(() => sortData(stats?.byModel), [stats?.byModel, sortBy, sortOrder]); const sortedAccounts = useMemo(() => sortData(stats?.byAccount), [stats?.byAccount, sortBy, sortOrder]); useEffect(() => { fetchStats(); }, []); useEffect(() => { let interval; if (autoRefresh) { interval = setInterval(() => { fetchStats(false); // fetch without loading skeleton }, 5000); } return () => clearInterval(interval); }, [autoRefresh]); const fetchStats = async (showLoading = true) => { if (showLoading) setLoading(true); try { const res = await fetch("/api/usage/history"); if (res.ok) { const data = await res.json(); setStats(data); } } catch (error) { console.error("Failed to fetch usage stats:", error); } finally { if (showLoading) setLoading(false); } }; if (loading) return ; if (!stats) return
Failed to load usage statistics.
; // Format number with commas const fmt = (n) => new Intl.NumberFormat().format(n || 0); return (
{/* Header with Auto Refresh Toggle */}

Usage Overview

{/* Overview Cards */}
Total Requests {fmt(stats.totalRequests)}
m.requests)} colorClass="bg-text-muted/30" />
Total Input Tokens {fmt(stats.totalPromptTokens)}
m.promptTokens)} colorClass="bg-primary/50" />
Total Output Tokens {fmt(stats.totalCompletionTokens)}
m.completionTokens)} colorClass="bg-success/50" />
{/* Usage by Model Table */}

Usage by Model

{sortedModels.map((data) => ( ))} {sortedModels.length === 0 && ( )}
toggleSort("rawModel")}> Model toggleSort("provider")}> Provider toggleSort("requests")}> Requests toggleSort("promptTokens")}> Input Tokens toggleSort("completionTokens")}> Output Tokens toggleSort("totalTokens")}> Total Tokens
{data.rawModel} {data.provider} {fmt(data.requests)} {fmt(data.promptTokens)} {fmt(data.completionTokens)} {fmt(data.totalTokens)}
No usage recorded yet. Make some requests to see data here.
{/* Usage by Account Table */}

Usage by Account

{sortedAccounts.map((data) => ( ))} {sortedAccounts.length === 0 && ( )}
toggleSort("rawModel")}> Model toggleSort("provider")}> Provider toggleSort("accountName")}> Account toggleSort("requests")}> Requests toggleSort("promptTokens")}> Input Tokens toggleSort("completionTokens")}> Output Tokens toggleSort("totalTokens")}> Total Tokens
{data.rawModel} {data.provider} {data.accountName || `Account ${data.connectionId?.slice(0, 8)}...`} {fmt(data.requests)} {fmt(data.promptTokens)} {fmt(data.completionTokens)} {fmt(data.totalTokens)}
No account-specific usage recorded yet. Make requests using OAuth accounts to see data here.
); }