Presidentlin commited on
Commit
ef01c66
1 Parent(s): 890a57f

Update src/App.tsx

Browse files
Files changed (1) hide show
  1. src/App.tsx +196 -138
src/App.tsx CHANGED
@@ -1,12 +1,7 @@
1
- import React, { useState, useEffect } from 'react';
2
- import {
3
- Card,
4
- CardContent,
5
- CardHeader,
6
- CardTitle,
7
- } from '@/components/ui/card';
8
- import { Checkbox } from '@/components/ui/checkbox';
9
- import { Input } from '@/components/ui/input';
10
  import {
11
  Table,
12
  TableBody,
@@ -14,16 +9,17 @@ import {
14
  TableHead,
15
  TableHeader,
16
  TableRow,
17
- } from '@/components/ui/table';
18
- import { MultiSelect } from '@/components/ui/multi-select';
19
  import {
20
  Collapsible,
21
  CollapsibleContent,
22
  CollapsibleTrigger,
23
- } from '@/components/ui/collapsible';
24
- import { Button } from '@/components/ui/button';
25
- import { ChevronDown, ChevronRight } from 'lucide-react';
26
- import { mockData } from './lib/data'; // Assuming you have this file for mock data
 
27
 
28
  interface FlattenedModel extends Model {
29
  provider: string;
@@ -50,7 +46,8 @@ const App: React.FC = () => {
50
  const [selectedProviders, setSelectedProviders] = useState<string[]>([]);
51
  const [selectedModels, setSelectedModels] = useState<string[]>([]);
52
  const [expandedProviders, setExpandedProviders] = useState<string[]>([]);
53
- const [tokenCalculation, setTokenCalculation] = useState<string>('million');
 
54
 
55
  const [sortConfig, setSortConfig] = useState<{
56
  key: keyof FlattenedModel;
@@ -63,23 +60,23 @@ const App: React.FC = () => {
63
 
64
  const calculatePrice = (price: number, tokens: number): number => {
65
  let multiplier = 1;
66
- if (tokenCalculation === 'thousand') {
67
  multiplier = 1e-3;
68
- } else if (tokenCalculation === 'unit') {
69
  multiplier = 1e-6;
70
- } else if (tokenCalculation === 'billion') {
71
  multiplier = 1e3;
72
  }
73
  return price * tokens * multiplier;
74
  };
75
 
76
-
77
-
78
  const calculateComparison = (
79
  modelPrice: number,
80
  comparisonPrice: number
81
  ): string => {
82
- return (((modelPrice - comparisonPrice) / comparisonPrice) * 100).toFixed(2);
 
 
83
  };
84
 
85
  const flattenData = (data: Provider[]) => {
@@ -92,20 +89,39 @@ const App: React.FC = () => {
92
  );
93
  };
94
 
95
- const filteredData = data
96
- .filter(
97
- (provider) =>
98
- selectedProviders.length === 0 ||
99
- selectedProviders.includes(provider.provider)
100
- )
101
- .map((provider) => ({
102
- ...provider,
103
- models: provider.models.filter(
104
- (model) =>
105
- selectedModels.length === 0 || selectedModels.includes(model.name)
106
- ),
107
- }))
108
- .filter((provider) => provider.models.length > 0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
  const sortedFlattenedData = React.useMemo(() => {
111
  let sortableData: FlattenedModel[] = flattenData(filteredData);
@@ -114,12 +130,12 @@ const App: React.FC = () => {
114
  const aValue = a[sortConfig.key];
115
  const bValue = b[sortConfig.key];
116
 
117
- if (typeof aValue === 'string' && typeof bValue === 'string') {
118
- return sortConfig.direction === 'ascending'
119
  ? aValue.localeCompare(bValue)
120
  : bValue.localeCompare(aValue);
121
- } else if (typeof aValue === 'number' && typeof bValue === 'number') {
122
- return sortConfig.direction === 'ascending'
123
  ? aValue - bValue
124
  : bValue - aValue;
125
  } else {
@@ -131,13 +147,13 @@ const App: React.FC = () => {
131
  }, [filteredData, sortConfig]);
132
 
133
  const requestSort = (key: keyof FlattenedModel) => {
134
- let direction = 'ascending';
135
  if (
136
  sortConfig &&
137
  sortConfig.key === key &&
138
- sortConfig.direction === 'ascending'
139
  ) {
140
- direction = 'descending';
141
  }
142
  setSortConfig({ key, direction });
143
  };
@@ -150,6 +166,53 @@ const App: React.FC = () => {
150
  );
151
  };
152
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  return (
154
  <Card className="w-full max-w-6xl mx-auto">
155
  <CardHeader>
@@ -158,9 +221,16 @@ const App: React.FC = () => {
158
  <CardContent>
159
  <div className="mb-4">
160
  <p className="italic text-sm text-muted-foreground mb-4">
161
- <a href="https://huggingface.co/spaces/philschmid/llm-pricing" className='underline'>This is a fork of philschmid tool: philschmid/llm-pricing</a>
 
 
 
 
 
162
  </p>
163
- <h3 className="text-lg font-semibold mb-2">Select Comparison Models</h3>
 
 
164
  <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
165
  {data.map((provider) => (
166
  <Collapsible
@@ -198,7 +268,8 @@ const App: React.FC = () => {
198
  } else {
199
  setComparisonModels((prev) =>
200
  prev.filter(
201
- (m) => m !== `${provider.provider}:${model.name}`
 
202
  )
203
  );
204
  }
@@ -224,7 +295,6 @@ const App: React.FC = () => {
224
  htmlFor="inputTokens"
225
  className="block text-sm font-medium text-gray-700"
226
  >
227
-
228
  Input Tokens ({tokenCalculation})
229
  </label>
230
  <Input
@@ -270,72 +340,75 @@ const App: React.FC = () => {
270
  <option value="thousand">Thousand Tokens</option>
271
  <option value="unit">Unit Tokens</option>
272
  </select>
273
-
274
  </div>
275
-
276
  </div>
277
 
278
-
279
  <p className="italic text-sm text-muted-foreground mb-4">
280
  Note: If you use Amazon Bedrock or Azure prices for Anthropic, Cohere
281
  or OpenAI should be the same.
282
  </p>
 
 
 
 
 
 
 
 
 
 
283
 
284
  <Table>
285
  <TableHeader>
286
  <TableRow>
287
  <TableHead>
288
- <button type="button" onClick={() => requestSort('provider')}>
289
- Provider{' '}
290
- {sortConfig?.key === 'provider' ? (
291
- sortConfig.direction === 'ascending' ? (
292
- ''
293
- ) : (
294
- '▼'
295
- )
296
- ) : null}
297
  </button>
298
  </TableHead>
299
  <TableHead>
300
- <button type="button" onClick={() => requestSort('name')}>
301
- Model{' '}
302
- {sortConfig?.key === 'name' ? (
303
- sortConfig.direction === 'ascending' ? (
304
- ''
305
- ) : (
306
- '▼'
307
- )
308
- ) : null}
309
  </button>
310
  </TableHead>
311
 
312
  <TableHead>
313
- <button type="button" onClick={() => requestSort('inputPrice')}>
314
  Input Price (million tokens)
315
- {sortConfig?.key === 'inputPrice' ? (
316
- sortConfig.direction === 'ascending' ? (
317
- ''
318
- ) : (
319
- '▼'
320
- )
321
- ) : null}
322
  </button>
323
  </TableHead>
324
  <TableHead>
325
- <button type="button" onClick={() => requestSort('outputPrice')}>
 
 
 
326
  Output Price (million tokens)
327
- {sortConfig?.key === 'outputPrice' ? (
328
- sortConfig.direction === 'ascending' ? (
329
- ''
330
- ) : (
331
- '▼'
332
- )
333
- ) : null}
334
  </button>
335
  </TableHead>
336
 
337
-
338
- <TableHead>Total Price (per {tokenCalculation} tokens){' '}</TableHead>
 
339
  {comparisonModels.map((model) => (
340
  <TableHead key={model} colSpan={2}>
341
  Compared to {model}
@@ -357,23 +430,7 @@ const App: React.FC = () => {
357
  </TableHead>
358
  <TableHead>
359
  <MultiSelect
360
- options={
361
- data
362
- .flatMap((provider) => provider.models)
363
- .map((model) => ({ label: model.name, value: model.name }))
364
- .reduce(
365
- (
366
- acc: { label: string; value: string }[],
367
- curr: { label: string; value: string }
368
- ) => {
369
- if (!acc.find((m) => m.value === curr.value)) {
370
- acc.push(curr);
371
- }
372
- return acc;
373
- },
374
- []
375
- ) || []
376
- }
377
  defaultValue={selectedModels}
378
  onValueChange={setSelectedModels}
379
  />
@@ -391,7 +448,7 @@ const App: React.FC = () => {
391
  {sortedFlattenedData.map((item) => (
392
  <TableRow key={`${item.provider}-${item.name}`}>
393
  <TableCell>
394
- {' '}
395
  <a href={item.uri} className="underline">
396
  {item.provider}
397
  </a>
@@ -409,65 +466,66 @@ const App: React.FC = () => {
409
  ).toFixed(2)}
410
  </TableCell>
411
 
412
-
413
  {comparisonModels.flatMap((comparisonModel) => {
414
  const [comparisonProvider, comparisonModelName] =
415
- comparisonModel.split(':');
416
  const comparisonModelData = data
417
  .find((p) => p.provider === comparisonProvider)
418
  ?.models.find((m) => m.name === comparisonModelName)!;
419
  return [
420
  <TableCell
421
  key={`${comparisonModel}-input`}
422
- className={`${parseFloat(
423
- calculateComparison(
424
- item.inputPrice,
425
- comparisonModelData.inputPrice
426
- )
427
- ) < 0
428
- ? 'bg-green-100'
429
- : parseFloat(
430
  calculateComparison(
431
  item.inputPrice,
432
  comparisonModelData.inputPrice
433
  )
434
- ) > 0
435
- ? 'bg-red-100'
436
- : ''
437
- }`}
 
 
 
 
 
 
 
438
  >
439
  {`${item.provider}:${item.name}` === comparisonModel
440
- ? '0.00%'
441
  : `${calculateComparison(
442
- item.inputPrice,
443
- comparisonModelData.inputPrice
444
- )}%`}
445
  </TableCell>,
446
  <TableCell
447
  key={`${comparisonModel}-output`}
448
- className={`${parseFloat(
449
- calculateComparison(
450
- item.outputPrice,
451
- comparisonModelData.outputPrice
452
- )
453
- ) < 0
454
- ? 'bg-green-100'
455
- : parseFloat(
456
  calculateComparison(
457
  item.outputPrice,
458
  comparisonModelData.outputPrice
459
  )
460
- ) > 0
461
- ? 'bg-red-100'
462
- : ''
463
- }`}
 
 
 
 
 
 
 
464
  >
465
  {`${item.provider}:${item.name}` === comparisonModel
466
- ? '0.00%'
467
  : `${calculateComparison(
468
- item.outputPrice,
469
- comparisonModelData.outputPrice
470
- )}%`}
471
  </TableCell>,
472
  ];
473
  })}
 
1
+ import React, { useState, useEffect } from "react";
2
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
3
+ import { Checkbox } from "@/components/ui/checkbox";
4
+ import { Input } from "@/components/ui/input";
 
 
 
 
 
5
  import {
6
  Table,
7
  TableBody,
 
9
  TableHead,
10
  TableHeader,
11
  TableRow,
12
+ } from "@/components/ui/table";
13
+ import { MultiSelect } from "@/components/ui/multi-select";
14
  import {
15
  Collapsible,
16
  CollapsibleContent,
17
  CollapsibleTrigger,
18
+ } from "@/components/ui/collapsible";
19
+ import { Button } from "@/components/ui/button";
20
+ import { ChevronDown, ChevronRight } from "lucide-react";
21
+ import { mockData } from "./lib/data"; // Assuming you have this file for mock data
22
+ import { Switch } from "@/components/ui/switch";
23
 
24
  interface FlattenedModel extends Model {
25
  provider: string;
 
46
  const [selectedProviders, setSelectedProviders] = useState<string[]>([]);
47
  const [selectedModels, setSelectedModels] = useState<string[]>([]);
48
  const [expandedProviders, setExpandedProviders] = useState<string[]>([]);
49
+ const [tokenCalculation, setTokenCalculation] = useState<string>("million");
50
+ const [linkProviderModel, setLinkProviderModel] = useState<boolean>(false);
51
 
52
  const [sortConfig, setSortConfig] = useState<{
53
  key: keyof FlattenedModel;
 
60
 
61
  const calculatePrice = (price: number, tokens: number): number => {
62
  let multiplier = 1;
63
+ if (tokenCalculation === "thousand") {
64
  multiplier = 1e-3;
65
+ } else if (tokenCalculation === "unit") {
66
  multiplier = 1e-6;
67
+ } else if (tokenCalculation === "billion") {
68
  multiplier = 1e3;
69
  }
70
  return price * tokens * multiplier;
71
  };
72
 
 
 
73
  const calculateComparison = (
74
  modelPrice: number,
75
  comparisonPrice: number
76
  ): string => {
77
+ return (((modelPrice - comparisonPrice) / comparisonPrice) * 100).toFixed(
78
+ 2
79
+ );
80
  };
81
 
82
  const flattenData = (data: Provider[]) => {
 
89
  );
90
  };
91
 
92
+ const filteredData =
93
+ selectedProviders.length === 0 &&
94
+ selectedModels.length === 0 &&
95
+ !linkProviderModel
96
+ ? data.map((provider) => ({
97
+ ...provider,
98
+ models: provider.models,
99
+ }))
100
+ : data
101
+ .filter(
102
+ (provider) =>
103
+ selectedProviders.length === 0 ||
104
+ selectedProviders.includes(provider.provider)
105
+ )
106
+ .map((provider) => ({
107
+ ...provider,
108
+ models: provider.models.filter((model) => {
109
+ // If linking is enabled and no models are selected, filter by provider
110
+ if (linkProviderModel && selectedModels.length === 0)
111
+ return selectedProviders.includes(provider.provider);
112
+
113
+ // If no models are selected and linking is off, show all models from selected providers (or all if no providers selected)
114
+ if (!linkProviderModel && selectedModels.length === 0)
115
+ return (
116
+ selectedProviders.length === 0 ||
117
+ selectedProviders.includes(provider.provider)
118
+ );
119
+
120
+ // Otherwise, only show selected models
121
+ return selectedModels.includes(model.name);
122
+ }),
123
+ }))
124
+ .filter((provider) => provider.models.length > 0);
125
 
126
  const sortedFlattenedData = React.useMemo(() => {
127
  let sortableData: FlattenedModel[] = flattenData(filteredData);
 
130
  const aValue = a[sortConfig.key];
131
  const bValue = b[sortConfig.key];
132
 
133
+ if (typeof aValue === "string" && typeof bValue === "string") {
134
+ return sortConfig.direction === "ascending"
135
  ? aValue.localeCompare(bValue)
136
  : bValue.localeCompare(aValue);
137
+ } else if (typeof aValue === "number" && typeof bValue === "number") {
138
+ return sortConfig.direction === "ascending"
139
  ? aValue - bValue
140
  : bValue - aValue;
141
  } else {
 
147
  }, [filteredData, sortConfig]);
148
 
149
  const requestSort = (key: keyof FlattenedModel) => {
150
+ let direction = "ascending";
151
  if (
152
  sortConfig &&
153
  sortConfig.key === key &&
154
+ sortConfig.direction === "ascending"
155
  ) {
156
+ direction = "descending";
157
  }
158
  setSortConfig({ key, direction });
159
  };
 
166
  );
167
  };
168
 
169
+ const getModelsForSelectedProviders = () => {
170
+ if (!linkProviderModel) {
171
+ return data
172
+ .flatMap((provider) =>
173
+ provider.models.map((model) => ({
174
+ label: model.name,
175
+ value: model.name,
176
+ provider: provider.provider,
177
+ }))
178
+ )
179
+ .reduce(
180
+ (
181
+ acc: { label: string; value: string; provider: string }[],
182
+ curr: { label: string; value: string; provider: string }
183
+ ) => {
184
+ if (!acc.find((m) => m.value === curr.value)) {
185
+ acc.push(curr);
186
+ }
187
+ return acc;
188
+ },
189
+ []
190
+ );
191
+ }
192
+
193
+ return data
194
+ .filter((provider) => selectedProviders.includes(provider.provider))
195
+ .flatMap((provider) =>
196
+ provider.models.map((model) => ({
197
+ label: model.name,
198
+ value: model.name,
199
+ provider: provider.provider,
200
+ }))
201
+ )
202
+ .reduce(
203
+ (
204
+ acc: { label: string; value: string; provider: string }[],
205
+ curr: { label: string; value: string; provider: string }
206
+ ) => {
207
+ if (!acc.find((m) => m.value === curr.value)) {
208
+ acc.push(curr);
209
+ }
210
+ return acc;
211
+ },
212
+ []
213
+ );
214
+ };
215
+
216
  return (
217
  <Card className="w-full max-w-6xl mx-auto">
218
  <CardHeader>
 
221
  <CardContent>
222
  <div className="mb-4">
223
  <p className="italic text-sm text-muted-foreground mb-4">
224
+ <a
225
+ href="https://huggingface.co/spaces/philschmid/llm-pricing"
226
+ className="underline"
227
+ >
228
+ This is a fork of philschmid tool: philschmid/llm-pricing
229
+ </a>
230
  </p>
231
+ <h3 className="text-lg font-semibold mb-2">
232
+ Select Comparison Models
233
+ </h3>
234
  <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
235
  {data.map((provider) => (
236
  <Collapsible
 
268
  } else {
269
  setComparisonModels((prev) =>
270
  prev.filter(
271
+ (m) =>
272
+ m !== `${provider.provider}:${model.name}`
273
  )
274
  );
275
  }
 
295
  htmlFor="inputTokens"
296
  className="block text-sm font-medium text-gray-700"
297
  >
 
298
  Input Tokens ({tokenCalculation})
299
  </label>
300
  <Input
 
340
  <option value="thousand">Thousand Tokens</option>
341
  <option value="unit">Unit Tokens</option>
342
  </select>
 
343
  </div>
 
344
  </div>
345
 
 
346
  <p className="italic text-sm text-muted-foreground mb-4">
347
  Note: If you use Amazon Bedrock or Azure prices for Anthropic, Cohere
348
  or OpenAI should be the same.
349
  </p>
350
+ <div className="flex items-center space-x-2 mb-4">
351
+ <Switch
352
+ id="linkProviderModel"
353
+ checked={linkProviderModel}
354
+ onCheckedChange={setLinkProviderModel}
355
+ />
356
+ <label htmlFor="linkProviderModel" className="text-sm">
357
+ Link Provider and Model
358
+ </label>
359
+ </div>
360
 
361
  <Table>
362
  <TableHeader>
363
  <TableRow>
364
  <TableHead>
365
+ <button type="button" onClick={() => requestSort("provider")}>
366
+ Provider{" "}
367
+ {sortConfig?.key === "provider"
368
+ ? sortConfig.direction === "ascending"
369
+ ? ""
370
+ : "▼"
371
+ : null}
 
 
372
  </button>
373
  </TableHead>
374
  <TableHead>
375
+ <button type="button" onClick={() => requestSort("name")}>
376
+ Model{" "}
377
+ {sortConfig?.key === "name"
378
+ ? sortConfig.direction === "ascending"
379
+ ? ""
380
+ : "▼"
381
+ : null}
 
 
382
  </button>
383
  </TableHead>
384
 
385
  <TableHead>
386
+ <button type="button" onClick={() => requestSort("inputPrice")}>
387
  Input Price (million tokens)
388
+ {sortConfig?.key === "inputPrice"
389
+ ? sortConfig.direction === "ascending"
390
+ ? ""
391
+ : "▼"
392
+ : null}
 
 
393
  </button>
394
  </TableHead>
395
  <TableHead>
396
+ <button
397
+ type="button"
398
+ onClick={() => requestSort("outputPrice")}
399
+ >
400
  Output Price (million tokens)
401
+ {sortConfig?.key === "outputPrice"
402
+ ? sortConfig.direction === "ascending"
403
+ ? ""
404
+ : "▼"
405
+ : null}
 
 
406
  </button>
407
  </TableHead>
408
 
409
+ <TableHead>
410
+ Total Price (per {tokenCalculation} tokens){" "}
411
+ </TableHead>
412
  {comparisonModels.map((model) => (
413
  <TableHead key={model} colSpan={2}>
414
  Compared to {model}
 
430
  </TableHead>
431
  <TableHead>
432
  <MultiSelect
433
+ options={getModelsForSelectedProviders()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  defaultValue={selectedModels}
435
  onValueChange={setSelectedModels}
436
  />
 
448
  {sortedFlattenedData.map((item) => (
449
  <TableRow key={`${item.provider}-${item.name}`}>
450
  <TableCell>
451
+ {" "}
452
  <a href={item.uri} className="underline">
453
  {item.provider}
454
  </a>
 
466
  ).toFixed(2)}
467
  </TableCell>
468
 
 
469
  {comparisonModels.flatMap((comparisonModel) => {
470
  const [comparisonProvider, comparisonModelName] =
471
+ comparisonModel.split(":");
472
  const comparisonModelData = data
473
  .find((p) => p.provider === comparisonProvider)
474
  ?.models.find((m) => m.name === comparisonModelName)!;
475
  return [
476
  <TableCell
477
  key={`${comparisonModel}-input`}
478
+ className={`${
479
+ parseFloat(
 
 
 
 
 
 
480
  calculateComparison(
481
  item.inputPrice,
482
  comparisonModelData.inputPrice
483
  )
484
+ ) < 0
485
+ ? "bg-green-100"
486
+ : parseFloat(
487
+ calculateComparison(
488
+ item.inputPrice,
489
+ comparisonModelData.inputPrice
490
+ )
491
+ ) > 0
492
+ ? "bg-red-100"
493
+ : ""
494
+ }`}
495
  >
496
  {`${item.provider}:${item.name}` === comparisonModel
497
+ ? "0.00%"
498
  : `${calculateComparison(
499
+ item.inputPrice,
500
+ comparisonModelData.inputPrice
501
+ )}%`}
502
  </TableCell>,
503
  <TableCell
504
  key={`${comparisonModel}-output`}
505
+ className={`${
506
+ parseFloat(
 
 
 
 
 
 
507
  calculateComparison(
508
  item.outputPrice,
509
  comparisonModelData.outputPrice
510
  )
511
+ ) < 0
512
+ ? "bg-green-100"
513
+ : parseFloat(
514
+ calculateComparison(
515
+ item.outputPrice,
516
+ comparisonModelData.outputPrice
517
+ )
518
+ ) > 0
519
+ ? "bg-red-100"
520
+ : ""
521
+ }`}
522
  >
523
  {`${item.provider}:${item.name}` === comparisonModel
524
+ ? "0.00%"
525
  : `${calculateComparison(
526
+ item.outputPrice,
527
+ comparisonModelData.outputPrice
528
+ )}%`}
529
  </TableCell>,
530
  ];
531
  })}