SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
14 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
5.7 KB · 108 lines tsx
Raw Blame History
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