import { ANTIGRAVITY_DEFAULT_MODEL, type ProviderInstanceId, type ProviderDriverKind, type ResolvedKeybindingsConfig, } from "@t3tools/contracts"; import { memo, useEffect, useMemo, useState } from "react"; import type { VariantProps } from "class-variance-authority"; import { Badge } from "../ui/badge"; import { 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, resolveModelPickerSelectedModel } from "./ModelPickerContent"; import { ProviderInstanceIcon } from "./ProviderInstanceIcon"; import { ModelEsque, getTriggerDisplayModelLabel, getTriggerDisplayModelName, } from "./providerIconUtils"; import { shouldShowInstanceBadge, type ProviderInstanceEntry } from "../../providerInstances"; import { ComposerControl, ComposerControlChevron, type ComposerControlSize, } from "./ComposerControl"; import { composerFloatingLayerProps } from "./composerEventScope"; 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; keybindings?: ResolvedKeybindingsConfig; modelOptionsByInstance: ReadonlyMap>; activeProviderIconClassName?: string; instanceIndicatorBackground?: string; size?: ComposerControlSize; compact?: boolean; isComposerOwned?: boolean; disabled?: boolean; terminalOpen?: boolean; open?: boolean; triggerVariant?: VariantProps["variant"]; triggerClassName?: string; triggerAriaLabel?: string; onOpenChange?: (open: boolean) => void; onOpenProviderSetup?: (instanceId: ProviderInstanceId) => void; getModelDisabledReason?: (instanceId: ProviderInstanceId, model: string) => string | null; onInstanceModelChange: (instanceId: ProviderInstanceId, model: string) => void; }) { const [uncontrolledIsMenuOpen, setUncontrolledIsMenuOpen] = useState(false); const isMenuOpen = props.open ?? uncontrolledIsMenuOpen; const size = props.size ?? "sm"; // 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) ?? []; // Account-specific catalogs must keep the selected model label while unavailable. const selectedModel = resolveModelPickerSelectedModel({ driverKind: activeEntry?.driverKind, model: props.model, options: selectedInstanceOptions, }) ?? (activeEntry?.driverKind === "opencode" || activeEntry?.driverKind === "antigravity" ? undefined : selectedInstanceOptions[0]); const triggerTitle = selectedModel ? getTriggerDisplayModelName(selectedModel) : props.model === ANTIGRAVITY_DEFAULT_MODEL ? "Choose model" : props.model || "Choose model"; const triggerLabel = selectedModel ? `${getTriggerDisplayModelLabel(selectedModel)}${selectedModel.isUnavailable ? " (Unavailable)" : ""}` : triggerTitle; const showInstanceBadge = activeEntry !== null && shouldShowInstanceBadge(activeEntry, props.instanceEntries); 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 ( { if (props.disabled) { setIsMenuOpen(false); return; } setIsMenuOpen(open); }} > } > {activeEntry ? ( ) : null} } > {triggerTitle} {triggerLabel} {selectedModel?.isUnavailable ? ( Unavailable ) : null} setIsMenuOpen(false)} {...(props.onOpenProviderSetup ? { onOpenProviderSetup: props.onOpenProviderSetup } : {})} {...(props.getModelDisabledReason ? { getModelDisabledReason: props.getModelDisabledReason } : {})} onInstanceModelChange={handleInstanceModelChange} /> ); });