"use client"; import { useEffect, useState, useCallback, useRef, startTransition } from "react"; import { createPortal } from "react-dom"; import { motion } from "motion/react"; import { Users, Calendar, Hash, Zap, ArrowUpDown, ArrowDown, ArrowUp, HelpCircle } from "lucide-react"; import { TimeRangeSelector } from "@/components/TimeRangeSelector"; import { buildQuery, formatNumber, formatTokens } from "@/lib/utils"; import { useTimeRange } from "@/lib/time-range-context"; import { useI18n } from "@/lib/i18n"; interface AggItem { rank: number; name: string; calls: number; prompt_tokens: number; completion_tokens: number; cache_creation_tokens: number; cache_read_tokens: number; total_tokens: number; } type SortKey = "total_tokens" | "calls" | "prompt_tokens" | "completion_tokens" | "cache_creation_tokens" | "cache_read_tokens" | "ratio"; function RatioTooltip({ text }: { text: string }) { const [show, setShow] = useState(false); const [pos, setPos] = useState({ x: 0, y: 0 }); const iconRef = useRef(null); const [mounted, setMounted] = useState(false); useEffect(() => { startTransition(() => setMounted(true)); }, []); function handleEnter() { if (iconRef.current) { const rect = iconRef.current.getBoundingClientRect(); setPos({ x: rect.right, y: rect.top }); } setShow(true); } return ( <> e.stopPropagation()} onMouseEnter={handleEnter} onMouseLeave={() => setShow(false)} > {mounted && show && createPortal(
{text}
, document.body )} ); } export default function AggregationPage() { const { t } = useI18n(); const { getEffectiveRange } = useTimeRange(); const [data, setData] = useState([]); const [loading, setLoading] = useState(true); const [sortKey, setSortKey] = useState("total_tokens"); const [sortAsc, setSortAsc] = useState(false); const fetchData = useCallback(async () => { startTransition(() => setLoading(true)); const { start, end } = getEffectiveRange(); const res = await fetch(buildQuery("/api/aggregation", { start, end })); const json = await res.json(); startTransition(() => { setData(json); setLoading(false); }); }, [getEffectiveRange]); useEffect(() => { fetchData(); }, [fetchData]); const sorted = [...data].sort((a, b) => { const getVal = (item: AggItem) => sortKey === "ratio" ? (item.prompt_tokens > 0 ? item.completion_tokens / item.prompt_tokens : 0) : (item[sortKey] as number); const diff = getVal(a) - getVal(b); return sortAsc ? diff : -diff; }); const totals = data.reduce( (acc, d) => ({ calls: acc.calls + d.calls, tokens: acc.tokens + d.total_tokens }), { calls: 0, tokens: 0 } ); function handleSort(key: SortKey) { if (sortKey === key) setSortAsc(!sortAsc); else { setSortKey(key); setSortAsc(false); } } const renderSortIcon = (col: SortKey) => { if (sortKey !== col) return ; return sortAsc ? : ; }; const sortHeaders: { key: SortKey; label: string }[] = [ { key: "calls", label: t("th.calls") }, { key: "prompt_tokens", label: t("th.input") }, { key: "cache_creation_tokens", label: t("th.cacheCreation") }, { key: "cache_read_tokens", label: t("th.cacheRead") }, { key: "completion_tokens", label: t("th.output") }, { key: "total_tokens", label: t("th.totalToken") }, ]; return (

{t("agg.title")}

{!loading && data.length > 0 && (
{t("agg.userCount")} {data.length}
{t("agg.totalCalls")} {formatNumber(totals.calls)}
{t("agg.totalToken")} {formatTokens(totals.tokens)}
)} {sortHeaders.map(h => ( ))} {loading ? ( ) : sorted.map((item, i) => { const pct = totals.tokens > 0 ? (item.total_tokens / totals.tokens * 100) : 0; const ratio = item.prompt_tokens > 0 ? (item.completion_tokens / item.prompt_tokens) : 0; return ( ); })}
# {t("th.user")} handleSort(h.key)}> {h.label} {renderSortIcon(h.key)} handleSort("ratio")}> {t("agg.ratio")} {renderSortIcon("ratio")} {t("common.share")}
{i + 1} {item.name} {formatNumber(item.calls)} {formatTokens(item.prompt_tokens)} {formatTokens(item.cache_creation_tokens)} {formatTokens(item.cache_read_tokens)} {formatTokens(item.completion_tokens)} {formatTokens(item.total_tokens)} = 1 ? "var(--accent)" : "var(--text-muted)" }}>{ratio.toFixed(2)}
{pct.toFixed(1)}%
); }