-
+
+
+
+
+
+
{Math.round((assignedConfigIds.length / 3) * 100)}%
-
Completion
+
Completion
{isAssignmentComplete ? (
-
+
) : (
-
+
)}
-
-
-
-
+
+
+
+
+
{isAssignmentComplete ? "Ready" : "Setup"}
-
Status
+
Status
{isAssignmentComplete ? (
-
+
) : (
-
+
)}
diff --git a/surfsense_web/components/settings/model-config-manager.tsx b/surfsense_web/components/settings/model-config-manager.tsx
index 3bd871135..abdde04e3 100644
--- a/surfsense_web/components/settings/model-config-manager.tsx
+++ b/surfsense_web/components/settings/model-config-manager.tsx
@@ -8,8 +8,6 @@ import {
ChevronsUpDown,
Clock,
Edit3,
- Eye,
- EyeOff,
Loader2,
Plus,
RefreshCw,
@@ -20,6 +18,16 @@ import { AnimatePresence, motion } from "motion/react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { Alert, AlertDescription } from "@/components/ui/alert";
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from "@/components/ui/alert-dialog";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
@@ -77,7 +85,6 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
const { globalConfigs } = useGlobalLLMConfigs();
const [isAddingNew, setIsAddingNew] = useState(false);
const [editingConfig, setEditingConfig] = useState(null);
- const [showApiKey, setShowApiKey] = useState>({});
const [formData, setFormData] = useState({
name: "",
provider: "",
@@ -91,6 +98,8 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [modelComboboxOpen, setModelComboboxOpen] = useState(false);
+ const [configToDelete, setConfigToDelete] = useState(null);
+ const [isDeleting, setIsDeleting] = useState(false);
// Populate form when editing
useEffect(() => {
@@ -162,19 +171,22 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
}
};
- const handleDelete = async (id: number) => {
- if (
- confirm("Are you sure you want to delete this configuration? This action cannot be undone.")
- ) {
- await deleteLLMConfig(id);
- }
+ const handleDeleteClick = (config: LLMConfig) => {
+ setConfigToDelete(config);
};
- const toggleApiKeyVisibility = (configId: number) => {
- setShowApiKey((prev) => ({
- ...prev,
- [configId]: !prev[configId],
- }));
+ const handleConfirmDelete = async () => {
+ if (!configToDelete) return;
+ setIsDeleting(true);
+ try {
+ await deleteLLMConfig(configToDelete.id);
+ toast.success("Configuration deleted successfully");
+ } catch (error) {
+ toast.error("Failed to delete configuration");
+ } finally {
+ setIsDeleting(false);
+ setConfigToDelete(null);
+ }
};
const selectedProvider = LLM_PROVIDERS.find((p) => p.value === formData.provider);
@@ -184,13 +196,6 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
return LLM_PROVIDERS.find((p) => p.value === providerValue);
};
- const maskApiKey = (apiKey: string) => {
- if (apiKey.length <= 8) return "*".repeat(apiKey.length);
- return (
- apiKey.substring(0, 4) + "*".repeat(apiKey.length - 8) + apiKey.substring(apiKey.length - 4)
- );
- };
-
return (
{/* Header */}
@@ -258,46 +263,49 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
{/* Stats Overview */}
{!loading && !error && (
-
-
-
-
-
-
{llmConfigs.length}
-
Total Configurations
+
+
+
+
+
+
+
{llmConfigs.length}
+
Total Configs
-
-
-
-
-
-
+
+
+
+
+
+
{new Set(llmConfigs.map((c) => c.provider)).size}
-
Unique Providers
+
Providers
-
-
-
-
-
-
Active
-
System Status
+
+
+
+
+
-
@@ -352,119 +360,91 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
>
-
-
-
-
- {/* Header */}
-
-
-
-
-
-
-
- {config.name}
-
-
- {config.provider}
-
+
+
+
+ {/* Left accent bar */}
+
+
+
+
+ {/* Main content */}
+
+
+
-
- {config.model_name}
-
- {config.language && (
-
-
- {config.language}
-
+
+ {/* Title row */}
+
+
+ {config.name}
+
+
+
+ {config.provider}
+
+ {config.language && (
+
+ {config.language}
+
+ )}
+
- )}
-
-
- {/* Provider Description */}
- {providerInfo && (
-
- {providerInfo.description}
-
- )}
+ {/* Model name */}
+
+
+ {config.model_name}
+
+
- {/* Configuration Details */}
-
-
-
-
-
- {showApiKey[config.id]
- ? config.api_key
- : maskApiKey(config.api_key)}
-
-
- {config.api_base && (
-
-
-
- {config.api_base}
-
-
- )}
-
-
- {/* Metadata */}
-
- {config.created_at && (
-
-
-
- Created {new Date(config.created_at).toLocaleDateString()}
-
-
- )}
-
-
-
Active
+ {/* Actions */}
+
+
+
-
- {/* Actions */}
-
-
-
-
@@ -803,6 +783,46 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
+
+ {/* Delete Confirmation Dialog */}
+
!open && setConfigToDelete(null)}
+ >
+
+
+
+
+ Delete Configuration
+
+
+ Are you sure you want to delete{" "}
+ {configToDelete?.name}? This
+ action cannot be undone and will permanently remove this model configuration.
+
+
+
+ Cancel
+
+ {isDeleting ? (
+ <>
+
+ Deleting...
+ >
+ ) : (
+ <>
+
+ Delete
+ >
+ )}
+
+
+
+
);
}
diff --git a/surfsense_web/components/sidebar/AppSidebarProvider.tsx b/surfsense_web/components/sidebar/AppSidebarProvider.tsx
index e100a4c33..76d92ba3b 100644
--- a/surfsense_web/components/sidebar/AppSidebarProvider.tsx
+++ b/surfsense_web/components/sidebar/AppSidebarProvider.tsx
@@ -193,6 +193,7 @@ export function AppSidebarProvider({
if (!isClient) {
return (
= 2) {
+ return (parts[0][0] + parts[1][0]).toUpperCase();
+ }
+ return name.slice(0, 2).toUpperCase();
+}
+
+/**
+ * Dynamic avatar component that generates an SVG based on email
+ */
+function UserAvatar({ email, size = 32 }: { email: string; size?: number }) {
+ const bgColor = stringToColor(email);
+ const initials = getInitials(email);
+
+ return (
+
+ );
+}
-import { Logo } from "@/components/Logo";
import { NavMain } from "@/components/sidebar/nav-main";
import { NavProjects } from "@/components/sidebar/nav-projects";
import { NavSecondary } from "@/components/sidebar/nav-secondary";
@@ -122,6 +212,7 @@ const defaultData = {
};
interface AppSidebarProps extends React.ComponentProps {
+ searchSpaceId?: string;
navMain?: {
title: string;
url: string;
@@ -162,12 +253,22 @@ interface AppSidebarProps extends React.ComponentProps {
// Memoized AppSidebar component for better performance
export const AppSidebar = memo(function AppSidebar({
+ searchSpaceId,
navMain = defaultData.navMain,
navSecondary = defaultData.navSecondary,
RecentChats = defaultData.RecentChats,
pageUsage,
...props
}: AppSidebarProps) {
+ const router = useRouter();
+ const { theme, setTheme } = useTheme();
+ const { user, loading: isLoadingUser } = useUser();
+ const [isClient, setIsClient] = useState(false);
+
+ useEffect(() => {
+ setIsClient(true);
+ }, []);
+
// Process navMain to resolve icon names to components
const processedNavMain = useMemo(() => {
return navMain.map((item) => ({
@@ -194,28 +295,111 @@ export const AppSidebar = memo(function AppSidebar({
);
}, [RecentChats]);
+ // Get user display name from email
+ const userDisplayName = user?.email ? user.email.split("@")[0] : "User";
+ const userEmail = user?.email || (isLoadingUser ? "Loading..." : "Unknown");
+
+ const handleLogout = () => {
+ try {
+ if (typeof window !== "undefined") {
+ localStorage.removeItem("surfsense_bearer_token");
+ router.push("/");
+ }
+ } catch (error) {
+ console.error("Error during logout:", error);
+ router.push("/");
+ }
+ };
+
return (
-
-
-
-
-
-
- SurfSense
- beta v0.0.8
-
-
-
+
+
+
+
+ {user?.email ? (
+
+ ) : (
+
+ )}
+
+
+ {userDisplayName}
+ {userEmail}
+
+
+
+
+
+
+
+
+ {user?.email ? (
+
+ ) : (
+
+ )}
+
+
+ {userDisplayName}
+ {userEmail}
+
+
+
+
+
+ {searchSpaceId && (
+ <>
+ router.push(`/dashboard/${searchSpaceId}/settings`)}
+ >
+
+ Settings
+
+ router.push(`/dashboard/${searchSpaceId}/team`)}
+ >
+
+ Invite members
+
+ >
+ )}
+ router.push("/dashboard")}>
+
+ Switch workspace
+
+
+
+
+ {isClient && (
+ setTheme(theme === "dark" ? "light" : "dark")}>
+ {theme === "dark" ? (
+
+ ) : (
+
+ )}
+ {theme === "dark" ? "Light mode" : "Dark mode"}
+
+ )}
+
+
+
+
+ Logout
+
+
+