"use client"; import { useAtomValue } from "jotai"; import { AlertCircle, Bot, Brain, Check, CheckCircle, ChevronDown, ChevronsUpDown, ChevronUp, Plus, Trash2, Zap, } from "lucide-react"; import { motion } from "motion/react"; import { useTranslations } from "next-intl"; import { useEffect, useState } from "react"; import { toast } from "sonner"; import { createLLMConfigMutationAtom, deleteLLMConfigMutationAtom, updateLLMPreferencesMutationAtom, } from "@/atoms/llm-config/llm-config-mutation.atoms"; import { globalLLMConfigsAtom, llmConfigsAtom, llmPreferencesAtom, } from "@/atoms/llm-config/llm-config-query.atoms"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 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 { LANGUAGES } from "@/contracts/enums/languages"; import { getModelsByProvider } from "@/contracts/enums/llm-models"; import { LLM_PROVIDERS } from "@/contracts/enums/llm-providers"; import { type CreateLLMConfigRequest, LLMConfig } from "@/contracts/types/llm-config.types"; import { cn } from "@/lib/utils"; import InferenceParamsEditor from "../inference-params-editor"; interface SetupLLMStepProps { searchSpaceId: number; onConfigCreated?: () => void; onConfigDeleted?: () => void; onPreferencesUpdated?: () => Promise; } const ROLE_DESCRIPTIONS = { long_context: { icon: Brain, key: "long_context_llm_id" as const, titleKey: "long_context_llm_title", descKey: "long_context_llm_desc", examplesKey: "long_context_llm_examples", color: "bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-950 dark:text-blue-200 dark:border-blue-800", }, fast: { icon: Zap, key: "fast_llm_id" as const, titleKey: "fast_llm_title", descKey: "fast_llm_desc", examplesKey: "fast_llm_examples", color: "bg-green-100 text-green-800 border-green-200 dark:bg-green-950 dark:text-green-200 dark:border-green-800", }, strategic: { icon: Bot, key: "strategic_llm_id" as const, titleKey: "strategic_llm_title", descKey: "strategic_llm_desc", examplesKey: "strategic_llm_examples", color: "bg-purple-100 text-purple-800 border-purple-200 dark:bg-purple-950 dark:text-purple-200 dark:border-purple-800", }, }; export function SetupLLMStep({ searchSpaceId, onConfigCreated, onConfigDeleted, onPreferencesUpdated, }: SetupLLMStepProps) { const { mutate: createLLMConfig, isPending: isCreatingLlmConfig } = useAtomValue( createLLMConfigMutationAtom ); const t = useTranslations("onboard"); const { mutateAsync: deleteLLMConfig } = useAtomValue(deleteLLMConfigMutationAtom); const { data: llmConfigs = [] } = useAtomValue(llmConfigsAtom); const { data: globalConfigs = [] } = useAtomValue(globalLLMConfigsAtom); const { data: preferences = {} } = useAtomValue(llmPreferencesAtom); const { mutateAsync: updatePreferences } = useAtomValue(updateLLMPreferencesMutationAtom); const [isAddingNew, setIsAddingNew] = useState(false); const [formData, setFormData] = useState({ name: "", provider: "" as CreateLLMConfigRequest["provider"], // Allow it as Default custom_provider: "", model_name: "", api_key: "", api_base: "", language: "English", litellm_params: {}, search_space_id: searchSpaceId, }); const [modelComboboxOpen, setModelComboboxOpen] = useState(false); const [showProviderForm, setShowProviderForm] = useState(false); // Role assignments state const [assignments, setAssignments] = useState({ long_context_llm_id: preferences.long_context_llm_id || "", fast_llm_id: preferences.fast_llm_id || "", strategic_llm_id: preferences.strategic_llm_id || "", }); // Combine global and user-specific configs const allConfigs = [...globalConfigs, ...llmConfigs]; useEffect(() => { setAssignments({ long_context_llm_id: preferences.long_context_llm_id || "", fast_llm_id: preferences.fast_llm_id || "", strategic_llm_id: preferences.strategic_llm_id || "", }); }, [preferences]); const handleInputChange = (field: keyof CreateLLMConfigRequest, value: string) => { setFormData((prev) => ({ ...prev, [field]: value })); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!formData.name || !formData.provider || !formData.model_name || !formData.api_key) { toast.error("Please fill in all required fields"); return; } createLLMConfig(formData, { onError: (error) => { console.error("Error creating LLM config:", error); if (error instanceof Error) { toast.error(error?.message || "Failed to create LLM config"); } }, onSuccess: () => { toast.success("LLM config created successfully"); setFormData({ name: "", provider: "" as CreateLLMConfigRequest["provider"], custom_provider: "", model_name: "", api_key: "", api_base: "", language: "English", litellm_params: {}, search_space_id: searchSpaceId, }); onConfigCreated?.(); }, onSettled: () => { setIsAddingNew(false); }, }); }; const handleRoleAssignment = async (role: string, configId: string) => { const newAssignments = { ...assignments, [role]: configId === "" ? "" : parseInt(configId), }; setAssignments(newAssignments); // Auto-save if this assignment completes all roles const hasAllAssignments = newAssignments.long_context_llm_id && newAssignments.fast_llm_id && newAssignments.strategic_llm_id; if (hasAllAssignments) { const numericAssignments = { long_context_llm_id: typeof newAssignments.long_context_llm_id === "string" ? parseInt(newAssignments.long_context_llm_id) : newAssignments.long_context_llm_id, fast_llm_id: typeof newAssignments.fast_llm_id === "string" ? parseInt(newAssignments.fast_llm_id) : newAssignments.fast_llm_id, strategic_llm_id: typeof newAssignments.strategic_llm_id === "string" ? parseInt(newAssignments.strategic_llm_id) : newAssignments.strategic_llm_id, }; await updatePreferences({ search_space_id: searchSpaceId, data: numericAssignments, }); if (onPreferencesUpdated) { await onPreferencesUpdated(); } } }; const selectedProvider = LLM_PROVIDERS.find((p) => p.value === formData.provider); const availableModels = formData.provider ? getModelsByProvider(formData.provider) : []; const handleParamsChange = (newParams: Record) => { setFormData((prev) => ({ ...prev, litellm_params: newParams })); }; const handleProviderChange = (value: string) => { handleInputChange("provider", value); setFormData((prev) => ({ ...prev, model_name: "" })); }; const isAssignmentComplete = assignments.long_context_llm_id && assignments.fast_llm_id && assignments.strategic_llm_id; return (
{/* Global Configs Notice - Prominent at top */} {globalConfigs.length > 0 && (

{globalConfigs.length} global configuration(s) available!

You can skip adding your own LLM provider and use our pre-configured models in the role assignment section below.

Or expand "Add LLM Provider" to add your own custom configurations.

)} {/* Section 1: Add LLM Providers */}

{t("add_llm_provider")}

{t("configure_first_provider")}

{showProviderForm && ( {/* Info Alert */} {t("add_provider_instruction")} {/* Existing Configurations */} {llmConfigs.length > 0 && (

{t("your_llm_configs")}

{llmConfigs.map((config) => (

{config.name}

{config.provider}

{t("model")}: {config.model_name} {config.language && ` • ${t("language")}: ${config.language}`} {config.api_base && ` • ${t("base")}: ${config.api_base}`}

))}
)} {/* Add New Provider */} {!isAddingNew ? (

{t("add_provider_title")}

{t("add_provider_subtitle")}

) : ( {t("add_new_llm_provider")} {t("configure_new_provider")}
handleInputChange("name", e.target.value)} required />
{formData.provider === "CUSTOM" && (
handleInputChange("custom_provider", e.target.value)} required />
)}
handleInputChange("model_name", value)} />
{formData.model_name ? `Using custom model: "${formData.model_name}"` : "Type your model name above"}
{availableModels.length > 0 && ( {availableModels .filter( (model) => !formData.model_name || model.value .toLowerCase() .includes(formData.model_name.toLowerCase()) || model.label .toLowerCase() .includes(formData.model_name.toLowerCase()) ) .map((model) => ( { handleInputChange("model_name", currentValue); setModelComboboxOpen(false); }} className="flex flex-col items-start py-3" >
{model.label}
{model.contextWindow && (
Context: {model.contextWindow}
)}
))}
)}

{availableModels.length > 0 ? `Type freely or select from ${availableModels.length} model suggestions` : selectedProvider?.example ? `${t("examples")}: ${selectedProvider.example}` : "Type your model name freely"}

handleInputChange("api_key", e.target.value)} required /> {formData.provider === "OLLAMA" && (

💡 Ollama doesn't require authentication — enter any value (e.g., "ollama")

)}
handleInputChange("api_base", e.target.value)} /> {/* Ollama-specific help */} {formData.provider === "OLLAMA" && (

💡 Ollama API Base URL Examples:

)}
)}
)}
{/* Section 2: Assign Roles */}

{t("assign_llm_roles")}

{t("assign_specific_roles")}

{allConfigs.length === 0 ? ( {t("add_provider_before_roles")} ) : (
{t("assign_roles_instruction")}
{Object.entries(ROLE_DESCRIPTIONS).map(([roleKey, role]) => { const IconComponent = role.icon; const currentAssignment = assignments[role.key]; const assignedConfig = allConfigs.find((config) => config.id === currentAssignment); return (
{t(role.titleKey)} {t(role.descKey)}
{currentAssignment && }
{assignedConfig && (
{t("assigned")}: {"is_global" in assignedConfig && assignedConfig.is_global && ( 🌐 Global )} {assignedConfig.provider} {assignedConfig.name}
{t("model")}: {assignedConfig.model_name}
)}
); })}
{/* Status Indicators */}
{t("progress")}:
{Object.keys(ROLE_DESCRIPTIONS).map((key) => { const roleKey = ROLE_DESCRIPTIONS[key as keyof typeof ROLE_DESCRIPTIONS].key; return (
); })}
{t("roles_assigned", { assigned: Object.values(assignments).filter(Boolean).length, total: Object.keys(ROLE_DESCRIPTIONS).length, })}
{isAssignmentComplete && (
{t("all_roles_assigned_saved")}
)}
)}
); }