// CRITICAL "use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import api from "@/lib/api"; import type { PeakMetrics, SortDirection, SortField, UsageStats } from "@/lib/types"; export function useUsage() { const [stats, setStats] = useState(null); const [peakMetrics, setPeakMetrics] = useState>(new Map()); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [expandedRows, setExpandedRows] = useState>(new Set()); const [sortField, setSortField] = useState("success"); const [sortDirection, setSortDirection] = useState("desc"); const loadStats = useCallback(async () => { try { setLoading(true); setError(null); const [usageData, peakData] = await Promise.all([api.getUsageStats(), api.getPeakMetrics()]); setStats(usageData); if (peakData.metrics) { const metricsMap = new Map(); for (const metric of peakData.metrics) { metricsMap.set(metric.model_id, metric); } setPeakMetrics(metricsMap); } } catch (e) { setError((e as Error).message); } finally { setLoading(false); } }, []); useEffect(() => { loadStats(); }, [loadStats]); const dailyByModel = useMemo(() => { if (!stats || !stats.daily_by_model || !Array.isArray(stats.daily_by_model)) { return new Map>(); } const grouped = new Map>(); for (const entry of stats.daily_by_model) { if (!grouped.has(entry.model)) { grouped.set(entry.model, new Map()); } grouped.get(entry.model)!.set(entry.date, entry); } return grouped; }, [stats]); const modelsForChart = useMemo(() => { if (!stats) return []; return [...stats.by_model].sort((a, b) => b.total_tokens - a.total_tokens).map((m) => m.model); }, [stats]); const sortedModels = useMemo(() => { if (!stats) return []; const sorted = [...stats.by_model]; sorted.sort((a, b) => { let aVal: number | string; let bVal: number | string; switch (sortField) { case "model": aVal = a.model.toLowerCase(); bVal = b.model.toLowerCase(); break; case "requests": aVal = a.requests; bVal = b.requests; break; case "tokens": aVal = a.total_tokens; bVal = b.total_tokens; break; case "success": aVal = a.success_rate; bVal = b.success_rate; break; case "latency": aVal = a.avg_latency_ms; bVal = b.avg_latency_ms; break; case "ttft": aVal = a.avg_ttft_ms; bVal = b.avg_ttft_ms; break; case "speed": aVal = a.tokens_per_sec ?? 0; bVal = b.tokens_per_sec ?? 0; break; default: return 0; } if (typeof aVal === "string" && typeof bVal === "string") { return sortDirection === "asc" ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal); } return sortDirection === "asc" ? (aVal as number) - (bVal as number) : (bVal as number) - (aVal as number); }); return sorted; }, [sortField, sortDirection, stats]); const handleSort = useCallback( (field: SortField) => { if (sortField === field) { setSortDirection(sortDirection === "asc" ? "desc" : "asc"); } else { setSortField(field); setSortDirection("desc"); } }, [sortDirection, sortField], ); const toggleRow = useCallback((model: string) => { setExpandedRows((prev) => { const next = new Set(prev); if (next.has(model)) { next.delete(model); } else { next.add(model); } return next; }); }, []); return { stats, peakMetrics, loading, error, expandedRows, sortField, sortDirection, loadStats, dailyByModel, modelsForChart, sortedModels, handleSort, toggleRow, }; }