mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-31 23:58:50 +01:00
🪟 style: Agent Marketplace UI Responsiveness, a11y, and Navigation (#9068)
* refactor: Agent Marketplace Button with access control * fix(agent-marketplace): update marketplace UI and access control * fix(agent-card): handle optional agent description for accessibility * fix(agent-card): remove unnecessary icon checks from tests * chore: remove unused keys --------- Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
parent
c78fd0fc83
commit
4ec7bcb60f
17 changed files with 164 additions and 162 deletions
66
client/src/components/Nav/AgentMarketplaceButton.tsx
Normal file
66
client/src/components/Nav/AgentMarketplaceButton.tsx
Normal file
|
|
@ -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 (
|
||||
<TooltipAnchor
|
||||
description={localize('com_agents_marketplace')}
|
||||
render={
|
||||
<Button
|
||||
variant="outline"
|
||||
data-testid="nav-agents-marketplace-button"
|
||||
aria-label={localize('com_agents_marketplace')}
|
||||
className="rounded-full border-none bg-transparent p-2 hover:bg-surface-hover md:rounded-xl"
|
||||
onClick={handleAgentMarketplace}
|
||||
>
|
||||
<LayoutGrid className="icon-lg text-text-primary" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -45,16 +45,9 @@ const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags, isSmallScreen }: Boo
|
|||
data-testid="bookmark-menu"
|
||||
>
|
||||
{tags.length > 0 ? (
|
||||
<BookmarkFilledIcon
|
||||
/** `isSmallScreen` is used because lazy loading is not influencing `md:` prefix for some reason */
|
||||
className={cn('text-text-primary', isSmallScreen ? 'icon-md-heavy' : 'icon-lg')}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<BookmarkFilledIcon className="icon-lg text-text-primary" aria-hidden="true" />
|
||||
) : (
|
||||
<BookmarkIcon
|
||||
className={cn('text-text-primary', isSmallScreen ? 'icon-md-heavy' : 'icon-lg')}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<BookmarkIcon className="icon-lg text-text-primary" aria-hidden="true" />
|
||||
)}
|
||||
</MenuButton>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import store from '~/store';
|
|||
|
||||
const BookmarkNav = lazy(() => import('./Bookmarks/BookmarkNav'));
|
||||
const AccountSettings = lazy(() => import('./AccountSettings'));
|
||||
const AgentMarketplaceButton = lazy(() => import('./AgentMarketplaceButton'));
|
||||
|
||||
const NAV_WIDTH_DESKTOP = '260px';
|
||||
const NAV_WIDTH_MOBILE = '320px';
|
||||
|
|
@ -155,16 +156,22 @@ const Nav = memo(
|
|||
);
|
||||
|
||||
const headerButtons = useMemo(
|
||||
() =>
|
||||
hasAccessToBookmarks && (
|
||||
<>
|
||||
<div className="mt-1.5" />
|
||||
<Suspense fallback={null}>
|
||||
<BookmarkNav tags={tags} setTags={setTags} isSmallScreen={isSmallScreen} />
|
||||
</Suspense>
|
||||
</>
|
||||
),
|
||||
[hasAccessToBookmarks, tags, isSmallScreen],
|
||||
() => (
|
||||
<>
|
||||
<Suspense fallback={null}>
|
||||
<AgentMarketplaceButton isSmallScreen={isSmallScreen} toggleNav={toggleNavVisible} />
|
||||
</Suspense>
|
||||
{hasAccessToBookmarks && (
|
||||
<>
|
||||
<div className="mt-1.5" />
|
||||
<Suspense fallback={null}>
|
||||
<BookmarkNav tags={tags} setTags={setTags} isSmallScreen={isSmallScreen} />
|
||||
</Suspense>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
[hasAccessToBookmarks, tags, isSmallScreen, toggleNavVisible],
|
||||
);
|
||||
|
||||
const [isSearchLoading, setIsSearchLoading] = useState(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import React, { useCallback, useContext } from 'react';
|
||||
import { LayoutGrid } from 'lucide-react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { QueryKeys, Constants, PermissionTypes, Permissions } from 'librechat-data-provider';
|
||||
import { QueryKeys, Constants } from 'librechat-data-provider';
|
||||
import { TooltipAnchor, NewChatIcon, MobileSidebar, Sidebar, Button } from '@librechat/client';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
import { useLocalize, useNewConvo, useHasAccess, AuthContext } from '~/hooks';
|
||||
import { useLocalize, useNewConvo } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
||||
export default function NewChat({
|
||||
|
|
@ -27,15 +26,6 @@ export default function NewChat({
|
|||
const navigate = useNavigate();
|
||||
const localize = useLocalize();
|
||||
const { conversation } = store.useCreateConversationAtom(index);
|
||||
const authContext = useContext(AuthContext);
|
||||
const hasAccessToAgents = useHasAccess({
|
||||
permissionType: PermissionTypes.AGENTS,
|
||||
permission: Permissions.USE,
|
||||
});
|
||||
const hasAccessToMarketplace = useHasAccess({
|
||||
permissionType: PermissionTypes.MARKETPLACE,
|
||||
permission: Permissions.USE,
|
||||
});
|
||||
|
||||
const clickHandler: React.MouseEventHandler<HTMLButtonElement> = useCallback(
|
||||
(e) => {
|
||||
|
|
@ -57,21 +47,6 @@ export default function NewChat({
|
|||
[queryClient, conversation, newConvo, navigate, toggleNav, isSmallScreen],
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between py-[2px] md:py-2">
|
||||
|
|
@ -91,25 +66,7 @@ export default function NewChat({
|
|||
</Button>
|
||||
}
|
||||
/>
|
||||
<div className="flex">
|
||||
{showAgentMarketplace && (
|
||||
<div className="flex">
|
||||
<TooltipAnchor
|
||||
description={localize('com_agents_marketplace')}
|
||||
render={
|
||||
<Button
|
||||
variant="outline"
|
||||
data-testid="nav-agents-marketplace-button"
|
||||
aria-label={localize('com_agents_marketplace')}
|
||||
className="rounded-full border-none bg-transparent p-2 hover:bg-surface-hover md:rounded-xl"
|
||||
onClick={handleAgentMarketplace}
|
||||
>
|
||||
<LayoutGrid className="icon-md md:h-6 md:w-6" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-0.5">
|
||||
{headerButtons}
|
||||
|
||||
<TooltipAnchor
|
||||
|
|
@ -123,7 +80,7 @@ export default function NewChat({
|
|||
className="rounded-full border-none bg-transparent p-2 hover:bg-surface-hover md:rounded-xl"
|
||||
onClick={clickHandler}
|
||||
>
|
||||
<NewChatIcon className="icon-md md:h-6 md:w-6" />
|
||||
<NewChatIcon className="icon-lg text-text-primary" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue