import React, { useState, useEffect, useMemo } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { mockData } from "@/lib/data"; import { ComparisonSelector } from "@/components/ComparisonSelector"; import { PricingTable } from "@/components/PricingTable"; import { BenchmarkTable } from "./components/BenchmarkTable"; import { benchmarkData } from "./lib/benchmarks/ index"; import { BenchmarkComparisonSelector } from "./components/BenchmarkComparisonSelector"; import { benchmarkMetricOrder } from "./lib/benchmarks/types"; export interface FlattenedModel extends Model { provider: string; uri: string; benchmark?: { [key: string]: number; }; } 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 [benchmarkComparisonMetrics, setBenchmarkComparisonMetrics] = useState([]); const [selectedBenchmarkProviders, setSelectedBenchmarkProviders] = useState([]); const [selectedBenchmarkModels, setSelectedBenchmarkModels] = useState([]); const [sortConfig, setSortConfig] = useState<{ key: keyof FlattenedModel; direction: string; } | null>(null); const [benchmarkSortConfig, setBenchmarkSortConfig] = useState<{ key: string; direction: "ascending" | "descending"; } | null>(null); useEffect(() => { setData(mockData); }, []); const flattenDataFromPricing = (data: Provider[]): FlattenedModel[] => data.flatMap((provider) => provider.models.map((model) => ({ provider: provider.provider, uri: provider.uri, ...model, benchmark: {}, })) ); const flattenDataFromBenchmarks = (): FlattenedModel[] => benchmarkData.map((b) => ({ provider: b.provider ?? "Unknown", uri: b.source, // placeholder or use an optional `b.link` if available name: b.model, inputPrice: b.inputPrice, outputPrice: b.outputPrice, benchmark: b.benchmark ?? {}, })); const filteredData = useMemo(() => { if (!selectedProviders.length && !selectedModels.length) return data; return data .filter((p) => !selectedProviders.length || selectedProviders.includes(p.provider)) .map((p) => ({ ...p, models: p.models.filter((m) => { if (!selectedModels.length) return selectedProviders.includes(p.provider); if (!selectedModels.length) return !selectedProviders.length || selectedProviders.includes(p.provider); return selectedModels.includes(m.name); }), })) .filter((p) => p.models.length > 0); }, [data, selectedProviders, selectedModels]); const benchmarkedModels = useMemo(() => { return flattenDataFromBenchmarks(); }, []); const filteredBenchmarkedModels = useMemo(() => { return benchmarkedModels.filter((model) => { const providerMatch = selectedBenchmarkProviders.length === 0 || selectedBenchmarkProviders.includes(model.provider); const modelMatch = selectedBenchmarkModels.length === 0 || selectedBenchmarkModels.includes(model.name); // When not linking, allow filtering by either if (selectedBenchmarkProviders.length > 0 && selectedBenchmarkModels.length > 0) { return providerMatch || modelMatch; } return providerMatch && modelMatch; // this handles the case where one or both are empty }); }, [ benchmarkedModels, selectedBenchmarkProviders, selectedBenchmarkModels, ]); const sortedBenchmarkedModels = useMemo(() => { if (!benchmarkSortConfig) return filteredBenchmarkedModels; return [...filteredBenchmarkedModels].sort((a, b) => { const key = benchmarkSortConfig.key; const isTopLevelKey = ["provider", "name", "inputPrice", "outputPrice"].includes(key); const aVal = isTopLevelKey ? (a as any)[key] : a.benchmark?.[key] ?? -Infinity; const bVal = isTopLevelKey ? (b as any)[key] : b.benchmark?.[key] ?? -Infinity; if (typeof aVal === "string" && typeof bVal === "string") { return benchmarkSortConfig.direction === "ascending" ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal); } return benchmarkSortConfig.direction === "ascending" ? aVal - bVal : bVal - aVal; }); }, [filteredBenchmarkedModels, benchmarkSortConfig]); const pricingProviders = useMemo(() => { const grouped: Record = {}; flattenDataFromPricing(data).forEach((model) => { const key = model.provider; if (!grouped[key]) grouped[key] = []; grouped[key].push(model); }); return Object.entries(grouped).map(([provider, models]) => ({ provider, uri: models[0]?.uri ?? "#", models: models.map(({ name, inputPrice, outputPrice }) => ({ name, inputPrice, outputPrice, })), })); }, [data]); const benchmarkProviders = useMemo(() => { const grouped: Record = {}; benchmarkedModels.forEach((model) => { const key = model.provider; if (!grouped[key]) grouped[key] = []; grouped[key].push(model); }); return Object.entries(grouped).map(([provider, models]) => ({ provider, uri: models[0]?.uri ?? "#", models: models.map(({ name, inputPrice, outputPrice }) => ({ name, inputPrice, outputPrice, })), })); }, [benchmarkedModels]); const sortedFlattenedData = useMemo(() => { const flattened = flattenDataFromPricing(filteredData); if (!sortConfig) return flattened; return [...flattened].sort((a, b) => { const aVal = a[sortConfig.key]; const bVal = b[sortConfig.key]; if (typeof aVal === "string" && typeof bVal === "string") { return sortConfig.direction === "ascending" ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal); } else if (typeof aVal === "number" && typeof bVal === "number") { return sortConfig.direction === "ascending" ? aVal - bVal : bVal - aVal; } return 0; }); }, [filteredData, sortConfig]); const requestSort = (key: keyof FlattenedModel) => { const direction = sortConfig?.key === key && sortConfig.direction === "ascending" ? "descending" : "ascending"; setSortConfig({ key, direction }); }; const toggleProviderExpansion = (provider: string) => { setExpandedProviders((prev) => prev.includes(provider) ? prev.filter((p) => p !== provider) : [...prev, provider] ); }; return ( LLM Pricing Calculator {/* Source Link */}

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

{/* Comparison Model Selector */}

Select Comparison Models

setComparisonModels((prev) => checked ? [...prev, modelId] : prev.filter((m) => m !== modelId) ) } /> {/* Token Inputs */}
setInputTokens(Number(e.target.value))} />
setOutputTokens(Number(e.target.value))} />
{/* Pricing Table */}

Pricing Table

{/* Benchmark Table */}

Select Benchmark Metrics to Compare

benchmarkedModels.some((m) => m.benchmark?.[metric] !== undefined) )} selected={benchmarkComparisonMetrics} onChange={(metric, checked) => setBenchmarkComparisonMetrics((prev) => checked ? [...prev, metric] : prev.filter((m) => m !== metric) ) } />

Benchmark Table

{ setBenchmarkSortConfig((prev) => prev?.key === key ? { key, direction: prev.direction === "ascending" ? "descending" : "ascending" } : { key, direction: "descending" } ); }} sortConfig={benchmarkSortConfig} allProviders={benchmarkProviders} selectedProviders={selectedBenchmarkProviders} selectedModels={selectedBenchmarkModels} onProviderChange={setSelectedBenchmarkProviders} onModelChange={setSelectedBenchmarkModels} />
); }; export default App;