Skip to main content
Glama
ToolGroupsSection.tsx11.3 kB
import { ChevronLeft, ChevronRight, FileEdit, Trash2, Wrench, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { NoToolGroupsPlaceholder } from "@/components/tools/EmptyStatePlaceholders"; import { useDomainIcon } from "@/hooks/useDomainIcon"; import McpIcon from "../dashboard/SystemConnectivity/nodes/Mcpx_Icon.svg?react"; import { TargetServerNew } from "@mcpx/shared-model"; import { EllipsisActions } from "../ui/ellipsis-action"; import { ToolGroup } from "@/store/access-controls"; interface TransformedToolGroup { id: string; name: string; description?: string; icon: string; tools: Array<{ name: string; provider: string; count: number; }>; } interface ToolGroupsSectionProps { transformedToolGroups: TransformedToolGroup[]; toolGroups: ToolGroup[]; currentGroupIndex: number; selectedToolGroup: string | null; onGroupNavigation: (direction: "left" | "right") => void; onGroupClick: (groupId: string) => void; onEditModeToggle: () => void; onEditGroup: (group: ToolGroup) => void; onEditToolGroup?: (group: ToolGroup) => void; isAddCustomToolMode: boolean; onDeleteGroup: (group: ToolGroup) => void; isEditMode: boolean; providers: TargetServerNew[]; setCurrentGroupIndex: (index: number) => void; selectedToolGroupForDialog?: ToolGroup; } interface DomainIconProps { providerName: string; providers: TargetServerNew[]; size?: number; } export function DomainIcon({ providerName, providers, size = 16, }: DomainIconProps) { const iconSrc = useDomainIcon(providerName); let imageColor = "black"; if (!iconSrc) { const currProvider = providers.find( (provider) => provider.name === providerName, ); imageColor = currProvider?.icon || imageColor; } return iconSrc ? ( <img src={iconSrc} alt={`${providerName} icon`} className="object-contain" style={{ width: size, height: size }} /> ) : ( <McpIcon style={{ color: imageColor, width: size, height: size }} /> ); } export function ToolGroupsSection({ onEditGroup, onEditToolGroup, onDeleteGroup, transformedToolGroups, toolGroups, currentGroupIndex, selectedToolGroup, onGroupNavigation, onGroupClick, onEditModeToggle, providers, setCurrentGroupIndex, selectedToolGroupForDialog, }: ToolGroupsSectionProps) { const visibleGroups = transformedToolGroups.slice( currentGroupIndex * 8, (currentGroupIndex + 1) * 8, ); return ( <div className="mb-12"> {transformedToolGroups.length > 0 ? ( <div className="bg-white rounded-lg p-6 shadow-sm border border-gray-200"> <div className="relative w-full"> <div className="mb-6"> <h2 className="text-xl font-semibold text-gray-900 mb-4"> Tool Group </h2> </div> <div className="flex items-center gap-4 overflow-hidden w-full"> {currentGroupIndex > 0 && ( <Button variant="secondary" size="sm" onClick={() => onGroupNavigation("left")} className="absolute left-0 top-1/2 transform -translate-y-1/2 z-10 bg-white shadow-md" > <ChevronLeft className="w-4 h-4" /> </Button> )} <div className="grid grid-cols-4 gap-4 flex-1 w-full"> {visibleGroups.slice(0, 8).map((group) => { return ( <div key={group.id} data-group-id={group.id} className={`rounded-lg border p-4 w-full cursor-pointer transition-colors min-h-[80px] ${ selectedToolGroup === group.id ? "bg-[#4F33CC] border-[#4F33CC] hover:bg-[#4F33CC]" : "bg-gray-50 border-gray-200 hover:bg-gray-100" } ${ selectedToolGroupForDialog && selectedToolGroupForDialog.id === group.id ? "!border-[#B4108B] !shadow-lg !shadow-[#B4108B]/40" : "" }`} onClick={() => onGroupClick(group.id)} > <div className="flex items-center gap-3 mb-3"> <div className="flex flex-row items-start gap-3 justify-between w-full"> <div className="flex flex-row items-center gap-3 "> <span className={`text-xl min-w-12 w-12 min-h-12 h-12 rounded-full flex items-center justify-center bg-white border-2 ${ selectedToolGroup === group.id ? "border-[#4F33CC]" : "border-gray-200" }`} > {group.icon} </span> <div> <p className="text-[18px] leading-[100%] text-[#231A4D] truncate max-w-[200px]" title={group.name} > {group.name} </p> <p className="text-[12px] text-[#231A4D] line-clamp-2 max-w-[200px]" title={group.description || ""} style={{ display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical", overflow: "hidden", textOverflow: "ellipsis", }} > {group.description || ""} </p> </div> </div> <div className="flex items-start justify-start"> <EllipsisActions items={[ ...(onEditToolGroup ? [ { label: "Edit Tool Group", icon: <FileEdit />, callback: () => { const originalGroup = toolGroups.find( (g) => g.id === group.id, ); if (originalGroup) { onEditToolGroup(originalGroup); } }, }, ] : []), { label: "Update Tools", icon: <Wrench />, callback: () => { const originalGroup = toolGroups.find( (g) => g.id === group.id, ); if (originalGroup) { onEditGroup(originalGroup); } }, }, { label: "Delete", icon: <Trash2 />, callback: () => { const originalGroup = toolGroups.find( (g) => g.id === group.id, ); if (originalGroup) { onDeleteGroup(originalGroup); } }, }, ]} /> </div> </div> </div> <div className="flex flex-wrap gap-2"> {group.tools.slice(0, 5).map((tool, toolIndex) => ( <div key={toolIndex} className=" rounded-lg flex items-center gap-1 bg-white rounded px-2 py-1 text-xs border border-gray-200" > <DomainIcon providerName={tool.provider} providers={providers} /> <span className="text-gray-600"> {tool.provider} </span> <span className="bg-gray-100 text-gray-400 rounded-full w-5 h-5 flex items-center justify-center text-xs font-medium"> {tool.count} </span> </div> ))} {group.tools.length > 5 && ( <div className="flex items-center gap-1 bg-white rounded px-2 py-1 text-xs border border-gray-200"> <span className="bg-gray-100 text-gray-400 rounded-full w-5 h-5 flex items-center justify-center text-xs font-medium"> +{group.tools.length - 5} </span> </div> )} </div> </div> ); })} </div> {currentGroupIndex < Math.ceil(transformedToolGroups.length / 8) - 1 && ( <Button variant="secondary" size="sm" onClick={() => onGroupNavigation("right")} className="absolute right-0 top-1/2 transform -translate-y-1/2 z-10 bg-white shadow-md" > <ChevronRight className="w-4 h-4" /> </Button> )} </div> {/* Pagination dots */} <div className="flex justify-center gap-2 mt-4"> {Array.from({ length: Math.ceil(transformedToolGroups.length / 8), }).map((_, index) => ( <button key={index} className={`w-2 h-2 rounded-full transition-colors ${ index === currentGroupIndex ? "bg-[#4F33CC]" : "bg-gray-300" }`} onClick={() => setCurrentGroupIndex(index)} /> ))} </div> </div> </div> ) : ( <NoToolGroupsPlaceholder onAction={onEditModeToggle} /> )} </div> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/TheLunarCompany/lunar'

If you have feedback or need assistance with the MCP directory API, please join our Discord server