"use client"; import { useState, useEffect, useMemo, useCallback } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import { CardSkeleton } from "./Loading"; import Badge from "./Badge"; import Card from "./Card"; import OverviewCards from "@/app/(dashboard)/dashboard/usage/components/OverviewCards"; import UsageTable, { fmt, fmtTime } from "@/app/(dashboard)/dashboard/usage/components/UsageTable"; import ProviderTopology from "@/app/(dashboard)/dashboard/usage/components/ProviderTopology"; import UsageChart from "@/app/(dashboard)/dashboard/usage/components/UsageChart"; function timeAgo(timestamp) { const diff = Math.floor((Date.now() - new Date(timestamp)) / 1000); if (diff < 60) return `${diff}s ago`; if (diff < 3600) return `${Math.floor(diff / 60)}m ago`; if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`; return `${Math.floor(diff / 86400)}d ago`; } function RecentRequests({ requests = [] }) { return ( {/* Header */}
Recent Requests
{!requests.length ? (
No requests yet.
) : (
{requests.map((r, i) => { const ok = !r.status || r.status === "ok" || r.status === "success"; return ( ); })}
Model In / Out When
{r.model} {fmt(r.promptTokens)}↑ {" "} {fmt(r.completionTokens)}↓ {timeAgo(r.timestamp)}
)}
); } function sortData(dataMap, pendingMap = {}, sortBy, sortOrder) { return Object.entries(dataMap || {}) .map(([key, data]) => { const totalTokens = (data.promptTokens || 0) + (data.completionTokens || 0); const totalCost = data.cost || 0; const inputCost = totalTokens > 0 ? (data.promptTokens || 0) * (totalCost / totalTokens) : 0; const outputCost = totalTokens > 0 ? (data.completionTokens || 0) * (totalCost / totalTokens) : 0; return { ...data, key, totalTokens, totalCost, inputCost, outputCost, pending: pendingMap[key] || 0 }; }) .sort((a, b) => { let valA = a[sortBy]; let valB = b[sortBy]; 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; }); } function getGroupKey(item, keyField) { switch (keyField) { case "rawModel": return item.rawModel || "Unknown Model"; case "accountName": return item.accountName || `Account ${item.connectionId?.slice(0, 8)}...` || "Unknown Account"; case "keyName": return item.keyName || "Unknown Key"; case "endpoint": return item.endpoint || "Unknown Endpoint"; default: return item[keyField] || "Unknown"; } } function groupDataByKey(data, keyField) { if (!Array.isArray(data)) return []; const groups = {}; data.forEach((item) => { const gk = getGroupKey(item, keyField); if (!groups[gk]) { groups[gk] = { groupKey: gk, summary: { requests: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0, cost: 0, inputCost: 0, outputCost: 0, lastUsed: null, pending: 0 }, items: [], }; } const s = groups[gk].summary; s.requests += item.requests || 0; s.promptTokens += item.promptTokens || 0; s.completionTokens += item.completionTokens || 0; s.totalTokens += item.totalTokens || 0; s.cost += item.cost || 0; s.inputCost += item.inputCost || 0; s.outputCost += item.outputCost || 0; s.pending += item.pending || 0; if (item.lastUsed && (!s.lastUsed || new Date(item.lastUsed) > new Date(s.lastUsed))) { s.lastUsed = item.lastUsed; } groups[gk].items.push(item); }); return Object.values(groups); } const MODEL_COLUMNS = [ { field: "rawModel", label: "Model" }, { field: "provider", label: "Provider" }, { field: "requests", label: "Requests", align: "right" }, { field: "lastUsed", label: "Last Used", align: "right" }, ]; const ACCOUNT_COLUMNS = [ { field: "rawModel", label: "Model" }, { field: "provider", label: "Provider" }, { field: "accountName", label: "Account" }, { field: "requests", label: "Requests", align: "right" }, { field: "lastUsed", label: "Last Used", align: "right" }, ]; const API_KEY_COLUMNS = [ { field: "keyName", label: "API Key Name" }, { field: "rawModel", label: "Model" }, { field: "provider", label: "Provider" }, { field: "requests", label: "Requests", align: "right" }, { field: "lastUsed", label: "Last Used", align: "right" }, ]; const ENDPOINT_COLUMNS = [ { field: "endpoint", label: "Endpoint" }, { field: "rawModel", label: "Model" }, { field: "provider", label: "Provider" }, { field: "requests", label: "Requests", align: "right" }, { field: "lastUsed", label: "Last Used", align: "right" }, ]; const TABLE_OPTIONS = [ { value: "model", label: "Usage by Model" }, { value: "account", label: "Usage by Account" }, { value: "apiKey", label: "Usage by API Key" }, { value: "endpoint", label: "Usage by Endpoint" }, ]; 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 [tableView, setTableView] = useState("model"); const [providers, setProviders] = useState([]); // Fetch connected providers once, deduplicate by provider type useEffect(() => { fetch("/api/providers") .then((r) => r.ok ? r.json() : null) .then((d) => { if (!d?.connections) return; const seen = new Set(); const unique = d.connections.filter((c) => { if (seen.has(c.provider)) return false; seen.add(c.provider); return true; }); setProviders(unique); }) .catch(() => {}); }, []); // SSE connection - no polling, event-driven useEffect(() => { console.log("[SSE CLIENT] connecting..."); const es = new EventSource("/api/usage/stream"); es.onopen = () => console.log("[SSE CLIENT] connected ✓"); es.onmessage = (e) => { try { const data = JSON.parse(e.data); console.log("[SSE CLIENT] message received | activeRequests:", data.activeRequests?.length || 0, "| providers:", data.activeRequests?.map(r => r.provider)); setStats(data); setLoading(false); } catch (err) { console.error("[SSE CLIENT] parse error:", err); } }; es.onerror = (e) => { console.error("[SSE CLIENT] error | readyState:", es.readyState, e); setLoading(false); }; return () => { console.log("[SSE CLIENT] closing"); es.close(); }; }, []); const toggleSort = useCallback((tableType, field) => { const params = new URLSearchParams(searchParams.toString()); if (params.get("sortBy") === field) { params.set("sortOrder", params.get("sortOrder") === "asc" ? "desc" : "asc"); } else { params.set("sortBy", field); params.set("sortOrder", "asc"); } router.replace(`?${params.toString()}`, { scroll: false }); }, [searchParams, router]); // Compute active table data const activeTableConfig = useMemo(() => { if (!stats) return null; switch (tableView) { case "model": { const pendingMap = stats.pending?.byModel || {}; return { columns: MODEL_COLUMNS, groupedData: groupDataByKey(sortData(stats.byModel, pendingMap, sortBy, sortOrder), "rawModel"), storageKey: "usage-stats:expanded-models", emptyMessage: "No usage recorded yet.", renderSummaryCells: (group) => ( <> — {fmt(group.summary.requests)} {fmtTime(group.summary.lastUsed)} ), renderDetailCells: (item) => ( <> 0 ? "text-primary" : ""}`}>{item.rawModel} 0 ? "primary" : "neutral"} size="sm">{item.provider} {fmt(item.requests)} {fmtTime(item.lastUsed)} ), }; } case "account": { const pendingMap = {}; if (stats?.pending?.byAccount) { Object.entries(stats.byAccount || {}).forEach(([accountKey, data]) => { const connPending = stats.pending.byAccount[data.connectionId]; if (connPending) { const modelKey = data.provider ? `${data.rawModel} (${data.provider})` : data.rawModel; pendingMap[accountKey] = connPending[modelKey] || 0; } }); } return { columns: ACCOUNT_COLUMNS, groupedData: groupDataByKey(sortData(stats.byAccount, pendingMap, sortBy, sortOrder), "accountName"), storageKey: "usage-stats:expanded-accounts", emptyMessage: "No account-specific usage recorded yet.", renderSummaryCells: (group) => ( <> — — {fmt(group.summary.requests)} {fmtTime(group.summary.lastUsed)} ), renderDetailCells: (item) => ( <> 0 ? "text-primary" : ""}`}>{item.accountName || `Account ${item.connectionId?.slice(0, 8)}...`} 0 ? "text-primary" : ""}`}>{item.rawModel} 0 ? "primary" : "neutral"} size="sm">{item.provider} {fmt(item.requests)} {fmtTime(item.lastUsed)} ), }; } case "apiKey": { return { columns: API_KEY_COLUMNS, groupedData: groupDataByKey(sortData(stats.byApiKey, {}, sortBy, sortOrder), "keyName"), storageKey: "usage-stats:expanded-apikeys", emptyMessage: "No API key usage recorded yet.", renderSummaryCells: (group) => ( <> — — {fmt(group.summary.requests)} {fmtTime(group.summary.lastUsed)} ), renderDetailCells: (item) => ( <> {item.keyName} {item.rawModel} {item.provider} {fmt(item.requests)} {fmtTime(item.lastUsed)} ), }; } case "endpoint": default: { return { columns: ENDPOINT_COLUMNS, groupedData: groupDataByKey(sortData(stats.byEndpoint, {}, sortBy, sortOrder), "endpoint"), storageKey: "usage-stats:expanded-endpoints", emptyMessage: "No endpoint usage recorded yet.", renderSummaryCells: (group) => ( <> — — {fmt(group.summary.requests)} {fmtTime(group.summary.lastUsed)} ), renderDetailCells: (item) => ( <> {item.endpoint} {item.rawModel} {item.provider} {fmt(item.requests)} {fmtTime(item.lastUsed)} ), }; } } }, [stats, tableView, sortBy, sortOrder]); if (loading) return ; if (!stats) return
Failed to load usage statistics.
; return (
{/* Overview cards */} {/* Provider topology + Recent Requests */}
{/* Token / Cost chart */} {/* Table with dropdown selector */}
{activeTableConfig && ( )}
); }