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 { connectors, isLoading, error, deleteConnector, indexConnector } =
const [connectorToDelete, setConnectorToDelete] = useState<number | null>(null); useSearchSourceConnectors();
const [indexingConnectorId, setIndexingConnectorId] = useState<number | null>(null); const [connectorToDelete, setConnectorToDelete] = useState<number | null>(
null,
);
const [indexingConnectorId, setIndexingConnectorId] = useState<number | null>(
null,
);
useEffect(() => { useEffect(() => {
if (error) { if (error) {
toast.error("Failed to load connectors"); toast.error("Failed to load connectors");
console.error("Error fetching connectors:", error); console.error("Error fetching connectors:", error);
} }
}, [error]); }, [error]);
// Handle connector deletion // Handle connector deletion
const handleDeleteConnector = async () => { const handleDeleteConnector = async () => {
if (connectorToDelete === null) return; if (connectorToDelete === null) return;
try { try {
await deleteConnector(connectorToDelete); await deleteConnector(connectorToDelete);
toast.success("Connector deleted successfully"); toast.success("Connector deleted successfully");
} catch (error) { } catch (error) {
console.error("Error deleting connector:", error); console.error("Error deleting connector:", error);
toast.error("Failed to delete connector"); toast.error("Failed to delete connector");
} finally { } finally {
setConnectorToDelete(null); setConnectorToDelete(null);
} }
}; };
// Handle connector indexing // Handle connector indexing
const handleIndexConnector = async (connectorId: number) => { const handleIndexConnector = async (connectorId: number) => {
setIndexingConnectorId(connectorId); setIndexingConnectorId(connectorId);
try { try {
await indexConnector(connectorId, searchSpaceId); await indexConnector(connectorId, searchSpaceId);
toast.success("Connector content indexed successfully"); toast.success("Connector content indexed successfully");
} catch (error) { } catch (error) {
console.error("Error indexing connector content:", error); console.error("Error indexing connector content:", error);
toast.error(error instanceof Error ? error.message : "Failed to index connector content"); toast.error(
} finally { error instanceof Error
setIndexingConnectorId(null); ? error.message
} : "Failed to index connector content",
}; );
} finally {
setIndexingConnectorId(null);
}
};
return ( return (
<div className="container mx-auto py-8 max-w-6xl"> <div className="container mx-auto py-8 max-w-6xl">
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="mb-8 flex items-center justify-between" className="mb-8 flex items-center justify-between"
> >
<div> <div>
<h1 className="text-3xl font-bold tracking-tight">Connectors</h1> <h1 className="text-3xl font-bold tracking-tight">Connectors</h1>
<p className="text-muted-foreground mt-2"> <p className="text-muted-foreground mt-2">
Manage your connected services and data sources. Manage your connected services and data sources.
</p> </p>
</div> </div>
<Button onClick={() => router.push(`/dashboard/${searchSpaceId}/connectors/add`)}> <Button
<Plus className="mr-2 h-4 w-4" /> onClick={() =>
Add Connector router.push(`/dashboard/${searchSpaceId}/connectors/add`)
</Button> }
</motion.div> >
<Plus className="mr-2 h-4 w-4" />
Add Connector
</Button>
</motion.div>
<Card> <Card>
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<CardTitle>Your Connectors</CardTitle> <CardTitle>Your Connectors</CardTitle>
<CardDescription> <CardDescription>
View and manage all your connected services. View and manage all your connected services.
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{isLoading ? ( {isLoading ? (
<div className="flex justify-center py-8"> <div className="flex justify-center py-8">
<div className="animate-pulse text-center"> <div className="animate-pulse text-center">
<div className="h-6 w-32 bg-muted rounded mx-auto mb-2"></div> <div className="h-6 w-32 bg-muted rounded mx-auto mb-2"></div>
<div className="h-4 w-48 bg-muted rounded mx-auto"></div> <div className="h-4 w-48 bg-muted rounded mx-auto"></div>
</div> </div>
</div> </div>
) : connectors.length === 0 ? ( ) : connectors.length === 0 ? (
<div className="text-center py-12"> <div className="text-center py-12">
<h3 className="text-lg font-medium mb-2">No connectors found</h3> <h3 className="text-lg font-medium mb-2">No connectors found</h3>
<p className="text-muted-foreground mb-6"> <p className="text-muted-foreground mb-6">
You haven't added any connectors yet. Add one to enhance your search capabilities. You haven't added any connectors yet. Add one to enhance your
</p> search capabilities.
<Button onClick={() => router.push(`/dashboard/${searchSpaceId}/connectors/add`)}> </p>
<Plus className="mr-2 h-4 w-4" /> <Button
Add Your First Connector onClick={() =>
</Button> router.push(`/dashboard/${searchSpaceId}/connectors/add`)
</div> }
) : ( >
<div className="rounded-md border"> <Plus className="mr-2 h-4 w-4" />
<Table> Add Your First Connector
<TableHeader> </Button>
<TableRow> </div>
<TableHead>Name</TableHead> ) : (
<TableHead>Type</TableHead> <div className="rounded-md border">
<TableHead>Last Indexed</TableHead> <Table>
<TableHead className="text-right">Actions</TableHead> <TableHeader>
</TableRow> <TableRow>
</TableHeader> <TableHead>Name</TableHead>
<TableBody> <TableHead>Type</TableHead>
{connectors.map((connector) => ( <TableHead>Last Indexed</TableHead>
<TableRow key={connector.id}> <TableHead className="text-right">Actions</TableHead>
<TableCell className="font-medium">{connector.name}</TableCell> </TableRow>
<TableCell>{getConnectorTypeDisplay(connector.connector_type)}</TableCell> </TableHeader>
<TableCell> <TableBody>
{connector.is_indexable {connectors.map((connector) => (
? formatDateTime(connector.last_indexed_at) <TableRow key={connector.id}>
: "Not indexable"} <TableCell className="font-medium">
</TableCell> {connector.name}
<TableCell className="text-right"> </TableCell>
<div className="flex justify-end gap-2"> <TableCell>
{connector.is_indexable && ( {getConnectorIcon(connector.connector_type)}
<TooltipProvider> </TableCell>
<Tooltip> <TableCell>
<TooltipTrigger asChild> {connector.is_indexable
<Button ? formatDateTime(connector.last_indexed_at)
variant="outline" : "Not indexable"}
size="sm" </TableCell>
onClick={() => handleIndexConnector(connector.id)} <TableCell className="text-right">
disabled={indexingConnectorId === connector.id} <div className="flex justify-end gap-2">
> {connector.is_indexable && (
{indexingConnectorId === connector.id ? ( <TooltipProvider>
<RefreshCw className="h-4 w-4 animate-spin" /> <Tooltip>
) : ( <TooltipTrigger asChild>
<RefreshCw className="h-4 w-4" /> <Button
)} variant="outline"
<span className="sr-only">Index Content</span> size="sm"
</Button> onClick={() =>
</TooltipTrigger> handleIndexConnector(connector.id)
<TooltipContent> }
<p>Index Content</p> disabled={
</TooltipContent> indexingConnectorId === connector.id
</Tooltip> }
</TooltipProvider> >
)} {indexingConnectorId === connector.id ? (
<Button <RefreshCw className="h-4 w-4 animate-spin" />
variant="outline" ) : (
size="sm" <RefreshCw className="h-4 w-4" />
onClick={() => router.push(`/dashboard/${searchSpaceId}/connectors/${connector.id}/edit`)} )}
> <span className="sr-only">
<Edit className="h-4 w-4" /> Index Content
<span className="sr-only">Edit</span> </span>
</Button> </Button>
<AlertDialog> </TooltipTrigger>
<AlertDialogTrigger asChild> <TooltipContent>
<Button <p>Index Content</p>
variant="outline" </TooltipContent>
size="sm" </Tooltip>
className="text-destructive-foreground hover:bg-destructive/10" </TooltipProvider>
onClick={() => setConnectorToDelete(connector.id)} )}
> <Button
<Trash2 className="h-4 w-4" /> variant="outline"
<span className="sr-only">Delete</span> size="sm"
</Button> onClick={() =>
</AlertDialogTrigger> router.push(
<AlertDialogContent> `/dashboard/${searchSpaceId}/connectors/${connector.id}/edit`,
<AlertDialogHeader> )
<AlertDialogTitle>Delete Connector</AlertDialogTitle> }
<AlertDialogDescription> >
Are you sure you want to delete this connector? This action cannot be undone. <Edit className="h-4 w-4" />
</AlertDialogDescription> <span className="sr-only">Edit</span>
</AlertDialogHeader> </Button>
<AlertDialogFooter> <AlertDialog>
<AlertDialogCancel onClick={() => setConnectorToDelete(null)}> <AlertDialogTrigger asChild>
Cancel <Button
</AlertDialogCancel> variant="outline"
<AlertDialogAction size="sm"
className="bg-destructive text-destructive-foreground hover:bg-destructive/90" className="text-destructive-foreground hover:bg-destructive/10"
onClick={handleDeleteConnector} onClick={() =>
> setConnectorToDelete(connector.id)
Delete }
</AlertDialogAction> >
</AlertDialogFooter> <Trash2 className="h-4 w-4" />
</AlertDialogContent> <span className="sr-only">Delete</span>
</AlertDialog> </Button>
</div> </AlertDialogTrigger>
</TableCell> <AlertDialogContent>
</TableRow> <AlertDialogHeader>
))} <AlertDialogTitle>
</TableBody> Delete Connector
</Table> </AlertDialogTitle>
</div> <AlertDialogDescription>
)} Are you sure you want to delete this
</CardContent> connector? This action cannot be undone.
</Card> </AlertDialogDescription>
</div> </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>
);
} }