| import * as React from "react"; | |
| import { | |
| Table, | |
| TableHeader, | |
| TableBody, | |
| TableRow, | |
| TableHead, | |
| TableCell, | |
| } from "@/components/ui/table"; | |
| import { MultiSelect } from "@/components/ui/multi-select"; | |
| import { Provider, FlattenedModel } from "@/App"; | |
| interface BenchmarkTableProps { | |
| data: FlattenedModel[]; | |
| comparisonMetrics?: string[]; | |
| requestSort: (key: string) => void; | |
| sortConfig: { | |
| key: string; | |
| direction: "ascending" | "descending"; | |
| } | null; | |
| allProviders: Provider[]; | |
| selectedProviders: string[]; | |
| selectedModels: string[]; | |
| onProviderChange: (values: string[]) => void; | |
| onModelChange: (values: string[]) => void; | |
| } | |
| export const BenchmarkTable: React.FC<BenchmarkTableProps> = ({ | |
| data, | |
| comparisonMetrics, | |
| requestSort, | |
| sortConfig, | |
| allProviders, | |
| selectedProviders, | |
| selectedModels, | |
| onProviderChange, | |
| onModelChange, | |
| }) => { | |
| const benchmarkMetrics = React.useMemo(() => { | |
| if (!comparisonMetrics || comparisonMetrics.length === 0) return []; | |
| return comparisonMetrics; | |
| }, [comparisonMetrics]); | |
| const getCellStyle = (value?: number) => { | |
| if (value === undefined) return ""; | |
| if (value >= 85) return "bg-green-100"; | |
| if (value >= 60) return "bg-yellow-100"; | |
| return "bg-red-100"; | |
| }; | |
| const renderSortIndicator = (key: string) => { | |
| if (sortConfig?.key !== key) return null; | |
| return sortConfig.direction === "ascending" ? " ▲" : " ▼"; | |
| }; | |
| const modelOptions = React.useMemo(() => { | |
| const flat = allProviders.flatMap((provider) => | |
| provider.models.map((model) => ({ | |
| label: model.name, | |
| value: model.name, | |
| provider: provider.provider, | |
| })) | |
| ); | |
| const filtered = flat.filter((m) => | |
| !selectedProviders.length || selectedProviders.includes(m.provider) | |
| ); | |
| return Array.from(new Map(filtered.map((m) => [m.value, m])).values()); | |
| }, [allProviders, selectedProviders]); | |
| return ( | |
| <> | |
| <div className="flex items-center space-x-4 mb-4"> | |
| <div className="flex-1"> | |
| <label className="text-sm font-medium block mb-1">Providers</label> | |
| <MultiSelect | |
| options={allProviders.map((p) => ({ label: p.provider, value: p.provider }))} | |
| defaultValue={selectedProviders} | |
| onValueChange={onProviderChange} | |
| /> | |
| </div> | |
| <div className="flex-1"> | |
| <label className="text-sm font-medium block mb-1">Models</label> | |
| <MultiSelect | |
| options={modelOptions.map(({ label, value }) => ({ label, value }))} | |
| defaultValue={selectedModels} | |
| onValueChange={onModelChange} | |
| /> | |
| </div> | |
| </div> | |
| <Table> | |
| <TableHeader> | |
| <TableRow> | |
| <TableHead> | |
| <button | |
| onClick={() => requestSort("provider")} | |
| className="font-medium underline underline-offset-2" | |
| > | |
| Provider{renderSortIndicator("provider")} | |
| </button> | |
| </TableHead> | |
| <TableHead> | |
| <button | |
| onClick={() => requestSort("name")} | |
| className="font-medium underline underline-offset-2" | |
| > | |
| Model{renderSortIndicator("name")} | |
| </button> | |
| </TableHead> | |
| <TableHead> | |
| <button | |
| onClick={() => requestSort("inputPrice")} | |
| className="font-medium underline underline-offset-2" | |
| > | |
| Input Price (USD){renderSortIndicator("inputPrice")} | |
| </button> | |
| </TableHead> | |
| <TableHead> | |
| <button | |
| onClick={() => requestSort("outputPrice")} | |
| className="font-medium underline underline-offset-2" | |
| > | |
| Output Price (USD){renderSortIndicator("outputPrice")} | |
| </button> | |
| </TableHead> | |
| {benchmarkMetrics.map((metric) => ( | |
| <TableHead key={metric}> | |
| <button | |
| onClick={() => requestSort(metric)} | |
| className="font-medium underline underline-offset-2" | |
| > | |
| {metric.replace(/_/g, " ").toUpperCase()} | |
| {renderSortIndicator(metric)} | |
| </button> | |
| </TableHead> | |
| ))} | |
| </TableRow> | |
| </TableHeader> | |
| <TableBody> | |
| {data.map((model) => ( | |
| <TableRow key={`${model.provider}-${model.name}`}> | |
| <TableCell> | |
| <a href={model.uri} className="underline"> | |
| {model.provider} | |
| </a> | |
| </TableCell> | |
| <TableCell>{model.name}</TableCell> | |
| <TableCell>${model.inputPrice.toFixed(2)}</TableCell> | |
| <TableCell>${model.outputPrice.toFixed(2)}</TableCell> | |
| {benchmarkMetrics.map((metric) => { | |
| const score = model.benchmark?.[metric]; | |
| return ( | |
| <TableCell key={metric} className={getCellStyle(score)}> | |
| {score !== undefined ? `${score.toFixed(1)}%` : "-"} | |
| </TableCell> | |
| ); | |
| })} | |
| </TableRow> | |
| ))} | |
| </TableBody> | |
| </Table> | |
| </> | |
| ); | |
| }; | |