{/* Show error if a deprecated model is selected */}
diff --git a/webview-ui/src/components/settings/StaticModelSelector.tsx b/webview-ui/src/components/settings/StaticModelSelector.tsx
new file mode 100644
index 0000000000..77b84767d6
--- /dev/null
+++ b/webview-ui/src/components/settings/StaticModelSelector.tsx
@@ -0,0 +1,187 @@
+import { useState, useMemo, useCallback, useRef, useEffect } from "react"
+import { Check, ChevronsUpDown, X } from "lucide-react"
+
+import { cn } from "@src/lib/utils"
+import { useAppTranslation } from "@src/i18n/TranslationContext"
+import { useEscapeKey } from "@src/hooks/useEscapeKey"
+import {
+ Button,
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@src/components/ui"
+
+interface StaticModelSelectorProps {
+ value: string
+ onValueChange: (value: string) => void
+ options: Array<{ value: string; label: string }>
+ placeholder?: string
+ className?: string
+ "data-testid"?: string
+}
+
+export const StaticModelSelector = ({
+ value,
+ onValueChange,
+ options,
+ placeholder,
+ className,
+ "data-testid": dataTestId,
+}: StaticModelSelectorProps) => {
+ const { t } = useAppTranslation()
+ const [open, setOpen] = useState(false)
+ const [searchValue, setSearchValue] = useState("")
+ const searchInputRef = useRef(null)
+ const selectTimeoutRef = useRef(null)
+ const closeTimeoutRef = useRef(null)
+
+ // Check if the search value exactly matches any existing option
+ const searchMatchesExactOption = useMemo(() => {
+ if (!searchValue) return false
+ return options.some((option) => option.value === searchValue)
+ }, [options, searchValue])
+
+ const onSelect = useCallback(
+ (selectedValue: string) => {
+ if (!selectedValue) return
+
+ setOpen(false)
+ onValueChange(selectedValue)
+
+ // Clear any existing timeout
+ if (selectTimeoutRef.current) {
+ clearTimeout(selectTimeoutRef.current)
+ }
+
+ // Delay to ensure the popover is closed before clearing search
+ selectTimeoutRef.current = setTimeout(() => setSearchValue(""), 100)
+ },
+ [onValueChange],
+ )
+
+ const onOpenChange = useCallback((isOpen: boolean) => {
+ setOpen(isOpen)
+
+ // Clear search when closing
+ if (!isOpen) {
+ if (closeTimeoutRef.current) {
+ clearTimeout(closeTimeoutRef.current)
+ }
+ closeTimeoutRef.current = setTimeout(() => setSearchValue(""), 100)
+ }
+ }, [])
+
+ const onClearSearch = useCallback(() => {
+ setSearchValue("")
+ searchInputRef.current?.focus()
+ }, [])
+
+ // Cleanup timeouts on unmount
+ useEffect(() => {
+ return () => {
+ if (selectTimeoutRef.current) {
+ clearTimeout(selectTimeoutRef.current)
+ }
+ if (closeTimeoutRef.current) {
+ clearTimeout(closeTimeoutRef.current)
+ }
+ }
+ }, [])
+
+ // Use ESC key handler
+ useEscapeKey(open, () => setOpen(false))
+
+ // Check if current value is a custom model (not in the options list)
+ const isCustomModel = value && !options.some((opt) => opt.value === value)
+
+ return (
+
+
+
+
+
+
+
+
+ {searchValue.length > 0 && (
+
+
+
+ )}
+
+
+
+ {searchValue && (
+
{t("settings:modelPicker.noMatchFound")}
+ )}
+
+
+ {/* Show current custom model at the top if it exists */}
+ {isCustomModel && (
+
+
+ {value} (custom)
+
+
+
+ )}
+ {/* Show all options - Command will filter based on search */}
+ {options.map((option) => (
+
+
+ {option.label}
+
+
+
+ ))}
+
+
+ {/* Show option to use custom model if search value doesn't exactly match any option */}
+ {searchValue && !searchMatchesExactOption && searchValue !== value && (
+