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%
4.3 KB · 74 lines tsx
Raw Blame History
1"use client";2import * as React from "react";3import { Coins, Gauge, Timer, Zap } from "lucide-react";4import type { LiveMetrics } from "@/lib/arena/metrics";5import type { ColumnStatus } from "./types";6import { cn, formatMs, formatTokens, formatUsd } from "@/lib/utils";78export const STATUS_META: Record<ColumnStatus, { label: string; dot: string; pulse: boolean }> = {9  idle: { label: "Ready", dot: "bg-fg-subtle", pulse: false },10  waiting: { label: "Queued", dot: "bg-fg-subtle", pulse: true },11  thinking: { label: "Thinking", dot: "bg-accent", pulse: true },12  streaming: { label: "Streaming", dot: "bg-info", pulse: true },13  done: { label: "Done", dot: "bg-success", pulse: false },14  error: { label: "Error", dot: "bg-danger", pulse: false },15  stopped: { label: "Stopped", dot: "bg-warning", pulse: false },16};1718export function StatusDot({ status, className }: { status: ColumnStatus; className?: string }) {19  const s = STATUS_META[status];20  return <span className={cn("inline-block size-2 shrink-0 rounded-full", s.dot, s.pulse && "animate-pulse-soft", className)} aria-hidden />;21}2223/** A shared clock: re-renders the caller every `ms` while `active`. Returns `Date.now()` snapshots. */24export function useTick(active: boolean, ms = 500): number {25  const [now, setNow] = React.useState(() => Date.now());26  React.useEffect(() => {27    if (!active) return;28    // eslint-disable-next-line react-hooks/set-state-in-effect -- clock start29    setNow(Date.now());30    const t = setInterval(() => setNow(Date.now()), ms);31    return () => clearInterval(t);32  }, [active, ms]);33  return now;34}3536export function formatTps(v: number | null): string {37  return v ? `${v} tok/s` : "—";38}3940/**41 * Compact metrics strip shown under each Arena response: status, elapsed/TTFT, tok/s, tokens, cost.42 * Estimates (while streaming) are prefixed with ≈; exact server numbers are not.43 */44export function MetricsStrip({ metrics: m, showCosts = true, fastest, cheapest, className }: { metrics: LiveMetrics; showCosts?: boolean; fastest?: boolean; cheapest?: boolean; className?: string }) {45  const approx = m.exact ? "" : "≈ ";46  const live = m.status === "waiting" || m.status === "thinking" || m.status === "streaming";47  const tokens = m.inputTokens === null && m.outputTokens === null ? "—" : `${formatTokens(m.inputTokens ?? 0)} → ${formatTokens(m.outputTokens ?? 0)}`;48  return (49    <dl className={cn("grid grid-cols-3 gap-x-3 gap-y-1.5 text-[11.5px] tabular-nums sm:grid-cols-5", className)} aria-live={live ? "polite" : undefined}>50      <Metric icon={<Timer />} label={m.ttftMs !== null ? "TTFT" : "Elapsed"} value={m.ttftMs !== null ? formatMs(m.ttftMs) : formatMs(m.elapsedMs)} highlight={fastest} hint={m.ttftMs !== null && live ? `${formatMs(m.elapsedMs)} total` : m.ttftMs !== null && !live ? `${formatMs(m.elapsedMs)} total` : undefined} />51      <Metric icon={<Zap />} label="Speed" value={formatTps(m.tokensPerSecond)} />52      <Metric icon={<Gauge />} label="Tokens" value={`${m.exact || tokens === "—" ? "" : approx}${tokens}`} hint={m.reasoningTokens ? `+${formatTokens(m.reasoningTokens)} reasoning` : m.cachedTokens ? `${formatTokens(m.cachedTokens)} cached` : undefined} />53      {showCosts ? <Metric icon={<Coins />} label="Cost" value={m.costUsd === null ? "—" : `${approx}${formatUsd(m.costUsd, { precise: m.costUsd < 0.01 })}`} highlight={cheapest} title={m.exact ? "From provider usage and list price" : "Estimated from characters streamed so far"} /> : null}54      <Metric label="Status" value={STATUS_META[m.status].label} dot={<StatusDot status={m.status} />} className="hidden sm:block" />55    </dl>56  );57}5859function Metric({ icon, label, value, hint, highlight, title, dot, className }: { icon?: React.ReactNode; label: string; value: string; hint?: string; highlight?: boolean; title?: string; dot?: React.ReactNode; className?: string }) {60  return (61    <div className={cn("min-w-0", className)} title={title}>62      <dt className="flex items-center gap-1 text-[10.5px] uppercase tracking-wide text-fg-subtle [&_svg]:size-3">63        {icon}64        {label}65      </dt>66      <dd className={cn("flex items-center gap-1.5 truncate font-medium", highlight ? "text-success" : "text-fg")}>67        {dot}68        {value}69      </dd>70      {hint ? <dd className="truncate text-[10.5px] text-fg-subtle">{hint}</dd> : null}71    </div>72  );73}74