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 · 92 lines tsx
Raw Blame History
1"use client";2import * as React from "react";3import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup, SelectLabel } from "@/components/ui/select";4import { ProviderIcon } from "@/components/brand/provider-icon";5import { useApp } from "@/components/app/store";6import { PROVIDERS, PROVIDER_ORDER } from "@/lib/client/providers";7import type { PolyModel, ProviderId } from "@/lib/client/types";89const NONE = "__none__";1011export interface ModelSelectProps {12  value: string | null;13  onChange: (key: string | null) => void;14  /** Only list models whose provider has a valid key (default true). */15  connectedOnly?: boolean;16  includeDeprecated?: boolean;17  allowNone?: boolean;18  noneLabel?: string;19  placeholder?: string;20  disabled?: boolean;21  id?: string;22  className?: string;23}2425/** Model picker grouped by provider, with provider glyphs. Shows a disabled hint when nothing is connected. */26export function ModelSelect({ value, onChange, connectedOnly = true, includeDeprecated = false, allowNone, noneLabel = "None", placeholder = "Choose a model", disabled, id, className }: ModelSelectProps) {27  const { models, modelsByKey, connectedProviders } = useApp();2829  const groups = React.useMemo(() => {30    const out: { provider: ProviderId; models: PolyModel[] }[] = [];31    for (const p of PROVIDER_ORDER) {32      if (connectedOnly && !connectedProviders.has(p)) continue;33      const list = models.filter((m) => m.provider === p && (includeDeprecated || m.status !== "deprecated"));34      if (list.length) out.push({ provider: p, models: list });35    }36    return out;37  }, [models, connectedProviders, connectedOnly, includeDeprecated]);3839  const selected = value ? modelsByKey.get(value) : undefined;40  const empty = groups.length === 0;41  // Keep a stale/unknown selection visible so the user understands what is stored.42  const unknownSelected = value && !selected;4344  return (45    <Select value={value ?? (allowNone ? NONE : undefined)} onValueChange={(v) => onChange(v === NONE ? null : v)} disabled={disabled || (empty && !allowNone)}>46      <SelectTrigger id={id} className={className} aria-label="Model">47        <SelectValue placeholder={empty ? "Connect a provider first" : placeholder}>48          {selected ? (49            <span className="flex items-center gap-2">50              <ProviderIcon provider={selected.provider} size={14} />51              <span className="truncate">{selected.displayName}</span>52              <span className="truncate font-mono text-[11px] text-fg-subtle">{selected.id}</span>53            </span>54          ) : unknownSelected ? (55            <span className="flex items-center gap-2 text-fg-muted">56              <ProviderIcon provider={value?.split("/")[0]} size={14} />57              <span className="truncate font-mono text-xs">{value}</span>58            </span>59          ) : allowNone && !value ? (60            <span className="text-fg-muted">{noneLabel}</span>61          ) : undefined}62        </SelectValue>63      </SelectTrigger>64      <SelectContent className="max-h-80">65        {allowNone ? <SelectItem value={NONE}>{noneLabel}</SelectItem> : null}66        {unknownSelected ? (67          <SelectItem value={value!} className="font-mono text-xs text-fg-muted">68            {value} (not available)69          </SelectItem>70        ) : null}71        {groups.map((g) => (72          <SelectGroup key={g.provider}>73            <SelectLabel className="flex items-center gap-1.5">74              <ProviderIcon provider={g.provider} size={12} /> {PROVIDERS[g.provider].name}75            </SelectLabel>76            {g.models.map((m) => (77              <SelectItem key={m.key} value={m.key}>78                <span className="flex items-center gap-2">79                  <span>{m.displayName}</span>80                  {m.status === "preview" ? <span className="rounded bg-info-soft px-1 text-[10px] font-medium text-info">preview</span> : null}81                  {m.status === "deprecated" ? <span className="rounded bg-warning-soft px-1 text-[10px] font-medium text-warning">deprecated</span> : null}82                </span>83              </SelectItem>84            ))}85          </SelectGroup>86        ))}87        {empty && !allowNone ? <div className="px-2 py-3 text-center text-xs text-fg-subtle">No connected providers. Add a key in Settings → Providers.</div> : null}88      </SelectContent>89    </Select>90  );91}92