mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-21 21:50:49 +02:00
🔖 fix: Agent Marketplace Bookmark and New Chat buttons (#9549)
* don't require conversation for bookmark button * wrap marketplace component so it can correctly use context hooks * chore: re-order import statement for MarketplaceProvider --------- Co-authored-by: Danny Avila <danacordially@gmail.com>
This commit is contained in:
parent
04c3a5a861
commit
31445e391a
4 changed files with 203 additions and 214 deletions
|
@ -11,7 +11,6 @@ import { useDocumentTitle, useHasAccess, useLocalize, TranslationKeys } from '~/
|
||||||
import { useGetEndpointsQuery, useGetAgentCategoriesQuery } from '~/data-provider';
|
import { useGetEndpointsQuery, useGetAgentCategoriesQuery } from '~/data-provider';
|
||||||
import MarketplaceAdminSettings from './MarketplaceAdminSettings';
|
import MarketplaceAdminSettings from './MarketplaceAdminSettings';
|
||||||
import { SidePanelProvider, useChatContext } from '~/Providers';
|
import { SidePanelProvider, useChatContext } from '~/Providers';
|
||||||
import { MarketplaceProvider } from './MarketplaceContext';
|
|
||||||
import { SidePanelGroup } from '~/components/SidePanel';
|
import { SidePanelGroup } from '~/components/SidePanel';
|
||||||
import { OpenSidebar } from '~/components/Chat/Menus';
|
import { OpenSidebar } from '~/components/Chat/Menus';
|
||||||
import CategoryTabs from './CategoryTabs';
|
import CategoryTabs from './CategoryTabs';
|
||||||
|
@ -272,100 +271,176 @@ const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) =
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className={`relative flex w-full grow overflow-hidden bg-presentation ${className}`}>
|
<div className={`relative flex w-full grow overflow-hidden bg-presentation ${className}`}>
|
||||||
<MarketplaceProvider>
|
<SidePanelProvider>
|
||||||
<SidePanelProvider>
|
<SidePanelGroup
|
||||||
<SidePanelGroup
|
defaultLayout={defaultLayout}
|
||||||
defaultLayout={defaultLayout}
|
fullPanelCollapse={fullCollapse}
|
||||||
fullPanelCollapse={fullCollapse}
|
defaultCollapsed={defaultCollapsed}
|
||||||
defaultCollapsed={defaultCollapsed}
|
>
|
||||||
>
|
<main className="flex h-full flex-col overflow-hidden" role="main">
|
||||||
<main className="flex h-full flex-col overflow-hidden" role="main">
|
{/* Scrollable container */}
|
||||||
{/* Scrollable container */}
|
<div
|
||||||
<div
|
ref={scrollContainerRef}
|
||||||
ref={scrollContainerRef}
|
className="scrollbar-gutter-stable relative flex h-full flex-col overflow-y-auto overflow-x-hidden"
|
||||||
className="scrollbar-gutter-stable relative flex h-full flex-col overflow-y-auto overflow-x-hidden"
|
>
|
||||||
>
|
{/* Simplified header for agents marketplace - only show nav controls when needed */}
|
||||||
{/* Simplified header for agents marketplace - only show nav controls when needed */}
|
{!isSmallScreen && (
|
||||||
{!isSmallScreen && (
|
<div className="sticky top-0 z-20 flex items-center justify-between bg-surface-secondary p-2 font-semibold text-text-primary md:h-14">
|
||||||
<div className="sticky top-0 z-20 flex items-center justify-between bg-surface-secondary p-2 font-semibold text-text-primary md:h-14">
|
<div className="mx-1 flex items-center gap-2">
|
||||||
<div className="mx-1 flex items-center gap-2">
|
{!navVisible ? (
|
||||||
{!navVisible ? (
|
<>
|
||||||
<>
|
<OpenSidebar setNavVisible={setNavVisible} />
|
||||||
<OpenSidebar setNavVisible={setNavVisible} />
|
<TooltipAnchor
|
||||||
<TooltipAnchor
|
description={localize('com_ui_new_chat')}
|
||||||
description={localize('com_ui_new_chat')}
|
render={
|
||||||
render={
|
<Button
|
||||||
<Button
|
size="icon"
|
||||||
size="icon"
|
variant="outline"
|
||||||
variant="outline"
|
data-testid="agents-new-chat-button"
|
||||||
data-testid="agents-new-chat-button"
|
aria-label={localize('com_ui_new_chat')}
|
||||||
aria-label={localize('com_ui_new_chat')}
|
className="rounded-xl border border-border-light bg-surface-secondary p-2 hover:bg-surface-hover max-md:hidden"
|
||||||
className="rounded-xl border border-border-light bg-surface-secondary p-2 hover:bg-surface-hover max-md:hidden"
|
onClick={handleNewChat}
|
||||||
onClick={handleNewChat}
|
>
|
||||||
>
|
<NewChatIcon />
|
||||||
<NewChatIcon />
|
</Button>
|
||||||
</Button>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
</>
|
||||||
</>
|
) : (
|
||||||
) : (
|
// Invisible placeholder to maintain height
|
||||||
// Invisible placeholder to maintain height
|
<div className="h-10 w-10" />
|
||||||
<div className="h-10 w-10" />
|
)}
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{/* Hero Section - scrolls away */}
|
|
||||||
{!isSmallScreen && (
|
|
||||||
<div className="container mx-auto max-w-4xl">
|
|
||||||
<div className={cn('mb-8 text-center', 'mt-12')}>
|
|
||||||
<h1 className="mb-3 text-3xl font-bold tracking-tight text-text-primary md:text-5xl">
|
|
||||||
{localize('com_agents_marketplace')}
|
|
||||||
</h1>
|
|
||||||
<p className="mx-auto mb-6 max-w-2xl text-lg text-text-secondary">
|
|
||||||
{localize('com_agents_marketplace_subtitle')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{/* Sticky wrapper for search bar and categories */}
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'sticky z-10 bg-presentation pb-4',
|
|
||||||
isSmallScreen ? 'top-0' : 'top-14',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="container mx-auto max-w-4xl px-4">
|
|
||||||
{/* Search bar */}
|
|
||||||
<div className="mx-auto flex max-w-2xl gap-2 pb-6">
|
|
||||||
<SearchBar value={searchQuery} onSearch={handleSearch} />
|
|
||||||
{/* TODO: Remove this once we have a better way to handle admin settings */}
|
|
||||||
{/* Admin Settings */}
|
|
||||||
<MarketplaceAdminSettings />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Category tabs */}
|
|
||||||
<CategoryTabs
|
|
||||||
categories={categoriesQuery.data || []}
|
|
||||||
activeTab={displayCategory}
|
|
||||||
isLoading={categoriesQuery.isLoading}
|
|
||||||
onChange={handleTabChange}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Scrollable content area */}
|
)}
|
||||||
<div className="container mx-auto max-w-4xl px-4 pb-8">
|
{/* Hero Section - scrolls away */}
|
||||||
{/* Two-pane animated container wrapping category header + grid */}
|
{!isSmallScreen && (
|
||||||
<div className="relative overflow-hidden">
|
<div className="container mx-auto max-w-4xl">
|
||||||
{/* Current content pane */}
|
<div className={cn('mb-8 text-center', 'mt-12')}>
|
||||||
|
<h1 className="mb-3 text-3xl font-bold tracking-tight text-text-primary md:text-5xl">
|
||||||
|
{localize('com_agents_marketplace')}
|
||||||
|
</h1>
|
||||||
|
<p className="mx-auto mb-6 max-w-2xl text-lg text-text-secondary">
|
||||||
|
{localize('com_agents_marketplace_subtitle')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* Sticky wrapper for search bar and categories */}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'sticky z-10 bg-presentation pb-4',
|
||||||
|
isSmallScreen ? 'top-0' : 'top-14',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="container mx-auto max-w-4xl px-4">
|
||||||
|
{/* Search bar */}
|
||||||
|
<div className="mx-auto flex max-w-2xl gap-2 pb-6">
|
||||||
|
<SearchBar value={searchQuery} onSearch={handleSearch} />
|
||||||
|
{/* TODO: Remove this once we have a better way to handle admin settings */}
|
||||||
|
{/* Admin Settings */}
|
||||||
|
<MarketplaceAdminSettings />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Category tabs */}
|
||||||
|
<CategoryTabs
|
||||||
|
categories={categoriesQuery.data || []}
|
||||||
|
activeTab={displayCategory}
|
||||||
|
isLoading={categoriesQuery.isLoading}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Scrollable content area */}
|
||||||
|
<div className="container mx-auto max-w-4xl px-4 pb-8">
|
||||||
|
{/* Two-pane animated container wrapping category header + grid */}
|
||||||
|
<div className="relative overflow-hidden">
|
||||||
|
{/* Current content pane */}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
isTransitioning &&
|
||||||
|
(animationDirection === 'right'
|
||||||
|
? 'motion-safe:animate-slide-out-left'
|
||||||
|
: 'motion-safe:animate-slide-out-right'),
|
||||||
|
)}
|
||||||
|
key={`pane-current-${displayCategory}`}
|
||||||
|
>
|
||||||
|
{/* Category header - only show when not searching */}
|
||||||
|
{!searchQuery && (
|
||||||
|
<div className="mb-6 mt-6">
|
||||||
|
{(() => {
|
||||||
|
// Get category data for display
|
||||||
|
const getCategoryData = () => {
|
||||||
|
if (displayCategory === 'promoted') {
|
||||||
|
return {
|
||||||
|
name: localize('com_agents_top_picks'),
|
||||||
|
description: localize('com_agents_recommended'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (displayCategory === 'all') {
|
||||||
|
return {
|
||||||
|
name: localize('com_agents_all'),
|
||||||
|
description: localize('com_agents_all_description'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the category in the API data
|
||||||
|
const categoryData = categoriesQuery.data?.find(
|
||||||
|
(cat) => cat.value === displayCategory,
|
||||||
|
);
|
||||||
|
if (categoryData) {
|
||||||
|
return {
|
||||||
|
name: categoryData.label?.startsWith('com_')
|
||||||
|
? localize(categoryData.label as TranslationKeys)
|
||||||
|
: categoryData.label,
|
||||||
|
description: categoryData.description?.startsWith('com_')
|
||||||
|
? localize(categoryData.description as TranslationKeys)
|
||||||
|
: categoryData.description || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for unknown categories
|
||||||
|
return {
|
||||||
|
name:
|
||||||
|
displayCategory.charAt(0).toUpperCase() + displayCategory.slice(1),
|
||||||
|
description: '',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const { name, description } = getCategoryData();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="text-left">
|
||||||
|
<h2 className="text-2xl font-bold text-text-primary">{name}</h2>
|
||||||
|
{description && (
|
||||||
|
<p className="mt-2 text-text-secondary">{description}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Agent grid */}
|
||||||
|
<AgentGrid
|
||||||
|
key={`grid-${displayCategory}`}
|
||||||
|
category={displayCategory}
|
||||||
|
searchQuery={searchQuery}
|
||||||
|
onSelectAgent={handleAgentSelect}
|
||||||
|
scrollElement={scrollContainerRef.current}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Next content pane, only during transition */}
|
||||||
|
{isTransitioning && nextCategory && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
isTransitioning &&
|
'absolute inset-0',
|
||||||
(animationDirection === 'right'
|
animationDirection === 'right'
|
||||||
? 'motion-safe:animate-slide-out-left'
|
? 'motion-safe:animate-slide-in-right'
|
||||||
: 'motion-safe:animate-slide-out-right'),
|
: 'motion-safe:animate-slide-in-left',
|
||||||
)}
|
)}
|
||||||
key={`pane-current-${displayCategory}`}
|
key={`pane-next-${nextCategory}-${animationDirection}`}
|
||||||
>
|
>
|
||||||
{/* Category header - only show when not searching */}
|
{/* Category header - only show when not searching */}
|
||||||
{!searchQuery && (
|
{!searchQuery && (
|
||||||
|
@ -373,13 +448,13 @@ const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) =
|
||||||
{(() => {
|
{(() => {
|
||||||
// Get category data for display
|
// Get category data for display
|
||||||
const getCategoryData = () => {
|
const getCategoryData = () => {
|
||||||
if (displayCategory === 'promoted') {
|
if (nextCategory === 'promoted') {
|
||||||
return {
|
return {
|
||||||
name: localize('com_agents_top_picks'),
|
name: localize('com_agents_top_picks'),
|
||||||
description: localize('com_agents_recommended'),
|
description: localize('com_agents_recommended'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (displayCategory === 'all') {
|
if (nextCategory === 'all') {
|
||||||
return {
|
return {
|
||||||
name: localize('com_agents_all'),
|
name: localize('com_agents_all'),
|
||||||
description: localize('com_agents_all_description'),
|
description: localize('com_agents_all_description'),
|
||||||
|
@ -388,7 +463,7 @@ const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) =
|
||||||
|
|
||||||
// Find the category in the API data
|
// Find the category in the API data
|
||||||
const categoryData = categoriesQuery.data?.find(
|
const categoryData = categoriesQuery.data?.find(
|
||||||
(cat) => cat.value === displayCategory,
|
(cat) => cat.value === nextCategory,
|
||||||
);
|
);
|
||||||
if (categoryData) {
|
if (categoryData) {
|
||||||
return {
|
return {
|
||||||
|
@ -396,7 +471,9 @@ const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) =
|
||||||
? localize(categoryData.label as TranslationKeys)
|
? localize(categoryData.label as TranslationKeys)
|
||||||
: categoryData.label,
|
: categoryData.label,
|
||||||
description: categoryData.description?.startsWith('com_')
|
description: categoryData.description?.startsWith('com_')
|
||||||
? localize(categoryData.description as TranslationKeys)
|
? localize(
|
||||||
|
categoryData.description as Parameters<typeof localize>[0],
|
||||||
|
)
|
||||||
: categoryData.description || '',
|
: categoryData.description || '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -404,8 +481,8 @@ const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) =
|
||||||
// Fallback for unknown categories
|
// Fallback for unknown categories
|
||||||
return {
|
return {
|
||||||
name:
|
name:
|
||||||
displayCategory.charAt(0).toUpperCase() +
|
(nextCategory || '').charAt(0).toUpperCase() +
|
||||||
displayCategory.slice(1),
|
(nextCategory || '').slice(1),
|
||||||
description: '',
|
description: '',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -426,113 +503,30 @@ const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) =
|
||||||
|
|
||||||
{/* Agent grid */}
|
{/* Agent grid */}
|
||||||
<AgentGrid
|
<AgentGrid
|
||||||
key={`grid-${displayCategory}`}
|
key={`grid-${nextCategory}`}
|
||||||
category={displayCategory}
|
category={nextCategory}
|
||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
onSelectAgent={handleAgentSelect}
|
onSelectAgent={handleAgentSelect}
|
||||||
scrollElement={scrollContainerRef.current}
|
scrollElement={scrollContainerRef.current}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Next content pane, only during transition */}
|
{/* Note: Using Tailwind keyframes for slide in/out animations */}
|
||||||
{isTransitioning && nextCategory && (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'absolute inset-0',
|
|
||||||
animationDirection === 'right'
|
|
||||||
? 'motion-safe:animate-slide-in-right'
|
|
||||||
: 'motion-safe:animate-slide-in-left',
|
|
||||||
)}
|
|
||||||
key={`pane-next-${nextCategory}-${animationDirection}`}
|
|
||||||
>
|
|
||||||
{/* Category header - only show when not searching */}
|
|
||||||
{!searchQuery && (
|
|
||||||
<div className="mb-6 mt-6">
|
|
||||||
{(() => {
|
|
||||||
// Get category data for display
|
|
||||||
const getCategoryData = () => {
|
|
||||||
if (nextCategory === 'promoted') {
|
|
||||||
return {
|
|
||||||
name: localize('com_agents_top_picks'),
|
|
||||||
description: localize('com_agents_recommended'),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (nextCategory === 'all') {
|
|
||||||
return {
|
|
||||||
name: localize('com_agents_all'),
|
|
||||||
description: localize('com_agents_all_description'),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the category in the API data
|
|
||||||
const categoryData = categoriesQuery.data?.find(
|
|
||||||
(cat) => cat.value === nextCategory,
|
|
||||||
);
|
|
||||||
if (categoryData) {
|
|
||||||
return {
|
|
||||||
name: categoryData.label?.startsWith('com_')
|
|
||||||
? localize(categoryData.label as TranslationKeys)
|
|
||||||
: categoryData.label,
|
|
||||||
description: categoryData.description?.startsWith('com_')
|
|
||||||
? localize(
|
|
||||||
categoryData.description as Parameters<
|
|
||||||
typeof localize
|
|
||||||
>[0],
|
|
||||||
)
|
|
||||||
: categoryData.description || '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback for unknown categories
|
|
||||||
return {
|
|
||||||
name:
|
|
||||||
(nextCategory || '').charAt(0).toUpperCase() +
|
|
||||||
(nextCategory || '').slice(1),
|
|
||||||
description: '',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const { name, description } = getCategoryData();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="text-left">
|
|
||||||
<h2 className="text-2xl font-bold text-text-primary">{name}</h2>
|
|
||||||
{description && (
|
|
||||||
<p className="mt-2 text-text-secondary">{description}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Agent grid */}
|
|
||||||
<AgentGrid
|
|
||||||
key={`grid-${nextCategory}`}
|
|
||||||
category={nextCategory}
|
|
||||||
searchQuery={searchQuery}
|
|
||||||
onSelectAgent={handleAgentSelect}
|
|
||||||
scrollElement={scrollContainerRef.current}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Note: Using Tailwind keyframes for slide in/out animations */}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/* Agent detail dialog */}
|
|
||||||
{isDetailOpen && selectedAgent && (
|
|
||||||
<AgentDetail
|
|
||||||
agent={selectedAgent}
|
|
||||||
isOpen={isDetailOpen}
|
|
||||||
onClose={handleDetailClose}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
{/* Agent detail dialog */}
|
||||||
</SidePanelGroup>
|
{isDetailOpen && selectedAgent && (
|
||||||
</SidePanelProvider>
|
<AgentDetail
|
||||||
</MarketplaceProvider>
|
agent={selectedAgent}
|
||||||
|
isOpen={isDetailOpen}
|
||||||
|
onClose={handleDetailClose}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</SidePanelGroup>
|
||||||
|
</SidePanelProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { TooltipAnchor } from '@librechat/client';
|
import { TooltipAnchor } from '@librechat/client';
|
||||||
import { Menu, MenuButton, MenuItems } from '@headlessui/react';
|
import { Menu, MenuButton, MenuItems } from '@headlessui/react';
|
||||||
import { BookmarkFilledIcon, BookmarkIcon } from '@radix-ui/react-icons';
|
import { BookmarkFilledIcon, BookmarkIcon } from '@radix-ui/react-icons';
|
||||||
|
@ -9,7 +8,6 @@ import { useGetConversationTags } from '~/data-provider';
|
||||||
import BookmarkNavItems from './BookmarkNavItems';
|
import BookmarkNavItems from './BookmarkNavItems';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
import store from '~/store';
|
|
||||||
|
|
||||||
type BookmarkNavProps = {
|
type BookmarkNavProps = {
|
||||||
tags: string[];
|
tags: string[];
|
||||||
|
@ -20,7 +18,6 @@ type BookmarkNavProps = {
|
||||||
const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags, isSmallScreen }: BookmarkNavProps) => {
|
const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags, isSmallScreen }: BookmarkNavProps) => {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { data } = useGetConversationTags();
|
const { data } = useGetConversationTags();
|
||||||
const conversation = useRecoilValue(store.conversationByIndex(0));
|
|
||||||
const label = useMemo(
|
const label = useMemo(
|
||||||
() => (tags.length > 0 ? tags.join(', ') : localize('com_ui_bookmarks')),
|
() => (tags.length > 0 ? tags.join(', ') : localize('com_ui_bookmarks')),
|
||||||
[tags, localize],
|
[tags, localize],
|
||||||
|
@ -56,11 +53,9 @@ const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags, isSmallScreen }: Boo
|
||||||
anchor="bottom"
|
anchor="bottom"
|
||||||
className="absolute left-0 top-full z-[100] mt-1 w-60 translate-y-0 overflow-hidden rounded-lg bg-surface-secondary p-1.5 shadow-lg outline-none"
|
className="absolute left-0 top-full z-[100] mt-1 w-60 translate-y-0 overflow-hidden rounded-lg bg-surface-secondary p-1.5 shadow-lg outline-none"
|
||||||
>
|
>
|
||||||
{data && conversation && (
|
{data && (
|
||||||
<BookmarkContext.Provider value={{ bookmarks: data.filter((tag) => tag.count > 0) }}>
|
<BookmarkContext.Provider value={{ bookmarks: data.filter((tag) => tag.count > 0) }}>
|
||||||
<BookmarkNavItems
|
<BookmarkNavItems
|
||||||
// Currently selected conversation
|
|
||||||
conversation={conversation}
|
|
||||||
// List of selected tags(string)
|
// List of selected tags(string)
|
||||||
tags={tags}
|
tags={tags}
|
||||||
// When a user selects a tag, this `setTags` function is called to refetch the list of conversations for the selected tag
|
// When a user selects a tag, this `setTags` function is called to refetch the list of conversations for the selected tag
|
||||||
|
|
|
@ -1,25 +1,16 @@
|
||||||
import { useEffect, useState, type FC } from 'react';
|
import { type FC } from 'react';
|
||||||
import { CrossCircledIcon } from '@radix-ui/react-icons';
|
import { CrossCircledIcon } from '@radix-ui/react-icons';
|
||||||
import type { TConversation } from 'librechat-data-provider';
|
|
||||||
import { useBookmarkContext } from '~/Providers/BookmarkContext';
|
import { useBookmarkContext } from '~/Providers/BookmarkContext';
|
||||||
import { BookmarkItems, BookmarkItem } from '~/components/Bookmarks';
|
import { BookmarkItems, BookmarkItem } from '~/components/Bookmarks';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
const BookmarkNavItems: FC<{
|
const BookmarkNavItems: FC<{
|
||||||
conversation: TConversation;
|
|
||||||
tags: string[];
|
tags: string[];
|
||||||
setTags: (tags: string[]) => void;
|
setTags: (tags: string[]) => void;
|
||||||
}> = ({ conversation, tags = [], setTags }) => {
|
}> = ({ tags = [], setTags }) => {
|
||||||
const [currentConversation, setCurrentConversation] = useState<TConversation>();
|
|
||||||
const { bookmarks } = useBookmarkContext();
|
const { bookmarks } = useBookmarkContext();
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!currentConversation) {
|
|
||||||
setCurrentConversation(conversation);
|
|
||||||
}
|
|
||||||
}, [conversation, currentConversation]);
|
|
||||||
|
|
||||||
const getUpdatedSelected = (tag: string) => {
|
const getUpdatedSelected = (tag: string) => {
|
||||||
if (tags.some((selectedTag) => selectedTag === tag)) {
|
if (tags.some((selectedTag) => selectedTag === tag)) {
|
||||||
return tags.filter((selectedTag) => selectedTag !== tag);
|
return tags.filter((selectedTag) => selectedTag !== tag);
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
TwoFactorScreen,
|
TwoFactorScreen,
|
||||||
RequestPasswordReset,
|
RequestPasswordReset,
|
||||||
} from '~/components/Auth';
|
} from '~/components/Auth';
|
||||||
|
import { MarketplaceProvider } from '~/components/Agents/MarketplaceContext';
|
||||||
import AgentMarketplace from '~/components/Agents/Marketplace';
|
import AgentMarketplace from '~/components/Agents/Marketplace';
|
||||||
import { OAuthSuccess, OAuthError } from '~/components/OAuth';
|
import { OAuthSuccess, OAuthError } from '~/components/OAuth';
|
||||||
import { AuthContextProvider } from '~/hooks/AuthContext';
|
import { AuthContextProvider } from '~/hooks/AuthContext';
|
||||||
|
@ -112,11 +113,19 @@ export const router = createBrowserRouter(
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'agents',
|
path: 'agents',
|
||||||
element: <AgentMarketplace />,
|
element: (
|
||||||
|
<MarketplaceProvider>
|
||||||
|
<AgentMarketplace />
|
||||||
|
</MarketplaceProvider>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'agents/:category',
|
path: 'agents/:category',
|
||||||
element: <AgentMarketplace />,
|
element: (
|
||||||
|
<MarketplaceProvider>
|
||||||
|
<AgentMarketplace />
|
||||||
|
</MarketplaceProvider>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue