🔒 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:
Danny Avila 2025-07-12 01:52:46 -04:00 committed by GitHub
parent 6aa4bb5a4a
commit f1b29ffb45
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 1216 additions and 35 deletions

View file

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

View file

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

View file

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

View file

@ -1,2 +1,2 @@
export * from './queries';
export * from './mutations';
export * from './queries';

View file

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

View file

@ -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> => {

View file

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

View file

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