mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-03 00:58:50 +01:00
refactor: Add PromptGroups context provider and integrate it into relevant components
This commit is contained in:
parent
a928115a84
commit
6137a089b3
11 changed files with 106 additions and 156 deletions
76
client/src/Providers/PromptGroupsContext.tsx
Normal file
76
client/src/Providers/PromptGroupsContext.tsx
Normal file
|
|
@ -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<string, TPromptGroup>;
|
||||
promptGroups: PromptOption[];
|
||||
}
|
||||
| undefined;
|
||||
|
||||
type PromptGroupsContextType =
|
||||
| (ReturnType<typeof usePromptGroupsNav> & {
|
||||
allPromptGroups: {
|
||||
data: AllPromptGroupsData;
|
||||
isLoading: boolean;
|
||||
};
|
||||
})
|
||||
| null;
|
||||
|
||||
const PromptGroupsContext = createContext<PromptGroupsContextType>(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: <CategoryIcon category={group.category ?? ''} className="h-5 w-5" />,
|
||||
}));
|
||||
|
||||
const promptsMap = mapPromptGroups(data);
|
||||
|
||||
return {
|
||||
promptsMap,
|
||||
promptGroups: mappedArray,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
...promptGroupsNav,
|
||||
allPromptGroups: {
|
||||
data: allGroupsData,
|
||||
isLoading: isLoadingAll,
|
||||
},
|
||||
}),
|
||||
[promptGroupsNav, allGroupsData, isLoadingAll],
|
||||
);
|
||||
|
||||
return (
|
||||
<PromptGroupsContext.Provider value={contextValue}>{children}</PromptGroupsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const usePromptGroupsContext = () => {
|
||||
const context = useContext(PromptGroupsContext);
|
||||
if (!context) {
|
||||
throw new Error('usePromptGroupsContext must be used within a PromptGroupsProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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: <CategoryIcon category={group.category ?? ''} className="h-5 w-5" />,
|
||||
}));
|
||||
|
||||
const promptsMap = mapPromptGroups(data);
|
||||
|
||||
return {
|
||||
promptsMap,
|
||||
promptGroups: mappedArray,
|
||||
};
|
||||
},
|
||||
});
|
||||
const { allPromptGroups } = usePromptGroupsContext();
|
||||
const { data, isLoading } = allPromptGroups;
|
||||
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(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 (
|
||||
<div className="hover:bg-token-main-surface-secondary relative flex w-40 cursor-pointer flex-col gap-2 rounded-2xl border px-3 pb-4 pt-3 text-start align-top text-[15px] shadow-[0_0_2px_0_rgba(0,0,0,0.05),0_4px_6px_0_rgba(0,0,0,0.02)] transition-colors duration-300 ease-in-out fade-in hover:bg-slate-100 dark:border-gray-600 dark:hover:bg-gray-700">
|
||||
<div className="">
|
||||
<CategoryIcon className="size-4" category={promptGroup?.category ?? ''} />
|
||||
</div>
|
||||
<p className="break-word line-clamp-3 text-balance text-gray-600 dark:text-gray-400">
|
||||
{(promptGroup?.oneliner ?? '') || promptGroup?.productionPrompt?.prompt}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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) => <PromptCard key={promptGroup._id} promptGroup={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 (
|
||||
<div className="mx-3 flex h-full max-w-3xl flex-col items-stretch justify-center gap-4">
|
||||
<div className="mt-2 flex justify-end gap-2">
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
onClick={() => setPageSize(4)}
|
||||
className={`rounded px-3 py-2 hover:bg-transparent ${
|
||||
pageSize === 4 ? 'text-white' : 'text-gray-500 dark:text-gray-500'
|
||||
}`}
|
||||
>
|
||||
4
|
||||
</Button>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
onClick={() => setPageSize(8)}
|
||||
className={`rounded px-3 py-2 hover:bg-transparent ${
|
||||
pageSize === 8 ? 'text-white' : 'text-gray-500 dark:text-gray-500'
|
||||
}`}
|
||||
>
|
||||
8
|
||||
</Button>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
onClick={() => setPageSize(12)}
|
||||
className={`rounded p-2 hover:bg-transparent ${
|
||||
pageSize === 12 ? 'text-white' : 'text-gray-500 dark:text-gray-500'
|
||||
}`}
|
||||
>
|
||||
12
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex h-full flex-col items-start gap-2">
|
||||
<div
|
||||
className={
|
||||
'flex min-h-[121.1px] min-w-full max-w-3xl flex-col gap-4 overflow-y-auto md:min-w-[22rem] lg:min-w-[43rem]'
|
||||
}
|
||||
>
|
||||
{rows.map((rowSize, index) => (
|
||||
<div key={index} className="flex flex-wrap justify-center gap-4">
|
||||
{renderPromptCards(rowSize * index, rowSize)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex w-full justify-between">
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
onClick={prevPage}
|
||||
disabled={!hasPreviousPage}
|
||||
className="m-0 self-start p-0 hover:bg-transparent"
|
||||
aria-label="previous"
|
||||
>
|
||||
<ChevronLeft className={`${hasPreviousPage ? '' : 'text-gray-500'}`} />
|
||||
</Button>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
onClick={nextPage}
|
||||
disabled={!hasNextPage}
|
||||
className="m-0 self-end p-0 hover:bg-transparent"
|
||||
>
|
||||
<ChevronRight className={`${hasNextPage ? '' : 'text-gray-500'}`} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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 || '',
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
<PromptSidePanel className="mt-2 space-y-2 lg:w-full xl:w-full" {...groupsNav}>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
useFileMap,
|
||||
} from '~/hooks';
|
||||
import {
|
||||
PromptGroupsProvider,
|
||||
AssistantsMapContext,
|
||||
AgentsMapContext,
|
||||
SetConvoProvider,
|
||||
|
|
@ -68,16 +69,18 @@ export default function Root() {
|
|||
<FileMapContext.Provider value={fileMap}>
|
||||
<AssistantsMapContext.Provider value={assistantsMap}>
|
||||
<AgentsMapContext.Provider value={agentsMap}>
|
||||
<Banner onHeightChange={setBannerHeight} />
|
||||
<div className="flex" style={{ height: `calc(100dvh - ${bannerHeight}px)` }}>
|
||||
<div className="relative z-0 flex h-full w-full overflow-hidden">
|
||||
<Nav navVisible={navVisible} setNavVisible={setNavVisible} />
|
||||
<div className="relative flex h-full max-w-full flex-1 flex-col overflow-hidden">
|
||||
<MobileNav setNavVisible={setNavVisible} />
|
||||
<Outlet context={{ navVisible, setNavVisible } satisfies ContextType} />
|
||||
<PromptGroupsProvider>
|
||||
<Banner onHeightChange={setBannerHeight} />
|
||||
<div className="flex" style={{ height: `calc(100dvh - ${bannerHeight}px)` }}>
|
||||
<div className="relative z-0 flex h-full w-full overflow-hidden">
|
||||
<Nav navVisible={navVisible} setNavVisible={setNavVisible} />
|
||||
<div className="relative flex h-full max-w-full flex-1 flex-col overflow-hidden">
|
||||
<MobileNav setNavVisible={setNavVisible} />
|
||||
<Outlet context={{ navVisible, setNavVisible } satisfies ContextType} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PromptGroupsProvider>
|
||||
</AgentsMapContext.Provider>
|
||||
{config?.interface?.termsOfService?.modalAcceptance === true && (
|
||||
<TermsAndConditionsModal
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue