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 */}
-
-
-
+ {/* Header actions */}
+
+ refreshConfigs()}
+ disabled={isLoading}
+ className="flex items-center gap-2 text-xs md:text-sm h-8 md:h-9"
+ >
+
+ Refresh
+
+ {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 */}
+
+
+ Configuration
+
+
+ handleRoleAssignment(`${key}_llm_id`, value)
+ }
+ >
+
+
+
+
+
+
+ Unassigned
+
+
- return (
-
- 0 && (
+
+
+ Global Configurations
+
+ {globalConfigs.map((config) => {
+ const isAuto =
+ "is_auto_mode" in config &&
+ config.is_auto_mode;
+ return (
+
+
+ {isAuto ? (
+
+
+ AUTO
+
+ ) : (
+
+ {config.provider}
+
+ )}
+
+ {config.name}
+
+ {!isAuto && (
+
+ (
+ {
+ config.model_name
+ }
+ )
+
+ )}
+ {isAuto && (
+
+ Recommended
+
+ )}
+
+
+ );
+ })}
+
+ )}
+
+ {/* Custom Configurations */}
+ {newLLMConfigs.length > 0 && (
+
+
+ Your Configurations
+
+ {newLLMConfigs
+ .filter(
+ (config) =>
+ config.id &&
+ config.id
+ .toString()
+ .trim() !== ""
+ )
+ .map((config) => (
+
+
+
+ {config.provider}
+
+
+ {config.name}
+
+
+ (
+ {
+ config.model_name
+ }
+ )
+
+
+
+ ))}
+
+ )}
+
+
+
+
+ {/* Assigned Config Summary */}
+ {assignedConfig && (
+
-
-
-
-
-
-
-
-
{role.title}
-
- {role.description}
-
+ {isAutoMode ? (
+
+
+
+
+ Auto Load Balanced
+
+
+ Routes across all available providers
+
- {currentAssignment && (
-
- )}
-
-
-
-
-
- Assign LLM Configuration:
-
-
handleRoleAssignment(`${key}_llm_id`, value)}
- >
-
-
-
-
-
- Unassigned
-
-
- {/* Global Configurations */}
- {globalConfigs.length > 0 && (
- <>
-
- Global Configurations
-
- {globalConfigs.map((config) => {
- const isAutoMode =
- "is_auto_mode" in config && config.is_auto_mode;
- return (
-
-
- {isAutoMode ? (
-
-
- AUTO
-
- ) : (
-
- {config.provider}
-
- )}
- {config.name}
- {!isAutoMode && (
-
- ({config.model_name})
-
- )}
- {isAutoMode ? (
-
- Recommended
-
- ) : (
-
- ๐ Global
-
- )}
-
-
- );
- })}
- >
- )}
-
- {/* Custom Configurations */}
- {newLLMConfigs.length > 0 && (
- <>
-
- Your Configurations
-
- {newLLMConfigs
- .filter(
- (config) => config.id && config.id.toString().trim() !== ""
- )
- .map((config) => (
-
-
-
- {config.provider}
-
- {config.name}
-
- ({config.model_name})
-
-
-
- ))}
- >
- )}
-
-
-
-
- {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 && (
-
-
-
- {isSaving ? "Saving" : "Save Changes"}
-
+ {/* Save / Reset Bar */}
+
+ {hasChanges && (
+
+
+ You have unsaved changes
+
+
-
+
Reset
+
+
+ {isSaving ? "Savingโฆ" : "Save Changes"}
+
- )}
-
- )}
+
+ )}
+
);
}