From 6137a089b3ff1b09439e74bb7bd683c9e000cb53 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 11 Aug 2025 22:15:33 -0400 Subject: [PATCH] refactor: Add PromptGroups context provider and integrate it into relevant components --- client/src/Providers/PromptGroupsContext.tsx | 76 +++++++++++++++ client/src/Providers/index.ts | 1 + .../components/Chat/Input/PromptsCommand.tsx | 31 +----- client/src/components/Chat/PromptCard.tsx | 15 --- client/src/components/Chat/Prompts.tsx | 96 ------------------- .../Prompts/Groups/FilterPrompts.tsx | 2 +- .../Prompts/Groups/GroupSidePanel.tsx | 2 +- client/src/components/Prompts/PromptForm.tsx | 11 ++- .../components/Prompts/PromptsAccordion.tsx | 4 +- client/src/components/Prompts/PromptsView.tsx | 5 +- client/src/routes/Root.tsx | 19 ++-- 11 files changed, 106 insertions(+), 156 deletions(-) create mode 100644 client/src/Providers/PromptGroupsContext.tsx delete mode 100644 client/src/components/Chat/PromptCard.tsx delete mode 100644 client/src/components/Chat/Prompts.tsx diff --git a/client/src/Providers/PromptGroupsContext.tsx b/client/src/Providers/PromptGroupsContext.tsx new file mode 100644 index 0000000000..1dd158d7d2 --- /dev/null +++ b/client/src/Providers/PromptGroupsContext.tsx @@ -0,0 +1,76 @@ +import React, { createContext, useContext, ReactNode, useMemo } from 'react'; +import type { TPromptGroup } from 'librechat-data-provider'; +import type { PromptOption } from '~/common'; +import CategoryIcon from '~/components/Prompts/Groups/CategoryIcon'; +import { useGetAllPromptGroups } from '~/data-provider'; +import { usePromptGroupsNav } from '~/hooks'; +import { mapPromptGroups } from '~/utils'; + +type AllPromptGroupsData = + | { + promptsMap: Record; + promptGroups: PromptOption[]; + } + | undefined; + +type PromptGroupsContextType = + | (ReturnType & { + allPromptGroups: { + data: AllPromptGroupsData; + isLoading: boolean; + }; + }) + | null; + +const PromptGroupsContext = createContext(null); + +export const PromptGroupsProvider = ({ children }: { children: ReactNode }) => { + const promptGroupsNav = usePromptGroupsNav(); + const { data: allGroupsData, isLoading: isLoadingAll } = useGetAllPromptGroups(undefined, { + select: (data) => { + const mappedArray: PromptOption[] = data.map((group) => ({ + id: group._id ?? '', + type: 'prompt', + value: group.command ?? group.name, + label: `${group.command != null && group.command ? `/${group.command} - ` : ''}${ + group.name + }: ${ + (group.oneliner?.length ?? 0) > 0 + ? group.oneliner + : (group.productionPrompt?.prompt ?? '') + }`, + icon: , + })); + + const promptsMap = mapPromptGroups(data); + + return { + promptsMap, + promptGroups: mappedArray, + }; + }, + }); + + const contextValue = useMemo( + () => ({ + ...promptGroupsNav, + allPromptGroups: { + data: allGroupsData, + isLoading: isLoadingAll, + }, + }), + [promptGroupsNav, allGroupsData, isLoadingAll], + ); + + return ( + {children} + ); +}; + +export const usePromptGroupsContext = () => { + const context = useContext(PromptGroupsContext); + if (!context) { + throw new Error('usePromptGroupsContext must be used within a PromptGroupsProvider'); + } + return context; +}; diff --git a/client/src/Providers/index.ts b/client/src/Providers/index.ts index 50dabaf5a9..581e3cd55a 100644 --- a/client/src/Providers/index.ts +++ b/client/src/Providers/index.ts @@ -24,4 +24,5 @@ export * from './SearchContext'; export * from './BadgeRowContext'; export * from './SidePanelContext'; export * from './ArtifactsContext'; +export * from './PromptGroupsContext'; export { default as BadgeRowProvider } from './BadgeRowContext'; diff --git a/client/src/components/Chat/Input/PromptsCommand.tsx b/client/src/components/Chat/Input/PromptsCommand.tsx index 0756be17e8..5f384e631a 100644 --- a/client/src/components/Chat/Input/PromptsCommand.tsx +++ b/client/src/components/Chat/Input/PromptsCommand.tsx @@ -5,11 +5,10 @@ import { useSetRecoilState, useRecoilValue } from 'recoil'; import { PermissionTypes, Permissions } from 'librechat-data-provider'; import type { TPromptGroup } from 'librechat-data-provider'; import type { PromptOption } from '~/common'; -import { removeCharIfLast, mapPromptGroups, detectVariables } from '~/utils'; +import { removeCharIfLast, detectVariables } from '~/utils'; import VariableDialog from '~/components/Prompts/Groups/VariableDialog'; -import CategoryIcon from '~/components/Prompts/Groups/CategoryIcon'; +import { usePromptGroupsContext } from '~/Providers'; import { useLocalize, useHasAccess } from '~/hooks'; -import { useGetAllPromptGroups } from '~/data-provider'; import MentionItem from './MentionItem'; import store from '~/store'; @@ -60,30 +59,8 @@ function PromptsCommand({ permission: Permissions.USE, }); - const { data, isLoading } = useGetAllPromptGroups(undefined, { - enabled: hasAccess, - select: (data) => { - const mappedArray = data.map((group) => ({ - id: group._id, - value: group.command ?? group.name, - label: `${group.command != null && group.command ? `/${group.command} - ` : ''}${ - group.name - }: ${ - (group.oneliner?.length ?? 0) > 0 - ? group.oneliner - : (group.productionPrompt?.prompt ?? '') - }`, - icon: , - })); - - const promptsMap = mapPromptGroups(data); - - return { - promptsMap, - promptGroups: mappedArray, - }; - }, - }); + const { allPromptGroups } = usePromptGroupsContext(); + const { data, isLoading } = allPromptGroups; const [activeIndex, setActiveIndex] = useState(0); const timeoutRef = useRef(null); diff --git a/client/src/components/Chat/PromptCard.tsx b/client/src/components/Chat/PromptCard.tsx deleted file mode 100644 index 4a37fd998c..0000000000 --- a/client/src/components/Chat/PromptCard.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { TPromptGroup } from 'librechat-data-provider'; -import CategoryIcon from '~/components/Prompts/Groups/CategoryIcon'; - -export default function PromptCard({ promptGroup }: { promptGroup?: TPromptGroup }) { - return ( -
-
- -
-

- {(promptGroup?.oneliner ?? '') || promptGroup?.productionPrompt?.prompt} -

-
- ); -} diff --git a/client/src/components/Chat/Prompts.tsx b/client/src/components/Chat/Prompts.tsx deleted file mode 100644 index f6c1ba09b3..0000000000 --- a/client/src/components/Chat/Prompts.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { ChevronLeft, ChevronRight } from 'lucide-react'; -import { usePromptGroupsNav } from '~/hooks'; -import PromptCard from './PromptCard'; -import { Button } from '../ui'; - -export default function Prompts() { - const { prevPage, nextPage, hasNextPage, promptGroups, hasPreviousPage, setPageSize, pageSize } = - usePromptGroupsNav(); - - const renderPromptCards = (start = 0, count) => { - return promptGroups - .slice(start, count + start) - .map((promptGroup) => ); - }; - - const getRows = () => { - switch (pageSize) { - case 4: - return [4]; - case 8: - return [4, 4]; - case 12: - return [4, 4, 4]; - default: - return []; - } - }; - - const rows = getRows(); - - return ( -
-
- - - -
-
-
- {rows.map((rowSize, index) => ( -
- {renderPromptCards(rowSize * index, rowSize)} -
- ))} -
-
- - -
-
-
- ); -} diff --git a/client/src/components/Prompts/Groups/FilterPrompts.tsx b/client/src/components/Prompts/Groups/FilterPrompts.tsx index 47608eba9b..0e66027493 100644 --- a/client/src/components/Prompts/Groups/FilterPrompts.tsx +++ b/client/src/components/Prompts/Groups/FilterPrompts.tsx @@ -4,7 +4,7 @@ import { SystemCategories } from 'librechat-data-provider'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { Dropdown, AnimatedSearchInput } from '@librechat/client'; import type { Option } from '~/common'; -import { usePromptGroupsNav, useLocalize, useCategories } from '~/hooks'; +import { useLocalize, useCategories, usePromptGroupsNav } from '~/hooks'; import { cn } from '~/utils'; import store from '~/store'; diff --git a/client/src/components/Prompts/Groups/GroupSidePanel.tsx b/client/src/components/Prompts/Groups/GroupSidePanel.tsx index ae263dca54..705a557807 100644 --- a/client/src/components/Prompts/Groups/GroupSidePanel.tsx +++ b/client/src/components/Prompts/Groups/GroupSidePanel.tsx @@ -4,7 +4,7 @@ import { useMediaQuery } from '@librechat/client'; import PanelNavigation from '~/components/Prompts/Groups/PanelNavigation'; import ManagePrompts from '~/components/Prompts/ManagePrompts'; import List from '~/components/Prompts/Groups/List'; -import { usePromptGroupsNav } from '~/hooks'; +import type { usePromptGroupsNav } from '~/hooks'; import { cn } from '~/utils'; export default function GroupSidePanel({ diff --git a/client/src/components/Prompts/PromptForm.tsx b/client/src/components/Prompts/PromptForm.tsx index 60501ee0ab..565d822ea0 100644 --- a/client/src/components/Prompts/PromptForm.tsx +++ b/client/src/components/Prompts/PromptForm.tsx @@ -6,7 +6,12 @@ import { Menu, Rocket } from 'lucide-react'; import { useForm, FormProvider } from 'react-hook-form'; import { useParams, useOutletContext } from 'react-router-dom'; import { Button, Skeleton, useToastContext } from '@librechat/client'; -import { Permissions, PermissionTypes, PermissionBits } from 'librechat-data-provider'; +import { + Permissions, + ResourceType, + PermissionBits, + PermissionTypes, +} from 'librechat-data-provider'; import type { TCreatePrompt, TPrompt, TPromptGroup } from 'librechat-data-provider'; import { useGetPrompts, @@ -173,16 +178,14 @@ const PromptForm = () => { const [showSidePanel, setShowSidePanel] = useState(false); const sidePanelWidth = '320px'; - // Fetch group early so it is available for later hooks. const { data: group, isLoading: isLoadingGroup } = useGetPromptGroup(promptId); const { data: prompts = [], isLoading: isLoadingPrompts } = useGetPrompts( { groupId: promptId }, { enabled: !!promptId }, ); - // Check permissions for the promptGroup const { hasPermission, isLoading: permissionsLoading } = useResourcePermissions( - 'promptGroup', + ResourceType.PROMPTGROUP, group?._id || '', ); diff --git a/client/src/components/Prompts/PromptsAccordion.tsx b/client/src/components/Prompts/PromptsAccordion.tsx index 6bc84005c4..91523c5a82 100644 --- a/client/src/components/Prompts/PromptsAccordion.tsx +++ b/client/src/components/Prompts/PromptsAccordion.tsx @@ -1,10 +1,10 @@ import PromptSidePanel from '~/components/Prompts/Groups/GroupSidePanel'; import AutoSendPrompt from '~/components/Prompts/Groups/AutoSendPrompt'; import FilterPrompts from '~/components/Prompts/Groups/FilterPrompts'; -import { usePromptGroupsNav } from '~/hooks'; +import { usePromptGroupsContext } from '~/Providers'; export default function PromptsAccordion() { - const groupsNav = usePromptGroupsNav(); + const groupsNav = usePromptGroupsContext(); return (
diff --git a/client/src/components/Prompts/PromptsView.tsx b/client/src/components/Prompts/PromptsView.tsx index 9a5b3f4eaa..d3a62a9586 100644 --- a/client/src/components/Prompts/PromptsView.tsx +++ b/client/src/components/Prompts/PromptsView.tsx @@ -3,14 +3,15 @@ import { Outlet, useParams, useNavigate } from 'react-router-dom'; import { PermissionTypes, Permissions } from 'librechat-data-provider'; import FilterPrompts from '~/components/Prompts/Groups/FilterPrompts'; import DashBreadcrumb from '~/routes/Layouts/DashBreadcrumb'; -import { usePromptGroupsNav, useHasAccess } from '~/hooks'; import GroupSidePanel from './Groups/GroupSidePanel'; +import { usePromptGroupsContext } from '~/Providers'; +import { useHasAccess } from '~/hooks'; import { cn } from '~/utils'; export default function PromptsView() { const params = useParams(); const navigate = useNavigate(); - const groupsNav = usePromptGroupsNav(); + const groupsNav = usePromptGroupsContext(); const isDetailView = useMemo(() => !!(params.promptId || params['*'] === 'new'), [params]); const hasAccess = useHasAccess({ permissionType: PermissionTypes.PROMPTS, diff --git a/client/src/routes/Root.tsx b/client/src/routes/Root.tsx index 8a34eb944b..ab84537522 100644 --- a/client/src/routes/Root.tsx +++ b/client/src/routes/Root.tsx @@ -9,6 +9,7 @@ import { useFileMap, } from '~/hooks'; import { + PromptGroupsProvider, AssistantsMapContext, AgentsMapContext, SetConvoProvider, @@ -68,16 +69,18 @@ export default function Root() { - -
-
-