diff --git a/surfsense_web/components/public-chat-snapshots/public-chat-snapshot-row.tsx b/surfsense_web/components/public-chat-snapshots/public-chat-snapshot-row.tsx index d27166bf1..e1fdb5605 100644 --- a/surfsense_web/components/public-chat-snapshots/public-chat-snapshot-row.tsx +++ b/surfsense_web/components/public-chat-snapshots/public-chat-snapshot-row.tsx @@ -146,7 +146,7 @@ export function PublicChatSnapshotRow({ )} - + {member.name} diff --git a/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-manager.tsx b/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-manager.tsx index 07ff57c0d..a11e11198 100644 --- a/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-manager.tsx +++ b/surfsense_web/components/public-chat-snapshots/public-chat-snapshots-manager.tsx @@ -8,6 +8,7 @@ import { membersAtom, myAccessAtom } from "@/atoms/members/members-query.atoms"; import { deletePublicChatSnapshotMutationAtom } from "@/atoms/public-chat-snapshots/public-chat-snapshots-mutation.atoms"; import { publicChatSnapshotsAtom } from "@/atoms/public-chat-snapshots/public-chat-snapshots-query.atoms"; import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Card, CardContent } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import type { PublicChatSnapshotDetail } from "@/contracts/types/chat-threads.types"; import { PublicChatSnapshotsList } from "./public-chat-snapshots-list"; @@ -85,11 +86,31 @@ export function PublicChatSnapshotsManager({ if (isLoading) { return (
+ {/* Info alert skeleton */} + + {/* Cards grid skeleton */}
- - - + {["skeleton-a", "skeleton-b", "skeleton-c"].map((key) => ( + + + {/* Header: Title */} +
+ +
+ {/* Message count badge */} +
+ +
+ {/* Footer: Date + Creator */} +
+ + + +
+
+
+ ))}
); diff --git a/surfsense_web/components/settings/general-settings-manager.tsx b/surfsense_web/components/settings/general-settings-manager.tsx index 64d9ed876..bd22e9180 100644 --- a/surfsense_web/components/settings/general-settings-manager.tsx +++ b/surfsense_web/components/settings/general-settings-manager.tsx @@ -108,7 +108,7 @@ export function GeneralSettingsManager({ searchSpaceId }: GeneralSettingsManager return (
- + Update your search space name and description. These details help identify and organize diff --git a/surfsense_web/components/settings/image-model-manager.tsx b/surfsense_web/components/settings/image-model-manager.tsx index 013b7cae2..445d87a2a 100644 --- a/surfsense_web/components/settings/image-model-manager.tsx +++ b/surfsense_web/components/settings/image-model-manager.tsx @@ -5,7 +5,6 @@ import { AlertCircle, Check, ChevronsUpDown, - Clock, Edit3, ImageIcon, Key, @@ -14,10 +13,10 @@ import { Shuffle, Info, Trash2, - User, Wand2, } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; +import Image from "next/image"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; import { membersAtom } from "@/atoms/members/members-query.atoms"; @@ -72,6 +71,7 @@ import { SelectValue, } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; +import { Skeleton } from "@/components/ui/skeleton"; import { Spinner } from "@/components/ui/spinner"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { @@ -95,6 +95,14 @@ const item = { show: { opacity: 1, y: 0 }, }; +function getInitials(name: string): string { + const parts = name.trim().split(/\s+/); + if (parts.length >= 2) { + return (parts[0][0] + parts[1][0]).toUpperCase(); + } + return name.slice(0, 2).toUpperCase(); +} + export function ImageModelManager({ searchSpaceId }: ImageModelManagerProps) { // Image gen config atoms const { @@ -127,12 +135,13 @@ export function ImageModelManager({ searchSpaceId }: ImageModelManagerProps) { // Members for user resolution const { data: members } = useAtomValue(membersAtom); const memberMap = useMemo(() => { - const map = new Map(); + const map = new Map(); if (members) { for (const m of members) { map.set(m.user_id, { name: m.user_display_name || m.user_email || "Unknown", email: m.user_email || undefined, + avatarUrl: m.user_avatar_url || undefined, }); } } @@ -459,13 +468,61 @@ export function ImageModelManager({ searchSpaceId }: ImageModelManagerProps) { )} - {/* Loading */} + {/* Loading Skeleton */} {isLoading && ( - - - - - +
+ {/* Active Preference Skeleton */} + + +
+ +
+ + +
+
+
+ + + +
+ + {/* Your Image Models Section Skeleton */} +
+
+ + +
+ + {/* Cards Grid Skeleton */} +
+ {["skeleton-a", "skeleton-b", "skeleton-c"].map((key) => ( + + + {/* Header */} +
+
+ + +
+
+ {/* Provider + Model */} +
+ + +
+ {/* Footer */} +
+ + + +
+
+
+ ))} +
+
+
)} {/* User Configs */} @@ -477,7 +534,6 @@ export function ImageModelManager({ searchSpaceId }: ImageModelManagerProps) { onClick={openNewDialog} className="flex items-center gap-2 text-xs md:text-sm h-8 md:h-9" > - Add Image Model
@@ -492,105 +548,143 @@ export function ImageModelManager({ searchSpaceId }: ImageModelManagerProps) {

Add your own image generation model (DALL-E 3, GPT Image 1, etc.)

- ) : ( - + - {userConfigs?.map((config) => ( - - - -
-
-
-
-
-
- -
-
-
-

- {config.name} -

- { + const member = config.user_id ? memberMap.get(config.user_id) : null; + + return ( + + + + {/* Header: Name + Actions */} +
+
+

+ {config.name} +

+ {config.description && ( +

+ {config.description} +

+ )} +
+
+ + + +
- - {config.model_name} - - {config.description && ( -

- {config.description} -

- )} -
-
- - {new Date(config.created_at).toLocaleDateString()} -
- {config.user_id && memberMap.get(config.user_id) && ( -
- - {memberMap.get(config.user_id)?.name} -
- )} -
-
-
-
- - - - - - Edit - - - - - - - - Delete - - -
+ + + + Edit + + + + + + + + Delete + +
-
- - - - ))} + + {/* Provider + Model */} +
+ + {config.provider} + + + {config.model_name} + +
+ + {/* Footer: Date + Creator */} +
+ + {new Date(config.created_at).toLocaleDateString( + undefined, + { + year: "numeric", + month: "short", + day: "numeric", + } + )} + + {member && ( + <> + ยท + + + +
+ {member.avatarUrl ? ( + {member.name} + ) : ( +
+ + {getInitials(member.name)} + +
+ )} + + {member.name} + +
+
+ + {member.email || member.name} + +
+
+ + )} +
+ + + + ); + })} )} @@ -608,14 +702,12 @@ export function ImageModelManager({ searchSpaceId }: ImageModelManagerProps) { } }} > - + e.preventDefault()} + > - - {editingConfig ? ( - - ) : ( - - )} + {editingConfig ? "Edit Image Model" : "Add Image Model"} diff --git a/surfsense_web/components/settings/model-config-manager.tsx b/surfsense_web/components/settings/model-config-manager.tsx index 3ee5458b8..238286a96 100644 --- a/surfsense_web/components/settings/model-config-manager.tsx +++ b/surfsense_web/components/settings/model-config-manager.tsx @@ -47,6 +47,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { Skeleton } from "@/components/ui/skeleton"; import { Spinner } from "@/components/ui/spinner"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import type { NewLLMConfig } from "@/contracts/types/new-llm-config.types"; @@ -84,17 +85,14 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) { const { mutateAsync: createConfig, isPending: isCreating, - error: createError, } = useAtomValue(createNewLLMConfigMutationAtom); const { mutateAsync: updateConfig, isPending: isUpdating, - error: updateError, } = useAtomValue(updateNewLLMConfigMutationAtom); const { mutateAsync: deleteConfig, isPending: isDeleting, - error: deleteError, } = useAtomValue(deleteNewLLMConfigMutationAtom); // Queries @@ -128,7 +126,6 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) { const [configToDelete, setConfigToDelete] = useState(null); const isSubmitting = isCreating || isUpdating; - const errors = [createError, updateError, deleteError, fetchError].filter(Boolean) as Error[]; const handleFormSubmit = useCallback( async (formData: LLMConfigFormData) => { @@ -145,7 +142,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) { setIsDialogOpen(false); setEditingConfig(null); } catch { - // Error handled by mutation + // Error is displayed inside the dialog by the form } }, [editingConfig, createConfig, updateConfig] @@ -157,7 +154,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) { await deleteConfig({ id: configToDelete.id }); setConfigToDelete(null); } catch { - // Error handled by mutation + // Error handled by mutation state } }; @@ -195,29 +192,27 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) { size="sm" className="flex items-center gap-2 text-xs md:text-sm h-8 md:h-9" > - Add Configuration
- {/* Error Alerts */} + {/* Fetch Error Alert */} - {errors.length > 0 && - errors.map((err) => ( - - - - - {err?.message ?? "Something went wrong"} - - - - ))} + {fetchError && ( + + + + + {fetchError?.message ?? "Failed to load configurations"} + + + + )} {/* Global Configs Info */} @@ -236,18 +231,39 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) { )} - {/* Loading State */} + {/* Loading Skeleton */} {isLoading && ( - - -
- - - Loading configurations... - -
-
-
+
+ {["skeleton-a", "skeleton-b", "skeleton-c"].map((key) => ( + + + {/* Header */} +
+
+ + +
+
+ {/* Provider + Model */} +
+ + +
+ {/* Feature badges */} +
+ + +
+ {/* Footer */} +
+ + + +
+
+
+ ))} +
)} {/* Configurations List */} @@ -413,7 +429,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
)} - + {member.name}
@@ -444,12 +460,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) { onOpenAutoFocus={(e) => e.preventDefault()} > - - {editingConfig ? ( - - ) : ( - - )} + {editingConfig ? "Edit Configuration" : "Create New Configuration"}