diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index b7c1d06..8cff886 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import type { FormEvent } from "react"; import { api } from "@/lib/api"; import type { ModelCatalogProvider, ModelProfile, ResolvedApiKey } from "@/lib/types"; @@ -8,17 +8,6 @@ import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; -import { - Command, - CommandInput, - CommandList, - CommandEmpty, - CommandGroup, - CommandItem, -} from "@/components/ui/command"; -import { ChevronsUpDown, Check } from "lucide-react"; -import { cn } from "@/lib/utils"; import { AlertDialog, AlertDialogAction, @@ -53,108 +42,74 @@ function emptyForm(): ProfileForm { }; } -function ComboboxField({ +function AutocompleteField({ value, onChange, - onOpen, + onFocus, options, placeholder, }: { value: string; onChange: (val: string) => void; - onOpen?: () => void; + onFocus?: () => void; options: { value: string; label: string }[]; placeholder: string; }) { const [open, setOpen] = useState(false); - const [search, setSearch] = useState(""); + const wrapperRef = useRef(null); + + const filtered = options.filter( + (o) => + !value || + o.value.toLowerCase().includes(value.toLowerCase()) || + o.label.toLowerCase().includes(value.toLowerCase()), + ); + + useEffect(() => { + function handleClickOutside(e: MouseEvent) { + if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) { + setOpen(false); + } + } + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); return ( - { - setOpen(o); - if (o && onOpen) onOpen(); - }} - > - - - - - - - - No results found. - - {/* Show typed value as option if it doesn't match any existing option */} - {search && - !options.some( - (o) => o.value.toLowerCase() === search.toLowerCase(), - ) && ( - { - onChange(search); - setOpen(false); - setSearch(""); - }} - > - - Use "{search}" - - )} - {options - .filter( - (o) => - !search || - o.value.toLowerCase().includes(search.toLowerCase()) || - o.label.toLowerCase().includes(search.toLowerCase()), - ) - .map((option) => ( - { - onChange(option.value); - setOpen(false); - setSearch(""); - }} - > - - {option.label} - - ))} - - - - - +
+ { + onChange(e.target.value); + setOpen(true); + }} + onFocus={() => { + setOpen(true); + onFocus?.(); + }} + onKeyDown={(e) => { + if (e.key === "Escape") setOpen(false); + }} + /> + {open && filtered.length > 0 && ( +
+ {filtered.map((option) => ( +
{ + e.preventDefault(); + onChange(option.value); + setOpen(false); + }} + > + {option.label} +
+ ))} +
+ )} +
); } @@ -272,12 +227,12 @@ export function Settings() {
- setForm((p) => ({ ...p, provider: val, model: "" })) } - onOpen={ensureCatalog} + onFocus={ensureCatalog} options={catalog.map((c) => ({ value: c.provider, label: c.provider, @@ -288,12 +243,12 @@ export function Settings() {
- setForm((p) => ({ ...p, model: val })) } - onOpen={ensureCatalog} + onFocus={ensureCatalog} options={modelCandidates.map((m) => ({ value: m.id, label: m.name || m.id,