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 import { Switch } from "@/components/ui/switch"; 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 [linkProviderModel, setLinkProviderModel] = useState(false); 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 = selectedProviders.length === 0 && selectedModels.length === 0 && !linkProviderModel ? data.map((provider) => ({ ...provider, models: provider.models, })) : data .filter( (provider) => selectedProviders.length === 0 || selectedProviders.includes(provider.provider) ) .map((provider) => ({ ...provider, models: provider.models.filter((model) => { // If linking is enabled and no models are selected, filter by provider if (linkProviderModel && selectedModels.length === 0) return selectedProviders.includes(provider.provider); // If no models are selected and linking is off, show all models from selected providers (or all if no providers selected) if (!linkProviderModel && selectedModels.length === 0) return ( selectedProviders.length === 0 || selectedProviders.includes(provider.provider) ); // Otherwise, only show selected models return 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] ); }; const getModelsForSelectedProviders = () => { if (!linkProviderModel) { return data .flatMap((provider) => provider.models.map((model) => ({ label: model.name, value: model.name, provider: provider.provider, })) ) .reduce( ( acc: { label: string; value: string; provider: string }[], curr: { label: string; value: string; provider: string } ) => { if (!acc.find((m) => m.value === curr.value)) { acc.push(curr); } return acc; }, [] ); } return data .filter((provider) => selectedProviders.includes(provider.provider)) .flatMap((provider) => provider.models.map((model) => ({ label: model.name, value: model.name, provider: provider.provider, })) ) .reduce( ( acc: { label: string; value: string; provider: string }[], curr: { label: string; value: string; provider: string } ) => { if (!acc.find((m) => m.value === curr.value)) { acc.push(curr); } return acc; }, [] ); }; 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} /> {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;