| import { useEffect } from 'react'; |
| import { useToastContext } from '@librechat/client'; |
| import { EToolResources } from 'librechat-data-provider'; |
| import type { ExtendedFile } from '~/common'; |
| import { useDeleteFilesMutation } from '~/data-provider'; |
| import { useFileDeletion } from '~/hooks/Files'; |
| import FileContainer from './FileContainer'; |
| import { useLocalize } from '~/hooks'; |
| import { logger } from '~/utils'; |
| import Image from './Image'; |
|
|
| export default function FileRow({ |
| files: _files, |
| setFiles, |
| abortUpload, |
| setFilesLoading, |
| assistant_id, |
| agent_id, |
| tool_resource, |
| fileFilter, |
| isRTL = false, |
| Wrapper, |
| }: { |
| files: Map<string, ExtendedFile> | undefined; |
| abortUpload?: () => void; |
| setFiles: React.Dispatch<React.SetStateAction<Map<string, ExtendedFile>>>; |
| setFilesLoading: React.Dispatch<React.SetStateAction<boolean>>; |
| fileFilter?: (file: ExtendedFile) => boolean; |
| assistant_id?: string; |
| agent_id?: string; |
| tool_resource?: EToolResources; |
| 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, |
| ); |
|
|
| const { mutateAsync } = useDeleteFilesMutation({ |
| onMutate: async () => |
| logger.log( |
| 'agents', |
| 'Deleting files: agent_id, assistant_id, tool_resource', |
| agent_id, |
| assistant_id, |
| tool_resource, |
| ), |
| onSuccess: () => { |
| console.log('Files deleted'); |
| }, |
| onError: (error) => { |
| console.log('Error deleting files:', error); |
| }, |
| }); |
|
|
| const { deleteFile } = useFileDeletion({ mutateAsync, agent_id, assistant_id, tool_resource }); |
|
|
| useEffect(() => { |
| if (files.length === 0) { |
| setFilesLoading(false); |
| return; |
| } |
|
|
| if (files.some((file) => file.progress < 1)) { |
| setFilesLoading(true); |
| return; |
| } |
|
|
| if (files.every((file) => file.progress === 1)) { |
| setFilesLoading(false); |
| } |
| |
| }, [files]); |
|
|
| if (files.length === 0) { |
| return null; |
| } |
|
|
| const renderFiles = () => { |
| const rowStyle = isRTL |
| ? { |
| display: 'flex', |
| flexDirection: 'row-reverse', |
| flexWrap: 'wrap', |
| gap: '4px', |
| width: '100%', |
| maxWidth: '100%', |
| } |
| : { |
| display: 'flex', |
| flexWrap: 'wrap', |
| gap: '4px', |
| width: '100%', |
| maxWidth: '100%', |
| }; |
|
|
| return ( |
| <div style={rowStyle as React.CSSProperties}> |
| {files |
| .reduce( |
| (acc, current) => { |
| if (!acc.map.has(current.file_id)) { |
| acc.map.set(current.file_id, true); |
| acc.uniqueFiles.push(current); |
| } |
| return acc; |
| }, |
| { map: new Map(), uniqueFiles: [] as ExtendedFile[] }, |
| ) |
| .uniqueFiles.map((file: ExtendedFile, index: number) => { |
| const handleDelete = () => { |
| showToast({ |
| message: localize('com_ui_deleting_file'), |
| status: 'info', |
| }); |
| if (abortUpload && file.progress < 1) { |
| abortUpload(); |
| } |
| deleteFile({ file, setFiles }); |
| }; |
| const isImage = file.type?.startsWith('image') ?? false; |
| |
| return ( |
| <div |
| key={index} |
| style={{ |
| flexBasis: '70px', |
| flexGrow: 0, |
| flexShrink: 0, |
| }} |
| > |
| {isImage ? ( |
| <Image |
| url={file.progress === 1 ? file.filepath : (file.preview ?? file.filepath)} |
| onDelete={handleDelete} |
| progress={file.progress} |
| source={file.source} |
| /> |
| ) : ( |
| <FileContainer file={file} onDelete={handleDelete} /> |
| )} |
| </div> |
| ); |
| })} |
| </div> |
| ); |
| }; |
|
|
| if (Wrapper) { |
| return <Wrapper>{renderFiles()}</Wrapper>; |
| } |
|
|
| return renderFiles(); |
| } |
|
|