🗨️ refactor: Optimize Prompt Queries

feat: Refactor prompt and prompt group schemas; move types to separate file

feat: Implement paginated access to prompt groups with filtering and public visibility

refactor: Add PromptGroups context provider and integrate it into relevant components

refactor: Optimize filter change handling and query invalidation in usePromptGroupsNav hook

refactor: Simplify context usage in FilterPrompts and GroupSidePanel components
This commit is contained in:
Danny Avila 2025-08-11 22:06:15 -04:00
parent 53c31b85d0
commit dcd96c29c5
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
22 changed files with 583 additions and 259 deletions

View file

@ -1,25 +1,21 @@
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import { useRecoilState } from 'recoil';
import { ListFilter, User, Share2 } from 'lucide-react';
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 } from '~/hooks';
import { usePromptGroupsContext } from '~/Providers';
import { cn } from '~/utils';
import store from '~/store';
export default function FilterPrompts({
setName,
className = '',
}: Pick<ReturnType<typeof usePromptGroupsNav>, 'setName'> & {
className?: string;
}) {
export default function FilterPrompts({ className = '' }: { className?: string }) {
const localize = useLocalize();
const [displayName, setDisplayName] = useState('');
const setCategory = useSetRecoilState(store.promptsCategory);
const categoryFilter = useRecoilValue(store.promptsCategory);
const { setName } = usePromptGroupsContext();
const { categories } = useCategories('h-4 w-4');
const [displayName, setDisplayName] = useState('');
const [isSearching, setIsSearching] = useState(false);
const [categoryFilter, setCategory] = useRecoilState(store.promptsCategory);
const filterOptions = useMemo(() => {
const baseOptions: Option[] = [

View file

@ -3,30 +3,31 @@ import { useLocation } from 'react-router-dom';
import { useMediaQuery } from '@librechat/client';
import PanelNavigation from '~/components/Prompts/Groups/PanelNavigation';
import ManagePrompts from '~/components/Prompts/ManagePrompts';
import { usePromptGroupsContext } from '~/Providers';
import List from '~/components/Prompts/Groups/List';
import { usePromptGroupsNav } from '~/hooks';
import { cn } from '~/utils';
export default function GroupSidePanel({
children,
isDetailView,
className = '',
/* usePromptGroupsNav */
nextPage,
prevPage,
isFetching,
hasNextPage,
groupsQuery,
promptGroups,
hasPreviousPage,
}: {
children?: React.ReactNode;
isDetailView?: boolean;
className?: string;
} & ReturnType<typeof usePromptGroupsNav>) {
}) {
const location = useLocation();
const isSmallerScreen = useMediaQuery('(max-width: 1024px)');
const isChatRoute = useMemo(() => location.pathname?.startsWith('/c/'), [location.pathname]);
const {
nextPage,
prevPage,
isFetching,
hasNextPage,
groupsQuery,
promptGroups,
hasPreviousPage,
} = usePromptGroupsContext();
return (
<div

View file

@ -3,10 +3,15 @@ import React from 'react';
import debounce from 'lodash/debounce';
import { useRecoilValue } from 'recoil';
import { Menu, Rocket } from 'lucide-react';
import { useParams } from 'react-router-dom';
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,
@ -15,8 +20,9 @@ import {
useUpdatePromptGroup,
useMakePromptProduction,
} from '~/data-provider';
import { useResourcePermissions, usePromptGroupsNav, useHasAccess, useLocalize } from '~/hooks';
import { useResourcePermissions, useHasAccess, useLocalize } from '~/hooks';
import CategorySelector from './Groups/CategorySelector';
import { usePromptGroupsContext } from '~/Providers';
import NoPromptGroup from './Groups/NoPromptGroup';
import PromptVariables from './PromptVariables';
import { cn, findPromptGroup } from '~/utils';
@ -173,16 +179,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 || '',
);
@ -206,7 +210,7 @@ const PromptForm = () => {
const selectedPromptId = useMemo(() => selectedPrompt?._id, [selectedPrompt?._id]);
const { groupsQuery } = useOutletContext<ReturnType<typeof usePromptGroupsNav>>();
const { groupsQuery } = usePromptGroupsContext();
const updateGroupMutation = useUpdatePromptGroup({
onError: () => {

View file

@ -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}>

View file

@ -3,14 +3,14 @@ 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 { PromptGroupsProvider } from '~/Providers';
import { useHasAccess } from '~/hooks';
import { cn } from '~/utils';
export default function PromptsView() {
const params = useParams();
const navigate = useNavigate();
const groupsNav = usePromptGroupsNav();
const isDetailView = useMemo(() => !!(params.promptId || params['*'] === 'new'), [params]);
const hasAccess = useHasAccess({
permissionType: PermissionTypes.PROMPTS,
@ -34,23 +34,25 @@ export default function PromptsView() {
}
return (
<div className="flex h-screen w-full flex-col bg-surface-primary p-0 lg:p-2">
<DashBreadcrumb />
<div className="flex w-full flex-grow flex-row divide-x overflow-hidden dark:divide-gray-600">
<GroupSidePanel isDetailView={isDetailView} {...groupsNav}>
<div className="mx-2 mt-1 flex flex-row items-center justify-between">
<FilterPrompts setName={groupsNav.setName} />
<PromptGroupsProvider>
<div className="flex h-screen w-full flex-col bg-surface-primary p-0 lg:p-2">
<DashBreadcrumb />
<div className="flex w-full flex-grow flex-row divide-x overflow-hidden dark:divide-gray-600">
<GroupSidePanel isDetailView={isDetailView}>
<div className="mx-2 mt-1 flex flex-row items-center justify-between">
<FilterPrompts />
</div>
</GroupSidePanel>
<div
className={cn(
'scrollbar-gutter-stable w-full overflow-y-auto lg:w-3/4 xl:w-3/4',
isDetailView ? 'block' : 'hidden md:block',
)}
>
<Outlet />
</div>
</GroupSidePanel>
<div
className={cn(
'scrollbar-gutter-stable w-full overflow-y-auto lg:w-3/4 xl:w-3/4',
isDetailView ? 'block' : 'hidden md:block',
)}
>
<Outlet context={groupsNav} />
</div>
</div>
</div>
</PromptGroupsProvider>
);
}