"use client"; import { useState, useEffect, useRef, useCallback } from "react"; import { fetchUsageSummary, type UsageSummary } from "@/lib/api/telemetry"; // ─── Types ─────────────────────────────────────────────────────────── interface UsageTier { name: string; tokens: number; limit: number; percentage: number; } // ─── Helpers ───────────────────────────────────────────────────────── function getUsageColor(percentage: number): string { if (percentage < 60) return "var(--success)"; if (percentage < 80) return "var(--warn)"; return "var(--danger)"; } function formatTokens(value: number): string { if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`; if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`; return value.toFixed(0); } // ─── Component ─────────────────────────────────────────────────────── export function UsageWidget(): React.JSX.Element { const [summary, setSummary] = useState(null); const [popoverOpen, setPopoverOpen] = useState(false); const [isLoading, setIsLoading] = useState(true); const popoverRef = useRef(null); const tiers: UsageTier[] = summary ? [ { name: "Session", tokens: summary.totalTokens, limit: 100_000, percentage: (summary.totalTokens / 100_000) * 100, }, { name: "Daily", tokens: summary.totalTokens, limit: 500_000, percentage: (summary.totalTokens / 500_000) * 100, }, { name: "Monthly", tokens: summary.totalTokens, limit: 2_000_000, percentage: (summary.totalTokens / 2_000_000) * 100, }, ] : []; const currentTier = tiers[0]; const usageColor = currentTier ? getUsageColor(currentTier.percentage) : "var(--muted)"; const loadSummary = useCallback(async () => { try { const data = await fetchUsageSummary("30d"); setSummary(data); } catch (err) { console.error("Failed to load usage summary:", err); } finally { setIsLoading(false); } }, []); useEffect(() => { void loadSummary(); }, [loadSummary]); useEffect(() => { function handleClickOutside(event: MouseEvent): void { if (popoverRef.current && !popoverRef.current.contains(event.target as Node)) { setPopoverOpen(false); } } if (!popoverOpen) { return; } document.addEventListener("mousedown", handleClickOutside); return (): void => { document.removeEventListener("mousedown", handleClickOutside); }; }, [popoverOpen]); const pct = currentTier ? Math.min(currentTier.percentage, 100) : 0; return (
{popoverOpen && (
Token Usage
{isLoading ? (
Loading usage data…
) : summary ? ( <>
Total Tokens {formatTokens(summary.totalTokens)}
Estimated Cost ${summary.totalCost.toFixed(2)}
Tasks {summary.taskCount}
{tiers.map((tier) => { const tierPct = Math.min(tier.percentage, 100); return (
{tier.name} {formatTokens(tier.tokens)} / {formatTokens(tier.limit)}
); })}
{ setPopoverOpen(false); }} style={{ display: "block", marginTop: 12, paddingTop: 8, borderTop: "1px solid var(--border)", fontSize: "0.75rem", color: "var(--primary)", textDecoration: "none", textAlign: "center", }} onMouseEnter={(e): void => { (e.currentTarget as HTMLAnchorElement).style.textDecoration = "underline"; }} onMouseLeave={(e): void => { (e.currentTarget as HTMLAnchorElement).style.textDecoration = "none"; }} > View detailed usage → ) : (
No usage data available
)}
)}
); }