2026-02-21 02:36:06 -05:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect, useCallback } from "react";
|
|
|
|
|
import PropTypes from "prop-types";
|
|
|
|
|
import {
|
|
|
|
|
AreaChart,
|
|
|
|
|
Area,
|
|
|
|
|
XAxis,
|
|
|
|
|
YAxis,
|
|
|
|
|
CartesianGrid,
|
|
|
|
|
Tooltip,
|
|
|
|
|
ResponsiveContainer,
|
|
|
|
|
Legend,
|
|
|
|
|
} from "recharts";
|
|
|
|
|
import Card from "@/shared/components/Card";
|
|
|
|
|
|
|
|
|
|
const fmtTokens = (n) => {
|
|
|
|
|
if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`;
|
|
|
|
|
if (n >= 1000) return `${(n / 1000).toFixed(1)}K`;
|
|
|
|
|
return String(n || 0);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fmtCost = (n) => `$${(n || 0).toFixed(4)}`;
|
|
|
|
|
|
2026-03-03 04:19:44 -05:00
|
|
|
export default function UsageChart({ period = "7d" }) {
|
2026-02-21 02:36:06 -05:00
|
|
|
const [data, setData] = useState([]);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
const [viewMode, setViewMode] = useState("tokens");
|
|
|
|
|
|
|
|
|
|
const fetchData = useCallback(async () => {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`/api/usage/chart?period=${period}`);
|
|
|
|
|
if (res.ok) {
|
|
|
|
|
const json = await res.json();
|
|
|
|
|
setData(json);
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error("Failed to fetch chart data:", e);
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
}, [period]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
fetchData();
|
|
|
|
|
}, [fetchData]);
|
|
|
|
|
|
|
|
|
|
const hasData = data.some((d) => d.tokens > 0 || d.cost > 0);
|
|
|
|
|
|
|
|
|
|
return (
|
2026-05-01 05:34:07 -04:00
|
|
|
<Card className="flex min-w-0 flex-col gap-3 p-3 sm:p-4">
|
|
|
|
|
<div className="grid w-full grid-cols-2 items-center gap-1 rounded-lg border border-border bg-bg-subtle p-1 sm:w-auto sm:self-start">
|
2026-03-03 04:19:44 -05:00
|
|
|
<button
|
|
|
|
|
onClick={() => setViewMode("tokens")}
|
|
|
|
|
className={`px-3 py-1 rounded-md text-sm font-medium transition-colors ${viewMode === "tokens" ? "bg-primary text-white shadow-sm" : "text-text-muted hover:text-text hover:bg-bg-hover"}`}
|
|
|
|
|
>
|
|
|
|
|
Tokens
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setViewMode("cost")}
|
|
|
|
|
className={`px-3 py-1 rounded-md text-sm font-medium transition-colors ${viewMode === "cost" ? "bg-primary text-white shadow-sm" : "text-text-muted hover:text-text hover:bg-bg-hover"}`}
|
|
|
|
|
>
|
|
|
|
|
Cost
|
|
|
|
|
</button>
|
2026-02-21 02:36:06 -05:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{loading ? (
|
|
|
|
|
<div className="h-48 flex items-center justify-center text-text-muted text-sm">Loading...</div>
|
|
|
|
|
) : !hasData ? (
|
|
|
|
|
<div className="h-48 flex items-center justify-center text-text-muted text-sm">No data for this period</div>
|
|
|
|
|
) : (
|
2026-05-01 05:34:07 -04:00
|
|
|
<ResponsiveContainer width="100%" height={220}>
|
2026-02-21 02:36:06 -05:00
|
|
|
<AreaChart data={data} margin={{ top: 4, right: 8, left: 0, bottom: 0 }}>
|
|
|
|
|
<defs>
|
|
|
|
|
<linearGradient id="gradTokens" x1="0" y1="0" x2="0" y2="1">
|
|
|
|
|
<stop offset="5%" stopColor="#6366f1" stopOpacity={0.25} />
|
|
|
|
|
<stop offset="95%" stopColor="#6366f1" stopOpacity={0} />
|
|
|
|
|
</linearGradient>
|
|
|
|
|
<linearGradient id="gradCost" x1="0" y1="0" x2="0" y2="1">
|
|
|
|
|
<stop offset="5%" stopColor="#f59e0b" stopOpacity={0.25} />
|
|
|
|
|
<stop offset="95%" stopColor="#f59e0b" stopOpacity={0} />
|
|
|
|
|
</linearGradient>
|
|
|
|
|
</defs>
|
|
|
|
|
<CartesianGrid strokeDasharray="3 3" strokeOpacity={0.1} />
|
|
|
|
|
<XAxis
|
|
|
|
|
dataKey="label"
|
|
|
|
|
tick={{ fontSize: 10, fill: "currentColor", fillOpacity: 0.5 }}
|
|
|
|
|
tickLine={false}
|
|
|
|
|
axisLine={false}
|
|
|
|
|
interval="preserveStartEnd"
|
|
|
|
|
/>
|
|
|
|
|
<YAxis
|
|
|
|
|
tick={{ fontSize: 10, fill: "currentColor", fillOpacity: 0.5 }}
|
|
|
|
|
tickLine={false}
|
|
|
|
|
axisLine={false}
|
|
|
|
|
tickFormatter={viewMode === "tokens" ? fmtTokens : fmtCost}
|
|
|
|
|
width={50}
|
|
|
|
|
/>
|
|
|
|
|
<Tooltip
|
|
|
|
|
contentStyle={{
|
|
|
|
|
backgroundColor: "var(--color-bg)",
|
|
|
|
|
border: "1px solid var(--color-border)",
|
|
|
|
|
borderRadius: "8px",
|
|
|
|
|
fontSize: "12px",
|
|
|
|
|
}}
|
|
|
|
|
formatter={(value, name) =>
|
|
|
|
|
name === "tokens" ? [fmtTokens(value), "Tokens"] : [fmtCost(value), "Cost"]
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
{viewMode === "tokens" ? (
|
|
|
|
|
<Area
|
|
|
|
|
type="monotone"
|
|
|
|
|
dataKey="tokens"
|
|
|
|
|
stroke="#6366f1"
|
|
|
|
|
strokeWidth={2}
|
|
|
|
|
fill="url(#gradTokens)"
|
|
|
|
|
dot={false}
|
|
|
|
|
activeDot={{ r: 4 }}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<Area
|
|
|
|
|
type="monotone"
|
|
|
|
|
dataKey="cost"
|
|
|
|
|
stroke="#f59e0b"
|
|
|
|
|
strokeWidth={2}
|
|
|
|
|
fill="url(#gradCost)"
|
|
|
|
|
dot={false}
|
|
|
|
|
activeDot={{ r: 4 }}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</AreaChart>
|
|
|
|
|
</ResponsiveContainer>
|
|
|
|
|
)}
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-03 04:19:44 -05:00
|
|
|
UsageChart.propTypes = {
|
|
|
|
|
period: PropTypes.string,
|
|
|
|
|
};
|