"use client"; import { useAtomValue } from "jotai"; import { AlertCircle, Bot, CircleCheck, CircleDashed, Eye, FileText, ImageIcon, RefreshCw, Shuffle, } from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; import { toast } from "sonner"; import { globalImageGenConfigsAtom, imageGenConfigsAtom, } from "@/atoms/image-gen-config/image-gen-config-query.atoms"; import { updateLLMPreferencesMutationAtom } from "@/atoms/new-llm-config/new-llm-config-mutation.atoms"; import { globalNewLLMConfigsAtom, llmPreferencesAtom, newLLMConfigsAtom, } from "@/atoms/new-llm-config/new-llm-config-query.atoms"; import { globalVisionLLMConfigsAtom, visionLLMConfigsAtom, } from "@/atoms/vision-llm-config/vision-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 } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Skeleton } from "@/components/ui/skeleton"; import { Spinner } from "@/components/ui/spinner"; import { getProviderIcon } from "@/lib/provider-icons"; import { cn } from "@/lib/utils"; const ROLE_DESCRIPTIONS = { agent: { icon: Bot, title: "Agent LLM", description: "Primary LLM for chat interactions and agent operations", color: "text-muted-foreground", bgColor: "bg-muted", prefKey: "agent_llm_id" as const, configType: "llm" as const, }, document_summary: { icon: FileText, title: "Document Summary LLM", description: "Handles document summarization and research synthesis", color: "text-muted-foreground", bgColor: "bg-muted", prefKey: "document_summary_llm_id" as const, configType: "llm" as const, }, image_generation: { icon: ImageIcon, title: "Image Generation Model", description: "Model used for AI image generation (DALL-E, GPT Image, etc.)", color: "text-muted-foreground", bgColor: "bg-muted", prefKey: "image_generation_config_id" as const, configType: "image" as const, }, vision: { icon: Eye, title: "Vision LLM", description: "Vision-capable model for screenshot analysis and context extraction", color: "text-amber-600 dark:text-amber-400", bgColor: "bg-amber-500/10", prefKey: "vision_llm_config_id" as const, configType: "vision" as const, }, }; interface LLMRoleManagerProps { searchSpaceId: number; } export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { // LLM configs const { data: newLLMConfigs = [], isFetching: configsLoading, error: configsError, refetch: refreshConfigs, } = useAtomValue(newLLMConfigsAtom); const { data: globalConfigs = [], isFetching: globalConfigsLoading, error: globalConfigsError, } = useAtomValue(globalNewLLMConfigsAtom); // Image gen configs const { data: userImageConfigs = [], isFetching: imageConfigsLoading, error: imageConfigsError, } = useAtomValue(imageGenConfigsAtom); const { data: globalImageConfigs = [], isFetching: globalImageConfigsLoading, error: globalImageConfigsError, } = useAtomValue(globalImageGenConfigsAtom); // Vision LLM configs const { data: userVisionConfigs = [], isFetching: visionConfigsLoading, error: visionConfigsError, } = useAtomValue(visionLLMConfigsAtom); const { data: globalVisionConfigs = [], isFetching: globalVisionConfigsLoading, error: globalVisionConfigsError, } = useAtomValue(globalVisionLLMConfigsAtom); // Preferences const { data: preferences = {}, isFetching: preferencesLoading, error: preferencesError, } = useAtomValue(llmPreferencesAtom); const { mutateAsync: updatePreferences } = useAtomValue(updateLLMPreferencesMutationAtom); const [assignments, setAssignments] = useState(() => ({ agent_llm_id: preferences.agent_llm_id ?? "", document_summary_llm_id: preferences.document_summary_llm_id ?? "", image_generation_config_id: preferences.image_generation_config_id ?? "", vision_llm_config_id: preferences.vision_llm_config_id ?? "", })); const [savingRole, setSavingRole] = useState(null); const savingRef = useRef(false); useEffect(() => { if (!savingRef.current) { setAssignments({ agent_llm_id: preferences.agent_llm_id ?? "", document_summary_llm_id: preferences.document_summary_llm_id ?? "", image_generation_config_id: preferences.image_generation_config_id ?? "", vision_llm_config_id: preferences.vision_llm_config_id ?? "", }); } }, [ preferences?.agent_llm_id, preferences?.document_summary_llm_id, preferences?.image_generation_config_id, preferences?.vision_llm_config_id, ]); const handleRoleAssignment = useCallback( async (prefKey: string, configId: string) => { const value = configId === "unassigned" ? "" : parseInt(configId); setAssignments((prev) => ({ ...prev, [prefKey]: value })); setSavingRole(prefKey); savingRef.current = true; try { await updatePreferences({ search_space_id: searchSpaceId, data: { [prefKey]: value || undefined }, }); toast.success("Role assignment updated"); } finally { setSavingRole(null); savingRef.current = false; } }, [updatePreferences, searchSpaceId] ); // Combine global and custom LLM configs const allLLMConfigs = [ ...globalConfigs.map((config) => ({ ...config, is_global: true })), ...newLLMConfigs.filter((config) => config.id && config.id.toString().trim() !== ""), ]; // Combine global and custom image gen configs const allImageConfigs = [ ...globalImageConfigs.map((config) => ({ ...config, is_global: true })), ...(userImageConfigs ?? []).filter((config) => config.id && config.id.toString().trim() !== ""), ]; // Combine global and custom vision LLM configs const allVisionConfigs = [ ...globalVisionConfigs.map((config) => ({ ...config, is_global: true })), ...(userVisionConfigs ?? []).filter( (config) => config.id && config.id.toString().trim() !== "" ), ]; const isAssignmentComplete = allLLMConfigs.some((c) => c.id === assignments.agent_llm_id) && allLLMConfigs.some((c) => c.id === assignments.document_summary_llm_id) && allImageConfigs.some((c) => c.id === assignments.image_generation_config_id); const isLoading = configsLoading || preferencesLoading || globalConfigsLoading || imageConfigsLoading || globalImageConfigsLoading || visionConfigsLoading || globalVisionConfigsLoading; const hasError = configsError || preferencesError || globalConfigsError || imageConfigsError || globalImageConfigsError || visionConfigsError || globalVisionConfigsError; const hasAnyConfigs = allLLMConfigs.length > 0 || allImageConfigs.length > 0; return (
{/* Header actions */}
{isAssignmentComplete && !isLoading && !hasError && ( All roles assigned )}
{/* Error Alert */} {hasError && (
{(configsError?.message ?? "Failed to load LLM configurations") || (preferencesError?.message ?? "Failed to load preferences") || (globalConfigsError?.message ?? "Failed to load global configurations")}
)} {/* Loading Skeleton */} {isLoading && (
{["skeleton-a", "skeleton-b", "skeleton-c"].map((key) => ( {/* Header: icon + title + status */}
{/* Label */}
{/* Summary block */}
))}
)} {/* No configs warning */} {!isLoading && !hasError && !hasAnyConfigs && ( No configurations found. Please add at least one LLM provider or image model in the respective settings tabs before assigning roles. )} {/* Role Assignment Cards */} {!isLoading && !hasError && hasAnyConfigs && (
{Object.entries(ROLE_DESCRIPTIONS).map(([key, role]) => { const IconComponent = role.icon; const currentAssignment = assignments[role.prefKey as keyof typeof assignments]; // Pick the right config lists based on role type const roleGlobalConfigs = role.configType === "image" ? globalImageConfigs : role.configType === "vision" ? globalVisionConfigs : globalConfigs; const roleUserConfigs = role.configType === "image" ? (userImageConfigs ?? []).filter((c) => c.id && c.id.toString().trim() !== "") : role.configType === "vision" ? (userVisionConfigs ?? []).filter((c) => c.id && c.id.toString().trim() !== "") : newLLMConfigs.filter((c) => c.id && c.id.toString().trim() !== ""); const roleAllConfigs = role.configType === "image" ? allImageConfigs : role.configType === "vision" ? allVisionConfigs : allLLMConfigs; const assignedConfig = roleAllConfigs.find((config) => config.id === currentAssignment); const isAssigned = !!assignedConfig; const isAutoMode = assignedConfig && "is_auto_mode" in assignedConfig && assignedConfig.is_auto_mode; return (
{/* Role Header */}

{role.title}

{role.description}

{savingRole === role.prefKey ? ( ) : isAssigned ? ( ) : ( )}
{/* Selector */}
{/* Assigned Config Summary */} {assignedConfig && (
{isAutoMode ? (

Auto Mode

Routes across all available providers

) : (
{assignedConfig.name} {"is_global" in assignedConfig && assignedConfig.is_global && ( 🌐 Global )}
{getProviderIcon(assignedConfig.provider, { className: "size-3 shrink-0", })} {assignedConfig.model_name}
{assignedConfig.api_base && (

{assignedConfig.api_base}

)}
)}
)}
); })}
)}
); }