import React, { useState, useEffect } from 'react' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Checkbox } from '@/components/ui/checkbox' import { Input } from '@/components/ui/input' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { MultiSelect } from '@/components/ui/multi-select' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' import { Button } from '@/components/ui/button' import { ChevronDown, ChevronRight } from 'lucide-react' import { mockData } from './lib/data' export interface Model { name: string inputPrice: number outputPrice: number } export interface Provider { provider: string uri: string models: Model[] } const App: React.FC = () => { const [data, setData] = useState<Provider[]>([]) const [comparisonModels, setComparisonModels] = useState<string[]>([]) const [inputTokens, setInputTokens] = useState<number>(1) const [outputTokens, setOutputTokens] = useState<number>(1) const [selectedProviders, setSelectedProviders] = useState<string[]>([]) const [selectedModels, setSelectedModels] = useState<string[]>([]) const [expandedProviders, setExpandedProviders] = useState<string[]>([]) useEffect(() => { setData(mockData) setComparisonModels(['OpenAI:GPT-4o', 'Anthropic:Claude 3.5 (Sonnet)', 'Google:Gemini 1.5 Pro']) }, []) const calculatePrice = (price: number, tokens: number): number => { return price * tokens } const calculateComparison = (modelPrice: number, comparisonPrice: number): string => { return (((modelPrice - comparisonPrice) / comparisonPrice) * 100).toFixed(2) } const filteredData = data .filter((provider) => selectedProviders.length === 0 || selectedProviders.includes(provider.provider)) .map((provider) => ({ ...provider, models: provider.models.filter((model) => selectedModels.length === 0 || selectedModels.includes(model.name)), })) .filter((provider) => provider.models.length > 0) console.log(filteredData) const toggleProviderExpansion = (provider: string) => { setExpandedProviders((prev) => (prev.includes(provider) ? prev.filter((p) => p !== provider) : [...prev, provider])) } return ( <Card className="w-full max-w-6xl mx-auto"> <CardHeader> <CardTitle>LLM Pricing Comparison Tool</CardTitle> </CardHeader> <CardContent> <div className="mb-4"> <h3 className="text-lg font-semibold mb-2">Select Comparison Models</h3> <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"> {data.map((provider) => ( <Collapsible key={provider.provider} open={expandedProviders.includes(provider.provider)} onOpenChange={() => toggleProviderExpansion(provider.provider)} > <CollapsibleTrigger asChild> <Button variant="outline" className="w-full justify-between"> {provider.provider} {expandedProviders.includes(provider.provider) ? ( <ChevronDown className="h-4 w-4" /> ) : ( <ChevronRight className="h-4 w-4" /> )} </Button> </CollapsibleTrigger> <CollapsibleContent className="mt-2"> {provider.models.map((model) => ( <div key={`${provider.provider}:${model.name}`} className="flex items-center space-x-2 mb-1"> <Checkbox id={`${provider.provider}:${model.name}`} checked={comparisonModels.includes(`${provider.provider}:${model.name}`)} onCheckedChange={(checked) => { if (checked) { setComparisonModels((prev) => [...prev, `${provider.provider}:${model.name}`]) } else { setComparisonModels((prev) => prev.filter((m) => m !== `${provider.provider}:${model.name}`) ) } }} /> <label htmlFor={`${provider.provider}:${model.name}`} className="text-sm font-medium text-gray-700" > {model.name} </label> </div> ))} </CollapsibleContent> </Collapsible> ))} </div> </div> <div className="flex gap-4 mb-4"> <div className="flex-1"> <label htmlFor="inputTokens" className="block text-sm font-medium text-gray-700"> Input Tokens (millions) </label> <Input id="inputTokens" type="number" value={inputTokens} onChange={(e) => setInputTokens(Number(e.target.value))} className="mt-1" /> </div> <div className="flex-1"> <label htmlFor="outputTokens" className="block text-sm font-medium text-gray-700"> Output Tokens (millions) </label> <Input id="outputTokens" type="number" value={outputTokens} onChange={(e) => setOutputTokens(Number(e.target.value))} className="mt-1" /> </div> </div> <p className="italic text-sm text-muted-foreground mb-4"> Note: If you use Amazon Bedrock or Azure prices for Anthropic, Cohere or OpenAI should be the same. </p> <Table> <TableHeader> <TableRow> <TableHead>Provider</TableHead> <TableHead>Model</TableHead> <TableHead>Input Price (per 1M tokens)</TableHead> <TableHead>Output Price (per 1M tokens)</TableHead> <TableHead>Total Price</TableHead> {comparisonModels.map((model) => ( <TableHead key={model} colSpan={2}> Compared to {model} </TableHead> ))} </TableRow> <TableRow> <TableHead> <MultiSelect options={data.map((provider) => ({ label: provider.provider, value: provider.provider })) || []} onValueChange={setSelectedProviders} defaultValue={selectedProviders} /> </TableHead> <TableHead> <MultiSelect options={ data .flatMap((provider) => provider.models) .map((model) => ({ label: model.name, value: model.name })) .reduce((acc: { label: string; value: string }[], curr: { label: string; value: string }) => { if (!acc.find((m) => m.value === curr.value)) { acc.push(curr) } return acc }, []) || [] } defaultValue={selectedModels} onValueChange={setSelectedModels} /> </TableHead> <TableHead /> <TableHead /> <TableHead /> {comparisonModels.flatMap((model) => [ <TableHead key={`${model}-input`}>Input</TableHead>, <TableHead key={`${model}-output`}>Output</TableHead>, ])} </TableRow> </TableHeader> <TableBody> {filteredData.flatMap((provider) => provider.models.map((model) => ( <TableRow key={`${provider.provider}-${model.name}`}> <TableCell> {' '} <a href={provider.uri} className="underline"> {provider.provider} </a> </TableCell> <TableCell>{model.name}</TableCell> <TableCell>${model.inputPrice.toFixed(2)}</TableCell> <TableCell>${model.outputPrice.toFixed(2)}</TableCell> <TableCell className="font-bold"> $ {( calculatePrice(model.inputPrice, inputTokens) + calculatePrice(model.outputPrice, outputTokens) ).toFixed(2)} </TableCell> {comparisonModels.flatMap((comparisonModel) => { const [comparisonProvider, comparisonModelName] = comparisonModel.split(':') const comparisonModelData = data .find((p) => p.provider === comparisonProvider) ?.models.find((m) => m.name === comparisonModelName)! return [ <TableCell key={`${comparisonModel}-input`} className={`${ parseFloat(calculateComparison(model.inputPrice, comparisonModelData.inputPrice)) < 0 ? 'bg-green-100' : parseFloat(calculateComparison(model.inputPrice, comparisonModelData.inputPrice)) > 0 ? 'bg-red-100' : '' }`} > {`${provider.provider}:${model.name}` === comparisonModel ? '0.00%' : `${calculateComparison(model.inputPrice, comparisonModelData.inputPrice)}%`} </TableCell>, <TableCell key={`${comparisonModel}-output`} className={`${ parseFloat(calculateComparison(model.outputPrice, comparisonModelData.outputPrice)) < 0 ? 'bg-green-100' : parseFloat(calculateComparison(model.outputPrice, comparisonModelData.outputPrice)) > 0 ? 'bg-red-100' : '' }`} > {`${provider.provider}:${model.name}` === comparisonModel ? '0.00%' : `${calculateComparison(model.outputPrice, comparisonModelData.outputPrice)}%`} </TableCell>, ] })} </TableRow> )) )} </TableBody> </Table> </CardContent> </Card> ) } export default App