11"use client" ;
22import { zodResolver } from "@hookform/resolvers/zod" ;
3- import { PenBoxIcon , PlusIcon } from "lucide-react" ;
3+ import { Check , ChevronDown , PenBoxIcon , PlusIcon } from "lucide-react" ;
44import { useEffect , useState } from "react" ;
55import { useForm } from "react-hook-form" ;
66import { toast } from "sonner" ;
77import { z } from "zod" ;
88import { AlertBlock } from "@/components/shared/alert-block" ;
99import { Button } from "@/components/ui/button" ;
10+ import {
11+ Command ,
12+ CommandEmpty ,
13+ CommandInput ,
14+ CommandItem ,
15+ CommandList ,
16+ } from "@/components/ui/command" ;
1017import {
1118 Dialog ,
1219 DialogContent ,
@@ -26,13 +33,12 @@ import {
2633} from "@/components/ui/form" ;
2734import { Input } from "@/components/ui/input" ;
2835import {
29- Select ,
30- SelectContent ,
31- SelectItem ,
32- SelectTrigger ,
33- SelectValue ,
34- } from "@/components/ui/select" ;
36+ Popover ,
37+ PopoverContent ,
38+ PopoverTrigger ,
39+ } from "@/components/ui/popover" ;
3540import { Switch } from "@/components/ui/switch" ;
41+ import { cn } from "@/lib/utils" ;
3642import { api } from "@/utils/api" ;
3743
3844const Schema = z . object ( {
@@ -53,6 +59,8 @@ export const HandleAi = ({ aiId }: Props) => {
5359 const utils = api . useUtils ( ) ;
5460 const [ error , setError ] = useState < string | null > ( null ) ;
5561 const [ open , setOpen ] = useState ( false ) ;
62+ const [ modelPopoverOpen , setModelPopoverOpen ] = useState ( false ) ;
63+ const [ modelSearch , setModelSearch ] = useState ( "" ) ;
5664 const { data, refetch } = api . ai . one . useQuery (
5765 {
5866 aiId : aiId || "" ,
@@ -77,13 +85,17 @@ export const HandleAi = ({ aiId }: Props) => {
7785 } ) ;
7886
7987 useEffect ( ( ) => {
80- form . reset ( {
81- name : data ?. name ?? "" ,
82- apiUrl : data ?. apiUrl ?? "https://api.openai.com/v1" ,
83- apiKey : data ?. apiKey ?? "" ,
84- model : data ?. model ?? "" ,
85- isEnabled : data ?. isEnabled ?? true ,
86- } ) ;
88+ if ( data ) {
89+ form . reset ( {
90+ name : data ?. name ?? "" ,
91+ apiUrl : data ?. apiUrl ?? "https://api.openai.com/v1" ,
92+ apiKey : data ?. apiKey ?? "" ,
93+ model : data ?. model ?? "" ,
94+ isEnabled : data ?. isEnabled ?? true ,
95+ } ) ;
96+ }
97+ setModelSearch ( "" ) ;
98+ setModelPopoverOpen ( false ) ;
8799 } , [ aiId , form , data ] ) ;
88100
89101 const apiUrl = form . watch ( "apiUrl" ) ;
@@ -104,14 +116,6 @@ export const HandleAi = ({ aiId }: Props) => {
104116 } ,
105117 ) ;
106118
107- useEffect ( ( ) => {
108- const apiUrl = form . watch ( "apiUrl" ) ;
109- const apiKey = form . watch ( "apiKey" ) ;
110- if ( apiUrl && apiKey ) {
111- form . setValue ( "model" , "" ) ;
112- }
113- } , [ form . watch ( "apiUrl" ) , form . watch ( "apiKey" ) ] ) ;
114-
115119 const onSubmit = async ( data : Schema ) => {
116120 try {
117121 await mutateAsync ( {
@@ -131,7 +135,16 @@ export const HandleAi = ({ aiId }: Props) => {
131135 } ;
132136
133137 return (
134- < Dialog open = { open } onOpenChange = { setOpen } >
138+ < Dialog
139+ open = { open }
140+ onOpenChange = { ( isOpen ) => {
141+ setOpen ( isOpen ) ;
142+ if ( ! isOpen ) {
143+ setModelSearch ( "" ) ;
144+ setModelPopoverOpen ( false ) ;
145+ }
146+ } }
147+ >
135148 < DialogTrigger className = "" asChild >
136149 { aiId ? (
137150 < Button
@@ -182,7 +195,17 @@ export const HandleAi = ({ aiId }: Props) => {
182195 < FormItem >
183196 < FormLabel > API URL</ FormLabel >
184197 < FormControl >
185- < Input placeholder = "https://api.openai.com/v1" { ...field } />
198+ < Input
199+ placeholder = "https://api.openai.com/v1"
200+ { ...field }
201+ onChange = { ( e ) => {
202+ field . onChange ( e ) ;
203+ // Reset model when user changes API URL
204+ if ( form . getValues ( "model" ) ) {
205+ form . setValue ( "model" , "" ) ;
206+ }
207+ } }
208+ />
186209 </ FormControl >
187210 < FormDescription >
188211 The base URL for your AI provider's API
@@ -205,6 +228,13 @@ export const HandleAi = ({ aiId }: Props) => {
205228 placeholder = "sk-..."
206229 autoComplete = "one-time-code"
207230 { ...field }
231+ onChange = { ( e ) => {
232+ field . onChange ( e ) ;
233+ // Reset model when user changes API Key
234+ if ( form . getValues ( "model" ) ) {
235+ form . setValue ( "model" , "" ) ;
236+ }
237+ } }
208238 />
209239 </ FormControl >
210240 < FormDescription >
@@ -232,30 +262,89 @@ export const HandleAi = ({ aiId }: Props) => {
232262 < FormField
233263 control = { form . control }
234264 name = "model"
235- render = { ( { field } ) => (
236- < FormItem >
237- < FormLabel > Model</ FormLabel >
238- < Select
239- onValueChange = { field . onChange }
240- value = { field . value || "" }
241- >
242- < FormControl >
243- < SelectTrigger >
244- < SelectValue placeholder = "Select a model" />
245- </ SelectTrigger >
246- </ FormControl >
247- < SelectContent >
248- { models . map ( ( model ) => (
249- < SelectItem key = { model . id } value = { model . id } >
250- { model . id }
251- </ SelectItem >
252- ) ) }
253- </ SelectContent >
254- </ Select >
255- < FormDescription > Select an AI model to use</ FormDescription >
256- < FormMessage />
257- </ FormItem >
258- ) }
265+ render = { ( { field } ) => {
266+ const selectedModel = models . find (
267+ ( m ) => m . id === field . value ,
268+ ) ;
269+ const filteredModels = models . filter ( ( model ) =>
270+ model . id . toLowerCase ( ) . includes ( modelSearch . toLowerCase ( ) ) ,
271+ ) ;
272+
273+ // Ensure selected model is always in the filtered list
274+ const displayModels =
275+ field . value &&
276+ ! filteredModels . find ( ( m ) => m . id === field . value ) &&
277+ selectedModel
278+ ? [ selectedModel , ...filteredModels ]
279+ : filteredModels ;
280+
281+ return (
282+ < FormItem >
283+ < FormLabel > Model</ FormLabel >
284+ < Popover
285+ open = { modelPopoverOpen }
286+ onOpenChange = { setModelPopoverOpen }
287+ >
288+ < PopoverTrigger asChild >
289+ < FormControl >
290+ < Button
291+ variant = "outline"
292+ className = { cn (
293+ "w-full justify-between" ,
294+ ! field . value && "text-muted-foreground" ,
295+ ) }
296+ >
297+ { field . value
298+ ? ( selectedModel ?. id ?? field . value )
299+ : "Select a model" }
300+ < ChevronDown className = "ml-2 h-4 w-4 shrink-0 opacity-50" />
301+ </ Button >
302+ </ FormControl >
303+ </ PopoverTrigger >
304+ < PopoverContent className = "w-[400px] p-0" align = "start" >
305+ < Command >
306+ < CommandInput
307+ placeholder = "Search models..."
308+ value = { modelSearch }
309+ onValueChange = { setModelSearch }
310+ />
311+ < CommandList >
312+ < CommandEmpty > No models found.</ CommandEmpty >
313+ { displayModels . map ( ( model ) => {
314+ const isSelected = field . value === model . id ;
315+ return (
316+ < CommandItem
317+ key = { model . id }
318+ value = { model . id }
319+ onSelect = { ( ) => {
320+ field . onChange ( model . id ) ;
321+ setModelPopoverOpen ( false ) ;
322+ setModelSearch ( "" ) ;
323+ } }
324+ >
325+ < Check
326+ className = { cn (
327+ "mr-2 h-4 w-4" ,
328+ isSelected
329+ ? "opacity-100"
330+ : "opacity-0" ,
331+ ) }
332+ />
333+ { model . id }
334+ </ CommandItem >
335+ ) ;
336+ } ) }
337+ </ CommandList >
338+ </ Command >
339+ </ PopoverContent >
340+ </ Popover >
341+ < FormDescription >
342+ Select an AI model to use
343+ </ FormDescription >
344+ < FormMessage />
345+ </ FormItem >
346+ ) ;
347+ } }
259348 />
260349 ) }
261350
0 commit comments