Presidentlin commited on
Commit
5403286
·
1 Parent(s): d4f1866
Files changed (1) hide show
  1. src/App.tsx +129 -133
src/App.tsx CHANGED
@@ -43,6 +43,7 @@ const App: React.FC = () => {
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;
@@ -54,12 +55,10 @@ const App: React.FC = () => {
54
  direction: "ascending" | "descending";
55
  } | null>(null);
56
 
57
-
58
  useEffect(() => {
59
  setData(mockData);
60
  }, []);
61
 
62
-
63
  const flattenDataFromPricing = (data: Provider[]): FlattenedModel[] =>
64
  data.flatMap((provider) =>
65
  provider.models.map((model) => ({
@@ -73,15 +72,13 @@ const App: React.FC = () => {
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
 
@@ -91,49 +88,33 @@ const App: React.FC = () => {
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
- return providerMatch && modelMatch;
115
- });
116
- }, [
117
- benchmarkedModels,
118
- selectedBenchmarkProviders,
119
- selectedBenchmarkModels,
120
- ]);
121
-
122
 
123
  const sortedBenchmarkedModels = useMemo(() => {
124
  if (!benchmarkSortConfig) return filteredBenchmarkedModels;
125
 
126
  return [...filteredBenchmarkedModels].sort((a, b) => {
127
  const key = benchmarkSortConfig.key;
128
-
129
  const isTopLevelKey = ["provider", "name", "inputPrice", "outputPrice"].includes(key);
130
-
131
- const aVal = isTopLevelKey
132
- ? (a as any)[key]
133
- : a.benchmark?.[key] ?? -Infinity;
134
- const bVal = isTopLevelKey
135
- ? (b as any)[key]
136
- : b.benchmark?.[key] ?? -Infinity;
137
 
138
  if (typeof aVal === "string" && typeof bVal === "string") {
139
  return benchmarkSortConfig.direction === "ascending"
@@ -147,7 +128,6 @@ const filteredBenchmarkedModels = useMemo(() => {
147
  });
148
  }, [filteredBenchmarkedModels, benchmarkSortConfig]);
149
 
150
-
151
  const pricingProviders = useMemo(() => {
152
  const grouped: Record<string, FlattenedModel[]> = {};
153
 
@@ -168,7 +148,6 @@ const filteredBenchmarkedModels = useMemo(() => {
168
  }));
169
  }, [data]);
170
 
171
-
172
  const benchmarkProviders = useMemo(() => {
173
  const grouped: Record<string, FlattenedModel[]> = {};
174
 
@@ -189,11 +168,6 @@ const filteredBenchmarkedModels = useMemo(() => {
189
  }));
190
  }, [benchmarkedModels]);
191
 
192
-
193
-
194
-
195
-
196
-
197
  const sortedFlattenedData = useMemo(() => {
198
  const flattened = flattenDataFromPricing(filteredData);
199
  if (!sortConfig) return flattened;
@@ -222,15 +196,12 @@ const filteredBenchmarkedModels = useMemo(() => {
222
  );
223
  };
224
 
225
-
226
-
227
  return (
228
  <Card className="w-full max-w-6xl mx-auto">
229
  <CardHeader>
230
- <CardTitle>LLM Pricing Calculator</CardTitle>
231
  </CardHeader>
232
  <CardContent>
233
- {/* Source Link */}
234
  <p className="italic text-sm text-muted-foreground mb-4">
235
  <a
236
  href="https://huggingface.co/spaces/philschmid/llm-pricing"
@@ -240,97 +211,122 @@ const filteredBenchmarkedModels = useMemo(() => {
240
  </a>
241
  </p>
242
 
243
- {/* Comparison Model Selector */}
244
- <h3 className="text-lg font-semibold mb-2">Select Comparison Models</h3>
245
- <ComparisonSelector
246
- data={data}
247
- expanded={expandedProviders}
248
- comparisonModels={comparisonModels}
249
- onToggleExpand={toggleProviderExpansion}
250
- onChangeModel={(modelId, checked) =>
251
- setComparisonModels((prev) =>
252
- checked ? [...prev, modelId] : prev.filter((m) => m !== modelId)
253
- )
254
- }
255
- />
256
-
257
- {/* Token Inputs */}
258
- <div className="flex gap-4 mt-6 mb-4">
259
- <div className="flex-1">
260
- <label className="block text-sm font-medium">Input Tokens ({tokenCalculation})</label>
261
- <Input type="number" value={inputTokens} min={1} onChange={(e) => setInputTokens(Number(e.target.value))} />
262
- </div>
263
- <div className="flex-1">
264
- <label className="block text-sm font-medium">Output Tokens ({tokenCalculation})</label>
265
- <Input type="number" value={outputTokens} min={1} onChange={(e) => setOutputTokens(Number(e.target.value))} />
266
- </div>
267
- <div className="flex-1">
268
- <label className="block text-sm font-medium">Token Calculation</label>
269
- <select
270
- value={tokenCalculation}
271
- onChange={(e) => setTokenCalculation(e.target.value)}
272
- className="mt-1 block w-full pl-3 pr-10 py-2 text-base border rounded-md"
273
- >
274
- <option value="billion">Billion Tokens</option>
275
- <option value="million">Million Tokens</option>
276
- <option value="thousand">Thousand Tokens</option>
277
- <option value="unit">Unit Tokens</option>
278
- </select>
279
- </div>
280
  </div>
281
 
282
- {/* Pricing Table */}
283
- <h2 className="text-lg font-semibold mb-2">Pricing Table</h2>
284
- <PricingTable
285
- data={sortedFlattenedData}
286
- providers={pricingProviders}
287
- selectedProviders={selectedProviders}
288
- selectedModels={selectedModels}
289
- onProviderChange={setSelectedProviders}
290
- onModelChange={setSelectedModels}
291
- comparisonModels={comparisonModels}
292
- inputTokens={inputTokens}
293
- outputTokens={outputTokens}
294
- tokenCalculation={tokenCalculation}
295
- requestSort={requestSort}
296
- sortConfig={sortConfig}
297
- />
298
-
299
-
300
- {/* Benchmark Table */}
301
- <h3 className="text-lg font-semibold mt-12 mb-2">Select Benchmark Metrics to Compare</h3>
302
- <BenchmarkComparisonSelector
303
- allMetrics={benchmarkMetricOrder.filter(
304
- (metric) => benchmarkedModels.some((m) => m.benchmark?.[metric] !== undefined)
305
- )}
306
- selected={benchmarkComparisonMetrics}
307
- onChange={(metric, checked) =>
308
- setBenchmarkComparisonMetrics((prev) =>
309
- checked ? [...prev, metric] : prev.filter((m) => m !== metric)
310
- )
311
- }
312
- />
313
-
314
- <h2 className="text-lg font-semibold mt-12 mb-2">Benchmark Table</h2>
315
- <BenchmarkTable
316
- data={sortedBenchmarkedModels}
317
- comparisonMetrics={benchmarkComparisonMetrics}
318
- requestSort={(key) => {
319
- setBenchmarkSortConfig((prev) =>
320
- prev?.key === key
321
- ? { key, direction: prev.direction === "ascending" ? "descending" : "ascending" }
322
- : { key, direction: "descending" }
323
- );
324
- }}
325
- sortConfig={benchmarkSortConfig}
326
- allProviders={benchmarkProviders}
327
- selectedProviders={selectedBenchmarkProviders}
328
- selectedModels={selectedBenchmarkModels}
329
- onProviderChange={setSelectedBenchmarkProviders}
330
- onModelChange={setSelectedBenchmarkModels}
331
- />
332
-
333
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  </CardContent>
335
  </Card>
336
  );
 
43
  const [benchmarkComparisonMetrics, setBenchmarkComparisonMetrics] = useState<string[]>([]);
44
  const [selectedBenchmarkProviders, setSelectedBenchmarkProviders] = useState<string[]>([]);
45
  const [selectedBenchmarkModels, setSelectedBenchmarkModels] = useState<string[]>([]);
46
+ const [viewMode, setViewMode] = useState<"pricing" | "benchmark">("pricing");
47
 
48
  const [sortConfig, setSortConfig] = useState<{
49
  key: keyof FlattenedModel;
 
55
  direction: "ascending" | "descending";
56
  } | null>(null);
57
 
 
58
  useEffect(() => {
59
  setData(mockData);
60
  }, []);
61
 
 
62
  const flattenDataFromPricing = (data: Provider[]): FlattenedModel[] =>
63
  data.flatMap((provider) =>
64
  provider.models.map((model) => ({
 
72
  const flattenDataFromBenchmarks = (): FlattenedModel[] =>
73
  benchmarkData.map((b) => ({
74
  provider: b.provider ?? "Unknown",
75
+ uri: b.source,
76
  name: b.model,
77
  inputPrice: b.inputPrice,
78
  outputPrice: b.outputPrice,
79
  benchmark: b.benchmark ?? {},
80
  }));
81
 
 
 
82
  const filteredData = useMemo(() => {
83
  if (!selectedProviders.length && !selectedModels.length) return data;
84
 
 
88
  ...p,
89
  models: p.models.filter((m) => {
90
  if (!selectedModels.length) return selectedProviders.includes(p.provider);
 
91
  return selectedModels.includes(m.name);
92
  }),
93
  }))
94
  .filter((p) => p.models.length > 0);
95
  }, [data, selectedProviders, selectedModels]);
96
 
97
+ const benchmarkedModels = useMemo(() => flattenDataFromBenchmarks(), []);
 
 
 
98
 
99
+ const filteredBenchmarkedModels = useMemo(() => {
100
+ return benchmarkedModels.filter((model) => {
101
+ const providerMatch =
102
+ selectedBenchmarkProviders.length === 0 || selectedBenchmarkProviders.includes(model.provider);
103
+ const modelMatch =
104
+ selectedBenchmarkModels.length === 0 || selectedBenchmarkModels.includes(model.name);
105
 
106
+ return providerMatch && modelMatch;
107
+ });
108
+ }, [benchmarkedModels, selectedBenchmarkProviders, selectedBenchmarkModels]);
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
  const sortedBenchmarkedModels = useMemo(() => {
111
  if (!benchmarkSortConfig) return filteredBenchmarkedModels;
112
 
113
  return [...filteredBenchmarkedModels].sort((a, b) => {
114
  const key = benchmarkSortConfig.key;
 
115
  const isTopLevelKey = ["provider", "name", "inputPrice", "outputPrice"].includes(key);
116
+ const aVal = isTopLevelKey ? (a as any)[key] : a.benchmark?.[key] ?? -Infinity;
117
+ const bVal = isTopLevelKey ? (b as any)[key] : b.benchmark?.[key] ?? -Infinity;
 
 
 
 
 
118
 
119
  if (typeof aVal === "string" && typeof bVal === "string") {
120
  return benchmarkSortConfig.direction === "ascending"
 
128
  });
129
  }, [filteredBenchmarkedModels, benchmarkSortConfig]);
130
 
 
131
  const pricingProviders = useMemo(() => {
132
  const grouped: Record<string, FlattenedModel[]> = {};
133
 
 
148
  }));
149
  }, [data]);
150
 
 
151
  const benchmarkProviders = useMemo(() => {
152
  const grouped: Record<string, FlattenedModel[]> = {};
153
 
 
168
  }));
169
  }, [benchmarkedModels]);
170
 
 
 
 
 
 
171
  const sortedFlattenedData = useMemo(() => {
172
  const flattened = flattenDataFromPricing(filteredData);
173
  if (!sortConfig) return flattened;
 
196
  );
197
  };
198
 
 
 
199
  return (
200
  <Card className="w-full max-w-6xl mx-auto">
201
  <CardHeader>
202
+ <CardTitle>LLM Pricing & Benchmark Comparison Tool</CardTitle>
203
  </CardHeader>
204
  <CardContent>
 
205
  <p className="italic text-sm text-muted-foreground mb-4">
206
  <a
207
  href="https://huggingface.co/spaces/philschmid/llm-pricing"
 
211
  </a>
212
  </p>
213
 
214
+ {/* View Toggle */}
215
+ <div className="flex justify-center gap-6 mb-6">
216
+ <label className="flex items-center space-x-2">
217
+ <input
218
+ type="radio"
219
+ name="view"
220
+ value="pricing"
221
+ checked={viewMode === "pricing"}
222
+ onChange={() => setViewMode("pricing")}
223
+ />
224
+ <span>Pricing</span>
225
+ </label>
226
+ <label className="flex items-center space-x-2">
227
+ <input
228
+ type="radio"
229
+ name="view"
230
+ value="benchmark"
231
+ checked={viewMode === "benchmark"}
232
+ onChange={() => setViewMode("benchmark")}
233
+ />
234
+ <span>Benchmark</span>
235
+ </label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  </div>
237
 
238
+ {viewMode === "pricing" && (
239
+ <>
240
+ <h3 className="text-lg font-semibold mb-2">Select Comparison Models</h3>
241
+ <ComparisonSelector
242
+ data={data}
243
+ expanded={expandedProviders}
244
+ comparisonModels={comparisonModels}
245
+ onToggleExpand={toggleProviderExpansion}
246
+ onChangeModel={(modelId, checked) =>
247
+ setComparisonModels((prev) =>
248
+ checked ? [...prev, modelId] : prev.filter((m) => m !== modelId)
249
+ )
250
+ }
251
+ />
252
+
253
+ <div className="flex gap-4 mt-6 mb-4">
254
+ <div className="flex-1">
255
+ <label className="block text-sm font-medium">Input Tokens ({tokenCalculation})</label>
256
+ <Input type="number" value={inputTokens} min={1} onChange={(e) => setInputTokens(Number(e.target.value))} />
257
+ </div>
258
+ <div className="flex-1">
259
+ <label className="block text-sm font-medium">Output Tokens ({tokenCalculation})</label>
260
+ <Input type="number" value={outputTokens} min={1} onChange={(e) => setOutputTokens(Number(e.target.value))} />
261
+ </div>
262
+ <div className="flex-1">
263
+ <label className="block text-sm font-medium">Token Calculation</label>
264
+ <select
265
+ value={tokenCalculation}
266
+ onChange={(e) => setTokenCalculation(e.target.value)}
267
+ className="mt-1 block w-full pl-3 pr-10 py-2 text-base border rounded-md"
268
+ >
269
+ <option value="billion">Billion Tokens</option>
270
+ <option value="million">Million Tokens</option>
271
+ <option value="thousand">Thousand Tokens</option>
272
+ <option value="unit">Unit Tokens</option>
273
+ </select>
274
+ </div>
275
+ </div>
276
+
277
+ <h2 className="text-lg font-semibold mb-2">Pricing Table</h2>
278
+ <PricingTable
279
+ data={sortedFlattenedData}
280
+ providers={pricingProviders}
281
+ selectedProviders={selectedProviders}
282
+ selectedModels={selectedModels}
283
+ onProviderChange={setSelectedProviders}
284
+ onModelChange={setSelectedModels}
285
+ comparisonModels={comparisonModels}
286
+ inputTokens={inputTokens}
287
+ outputTokens={outputTokens}
288
+ tokenCalculation={tokenCalculation}
289
+ requestSort={requestSort}
290
+ sortConfig={sortConfig}
291
+ />
292
+ </>
293
+ )}
294
+
295
+ {viewMode === "benchmark" && (
296
+ <>
297
+ <h3 className="text-lg font-semibold mb-2">Select Benchmark Metrics to Compare</h3>
298
+ <BenchmarkComparisonSelector
299
+ allMetrics={benchmarkMetricOrder.filter(
300
+ (metric) => benchmarkedModels.some((m) => m.benchmark?.[metric] !== undefined)
301
+ )}
302
+ selected={benchmarkComparisonMetrics}
303
+ onChange={(metric, checked) =>
304
+ setBenchmarkComparisonMetrics((prev) =>
305
+ checked ? [...prev, metric] : prev.filter((m) => m !== metric)
306
+ )
307
+ }
308
+ />
309
+
310
+ <h2 className="text-lg font-semibold mb-2">Benchmark Table</h2>
311
+ <BenchmarkTable
312
+ data={sortedBenchmarkedModels}
313
+ comparisonMetrics={benchmarkComparisonMetrics}
314
+ requestSort={(key) => {
315
+ setBenchmarkSortConfig((prev) =>
316
+ prev?.key === key
317
+ ? { key, direction: prev.direction === "ascending" ? "descending" : "ascending" }
318
+ : { key, direction: "descending" }
319
+ );
320
+ }}
321
+ sortConfig={benchmarkSortConfig}
322
+ allProviders={benchmarkProviders}
323
+ selectedProviders={selectedBenchmarkProviders}
324
+ selectedModels={selectedBenchmarkModels}
325
+ onProviderChange={setSelectedBenchmarkProviders}
326
+ onModelChange={setSelectedBenchmarkModels}
327
+ />
328
+ </>
329
+ )}
330
  </CardContent>
331
  </Card>
332
  );