Skip to content

Commit 9327a87

Browse files
feat: add search functionality to model selection dropdowns
- Add SearchableModelSelect component with real-time filtering - Apply to "Others" section where users configure custom endpoints - Also apply to "OpenRouter" section for consistency - Show dynamic model count in search placeholder - Handle empty search results gracefully - Maintain accessibility with proper ARIA attributes - Use existing UI components (Command, Popover, Button) Resolves #1386 - Users can now search through ~100 models instead of scrolling manually
1 parent 2a8ee12 commit 9327a87

File tree

1 file changed

+65
-26
lines changed

1 file changed

+65
-26
lines changed

apps/desktop/src/components/settings/components/ai/llm-custom-view.tsx

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { fetch as tauriFetch } from "@tauri-apps/plugin-http";
44
import useDebouncedCallback from "beautiful-react-hooks/useDebouncedCallback";
55
import { useEffect } from "react";
66

7+
import { Button } from "@hypr/ui/components/ui/button";
8+
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from "@hypr/ui/components/ui/command";
79
import {
810
Form,
911
FormControl,
@@ -14,8 +16,10 @@ import {
1416
FormMessage,
1517
} from "@hypr/ui/components/ui/form";
1618
import { Input } from "@hypr/ui/components/ui/input";
19+
import { Popover, PopoverContent, PopoverTrigger } from "@hypr/ui/components/ui/popover";
1720
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@hypr/ui/components/ui/select";
1821
import { cn } from "@hypr/ui/lib/utils";
22+
import { Check, ChevronsUpDown } from "lucide-react";
1923
import { useState } from "react";
2024
import { SharedCustomEndpointProps } from "./shared";
2125

@@ -46,6 +50,59 @@ const openrouterModels = [
4650
"mistralai/mistral-small-3.2-24b-instruct",
4751
];
4852

53+
interface SearchableModelSelectProps {
54+
models: string[];
55+
value?: string;
56+
placeholder: string;
57+
onValueChange: (value: string) => void;
58+
}
59+
60+
function SearchableModelSelect({ models, value, placeholder, onValueChange }: SearchableModelSelectProps) {
61+
const [open, setOpen] = useState(false);
62+
63+
return (
64+
<Popover open={open} onOpenChange={setOpen}>
65+
<PopoverTrigger asChild>
66+
<Button
67+
variant="outline"
68+
role="combobox"
69+
aria-expanded={open}
70+
className="w-full justify-between"
71+
>
72+
{value || placeholder}
73+
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
74+
</Button>
75+
</PopoverTrigger>
76+
<PopoverContent className="w-[--radix-popover-trigger-width] p-0">
77+
<Command>
78+
<CommandInput placeholder={`Search ${models.length} models...`} />
79+
<CommandEmpty>No model found.</CommandEmpty>
80+
<CommandGroup className="max-h-64 overflow-auto">
81+
{models.map((model) => (
82+
<CommandItem
83+
key={model}
84+
value={model}
85+
onSelect={() => {
86+
onValueChange(model);
87+
setOpen(false);
88+
}}
89+
>
90+
<Check
91+
className={cn(
92+
"mr-2 h-4 w-4",
93+
value === model ? "opacity-100" : "opacity-0",
94+
)}
95+
/>
96+
{model}
97+
</CommandItem>
98+
))}
99+
</CommandGroup>
100+
</Command>
101+
</PopoverContent>
102+
</Popover>
103+
);
104+
}
105+
49106
export function LLMCustomView({
50107
customLLMEnabled,
51108
selectedLLMModel,
@@ -534,21 +591,12 @@ export function LLMCustomView({
534591
<Trans>Model</Trans>
535592
</FormLabel>
536593
<FormControl>
537-
<Select
594+
<SearchableModelSelect
595+
models={openrouterModels}
538596
value={field.value}
597+
placeholder="Select OpenRouter model"
539598
onValueChange={field.onChange}
540-
>
541-
<SelectTrigger>
542-
<SelectValue placeholder="Select OpenRouter model" />
543-
</SelectTrigger>
544-
<SelectContent>
545-
{openrouterModels.map((model) => (
546-
<SelectItem key={model} value={model}>
547-
{model}
548-
</SelectItem>
549-
))}
550-
</SelectContent>
551-
</Select>
599+
/>
552600
</FormControl>
553601
<FormMessage />
554602
</FormItem>
@@ -664,21 +712,12 @@ export function LLMCustomView({
664712
)
665713
: othersModels.data && othersModels.data.length > 0
666714
? (
667-
<Select
715+
<SearchableModelSelect
716+
models={othersModels.data}
668717
value={field.value}
718+
placeholder="Select model"
669719
onValueChange={field.onChange}
670-
>
671-
<SelectTrigger>
672-
<SelectValue placeholder="Select model" />
673-
</SelectTrigger>
674-
<SelectContent>
675-
{othersModels.data.map((model) => (
676-
<SelectItem key={model} value={model}>
677-
{model}
678-
</SelectItem>
679-
))}
680-
</SelectContent>
681-
</Select>
720+
/>
682721
)
683722
: (
684723
<Input

0 commit comments

Comments
 (0)