import { ANTIGRAVITY_DEFAULT_MODEL, type ProviderInstanceId, type ProviderDriverKind, type ResolvedKeybindingsConfig,} from "@t3tools/contracts";import { resolveSelectableModel } from "@t3tools/shared/model";import { useAtomValue } from "@effect/atom-react";import { LegendList, type LegendListRef } from "@legendapp/list/react";import { memo, useMemo, useState, useCallback, useEffect, useLayoutEffect, useRef } from "react";import { ChevronRightIcon, SearchIcon } from "lucide-react";import { ModelListRow } from "./ModelListRow";import { ModelPickerSidebar } from "./ModelPickerSidebar";import { getProviderStatusMessage, hasProviderSetup } from "./ProviderStatusBanner";import { modelPickerLegacySectionKey, modelPickerModelKey, parseModelPickerLegacySectionKey, parseModelPickerModelKey,} from "./modelPickerKeys";import { buildModelPickerSearchText, scoreModelPickerSearch } from "./modelPickerSearch";import { Combobox, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxListVirtualized,} from "../ui/combobox";import { ModelEsque } from "./providerIconUtils";import { isCommandPaletteOpen } from "../../commandPaletteBus";import { primaryServerKeybindingsAtom } from "../../state/server";import { modelPickerJumpCommandForIndex, modelPickerJumpIndexFromCommand, resolveShortcutCommand, shortcutLabelForCommand,} from "../../keybindings";import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings";import { cn } from "~/lib/utils";import { getVirtualizedScrollFadeClassName } from "../ui/scroll-area";import { TooltipProvider } from "../ui/tooltip";import { Button } from "../ui/button";import { isProviderInstancePickerReady, isProviderInstancePickerVisible, type ProviderInstanceEntry,} from "../../providerInstances";import { providerModelKey, sortProviderModelItems } from "../../modelOrdering"; type ModelPickerItem = { slug: string; name: string; shortName?: string; subProvider?: string; badge?: "new"; instanceId: ProviderInstanceId; driverKind: ProviderDriverKind; instanceDisplayName: string; instanceAccentColor?: string | undefined; continuationGroupKey?: string | undefined; isLegacy?: boolean | undefined; isUnavailable?: boolean | undefined;}; export function resolveModelPickerSelectedModel(input: { driverKind: ProviderDriverKind | undefined; model: string; options: ReadonlyArray<ModelEsque>;}) { if (input.driverKind === "antigravity" && input.model === ANTIGRAVITY_DEFAULT_MODEL) { const availableModels = input.options.filter( (option) => option.slug !== ANTIGRAVITY_DEFAULT_MODEL && !option.isUnavailable, ); return ( availableModels.find((option) => option.aliases?.includes(ANTIGRAVITY_DEFAULT_MODEL)) ?? availableModels.find((option) => option.isDefault) ); } return input.options.find((option) => option.slug === input.model);} export function shouldIncludeModelPickerOption(input: { readonly entry: ProviderInstanceEntry; readonly option: ModelEsque; readonly activeInstanceId: ProviderInstanceId; readonly activeModel: string;}): boolean { if (input.entry.driverKind === "antigravity" && input.option.slug === ANTIGRAVITY_DEFAULT_MODEL) { return false; } if (isProviderInstancePickerReady(input.entry)) return true; return ( input.entry.enabled && (input.entry.driverKind === "opencode" || input.entry.driverKind === "antigravity") && input.entry.instanceId === input.activeInstanceId && input.option.slug === input.activeModel && input.option.isUnavailable === true );} export function shouldOfferModelPickerSetup( entry: ProviderInstanceEntry, options: ReadonlyArray<ModelEsque>,): boolean { return ( entry.enabled && entry.status !== "disabled" && hasProviderSetup(entry.snapshot) && (!isProviderInstancePickerReady(entry) || !entry.installed || entry.snapshot.auth.status === "unauthenticated" || !options.some((option) => !option.isUnavailable)) );} const EMPTY_MODEL_JUMP_LABELS = new Map<string, string>(); function ModelListSeparator() { return <div className="h-0.5" />;} export const ModelPickerContent = memo(function ModelPickerContent(props: { /** The instance currently selected in the composer (combobox "value"). */ activeInstanceId: ProviderInstanceId; model: string; /** * When set, the picker is locked to the given driver kind — typically * because the user is editing a previously-sent message and can't change * which driver served the turn. Multiple instances of the same kind * remain selectable (e.g. locked to `codex` still lets the user switch * between the default Codex and a custom Codex Personal). */ lockedProvider: ProviderDriverKind | null; lockedContinuationGroupKey?: string | null; /** * All configured provider instances in display order. Used to render * the sidebar (one button per instance) and to resolve display names * for the locked-mode header. */ instanceEntries: ReadonlyArray<ProviderInstanceEntry>; keybindings?: ResolvedKeybindingsConfig; /** * Model options per instance. Keyed by `ProviderInstanceId` so the * default Codex instance and any custom Codex instances each have their * own list (custom instances typically start with the same built-in * model set but are free to diverge via customModels). */ modelOptionsByInstance: ReadonlyMap<ProviderInstanceId, ReadonlyArray<ModelEsque>>; terminalOpen: boolean; onRequestClose?: () => void; onOpenProviderSetup?: (instanceId: ProviderInstanceId) => void; getModelDisabledReason?: (instanceId: ProviderInstanceId, model: string) => string | null; onInstanceModelChange: (instanceId: ProviderInstanceId, model: string) => void;}) { const { keybindings: providedKeybindings, modelOptionsByInstance, instanceEntries, getModelDisabledReason, onInstanceModelChange, } = props; const [searchQuery, setSearchQuery] = useState(""); const [showTopScrollFade, setShowTopScrollFade] = useState(false); const [showBottomScrollFade, setShowBottomScrollFade] = useState(false); const searchInputRef = useRef<HTMLInputElement>(null); const modelListRef = useRef<LegendListRef | null>(null); const highlightedModelKeyRef = useRef<string | null>(null); const favorites = useClientSettings((s) => s.favorites ?? []); const activeEntry = props.instanceEntries.find( (entry) => entry.instanceId === props.activeInstanceId, ); const activeModel = resolveModelPickerSelectedModel({ driverKind: activeEntry?.driverKind, model: props.model, options: modelOptionsByInstance.get(props.activeInstanceId) ?? [], }); const activeModelSlug = activeModel?.slug ?? (props.model === ANTIGRAVITY_DEFAULT_MODEL ? "" : props.model); const activeModelKey = activeModelSlug ? modelPickerModelKey(props.activeInstanceId, activeModelSlug) : null; const activeInstanceHasSelectableUnavailableModel = activeEntry !== undefined && (modelOptionsByInstance.get(props.activeInstanceId) ?? []).some((option) => shouldIncludeModelPickerOption({ entry: activeEntry, option, activeInstanceId: props.activeInstanceId, activeModel: activeModelSlug, }), ) && !isProviderInstancePickerReady(activeEntry); const activeInstanceNeedsSetup = props.onOpenProviderSetup !== undefined && activeEntry !== undefined && shouldOfferModelPickerSetup( activeEntry, modelOptionsByInstance.get(props.activeInstanceId) ?? [], ); const [selectedInstanceId, setSelectedInstanceId] = useState<ProviderInstanceId | "favorites">( () => { if ( props.lockedProvider !== null || activeInstanceHasSelectableUnavailableModel || activeInstanceNeedsSetup ) { // Keep the active instance visible when it is locked or needs setup. return props.activeInstanceId; } return favorites.length > 0 ? "favorites" : props.activeInstanceId; }, ); const [expandedLegacyInstances, setExpandedLegacyInstances] = useState( () => new Set<ProviderInstanceId>( modelOptionsByInstance .get(props.activeInstanceId) ?.some((model) => model.slug === activeModelSlug && model.isLegacy) ? [props.activeInstanceId] : [], ), ); const serverKeybindings = useAtomValue(primaryServerKeybindingsAtom); const keybindings = providedKeybindings ?? serverKeybindings; const updateSettings = useUpdateClientSettings(); const focusSearchInput = useCallback(() => { searchInputRef.current?.focus({ preventScroll: true }); }, []); const handleSelectInstance = useCallback( (instanceId: ProviderInstanceId | "favorites") => { setSelectedInstanceId(instanceId); 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. Favorites are keyed by // `${instanceId}:${slug}`; the storage schema widened from ProviderDriverKind // to ProviderInstanceId so pre-migration favorites keyed by driver slugs // (e.g. `"codex:gpt-5"`) still resolve — the default instance id equals // the driver slug. const favoritesSet = useMemo(() => { return new Set(favorites.map((fav) => providerModelKey(fav.provider, fav.model))); }, [favorites]); /** * Lookup table keyed by `instanceId`. Used for display name + driver * kind enrichment and for `ready`/enabled filtering before flattening * models into the search list. */ const entryByInstanceId = useMemo( () => new Map(instanceEntries.map((entry) => [entry.instanceId, entry])), [instanceEntries], ); const matchesLockedProvider = useCallback( (entry: Pick<ProviderInstanceEntry, "driverKind" | "continuationGroupKey">): boolean => { if (props.lockedProvider === null) return true; if (entry.driverKind !== props.lockedProvider) return false; if (!props.lockedContinuationGroupKey) return true; return entry.continuationGroupKey === props.lockedContinuationGroupKey; }, [props.lockedContinuationGroupKey, props.lockedProvider], ); const selectableUnavailableInstanceIds = useMemo(() => { const instanceIds = new Set<ProviderInstanceId>(); if (activeInstanceHasSelectableUnavailableModel) { instanceIds.add(props.activeInstanceId); } if (props.onOpenProviderSetup) { for (const entry of instanceEntries) { if ( shouldOfferModelPickerSetup(entry, modelOptionsByInstance.get(entry.instanceId) ?? []) ) { instanceIds.add(entry.instanceId); } } } return instanceIds.size > 0 ? instanceIds : undefined; }, [ activeInstanceHasSelectableUnavailableModel, instanceEntries, modelOptionsByInstance, props.activeInstanceId, props.onOpenProviderSetup, ]); // Flatten models into a searchable array. One pass over the // instance-keyed map; each model carries its instance id + driver kind // so the list row can render the right icon and display name without // another lookup. const flatModels = useMemo(() => { const out: ModelPickerItem[] = []; for (const [instanceId, models] of modelOptionsByInstance) { const entry = entryByInstanceId.get(instanceId); if (!entry) { // Instance disappeared between renders (configuration change). Skip // its models — stale options shouldn't appear in the picker. continue; } for (const model of models) { if ( !shouldIncludeModelPickerOption({ entry, option: model, activeInstanceId: props.activeInstanceId, activeModel: activeModelSlug, }) ) { continue; } out.push({ slug: model.slug, name: model.name, ...(model.shortName ? { shortName: model.shortName } : {}), ...(model.subProvider ? { subProvider: model.subProvider } : {}), ...(model.badge ? { badge: model.badge } : {}), ...(model.isLegacy ? { isLegacy: true } : {}), ...(model.isUnavailable ? { isUnavailable: true } : {}), instanceId, driverKind: entry.driverKind, instanceDisplayName: entry.displayName, ...(entry.accentColor ? { instanceAccentColor: entry.accentColor } : {}), ...(entry.continuationGroupKey ? { continuationGroupKey: entry.continuationGroupKey } : {}), }); } } return out; }, [modelOptionsByInstance, entryByInstanceId, props.activeInstanceId, activeModelSlug]); const isLocked = props.lockedProvider !== null; const isSearching = searchQuery.trim().length > 0; const lockedDisabledInstanceIds = useMemo(() => { if (!isLocked) { return undefined; } const disabled = new Set<ProviderInstanceId>(); for (const entry of instanceEntries) { if (!matchesLockedProvider(entry)) { disabled.add(entry.instanceId); } } return disabled; }, [instanceEntries, isLocked, matchesLockedProvider]); const sidebarInstanceEntries = useMemo(() => { const enabledEntries = instanceEntries.filter(isProviderInstancePickerVisible); if (!isLocked) { return enabledEntries; } const available: ProviderInstanceEntry[] = []; const disabled: ProviderInstanceEntry[] = []; for (const entry of enabledEntries) { if (matchesLockedProvider(entry)) { available.push(entry); } else { disabled.push(entry); } } return [...available, ...disabled]; }, [instanceEntries, isLocked, matchesLockedProvider]); const showSidebar = !isSearching && sidebarInstanceEntries.length > 0; const instanceOrder = useMemo( () => instanceEntries.map((entry) => entry.instanceId), [instanceEntries], ); // Filter models based on search query and selected instance 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( { name: model.name, ...(model.shortName ? { shortName: model.shortName } : {}), ...(model.subProvider ? { subProvider: model.subProvider } : {}), driverKind: model.driverKind, providerDisplayName: model.instanceDisplayName, isFavorite: favoritesSet.has(providerModelKey(model.instanceId, model.slug)), }, searchQuery, ), isFavorite: favoritesSet.has(providerModelKey(model.instanceId, model.slug)), tieBreaker: buildModelPickerSearchText({ name: model.name, ...(model.shortName ? { shortName: model.shortName } : {}), ...(model.subProvider ? { subProvider: model.subProvider } : {}), driverKind: model.driverKind, providerDisplayName: model.instanceDisplayName, }), })) .filter( ( rankedModel, ): rankedModel is { model: ModelPickerItem; score: number; isFavorite: boolean; tieBreaker: string; } => rankedModel.score !== null, ); // When searching, we only respect locked provider (by driver kind), // ignoring sidebar selection so account-scoped searches can find a // model before the user chooses a specific instance rail item. if (props.lockedProvider !== null) { const lockedProviderMatches: Array<(typeof rankedMatches)[number]> = []; for (const rankedModel of rankedMatches) { if (matchesLockedProvider(rankedModel.model)) { lockedProviderMatches.push(rankedModel); } } return lockedProviderMatches .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); } if (props.lockedProvider !== null) { result = result.filter((m) => matchesLockedProvider(m)); if (selectedInstanceId === "favorites") { result = result.filter((m) => favoritesSet.has(providerModelKey(m.instanceId, m.slug))); } else { result = result.filter((m) => m.instanceId === selectedInstanceId); } } else if (selectedInstanceId === "favorites") { result = result.filter((m) => favoritesSet.has(providerModelKey(m.instanceId, m.slug))); } else { result = result.filter((m) => m.instanceId === selectedInstanceId); } return sortProviderModelItems(result, { favoriteModelKeys: favoritesSet, groupFavorites: selectedInstanceId !== "favorites", instanceOrder: selectedInstanceId === "favorites" ? instanceOrder : [], }); }, [ favoritesSet, flatModels, instanceOrder, matchesLockedProvider, props.lockedProvider, searchQuery, selectedInstanceId, ]); const legacySection = useMemo(() => { if (isSearching || selectedInstanceId === "favorites") { return null; } const currentModels = filteredModels.filter((model) => !model.isLegacy); const legacyModels = filteredModels.filter((model) => model.isLegacy); if (legacyModels.length === 0) { return null;Lines 1–500 of 957. Load more
import { ANTIGRAVITY_DEFAULT_MODEL, type ProviderInstanceId, type ProviderDriverKind, type ResolvedKeybindingsConfig, } from "@t3tools/contracts"; import { resolveSelectableModel } from "@t3tools/shared/model"; import { useAtomValue } from "@effect/atom-react"; import { LegendList, type LegendListRef } from "@legendapp/list/react"; import { memo, useMemo, useState, useCallback, useEffect, useLayoutEffect, useRef } from "react"; import { ChevronRightIcon, SearchIcon } from "lucide-react"; import { ModelListRow } from "./ModelListRow"; import { ModelPickerSidebar } from "./ModelPickerSidebar"; import { getProviderStatusMessage, hasProviderSetup } from "./ProviderStatusBanner"; import { modelPickerLegacySectionKey, modelPickerModelKey, parseModelPickerLegacySectionKey, parseModelPickerModelKey, } from "./modelPickerKeys"; import { buildModelPickerSearchText, scoreModelPickerSearch } from "./modelPickerSearch"; import { Combobox, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxListVirtualized, } from "../ui/combobox"; import { ModelEsque } from "./providerIconUtils"; import { isCommandPaletteOpen } from "../../commandPaletteBus"; import { primaryServerKeybindingsAtom } from "../../state/server"; import { modelPickerJumpCommandForIndex, modelPickerJumpIndexFromCommand, resolveShortcutCommand, shortcutLabelForCommand, } from "../../keybindings"; import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings"; import { cn } from "~/lib/utils"; import { getVirtualizedScrollFadeClassName } from "../ui/scroll-area"; import { TooltipProvider } from "../ui/tooltip"; import { Button } from "../ui/button"; import { isProviderInstancePickerReady, isProviderInstancePickerVisible, type ProviderInstanceEntry, } from "../../providerInstances"; import { providerModelKey, sortProviderModelItems } from "../../modelOrdering"; type ModelPickerItem = { slug: string; name: string; shortName?: string; subProvider?: string; badge?: "new"; instanceId: ProviderInstanceId; driverKind: ProviderDriverKind; instanceDisplayName: string; instanceAccentColor?: string | undefined; continuationGroupKey?: string | undefined; isLegacy?: boolean | undefined; isUnavailable?: boolean | undefined; }; export function resolveModelPickerSelectedModel(input: { driverKind: ProviderDriverKind | undefined; model: string; options: ReadonlyArray<ModelEsque>; }) { if (input.driverKind === "antigravity" && input.model === ANTIGRAVITY_DEFAULT_MODEL) { const availableModels = input.options.filter( (option) => option.slug !== ANTIGRAVITY_DEFAULT_MODEL && !option.isUnavailable, ); return ( availableModels.find((option) => option.aliases?.includes(ANTIGRAVITY_DEFAULT_MODEL)) ?? availableModels.find((option) => option.isDefault) ); } return input.options.find((option) => option.slug === input.model); } export function shouldIncludeModelPickerOption(input: { readonly entry: ProviderInstanceEntry; readonly option: ModelEsque; readonly activeInstanceId: ProviderInstanceId; readonly activeModel: string; }): boolean { if (input.entry.driverKind === "antigravity" && input.option.slug === ANTIGRAVITY_DEFAULT_MODEL) { return false; } if (isProviderInstancePickerReady(input.entry)) return true; return ( input.entry.enabled && (input.entry.driverKind === "opencode" || input.entry.driverKind === "antigravity") && input.entry.instanceId === input.activeInstanceId && input.option.slug === input.activeModel && input.option.isUnavailable === true ); } export function shouldOfferModelPickerSetup( entry: ProviderInstanceEntry, options: ReadonlyArray<ModelEsque>, ): boolean { return ( entry.enabled && entry.status !== "disabled" && hasProviderSetup(entry.snapshot) && (!isProviderInstancePickerReady(entry) || !entry.installed || entry.snapshot.auth.status === "unauthenticated" || !options.some((option) => !option.isUnavailable)) ); } const EMPTY_MODEL_JUMP_LABELS = new Map<string, string>(); function ModelListSeparator() { return <div className="h-0.5" />; } export const ModelPickerContent = memo(function ModelPickerContent(props: { /** The instance currently selected in the composer (combobox "value"). */ activeInstanceId: ProviderInstanceId; model: string; /** * When set, the picker is locked to the given driver kind — typically * because the user is editing a previously-sent message and can't change * which driver served the turn. Multiple instances of the same kind * remain selectable (e.g. locked to `codex` still lets the user switch * between the default Codex and a custom Codex Personal). */ lockedProvider: ProviderDriverKind | null; lockedContinuationGroupKey?: string | null; /** * All configured provider instances in display order. Used to render * the sidebar (one button per instance) and to resolve display names * for the locked-mode header. */ instanceEntries: ReadonlyArray<ProviderInstanceEntry>; keybindings?: ResolvedKeybindingsConfig; /** * Model options per instance. Keyed by `ProviderInstanceId` so the * default Codex instance and any custom Codex instances each have their * own list (custom instances typically start with the same built-in * model set but are free to diverge via customModels). */ modelOptionsByInstance: ReadonlyMap<ProviderInstanceId, ReadonlyArray<ModelEsque>>; terminalOpen: boolean; onRequestClose?: () => void; onOpenProviderSetup?: (instanceId: ProviderInstanceId) => void; getModelDisabledReason?: (instanceId: ProviderInstanceId, model: string) => string | null; onInstanceModelChange: (instanceId: ProviderInstanceId, model: string) => void; }) { const { keybindings: providedKeybindings, modelOptionsByInstance, instanceEntries, getModelDisabledReason, onInstanceModelChange, } = props; const [searchQuery, setSearchQuery] = useState(""); const [showTopScrollFade, setShowTopScrollFade] = useState(false); const [showBottomScrollFade, setShowBottomScrollFade] = useState(false); const searchInputRef = useRef<HTMLInputElement>(null); const modelListRef = useRef<LegendListRef | null>(null); const highlightedModelKeyRef = useRef<string | null>(null); const favorites = useClientSettings((s) => s.favorites ?? []); const activeEntry = props.instanceEntries.find( (entry) => entry.instanceId === props.activeInstanceId, ); const activeModel = resolveModelPickerSelectedModel({ driverKind: activeEntry?.driverKind, model: props.model, options: modelOptionsByInstance.get(props.activeInstanceId) ?? [], }); const activeModelSlug = activeModel?.slug ?? (props.model === ANTIGRAVITY_DEFAULT_MODEL ? "" : props.model); const activeModelKey = activeModelSlug ? modelPickerModelKey(props.activeInstanceId, activeModelSlug) : null; const activeInstanceHasSelectableUnavailableModel = activeEntry !== undefined && (modelOptionsByInstance.get(props.activeInstanceId) ?? []).some((option) => shouldIncludeModelPickerOption({ entry: activeEntry, option, activeInstanceId: props.activeInstanceId, activeModel: activeModelSlug, }), ) && !isProviderInstancePickerReady(activeEntry); const activeInstanceNeedsSetup = props.onOpenProviderSetup !== undefined && activeEntry !== undefined && shouldOfferModelPickerSetup( activeEntry, modelOptionsByInstance.get(props.activeInstanceId) ?? [], ); const [selectedInstanceId, setSelectedInstanceId] = useState<ProviderInstanceId | "favorites">( () => { if ( props.lockedProvider !== null || activeInstanceHasSelectableUnavailableModel || activeInstanceNeedsSetup ) { // Keep the active instance visible when it is locked or needs setup. return props.activeInstanceId; } return favorites.length > 0 ? "favorites" : props.activeInstanceId; }, ); const [expandedLegacyInstances, setExpandedLegacyInstances] = useState( () => new Set<ProviderInstanceId>( modelOptionsByInstance .get(props.activeInstanceId) ?.some((model) => model.slug === activeModelSlug && model.isLegacy) ? [props.activeInstanceId] : [], ), ); const serverKeybindings = useAtomValue(primaryServerKeybindingsAtom); const keybindings = providedKeybindings ?? serverKeybindings; const updateSettings = useUpdateClientSettings(); const focusSearchInput = useCallback(() => { searchInputRef.current?.focus({ preventScroll: true }); }, []); const handleSelectInstance = useCallback( (instanceId: ProviderInstanceId | "favorites") => { setSelectedInstanceId(instanceId); 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. Favorites are keyed by // `${instanceId}:${slug}`; the storage schema widened from ProviderDriverKind // to ProviderInstanceId so pre-migration favorites keyed by driver slugs // (e.g. `"codex:gpt-5"`) still resolve — the default instance id equals // the driver slug. const favoritesSet = useMemo(() => { return new Set(favorites.map((fav) => providerModelKey(fav.provider, fav.model))); }, [favorites]); /** * Lookup table keyed by `instanceId`. Used for display name + driver * kind enrichment and for `ready`/enabled filtering before flattening * models into the search list. */ const entryByInstanceId = useMemo( () => new Map(instanceEntries.map((entry) => [entry.instanceId, entry])), [instanceEntries], ); const matchesLockedProvider = useCallback( (entry: Pick<ProviderInstanceEntry, "driverKind" | "continuationGroupKey">): boolean => { if (props.lockedProvider === null) return true; if (entry.driverKind !== props.lockedProvider) return false; if (!props.lockedContinuationGroupKey) return true; return entry.continuationGroupKey === props.lockedContinuationGroupKey; }, [props.lockedContinuationGroupKey, props.lockedProvider], ); const selectableUnavailableInstanceIds = useMemo(() => { const instanceIds = new Set<ProviderInstanceId>(); if (activeInstanceHasSelectableUnavailableModel) { instanceIds.add(props.activeInstanceId); } if (props.onOpenProviderSetup) { for (const entry of instanceEntries) { if ( shouldOfferModelPickerSetup(entry, modelOptionsByInstance.get(entry.instanceId) ?? []) ) { instanceIds.add(entry.instanceId); } } } return instanceIds.size > 0 ? instanceIds : undefined; }, [ activeInstanceHasSelectableUnavailableModel, instanceEntries, modelOptionsByInstance, props.activeInstanceId, props.onOpenProviderSetup, ]); // Flatten models into a searchable array. One pass over the // instance-keyed map; each model carries its instance id + driver kind // so the list row can render the right icon and display name without // another lookup. const flatModels = useMemo(() => { const out: ModelPickerItem[] = []; for (const [instanceId, models] of modelOptionsByInstance) { const entry = entryByInstanceId.get(instanceId); if (!entry) { // Instance disappeared between renders (configuration change). Skip // its models — stale options shouldn't appear in the picker. continue; } for (const model of models) { if ( !shouldIncludeModelPickerOption({ entry, option: model, activeInstanceId: props.activeInstanceId, activeModel: activeModelSlug, }) ) { continue; } out.push({ slug: model.slug, name: model.name, ...(model.shortName ? { shortName: model.shortName } : {}), ...(model.subProvider ? { subProvider: model.subProvider } : {}), ...(model.badge ? { badge: model.badge } : {}), ...(model.isLegacy ? { isLegacy: true } : {}), ...(model.isUnavailable ? { isUnavailable: true } : {}), instanceId, driverKind: entry.driverKind, instanceDisplayName: entry.displayName, ...(entry.accentColor ? { instanceAccentColor: entry.accentColor } : {}), ...(entry.continuationGroupKey ? { continuationGroupKey: entry.continuationGroupKey } : {}), }); } } return out; }, [modelOptionsByInstance, entryByInstanceId, props.activeInstanceId, activeModelSlug]); const isLocked = props.lockedProvider !== null; const isSearching = searchQuery.trim().length > 0; const lockedDisabledInstanceIds = useMemo(() => { if (!isLocked) { return undefined; } const disabled = new Set<ProviderInstanceId>(); for (const entry of instanceEntries) { if (!matchesLockedProvider(entry)) { disabled.add(entry.instanceId); } } return disabled; }, [instanceEntries, isLocked, matchesLockedProvider]); const sidebarInstanceEntries = useMemo(() => { const enabledEntries = instanceEntries.filter(isProviderInstancePickerVisible); if (!isLocked) { return enabledEntries; } const available: ProviderInstanceEntry[] = []; const disabled: ProviderInstanceEntry[] = []; for (const entry of enabledEntries) { if (matchesLockedProvider(entry)) { available.push(entry); } else { disabled.push(entry); } } return [...available, ...disabled]; }, [instanceEntries, isLocked, matchesLockedProvider]); const showSidebar = !isSearching && sidebarInstanceEntries.length > 0; const instanceOrder = useMemo( () => instanceEntries.map((entry) => entry.instanceId), [instanceEntries], ); // Filter models based on search query and selected instance 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( { name: model.name, ...(model.shortName ? { shortName: model.shortName } : {}), ...(model.subProvider ? { subProvider: model.subProvider } : {}), driverKind: model.driverKind, providerDisplayName: model.instanceDisplayName, isFavorite: favoritesSet.has(providerModelKey(model.instanceId, model.slug)), }, searchQuery, ), isFavorite: favoritesSet.has(providerModelKey(model.instanceId, model.slug)), tieBreaker: buildModelPickerSearchText({ name: model.name, ...(model.shortName ? { shortName: model.shortName } : {}), ...(model.subProvider ? { subProvider: model.subProvider } : {}), driverKind: model.driverKind, providerDisplayName: model.instanceDisplayName, }), })) .filter( ( rankedModel, ): rankedModel is { model: ModelPickerItem; score: number; isFavorite: boolean; tieBreaker: string; } => rankedModel.score !== null, ); // When searching, we only respect locked provider (by driver kind), // ignoring sidebar selection so account-scoped searches can find a // model before the user chooses a specific instance rail item. if (props.lockedProvider !== null) { const lockedProviderMatches: Array<(typeof rankedMatches)[number]> = []; for (const rankedModel of rankedMatches) { if (matchesLockedProvider(rankedModel.model)) { lockedProviderMatches.push(rankedModel); } } return lockedProviderMatches .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); } if (props.lockedProvider !== null) { result = result.filter((m) => matchesLockedProvider(m)); if (selectedInstanceId === "favorites") { result = result.filter((m) => favoritesSet.has(providerModelKey(m.instanceId, m.slug))); } else { result = result.filter((m) => m.instanceId === selectedInstanceId); } } else if (selectedInstanceId === "favorites") { result = result.filter((m) => favoritesSet.has(providerModelKey(m.instanceId, m.slug))); } else { result = result.filter((m) => m.instanceId === selectedInstanceId); } return sortProviderModelItems(result, { favoriteModelKeys: favoritesSet, groupFavorites: selectedInstanceId !== "favorites", instanceOrder: selectedInstanceId === "favorites" ? instanceOrder : [], }); }, [ favoritesSet, flatModels, instanceOrder, matchesLockedProvider, props.lockedProvider, searchQuery, selectedInstanceId, ]); const legacySection = useMemo(() => { if (isSearching || selectedInstanceId === "favorites") { return null; } const currentModels = filteredModels.filter((model) => !model.isLegacy); const legacyModels = filteredModels.filter((model) => model.isLegacy); if (legacyModels.length === 0) { return null;