'use client' import { useState, useRef, useMemo, ChangeEvent, useCallback } from 'react'; import { useAllFilesExtensions } from '@/app/hooks/react-query/useAllFilesExtensions'; import { removeUserFile } from '@/app/actions/fileEntity'; import { useTranslations } from 'next-intl'; import { fileUpload, chunkUpload, checkChunkStatus } from '@/app/actions/fileUpload'; import { getFileType } from '@/app/lib/getFileType'; import { searchUserFiles } from '@/app/actions/searchActions'; import { toast } from 'sonner'; interface SelectedFile { file: File; preview: string | undefined; name: string; size: string; } interface FileUploadInitResponse { upload_id: string, file_name: string, total_chunks: number, chunk_size: number, status: string } export default function LocalFileCheck() { const { data: allFilesExtensions } = useAllFilesExtensions(); const fileInputRef = useRef(null); const [error, setError] = useState(null); const [uploadId, setUploadId] = useState(null); const [isFileUploaded, setIsFileUploaded] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [fileId, setFileId] = useState(null); const [searchResult, setSearchResult] = useState(null); const isCancelledRef = useRef(false); const childRef = useRef(null); const t = useTranslations('Global'); const allExtensions = useMemo(() => { if (allFilesExtensions) { return [...allFilesExtensions.images, ...allFilesExtensions.videos, ...allFilesExtensions.audios, ...allFilesExtensions.documents] } else { return [] } }, []); const acceptString = useMemo(() => { if (!allExtensions || !Array.isArray(allExtensions)) { return ''; } return allExtensions.map(e => `.${e}`).join(', '); }, [allExtensions]); const handleButtonClick = (): void => { if (fileInputRef.current) { fileInputRef.current.click(); } }; const handleFileInputChange = (event: ChangeEvent): void => { const file = event.target.files?.[0] || null; handleFileSelect(file); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; const validateFile = (file: File): { isValid: boolean; errorMessage?: string } => { if (!allExtensions.includes(file.type as string)) { const extension = file.name.split('.').pop()?.toLowerCase(); if (!extension || !allExtensions.includes(extension as string)) { return { isValid: false, errorMessage: t('unsupported-file-format') }; } } const MAX_SIZE = allFilesExtensions?.maxFileSize ? allFilesExtensions?.maxFileSize : 0; if (file.size > MAX_SIZE) { return { isValid: false, errorMessage: t('the-file-is-too-large') }; } return { isValid: true }; }; const handleFileSelect = useCallback(async (file: File | null): Promise => { setSearchResult(null); if (!file) { setError(null); return; } if (fileId) { await removeUserFile(fileId, 1); setFileId(null); } setError(null); setIsFileUploaded(false); if (childRef.current) { //@ts-ignore childRef.current.clearList(); } setUploadProgress(0); const validation = validateFile(file); if (!validation.isValid) { setError(validation.errorMessage || t('unknown-validation-error')); return; } handlerFileUpload({ file, name: file.name, size: `${(file.size / 1024 / 1024).toFixed(2)} MB`, preview: file.size < 10 * 1024 * 1024 ? URL.createObjectURL(file) : undefined }); }, [t]); const handlerFileUpload = useCallback(async (fileInfo: SelectedFile): Promise => { if (uploadId) return; setError(null); isCancelledRef.current = false; const file = fileInfo.file; const fileType = getFileType(fileInfo.file.name, allFilesExtensions); try { const extension = file.name.split('.').pop() || ''; const initMessageBody = { file_name: file.name, file_type: fileType, extension: extension, file_size: file.size }; const response = await fileUpload(initMessageBody) as FileUploadInitResponse; if (!response?.upload_id) { throw new Error('Failed to get upload_id'); } const CHUNK_SIZE = response.chunk_size; const totalChunks = response.total_chunks; setUploadId(response.upload_id); for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) { if (isCancelledRef.current) { return; } const isLastChunk = chunkIndex === totalChunks - 1; const start = chunkIndex * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, file.size); const chunk = file.slice(start, end); const formData = new FormData(); formData.append('upload_id', response.upload_id); formData.append('chunk_number', chunkIndex.toString()); formData.append('chunk', chunk); formData.append('findSimilar', '1'); const chunkResponse = await chunkUpload(formData); if (chunkResponse.errorMesage !== null) { throw chunkResponse.errorMesage; } setUploadProgress(Math.floor((chunkIndex + 1) / totalChunks * 100)); if (isLastChunk && chunkResponse.fileId) { setFileId(chunkResponse.fileId); document.cookie = `searchedFileId=${chunkResponse.fileId}` handlerSearchUserFile(chunkResponse.fileId, fileInfo.name); } } const chunkStatus = await checkChunkStatus(response.upload_id); if (chunkStatus.message_body.missing_chunks === 0) { setIsFileUploaded(true); } else { throw new Error('Not all chunks were uploaded'); } } catch (error) { if (!isCancelledRef.current) { setError(t('error-uploading-file')); console.error('Upload error:', error); } } finally { setUploadId(null); } }, [uploadId, allFilesExtensions]); const handlerSearchUserFile = useCallback(async (fileId: string, fileName: string): Promise => { try { let result = await searchUserFiles(fileId); if (result.errorMessage) { throw result.errorMessage } if (result?.content?.length) { result.content.some((e: any) => { if (e.status === 'PROTECTED') { toast.success(t('file-file-name-already-exists', { fileName: fileName })); setSearchResult(t('file-file-name-already-exists', { fileName: fileName })); return true; } return false; }); } else { toast.warning(t('file-file-name-does-not-exist', { fileName: fileName })); setSearchResult(t('file-file-name-does-not-exist', { fileName: fileName })); } } catch (error) { if (error === 'file-for-search-unsupported') { toast.error(t(error)); } else { toast.error(t('search-error')); } } }, [fileId]) return (
{ setSearchResult(null); setIsFileUploaded(false); }} > {searchResult}
) }