mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-26 21:28:50 +01:00
🔒 feat: View/Delete Shared Agent Files (#8419)
* 🔧 fix: Add localized message for delete operation not allowed
* refactor: improve file deletion operations ux
* feat: agent-based file access control and enhance file retrieval logic
* feat: implement agent-specific file retrieval
* feat: enhance agent file retrieval logic for authors and shared access
* ci: include userId and agentId in mockGetFiles call for OCR file retrieval
This commit is contained in:
parent
6aa4bb5a4a
commit
f1b29ffb45
22 changed files with 1216 additions and 35 deletions
|
|
@ -2,6 +2,8 @@ import { useEffect } from 'react';
|
|||
import { EToolResources } from 'librechat-data-provider';
|
||||
import type { ExtendedFile } from '~/common';
|
||||
import { useDeleteFilesMutation } from '~/data-provider';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { useFileDeletion } from '~/hooks/Files';
|
||||
import FileContainer from './FileContainer';
|
||||
import { logger } from '~/utils';
|
||||
|
|
@ -30,6 +32,8 @@ export default function FileRow({
|
|||
isRTL?: boolean;
|
||||
Wrapper?: React.FC<{ children: React.ReactNode }>;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
const files = Array.from(_files?.values() ?? []).filter((file) =>
|
||||
fileFilter ? fileFilter(file) : true,
|
||||
);
|
||||
|
|
@ -105,6 +109,10 @@ export default function FileRow({
|
|||
)
|
||||
.uniqueFiles.map((file: ExtendedFile, index: number) => {
|
||||
const handleDelete = () => {
|
||||
showToast({
|
||||
message: localize('com_ui_deleting_file'),
|
||||
status: 'info',
|
||||
});
|
||||
if (abortUpload && file.progress < 1) {
|
||||
abortUpload();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { useToastContext, useFileMapContext, useAgentPanelContext } from '~/Prov
|
|||
import useAgentCapabilities from '~/hooks/Agents/useAgentCapabilities';
|
||||
import Action from '~/components/SidePanel/Builder/Action';
|
||||
import { ToolSelectDialog } from '~/components/Tools';
|
||||
import { useGetAgentFiles } from '~/data-provider';
|
||||
import { icons } from '~/hooks/Endpoint/Icons';
|
||||
import { processAgentOption } from '~/utils';
|
||||
import Instructions from './Instructions';
|
||||
|
|
@ -49,6 +50,18 @@ export default function AgentConfig({ createMutation }: Pick<AgentPanelProps, 'c
|
|||
const tools = useWatch({ control, name: 'tools' });
|
||||
const agent_id = useWatch({ control, name: 'id' });
|
||||
|
||||
const { data: agentFiles = [] } = useGetAgentFiles(agent_id);
|
||||
|
||||
const mergedFileMap = useMemo(() => {
|
||||
const newFileMap = { ...fileMap };
|
||||
agentFiles.forEach((file) => {
|
||||
if (file.file_id) {
|
||||
newFileMap[file.file_id] = file;
|
||||
}
|
||||
});
|
||||
return newFileMap;
|
||||
}, [fileMap, agentFiles]);
|
||||
|
||||
const {
|
||||
ocrEnabled,
|
||||
codeEnabled,
|
||||
|
|
@ -74,10 +87,10 @@ export default function AgentConfig({ createMutation }: Pick<AgentPanelProps, 'c
|
|||
|
||||
const _agent = processAgentOption({
|
||||
agent,
|
||||
fileMap,
|
||||
fileMap: mergedFileMap,
|
||||
});
|
||||
return _agent.context_files ?? [];
|
||||
}, [agent, agent_id, fileMap]);
|
||||
}, [agent, agent_id, mergedFileMap]);
|
||||
|
||||
const knowledge_files = useMemo(() => {
|
||||
if (typeof agent === 'string') {
|
||||
|
|
@ -94,10 +107,10 @@ export default function AgentConfig({ createMutation }: Pick<AgentPanelProps, 'c
|
|||
|
||||
const _agent = processAgentOption({
|
||||
agent,
|
||||
fileMap,
|
||||
fileMap: mergedFileMap,
|
||||
});
|
||||
return _agent.knowledge_files ?? [];
|
||||
}, [agent, agent_id, fileMap]);
|
||||
}, [agent, agent_id, mergedFileMap]);
|
||||
|
||||
const code_files = useMemo(() => {
|
||||
if (typeof agent === 'string') {
|
||||
|
|
@ -114,10 +127,10 @@ export default function AgentConfig({ createMutation }: Pick<AgentPanelProps, 'c
|
|||
|
||||
const _agent = processAgentOption({
|
||||
agent,
|
||||
fileMap,
|
||||
fileMap: mergedFileMap,
|
||||
});
|
||||
return _agent.code_files ?? [];
|
||||
}, [agent, agent_id, fileMap]);
|
||||
}, [agent, agent_id, mergedFileMap]);
|
||||
|
||||
const handleAddActions = useCallback(() => {
|
||||
if (!agent_id) {
|
||||
|
|
|
|||
|
|
@ -135,10 +135,9 @@ export default function ShareAgent({
|
|||
'btn btn-neutral border-token-border-light relative h-9 rounded-lg font-medium',
|
||||
removeFocusOutlines,
|
||||
)}
|
||||
aria-label={localize(
|
||||
'com_ui_share_var',
|
||||
{ 0: agentName != null && agentName !== '' ? `"${agentName}"` : localize('com_ui_agent') },
|
||||
)}
|
||||
aria-label={localize('com_ui_share_var', {
|
||||
0: agentName != null && agentName !== '' ? `"${agentName}"` : localize('com_ui_agent'),
|
||||
})}
|
||||
type="button"
|
||||
>
|
||||
<div className="flex items-center justify-center gap-2 text-blue-500">
|
||||
|
|
@ -148,10 +147,9 @@ export default function ShareAgent({
|
|||
</OGDialogTrigger>
|
||||
<OGDialogContent className="w-11/12 md:max-w-xl">
|
||||
<OGDialogTitle>
|
||||
{localize(
|
||||
'com_ui_share_var',
|
||||
{ 0: agentName != null && agentName !== '' ? `"${agentName}"` : localize('com_ui_agent') },
|
||||
)}
|
||||
{localize('com_ui_share_var', {
|
||||
0: agentName != null && agentName !== '' ? `"${agentName}"` : localize('com_ui_agent'),
|
||||
})}
|
||||
</OGDialogTitle>
|
||||
<form
|
||||
className="p-2"
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
export * from './queries';
|
||||
export * from './mutations';
|
||||
export * from './queries';
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import {
|
|||
} from 'librechat-data-provider';
|
||||
import type * as t from 'librechat-data-provider';
|
||||
import type { UseMutationResult } from '@tanstack/react-query';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export const useUploadFileMutation = (
|
||||
_options?: t.UploadMutationOptions,
|
||||
|
|
@ -145,10 +147,24 @@ export const useDeleteFilesMutation = (
|
|||
unknown // context
|
||||
> => {
|
||||
const queryClient = useQueryClient();
|
||||
const { onSuccess, ...options } = _options || {};
|
||||
const { showToast } = useToastContext();
|
||||
const localize = useLocalize();
|
||||
const { onSuccess, onError, ...options } = _options || {};
|
||||
return useMutation([MutationKeys.fileDelete], {
|
||||
mutationFn: (body: t.DeleteFilesBody) => dataService.deleteFiles(body),
|
||||
...options,
|
||||
onError: (error, vars, context) => {
|
||||
if (error && typeof error === 'object' && 'response' in error) {
|
||||
const errorWithResponse = error as { response?: { status?: number } };
|
||||
if (errorWithResponse.response?.status === 403) {
|
||||
showToast({
|
||||
message: localize('com_ui_delete_not_allowed'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
onError?.(error, vars, context);
|
||||
},
|
||||
onSuccess: (data, vars, context) => {
|
||||
queryClient.setQueryData<t.TFile[] | undefined>([QueryKeys.files], (cachefiles) => {
|
||||
const { files: filesDeleted } = vars;
|
||||
|
|
@ -160,6 +176,12 @@ export const useDeleteFilesMutation = (
|
|||
|
||||
return (cachefiles ?? []).filter((file) => !fileMap.has(file.file_id));
|
||||
});
|
||||
|
||||
showToast({
|
||||
message: localize('com_ui_delete_success'),
|
||||
status: 'success',
|
||||
});
|
||||
|
||||
onSuccess?.(data, vars, context);
|
||||
if (vars.agent_id != null && vars.agent_id) {
|
||||
queryClient.refetchQueries([QueryKeys.agent, vars.agent_id]);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useRecoilValue } from 'recoil';
|
||||
import { QueryKeys, dataService } from 'librechat-data-provider';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { QueryKeys, DynamicQueryKeys, dataService } from 'librechat-data-provider';
|
||||
import type { QueryObserverResult, UseQueryOptions } from '@tanstack/react-query';
|
||||
import type t from 'librechat-data-provider';
|
||||
import { addFileToCache } from '~/utils';
|
||||
|
|
@ -19,6 +19,24 @@ export const useGetFiles = <TData = t.TFile[] | boolean>(
|
|||
});
|
||||
};
|
||||
|
||||
export const useGetAgentFiles = <TData = t.TFile[]>(
|
||||
agentId: string | undefined,
|
||||
config?: UseQueryOptions<t.TFile[], unknown, TData>,
|
||||
): QueryObserverResult<TData, unknown> => {
|
||||
const queriesEnabled = useRecoilValue<boolean>(store.queriesEnabled);
|
||||
return useQuery<t.TFile[], unknown, TData>(
|
||||
DynamicQueryKeys.agentFiles(agentId ?? ''),
|
||||
() => (agentId ? dataService.getAgentFiles(agentId) : Promise.resolve([])),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
...config,
|
||||
enabled: (config?.enabled ?? true) === true && queriesEnabled && !!agentId,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const useGetFileConfig = <TData = t.FileConfig>(
|
||||
config?: UseQueryOptions<t.FileConfig, unknown, TData>,
|
||||
): QueryObserverResult<TData, unknown> => {
|
||||
|
|
|
|||
|
|
@ -703,8 +703,11 @@
|
|||
"com_ui_delete_mcp_error": "Failed to delete MCP server",
|
||||
"com_ui_delete_mcp_success": "MCP server deleted successfully",
|
||||
"com_ui_delete_memory": "Delete Memory",
|
||||
"com_ui_delete_not_allowed": "Delete operation is not allowed",
|
||||
"com_ui_delete_prompt": "Delete Prompt?",
|
||||
"com_ui_delete_success": "Successfully deleted",
|
||||
"com_ui_delete_shared_link": "Delete shared link?",
|
||||
"com_ui_deleting_file": "Deleting file...",
|
||||
"com_ui_delete_tool": "Delete Tool",
|
||||
"com_ui_delete_tool_confirm": "Are you sure you want to delete this tool?",
|
||||
"com_ui_deleted": "Deleted",
|
||||
|
|
@ -1085,4 +1088,4 @@
|
|||
"com_ui_yes": "Yes",
|
||||
"com_ui_zoom": "Zoom",
|
||||
"com_user_message": "You"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export function mapAttachments(attachments: Array<t.TAttachment | null | undefin
|
|||
attachmentMap[key] = [];
|
||||
}
|
||||
|
||||
attachmentMap[key].push(attachment);
|
||||
attachmentMap[key]?.push(attachment);
|
||||
}
|
||||
|
||||
return attachmentMap;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue