'use client'; import { useState, useCallback, useId, type ReactNode } from 'react'; import { useDropzone } from 'react-dropzone'; import { UploadIcon } from '@/components/icons/Icons'; import { useDocuments } from '@/contexts/DocumentContext'; import { uploadDocxAsPdf } from '@/lib/client/api/documents'; import { useFeatureFlag } from '@/contexts/RuntimeConfigContext'; import { dropzoneSurfaceClass } from '@/components/ui'; interface DocumentUploaderProps { className?: string; variant?: 'default' | 'compact' | 'overlay'; children?: ReactNode; onUploadBatchChange?: (state: UploadBatchState) => void; } export interface UploadBatchState { uploaderId: string; isActive: boolean; totalFiles: number; completedFiles: number; phase: 'uploading' | 'converting'; currentFileName: string | null; } export function DocumentUploader({ className = '', variant = 'default', children, onUploadBatchChange, }: DocumentUploaderProps) { const uploaderId = useId(); const enableDocx = useFeatureFlag('enableDocxConversion'); const { addPDFDocument: addPDF, addEPUBDocument: addEPUB, addHTMLDocument: addHTML, refreshDocuments, } = useDocuments(); const [isUploading, setIsUploading] = useState(false); const [isConverting, setIsConverting] = useState(false); const [error, setError] = useState(null); const emitBatchState = useCallback((state: Omit) => { onUploadBatchChange?.({ uploaderId, ...state }); }, [onUploadBatchChange, uploaderId]); const onDrop = useCallback(async (acceptedFiles: File[]) => { if (!acceptedFiles || acceptedFiles.length === 0) return; const totalFiles = acceptedFiles.length; let completedFiles = 0; setIsUploading(true); setError(null); emitBatchState({ isActive: true, totalFiles, completedFiles, phase: 'uploading', currentFileName: acceptedFiles[0]?.name ?? null, }); try { for (const file of acceptedFiles) { if (file.type === 'application/pdf') { emitBatchState({ isActive: true, totalFiles, completedFiles, phase: 'uploading', currentFileName: file.name, }); await addPDF(file); completedFiles += 1; } else if (file.type === 'application/epub+zip') { emitBatchState({ isActive: true, totalFiles, completedFiles, phase: 'uploading', currentFileName: file.name, }); await addEPUB(file); completedFiles += 1; } else if (file.type === 'text/plain' || file.type === 'text/markdown' || file.name.endsWith('.md')) { emitBatchState({ isActive: true, totalFiles, completedFiles, phase: 'uploading', currentFileName: file.name, }); await addHTML(file); completedFiles += 1; } else if (enableDocx && file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') { // Preserve prior UX: show "Converting DOCX..." state rather than generic uploading. setIsUploading(false); setIsConverting(true); emitBatchState({ isActive: true, totalFiles, completedFiles, phase: 'converting', currentFileName: file.name, }); // Convert+upload directly on the server. Use sha(docx) as stable ID to avoid duplicates. await uploadDocxAsPdf(file); await refreshDocuments(); setIsConverting(false); setIsUploading(true); completedFiles += 1; } else { continue; } emitBatchState({ isActive: true, totalFiles, completedFiles, phase: 'uploading', currentFileName: null, }); } } catch (err) { setError('Failed to upload file. Please try again.'); console.error('Upload error:', err); } finally { setIsUploading(false); setIsConverting(false); emitBatchState({ isActive: false, totalFiles, completedFiles, phase: 'uploading', currentFileName: null, }); } }, [addHTML, addPDF, addEPUB, refreshDocuments, enableDocx, emitBatchState]); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: { 'application/pdf': ['.pdf'], 'application/epub+zip': ['.epub'], 'text/plain': ['.txt'], 'text/markdown': ['.md'], ...(enableDocx ? { 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'] } : {}) }, multiple: true, disabled: isUploading || isConverting, noClick: variant === 'overlay', noKeyboard: variant === 'overlay' }); const isDisabled = isUploading || isConverting; if (variant === 'overlay') { const rootProps = getRootProps(); return (
{children} {isDragActive && (

Drop files here to upload

{enableDocx ? 'Accepts PDF, EPUB, TXT, MD, or DOCX' : 'Accepts PDF, EPUB, TXT, or MD'}

{error && (

Upload failed: {error} — try again.

)}
)} {!isDragActive && error && (
Upload failed: {error} — try again.
)}
); } return (
{variant === 'compact' ? (
{isUploading ? (

Uploading…

) : isConverting ? (

Converting DOCX…

) : (

{isDragActive ? 'Drop files here' : 'Upload documents'}

{error &&

{error}

}
)}
) : (
{isUploading ? (

Uploading file...

) : isConverting ? (

Converting DOCX to PDF...

) : ( <>

{isDragActive ? 'Drop your file(s) here' : 'Drop your file(s) here, or click to select'}

{enableDocx ? 'PDF, EPUB, TXT, MD, or DOCX files are accepted' : 'PDF, EPUB, TXT, or MD files are accepted'}

{error &&

{error}

} )}
)}
); }