"use client"; import { useAtomValue } from "jotai"; import { AlertCircle, Check, ChevronsUpDown, Globe, ImageIcon, Key, Shuffle, X, Zap, } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { createPortal } from "react-dom"; 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 { 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, } from "@/contracts/types/new-llm-config.types"; import { cn } from "@/lib/utils"; interface ImageConfigSidebarProps { 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 ImageConfigSidebar({ open, onOpenChange, config, isGlobal, searchSpaceId, mode, }: ImageConfigSidebarProps) { const [isSubmitting, setIsSubmitting] = useState(false); const [mounted, setMounted] = useState(false); const [formData, setFormData] = useState(INITIAL_FORM); const [modelComboboxOpen, setModelComboboxOpen] = useState(false); useEffect(() => { setMounted(true); }, []); // Reset form when opening 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); } } }, [open, mode, config, isGlobal]); // Mutations const { mutateAsync: createConfig } = useAtomValue(createImageGenConfigMutationAtom); const { mutateAsync: updateConfig } = useAtomValue(updateImageGenConfigMutationAtom); const { mutateAsync: updatePreferences } = useAtomValue(updateLLMPreferencesMutationAtom); // Escape key useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === "Escape" && open) onOpenChange(false); }; window.addEventListener("keydown", handleEscape); return () => window.removeEventListener("keydown", handleEscape); }, [open, onOpenChange]); const isAutoMode = config && "is_auto_mode" in config && config.is_auto_mode; 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 (isAutoMode) return "Auto Mode (Load Balanced)"; if (isGlobal) return "View Global Image Model"; return "Edit Image Model"; }; const handleSubmit = useCallback(async () => { setIsSubmitting(true); try { if (mode === "create") { const result = await createConfig({ name: formData.name, provider: formData.provider, 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, }); // Set as active image model if (result?.id) { await updatePreferences({ search_space_id: searchSpaceId, data: { image_generation_config_id: result.id }, }); } toast.success("Image model created and assigned!"); onOpenChange(false); } else if (!isGlobal && config) { await updateConfig({ id: config.id, data: { name: formData.name, description: formData.description || undefined, provider: formData.provider, model_name: formData.model_name, api_key: formData.api_key, api_base: formData.api_base || undefined, api_version: formData.api_version || undefined, }, }); toast.success("Image model updated!"); 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); if (!mounted) return null; const sidebarContent = ( {open && ( <> {/* Backdrop */} onOpenChange(false)} /> {/* Sidebar */} {/* Header */}
{isAutoMode ? ( ) : ( )}

{getTitle()}

{isAutoMode ? ( Recommended ) : isGlobal ? ( Global ) : null} {config && !isAutoMode && ( {config.model_name} )}
{/* Content */}
{/* Auto mode */} {isAutoMode && ( <> Auto mode distributes image generation requests across all configured providers for optimal performance and rate limit protection.
)} {/* Global config (read-only) */} {isGlobal && !isAutoMode && 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}

)} {/* Create / Edit form */} {(mode === "create" || (mode === "edit" && !isGlobal)) && (
{/* Name */}
setFormData((p) => ({ ...p, name: e.target.value }))} />
{/* Description */}
setFormData((p) => ({ ...p, description: e.target.value })) } />
{/* Provider */}
{/* Model Name */}
{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 })) } /> )}
{/* API Key */}
setFormData((p) => ({ ...p, api_key: e.target.value }))} />
{/* API Base */}
setFormData((p) => ({ ...p, api_base: e.target.value }))} />
{/* Azure API Version */} {formData.provider === "AZURE_OPENAI" && (
setFormData((p) => ({ ...p, api_version: e.target.value })) } />
)} {/* Actions */}
)}
)}
); return typeof document !== "undefined" ? createPortal(sidebarContent, document.body) : null; }