Skip to content

Commit eb1668f

Browse files
authored
📂 refactor: Improve FileAttachment & File Form Deletion (#7471)
* refactor: optional attachment properties for `FileAttachment` * refactor: update ActionButton to use localized text * chore: localize text in DataTableFile, add missing translation, imports order, and linting * chore: linting in DataTable * fix: integrate Recoil state management for file deletion in DataTableFile * fix: integrate Recoil state management for file deletion in DataTable * fix: add temp_file_id to BatchFile type and update deleteFiles logic to properly remove files that are mapped to temp_file_id
1 parent e86842f commit eb1668f

File tree

7 files changed

+65
-40
lines changed

7 files changed

+65
-40
lines changed

client/src/components/Chat/Input/Files/Table/DataTable.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState } from 'react';
22
import { ListFilter } from 'lucide-react';
3+
import { useSetRecoilState } from 'recoil';
34
import {
45
flexRender,
56
getCoreRowModel,
@@ -36,6 +37,7 @@ import { TrashIcon, Spinner } from '~/components/svg';
3637
import useLocalize from '~/hooks/useLocalize';
3738
import { useMediaQuery } from '~/hooks';
3839
import { cn } from '~/utils';
40+
import store from '~/store';
3941

4042
interface DataTableProps<TData, TValue> {
4143
columns: ColumnDef<TData, TValue>[];
@@ -60,12 +62,14 @@ type Style = {
6062
export default function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
6163
const localize = useLocalize();
6264
const [isDeleting, setIsDeleting] = useState(false);
65+
const setFiles = useSetRecoilState(store.filesByIndex(0));
66+
const { deleteFiles } = useDeleteFilesFromTable(() => setIsDeleting(false));
67+
6368
const [rowSelection, setRowSelection] = useState({});
6469
const [sorting, setSorting] = useState<SortingState>([]);
6570
const isSmallScreen = useMediaQuery('(max-width: 768px)');
6671
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
6772
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
68-
const { deleteFiles } = useDeleteFilesFromTable(() => setIsDeleting(false));
6973

7074
const table = useReactTable({
7175
data,
@@ -96,7 +100,7 @@ export default function DataTable<TData, TValue>({ columns, data }: DataTablePro
96100
const filesToDelete = table
97101
.getFilteredSelectedRowModel()
98102
.rows.map((row) => row.original);
99-
deleteFiles({ files: filesToDelete as TFile[] });
103+
deleteFiles({ files: filesToDelete as TFile[], setFiles });
100104
setRowSelection({});
101105
}}
102106
disabled={!table.getFilteredSelectedRowModel().rows.length || isDeleting}
@@ -218,13 +222,10 @@ export default function DataTable<TData, TValue>({ columns, data }: DataTablePro
218222
<div className="flex items-center justify-end gap-2 py-4">
219223
<div className="ml-2 flex-1 truncate text-xs text-muted-foreground sm:ml-4 sm:text-sm">
220224
<span className="hidden sm:inline">
221-
{localize(
222-
'com_files_number_selected',
223-
{
224-
0: `${table.getFilteredSelectedRowModel().rows.length}`,
225-
1: `${table.getFilteredRowModel().rows.length}`,
226-
},
227-
)}
225+
{localize('com_files_number_selected', {
226+
0: `${table.getFilteredSelectedRowModel().rows.length}`,
227+
1: `${table.getFilteredRowModel().rows.length}`,
228+
})}
228229
</span>
229230
<span className="sm:hidden">
230231
{`${table.getFilteredSelectedRowModel().rows.length}/${

client/src/components/Chat/Messages/Content/Parts/Attachment.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,22 @@ import Image from '~/components/Chat/Messages/Content/Image';
66
import { useAttachmentLink } from './LogLink';
77
import { cn } from '~/utils';
88

9-
const FileAttachment = memo(({ attachment }: { attachment: TAttachment }) => {
9+
const FileAttachment = memo(({ attachment }: { attachment: Partial<TAttachment> }) => {
1010
const { handleDownload } = useAttachmentLink({
11-
href: attachment.filepath,
12-
filename: attachment.filename,
11+
href: attachment.filepath ?? '',
12+
filename: attachment.filename ?? '',
1313
});
14-
const extension = attachment.filename.split('.').pop();
14+
const extension = attachment.filename?.split('.').pop();
1515
const [isVisible, setIsVisible] = useState(false);
1616

1717
useEffect(() => {
1818
const timer = setTimeout(() => setIsVisible(true), 50);
1919
return () => clearTimeout(timer);
2020
}, []);
2121

22+
if (!attachment.filepath) {
23+
return null;
24+
}
2225
return (
2326
<div
2427
className={cn(
@@ -88,6 +91,8 @@ export default function Attachment({ attachment }: { attachment?: TAttachment })
8891

8992
if (isImage) {
9093
return <ImageAttachment attachment={attachment} />;
94+
} else if (!attachment.filepath) {
95+
return null;
9196
}
9297
return <FileAttachment attachment={attachment} />;
9398
}
@@ -119,9 +124,11 @@ export function AttachmentGroup({ attachments }: { attachments?: TAttachment[] }
119124
<>
120125
{fileAttachments.length > 0 && (
121126
<div className="my-2 flex flex-wrap items-center gap-2.5">
122-
{fileAttachments.map((attachment, index) => (
123-
<FileAttachment attachment={attachment} key={`file-${index}`} />
124-
))}
127+
{fileAttachments.map((attachment, index) =>
128+
attachment.filepath ? (
129+
<FileAttachment attachment={attachment} key={`file-${index}`} />
130+
) : null,
131+
)}
125132
</div>
126133
)}
127134
{imageAttachments.length > 0 && (

client/src/components/Files/ActionButton.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
import React from 'react';
2-
import { CrossIcon } from '~/components/svg';
32
import { Button } from '~/components/ui';
3+
import { useLocalize } from '~/hooks';
44

55
type ActionButtonProps = {
66
onClick: () => void;
77
};
88

99
export default function ActionButton({ onClick }: ActionButtonProps) {
10+
const localize = useLocalize();
1011
return (
1112
<div className="w-32">
1213
<Button
1314
className="w-full rounded-md border border-black bg-white p-0 text-black hover:bg-black hover:text-white"
1415
onClick={onClick}
1516
>
16-
Action Button
17+
{/* Action Button */}
18+
{localize('com_ui_action_button')}
1719
</Button>
1820
</div>
1921
);

client/src/components/Files/FileList/DataTableFile.tsx

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as React from 'react';
22
import { ListFilter } from 'lucide-react';
3+
import { useSetRecoilState } from 'recoil';
34
import {
45
flexRender,
56
getCoreRowModel,
@@ -18,24 +19,25 @@ import { FileContext } from 'librechat-data-provider';
1819
import type { AugmentedColumnDef } from '~/common';
1920
import type { TFile } from 'librechat-data-provider';
2021
import {
21-
Button,
2222
Input,
2323
Table,
24+
Button,
25+
TableRow,
2426
TableBody,
2527
TableCell,
2628
TableHead,
2729
TableHeader,
28-
TableRow,
2930
DropdownMenu,
30-
DropdownMenuCheckboxItem,
3131
DropdownMenuContent,
3232
DropdownMenuTrigger,
33+
DropdownMenuCheckboxItem,
3334
} from '~/components/ui';
35+
import ActionButton from '~/components/Files/ActionButton';
3436
import { useDeleteFilesFromTable } from '~/hooks/Files';
3537
import { TrashIcon, Spinner } from '~/components/svg';
36-
import useLocalize from '~/hooks/useLocalize';
37-
import ActionButton from '../ActionButton';
3838
import UploadFileButton from './UploadFileButton';
39+
import useLocalize from '~/hooks/useLocalize';
40+
import store from '~/store';
3941

4042
interface DataTableProps<TData, TValue> {
4143
columns: ColumnDef<TData, TValue>[];
@@ -57,12 +59,14 @@ export default function DataTableFile<TData, TValue>({
5759
data,
5860
}: DataTableProps<TData, TValue>) {
5961
const localize = useLocalize();
62+
const setFiles = useSetRecoilState(store.filesByIndex(0));
6063
const [isDeleting, setIsDeleting] = React.useState(false);
64+
const { deleteFiles } = useDeleteFilesFromTable(() => setIsDeleting(false));
65+
6166
const [rowSelection, setRowSelection] = React.useState({});
6267
const [sorting, setSorting] = React.useState<SortingState>([]);
6368
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
6469
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
65-
const { deleteFiles } = useDeleteFilesFromTable(() => setIsDeleting(false));
6670

6771
const table = useReactTable({
6872
data,
@@ -87,7 +91,7 @@ export default function DataTableFile<TData, TValue>({
8791
<>
8892
<div className="mt-2 flex flex-col items-start">
8993
<h2 className="text-lg">
90-
<strong>Files</strong>
94+
<strong>{localize('com_ui_files')}</strong>
9195
</h2>
9296
<div className="mt-3 flex w-full flex-col-reverse justify-between md:flex-row">
9397
<div className="mt-3 flex w-full flex-row justify-center gap-x-3 md:m-0 md:justify-start">
@@ -103,7 +107,7 @@ export default function DataTableFile<TData, TValue>({
103107
const filesToDelete = table
104108
.getFilteredSelectedRowModel()
105109
.rows.map((row) => row.original);
106-
deleteFiles({ files: filesToDelete as TFile[] });
110+
deleteFiles({ files: filesToDelete as TFile[], setFiles });
107111
setRowSelection({});
108112
}}
109113
className="ml-1 gap-2 dark:hover:bg-gray-850/25 sm:ml-0"
@@ -242,13 +246,11 @@ export default function DataTableFile<TData, TValue>({
242246
</Table>
243247
</div>
244248
<div className="ml-4 mr-4 mt-4 flex h-auto items-center justify-end space-x-2 py-4 sm:ml-0 sm:mr-0 sm:h-0">
245-
<div className="text-muted-foreground ml-2 flex-1 text-sm">
246-
{localize(
247-
'com_files_number_selected', {
248-
0: `${table.getFilteredSelectedRowModel().rows.length}`,
249-
1: `${table.getFilteredRowModel().rows.length}`,
250-
},
251-
)}
249+
<div className="ml-2 flex-1 text-sm text-muted-foreground">
250+
{localize('com_files_number_selected', {
251+
0: `${table.getFilteredSelectedRowModel().rows.length}`,
252+
1: `${table.getFilteredRowModel().rows.length}`,
253+
})}
252254
</div>
253255
<Button
254256
className="dark:border-gray-500 dark:hover:bg-gray-600"

client/src/hooks/Files/useFileDeletion.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ const useFileDeletion = ({
1919
assistant_id?: string;
2020
tool_resource?: EToolResources;
2121
}) => {
22-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2322
const [_batch, setFileDeleteBatch] = useState<t.BatchFile[]>([]);
2423
const setFilesToDelete = useSetFilesToDelete();
2524

@@ -109,22 +108,33 @@ const useFileDeletion = ({
109108

110109
const deleteFiles = useCallback(
111110
({ files, setFiles }: { files: ExtendedFile[] | t.TFile[]; setFiles?: FileMapSetter }) => {
112-
const batchFiles = files.map((_file) => {
113-
const { file_id, embedded, filepath = '', source = FileSources.local } = _file;
111+
const batchFiles: t.BatchFile[] = [];
112+
for (const _file of files) {
113+
const {
114+
file_id,
115+
embedded,
116+
temp_file_id,
117+
filepath = '',
118+
source = FileSources.local,
119+
} = _file;
114120

115-
return {
121+
batchFiles.push({
116122
source,
117123
file_id,
118124
filepath,
119-
embedded,
120-
};
121-
});
125+
temp_file_id,
126+
embedded: embedded ?? false,
127+
});
128+
}
122129

123130
if (setFiles) {
124131
setFiles((currentFiles) => {
125132
const updatedFiles = new Map(currentFiles);
126133
batchFiles.forEach((file) => {
127134
updatedFiles.delete(file.file_id);
135+
if (file.temp_file_id) {
136+
updatedFiles.delete(file.temp_file_id);
137+
}
128138
});
129139
const filesToUpdate = Object.fromEntries(updatedFiles);
130140
setFilesToDelete(filesToUpdate);

client/src/locales/en/translation.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@
460460
"com_ui_2fa_setup": "Setup 2FA",
461461
"com_ui_2fa_verified": "Successfully verified Two-Factor Authentication",
462462
"com_ui_accept": "I accept",
463+
"com_ui_action_button": "Action Button",
463464
"com_ui_add": "Add",
464465
"com_ui_add_model_preset": "Add a model or preset for an additional response",
465466
"com_ui_add_multi_conversation": "Add multi-conversation",
@@ -641,6 +642,7 @@
641642
"com_ui_expand_chat": "Expand Chat",
642643
"com_ui_export_convo_modal": "Export Conversation Modal",
643644
"com_ui_field_required": "This field is required",
645+
"com_ui_files": "Files",
644646
"com_ui_filter_prompts": "Filter Prompts",
645647
"com_ui_filter_prompts_name": "Filter prompts by name",
646648
"com_ui_finance": "Finance",

packages/data-provider/src/types/files.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ export type BatchFile = {
131131
filepath: string;
132132
embedded: boolean;
133133
source: FileSources;
134+
temp_file_id?: string;
134135
};
135136

136137
export type DeleteFilesBody = {

0 commit comments

Comments
 (0)