import { useState, useCallback } from "react"; import { useDropzone } from "react-dropzone"; import { motion, AnimatePresence } from "framer-motion"; import { Upload, FileText, X, CheckCircle, AlertCircle, Loader2, Trash2, File, } from "lucide-react"; import { uploadDocument, deleteDocument } from "../services/api"; const ACCEPTED_TYPES = { "application/pdf": [".pdf"], "text/plain": [".txt"], "text/markdown": [".md"], }; const MAX_SIZE = 50 * 1024 * 1024; // 50MB function FileItem({ file, onRemove }) { const statusIcons = { uploading: ( ), processing: , success: , error: , }; const statusColors = { uploading: "text-nexus-accent", processing: "text-amber-400", success: "text-nexus-success", error: "text-nexus-error", }; const statusLabels = { uploading: `Uploading... ${file.progress || 0}%`, processing: "Processing chunks...", success: `${file.chunks || 0} chunks ready`, error: file.error || "Upload failed", }; return ( {/* File icon */}
{/* File info */}

{file.name}

{statusIcons[file.status]} {statusLabels[file.status]}
{/* Progress bar */} {file.status === "uploading" && (
)}
{/* Remove button */} {(file.status === "success" || file.status === "error") && ( )}
); } export default function UploadSection({ sessionId, onUploadComplete }) { const [uploadedFiles, setUploadedFiles] = useState([]); const [isDragActive, setIsDragActive] = useState(false); const updateFile = (name, updates) => { setUploadedFiles((prev) => prev.map((f) => (f.name === name ? { ...f, ...updates } : f)), ); }; const processFile = async (file) => { // Add file with uploading status const fileEntry = { id: Date.now(), name: file.name, size: file.size, status: "uploading", progress: 0, chunks: 0, documentId: null, }; setUploadedFiles((prev) => [fileEntry, ...prev]); try { const result = await uploadDocument(file, sessionId, (progress) => updateFile(file.name, { progress }), ); updateFile(file.name, { status: "success", chunks: result.chunks, documentId: result.document_id, progress: 100, }); onUploadComplete?.(); } catch (err) { updateFile(file.name, { status: "error", error: err.response?.data?.detail || err.message || "Upload failed", }); } }; const onDrop = useCallback( async (acceptedFiles, rejectedFiles) => { if (!sessionId) { alert("Please start a chat session before uploading documents."); return; } // Handle rejected files if (rejectedFiles.length > 0) { rejectedFiles.forEach((rejection) => { const errors = rejection.errors.map((e) => e.message).join(", "); console.warn(`Rejected ${rejection.file.name}: ${errors}`); }); } // Process accepted files sequentially for (const file of acceptedFiles) { await processFile(file); } }, [sessionId], ); const { getRootProps, getInputProps } = useDropzone({ onDrop, onDragEnter: () => setIsDragActive(true), onDragLeave: () => setIsDragActive(false), accept: ACCEPTED_TYPES, maxSize: MAX_SIZE, multiple: true, }); const handleRemove = async (file) => { if (file.documentId) { try { await deleteDocument(file.documentId); } catch (err) { console.error("Failed to delete document:", err); } } setUploadedFiles((prev) => prev.filter((f) => f.name !== file.name)); onUploadComplete?.(); }; return (
{/* Dropzone */}

{isDragActive ? "Drop files here" : "Upload Documents"}

PDF, TXT, MD — up to 50MB

{/* File List */} {uploadedFiles.length > 0 && ( {uploadedFiles.map((file) => ( ))} )}
); }