211 lines · 8.4 KB · TypeScript
RawDownload
import {  type ProviderInstanceId,  type ProviderDriverKind,  type ResolvedKeybindingsConfig,} from "@t3tools/contracts";import { memo, useEffect, useMemo, useState } from "react";import type { VariantProps } from "class-variance-authority";import { ChevronDownIcon } from "lucide-react";import { Button, buttonVariants } from "../ui/button";import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover";import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";import { cn } from "~/lib/utils";import { ModelPickerContent } from "./ModelPickerContent";import { ProviderInstanceIcon } from "./ProviderInstanceIcon";import {  ModelEsque,  getTriggerDisplayModelLabel,  getTriggerDisplayModelName,} from "./providerIconUtils";import type { ProviderInstanceEntry } from "../../providerInstances"; export const ProviderModelPicker = memo(function ProviderModelPicker(props: {  /**   * The instance currently selected in the composer. Drives the trigger   * icon, label and the default-highlighted combobox row.   */  activeInstanceId: ProviderInstanceId;  model: string;  lockedProvider: ProviderDriverKind | null;  lockedContinuationGroupKey?: string | null;  /** Instance entries rendered in the sidebar + used to resolve display name. */  instanceEntries: ReadonlyArray<ProviderInstanceEntry>;  keybindings?: ResolvedKeybindingsConfig;  modelOptionsByInstance: ReadonlyMap<ProviderInstanceId, ReadonlyArray<ModelEsque>>;  activeProviderIconClassName?: string;  compact?: boolean;  disabled?: boolean;  terminalOpen?: boolean;  open?: boolean;  triggerVariant?: VariantProps<typeof buttonVariants>["variant"];  triggerClassName?: string;  onOpenChange?: (open: boolean) => void;  getModelDisabledReason?: (instanceId: ProviderInstanceId, model: string) => string | null;  onInstanceModelChange: (instanceId: ProviderInstanceId, model: string) => void;}) {  const [uncontrolledIsMenuOpen, setUncontrolledIsMenuOpen] = useState(false);  const isMenuOpen = props.open ?? uncontrolledIsMenuOpen;   // Resolve the active instance entry by exact routing key. The composer  // resolves fallbacks before rendering this component; if the selected  // instance disappears, do not infer a replacement from its driver kind.  const activeEntry = useMemo(() => {    return (      props.instanceEntries.find((entry) => entry.instanceId === props.activeInstanceId) ?? null    );  }, [props.activeInstanceId, props.instanceEntries]);   const activeInstanceId = props.activeInstanceId;  const selectedInstanceOptions = props.modelOptionsByInstance.get(activeInstanceId) ?? [];  // If the current slug belongs to a different instance (for example after  // a provider switch or disable), prefer the active instance's first  // option so the trigger icon and label stay in sync instead of showing  // a stale foreign slug.  const selectedModel =    selectedInstanceOptions.find((option) => option.slug === props.model) ??    selectedInstanceOptions[0];  const triggerTitle = selectedModel ? getTriggerDisplayModelName(selectedModel) : props.model;  const triggerLabel = selectedModel ? getTriggerDisplayModelLabel(selectedModel) : props.model;  const duplicateDriverCount = props.instanceEntries.filter(    (entry) => activeEntry !== null && entry.driverKind === activeEntry.driverKind,  ).length;  const showInstanceBadge = Boolean(activeEntry?.accentColor) || duplicateDriverCount > 1;   const setIsMenuOpen = (open: boolean) => {    props.onOpenChange?.(open);    if (props.open === undefined) {      setUncontrolledIsMenuOpen(open);    }  };   useEffect(() => {    if (!isMenuOpen) {      return;    }     const { documentElement, body } = document;    const previousDocumentOverscrollBehavior = documentElement.style.overscrollBehavior;    const previousBodyOverflow = body.style.overflow;    const previousBodyPaddingRight = body.style.paddingRight;    const scrollbarWidth = window.innerWidth - documentElement.clientWidth;     documentElement.style.overscrollBehavior = "contain";    body.style.overflow = "hidden";    if (scrollbarWidth > 0) {      body.style.paddingRight = `${scrollbarWidth}px`;    }     const shouldAllowOverlayScroll = (target: EventTarget | null) => {      return target instanceof Element && target.closest("[data-model-picker-content]");    };    const preventBackgroundWheel = (event: WheelEvent) => {      if (shouldAllowOverlayScroll(event.target)) {        return;      }      event.preventDefault();    };    const preventBackgroundTouchMove = (event: TouchEvent) => {      if (shouldAllowOverlayScroll(event.target)) {        return;      }      event.preventDefault();    };     document.addEventListener("wheel", preventBackgroundWheel, { capture: true, passive: false });    document.addEventListener("touchmove", preventBackgroundTouchMove, {      capture: true,      passive: false,    });     return () => {      document.removeEventListener("wheel", preventBackgroundWheel, { capture: true });      document.removeEventListener("touchmove", preventBackgroundTouchMove, { capture: true });      documentElement.style.overscrollBehavior = previousDocumentOverscrollBehavior;      body.style.overflow = previousBodyOverflow;      body.style.paddingRight = previousBodyPaddingRight;    };  }, [isMenuOpen]);   const handleInstanceModelChange = (instanceId: ProviderInstanceId, model: string) => {    if (props.disabled) return;    props.onInstanceModelChange(instanceId, model);    setIsMenuOpen(false);  };   return (    <Popover      open={isMenuOpen}      onOpenChange={(open) => {        if (props.disabled) {          setIsMenuOpen(false);          return;        }        setIsMenuOpen(open);      }}    >      <PopoverTrigger        render={          <Button            size="sm"            variant={props.triggerVariant ?? "ghost"}            data-chat-provider-model-picker="true"            className={cn(              "min-w-0 justify-between whitespace-nowrap px-2 text-muted-foreground/70 hover:text-foreground/80",              props.compact ? "max-w-42 shrink-0" : "max-w-48 shrink sm:max-w-56 sm:px-3",              props.triggerClassName,            )}            disabled={props.disabled}          />        }      >        <span className="flex min-w-0 flex-1 items-center gap-2">          {activeEntry ? (            <ProviderInstanceIcon              driverKind={activeEntry.driverKind}              displayName={activeEntry.displayName}              accentColor={activeEntry.accentColor}              showBadge={showInstanceBadge}              className={showInstanceBadge ? "size-5" : "size-4"}              iconClassName={cn("size-4", props.activeProviderIconClassName)}              indicatorBackground="var(--input)"              badgeClassName={cn(                "right-[-0.125rem] bottom-[-0.125rem] h-3 min-w-3",                "px-0.5 text-[7px]",              )}            />          ) : null}          <Tooltip>            <TooltipTrigger render={<span className="min-w-0 flex-1 overflow-hidden truncate" />}>              {triggerTitle}            </TooltipTrigger>            <TooltipPopup side="top">{triggerLabel}</TooltipPopup>          </Tooltip>        </span>        <span aria-hidden="true" className="flex items-center">          <ChevronDownIcon aria-hidden="true" className="!ms-0 !-me-1 size-3 shrink-0 opacity-60" />        </span>      </PopoverTrigger>      <PopoverPopup        align="start"        className="border-0 bg-transparent p-0 shadow-none before:hidden [-webkit-backdrop-filter:none]! [--viewport-inline-padding:0] [backdrop-filter:none]!"        viewportClassName="rounded-lg !overflow-hidden p-0"      >        <ModelPickerContent          activeInstanceId={activeInstanceId}          model={props.model}          lockedProvider={props.lockedProvider}          lockedContinuationGroupKey={props.lockedContinuationGroupKey ?? null}          instanceEntries={props.instanceEntries}          {...(props.keybindings ? { keybindings: props.keybindings } : {})}          modelOptionsByInstance={props.modelOptionsByInstance}          terminalOpen={props.terminalOpen ?? false}          onRequestClose={() => setIsMenuOpen(false)}          {...(props.getModelDisabledReason            ? { getModelDisabledReason: props.getModelDisabledReason }            : {})}          onInstanceModelChange={handleInstanceModelChange}        />      </PopoverPopup>    </Popover>  );});