Commit
·
f23b928
1
Parent(s):
f39f6c2
- src/App.tsx +85 -79
- src/components/BenchmarkTable.tsx +9 -19
- src/components/PricingTable.tsx +6 -5
src/App.tsx
CHANGED
@@ -1,8 +1,6 @@
|
|
1 |
import React, { useState, useEffect, useMemo } from "react";
|
2 |
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
3 |
import { Input } from "@/components/ui/input";
|
4 |
-
import { Switch } from "@/components/ui/switch";
|
5 |
-
|
6 |
import { mockData } from "@/lib/data";
|
7 |
|
8 |
import { ComparisonSelector } from "@/components/ComparisonSelector";
|
@@ -42,12 +40,9 @@ const App: React.FC = () => {
|
|
42 |
const [selectedModels, setSelectedModels] = useState<string[]>([]);
|
43 |
const [expandedProviders, setExpandedProviders] = useState<string[]>([]);
|
44 |
const [tokenCalculation, setTokenCalculation] = useState<string>("million");
|
45 |
-
const [linkProviderModel, setLinkProviderModel] = useState<boolean>(false);
|
46 |
const [benchmarkComparisonMetrics, setBenchmarkComparisonMetrics] = useState<string[]>([]);
|
47 |
const [selectedBenchmarkProviders, setSelectedBenchmarkProviders] = useState<string[]>([]);
|
48 |
const [selectedBenchmarkModels, setSelectedBenchmarkModels] = useState<string[]>([]);
|
49 |
-
const [linkBenchmarkProviderModel, setLinkBenchmarkProviderModel] = useState<boolean>(false);
|
50 |
-
|
51 |
|
52 |
const [sortConfig, setSortConfig] = useState<{
|
53 |
key: keyof FlattenedModel;
|
@@ -65,74 +60,70 @@ const App: React.FC = () => {
|
|
65 |
}, []);
|
66 |
|
67 |
|
68 |
-
const flattenDataFromPricing = (data: Provider[]): FlattenedModel[] =>
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
|
78 |
-
const flattenDataFromBenchmarks = (): FlattenedModel[] =>
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
|
88 |
|
89 |
|
90 |
const filteredData = useMemo(() => {
|
91 |
-
if (!selectedProviders.length && !selectedModels.length
|
92 |
|
93 |
return data
|
94 |
.filter((p) => !selectedProviders.length || selectedProviders.includes(p.provider))
|
95 |
.map((p) => ({
|
96 |
...p,
|
97 |
models: p.models.filter((m) => {
|
98 |
-
if (
|
99 |
-
if (!
|
100 |
return selectedModels.includes(m.name);
|
101 |
}),
|
102 |
}))
|
103 |
.filter((p) => p.models.length > 0);
|
104 |
-
}, [data, selectedProviders, selectedModels
|
105 |
|
106 |
-
const benchmarkedModels = useMemo(() => {
|
107 |
-
|
108 |
-
}, []);
|
109 |
|
110 |
|
111 |
|
112 |
-
const filteredBenchmarkedModels = useMemo(() => {
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
|
119 |
-
|
120 |
-
|
121 |
-
|
|
|
122 |
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
|
|
|
|
127 |
|
128 |
-
|
129 |
-
});
|
130 |
-
}, [
|
131 |
-
benchmarkedModels,
|
132 |
-
selectedBenchmarkProviders,
|
133 |
-
selectedBenchmarkModels,
|
134 |
-
linkBenchmarkProviderModel,
|
135 |
-
]);
|
136 |
|
137 |
|
138 |
const sortedBenchmarkedModels = useMemo(() => {
|
@@ -159,21 +150,49 @@ const filteredBenchmarkedModels = useMemo(() => {
|
|
159 |
});
|
160 |
}, [filteredBenchmarkedModels, benchmarkSortConfig]);
|
161 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
162 |
|
163 |
const benchmarkProviders = useMemo(() => {
|
164 |
-
const
|
165 |
-
|
166 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
167 |
|
168 |
-
return data
|
169 |
-
.map((provider) => ({
|
170 |
-
...provider,
|
171 |
-
models: provider.models.filter((model) =>
|
172 |
-
modelsInBenchmark.has(`${provider.provider}:${model.name}`)
|
173 |
-
),
|
174 |
-
}))
|
175 |
-
.filter((p) => p.models.length > 0);
|
176 |
-
}, [data, benchmarkedModels]);
|
177 |
|
178 |
|
179 |
|
@@ -262,17 +281,11 @@ const filteredBenchmarkedModels = useMemo(() => {
|
|
262 |
</div>
|
263 |
</div>
|
264 |
|
265 |
-
{/* Provider–Model Toggle */}
|
266 |
-
<div className="flex items-center space-x-2 mb-6">
|
267 |
-
<Switch id="linkProviderModel" checked={linkProviderModel} onCheckedChange={setLinkProviderModel} />
|
268 |
-
<label htmlFor="linkProviderModel" className="text-sm">Link Provider and Model</label>
|
269 |
-
</div>
|
270 |
-
|
271 |
{/* Pricing Table */}
|
272 |
<h2 className="text-lg font-semibold mb-2">Pricing Table</h2>
|
273 |
-
|
274 |
data={sortedFlattenedData}
|
275 |
-
providers={
|
276 |
selectedProviders={selectedProviders}
|
277 |
selectedModels={selectedModels}
|
278 |
onProviderChange={setSelectedProviders}
|
@@ -283,8 +296,7 @@ const filteredBenchmarkedModels = useMemo(() => {
|
|
283 |
tokenCalculation={tokenCalculation}
|
284 |
requestSort={requestSort}
|
285 |
sortConfig={sortConfig}
|
286 |
-
|
287 |
-
/>
|
288 |
|
289 |
|
290 |
{/* Benchmark Table */}
|
@@ -299,10 +311,6 @@ const filteredBenchmarkedModels = useMemo(() => {
|
|
299 |
}
|
300 |
/>
|
301 |
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
<h2 className="text-lg font-semibold mt-12 mb-2">Benchmark Table</h2>
|
307 |
<BenchmarkTable
|
308 |
data={sortedBenchmarkedModels}
|
@@ -320,8 +328,6 @@ const filteredBenchmarkedModels = useMemo(() => {
|
|
320 |
selectedModels={selectedBenchmarkModels}
|
321 |
onProviderChange={setSelectedBenchmarkProviders}
|
322 |
onModelChange={setSelectedBenchmarkModels}
|
323 |
-
linkProviderModel={linkBenchmarkProviderModel}
|
324 |
-
onLinkToggle={setLinkBenchmarkProviderModel}
|
325 |
/>
|
326 |
|
327 |
|
|
|
1 |
import React, { useState, useEffect, useMemo } from "react";
|
2 |
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
3 |
import { Input } from "@/components/ui/input";
|
|
|
|
|
4 |
import { mockData } from "@/lib/data";
|
5 |
|
6 |
import { ComparisonSelector } from "@/components/ComparisonSelector";
|
|
|
40 |
const [selectedModels, setSelectedModels] = useState<string[]>([]);
|
41 |
const [expandedProviders, setExpandedProviders] = useState<string[]>([]);
|
42 |
const [tokenCalculation, setTokenCalculation] = useState<string>("million");
|
|
|
43 |
const [benchmarkComparisonMetrics, setBenchmarkComparisonMetrics] = useState<string[]>([]);
|
44 |
const [selectedBenchmarkProviders, setSelectedBenchmarkProviders] = useState<string[]>([]);
|
45 |
const [selectedBenchmarkModels, setSelectedBenchmarkModels] = useState<string[]>([]);
|
|
|
|
|
46 |
|
47 |
const [sortConfig, setSortConfig] = useState<{
|
48 |
key: keyof FlattenedModel;
|
|
|
60 |
}, []);
|
61 |
|
62 |
|
63 |
+
const flattenDataFromPricing = (data: Provider[]): FlattenedModel[] =>
|
64 |
+
data.flatMap((provider) =>
|
65 |
+
provider.models.map((model) => ({
|
66 |
+
provider: provider.provider,
|
67 |
+
uri: provider.uri,
|
68 |
+
...model,
|
69 |
+
benchmark: {},
|
70 |
+
}))
|
71 |
+
);
|
72 |
|
73 |
+
const flattenDataFromBenchmarks = (): FlattenedModel[] =>
|
74 |
+
benchmarkData.map((b) => ({
|
75 |
+
provider: b.provider ?? "Unknown",
|
76 |
+
uri: b.source, // placeholder or use an optional `b.link` if available
|
77 |
+
name: b.model,
|
78 |
+
inputPrice: b.inputPrice,
|
79 |
+
outputPrice: b.outputPrice,
|
80 |
+
benchmark: b.benchmark ?? {},
|
81 |
+
}));
|
82 |
|
83 |
|
84 |
|
85 |
const filteredData = useMemo(() => {
|
86 |
+
if (!selectedProviders.length && !selectedModels.length) return data;
|
87 |
|
88 |
return data
|
89 |
.filter((p) => !selectedProviders.length || selectedProviders.includes(p.provider))
|
90 |
.map((p) => ({
|
91 |
...p,
|
92 |
models: p.models.filter((m) => {
|
93 |
+
if (!selectedModels.length) return selectedProviders.includes(p.provider);
|
94 |
+
if (!selectedModels.length) return !selectedProviders.length || selectedProviders.includes(p.provider);
|
95 |
return selectedModels.includes(m.name);
|
96 |
}),
|
97 |
}))
|
98 |
.filter((p) => p.models.length > 0);
|
99 |
+
}, [data, selectedProviders, selectedModels]);
|
100 |
|
101 |
+
const benchmarkedModels = useMemo(() => {
|
102 |
+
return flattenDataFromBenchmarks();
|
103 |
+
}, []);
|
104 |
|
105 |
|
106 |
|
107 |
+
const filteredBenchmarkedModels = useMemo(() => {
|
108 |
+
return benchmarkedModels.filter((model) => {
|
109 |
+
const providerMatch =
|
110 |
+
selectedBenchmarkProviders.length === 0 || selectedBenchmarkProviders.includes(model.provider);
|
111 |
+
const modelMatch =
|
112 |
+
selectedBenchmarkModels.length === 0 || selectedBenchmarkModels.includes(model.name);
|
113 |
|
114 |
+
// When not linking, allow filtering by either
|
115 |
+
if (selectedBenchmarkProviders.length > 0 && selectedBenchmarkModels.length > 0) {
|
116 |
+
return providerMatch || modelMatch;
|
117 |
+
}
|
118 |
|
119 |
+
return providerMatch && modelMatch; // this handles the case where one or both are empty
|
120 |
+
});
|
121 |
+
}, [
|
122 |
+
benchmarkedModels,
|
123 |
+
selectedBenchmarkProviders,
|
124 |
+
selectedBenchmarkModels,
|
125 |
|
126 |
+
]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
127 |
|
128 |
|
129 |
const sortedBenchmarkedModels = useMemo(() => {
|
|
|
150 |
});
|
151 |
}, [filteredBenchmarkedModels, benchmarkSortConfig]);
|
152 |
|
153 |
+
const pricingProviders = useMemo(() => {
|
154 |
+
const grouped: Record<string, FlattenedModel[]> = {};
|
155 |
+
|
156 |
+
flattenDataFromPricing(data).forEach((model) => {
|
157 |
+
const key = model.provider;
|
158 |
+
if (!grouped[key]) grouped[key] = [];
|
159 |
+
grouped[key].push(model);
|
160 |
+
});
|
161 |
+
|
162 |
+
return Object.entries(grouped).map(([provider, models]) => ({
|
163 |
+
provider,
|
164 |
+
uri: models[0]?.uri ?? "#",
|
165 |
+
models: models.map(({ name, inputPrice, outputPrice }) => ({
|
166 |
+
name,
|
167 |
+
inputPrice,
|
168 |
+
outputPrice,
|
169 |
+
})),
|
170 |
+
}));
|
171 |
+
}, [data]);
|
172 |
+
|
173 |
|
174 |
const benchmarkProviders = useMemo(() => {
|
175 |
+
const grouped: Record<string, FlattenedModel[]> = {};
|
176 |
+
|
177 |
+
benchmarkedModels.forEach((model) => {
|
178 |
+
const key = model.provider;
|
179 |
+
if (!grouped[key]) grouped[key] = [];
|
180 |
+
grouped[key].push(model);
|
181 |
+
});
|
182 |
+
|
183 |
+
return Object.entries(grouped).map(([provider, models]) => ({
|
184 |
+
provider,
|
185 |
+
uri: models[0]?.uri ?? "#",
|
186 |
+
models: models.map(({ name, inputPrice, outputPrice }) => ({
|
187 |
+
name,
|
188 |
+
inputPrice,
|
189 |
+
outputPrice,
|
190 |
+
})),
|
191 |
+
}));
|
192 |
+
}, [benchmarkedModels]);
|
193 |
+
|
194 |
+
|
195 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
196 |
|
197 |
|
198 |
|
|
|
281 |
</div>
|
282 |
</div>
|
283 |
|
|
|
|
|
|
|
|
|
|
|
|
|
284 |
{/* Pricing Table */}
|
285 |
<h2 className="text-lg font-semibold mb-2">Pricing Table</h2>
|
286 |
+
<PricingTable
|
287 |
data={sortedFlattenedData}
|
288 |
+
providers={pricingProviders}
|
289 |
selectedProviders={selectedProviders}
|
290 |
selectedModels={selectedModels}
|
291 |
onProviderChange={setSelectedProviders}
|
|
|
296 |
tokenCalculation={tokenCalculation}
|
297 |
requestSort={requestSort}
|
298 |
sortConfig={sortConfig}
|
299 |
+
/>
|
|
|
300 |
|
301 |
|
302 |
{/* Benchmark Table */}
|
|
|
311 |
}
|
312 |
/>
|
313 |
|
|
|
|
|
|
|
|
|
314 |
<h2 className="text-lg font-semibold mt-12 mb-2">Benchmark Table</h2>
|
315 |
<BenchmarkTable
|
316 |
data={sortedBenchmarkedModels}
|
|
|
328 |
selectedModels={selectedBenchmarkModels}
|
329 |
onProviderChange={setSelectedBenchmarkProviders}
|
330 |
onModelChange={setSelectedBenchmarkModels}
|
|
|
|
|
331 |
/>
|
332 |
|
333 |
|
src/components/BenchmarkTable.tsx
CHANGED
@@ -8,7 +8,6 @@ import {
|
|
8 |
TableCell,
|
9 |
} from "@/components/ui/table";
|
10 |
import { MultiSelect } from "@/components/ui/multi-select";
|
11 |
-
import { Switch } from "@/components/ui/switch";
|
12 |
import { Provider, FlattenedModel } from "@/App";
|
13 |
|
14 |
interface BenchmarkTableProps {
|
@@ -24,10 +23,9 @@ interface BenchmarkTableProps {
|
|
24 |
selectedModels: string[];
|
25 |
onProviderChange: (values: string[]) => void;
|
26 |
onModelChange: (values: string[]) => void;
|
27 |
-
linkProviderModel: boolean;
|
28 |
-
onLinkToggle: (checked: boolean) => void;
|
29 |
}
|
30 |
|
|
|
31 |
export const BenchmarkTable: React.FC<BenchmarkTableProps> = ({
|
32 |
data,
|
33 |
comparisonMetrics,
|
@@ -38,8 +36,7 @@ export const BenchmarkTable: React.FC<BenchmarkTableProps> = ({
|
|
38 |
selectedModels,
|
39 |
onProviderChange,
|
40 |
onModelChange,
|
41 |
-
|
42 |
-
onLinkToggle,
|
43 |
}) => {
|
44 |
const benchmarkMetrics = React.useMemo(() => {
|
45 |
if (!comparisonMetrics || comparisonMetrics.length === 0) return [];
|
@@ -67,12 +64,14 @@ export const BenchmarkTable: React.FC<BenchmarkTableProps> = ({
|
|
67 |
}))
|
68 |
);
|
69 |
|
70 |
-
|
71 |
-
|
72 |
-
|
|
|
73 |
|
74 |
return Array.from(new Map(filtered.map((m) => [m.value, m])).values());
|
75 |
-
}, [allProviders, selectedProviders
|
|
|
76 |
|
77 |
return (
|
78 |
<>
|
@@ -93,16 +92,7 @@ export const BenchmarkTable: React.FC<BenchmarkTableProps> = ({
|
|
93 |
onValueChange={onModelChange}
|
94 |
/>
|
95 |
</div>
|
96 |
-
|
97 |
-
<Switch
|
98 |
-
id="linkBenchmarkProviderModel"
|
99 |
-
checked={linkProviderModel}
|
100 |
-
onCheckedChange={onLinkToggle}
|
101 |
-
/>
|
102 |
-
<label htmlFor="linkBenchmarkProviderModel" className="text-sm">
|
103 |
-
Link Provider and Model
|
104 |
-
</label>
|
105 |
-
</div>
|
106 |
</div>
|
107 |
|
108 |
<Table>
|
|
|
8 |
TableCell,
|
9 |
} from "@/components/ui/table";
|
10 |
import { MultiSelect } from "@/components/ui/multi-select";
|
|
|
11 |
import { Provider, FlattenedModel } from "@/App";
|
12 |
|
13 |
interface BenchmarkTableProps {
|
|
|
23 |
selectedModels: string[];
|
24 |
onProviderChange: (values: string[]) => void;
|
25 |
onModelChange: (values: string[]) => void;
|
|
|
|
|
26 |
}
|
27 |
|
28 |
+
|
29 |
export const BenchmarkTable: React.FC<BenchmarkTableProps> = ({
|
30 |
data,
|
31 |
comparisonMetrics,
|
|
|
36 |
selectedModels,
|
37 |
onProviderChange,
|
38 |
onModelChange,
|
39 |
+
|
|
|
40 |
}) => {
|
41 |
const benchmarkMetrics = React.useMemo(() => {
|
42 |
if (!comparisonMetrics || comparisonMetrics.length === 0) return [];
|
|
|
64 |
}))
|
65 |
);
|
66 |
|
67 |
+
const filtered = flat.filter((m) =>
|
68 |
+
!selectedProviders.length || selectedProviders.includes(m.provider)
|
69 |
+
);
|
70 |
+
|
71 |
|
72 |
return Array.from(new Map(filtered.map((m) => [m.value, m])).values());
|
73 |
+
}, [allProviders, selectedProviders]);
|
74 |
+
|
75 |
|
76 |
return (
|
77 |
<>
|
|
|
92 |
onValueChange={onModelChange}
|
93 |
/>
|
94 |
</div>
|
95 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
96 |
</div>
|
97 |
|
98 |
<Table>
|
src/components/PricingTable.tsx
CHANGED
@@ -26,7 +26,7 @@ interface PricingTableProps {
|
|
26 |
key: keyof FlattenedModel;
|
27 |
direction: string;
|
28 |
} | null;
|
29 |
-
|
30 |
}
|
31 |
|
32 |
export const PricingTable: React.FC<PricingTableProps> = ({
|
@@ -42,7 +42,7 @@ export const PricingTable: React.FC<PricingTableProps> = ({
|
|
42 |
tokenCalculation,
|
43 |
requestSort,
|
44 |
sortConfig,
|
45 |
-
|
46 |
}) => {
|
47 |
const calculatePrice = (price: number, tokens: number): number => {
|
48 |
let multiplier = 1;
|
@@ -66,9 +66,10 @@ export const PricingTable: React.FC<PricingTableProps> = ({
|
|
66 |
}))
|
67 |
);
|
68 |
|
69 |
-
|
70 |
-
|
71 |
-
|
|
|
72 |
|
73 |
// Remove duplicates
|
74 |
return Array.from(new Map(filtered.map((m) => [m.value, m])).values());
|
|
|
26 |
key: keyof FlattenedModel;
|
27 |
direction: string;
|
28 |
} | null;
|
29 |
+
|
30 |
}
|
31 |
|
32 |
export const PricingTable: React.FC<PricingTableProps> = ({
|
|
|
42 |
tokenCalculation,
|
43 |
requestSort,
|
44 |
sortConfig,
|
45 |
+
|
46 |
}) => {
|
47 |
const calculatePrice = (price: number, tokens: number): number => {
|
48 |
let multiplier = 1;
|
|
|
66 |
}))
|
67 |
);
|
68 |
|
69 |
+
const filtered = flatModels.filter((m) =>
|
70 |
+
!selectedProviders.length || selectedProviders.includes(m.provider)
|
71 |
+
);
|
72 |
+
|
73 |
|
74 |
// Remove duplicates
|
75 |
return Array.from(new Map(filtered.map((m) => [m.value, m])).values());
|