"use client"; import { 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 } 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 { 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[]; channel_name?: string; } 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 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 breakdownItems = data?.models || data?.users || []; const sortedBreakdownItems = sortDetailBreakdown(breakdownItems, 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); } } 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" }, ]; return (
{t("common.backToRankings")}
{typeLabel}

{title}

{loading ? (
) : data ? ( <>

{t("detail.trend")}

{breakdownItems.length > 0 && (

{breakdownLabel}

{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(item.quota / 500000)}
)} ) : null}
); }