519 lines · 17.9 KB · TypeScript
RawDownload
import {  type ProviderKind,  PROVIDER_DISPLAY_NAMES,  type ResolvedKeybindingsConfig,  type ServerProvider,} from "@t3tools/contracts";import { resolveSelectableModel } from "@t3tools/shared/model";import { memo, useMemo, useState, useCallback, useEffect, useLayoutEffect, useRef } from "react";import { SearchIcon } from "lucide-react";import { ModelListRow } from "./ModelListRow";import { ModelPickerSidebar } from "./ModelPickerSidebar";import { isModelPickerNewModel } from "./modelPickerModelHighlights";import { buildModelPickerSearchText, scoreModelPickerSearch } from "./modelPickerSearch";import { Combobox, ComboboxEmpty, ComboboxInput, ComboboxList } from "../ui/combobox";import { ModelEsque, PROVIDER_ICON_BY_PROVIDER } from "./providerIconUtils";import {  modelPickerJumpCommandForIndex,  modelPickerJumpIndexFromCommand,  resolveShortcutCommand,  shortcutLabelForCommand,} from "../../keybindings";import { useSettings, useUpdateSettings } from "~/hooks/useSettings";import { cn } from "~/lib/utils";import { TooltipProvider } from "../ui/tooltip"; type ModelPickerItem = {  slug: string;  name: string;  shortName?: string;  subProvider?: string;  provider: ProviderKind;}; const EMPTY_MODEL_JUMP_LABELS = new Map<string, string>(); export const ModelPickerContent = memo(function ModelPickerContent(props: {  provider: ProviderKind;  model: string;  lockedProvider: ProviderKind | null;  providers?: ReadonlyArray<ServerProvider>;  keybindings?: ResolvedKeybindingsConfig;  modelOptionsByProvider: Record<ProviderKind, ReadonlyArray<ModelEsque>>;  terminalOpen: boolean;  onRequestClose?: () => void;  onProviderModelChange: (provider: ProviderKind, model: string) => void;}) {  const { keybindings: providedKeybindings, modelOptionsByProvider, onProviderModelChange } = props;  const [searchQuery, setSearchQuery] = useState("");  const searchInputRef = useRef<HTMLInputElement>(null);  const listRegionRef = useRef<HTMLDivElement>(null);  const highlightedModelKeyRef = useRef<string | null>(null);  const favorites = useSettings((s) => s.favorites ?? []);  const [selectedProvider, setSelectedProvider] = useState<ProviderKind | "favorites">(() => {    if (props.lockedProvider !== null) {      return props.lockedProvider;    }    return favorites.length > 0 ? "favorites" : props.provider;  });  const keybindings = useMemo<ResolvedKeybindingsConfig>(    () => providedKeybindings ?? [],    [providedKeybindings],  );  const { updateSettings } = useUpdateSettings();   const focusSearchInput = useCallback(() => {    searchInputRef.current?.focus({ preventScroll: true });  }, []);   const handleSelectProvider = useCallback(    (provider: ProviderKind | "favorites") => {      setSelectedProvider(provider);      window.requestAnimationFrame(() => {        focusSearchInput();      });    },    [focusSearchInput],  );   useLayoutEffect(() => {    focusSearchInput();    const frame = window.requestAnimationFrame(() => {      focusSearchInput();    });    const timeout = window.setTimeout(() => {      focusSearchInput();    }, 0);    return () => {      window.cancelAnimationFrame(frame);      window.clearTimeout(timeout);    };  }, [focusSearchInput]);   // Create a Set for efficient lookup  const favoritesSet = useMemo(() => {    return new Set(favorites.map((fav) => `${fav.provider}:${fav.model}`));  }, [favorites]);  const favoriteOrder = useMemo(() => {    return new Map(      favorites.map((favorite, index) => [`${favorite.provider}:${favorite.model}`, index]),    );  }, [favorites]);   const readyProviderSet = useMemo(() => {    if (!props.providers || props.providers.length === 0) {      return null;    }    return new Set(      props.providers        .filter((provider) => provider.status === "ready")        .map((provider) => provider.provider),    );  }, [props.providers]);   // Flatten models into a searchable array  const flatModels = useMemo(() => {    return Object.entries(props.modelOptionsByProvider).flatMap(([providerKind, models]) => {      if (readyProviderSet && !readyProviderSet.has(providerKind as ProviderKind)) {        return [];      }      return models.map((m) => ({        slug: m.slug,        name: m.name,        ...(m.shortName ? { shortName: m.shortName } : {}),        ...(m.subProvider ? { subProvider: m.subProvider } : {}),        provider: providerKind as ProviderKind,      })) satisfies Array<ModelPickerItem>;    });  }, [props.modelOptionsByProvider, readyProviderSet]);   // Filter models based on search query and selected provider  const filteredModels = useMemo(() => {    let result = flatModels;     // Apply tokenized fuzzy search across the combined provider/model search fields.    if (searchQuery.trim()) {      const rankedMatches = result        .map((model) => ({          model,          score: scoreModelPickerSearch(            {              ...model,              isFavorite: favoritesSet.has(`${model.provider}:${model.slug}`),            },            searchQuery,          ),          isFavorite: favoritesSet.has(`${model.provider}:${model.slug}`),          tieBreaker: buildModelPickerSearchText(model),        }))        .filter(          (            rankedModel,          ): rankedModel is {            model: ModelPickerItem;            score: number;            isFavorite: boolean;            tieBreaker: string;          } => rankedModel.score !== null,        );       // When searching, we only respect locked provider, ignoring sidebar selection      if (props.lockedProvider !== null) {        return rankedMatches          .filter((rankedModel) => rankedModel.model.provider === props.lockedProvider)          .toSorted((a, b) => {            const scoreDelta = a.score - b.score;            if (scoreDelta !== 0) {              return scoreDelta;            }            if (a.isFavorite !== b.isFavorite) {              return a.isFavorite ? -1 : 1;            }            return a.tieBreaker.localeCompare(b.tieBreaker);          })          .map((rankedModel) => rankedModel.model);      }       return rankedMatches        .toSorted((a, b) => {          const scoreDelta = a.score - b.score;          if (scoreDelta !== 0) {            return scoreDelta;          }          if (a.isFavorite !== b.isFavorite) {            return a.isFavorite ? -1 : 1;          }          return a.tieBreaker.localeCompare(b.tieBreaker);        })        .map((rankedModel) => rankedModel.model);    }     // Locked provider mode always shows that provider's models, with favorites first.    if (props.lockedProvider !== null) {      result = result.filter((m) => m.provider === props.lockedProvider);    } else if (selectedProvider === "favorites") {      result = result.filter((m) => favoritesSet.has(`${m.provider}:${m.slug}`));    } else {      result = result.filter((m) => m.provider === selectedProvider);    }     return result.toSorted((a, b) => {      const aOrder = favoriteOrder.get(`${a.provider}:${a.slug}`);      const bOrder = favoriteOrder.get(`${b.provider}:${b.slug}`);       if (aOrder !== undefined && bOrder !== undefined) {        return aOrder - bOrder;      }      if (aOrder !== undefined) {        return -1;      }      if (bOrder !== undefined) {        return 1;      }      return 0;    });  }, [    favoriteOrder,    favoritesSet,    flatModels,    props.lockedProvider,    searchQuery,    selectedProvider,  ]);   const handleModelSelect = useCallback(    (modelSlug: string, provider: ProviderKind) => {      const resolvedModel = resolveSelectableModel(        provider,        modelSlug,        modelOptionsByProvider[provider],      );      if (resolvedModel) {        onProviderModelChange(provider, resolvedModel);      }    },    [modelOptionsByProvider, onProviderModelChange],  );   const toggleFavorite = useCallback(    (provider: ProviderKind, model: string) => {      const newFavorites = [...favorites];      const index = newFavorites.findIndex((f) => f.provider === provider && f.model === model);      if (index >= 0) {        newFavorites.splice(index, 1);      } else {        newFavorites.push({ provider, model });      }      updateSettings({ favorites: newFavorites });    },    [favorites, updateSettings],  );   const isLocked = props.lockedProvider !== null;  const isSearching = searchQuery.trim().length > 0;  const showSidebar = !isLocked && !isSearching;  const LockedProviderIcon =    isLocked && props.lockedProvider ? PROVIDER_ICON_BY_PROVIDER[props.lockedProvider] : null;  const modelJumpCommandByKey = useMemo(() => {    const mapping = new Map<      string,      NonNullable<ReturnType<typeof modelPickerJumpCommandForIndex>>    >();    for (const [visibleModelIndex, model] of filteredModels.entries()) {      const jumpCommand = modelPickerJumpCommandForIndex(visibleModelIndex);      if (!jumpCommand) {        return mapping;      }      mapping.set(`${model.provider}:${model.slug}`, jumpCommand);    }    return mapping;  }, [filteredModels]);  const modelJumpModelKeys = useMemo(    () => [...modelJumpCommandByKey.keys()],    [modelJumpCommandByKey],  );  const allModelKeys = useMemo(    (): string[] => flatModels.map((model) => `${model.provider}:${model.slug}`),    [flatModels],  );  const filteredModelKeys = useMemo(    (): string[] => filteredModels.map((model) => `${model.provider}:${model.slug}`),    [filteredModels],  );  const filteredModelByKey = useMemo(    (): ReadonlyMap<string, ModelPickerItem> =>      new Map(filteredModels.map((model) => [`${model.provider}:${model.slug}`, model] as const)),    [filteredModels],  );  const modelJumpShortcutContext = useMemo(    () =>      ({        terminalFocus: false,        terminalOpen: props.terminalOpen,        modelPickerOpen: true,      }) as const,    [props.terminalOpen],  );  const modelJumpLabelByKey = useMemo((): ReadonlyMap<string, string> => {    if (modelJumpCommandByKey.size === 0) {      return EMPTY_MODEL_JUMP_LABELS;    }    const shortcutLabelOptions = {      platform: navigator.platform,      context: modelJumpShortcutContext,    };    const mapping = new Map<string, string>();    for (const [modelKey, command] of modelJumpCommandByKey) {      const label = shortcutLabelForCommand(keybindings, command, shortcutLabelOptions);      if (label) {        mapping.set(modelKey, label);      }    }    return mapping.size > 0 ? mapping : EMPTY_MODEL_JUMP_LABELS;  }, [keybindings, modelJumpCommandByKey, modelJumpShortcutContext]);   useEffect(() => {    const onWindowKeyDown = (event: globalThis.KeyboardEvent) => {      if (event.defaultPrevented || event.repeat) {        return;      }       const command = resolveShortcutCommand(event, keybindings, {        platform: navigator.platform,        context: modelJumpShortcutContext,      });      const jumpIndex = modelPickerJumpIndexFromCommand(command ?? "");      if (jumpIndex === null) {        return;      }       const targetModelKey = modelJumpModelKeys[jumpIndex];      if (!targetModelKey) {        return;      }      const [provider, slug] = targetModelKey.split(":") as [ProviderKind, string];      event.preventDefault();      event.stopPropagation();      handleModelSelect(slug, provider);    };     window.addEventListener("keydown", onWindowKeyDown, true);     return () => {      window.removeEventListener("keydown", onWindowKeyDown, true);    };  }, [handleModelSelect, keybindings, modelJumpModelKeys, modelJumpShortcutContext]);   useLayoutEffect(() => {    const listRegion = listRegionRef.current;    if (!listRegion) {      return;    }     let cancelled = false;    let frame = 0;    let nestedFrame = 0;    let timeout = 0;     const measureScrollArea = () => {      if (cancelled) {        return;      }      const viewport = listRegion.querySelector<HTMLElement>('[data-slot="scroll-area-viewport"]');      if (!viewport || viewport.scrollHeight <= viewport.clientHeight) {        return;      }      const originalScrollTop = viewport.scrollTop;      const maxScrollTop = viewport.scrollHeight - viewport.clientHeight;      if (maxScrollTop <= 0) {        return;      }      viewport.scrollTop = Math.min(originalScrollTop + 1, maxScrollTop);      viewport.scrollTop = originalScrollTop;    };     queueMicrotask(measureScrollArea);    frame = window.requestAnimationFrame(() => {      measureScrollArea();      nestedFrame = window.requestAnimationFrame(measureScrollArea);    });    timeout = window.setTimeout(measureScrollArea, 0);     return () => {      cancelled = true;      window.cancelAnimationFrame(frame);      window.cancelAnimationFrame(nestedFrame);      window.clearTimeout(timeout);    };  }, [filteredModelKeys]);   return (    <TooltipProvider delay={0}>      <div        className={cn(          "relative flex h-screen max-h-96 w-screen max-w-100 overflow-hidden rounded-lg border bg-popover not-dark:bg-clip-padding text-popover-foreground shadow-lg/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",          isLocked ? "flex-col" : "flex-row",        )}      >        {/* Locked provider header (only shown in locked mode) */}        {isLocked && LockedProviderIcon && props.lockedProvider && (          <div className="flex items-center gap-2 px-4 py-3 border-b">            <LockedProviderIcon className="size-5 shrink-0" />            <span className="font-medium text-sm">              {PROVIDER_DISPLAY_NAMES[props.lockedProvider]}            </span>          </div>        )}         {/* Sidebar (only in unlocked mode) */}        {showSidebar && (          <ModelPickerSidebar            selectedProvider={selectedProvider}            onSelectProvider={handleSelectProvider}            {...(props.providers && { providers: props.providers })}          />        )}         {/* Main content area */}        <Combobox          inline          items={allModelKeys}          filteredItems={filteredModelKeys}          filter={null}          autoHighlight          open          value={`${props.provider}:${props.model}`}          onItemHighlighted={(modelKey) => {            highlightedModelKeyRef.current = typeof modelKey === "string" ? modelKey : null;          }}          onValueChange={(modelKey) => {            if (typeof modelKey !== "string") {              return;            }            const [provider, slug] = modelKey.split(":") as [ProviderKind, string];            handleModelSelect(slug, provider);          }}        >          <div            className={cn(              "flex min-h-0 flex-1 flex-col overflow-hidden",              isLocked ? "min-w-0" : showSidebar && "border-l",            )}          >            {/* Search bar */}            <div className="border-b px-3 py-2">              <ComboboxInput                ref={searchInputRef}                className="[&_input]:font-sans rounded-md"                inputClassName="border-0 shadow-none ring-0 focus-visible:ring-0"                placeholder="Search models..."                showTrigger={false}                startAddon={<SearchIcon className="size-4 shrink-0 text-muted-foreground/50" />}                value={searchQuery}                onChange={(e) => setSearchQuery(e.target.value)}                onKeyDown={(e) => {                  if (e.key === "Escape") {                    e.preventDefault();                    e.stopPropagation();                    props.onRequestClose?.();                    return;                  }                  if (e.key === "Enter" && highlightedModelKeyRef.current) {                    (                      e as typeof e & { preventBaseUIHandler?: () => void }                    ).preventBaseUIHandler?.();                    e.preventDefault();                    e.stopPropagation();                    const [provider, slug] = highlightedModelKeyRef.current.split(":") as [                      ProviderKind,                      string,                    ];                    handleModelSelect(slug, provider);                    return;                  }                  e.stopPropagation();                }}                onMouseDown={(e) => e.stopPropagation()}                onTouchStart={(e) => e.stopPropagation()}                size="sm"              />            </div>             {/* Model list */}            <div              ref={listRegionRef}              className="relative min-h-0 flex-1 before:pointer-events-none before:absolute before:inset-0 before:bg-muted/40"            >              <ComboboxList className="model-picker-list size-full divide-y px-2 py-1">                {filteredModelKeys.map((modelKey, index) => {                  const model = filteredModelByKey.get(modelKey);                  if (!model) {                    return null;                  }                  return (                    <ModelListRow                      key={modelKey}                      index={index}                      model={model}                      provider={model.provider}                      isFavorite={favoritesSet.has(modelKey)}                      showProvider={!isLocked}

Lines 1–500 of 519.