diff --git a/surfsense_web/components/settings/llm-role-manager.tsx b/surfsense_web/components/settings/llm-role-manager.tsx index d91150a4f..a0c13d116 100644 --- a/surfsense_web/components/settings/llm-role-manager.tsx +++ b/surfsense_web/components/settings/llm-role-manager.tsx @@ -5,13 +5,14 @@ import { AlertCircle, Bot, CheckCircle, + CircleDashed, FileText, RefreshCw, RotateCcw, Save, Shuffle, } from "lucide-react"; -import { motion } from "motion/react"; +import { AnimatePresence, motion } from "motion/react"; import { useEffect, useState } from "react"; import { toast } from "sonner"; import { updateLLMPreferencesMutationAtom } from "@/atoms/new-llm-config/new-llm-config-mutation.atoms"; @@ -23,16 +24,18 @@ import { 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 { 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 { Spinner } from "@/components/ui/spinner"; +import { Skeleton } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; const ROLE_DESCRIPTIONS = { @@ -40,17 +43,15 @@ const ROLE_DESCRIPTIONS = { icon: Bot, title: "Agent LLM", description: "Primary LLM for chat interactions and agent operations", - color: "bg-blue-100 text-blue-800 border-blue-200", - examples: "Chat responses, agent tasks, real-time interactions", - characteristics: ["Fast responses", "Conversational", "Agent operations"], + color: "text-blue-600 dark:text-blue-400", + bgColor: "bg-blue-500/10", }, document_summary: { icon: FileText, title: "Document Summary LLM", - description: "Handles document summarization", - color: "bg-purple-100 text-purple-800 border-purple-200", - examples: "Document analysis, podcasts, research synthesis", - characteristics: ["Large context window", "Deep reasoning", "Summarization"], + description: "Handles document summarization and research synthesis", + color: "text-purple-600 dark:text-purple-400", + bgColor: "bg-purple-500/10", }, }; @@ -59,7 +60,6 @@ interface LLMRoleManagerProps { } export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { - // Use new LLM config system const { data: newLLMConfigs = [], isFetching: configsLoading, @@ -70,7 +70,6 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { data: globalConfigs = [], isFetching: globalConfigsLoading, error: globalConfigsError, - refetch: refreshGlobalConfigs, } = useAtomValue(globalNewLLMConfigsAtom); const { data: preferences = {}, @@ -105,7 +104,6 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { setAssignments(newAssignments); - // Check if there are changes compared to current preferences const currentPrefs = { agent_llm_id: preferences.agent_llm_id ?? "", document_summary_llm_id: preferences.document_summary_llm_id ?? "", @@ -165,325 +163,409 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { assignments.document_summary_llm_id !== null && assignments.document_summary_llm_id !== undefined; - // Combine global and custom configs (new system) + // Combine global and custom configs const allConfigs = [ ...globalConfigs.map((config) => ({ ...config, is_global: true })), ...newLLMConfigs.filter((config) => config.id && config.id.toString().trim() !== ""), ]; - const availableConfigs = allConfigs; - const isLoading = configsLoading || preferencesLoading || globalConfigsLoading; const hasError = configsError || preferencesError || globalConfigsError; return ( -
- {/* Header */} -
-
- + {isAssignmentComplete && !isLoading && !hasError && ( + refreshConfigs()} - disabled={isLoading} - className="flex items-center gap-2 text-xs md:text-sm h-8 md:h-9" + className="text-xs gap-1.5 border-emerald-500/30 text-emerald-700 dark:text-emerald-300 bg-emerald-500/5" > - - Refresh Configs - Configs - -
+ + All roles assigned + + )}
{/* Error Alert */} - {hasError && ( + + {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"].map((key) => ( + + + {/* Header: icon + title + status */} +
+
+ +
+ + +
+
+ +
+ {/* Label */} +
+ + +
+ {/* Summary block */} +
+
+ + +
+
+ + +
+
+
+
+ ))} +
+ )} + + {/* No configs warning */} + {!isLoading && !hasError && allConfigs.length === 0 && ( - {(configsError?.message ?? "Failed to load LLM configurations") || - (preferencesError?.message ?? "Failed to load preferences") || - (globalConfigsError?.message ?? "Failed to load global configurations")} + No LLM configurations found. Please add at least one LLM provider in the + Agent Configs tab before assigning roles. )} - {/* Loading State */} - {isLoading && ( - - -
- - - {configsLoading && preferencesLoading - ? "Loading configurations and preferences..." - : configsLoading - ? "Loading configurations..." - : "Loading preferences..."} - -
-
-
- )} + {/* Role Assignment Cards */} + {!isLoading && !hasError && allConfigs.length > 0 && ( + + {Object.entries(ROLE_DESCRIPTIONS).map(([key, role], index) => { + const IconComponent = role.icon; + const currentAssignment = + assignments[`${key}_llm_id` as keyof typeof assignments]; + const assignedConfig = allConfigs.find( + (config) => config.id === currentAssignment + ); + const isAssigned = + currentAssignment !== "" && + currentAssignment !== null && + currentAssignment !== undefined; + const isAutoMode = + assignedConfig && + "is_auto_mode" in assignedConfig && + assignedConfig.is_auto_mode; - {/* Info Alert */} - {!isLoading && !hasError && ( -
- {availableConfigs.length === 0 ? ( - - - - No LLM configurations found. Please add at least one LLM provider in the Agent - Configs tab before assigning roles. - - - ) : !isAssignmentComplete ? ( - - - - Complete all role assignments to enable full functionality. Each role serves - different purposes in your workflow. - - - ) : ( - - - - All roles are assigned and ready to use! Your LLM configuration is complete. - - - )} + return ( + + + + {/* Role Header */} +
+
+
+ +
+
+

+ {role.title} +

+

+ {role.description} +

+
+
+ {isAssigned ? ( + + ) : ( + + )} +
- {/* Role Assignment Cards */} - {availableConfigs.length > 0 && ( -
- {Object.entries(ROLE_DESCRIPTIONS).map(([key, role]) => { - const IconComponent = role.icon; - const currentAssignment = assignments[`${key}_llm_id` as keyof typeof assignments]; - const assignedConfig = availableConfigs.find( - (config) => config.id === currentAssignment - ); + {/* Selector */} +
+ + +
+ + {/* Assigned Config Summary */} + {assignedConfig && ( +
- -
-
-
- -
-
- {role.title} - - {role.description} - + {isAutoMode ? ( +
+ +
+

+ Auto Load Balanced +

+

+ Routes across all available providers +

- {currentAssignment && ( - - )} -
- - -
- - -
- - {assignedConfig && ( -
-
- {"is_auto_mode" in assignedConfig && assignedConfig.is_auto_mode ? ( - - ) : ( - - )} - Assigned: - {"is_auto_mode" in assignedConfig && assignedConfig.is_auto_mode ? ( - - AUTO - - ) : ( - - {assignedConfig.provider} - - )} - {assignedConfig.name} - {"is_auto_mode" in assignedConfig && assignedConfig.is_auto_mode ? ( + ) : ( +
+ +
+
+ + {assignedConfig.name} + + {"is_global" in assignedConfig && + assignedConfig.is_global && ( + + ๐ŸŒ Global + + )} +
+
- Recommended + {assignedConfig.provider} - ) : ( - "is_global" in assignedConfig && - assignedConfig.is_global && ( - - ๐ŸŒ Global - - ) + + {assignedConfig.model_name} + +
+ {assignedConfig.api_base && ( +

+ {assignedConfig.api_base} +

)}
- {"is_auto_mode" in assignedConfig && assignedConfig.is_auto_mode ? ( -
- Automatically load balances across all available LLM providers -
- ) : ( - <> -
- Model: {assignedConfig.model_name} -
- {assignedConfig.api_base && ( -
- Base: {assignedConfig.api_base} -
- )} - - )}
)} - - - - ); - })} -
- )} +
+ )} +
+ + + ); + })} + + )} - {/* Action Buttons */} - {hasChanges && ( -
- + {/* Save / Reset Bar */} + + {hasChanges && ( + +

+ You have unsaved changes +

+
+
- )} -
- )} + + )} +
); }