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'; // Assuming you have this file for mock data interface FlattenedModel extends Model { provider: string; uri: string; } 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([]); const [comparisonModels, setComparisonModels] = useState([]); const [inputTokens, setInputTokens] = useState(1); const [outputTokens, setOutputTokens] = useState(1); const [selectedProviders, setSelectedProviders] = useState([]); const [selectedModels, setSelectedModels] = useState([]); const [expandedProviders, setExpandedProviders] = useState([]); const [tokenCalculation, setTokenCalculation] = useState('million'); const [sortConfig, setSortConfig] = useState<{ key: keyof FlattenedModel; direction: string; } | null>(null); useEffect(() => { setData(mockData); }, []); const calculatePrice = (price: number, tokens: number): number => { let multiplier = 1; if (tokenCalculation === 'thousand') { multiplier = 1e-3; } else if (tokenCalculation === 'unit') { multiplier = 1e-6; } else if (tokenCalculation === 'billion') { multiplier = 1e3; } return price * tokens * multiplier; }; const calculateComparison = ( modelPrice: number, comparisonPrice: number ): string => { return (((modelPrice - comparisonPrice) / comparisonPrice) * 100).toFixed(2); }; const flattenData = (data: Provider[]) => { return data.flatMap((provider) => provider.models.map((model) => ({ provider: provider.provider, uri: provider.uri, ...model, })) ); }; 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); const sortedFlattenedData = React.useMemo(() => { let sortableData: FlattenedModel[] = flattenData(filteredData); if (sortConfig !== null) { sortableData.sort((a, b) => { const aValue = a[sortConfig.key]; const bValue = b[sortConfig.key]; if (typeof aValue === 'string' && typeof bValue === 'string') { return sortConfig.direction === 'ascending' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue); } else if (typeof aValue === 'number' && typeof bValue === 'number') { return sortConfig.direction === 'ascending' ? aValue - bValue : bValue - aValue; } else { return 0; } }); } return sortableData; }, [filteredData, sortConfig]); const requestSort = (key: keyof FlattenedModel) => { let direction = 'ascending'; if ( sortConfig && sortConfig.key === key && sortConfig.direction === 'ascending' ) { direction = 'descending'; } setSortConfig({ key, direction }); }; const toggleProviderExpansion = (provider: string) => { setExpandedProviders((prev) => prev.includes(provider) ? prev.filter((p) => p !== provider) : [...prev, provider] ); }; return ( LLM Pricing Calculator

This is a fork of philschmid tool: philschmid/llm-pricing

Select Comparison Models

{data.map((provider) => ( toggleProviderExpansion(provider.provider)} > {provider.models.map((model) => (
{ if (checked) { setComparisonModels((prev) => [ ...prev, `${provider.provider}:${model.name}`, ]); } else { setComparisonModels((prev) => prev.filter( (m) => m !== `${provider.provider}:${model.name}` ) ); } }} />
))}
))}
setInputTokens(Number(e.target.value))} className="mt-1" />
setOutputTokens(Number(e.target.value))} className="mt-1" />

Note: If you use Amazon Bedrock or Azure prices for Anthropic, Cohere or OpenAI should be the same.

Total Price (per {tokenCalculation} tokens){' '} {comparisonModels.map((model) => ( Compared to {model} ))} ({ label: provider.provider, value: provider.provider, })) || [] } onValueChange={setSelectedProviders} defaultValue={selectedProviders} /> 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} /> {comparisonModels.flatMap((model) => [ Input, Output, ])} {sortedFlattenedData.map((item) => ( {' '} {item.provider} {item.name} {item.inputPrice.toFixed(2)} {item.outputPrice.toFixed(2)} $ {( calculatePrice(item.inputPrice, inputTokens) + calculatePrice(item.outputPrice, outputTokens) ).toFixed(2)} {comparisonModels.flatMap((comparisonModel) => { const [comparisonProvider, comparisonModelName] = comparisonModel.split(':'); const comparisonModelData = data .find((p) => p.provider === comparisonProvider) ?.models.find((m) => m.name === comparisonModelName)!; return [ 0 ? 'bg-red-100' : '' }`} > {`${item.provider}:${item.name}` === comparisonModel ? '0.00%' : `${calculateComparison( item.inputPrice, comparisonModelData.inputPrice )}%`} , 0 ? 'bg-red-100' : '' }`} > {`${item.provider}:${item.name}` === comparisonModel ? '0.00%' : `${calculateComparison( item.outputPrice, comparisonModelData.outputPrice )}%`} , ]; })} ))}
); }; export default App;