1"use client";
2
3import React, { useState } from "react";
4import {
5 Bar,
6 BarChart,
7 CartesianGrid,
8 ResponsiveContainer,
9 Tooltip,
10 XAxis,
11 YAxis,
12} from "recharts";
13
14interface ModelData {
15 name: string;
16 value: number;
17 color: string;
18}
19
20interface ChartDataPoint {
21 date: string;
22 Others: number;
23 "Grok Code Fast 1": number;
24 "Claude Sonnet 4": number;
25 "Gemini 2.5 Flash": number;
26 "Sonoma Sky Alpha": number;
27 "Gemini 2.0 Flash": number;
28 "DeepSeek V3.1 (free)": number;
29 "Grok 4 Fast (free)": number;
30 "GPT-4.1 Mini": number;
31 "DeepSeek V3 0324": number;
32 [key: string]: string | number;
33}
34
35const chartData: ChartDataPoint[] = [
36 {
37 date: "21 Oct 2024",
38 Others: 50,
39 "Grok Code Fast 1": 30,
40 "Claude Sonnet 4": 15,
41 "Gemini 2.5 Flash": 10,
42 "Sonoma Sky Alpha": 8,
43 "Gemini 2.0 Flash": 6,
44 "DeepSeek V3.1 (free)": 5,
45 "Grok 4 Fast (free)": 4,
46 "GPT-4.1 Mini": 4,
47 "DeepSeek V3 0324": 3,
48 },
49 {
50 date: "25 Nov",
51 Others: 80,
52 "Grok Code Fast 1": 45,
53 "Claude Sonnet 4": 25,
54 "Gemini 2.5 Flash": 18,
55 "Sonoma Sky Alpha": 12,
56 "Gemini 2.0 Flash": 10,
57 "DeepSeek V3.1 (free)": 8,
58 "Grok 4 Fast (free)": 7,
59 "GPT-4.1 Mini": 6,
60 "DeepSeek V3 0324": 5,
61 },
62 {
63 date: "30 Dec",
64 Others: 120,
65 "Grok Code Fast 1": 70,
66 "Claude Sonnet 4": 38,
67 "Gemini 2.5 Flash": 28,
68 "Sonoma Sky Alpha": 20,
69 "Gemini 2.0 Flash": 16,
70 "DeepSeek V3.1 (free)": 14,
71 "Grok 4 Fast (free)": 12,
72 "GPT-4.1 Mini": 10,
73 "DeepSeek V3 0324": 8,
74 },
75 {
76 date: "3 Feb",
77 Others: 180,
78 "Grok Code Fast 1": 95,
79 "Claude Sonnet 4": 52,
80 "Gemini 2.5 Flash": 38,
81 "Sonoma Sky Alpha": 28,
82 "Gemini 2.0 Flash": 22,
83 "DeepSeek V3.1 (free)": 20,
84 "Grok 4 Fast (free)": 17,
85 "GPT-4.1 Mini": 15,
86 "DeepSeek V3 0324": 12,
87 },
88 {
89 date: "10 Mar",
90 Others: 280,
91 "Grok Code Fast 1": 160,
92 "Claude Sonnet 4": 85,
93 "Gemini 2.5 Flash": 58,
94 "Sonoma Sky Alpha": 42,
95 "Gemini 2.0 Flash": 34,
96 "DeepSeek V3.1 (free)": 30,
97 "Grok 4 Fast (free)": 26,
98 "GPT-4.1 Mini": 23,
99 "DeepSeek V3 0324": 19,
100 },
101 {
102 date: "14 Apr",
103 Others: 380,
104 "Grok Code Fast 1": 220,
105 "Claude Sonnet 4": 118,
106 "Gemini 2.5 Flash": 78,
107 "Sonoma Sky Alpha": 56,
108 "Gemini 2.0 Flash": 45,
109 "DeepSeek V3.1 (free)": 40,
110 "Grok 4 Fast (free)": 35,
111 "GPT-4.1 Mini": 31,
112 "DeepSeek V3 0324": 26,
113 },
114 {
115 date: "19 May",
116 Others: 520,
117 "Grok Code Fast 1": 310,
118 "Claude Sonnet 4": 165,
119 "Gemini 2.5 Flash": 108,
120 "Sonoma Sky Alpha": 78,
121 "Gemini 2.0 Flash": 62,
122 "DeepSeek V3.1 (free)": 56,
123 "Grok 4 Fast (free)": 48,
124 "GPT-4.1 Mini": 43,
125 "DeepSeek V3 0324": 36,
126 },
127 {
128 date: "23 Jun",
129 Others: 680,
130 "Grok Code Fast 1": 420,
131 "Claude Sonnet 4": 225,
132 "Gemini 2.5 Flash": 148,
133 "Sonoma Sky Alpha": 106,
134 "Gemini 2.0 Flash": 84,
135 "DeepSeek V3.1 (free)": 76,
136 "Grok 4 Fast (free)": 65,
137 "GPT-4.1 Mini": 58,
138 "DeepSeek V3 0324": 49,
139 },
140 {
141 date: "28 Jul",
142 Others: 850,
143 "Grok Code Fast 1": 540,
144 "Claude Sonnet 4": 290,
145 "Gemini 2.5 Flash": 190,
146 "Sonoma Sky Alpha": 136,
147 "Gemini 2.0 Flash": 108,
148 "DeepSeek V3.1 (free)": 98,
149 "Grok 4 Fast (free)": 84,
150 "GPT-4.1 Mini": 75,
151 "DeepSeek V3 0324": 63,
152 },
153 {
154 date: "1 Sept",
155 Others: 1050,
156 "Grok Code Fast 1": 680,
157 "Claude Sonnet 4": 365,
158 "Gemini 2.5 Flash": 240,
159 "Sonoma Sky Alpha": 172,
160 "Gemini 2.0 Flash": 136,
161 "DeepSeek V3.1 (free)": 124,
162 "Grok 4 Fast (free)": 106,
163 "GPT-4.1 Mini": 94,
164 "DeepSeek V3 0324": 79,
165 },
166 {
167 date: "15 September 2025",
168 Others: 1810,
169 "Grok Code Fast 1": 1150,
170 "Claude Sonnet 4": 586,
171 "Gemini 2.5 Flash": 325,
172 "Sonoma Sky Alpha": 227,
173 "Gemini 2.0 Flash": 187,
174 "DeepSeek V3.1 (free)": 180,
175 "Grok 4 Fast (free)": 158,
176 "GPT-4.1 Mini": 157,
177 "DeepSeek V3 0324": 142,
178 },
179 {
180 date: "6 Oct",
181 Others: 1650,
182 "Grok Code Fast 1": 1050,
183 "Claude Sonnet 4": 535,
184 "Gemini 2.5 Flash": 296,
185 "Sonoma Sky Alpha": 207,
186 "Gemini 2.0 Flash": 170,
187 "DeepSeek V3.1 (free)": 164,
188 "Grok 4 Fast (free)": 144,
189 "GPT-4.1 Mini": 143,
190 "DeepSeek V3 0324": 129,
191 },
192];
193
194const models = [
195 { key: "DeepSeek V3 0324", color: "#FFA07A" },
196 { key: "GPT-4.1 Mini", color: "#FFB6C1" },
197 { key: "Grok 4 Fast (free)", color: "#DDA0DD" },
198 { key: "DeepSeek V3.1 (free)", color: "#87CEEB" },
199 { key: "Gemini 2.0 Flash", color: "#98FB98" },
200 { key: "Sonoma Sky Alpha", color: "#F0E68C" },
201 { key: "Gemini 2.5 Flash", color: "#FFDAB9" },
202 { key: "Claude Sonnet 4", color: "#DEB887" },
203 { key: "Grok Code Fast 1", color: "#CD853F" },
204 { key: "Others", color: "#FF69B4" },
205];
206
207const formatValue = (value: number): string => {
208 if (value >= 1000) {
209 return `${(value / 1000).toFixed(2)}T`;
210 }
211 return `${value}B`;
212};
213
214interface TooltipProps {
215 active?: boolean;
216 payload?: Array<{
217 payload: ChartDataPoint;
218 }>;
219}
220
221const CustomTooltip = ({ active, payload }: TooltipProps) => {
222 if (active && payload && payload.length) {
223 const data = payload[0].payload;
224 const total = models.reduce(
225 (sum, model) => {
226 const value = data[model.key];
227 return sum + (typeof value === 'number' ? value : 0);
228 },
229 0
230 );
231
232 const modelData: ModelData[] = models.map((model) => {
233 const value = data[model.key];
234 return {
235 name: model.key,
236 value: typeof value === 'number' ? value : 0,
237 color: model.color,
238 };
239 });
240
241 return (
242 <div className="bg-primary/90 rounded-lg p-4 shadow-xl min-w-[240px]">
243 <div className="text-xs text-gray-400 mb-3 pb-2">
244 {data.date}
245 </div>
246 <div className="space-y-2">
247 {modelData.map((model, idx) => (
248 <div key={idx} className="flex items-center justify-between gap-4">
249 <div className="flex items-center gap-2 min-w-0">
250 <div
251 className="w-2 h-2 rounded-sm flex-shrink-0"
252 style={{ backgroundColor: model.color }}
253 />
254 <span className="text-xs text-gray-300 truncate">
255 {model.name}
256 </span>
257 </div>
258 <span className="text-xs font-medium text-white flex-shrink-0">
259 {formatValue(model.value)}
260 </span>
261 </div>
262 ))}
263 <div className="pt-2 mt-2 border-t border-gray-800">
264 <div className="flex items-center justify-between">
265 <span className="text-xs font-medium text-white">Total</span>
266 <span className="text-xs font-bold text-white">
267 {formatValue(total)}
268 </span>
269 </div>
270 </div>
271 </div>
272 </div>
273 );
274 }
275
276 return null;
277};
278
279export default function TokenLeaderboardChart() {
280 const [timeFilter] = useState("Top this week");
281
282 return (
283 <div className="flex items-center justify-center min-h-screen bg-primary p-8">
284 <div className="w-full max-w-6xl bg-[#0F0F0F] rounded-2xl p-8">
285 {/* Header */}
286 <div className="flex items-center justify-between mb-2">
287 <div className="flex items-center gap-3">
288 <svg
289 className="w-5 h-5 text-gray-400"
290 fill="none"
291 stroke="currentColor"
292 viewBox="0 0 24 24"
293 >
294 <path
295 strokeLinecap="round"
296 strokeLinejoin="round"
297 strokeWidth={2}
298 d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
299 />
300 </svg>
301 <h2 className="text-xl font-semibold text-white">Leaderboard</h2>
302 </div>
303 <button className="flex items-center gap-2 px-4 py-2 text-sm text-gray-300 hover:text-white bg-[#1A1A1A] hover:bg-[#252525] border border-gray-800 rounded-lg transition-colors">
304 {timeFilter}
305 <svg
306 className="w-4 h-4"
307 fill="none"
308 stroke="currentColor"
309 viewBox="0 0 24 24"
310 >
311 <path
312 strokeLinecap="round"
313 strokeLinejoin="round"
314 strokeWidth={2}
315 d="M19 9l-7 7-7-7"
316 />
317 </svg>
318 </button>
319 </div>
320
321 {/* Subtitle */}
322 <div className="flex items-center gap-2 mb-6">
323 <p className="text-sm text-primary-foreground">
324 Token usage across models on OpenRouter
325 </p>
326 <button className="text-primary-foreground hover:text-gray-400">
327 <svg
328 className="w-4 h-4"
329 fill="none"
330 stroke="currentColor"
331 viewBox="0 0 24 24"
332 >
333 <path
334 strokeLinecap="round"
335 strokeLinejoin="round"
336 strokeWidth={2}
337 d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
338 />
339 </svg>
340 </button>
341 </div>
342
343 {/* Chart */}
344 <div className="h-[450px] w-full">
345 <ResponsiveContainer width="100%" height="100%">
346 <BarChart data={chartData} barGap={2}>
347 <CartesianGrid
348 strokeDasharray="3 3"
349 stroke="#1F1F1F"
350 vertical={false}
351 />
352 <XAxis
353 dataKey="date"
354 stroke="#666"
355 tick={{ fontSize: 11, fill: "#999" }}
356 tickLine={false}
357 axisLine={{ stroke: "#1F1F1F" }}
358 />
359 <YAxis
360 stroke="#666"
361 tick={{ fontSize: 11, fill: "#999" }}
362 tickLine={false}
363 axisLine={{ stroke: "#1F1F1F" }}
364 tickFormatter={(value) => `${value / 1000}T`}
365 />
366 <Tooltip content={<CustomTooltip />} cursor={false} />
367 {models.map((model) => (
368 <Bar
369 key={model.key}
370 dataKey={model.key}
371 stackId="a"
372 fill={model.color}
373 radius={0}
374 />
375 ))}
376 </BarChart>
377 </ResponsiveContainer>
378 </div>
379 </div>
380 </div>
381 );
382}
383