TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1"use client";2import * as React from "react";3import { Brain, Eye, FileText, Globe, Wrench, Braces, Zap, Maximize2 } from "lucide-react";4import type { PolyModel } from "@/lib/client/types";5import { Badge } from "@/components/ui/badge";6import { Tooltip } from "@/components/ui/tooltip";7import { useApp } from "@/components/app/store";8import { BadgeChips, LifecycleChip } from "@/components/models/badge-chips";9import { buildBadgeContext, deriveBadges, isFastModel as isFastModelBase, lifecycleStatus, retiresSoon as retiresSoonBase, shutdownDateOf, LONG_CONTEXT_TOKENS, type BadgeContext } from "@/lib/models/badges";10import { formatPrice } from "@/lib/models/format";11import { formatTokens } from "@/lib/utils";12import { cn } from "@/lib/utils";1314/** Capability glyph definitions shared by rows, profile and catalog (order = display order). */15export const CAPABILITY_GLYPHS: { key: keyof PolyModel["capabilities"]; label: string; icon: React.ReactNode; title: string }[] = [16 { key: "vision", label: "Vision", icon: <Eye />, title: "Understands images" },17 { key: "reasoning", label: "Reasoning", icon: <Brain />, title: "Extended reasoning / thinking" },18 { key: "tools", label: "Tools", icon: <Wrench />, title: "Function / tool calling" },19 { key: "webSearch", label: "Web", icon: <Globe />, title: "Provider-native web search" },20 { key: "structuredOutput", label: "JSON", icon: <Braces />, title: "Structured output (JSON schema)" },21 { key: "files", label: "PDF", icon: <FileText />, title: "PDF / file input" },22];2324export function capabilityList(m: PolyModel) {25 const out: { key: string; label: string; icon: React.ReactNode; title: string }[] = CAPABILITY_GLYPHS.filter((g) => m.capabilities[g.key]).map((g) => ({ key: g.key, label: g.label, icon: g.icon, title: g.title }));26 if ((m.limits?.contextTokens ?? 0) >= LONG_CONTEXT_TOKENS) out.push({ key: "long", label: formatTokens(m.limits!.contextTokens!), icon: <Maximize2 />, title: "Long context window" });27 return out;28}2930/** Kept for existing callers (arena auto-pick). Prefer `speedTier()` from `lib/models/badges` for new code. */31export function isFastModel(m: PolyModel): boolean {32 return isFastModelBase(m);33}3435export const retiresSoon = retiresSoonBase;3637/** Registry-wide badge context (pricing quantiles, "new" window) memoized on the store's model list. */38export function useBadgeContext(): BadgeContext {39 const { models } = useApp();40 const [now] = React.useState(() => Date.now());41 return React.useMemo(() => buildBadgeContext(models, now), [models, now]);42}4344/** Compact icon-only capability row: Vision · Reasoning · Tools · Web · JSON · PDF. */45export function CapabilityGlyphs({ model, max = 6, className, size = "sm" }: { model: PolyModel; max?: number; className?: string; size?: "sm" | "md" }) {46 const on = CAPABILITY_GLYPHS.filter((g) => model.capabilities[g.key]).slice(0, max);47 if (!on.length) return <span className={cn("text-[11px] text-fg-subtle", className)}>text only</span>;48 return (49 <span className={cn("inline-flex items-center gap-0.5", className)} aria-label={on.map((g) => g.label).join(", ")}>50 {on.map((g) => (51 <Tooltip key={g.key} content={g.title}>52 <span className={cn("flex items-center justify-center rounded-md text-fg-muted", size === "sm" ? "size-5 [&_svg]:size-3" : "size-6 bg-bg-muted [&_svg]:size-3.5")}>{g.icon}</span>53 </Tooltip>54 ))}55 </span>56 );57}5859export function CapabilityBadges({ model, compact, max = 6, className }: { model: PolyModel; compact?: boolean; max?: number; className?: string }) {60 const caps = capabilityList(model).slice(0, max);61 return (62 <div className={cn("flex flex-wrap items-center gap-1", className)}>63 {isFastModel(model) ? (64 <Tooltip content="Fast / low-cost tier">65 <Badge variant="outline" className="gap-1">66 <Zap /> {compact ? null : "Fast"}67 </Badge>68 </Tooltip>69 ) : null}70 {caps.map((c) => (71 <Tooltip key={c.key} content={c.title}>72 <Badge variant="default" className="gap-1">73 {c.icon} {compact ? null : c.label}74 </Badge>75 </Tooltip>76 ))}77 </div>78 );79}8081/** NEW / FAST / CHEAP / REASONING / VISION / CODING / LONG CONTEXT from real registry data. */82export function ModelBadges({ model, ctx, max = 3, className, size }: { model: PolyModel; ctx: BadgeContext; max?: number; className?: string; size?: "xs" | "sm" }) {83 const badges = React.useMemo(() => deriveBadges(model, ctx), [model, ctx]);84 return <BadgeChips badges={badges} max={max} className={className} size={size} />;85}8687export function PriceLabel({ model, className }: { model: PolyModel; className?: string }) {88 const p = model.pricing;89 if (!p || (p.inputPerMillion === undefined && p.outputPerMillion === undefined)) return <span className={cn("text-fg-subtle", className)}>price n/a</span>;90 return (91 <span className={cn("tabular-nums text-fg-subtle", className)} title="USD per 1M tokens — input / output">92 {formatPrice(p.inputPerMillion)} / {formatPrice(p.outputPerMillion)} per 1M93 </span>94 );95}9697/**98 * Lifecycle badge: New / Active / Preview / Deprecated / Retiring / Unavailable.99 * `showActive` renders the green "Active" chip too (catalog); otherwise Active/New-less models render nothing.100 */101export function StatusBadge({ model, connected, ctx, showActive = false, className }: { model: PolyModel; connected?: boolean; ctx?: BadgeContext; showActive?: boolean; className?: string }) {102 const [now] = React.useState(() => Date.now());103 const lc = lifecycleStatus(model, { connected, ctx, now });104 if (lc.status === "active" && !showActive) return null;105 const sd = shutdownDateOf(model);106 return <LifecycleChip lifecycle={lc.status === "retiring" && sd ? { ...lc, label: `Retires ${sd}` } : lc} className={className} />;107}108