diff --git a/client/src/components/Agents/AgentCard.tsx b/client/src/components/Agents/AgentCard.tsx index 24d6b83834..fe36f3bf78 100644 --- a/client/src/components/Agents/AgentCard.tsx +++ b/client/src/components/Agents/AgentCard.tsx @@ -28,7 +28,7 @@ const AgentCard: React.FC = ({ agent, onClick, className = '' }) onClick={onClick} aria-label={localize('com_agents_agent_card_label', { name: agent.name, - description: agent.description || localize('com_agents_no_description'), + description: agent.description ?? '', })} aria-describedby={`agent-${agent.id}-description`} tabIndex={0} @@ -47,50 +47,47 @@ const AgentCard: React.FC = ({ agent, onClick, className = '' })
{renderAgentAvatar(agent, { size: 'sm' })}
{/* Category tag */} -
- {agent.category && ( + {agent.category && ( +
- )} -
+
+ )} {/* Right column: Name, description, and other content */} -
-
+
+
{/* Agent name */} - {/* Owner info */} - {(() => { - const displayName = getContactDisplayName(agent); - if (displayName) { - return ( -
- - -
- ); - } - return null; - })()} + {/* Agent description */} +

+ {agent.description ?? ''} +

- {/* Agent description */} -

- {agent.description || ( - - )} -

+ {/* Owner info - moved to bottom right */} + {(() => { + const displayName = getContactDisplayName(agent); + if (displayName) { + return ( +
+
+ +
+
+ ); + } + return null; + })()}
diff --git a/client/src/components/Agents/AgentDetail.tsx b/client/src/components/Agents/AgentDetail.tsx index 63c368357c..f4588fed8f 100644 --- a/client/src/components/Agents/AgentDetail.tsx +++ b/client/src/components/Agents/AgentDetail.tsx @@ -126,10 +126,7 @@ const AgentDetail: React.FC = ({ agent, isOpen, onClose }) => return ( !open && onClose()}> - + {/* Copy link button - positioned next to close button */} - + {`${localize('com_ui_admin_settings')} - ${localize( 'com_ui_marketplace', )}`} diff --git a/client/src/components/Agents/SearchBar.tsx b/client/src/components/Agents/SearchBar.tsx index b261bc90ed..af463682b2 100644 --- a/client/src/components/Agents/SearchBar.tsx +++ b/client/src/components/Agents/SearchBar.tsx @@ -73,7 +73,7 @@ const SearchBar: React.FC = ({ value, onSearch, className = '' } value={searchTerm} onChange={handleChange} placeholder={localize('com_agents_search_placeholder')} - className="h-14 rounded-2xl border-border-medium bg-transparent pl-12 pr-12 text-lg text-text-primary shadow-md transition-[border-color,box-shadow] duration-200 placeholder:text-text-secondary focus:border-border-heavy focus:shadow-lg focus:ring-0" + className="h-12 rounded-xl border-border-medium bg-transparent pl-12 pr-12 text-lg text-text-primary shadow-md transition-[border-color,box-shadow] duration-200 placeholder:text-text-secondary focus:border-border-heavy focus:shadow-lg focus:ring-0" aria-label={localize('com_agents_search_aria')} aria-describedby="search-instructions search-results-count" autoComplete="off" diff --git a/client/src/components/Agents/tests/Accessibility.spec.tsx b/client/src/components/Agents/tests/Accessibility.spec.tsx index 37baab47c2..e70ad3f3af 100644 --- a/client/src/components/Agents/tests/Accessibility.spec.tsx +++ b/client/src/components/Agents/tests/Accessibility.spec.tsx @@ -53,7 +53,6 @@ const mockLocalize = jest.fn((key: string, options?: any) => { com_agents_search_placeholder: 'Search agents...', com_agents_clear_search: 'Clear search', com_agents_agent_card_label: `${options?.name} agent. ${options?.description}`, - com_agents_no_description: 'No description available', com_agents_grid_announcement: `Showing ${options?.count} agents in ${options?.category} category`, com_agents_load_more_label: `Load more agents from ${options?.category} category`, com_agents_error_retry: 'Try Again', @@ -307,13 +306,6 @@ describe('Accessibility Improvements', () => { expect(card).toHaveAttribute('role', 'button'); }); - it('handles agents without descriptions', () => { - const agentWithoutDesc = { ...mockAgent, description: undefined }; - render(); - - expect(screen.getByText('No description available')).toBeInTheDocument(); - }); - it('supports keyboard interaction', () => { const onClick = jest.fn(); render(); diff --git a/client/src/components/Agents/tests/AgentCard.spec.tsx b/client/src/components/Agents/tests/AgentCard.spec.tsx index df6cdcaedd..35aa3e7e3a 100644 --- a/client/src/components/Agents/tests/AgentCard.spec.tsx +++ b/client/src/components/Agents/tests/AgentCard.spec.tsx @@ -48,7 +48,6 @@ describe('AgentCard', () => { expect(screen.getByText('Test Agent')).toBeInTheDocument(); expect(screen.getByText('A test agent for testing purposes')).toBeInTheDocument(); - expect(screen.getByText('🔹')).toBeInTheDocument(); expect(screen.getByText('Test Support')).toBeInTheDocument(); }); @@ -152,7 +151,6 @@ describe('AgentCard', () => { render(); - expect(screen.getByText('🔹')).toBeInTheDocument(); expect(screen.getByText('John Doe')).toBeInTheDocument(); }); @@ -165,7 +163,6 @@ describe('AgentCard', () => { render(); - expect(screen.getByText('🔹')).toBeInTheDocument(); expect(screen.getByText('contact@example.com')).toBeInTheDocument(); }); @@ -178,7 +175,6 @@ describe('AgentCard', () => { render(); - expect(screen.getByText('🔹')).toBeInTheDocument(); expect(screen.getByText('Support Team')).toBeInTheDocument(); expect(screen.queryByText('John Doe')).not.toBeInTheDocument(); }); @@ -195,7 +191,6 @@ describe('AgentCard', () => { render(); - expect(screen.getByText('🔹')).toBeInTheDocument(); expect(screen.getByText('Support Team')).toBeInTheDocument(); expect(screen.queryByText('support@example.com')).not.toBeInTheDocument(); }); diff --git a/client/src/components/Agents/tests/AgentDetail.spec.tsx b/client/src/components/Agents/tests/AgentDetail.spec.tsx index 2a514a58c1..54fa764a86 100644 --- a/client/src/components/Agents/tests/AgentDetail.spec.tsx +++ b/client/src/components/Agents/tests/AgentDetail.spec.tsx @@ -179,7 +179,6 @@ describe('AgentDetail', () => { renderWithProviders(); expect(screen.getByText('com_agents_loading')).toBeInTheDocument(); - expect(screen.getByText('com_agents_no_description')).toBeInTheDocument(); }); it('should render copy link button', () => { diff --git a/client/src/components/Nav/AgentMarketplaceButton.tsx b/client/src/components/Nav/AgentMarketplaceButton.tsx new file mode 100644 index 0000000000..72f6cc12db --- /dev/null +++ b/client/src/components/Nav/AgentMarketplaceButton.tsx @@ -0,0 +1,66 @@ +import React, { useCallback, useContext } from 'react'; +import { LayoutGrid } from 'lucide-react'; +import { useNavigate } from 'react-router-dom'; +import { PermissionTypes, Permissions } from 'librechat-data-provider'; +import { TooltipAnchor, Button } from '@librechat/client'; +import { useLocalize, useHasAccess, AuthContext } from '~/hooks'; + +interface AgentMarketplaceButtonProps { + isSmallScreen?: boolean; + toggleNav: () => void; +} + +export default function AgentMarketplaceButton({ + isSmallScreen, + toggleNav, +}: AgentMarketplaceButtonProps) { + const navigate = useNavigate(); + const localize = useLocalize(); + const authContext = useContext(AuthContext); + + const hasAccessToAgents = useHasAccess({ + permissionType: PermissionTypes.AGENTS, + permission: Permissions.USE, + }); + + const hasAccessToMarketplace = useHasAccess({ + permissionType: PermissionTypes.MARKETPLACE, + permission: Permissions.USE, + }); + + const handleAgentMarketplace = useCallback(() => { + navigate('/agents'); + if (isSmallScreen) { + toggleNav(); + } + }, [navigate, isSmallScreen, toggleNav]); + + // Check if auth is ready (avoid race conditions) + const authReady = + authContext?.isAuthenticated !== undefined && + (authContext?.isAuthenticated === false || authContext?.user !== undefined); + + // Show agent marketplace when marketplace permission is enabled, auth is ready, and user has access to agents + const showAgentMarketplace = authReady && hasAccessToAgents && hasAccessToMarketplace; + + if (!showAgentMarketplace) { + return null; + } + + return ( + + + + } + /> + ); +} diff --git a/client/src/components/Nav/Bookmarks/BookmarkNav.tsx b/client/src/components/Nav/Bookmarks/BookmarkNav.tsx index 54a000df5e..edff93a9e6 100644 --- a/client/src/components/Nav/Bookmarks/BookmarkNav.tsx +++ b/client/src/components/Nav/Bookmarks/BookmarkNav.tsx @@ -45,16 +45,9 @@ const BookmarkNav: FC = ({ tags, setTags, isSmallScreen }: Boo data-testid="bookmark-menu" > {tags.length > 0 ? ( -