"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { useAtomValue } from "jotai"; import { Bot, Check, ChevronDown, ChevronsUpDown, Key, MessageSquareQuote, Rocket, Sparkles, } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; import { useEffect, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { defaultSystemInstructionsAtom, modelListAtom, } from "@/atoms/new-llm-config/new-llm-config-query.atoms"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; 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 { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { LLM_PROVIDERS } from "@/contracts/enums/llm-providers"; import type { CreateNewLLMConfigRequest } from "@/contracts/types/new-llm-config.types"; import { cn } from "@/lib/utils"; import InferenceParamsEditor from "../inference-params-editor"; // Form schema with zod const formSchema = z.object({ name: z.string().min(1, "Name is required").max(100), description: z.string().max(500).optional().nullable(), provider: z.string().min(1, "Provider is required"), custom_provider: z.string().max(100).optional().nullable(), model_name: z.string().min(1, "Model name is required").max(100), api_key: z.string().min(1, "API key is required"), api_base: z.string().max(500).optional().nullable(), litellm_params: z.record(z.string(), z.any()).optional().nullable(), system_instructions: z.string().default(""), use_default_system_instructions: z.boolean().default(true), citations_enabled: z.boolean().default(true), search_space_id: z.number(), }); type FormValues = z.infer; export type LLMConfigFormData = CreateNewLLMConfigRequest; interface LLMConfigFormProps { initialData?: Partial; searchSpaceId: number; onSubmit: (data: LLMConfigFormData) => Promise; onCancel?: () => void; isSubmitting?: boolean; mode?: "create" | "edit"; submitLabel?: string; showAdvanced?: boolean; compact?: boolean; } export function LLMConfigForm({ initialData, searchSpaceId, onSubmit, onCancel, isSubmitting = false, mode = "create", submitLabel, showAdvanced = true, compact = false, }: LLMConfigFormProps) { const { data: defaultInstructions, isSuccess: defaultInstructionsLoaded } = useAtomValue( defaultSystemInstructionsAtom ); const { data: dynamicModels } = useAtomValue(modelListAtom); const [modelComboboxOpen, setModelComboboxOpen] = useState(false); const [advancedOpen, setAdvancedOpen] = useState(false); const [systemInstructionsOpen, setSystemInstructionsOpen] = useState(false); const form = useForm({ // eslint-disable-next-line @typescript-eslint/no-explicit-any resolver: zodResolver(formSchema) as any, defaultValues: { name: initialData?.name ?? "", description: initialData?.description ?? "", provider: initialData?.provider ?? "", custom_provider: initialData?.custom_provider ?? "", model_name: initialData?.model_name ?? "", api_key: initialData?.api_key ?? "", api_base: initialData?.api_base ?? "", litellm_params: initialData?.litellm_params ?? {}, system_instructions: initialData?.system_instructions ?? "", use_default_system_instructions: initialData?.use_default_system_instructions ?? true, citations_enabled: initialData?.citations_enabled ?? true, search_space_id: searchSpaceId, }, }); // Load default instructions when available (only for new configs) useEffect(() => { if ( mode === "create" && defaultInstructionsLoaded && defaultInstructions?.default_system_instructions && !form.getValues("system_instructions") ) { form.setValue("system_instructions", defaultInstructions.default_system_instructions); } }, [defaultInstructionsLoaded, defaultInstructions, mode, form]); const watchProvider = form.watch("provider"); const selectedProvider = LLM_PROVIDERS.find((p) => p.value === watchProvider); const availableModels = useMemo( () => (dynamicModels ?? []).filter((m) => m.provider === watchProvider), [dynamicModels, watchProvider] ); const handleProviderChange = (value: string) => { form.setValue("provider", value); form.setValue("model_name", ""); // Auto-fill API base for certain providers const provider = LLM_PROVIDERS.find((p) => p.value === value); if (provider?.apiBase) { form.setValue("api_base", provider.apiBase); } }; const handleFormSubmit = async (values: FormValues) => { await onSubmit(values as LLMConfigFormData); }; return (
{/* Model Configuration Section */}
Model Configuration
{/* Name & Description */}
( Configuration Name )} /> ( Description Optional )} />
{/* Provider Selection */} ( LLM Provider )} /> {/* Custom Provider (conditional) */} {watchProvider === "CUSTOM" && ( ( Custom Provider Name )} /> )} {/* Model Name with Combobox */} ( Model Name
{field.value ? `Using: "${field.value}"` : "Type your model name"}
{availableModels.length > 0 && ( {availableModels .filter( (model) => !field.value || model.value.toLowerCase().includes(field.value.toLowerCase()) || model.label.toLowerCase().includes(field.value.toLowerCase()) ) .slice(0, 50) .map((model) => ( { field.onChange(value); setModelComboboxOpen(false); }} className="py-2" >
{model.label}
{model.contextWindow && (
Context: {model.contextWindow}
)}
))}
)}
{selectedProvider?.example && ( Example: {selectedProvider.example} )}
)} /> {/* API Credentials */}
( API Key {watchProvider === "OLLAMA" && ( Ollama doesn't require auth — enter any value )} )} /> ( API Base URL {selectedProvider?.apiBase && ( Auto-filled )} )} />
{/* Ollama Quick Actions */} {watchProvider === "OLLAMA" && ( )}
{/* Advanced Parameters */} {showAdvanced && ( <> ( )} /> )} {/* System Instructions & Citations Section */} {/* System Instructions */} (
Instructions for the AI {defaultInstructions && ( )}