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 */}
{/* File List */}
{uploadedFiles.length > 0 && (
{uploadedFiles.map((file) => (
))}
)}
);
}