import { useState, useCallback } from 'react' import { FileRejection } from 'react-dropzone' import Button from '@/components/ui/Button' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/Dialog' import FileUploader from '@/components/ui/FileUploader' import { toast } from 'sonner' import { errorMessage } from '@/lib/utils' import { uploadDocument } from '@/api/lightrag' import { UploadIcon } from 'lucide-react' import { useTranslation } from 'react-i18next' export default function UploadDocumentsDialog() { const { t } = useTranslation() const [open, setOpen] = useState(false) const [isUploading, setIsUploading] = useState(false) const [progresses, setProgresses] = useState>({}) const [fileErrors, setFileErrors] = useState>({}) const handleRejectedFiles = useCallback( (rejectedFiles: FileRejection[]) => { // Process rejected files and add them to fileErrors rejectedFiles.forEach(({ file, errors }) => { // Get the first error message let errorMsg = errors[0]?.message || t('documentPanel.uploadDocuments.fileUploader.fileRejected', { name: file.name }) // Simplify error message for unsupported file types if (errorMsg.includes('file-invalid-type')) { errorMsg = t('documentPanel.uploadDocuments.fileUploader.unsupportedType') } // Set progress to 100% to display error message setProgresses((pre) => ({ ...pre, [file.name]: 100 })) // Add error message to fileErrors setFileErrors(prev => ({ ...prev, [file.name]: errorMsg })) }) }, [setProgresses, setFileErrors, t] ) const handleDocumentsUpload = useCallback( async (filesToUpload: File[]) => { setIsUploading(true) // Only clear errors for files that are being uploaded, keep errors for rejected files setFileErrors(prev => { const newErrors = { ...prev }; filesToUpload.forEach(file => { delete newErrors[file.name]; }); return newErrors; }); // Show uploading toast const toastId = toast.loading(t('documentPanel.uploadDocuments.batch.uploading')) try { // Track errors locally to ensure we have the final state const uploadErrors: Record = {} await Promise.all( filesToUpload.map(async (file) => { try { // Initialize upload progress setProgresses((pre) => ({ ...pre, [file.name]: 0 })) const result = await uploadDocument(file, (percentCompleted: number) => { console.debug(t('documentPanel.uploadDocuments.single.uploading', { name: file.name, percent: percentCompleted })) setProgresses((pre) => ({ ...pre, [file.name]: percentCompleted })) }) if (result.status === 'duplicated') { uploadErrors[file.name] = t('documentPanel.uploadDocuments.fileUploader.duplicateFile') setFileErrors(prev => ({ ...prev, [file.name]: t('documentPanel.uploadDocuments.fileUploader.duplicateFile') })) } else if (result.status !== 'success') { uploadErrors[file.name] = result.message setFileErrors(prev => ({ ...prev, [file.name]: result.message })) } } catch (err) { console.error(`Upload failed for ${file.name}:`, err) // Handle HTTP errors, including 400 errors let errorMsg = errorMessage(err) // If it's an axios error with response data, try to extract more detailed error info if (err && typeof err === 'object' && 'response' in err) { const axiosError = err as { response?: { status: number, data?: { detail?: string } } } if (axiosError.response?.status === 400) { // Extract specific error message from backend response errorMsg = axiosError.response.data?.detail || errorMsg } // Set progress to 100% to display error message setProgresses((pre) => ({ ...pre, [file.name]: 100 })) } // Record error message in both local tracking and state uploadErrors[file.name] = errorMsg setFileErrors(prev => ({ ...prev, [file.name]: errorMsg })) } }) ) // Check if any files failed to upload using our local tracking const hasErrors = Object.keys(uploadErrors).length > 0 // Update toast status if (hasErrors) { toast.error(t('documentPanel.uploadDocuments.batch.error'), { id: toastId }) } else { toast.success(t('documentPanel.uploadDocuments.batch.success'), { id: toastId }) } } catch (err) { console.error('Unexpected error during upload:', err) toast.error(t('documentPanel.uploadDocuments.generalError', { error: errorMessage(err) }), { id: toastId }) } finally { setIsUploading(false) } }, [setIsUploading, setProgresses, setFileErrors, t] ) return ( { if (isUploading) { return } if (!open) { setProgresses({}) setFileErrors({}) } setOpen(open) }} > e.preventDefault()}> {t('documentPanel.uploadDocuments.title')} {t('documentPanel.uploadDocuments.description')} ) }