import { type ModelSlug, type ProviderKind } from "@t3tools/contracts"; import { normalizeModelSlug } from "@t3tools/shared/model"; import { memo, useState } from "react"; import { type ProviderPickerKind, PROVIDER_OPTIONS } from "../../session-logic"; import { ChevronDownIcon } from "lucide-react"; import { Button } from "../ui/button"; import { Menu, MenuGroup, MenuItem, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuSeparator as MenuDivider, MenuSub, MenuSubPopup, MenuSubTrigger, MenuTrigger, } from "../ui/menu"; import { ClaudeAI, CursorIcon, Gemini, Icon, OpenAI, OpenCodeIcon } from "../Icons"; import { cn } from "~/lib/utils"; function isAvailableProviderOption(option: (typeof PROVIDER_OPTIONS)[number]): option is { value: ProviderKind; label: string; available: true; } { return option.available && option.value !== "claudeCode"; } function resolveModelForProviderPicker( provider: ProviderKind, value: string, options: ReadonlyArray<{ slug: string; name: string }>, ): ModelSlug | null { const trimmedValue = value.trim(); if (!trimmedValue) { return null; } const direct = options.find((option) => option.slug === trimmedValue); if (direct) { return direct.slug; } const byName = options.find((option) => option.name.toLowerCase() === trimmedValue.toLowerCase()); if (byName) { return byName.slug; } const normalized = normalizeModelSlug(trimmedValue, provider); if (!normalized) { return null; } const resolved = options.find((option) => option.slug === normalized); if (resolved) { return resolved.slug; } return null; } const PROVIDER_ICON_BY_PROVIDER: Record = { codex: OpenAI, claudeCode: ClaudeAI, cursor: CursorIcon, }; export const AVAILABLE_PROVIDER_OPTIONS = PROVIDER_OPTIONS.filter(isAvailableProviderOption); const UNAVAILABLE_PROVIDER_OPTIONS = PROVIDER_OPTIONS.filter((option) => !option.available); const COMING_SOON_PROVIDER_OPTIONS = [ { id: "opencode", label: "OpenCode", icon: OpenCodeIcon }, { id: "gemini", label: "Gemini", icon: Gemini }, ] as const; export const ProviderModelPicker = memo(function ProviderModelPicker(props: { provider: ProviderKind; model: ModelSlug; lockedProvider: ProviderKind | null; modelOptionsByProvider: Record>; compact?: boolean; disabled?: boolean; onProviderModelChange: (provider: ProviderKind, model: ModelSlug) => void; }) { const [isMenuOpen, setIsMenuOpen] = useState(false); const selectedProviderOptions = props.modelOptionsByProvider[props.provider]; const selectedModelLabel = selectedProviderOptions.find((option) => option.slug === props.model)?.name ?? props.model; const ProviderIcon = PROVIDER_ICON_BY_PROVIDER[props.provider]; return ( { if (props.disabled) { setIsMenuOpen(false); return; } setIsMenuOpen(open); }} > } > {AVAILABLE_PROVIDER_OPTIONS.map((option) => { const OptionIcon = PROVIDER_ICON_BY_PROVIDER[option.value]; const isDisabledByProviderLock = props.lockedProvider !== null && props.lockedProvider !== option.value; return ( { if (props.disabled) return; if (isDisabledByProviderLock) return; if (!value) return; const resolvedModel = resolveModelForProviderPicker( option.value, value, props.modelOptionsByProvider[option.value], ); if (!resolvedModel) return; props.onProviderModelChange(option.value, resolvedModel); setIsMenuOpen(false); }} > {props.modelOptionsByProvider[option.value].map((modelOption) => ( setIsMenuOpen(false)} > {modelOption.name} ))} ); })} {UNAVAILABLE_PROVIDER_OPTIONS.length > 0 && } {UNAVAILABLE_PROVIDER_OPTIONS.map((option) => { const OptionIcon = PROVIDER_ICON_BY_PROVIDER[option.value]; return ( ); })} {UNAVAILABLE_PROVIDER_OPTIONS.length === 0 && } {COMING_SOON_PROVIDER_OPTIONS.map((option) => { const OptionIcon = option.icon; return ( ); })} ); });