SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
3.8 KB · 79 lines tsx
Raw Blame History
1"use client";2import * as React from "react";3import { Coins, Timer, Trophy, Type } from "lucide-react";4import { ProviderIcon } from "@/components/brand/provider-icon";5import { Badge } from "@/components/ui/badge";6import { criterionById, type Criterion, type WinnerResult } from "@/lib/arena/scoring";7import { formatDelta } from "@/lib/arena/metrics";8import { cn } from "@/lib/utils";9import { BlindAvatar } from "./blind";1011interface Props {12  winner: WinnerResult;13  name: string;14  provider: string | null;15  /** Blind Arena, identity still hidden → neutral avatar and letter label. */16  hidden?: boolean;17  blindIndex?: number;18  customCriteria?: Criterion[];19  showCosts?: boolean;20  className?: string;21}2223/**24 * Arena Winner card: shown once every response is final and at least one vote exists.25 * Most criteria won; ties go to the fastest first token. Deltas compare the winner with the mean of the others.26 */27export function WinnerCard({ winner, name, provider, hidden, blindIndex = 0, customCriteria = [], showCosts = true, className }: Props) {28  const criteria = winner.criteriaWon.map((id) => criterionById(id, customCriteria));29  const d = winner.deltas;30  const better = (v: number | null) => (v === null ? null : v < 0);31  return (32    <section className={cn("animate-pop overflow-hidden rounded-xl border border-accent/40 bg-accent-soft/40 p-3.5 sm:p-4", className)} aria-label="Arena winner">33      <div className="flex items-start gap-3">34        <span className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-accent text-accent-fg shadow-glow">35          <Trophy className="size-5" />36        </span>37        <div className="min-w-0 flex-1">38          <p className="text-[11px] font-medium uppercase tracking-[0.12em] text-accent">Arena winner</p>39          <div className="mt-0.5 flex min-w-0 items-center gap-2">40            {hidden ? <BlindAvatar index={blindIndex} size={20} /> : <ProviderIcon provider={provider} size={16} />}41            <h3 className="truncate text-[16px] font-semibold tracking-tight">{name}</h3>42          </div>43          <p className="mt-1 text-[12.5px] text-fg-muted">44            Won {criteria.length} of your criteri{criteria.length === 1 ? "on" : "a"}45            {winner.tieBreak === "fastest" ? " — tie broken by time to first token" : winner.tieBreak === "order" ? " — tie broken by order" : ""}.46          </p>47          <ul className="mt-2 flex flex-wrap gap-1.5" aria-label="Criteria won">48            {criteria.map((c) => (49              <li key={c.id}>50                <Badge variant="accent">{c.label}</Badge>51              </li>52            ))}53          </ul>54        </div>55      </div>56      {d.others > 0 ? (57        <dl className="mt-3 grid grid-cols-3 gap-2 border-t border-accent/20 pt-3 text-[12px] tabular-nums">58          {showCosts ? <Delta icon={<Coins />} label="Δ cost" value={formatDelta(d.costUsd, "usd")} good={better(d.costUsd)} hint="vs. others" /> : null}59          <Delta icon={<Timer />} label="Δ TTFT" value={formatDelta(d.ttftMs, "ms")} good={better(d.ttftMs)} hint="vs. others" />60          <Delta icon={<Type />} label="Δ output" value={formatDelta(d.outputTokens, "tokens")} good={null} hint="vs. others" />61        </dl>62      ) : null}63    </section>64  );65}6667function Delta({ icon, label, value, good, hint }: { icon: React.ReactNode; label: string; value: string; good: boolean | null; hint: string }) {68  return (69    <div className="min-w-0">70      <dt className="flex items-center gap-1 text-[10.5px] uppercase tracking-wide text-fg-subtle [&_svg]:size-3">71        {icon}72        {label}73      </dt>74      <dd className={cn("truncate text-[13px] font-semibold", good === true ? "text-success" : good === false ? "text-warning" : "text-fg")}>{value}</dd>75      <dd className="truncate text-[10.5px] text-fg-subtle">{hint}</dd>76    </div>77  );78}79