"use client"; import { Fragment, useEffect, useState, useCallback, startTransition } from "react"; import { useParams } from "next/navigation"; import { motion } from "motion/react"; import { ArrowLeft, Hash, Zap, DollarSign, MessageSquare, DatabaseZap, BookOpen, ArrowUpDown, ArrowDown, ArrowUp, ChevronRight, KeyRound } from "lucide-react"; import Link from "next/link"; import { StatsCard } from "@/components/StatsCard"; import { TimeRangeSelector } from "@/components/TimeRangeSelector"; import { TrendChart } from "@/components/charts/TrendChart"; import { buildQuery, formatNumber, formatTokens, formatUSD } from "@/lib/utils"; import { sortDetailBreakdown, type DetailBreakdownItem, type DetailBreakdownSortKey } from "@/lib/detail-sort"; import { getPrimaryModelNames, getSharePercent, getTokenDisplayName, getTokenRowKey, shouldShowTokenTab, sortTokenBreakdown, type TokenBreakdownItem } from "@/lib/token-breakdown"; import { getDetailStats, type DetailStatKey } from "@/lib/detail-stats"; import { quotaToUsd } from "@/lib/metrics"; import { useTimeRange } from "@/lib/time-range-context"; import { useI18n } from "@/lib/i18n"; interface DetailData { calls: number; prompt_tokens: number; completion_tokens: number; cache_creation_tokens: number; cache_read_tokens: number; total_tokens: number; quota: number; display_name?: string; models?: DetailBreakdownItem[]; users?: DetailBreakdownItem[]; tokens?: TokenBreakdownItem[]; channel_name?: string; } type BreakdownTab = "models" | "tokens"; export default function DetailPage() { const { t } = useI18n(); const params = useParams(); const segments = Array.isArray(params.slug) ? params.slug : []; const type = segments[0] || ""; const id = segments[1] || ""; const decodedId = decodeURIComponent(id); const { getEffectiveRange } = useTimeRange(); const [data, setData] = useState(null); const [trends, setTrends] = useState<{ date: string; calls: number; total_tokens: number; prompt_tokens: number; completion_tokens: number }[]>([]); const [loading, setLoading] = useState(true); const [breakdownSortKey, setBreakdownSortKey] = useState("total_tokens"); const [breakdownSortAsc, setBreakdownSortAsc] = useState(false); const [breakdownTab, setBreakdownTab] = useState("models"); const [expandedTokens, setExpandedTokens] = useState>(() => new Set()); const fetchData = useCallback(async () => { startTransition(() => setLoading(true)); const { start, end } = getEffectiveRange(); const tp = { start, end }; const [detail, tr] = await Promise.all([ fetch(buildQuery(`/api/detail/${type}/${encodeURIComponent(decodedId)}`, tp)).then(r => r.json()), fetch(buildQuery("/api/trends", { ...tp, granularity: "day", ...(type === "user" ? { username: decodedId } : {}), ...(type === "model" ? { model: decodedId } : {}), ...(type === "channel" ? { channel_id: decodedId } : {}), })).then(r => r.json()), ]); startTransition(() => { setData(detail); setTrends(tr); setLoading(false); }); }, [type, decodedId, getEffectiveRange]); useEffect(() => { fetchData(); }, [fetchData]); const title = type === "channel" ? (data?.channel_name || decodedId) : (data?.display_name || decodedId); const typeLabel = { user: t("detail.user"), model: t("detail.model"), channel: t("detail.channel") }[type] || type; const modelBreakdownItems = data?.models || data?.users || []; const tokenBreakdownItems = data?.tokens || []; const showTokenTab = shouldShowTokenTab(type); const activeBreakdownTab = showTokenTab ? breakdownTab : "models"; const sortedBreakdownItems = sortDetailBreakdown(modelBreakdownItems, breakdownSortKey, breakdownSortAsc); const sortedTokenItems = sortTokenBreakdown(tokenBreakdownItems, breakdownSortKey, breakdownSortAsc); const breakdownLabel = data?.models ? t("detail.modelDist") : t("detail.userDist"); function handleBreakdownSort(key: DetailBreakdownSortKey) { if (breakdownSortKey === key) setBreakdownSortAsc(!breakdownSortAsc); else { setBreakdownSortKey(key); setBreakdownSortAsc(false); } } function toggleToken(tokenName: string) { setExpandedTokens((current) => { const next = new Set(current); if (next.has(tokenName)) next.delete(tokenName); else next.add(tokenName); return next; }); } const renderBreakdownSortIcon = (col: DetailBreakdownSortKey) => { if (breakdownSortKey !== col) return ; return breakdownSortAsc ? : ; }; const breakdownColumns: { key: DetailBreakdownSortKey | null; label: string; align: "left" | "right" }[] = [ { key: null, label: t("th.name"), align: "left" }, { key: "calls", label: t("th.calls"), align: "right" }, { key: "total_tokens", label: t("th.totalToken"), align: "right" }, { key: "quota", label: t("th.cost"), align: "right" }, ]; const statIcons: Record = { calls: Hash, total_tokens: Zap, quota: DollarSign, prompt_tokens: MessageSquare, completion_tokens: MessageSquare, cache_creation_tokens: DatabaseZap, cache_read_tokens: BookOpen, }; function renderEmptyBreakdown() { return (
{t("common.noData")}
); } function renderTokenDistribution() { if (sortedTokenItems.length === 0) return renderEmptyBreakdown(); return ( {breakdownColumns.slice(1).map((col) => { const key = col.key; return ( ); })} {sortedTokenItems.map((item) => { const isExpanded = expandedTokens.has(item.name); const tokenLabel = getTokenDisplayName(item.name, t("detail.unnamedToken")); const userShare = getSharePercent(item.total_tokens, data?.total_tokens ?? 0); return ( toggleToken(item.name)}> {isExpanded && ( )} ); })}
{t("th.name")} handleBreakdownSort(key) : undefined} > {key ? ( {col.label} {renderBreakdownSortIcon(key)} ) : col.label} {t("common.share")} {t("detail.primaryModels")}
{tokenLabel} {formatNumber(item.calls)} {formatTokens(item.total_tokens)} {formatUSD(quotaToUsd(item.quota))} {userShare.toFixed(1)}% {getPrimaryModelNames(item.models) || t("common.noData")}
{item.models.map((model) => { const modelShare = getSharePercent(model.total_tokens, item.total_tokens); return ( ); })}
{t("detail.model")} {t("th.calls")} {t("th.totalToken")} {t("th.cost")} {t("common.share")}
{model.name} {formatNumber(model.calls)} {formatTokens(model.total_tokens)} {formatUSD(quotaToUsd(model.quota))} {modelShare.toFixed(1)}%
); } return (
{t("common.backToRankings")}
{typeLabel}

{title}

{loading ? (
) : data ? ( <>
{getDetailStats(data).map((stat, i) => ( ))}

{t("detail.trend")}

{(modelBreakdownItems.length > 0 || showTokenTab) && (

{activeBreakdownTab === "tokens" ? t("detail.tokenDist") : breakdownLabel}

{showTokenTab && (
{([ ["models", t("detail.modelDist")], ["tokens", t("detail.tokenDist")], ] as const).map(([value, label]) => ( ))}
)}
{activeBreakdownTab === "tokens" ? ( renderTokenDistribution() ) : sortedBreakdownItems.length > 0 ? ( {breakdownColumns.map((col) => { const key = col.key; return ( ); })} {sortedBreakdownItems.map((item) => ( ))}
handleBreakdownSort(key) : undefined} > {key ? ( {col.label} {renderBreakdownSortIcon(key)} ) : col.label}
{item.name} {formatNumber(item.calls)} {formatTokens(item.total_tokens)} {formatUSD(quotaToUsd(item.quota))}
) : renderEmptyBreakdown()}
)} ) : null}
); }