mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-21 21:50:49 +02:00
🗨️ refactor: Only Allow Prompt Queries with Access (#9688)
This commit is contained in:
parent
02bfe32905
commit
208be7c06c
8 changed files with 64 additions and 38 deletions
|
@ -1,9 +1,10 @@
|
|||
import React, { createContext, useContext, ReactNode, useMemo } from 'react';
|
||||
import { PermissionTypes, Permissions } from 'librechat-data-provider';
|
||||
import type { TPromptGroup } from 'librechat-data-provider';
|
||||
import type { PromptOption } from '~/common';
|
||||
import CategoryIcon from '~/components/Prompts/Groups/CategoryIcon';
|
||||
import { usePromptGroupsNav, useHasAccess } from '~/hooks';
|
||||
import { useGetAllPromptGroups } from '~/data-provider';
|
||||
import { usePromptGroupsNav } from '~/hooks';
|
||||
import { mapPromptGroups } from '~/utils';
|
||||
|
||||
type AllPromptGroupsData =
|
||||
|
@ -19,14 +20,21 @@ type PromptGroupsContextType =
|
|||
data: AllPromptGroupsData;
|
||||
isLoading: boolean;
|
||||
};
|
||||
hasAccess: boolean;
|
||||
})
|
||||
| null;
|
||||
|
||||
const PromptGroupsContext = createContext<PromptGroupsContextType>(null);
|
||||
|
||||
export const PromptGroupsProvider = ({ children }: { children: ReactNode }) => {
|
||||
const promptGroupsNav = usePromptGroupsNav();
|
||||
const hasAccess = useHasAccess({
|
||||
permissionType: PermissionTypes.PROMPTS,
|
||||
permission: Permissions.USE,
|
||||
});
|
||||
|
||||
const promptGroupsNav = usePromptGroupsNav(hasAccess);
|
||||
const { data: allGroupsData, isLoading: isLoadingAll } = useGetAllPromptGroups(undefined, {
|
||||
enabled: hasAccess,
|
||||
select: (data) => {
|
||||
const mappedArray: PromptOption[] = data.map((group) => ({
|
||||
id: group._id ?? '',
|
||||
|
@ -55,11 +63,12 @@ export const PromptGroupsProvider = ({ children }: { children: ReactNode }) => {
|
|||
() => ({
|
||||
...promptGroupsNav,
|
||||
allPromptGroups: {
|
||||
data: allGroupsData,
|
||||
isLoading: isLoadingAll,
|
||||
data: hasAccess ? allGroupsData : undefined,
|
||||
isLoading: hasAccess ? isLoadingAll : false,
|
||||
},
|
||||
hasAccess,
|
||||
}),
|
||||
[promptGroupsNav, allGroupsData, isLoadingAll],
|
||||
[promptGroupsNav, allGroupsData, isLoadingAll, hasAccess],
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -2,14 +2,13 @@ import { useState, useRef, useEffect, useMemo, memo, useCallback } from 'react';
|
|||
import { AutoSizer, List } from 'react-virtualized';
|
||||
import { Spinner, useCombobox } from '@librechat/client';
|
||||
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, detectVariables } from '~/utils';
|
||||
import VariableDialog from '~/components/Prompts/Groups/VariableDialog';
|
||||
import { usePromptGroupsContext } from '~/Providers';
|
||||
import { useLocalize, useHasAccess } from '~/hooks';
|
||||
import MentionItem from './MentionItem';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
||||
const commandChar = '/';
|
||||
|
@ -54,12 +53,7 @@ function PromptsCommand({
|
|||
submitPrompt: (textPrompt: string) => void;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const hasAccess = useHasAccess({
|
||||
permissionType: PermissionTypes.PROMPTS,
|
||||
permission: Permissions.USE,
|
||||
});
|
||||
|
||||
const { allPromptGroups } = usePromptGroupsContext();
|
||||
const { allPromptGroups, hasAccess } = usePromptGroupsContext();
|
||||
const { data, isLoading } = allPromptGroups;
|
||||
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
|
|
|
@ -6,6 +6,7 @@ import { LocalStorageKeys } from 'librechat-data-provider';
|
|||
import { useFormContext, Controller } from 'react-hook-form';
|
||||
import type { MenuItemProps } from '@librechat/client';
|
||||
import type { ReactNode } from 'react';
|
||||
import { usePromptGroupsContext } from '~/Providers';
|
||||
import { useCategories } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
|
@ -22,8 +23,9 @@ const CategorySelector: React.FC<CategorySelectorProps> = ({
|
|||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const formContext = useFormContext();
|
||||
const { categories, emptyCategory } = useCategories();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { hasAccess } = usePromptGroupsContext();
|
||||
const { categories, emptyCategory } = useCategories({ hasAccess });
|
||||
|
||||
const control = formContext?.control;
|
||||
const watch = formContext?.watch;
|
||||
|
|
|
@ -7,6 +7,7 @@ import CategorySelector from '~/components/Prompts/Groups/CategorySelector';
|
|||
import VariablesDropdown from '~/components/Prompts/VariablesDropdown';
|
||||
import PromptVariables from '~/components/Prompts/PromptVariables';
|
||||
import Description from '~/components/Prompts/Description';
|
||||
import { usePromptGroupsContext } from '~/Providers';
|
||||
import { useLocalize, useHasAccess } from '~/hooks';
|
||||
import Command from '~/components/Prompts/Command';
|
||||
import { useCreatePrompt } from '~/data-provider';
|
||||
|
@ -37,10 +38,12 @@ const CreatePromptForm = ({
|
|||
}) => {
|
||||
const localize = useLocalize();
|
||||
const navigate = useNavigate();
|
||||
const hasAccess = useHasAccess({
|
||||
const { hasAccess: hasUseAccess } = usePromptGroupsContext();
|
||||
const hasCreateAccess = useHasAccess({
|
||||
permissionType: PermissionTypes.PROMPTS,
|
||||
permission: Permissions.CREATE,
|
||||
});
|
||||
const hasAccess = hasUseAccess && hasCreateAccess;
|
||||
|
||||
useEffect(() => {
|
||||
let timeoutId: ReturnType<typeof setTimeout>;
|
||||
|
|
|
@ -11,8 +11,8 @@ import store from '~/store';
|
|||
|
||||
export default function FilterPrompts({ className = '' }: { className?: string }) {
|
||||
const localize = useLocalize();
|
||||
const { name, setName } = usePromptGroupsContext();
|
||||
const { categories } = useCategories('h-4 w-4');
|
||||
const { name, setName, hasAccess } = usePromptGroupsContext();
|
||||
const { categories } = useCategories({ className: 'h-4 w-4', hasAccess });
|
||||
const [displayName, setDisplayName] = useState(name || '');
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [categoryFilter, setCategory] = useRecoilState(store.promptsCategory);
|
||||
|
|
|
@ -167,6 +167,7 @@ const PromptForm = () => {
|
|||
const params = useParams();
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
const { hasAccess } = usePromptGroupsContext();
|
||||
const alwaysMakeProd = useRecoilValue(store.alwaysMakeProd);
|
||||
const promptId = params.promptId || '';
|
||||
|
||||
|
@ -179,10 +180,12 @@ const PromptForm = () => {
|
|||
const [showSidePanel, setShowSidePanel] = useState(false);
|
||||
const sidePanelWidth = '320px';
|
||||
|
||||
const { data: group, isLoading: isLoadingGroup } = useGetPromptGroup(promptId);
|
||||
const { data: group, isLoading: isLoadingGroup } = useGetPromptGroup(promptId, {
|
||||
enabled: hasAccess && !!promptId,
|
||||
});
|
||||
const { data: prompts = [], isLoading: isLoadingPrompts } = useGetPrompts(
|
||||
{ groupId: promptId },
|
||||
{ enabled: !!promptId },
|
||||
{ enabled: hasAccess && !!promptId },
|
||||
);
|
||||
|
||||
const { hasPermission, isLoading: permissionsLoading } = useResourcePermissions(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useGetCategories } from '~/data-provider';
|
||||
import CategoryIcon from '~/components/Prompts/Groups/CategoryIcon';
|
||||
import { useLocalize, TranslationKeys } from '~/hooks';
|
||||
import { useGetCategories } from '~/data-provider';
|
||||
|
||||
const loadingCategories: { label: TranslationKeys; value: string }[] = [
|
||||
{
|
||||
|
@ -14,9 +14,17 @@ const emptyCategory: { label: TranslationKeys; value: string } = {
|
|||
value: '',
|
||||
};
|
||||
|
||||
const useCategories = (className = '') => {
|
||||
const useCategories = ({
|
||||
className = '',
|
||||
hasAccess = true,
|
||||
}: {
|
||||
className?: string;
|
||||
hasAccess?: boolean;
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
|
||||
const { data: categories = loadingCategories } = useGetCategories({
|
||||
enabled: hasAccess,
|
||||
select: (data) =>
|
||||
data.map((category) => ({
|
||||
label: localize(category.label as TranslationKeys),
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useRecoilState } from 'recoil';
|
|||
import { usePromptGroupsInfiniteQuery } from '~/data-provider';
|
||||
import store from '~/store';
|
||||
|
||||
export default function usePromptGroupsNav() {
|
||||
export default function usePromptGroupsNav(hasAccess = true) {
|
||||
const [pageSize] = useRecoilState(store.promptsPageSize);
|
||||
const [category] = useRecoilState(store.promptsCategory);
|
||||
const [name, setName] = useRecoilState(store.promptsName);
|
||||
|
@ -14,21 +14,26 @@ export default function usePromptGroupsNav() {
|
|||
|
||||
const prevFiltersRef = useRef({ name, category });
|
||||
|
||||
const groupsQuery = usePromptGroupsInfiniteQuery({
|
||||
name,
|
||||
pageSize,
|
||||
category,
|
||||
});
|
||||
const groupsQuery = usePromptGroupsInfiniteQuery(
|
||||
{
|
||||
name,
|
||||
pageSize,
|
||||
category,
|
||||
},
|
||||
{
|
||||
enabled: hasAccess,
|
||||
},
|
||||
);
|
||||
|
||||
// Get the current page data
|
||||
const currentPageData = useMemo(() => {
|
||||
if (!groupsQuery.data?.pages || groupsQuery.data.pages.length === 0) {
|
||||
if (!hasAccess || !groupsQuery.data?.pages || groupsQuery.data.pages.length === 0) {
|
||||
return null;
|
||||
}
|
||||
// Ensure we don't go out of bounds
|
||||
const pageIndex = Math.min(currentPageIndex, groupsQuery.data.pages.length - 1);
|
||||
return groupsQuery.data.pages[pageIndex];
|
||||
}, [groupsQuery.data?.pages, currentPageIndex]);
|
||||
}, [hasAccess, groupsQuery.data?.pages, currentPageIndex]);
|
||||
|
||||
// Get prompt groups for current page
|
||||
const promptGroups = useMemo(() => {
|
||||
|
@ -54,7 +59,7 @@ export default function usePromptGroupsNav() {
|
|||
|
||||
// Navigate to next page
|
||||
const nextPage = useCallback(async () => {
|
||||
if (!hasNextPage) return;
|
||||
if (!hasAccess || !hasNextPage) return;
|
||||
|
||||
const nextPageIndex = currentPageIndex + 1;
|
||||
|
||||
|
@ -72,16 +77,18 @@ export default function usePromptGroupsNav() {
|
|||
}
|
||||
|
||||
setCurrentPageIndex(nextPageIndex);
|
||||
}, [currentPageIndex, hasNextPage, groupsQuery]);
|
||||
}, [hasAccess, currentPageIndex, hasNextPage, groupsQuery]);
|
||||
|
||||
// Navigate to previous page
|
||||
const prevPage = useCallback(() => {
|
||||
if (!hasPreviousPage) return;
|
||||
if (!hasAccess || !hasPreviousPage) return;
|
||||
setCurrentPageIndex(currentPageIndex - 1);
|
||||
}, [currentPageIndex, hasPreviousPage]);
|
||||
}, [hasAccess, currentPageIndex, hasPreviousPage]);
|
||||
|
||||
// Reset when filters change
|
||||
useEffect(() => {
|
||||
if (!hasAccess) return;
|
||||
|
||||
const filtersChanged =
|
||||
prevFiltersRef.current.name !== name || prevFiltersRef.current.category !== category;
|
||||
|
||||
|
@ -90,18 +97,18 @@ export default function usePromptGroupsNav() {
|
|||
cursorHistoryRef.current = [null];
|
||||
prevFiltersRef.current = { name, category };
|
||||
}
|
||||
}, [name, category]);
|
||||
}, [hasAccess, name, category]);
|
||||
|
||||
return {
|
||||
promptGroups,
|
||||
promptGroups: hasAccess ? promptGroups : [],
|
||||
groupsQuery,
|
||||
currentPage,
|
||||
totalPages,
|
||||
hasNextPage,
|
||||
hasPreviousPage,
|
||||
hasNextPage: hasAccess && hasNextPage,
|
||||
hasPreviousPage: hasAccess && hasPreviousPage,
|
||||
nextPage,
|
||||
prevPage,
|
||||
isFetching: groupsQuery.isFetching,
|
||||
isFetching: hasAccess ? groupsQuery.isFetching : false,
|
||||
name,
|
||||
setName,
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue