From c8e7aae5c6c56b113547e5c65346d5e8f3f038b1 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sun, 30 Nov 2025 23:46:22 -0600 Subject: [PATCH] feat: enhance AI model selection with popover and search functionality - Replaced the select component with a popover for model selection, allowing for better user experience. - Added search capability to filter models, improving accessibility and usability. - Updated form handling to reset model selection when API URL or API Key changes. - Ensured proper state management for popover visibility and search input. --- .../dashboard/settings/handle-ai.tsx | 185 +++++++++++++----- 1 file changed, 137 insertions(+), 48 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/handle-ai.tsx b/apps/dokploy/components/dashboard/settings/handle-ai.tsx index 19716e0f3..b5a0d51d8 100644 --- a/apps/dokploy/components/dashboard/settings/handle-ai.tsx +++ b/apps/dokploy/components/dashboard/settings/handle-ai.tsx @@ -1,12 +1,19 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBoxIcon, PlusIcon } from "lucide-react"; +import { Check, ChevronDown, PenBoxIcon, PlusIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; import { Dialog, DialogContent, @@ -26,13 +33,12 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; import { Switch } from "@/components/ui/switch"; +import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; const Schema = z.object({ @@ -53,6 +59,8 @@ export const HandleAi = ({ aiId }: Props) => { const utils = api.useUtils(); const [error, setError] = useState(null); const [open, setOpen] = useState(false); + const [modelPopoverOpen, setModelPopoverOpen] = useState(false); + const [modelSearch, setModelSearch] = useState(""); const { data, refetch } = api.ai.one.useQuery( { aiId: aiId || "", @@ -77,13 +85,17 @@ export const HandleAi = ({ aiId }: Props) => { }); useEffect(() => { - form.reset({ - name: data?.name ?? "", - apiUrl: data?.apiUrl ?? "https://api.openai.com/v1", - apiKey: data?.apiKey ?? "", - model: data?.model ?? "", - isEnabled: data?.isEnabled ?? true, - }); + if (data) { + form.reset({ + name: data?.name ?? "", + apiUrl: data?.apiUrl ?? "https://api.openai.com/v1", + apiKey: data?.apiKey ?? "", + model: data?.model ?? "", + isEnabled: data?.isEnabled ?? true, + }); + } + setModelSearch(""); + setModelPopoverOpen(false); }, [aiId, form, data]); const apiUrl = form.watch("apiUrl"); @@ -104,14 +116,6 @@ export const HandleAi = ({ aiId }: Props) => { }, ); - useEffect(() => { - const apiUrl = form.watch("apiUrl"); - const apiKey = form.watch("apiKey"); - if (apiUrl && apiKey) { - form.setValue("model", ""); - } - }, [form.watch("apiUrl"), form.watch("apiKey")]); - const onSubmit = async (data: Schema) => { try { await mutateAsync({ @@ -131,7 +135,16 @@ export const HandleAi = ({ aiId }: Props) => { }; return ( - + { + setOpen(isOpen); + if (!isOpen) { + setModelSearch(""); + setModelPopoverOpen(false); + } + }} + > {aiId ? ( + + + + + + + No models found. + {displayModels.map((model) => { + const isSelected = field.value === model.id; + return ( + { + field.onChange(model.id); + setModelPopoverOpen(false); + setModelSearch(""); + }} + > + + {model.id} + + ); + })} + + + + + + Select an AI model to use + + + + ); + }} /> )}