import React, { useState } from 'react'; import { Button, Spinner } from '@librechat/client'; import type t from 'librechat-data-provider'; import { useDynamicAgentQuery, useAgentCategories } from '~/hooks/Agents'; import { SmartLoader, useHasData } from './SmartLoader'; import useLocalize from '~/hooks/useLocalize'; import ErrorDisplay from './ErrorDisplay'; import AgentCard from './AgentCard'; import { cn } from '~/utils'; interface AgentGridProps { category: string; // Currently selected category searchQuery: string; // Current search query onSelectAgent: (agent: t.Agent) => void; // Callback when agent is selected } // Interface for the actual data structure returned by the API interface AgentGridData { agents: t.Agent[]; pagination?: { hasMore: boolean; current: number; total: number; }; } /** * Component for displaying a grid of agent cards */ const AgentGrid: React.FC = ({ category, searchQuery, onSelectAgent }) => { const localize = useLocalize(); const [page, setPage] = useState(1); // Get category data from API const { categories } = useAgentCategories(); // Single dynamic query that handles all cases - much cleaner! const { data: rawData, isLoading, error, isFetching, refetch, } = useDynamicAgentQuery({ category, searchQuery, page, limit: 6, }); // Type the data properly const data = rawData as AgentGridData | undefined; // Check if we have meaningful data to prevent unnecessary loading states const hasData = useHasData(data); /** * Get category display name from API data or use fallback */ const getCategoryDisplayName = (categoryValue: string) => { const categoryData = categories.find((cat) => cat.value === categoryValue); if (categoryData) { return categoryData.label; } // Fallback for special categories or unknown categories if (categoryValue === 'promoted') { return localize('com_agents_top_picks'); } if (categoryValue === 'all') { return 'All'; } // Simple capitalization for unknown categories return categoryValue.charAt(0).toUpperCase() + categoryValue.slice(1); }; /** * Load more agents when "See More" button is clicked */ const handleLoadMore = () => { setPage((prevPage) => prevPage + 1); }; /** * Reset page when category or search changes */ React.useEffect(() => { setPage(1); }, [category, searchQuery]); /** * Get the appropriate title for the agents grid based on current state */ const getGridTitle = () => { if (searchQuery) { return localize('com_agents_results_for', { query: searchQuery }); } return getCategoryDisplayName(category); }; // Loading skeleton component const loadingSkeleton = (
{Array(6) .fill(0) .map((_, index) => (
))}
); // Handle error state with enhanced error display if (error) { return ( refetch()} context={{ searchQuery, category, }} /> ); } // Main content component with proper semantic structure const mainContent = (
{/* Grid title - only show for search results */} {searchQuery && (

{getGridTitle()}

)} {/* Handle empty results with enhanced accessibility */} {(!data?.agents || data.agents.length === 0) && !isLoading && !isFetching ? (

{searchQuery ? localize('com_agents_search_empty_heading') : localize('com_agents_empty_state_heading')}

{searchQuery ? localize('com_agents_no_results') : localize('com_agents_none_in_category')}

) : ( <> {/* Announcement for screen readers */}
{localize('com_agents_grid_announcement', { count: data?.agents?.length || 0, category: getCategoryDisplayName(category), })}
{/* Agent grid - 2 per row with proper semantic structure */} {data?.agents && data.agents.length > 0 && (
{data.agents.map((agent: t.Agent, index: number) => (
onSelectAgent(agent)} />
))}
)} {/* Loading indicator when fetching more with accessibility */} {isFetching && page > 1 && (
{localize('com_agents_loading')}
)} {/* Load more button with enhanced accessibility */} {data?.pagination?.hasMore && !isFetching && (
)} )}
); // Use SmartLoader to prevent unnecessary loading flashes return ( {mainContent} ); }; export default AgentGrid;