🗨️ 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

@ -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);

View file

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

View file

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