"use client"; import { useState, useEffect, useCallback } from "react"; import type { ReactElement } from "react"; import { LineChart, Line, BarChart, Bar, PieChart, Pie, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, } from "recharts"; import { Card, CardHeader, CardContent, CardTitle, CardDescription } from "@/components/ui/card"; import { fetchUsageSummary, fetchTokenUsage, fetchCostBreakdown, fetchTaskOutcomes, } from "@/lib/api/telemetry"; import type { TimeRange, UsageSummary, TokenUsagePoint, CostBreakdownItem, TaskOutcomeItem, } from "@/lib/api/telemetry"; // ─── Constants ─────────────────────────────────────────────────────── const TIME_RANGES: { value: TimeRange; label: string }[] = [ { value: "7d", label: "7 Days" }, { value: "30d", label: "30 Days" }, { value: "90d", label: "90 Days" }, ]; // Calm, PDA-friendly chart colors (no aggressive reds) const CHART_COLORS = { inputTokens: "#6366F1", // Indigo outputTokens: "#38BDF8", // Sky blue grid: "#E2E8F0", // Slate 200 barFill: "#818CF8", // Indigo 400 }; // ─── Helpers ───────────────────────────────────────────────────────── function formatNumber(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); } function formatCurrency(value: number): string { return `$${value.toFixed(2)}`; } function formatPercent(value: number): string { return `${(value * 100).toFixed(1)}%`; } function formatDateLabel(dateStr: string): string { const date = new Date(dateStr + "T00:00:00"); return date.toLocaleDateString("en-US", { month: "short", day: "numeric" }); } /** * Map TaskOutcomeItem[] to recharts-compatible data with `fill` property. * This replaces deprecated Cell component (removed in Recharts 4.0). */ function toFillData( outcomes: TaskOutcomeItem[] ): { outcome: string; count: number; fill: string }[] { return outcomes.map((item) => ({ outcome: item.outcome, count: item.count, fill: item.color, })); } // ─── Sub-components ────────────────────────────────────────────────── function SummaryCard({ title, value, subtitle, }: { title: string; value: string; subtitle?: string; }): ReactElement { return (

{title}

{value}

{subtitle ?

{subtitle}

: null}
); } function LoadingSkeleton(): ReactElement { return (
{/* Summary cards skeleton */}
{Array.from({ length: 4 }).map((_, i) => (
))}
{/* Chart skeletons */}
{Array.from({ length: 3 }).map((_, i) => (
))}
); } function EmptyState(): ReactElement { return (
📊

No usage data yet

Once you start using AI-powered features, your token usage and cost data will appear here.

); } // ─── Main Page Component ───────────────────────────────────────────── export default function UsagePage(): ReactElement { const [timeRange, setTimeRange] = useState("30d"); const [isLoading, setIsLoading] = useState(true); const [isEmpty, setIsEmpty] = useState(false); const [error, setError] = useState(null); const [summary, setSummary] = useState(null); const [tokenUsage, setTokenUsage] = useState([]); const [costBreakdown, setCostBreakdown] = useState([]); const [taskOutcomes, setTaskOutcomes] = useState([]); const loadData = useCallback(async (range: TimeRange): Promise => { setIsLoading(true); setError(null); try { const [summaryData, tokenData, costData, outcomeData] = await Promise.all([ fetchUsageSummary(range), fetchTokenUsage(range), fetchCostBreakdown(range), fetchTaskOutcomes(range), ]); setSummary(summaryData); setTokenUsage(tokenData); setCostBreakdown(costData); setTaskOutcomes(outcomeData); // Check if there's any meaningful data setIsEmpty(summaryData.taskCount === 0); } catch (err) { setError( err instanceof Error ? err.message : "We had trouble loading usage data. Please try again when you're ready." ); } finally { setIsLoading(false); } }, []); useEffect(() => { void loadData(timeRange); }, [timeRange, loadData]); function handleTimeRangeChange(range: TimeRange): void { setTimeRange(range); } return (
{/* Header */}

Usage

Token usage and cost overview

{/* Time range selector */}
{TIME_RANGES.map(({ value, label }) => ( ))}
{/* Error state */} {error !== null ? (

{error}

) : isLoading ? ( ) : isEmpty ? ( ) : (
{/* Summary Cards */}
{/* Charts */}
{/* Token Usage Over Time — Full width */} Token Usage Over Time Input and output tokens by day
[ formatNumber(value), name === "inputTokens" ? "Input Tokens" : "Output Tokens", ]) as never } labelFormatter={((label: string) => formatDateLabel(label)) as never} contentStyle={{ borderRadius: "8px", border: "1px solid #E2E8F0", boxShadow: "0 2px 8px rgba(0,0,0,0.08)", }} /> value === "inputTokens" ? "Input Tokens" : "Output Tokens" } />
{/* Cost Breakdown by Model */} Cost by Model Estimated cost breakdown
formatCurrency(v)} tick={{ fontSize: 12, fill: "#64748B" }} /> [formatCurrency(value), "Cost"]) as never} contentStyle={{ borderRadius: "8px", border: "1px solid #E2E8F0", boxShadow: "0 2px 8px rgba(0,0,0,0.08)", }} />
{/* Task Outcomes */} Task Outcomes Distribution of task completion results
`${props.outcome ?? ""}: ${String(props.count ?? 0)}`) as never } /> [value, name]) as never} contentStyle={{ borderRadius: "8px", border: "1px solid #E2E8F0", boxShadow: "0 2px 8px rgba(0,0,0,0.08)", }} />
)}
); }