Fix #45: Added connector icon

This commit is contained in:
ritikprajapat21 2025-05-10 09:55:50 +05:30
parent 1b9d7a0d96
commit 09c1693532

View file

@ -4,256 +4,308 @@ import { useState, useEffect } from "react";
import { useRouter, useParams } from "next/navigation"; import { useRouter, useParams } from "next/navigation";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { toast } from "sonner"; import { toast } from "sonner";
import { Edit, Plus, Search, Trash2, ExternalLink, RefreshCw } from "lucide-react"; import {
Edit,
Plus,
Search,
Trash2,
ExternalLink,
RefreshCw,
} from "lucide-react";
import { useSearchSourceConnectors } from "@/hooks/useSearchSourceConnectors"; import { useSearchSourceConnectors } from "@/hooks/useSearchSourceConnectors";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Card, Card,
CardContent, CardContent,
CardDescription, CardDescription,
CardFooter, CardFooter,
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { import {
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableHead, TableHead,
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
AlertDialogCancel, AlertDialogCancel,
AlertDialogContent, AlertDialogContent,
AlertDialogDescription, AlertDialogDescription,
AlertDialogFooter, AlertDialogFooter,
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
AlertDialogTrigger, AlertDialogTrigger,
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { getConnectorIcon } from "@/components/chat";
// Helper function to get connector type display name // Helper function to get connector type display name
const getConnectorTypeDisplay = (type: string): string => { const getConnectorTypeDisplay = (type: string): string => {
const typeMap: Record<string, string> = { const typeMap: Record<string, string> = {
"SERPER_API": "Serper API", SERPER_API: "Serper API",
"TAVILY_API": "Tavily API", TAVILY_API: "Tavily API",
"SLACK_CONNECTOR": "Slack", SLACK_CONNECTOR: "Slack",
"NOTION_CONNECTOR": "Notion", NOTION_CONNECTOR: "Notion",
"GITHUB_CONNECTOR": "GitHub", GITHUB_CONNECTOR: "GitHub",
"LINEAR_CONNECTOR": "Linear", LINEAR_CONNECTOR: "Linear",
"LINKUP_API": "Linkup", LINKUP_API: "Linkup",
// Add other connector types here as needed // Add other connector types here as needed
}; };
return typeMap[type] || type; return typeMap[type] || type;
}; };
// Helper function to format date with time // Helper function to format date with time
const formatDateTime = (dateString: string | null): string => { const formatDateTime = (dateString: string | null): string => {
if (!dateString) return "Never"; if (!dateString) return "Never";
const date = new Date(dateString); const date = new Date(dateString);
return new Intl.DateTimeFormat('en-US', { return new Intl.DateTimeFormat("en-US", {
year: 'numeric', year: "numeric",
month: 'short', month: "short",
day: 'numeric', day: "numeric",
hour: '2-digit', hour: "2-digit",
minute: '2-digit' minute: "2-digit",
}).format(date); }).format(date);
}; };
export default function ConnectorsPage() { export default function ConnectorsPage() {
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
const searchSpaceId = params.search_space_id as string; const searchSpaceId = params.search_space_id as string;
const { connectors, isLoading, error, deleteConnector, indexConnector } = useSearchSourceConnectors();
const [connectorToDelete, setConnectorToDelete] = useState<number | null>(null);
const [indexingConnectorId, setIndexingConnectorId] = useState<number | null>(null);
useEffect(() => { const { connectors, isLoading, error, deleteConnector, indexConnector } =
if (error) { useSearchSourceConnectors();
toast.error("Failed to load connectors"); const [connectorToDelete, setConnectorToDelete] = useState<number | null>(
console.error("Error fetching connectors:", error); null,
} );
}, [error]); const [indexingConnectorId, setIndexingConnectorId] = useState<number | null>(
null,
);
// Handle connector deletion useEffect(() => {
const handleDeleteConnector = async () => { if (error) {
if (connectorToDelete === null) return; toast.error("Failed to load connectors");
console.error("Error fetching connectors:", error);
try { }
await deleteConnector(connectorToDelete); }, [error]);
toast.success("Connector deleted successfully");
} catch (error) {
console.error("Error deleting connector:", error);
toast.error("Failed to delete connector");
} finally {
setConnectorToDelete(null);
}
};
// Handle connector indexing // Handle connector deletion
const handleIndexConnector = async (connectorId: number) => { const handleDeleteConnector = async () => {
setIndexingConnectorId(connectorId); if (connectorToDelete === null) return;
try {
await indexConnector(connectorId, searchSpaceId);
toast.success("Connector content indexed successfully");
} catch (error) {
console.error("Error indexing connector content:", error);
toast.error(error instanceof Error ? error.message : "Failed to index connector content");
} finally {
setIndexingConnectorId(null);
}
};
return ( try {
<div className="container mx-auto py-8 max-w-6xl"> await deleteConnector(connectorToDelete);
<motion.div toast.success("Connector deleted successfully");
initial={{ opacity: 0, y: 20 }} } catch (error) {
animate={{ opacity: 1, y: 0 }} console.error("Error deleting connector:", error);
transition={{ duration: 0.5 }} toast.error("Failed to delete connector");
className="mb-8 flex items-center justify-between" } finally {
> setConnectorToDelete(null);
<div> }
<h1 className="text-3xl font-bold tracking-tight">Connectors</h1> };
<p className="text-muted-foreground mt-2">
Manage your connected services and data sources.
</p>
</div>
<Button onClick={() => router.push(`/dashboard/${searchSpaceId}/connectors/add`)}>
<Plus className="mr-2 h-4 w-4" />
Add Connector
</Button>
</motion.div>
<Card> // Handle connector indexing
<CardHeader className="pb-3"> const handleIndexConnector = async (connectorId: number) => {
<CardTitle>Your Connectors</CardTitle> setIndexingConnectorId(connectorId);
<CardDescription> try {
View and manage all your connected services. await indexConnector(connectorId, searchSpaceId);
</CardDescription> toast.success("Connector content indexed successfully");
</CardHeader> } catch (error) {
<CardContent> console.error("Error indexing connector content:", error);
{isLoading ? ( toast.error(
<div className="flex justify-center py-8"> error instanceof Error
<div className="animate-pulse text-center"> ? error.message
<div className="h-6 w-32 bg-muted rounded mx-auto mb-2"></div> : "Failed to index connector content",
<div className="h-4 w-48 bg-muted rounded mx-auto"></div> );
</div> } finally {
</div> setIndexingConnectorId(null);
) : connectors.length === 0 ? ( }
<div className="text-center py-12"> };
<h3 className="text-lg font-medium mb-2">No connectors found</h3>
<p className="text-muted-foreground mb-6"> return (
You haven't added any connectors yet. Add one to enhance your search capabilities. <div className="container mx-auto py-8 max-w-6xl">
</p> <motion.div
<Button onClick={() => router.push(`/dashboard/${searchSpaceId}/connectors/add`)}> initial={{ opacity: 0, y: 20 }}
<Plus className="mr-2 h-4 w-4" /> animate={{ opacity: 1, y: 0 }}
Add Your First Connector transition={{ duration: 0.5 }}
</Button> className="mb-8 flex items-center justify-between"
</div> >
) : ( <div>
<div className="rounded-md border"> <h1 className="text-3xl font-bold tracking-tight">Connectors</h1>
<Table> <p className="text-muted-foreground mt-2">
<TableHeader> Manage your connected services and data sources.
<TableRow> </p>
<TableHead>Name</TableHead> </div>
<TableHead>Type</TableHead> <Button
<TableHead>Last Indexed</TableHead> onClick={() =>
<TableHead className="text-right">Actions</TableHead> router.push(`/dashboard/${searchSpaceId}/connectors/add`)
</TableRow> }
</TableHeader> >
<TableBody> <Plus className="mr-2 h-4 w-4" />
{connectors.map((connector) => ( Add Connector
<TableRow key={connector.id}> </Button>
<TableCell className="font-medium">{connector.name}</TableCell> </motion.div>
<TableCell>{getConnectorTypeDisplay(connector.connector_type)}</TableCell>
<TableCell> <Card>
{connector.is_indexable <CardHeader className="pb-3">
? formatDateTime(connector.last_indexed_at) <CardTitle>Your Connectors</CardTitle>
: "Not indexable"} <CardDescription>
</TableCell> View and manage all your connected services.
<TableCell className="text-right"> </CardDescription>
<div className="flex justify-end gap-2"> </CardHeader>
{connector.is_indexable && ( <CardContent>
<TooltipProvider> {isLoading ? (
<Tooltip> <div className="flex justify-center py-8">
<TooltipTrigger asChild> <div className="animate-pulse text-center">
<Button <div className="h-6 w-32 bg-muted rounded mx-auto mb-2"></div>
variant="outline" <div className="h-4 w-48 bg-muted rounded mx-auto"></div>
size="sm" </div>
onClick={() => handleIndexConnector(connector.id)} </div>
disabled={indexingConnectorId === connector.id} ) : connectors.length === 0 ? (
> <div className="text-center py-12">
{indexingConnectorId === connector.id ? ( <h3 className="text-lg font-medium mb-2">No connectors found</h3>
<RefreshCw className="h-4 w-4 animate-spin" /> <p className="text-muted-foreground mb-6">
) : ( You haven't added any connectors yet. Add one to enhance your
<RefreshCw className="h-4 w-4" /> search capabilities.
)} </p>
<span className="sr-only">Index Content</span> <Button
</Button> onClick={() =>
</TooltipTrigger> router.push(`/dashboard/${searchSpaceId}/connectors/add`)
<TooltipContent> }
<p>Index Content</p> >
</TooltipContent> <Plus className="mr-2 h-4 w-4" />
</Tooltip> Add Your First Connector
</TooltipProvider> </Button>
)} </div>
<Button ) : (
variant="outline" <div className="rounded-md border">
size="sm" <Table>
onClick={() => router.push(`/dashboard/${searchSpaceId}/connectors/${connector.id}/edit`)} <TableHeader>
> <TableRow>
<Edit className="h-4 w-4" /> <TableHead>Name</TableHead>
<span className="sr-only">Edit</span> <TableHead>Type</TableHead>
</Button> <TableHead>Last Indexed</TableHead>
<AlertDialog> <TableHead className="text-right">Actions</TableHead>
<AlertDialogTrigger asChild> </TableRow>
<Button </TableHeader>
variant="outline" <TableBody>
size="sm" {connectors.map((connector) => (
className="text-destructive-foreground hover:bg-destructive/10" <TableRow key={connector.id}>
onClick={() => setConnectorToDelete(connector.id)} <TableCell className="font-medium">
> {connector.name}
<Trash2 className="h-4 w-4" /> </TableCell>
<span className="sr-only">Delete</span> <TableCell>
</Button> {getConnectorIcon(connector.connector_type)}
</AlertDialogTrigger> </TableCell>
<AlertDialogContent> <TableCell>
<AlertDialogHeader> {connector.is_indexable
<AlertDialogTitle>Delete Connector</AlertDialogTitle> ? formatDateTime(connector.last_indexed_at)
<AlertDialogDescription> : "Not indexable"}
Are you sure you want to delete this connector? This action cannot be undone. </TableCell>
</AlertDialogDescription> <TableCell className="text-right">
</AlertDialogHeader> <div className="flex justify-end gap-2">
<AlertDialogFooter> {connector.is_indexable && (
<AlertDialogCancel onClick={() => setConnectorToDelete(null)}> <TooltipProvider>
Cancel <Tooltip>
</AlertDialogCancel> <TooltipTrigger asChild>
<AlertDialogAction <Button
className="bg-destructive text-destructive-foreground hover:bg-destructive/90" variant="outline"
onClick={handleDeleteConnector} size="sm"
> onClick={() =>
Delete handleIndexConnector(connector.id)
</AlertDialogAction> }
</AlertDialogFooter> disabled={
</AlertDialogContent> indexingConnectorId === connector.id
</AlertDialog> }
</div> >
</TableCell> {indexingConnectorId === connector.id ? (
</TableRow> <RefreshCw className="h-4 w-4 animate-spin" />
))} ) : (
</TableBody> <RefreshCw className="h-4 w-4" />
</Table> )}
</div> <span className="sr-only">
)} Index Content
</CardContent> </span>
</Card> </Button>
</div> </TooltipTrigger>
); <TooltipContent>
} <p>Index Content</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
<Button
variant="outline"
size="sm"
onClick={() =>
router.push(
`/dashboard/${searchSpaceId}/connectors/${connector.id}/edit`,
)
}
>
<Edit className="h-4 w-4" />
<span className="sr-only">Edit</span>
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="outline"
size="sm"
className="text-destructive-foreground hover:bg-destructive/10"
onClick={() =>
setConnectorToDelete(connector.id)
}
>
<Trash2 className="h-4 w-4" />
<span className="sr-only">Delete</span>
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Delete Connector
</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete this
connector? This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel
onClick={() => setConnectorToDelete(null)}
>
Cancel
</AlertDialogCancel>
<AlertDialogAction
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
onClick={handleDeleteConnector}
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</CardContent>
</Card>
</div>
);
}