| |
| import { ArrowUpDown, Database } from 'lucide-react'; |
| import { FileSources, FileContext } from 'librechat-data-provider'; |
| import { |
| Button, |
| Checkbox, |
| OpenAIMinimalIcon, |
| AzureMinimalIcon, |
| useMediaQuery, |
| } from '@librechat/client'; |
| import type { ColumnDef } from '@tanstack/react-table'; |
| import type { TFile } from 'librechat-data-provider'; |
| import ImagePreview from '~/components/Chat/Input/Files/ImagePreview'; |
| import FilePreview from '~/components/Chat/Input/Files/FilePreview'; |
| import { TranslationKeys, useLocalize } from '~/hooks'; |
| import { SortFilterHeader } from './SortFilterHeader'; |
| import { formatDate, getFileType } from '~/utils'; |
|
|
| const contextMap: Record<any, TranslationKeys> = { |
| [FileContext.avatar]: 'com_ui_avatar', |
| [FileContext.unknown]: 'com_ui_unknown', |
| [FileContext.assistants]: 'com_ui_assistants', |
| [FileContext.image_generation]: 'com_ui_image_gen', |
| [FileContext.assistants_output]: 'com_ui_assistants_output', |
| [FileContext.message_attachment]: 'com_ui_attachment', |
| }; |
|
|
| export const columns: ColumnDef<TFile>[] = [ |
| { |
| id: 'select', |
| header: ({ table }) => { |
| return ( |
| <Checkbox |
| checked={ |
| table.getIsAllPageRowsSelected() || |
| (table.getIsSomePageRowsSelected() && 'indeterminate') |
| } |
| onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} |
| aria-label="Select all" |
| className="flex" |
| /> |
| ); |
| }, |
| cell: ({ row }) => { |
| return ( |
| <Checkbox |
| checked={row.getIsSelected()} |
| onCheckedChange={(value) => row.toggleSelected(!!value)} |
| aria-label="Select row" |
| className="flex" |
| /> |
| ); |
| }, |
| enableSorting: false, |
| enableHiding: false, |
| }, |
| { |
| meta: { |
| size: '150px', |
| }, |
| accessorKey: 'filename', |
| header: ({ column }) => { |
| const localize = useLocalize(); |
| return ( |
| <Button |
| variant="ghost" |
| className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm" |
| onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')} |
| > |
| {localize('com_ui_name')} |
| <ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" /> |
| </Button> |
| ); |
| }, |
| cell: ({ row }) => { |
| const file = row.original; |
| if (file.type?.startsWith('image')) { |
| return ( |
| <div className="flex gap-2"> |
| <ImagePreview |
| url={file.filepath} |
| className="relative h-10 w-10 shrink-0 overflow-hidden rounded-md" |
| source={file.source} |
| /> |
| <span className="self-center truncate">{file.filename}</span> |
| </div> |
| ); |
| } |
|
|
| const fileType = getFileType(file.type); |
| return ( |
| <div className="flex gap-2"> |
| {fileType && <FilePreview fileType={fileType} className="relative" file={file} />} |
| <span className="self-center truncate">{file.filename}</span> |
| </div> |
| ); |
| }, |
| }, |
| { |
| accessorKey: 'updatedAt', |
| header: ({ column }) => { |
| const localize = useLocalize(); |
| return ( |
| <Button |
| variant="ghost" |
| onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')} |
| className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm" |
| > |
| {localize('com_ui_date')} |
| <ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" /> |
| </Button> |
| ); |
| }, |
| cell: ({ row }) => { |
| const isSmallScreen = useMediaQuery('(max-width: 768px)'); |
| return formatDate(row.original.updatedAt?.toString() ?? '', isSmallScreen); |
| }, |
| }, |
| { |
| accessorKey: 'filterSource', |
| header: ({ column }) => { |
| const localize = useLocalize(); |
| return ( |
| <SortFilterHeader |
| column={column} |
| title={localize('com_ui_storage')} |
| filters={{ |
| Storage: Object.values(FileSources).filter( |
| (value) => |
| value === FileSources.local || |
| value === FileSources.openai || |
| value === FileSources.azure, |
| ), |
| }} |
| valueMap={{ |
| [FileSources.azure]: 'com_ui_azure', |
| [FileSources.openai]: 'com_ui_openai', |
| [FileSources.local]: 'com_ui_host', |
| }} |
| /> |
| ); |
| }, |
| cell: ({ row }) => { |
| const localize = useLocalize(); |
| const { source } = row.original; |
| if (source === FileSources.openai) { |
| return ( |
| <div className="flex flex-wrap items-center gap-2"> |
| <OpenAIMinimalIcon className="icon-sm text-green-600/50" /> |
| {'OpenAI'} |
| </div> |
| ); |
| } else if (source === FileSources.azure) { |
| return ( |
| <div className="flex flex-wrap items-center gap-2"> |
| <AzureMinimalIcon className="icon-sm text-cyan-700" /> |
| {'Azure'} |
| </div> |
| ); |
| } |
| return ( |
| <div className="flex flex-wrap items-center gap-2"> |
| <Database className="icon-sm text-cyan-700" /> |
| {localize('com_ui_host')} |
| </div> |
| ); |
| }, |
| }, |
| { |
| accessorKey: 'context', |
| header: ({ column }) => { |
| const localize = useLocalize(); |
| return ( |
| <SortFilterHeader |
| column={column} |
| title={localize('com_ui_context')} |
| filters={{ |
| Context: Object.values(FileContext).filter( |
| (value) => value === FileContext[value ?? ''], |
| ), |
| }} |
| valueMap={contextMap} |
| /> |
| ); |
| }, |
| cell: ({ row }) => { |
| const { context } = row.original; |
| const localize = useLocalize(); |
| return ( |
| <div className="flex flex-wrap items-center gap-2"> |
| {localize(contextMap[context ?? FileContext.unknown])} |
| </div> |
| ); |
| }, |
| }, |
| { |
| accessorKey: 'bytes', |
| header: ({ column }) => { |
| const localize = useLocalize(); |
| return ( |
| <Button |
| variant="ghost" |
| className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm" |
| onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')} |
| > |
| {localize('com_ui_size')} |
| <ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" /> |
| </Button> |
| ); |
| }, |
| cell: ({ row }) => { |
| const suffix = ' MB'; |
| const value = Number((Number(row.original.bytes) / 1024 / 1024).toFixed(2)); |
| if (value < 0.01) { |
| return '< 0.01 MB'; |
| } |
|
|
| return `${value}${suffix}`; |
| }, |
| }, |
| ]; |
|
|