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 · 71 lines tsx
Raw Blame History
1"use client";2import * as React from "react";3import { Brain, Crown, X, Zap } from "lucide-react";4import { useApp } from "@/components/app/store";5import { ModelSelector } from "@/components/chat/model-selector";6import { ProviderIcon } from "@/components/brand/provider-icon";7import { Tooltip } from "@/components/ui/tooltip";8import { toast } from "@/components/ui/toast";9import { cn } from "@/lib/utils";10import { MAX_MODELS, presetModels, type PresetId } from "./types";1112const PRESETS: { id: PresetId; label: string; icon: React.ReactNode; hint: string }[] = [13  { id: "flagships", label: "Flagships", icon: <Crown className="size-3.5" />, hint: "The top model of each connected provider" },14  { id: "fast", label: "Fast", icon: <Zap className="size-3.5" />, hint: "Low-latency, low-cost tier" },15  { id: "reasoning", label: "Reasoning", icon: <Brain className="size-3.5" />, hint: "Models with extended thinking" },16];1718/** Selected-model chips + "Add model" + quick presets. One scrollable row on phones, wraps from `md`. */19export function ArenaModelPicker({ selected, onChange, disabled, className }: { selected: string[]; onChange: (keys: string[]) => void; disabled?: boolean; className?: string }) {20  const { models, modelsByKey, connectedProviders } = useApp();21  const set = React.useMemo(() => new Set(selected), [selected]);2223  const toggle = (key: string) => {24    if (set.has(key)) return onChange(selected.filter((k) => k !== key));25    if (selected.length >= MAX_MODELS) return toast.warning(`Up to ${MAX_MODELS} models`, "Remove one to add another.");26    onChange([...selected, key]);27  };2829  const applyPreset = (id: PresetId) => {30    const keys = presetModels(id, models, connectedProviders);31    if (!keys.length) return toast.info("No matching models", "Connect a provider or pick models manually.");32    onChange(keys);33  };3435  return (36    <div className={cn("flex items-center gap-2 overflow-x-auto scrollbar-none md:flex-wrap", disabled && "pointer-events-none opacity-60", className)} aria-disabled={disabled} data-no-edge-swipe>37      {selected.map((key, i) => {38        const m = modelsByKey.get(key);39        return (40          <span key={key} className="group inline-flex h-9 max-w-[220px] shrink-0 items-center gap-2 rounded-lg border border-border bg-bg-elevated pl-2.5 pr-1 text-[13px] shadow-xs animate-fade-up" style={{ animationDelay: `${i * 30}ms` }}>41            <ProviderIcon provider={m?.provider ?? key.split("/")[0]} size={14} />42            <span className="truncate font-medium">{m?.displayName ?? key}</span>43            <button type="button" onClick={() => toggle(key)} className="tap rounded p-1 text-fg-subtle transition-colors hover:bg-bg-muted hover:text-danger" aria-label={`Remove ${m?.displayName ?? key}`}>44              <X className="size-3.5" />45            </button>46          </span>47        );48      })}49      {selected.length < MAX_MODELS ? (50        <span className="shrink-0">51          <ModelSelector value={null} multiple selected={set} onToggle={toggle} buttonLabel={selected.length ? "Add model" : "Choose models"} size="md" />52        </span>53      ) : null}54      <span className="shrink-0 text-[12px] tabular-nums text-fg-subtle">55        {selected.length}/{MAX_MODELS}56      </span>57      <span className="hidden h-5 w-px shrink-0 bg-border sm:block" aria-hidden />58      <div className="flex min-w-0 items-center gap-1 overflow-x-auto scrollbar-none" role="group" aria-label="Quick presets">59        {PRESETS.map((p) => (60          <Tooltip key={p.id} content={p.hint}>61            <button type="button" onClick={() => applyPreset(p.id)} className="tap inline-flex h-8 items-center gap-1.5 rounded-md px-2.5 text-[12.5px] font-medium text-fg-muted transition-colors hover:bg-bg-muted hover:text-fg">62              {p.icon}63              {p.label}64            </button>65          </Tooltip>66        ))}67      </div>68    </div>69  );70}71