2026-06-10 14:38:44 +02:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
PieChart,
|
|
|
|
|
Pie,
|
|
|
|
|
Cell,
|
|
|
|
|
Tooltip,
|
|
|
|
|
ResponsiveContainer,
|
|
|
|
|
Legend,
|
|
|
|
|
} from "recharts";
|
|
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
|
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
2026-07-15 18:04:00 +02:00
|
|
|
import { HelpTip } from "@/components/ui/help-tip";
|
2026-07-03 19:24:00 +02:00
|
|
|
import { useIsMobile } from "@/hooks/use-is-mobile";
|
2026-07-20 20:29:45 +02:00
|
|
|
import { chartTooltipStyle } from "@/components/charts/chart-tooltip";
|
2026-06-10 14:38:44 +02:00
|
|
|
import type { ModelUsageSlice } from "@/types";
|
|
|
|
|
|
2026-06-17 06:36:28 +02:00
|
|
|
// Design-system chart tokens — resolves to theme-aware palette
|
2026-06-10 14:38:44 +02:00
|
|
|
const CHART_COLORS = [
|
|
|
|
|
"var(--chart-1)",
|
|
|
|
|
"var(--chart-2)",
|
|
|
|
|
"var(--chart-3)",
|
|
|
|
|
"var(--chart-4)",
|
|
|
|
|
"var(--chart-5)",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
interface ModelUsageDonutProps {
|
|
|
|
|
data: ModelUsageSlice[] | undefined;
|
|
|
|
|
isLoading: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function ModelUsageDonut({ data, isLoading }: ModelUsageDonutProps) {
|
2026-07-03 19:24:00 +02:00
|
|
|
const isMobile = useIsMobile();
|
2026-06-10 14:38:44 +02:00
|
|
|
const chartData = (data ?? []).map((s) => ({
|
|
|
|
|
name: s.model,
|
|
|
|
|
value: s.total_tokens,
|
|
|
|
|
cost: s.cost_usd,
|
|
|
|
|
pct: s.pct_of_total,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader className="pb-2">
|
2026-07-15 18:04:00 +02:00
|
|
|
<HelpTip label="Share of total tokens consumed per model in the selected window — hover a slice for exact tokens and cost">
|
|
|
|
|
<CardTitle className="text-base">By Model</CardTitle>
|
|
|
|
|
</HelpTip>
|
2026-06-10 14:38:44 +02:00
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
{isLoading ? (
|
|
|
|
|
<Skeleton className="h-52 w-full" />
|
2026-07-18 00:42:13 +02:00
|
|
|
) : chartData.length === 0 ? (
|
|
|
|
|
<p className="text-sm text-muted-foreground py-16 text-center">
|
|
|
|
|
No usage recorded in this window yet.
|
|
|
|
|
</p>
|
2026-06-10 14:38:44 +02:00
|
|
|
) : (
|
|
|
|
|
<ResponsiveContainer width="100%" height={208}>
|
|
|
|
|
<PieChart>
|
|
|
|
|
<Pie
|
|
|
|
|
data={chartData}
|
|
|
|
|
cx="50%"
|
|
|
|
|
cy="50%"
|
2026-07-03 19:24:00 +02:00
|
|
|
innerRadius={isMobile ? 44 : 52}
|
|
|
|
|
outerRadius={isMobile ? 68 : 80}
|
2026-06-10 14:38:44 +02:00
|
|
|
dataKey="value"
|
|
|
|
|
paddingAngle={3}
|
|
|
|
|
>
|
|
|
|
|
{chartData.map((_, idx) => (
|
|
|
|
|
<Cell
|
|
|
|
|
key={idx}
|
|
|
|
|
fill={CHART_COLORS[idx % CHART_COLORS.length]}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</Pie>
|
|
|
|
|
<Tooltip
|
2026-07-20 20:29:45 +02:00
|
|
|
{...chartTooltipStyle}
|
2026-06-10 14:38:44 +02:00
|
|
|
formatter={(value, name) => [
|
|
|
|
|
(typeof value === "number" ? value : 0).toLocaleString() +
|
|
|
|
|
" tokens",
|
|
|
|
|
name,
|
|
|
|
|
]}
|
|
|
|
|
/>
|
2026-07-03 19:24:00 +02:00
|
|
|
<Legend wrapperStyle={{ fontSize: isMobile ? 9 : 11 }} />
|
2026-06-10 14:38:44 +02:00
|
|
|
</PieChart>
|
|
|
|
|
</ResponsiveContainer>
|
|
|
|
|
)}
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
|
|
|
|
}
|