llm-pricing / src /App.tsx
philschmid's picture
philschmid HF staff
fix
69ff6ba
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