"use client"; import { useAtomValue } from "jotai"; import { AlertCircle, Check, ChevronsUpDown } from "lucide-react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; import { createImageGenConfigMutationAtom, updateImageGenConfigMutationAtom, } from "@/atoms/image-gen-config/image-gen-config-mutation.atoms"; import { updateLLMPreferencesMutationAtom } from "@/atoms/new-llm-config/new-llm-config-mutation.atoms"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; import { Spinner } from "@/components/ui/spinner"; import { IMAGE_GEN_MODELS, IMAGE_GEN_PROVIDERS } from "@/contracts/enums/image-gen-providers"; import type { GlobalImageGenConfig, ImageGenerationConfig, ImageGenProvider, } from "@/contracts/types/new-llm-config.types"; import { cn } from "@/lib/utils"; interface ImageConfigDialogProps { open: boolean; onOpenChange: (open: boolean) => void; config: ImageGenerationConfig | GlobalImageGenConfig | null; isGlobal: boolean; searchSpaceId: number; mode: "create" | "edit" | "view"; } const INITIAL_FORM = { name: "", description: "", provider: "", model_name: "", api_key: "", api_base: "", api_version: "", }; export function ImageConfigDialog({ open, onOpenChange, config, isGlobal, searchSpaceId, mode, }: ImageConfigDialogProps) { const [isSubmitting, setIsSubmitting] = useState(false); const [formData, setFormData] = useState(INITIAL_FORM); const [modelComboboxOpen, setModelComboboxOpen] = useState(false); const [scrollPos, setScrollPos] = useState<"top" | "middle" | "bottom">("top"); const scrollRef = useRef(null); useEffect(() => { if (open) { if (mode === "edit" && config && !isGlobal) { setFormData({ name: config.name || "", description: config.description || "", provider: config.provider || "", model_name: config.model_name || "", api_key: (config as ImageGenerationConfig).api_key || "", api_base: config.api_base || "", api_version: config.api_version || "", }); } else if (mode === "create") { setFormData(INITIAL_FORM); } setScrollPos("top"); } }, [open, mode, config, isGlobal]); const { mutateAsync: createConfig } = useAtomValue(createImageGenConfigMutationAtom); const { mutateAsync: updateConfig } = useAtomValue(updateImageGenConfigMutationAtom); const { mutateAsync: updatePreferences } = useAtomValue(updateLLMPreferencesMutationAtom); const handleScroll = useCallback((e: React.UIEvent) => { const el = e.currentTarget; const atTop = el.scrollTop <= 2; const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight <= 2; setScrollPos(atTop ? "top" : atBottom ? "bottom" : "middle"); }, []); const suggestedModels = useMemo(() => { if (!formData.provider) return []; return IMAGE_GEN_MODELS.filter((m) => m.provider === formData.provider); }, [formData.provider]); const getTitle = () => { if (mode === "create") return "Add Image Model"; if (isGlobal) return "View Global Image Model"; return "Edit Image Model"; }; const getSubtitle = () => { if (mode === "create") return "Set up a new image generation provider"; if (isGlobal) return "Read-only global configuration"; return "Update your image model settings"; }; const handleSubmit = useCallback(async () => { setIsSubmitting(true); try { if (mode === "create") { const result = await createConfig({ name: formData.name, provider: formData.provider as ImageGenProvider, model_name: formData.model_name, api_key: formData.api_key, api_base: formData.api_base || undefined, api_version: formData.api_version || undefined, description: formData.description || undefined, search_space_id: searchSpaceId, }); if (result?.id) { await updatePreferences({ search_space_id: searchSpaceId, data: { image_generation_config_id: result.id }, }); } onOpenChange(false); } else if (!isGlobal && config) { await updateConfig({ id: config.id, data: { name: formData.name, description: formData.description || undefined, provider: formData.provider as ImageGenProvider, model_name: formData.model_name, api_key: formData.api_key, api_base: formData.api_base || undefined, api_version: formData.api_version || undefined, }, }); onOpenChange(false); } } catch (error) { console.error("Failed to save image config:", error); toast.error("Failed to save image model"); } finally { setIsSubmitting(false); } }, [ mode, isGlobal, config, formData, searchSpaceId, createConfig, updateConfig, updatePreferences, onOpenChange, ]); const handleUseGlobalConfig = useCallback(async () => { if (!config || !isGlobal) return; setIsSubmitting(true); try { await updatePreferences({ search_space_id: searchSpaceId, data: { image_generation_config_id: config.id }, }); toast.success(`Now using ${config.name}`); onOpenChange(false); } catch (error) { console.error("Failed to set image model:", error); toast.error("Failed to set image model"); } finally { setIsSubmitting(false); } }, [config, isGlobal, searchSpaceId, updatePreferences, onOpenChange]); const isFormValid = formData.name && formData.provider && formData.model_name && formData.api_key; const selectedProvider = IMAGE_GEN_PROVIDERS.find((p) => p.value === formData.provider); return ( e.preventDefault()} > {getTitle()} {/* Header */}

{getTitle()}

{isGlobal && mode !== "create" && ( Global )}

{getSubtitle()}

{config && mode !== "create" && (

{config.model_name}

)}
{/* Scrollable content */}
{isGlobal && config && ( <> Global configurations are read-only. To customize, create a new model.
Name

{config.name}

{config.description && (
Description

{config.description}

)}
Provider

{config.provider}

Model

{config.model_name}

)} {(mode === "create" || (mode === "edit" && !isGlobal)) && (
setFormData((p) => ({ ...p, name: e.target.value }))} />
setFormData((p) => ({ ...p, description: e.target.value }))} />
{suggestedModels.length > 0 ? ( setFormData((p) => ({ ...p, model_name: val }))} /> Type a custom model name {suggestedModels.map((m) => ( { setFormData((p) => ({ ...p, model_name: m.value })); setModelComboboxOpen(false); }} > {m.value} {m.label} ))} ) : ( setFormData((p) => ({ ...p, model_name: e.target.value }))} /> )}
setFormData((p) => ({ ...p, api_key: e.target.value }))} />
setFormData((p) => ({ ...p, api_base: e.target.value }))} />
{formData.provider === "AZURE_OPENAI" && (
setFormData((p) => ({ ...p, api_version: e.target.value }))} />
)}
)}
{/* Fixed footer */}
{mode === "create" || (mode === "edit" && !isGlobal) ? ( ) : isGlobal && config ? ( ) : null}
); }