feat: integrate search space settings dialog across various components

- Added `searchSpaceSettingsDialogAtom` to manage the state of the settings dialog.
- Updated multiple components (OnboardPage, TeamManagementPage, ConnectorIndicator, DocumentUploadPopupContent, etc.) to utilize the new dialog state for navigating to settings.
- Removed unnecessary animations from ApiKeyContent and ProfileContent components for improved performance.
- Enhanced button styles for better UI consistency across settings actions.
- Refactored error handling in LLMRoleManager and ModelConfigManager to simplify the UI structure.
This commit is contained in:
Anish Sarkar 2026-03-16 21:10:46 +05:30
parent 60d12b0a70
commit b7d684ca8d
19 changed files with 646 additions and 483 deletions

View file

@ -12,7 +12,6 @@ import {
Trash2,
Wand2,
} from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import Image from "next/image";
import { useCallback, useMemo, useState } from "react";
import { membersAtom, myAccessAtom } from "@/atoms/members/members-query.atoms";
@ -51,6 +50,7 @@ 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";
import { useMediaQuery } from "@/hooks/use-media-query";
import { getProviderIcon } from "@/lib/provider-icons";
import { cn } from "@/lib/utils";
@ -58,21 +58,6 @@ interface ModelConfigManagerProps {
searchSpaceId: number;
}
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.05,
},
},
};
const item = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0 },
};
function getInitials(name: string): string {
const parts = name.trim().split(/\s+/);
if (parts.length >= 2) {
@ -82,6 +67,7 @@ function getInitials(name: string): string {
}
export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
const isDesktop = useMediaQuery("(min-width: 768px)");
// Mutations
const { mutateAsync: createConfig, isPending: isCreating } = useAtomValue(
createNewLLMConfigMutationAtom
@ -194,49 +180,42 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
<div className="space-y-5 md:space-y-6">
{/* Header actions */}
<div className="flex items-center justify-between">
<Button
variant="secondary"
size="sm"
onClick={() => refreshConfigs()}
disabled={isLoading}
className="gap-2"
>
<RefreshCw className={cn("h-3.5 w-3.5", isLoading && "animate-spin")} />
Refresh
</Button>
{canCreate && (
<Button
variant="outline"
size="sm"
onClick={() => refreshConfigs()}
disabled={isLoading}
className="flex items-center gap-2 text-xs md:text-sm h-8 md:h-9"
onClick={openNewDialog}
className="gap-2 bg-white text-black hover:bg-neutral-100 dark:bg-white dark:text-black dark:hover:bg-neutral-200"
>
<RefreshCw className={cn("h-3 w-3 md:h-4 md:w-4", isLoading && "animate-spin")} />
Refresh
Add Configuration
</Button>
{canCreate && (
<Button
onClick={openNewDialog}
size="sm"
className="flex items-center gap-2 text-xs md:text-sm h-8 md:h-9"
>
Add Configuration
</Button>
)}
</div>
{/* Fetch Error Alert */}
<AnimatePresence>
{fetchError && (
<motion.div
key="fetch-error"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
>
<Alert variant="destructive" className="py-3 md:py-4">
<AlertCircle className="h-3 w-3 md:h-4 md:w-4 shrink-0" />
<AlertDescription className="text-xs md:text-sm">
{fetchError?.message ?? "Failed to load configurations"}
</AlertDescription>
</Alert>
</motion.div>
)}
</AnimatePresence>
{fetchError && (
<div>
<Alert variant="destructive" className="py-3 md:py-4">
<AlertCircle className="h-3 w-3 md:h-4 md:w-4 shrink-0" />
<AlertDescription className="text-xs md:text-sm">
{fetchError?.message ?? "Failed to load configurations"}
</AlertDescription>
</Alert>
</div>
)}
{/* Read-only / Limited permissions notice */}
{access && !isLoading && isReadOnly && (
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }}>
<div>
<Alert className="bg-muted/50 py-3 md:py-4">
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" />
<AlertDescription className="text-xs md:text-sm">
@ -244,10 +223,10 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
Contact a space owner to request additional permissions.
</AlertDescription>
</Alert>
</motion.div>
</div>
)}
{access && !isLoading && !isReadOnly && (!canCreate || !canUpdate || !canDelete) && (
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }}>
<div>
<Alert className="bg-muted/50 py-3 md:py-4">
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" />
<AlertDescription className="text-xs md:text-sm">
@ -259,12 +238,12 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
{!canDelete && ", but cannot delete them"}.
</AlertDescription>
</Alert>
</motion.div>
</div>
)}
{/* Global Configs Info */}
{globalConfigs.length > 0 && (
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }}>
<div>
<Alert className="bg-muted/50 py-3 md:py-4">
<Info className="h-3 w-3 md:h-4 md:w-4 shrink-0" />
<AlertDescription className="text-xs md:text-sm">
@ -275,7 +254,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
</span>
</AlertDescription>
</Alert>
</motion.div>
</div>
)}
{/* Loading Skeleton */}
@ -317,7 +296,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
{!isLoading && (
<div className="space-y-4">
{configs?.length === 0 ? (
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}>
<div>
<Card className="border-dashed border-2 border-muted-foreground/25">
<CardContent className="flex flex-col items-center justify-center py-10 md:py-16 text-center">
<div className="rounded-full bg-gradient-to-br from-violet-500/10 to-purple-500/10 p-4 md:p-6 mb-4 md:mb-6">
@ -343,25 +322,14 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
)}
</CardContent>
</Card>
</motion.div>
</div>
) : (
<motion.div
variants={container}
initial="hidden"
animate="show"
className="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-3"
>
<AnimatePresence mode="popLayout">
{configs?.map((config) => {
const member = config.user_id ? memberMap.get(config.user_id) : null;
<div className="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-3">
{configs?.map((config) => {
const member = config.user_id ? memberMap.get(config.user_id) : null;
return (
<motion.div
key={config.id}
variants={item}
layout
exit={{ opacity: 0, scale: 0.95 }}
>
return (
<div key={config.id}>
<Card className="group relative overflow-hidden transition-all duration-200 border-border/60 hover:shadow-md h-full">
<CardContent className="p-4 flex flex-col gap-3 h-full">
{/* Header: Name + Actions */}
@ -380,7 +348,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
<div className="flex items-center gap-0.5 shrink-0 sm:opacity-0 sm:group-hover:opacity-100 transition-opacity duration-150">
{canUpdate && (
<TooltipProvider>
<Tooltip>
<Tooltip open={isDesktop ? undefined : false}>
<TooltipTrigger asChild>
<Button
variant="ghost"
@ -397,7 +365,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
)}
{canDelete && (
<TooltipProvider>
<Tooltip>
<Tooltip open={isDesktop ? undefined : false}>
<TooltipTrigger asChild>
<Button
variant="ghost"
@ -460,7 +428,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
<>
<span className="text-muted-foreground/30">·</span>
<TooltipProvider>
<Tooltip>
<Tooltip open={isDesktop ? undefined : false}>
<TooltipTrigger asChild>
<div className="flex items-center gap-1.5 cursor-default">
{member.avatarUrl ? (
@ -493,11 +461,10 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
</div>
</CardContent>
</Card>
</motion.div>
</div>
);
})}
</AnimatePresence>
</motion.div>
</div>
)}
</div>
)}