diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsx index 8631c7e..efea74b 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsx @@ -1,12 +1,17 @@ "use client"; import { AnimatePresence, motion } from "framer-motion"; -import { Calendar, CheckCircle2, FileType, Tag, Upload, X } from "lucide-react"; +import { CheckCircle2, FileType, Info, Tag, Upload, X } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; -import { useCallback, useRef, useState } from "react"; +import { useCallback, useState } from "react"; import { useDropzone } from "react-dropzone"; import { toast } from "sonner"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Progress } from "@/components/ui/progress"; +import { Separator } from "@/components/ui/separator"; // Grid pattern component inspired by Aceternity UI function GridPattern() { @@ -34,14 +39,13 @@ function GridPattern() { } export default function FileUploader() { - // Use the useParams hook to get the params const params = useParams(); const search_space_id = params.search_space_id as string; const [files, setFiles] = useState([]); const [isUploading, setIsUploading] = useState(false); + const [uploadProgress, setUploadProgress] = useState(0); const router = useRouter(); - const fileInputRef = useRef(null); // Audio files are always supported (using whisper) const audioFileTypes = { @@ -204,7 +208,6 @@ export default function FileUploader() { }; const acceptedFileTypes = getAcceptedFileTypes(); - const supportedExtensions = Array.from(new Set(Object.values(acceptedFileTypes).flat())).sort(); const onDrop = useCallback((acceptedFiles: File[]) => { @@ -215,12 +218,10 @@ export default function FileUploader() { onDrop, accept: acceptedFileTypes, maxSize: 50 * 1024 * 1024, // 50MB + noClick: false, // Ensure clicking is enabled + noKeyboard: false, // Ensure keyboard navigation is enabled }); - const handleClick = () => { - fileInputRef.current?.click(); - }; - const removeFile = (index: number) => { setFiles((prevFiles) => prevFiles.filter((_, i) => i !== index)); }; @@ -235,6 +236,7 @@ export default function FileUploader() { const handleUpload = async () => { setIsUploading(true); + setUploadProgress(0); const formData = new FormData(); files.forEach((file) => { @@ -244,12 +246,16 @@ export default function FileUploader() { formData.append("search_space_id", search_space_id); try { - // toast("File Upload", { - // description: "Files Uploading Initiated", - // }) + // Simulate progress for better UX + const progressInterval = setInterval(() => { + setUploadProgress((prev) => { + if (prev >= 90) return prev; + return prev + Math.random() * 10; + }); + }, 200); const response = await fetch( - `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL!}/api/v1/documents/fileupload`, + `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/fileupload`, { method: "POST", headers: { @@ -259,6 +265,9 @@ export default function FileUploader() { } ); + clearInterval(progressInterval); + setUploadProgress(100); + if (!response.ok) { throw new Error("Upload failed"); } @@ -272,31 +281,15 @@ export default function FileUploader() { router.push(`/dashboard/${search_space_id}/documents`); } catch (error: any) { setIsUploading(false); + setUploadProgress(0); toast("Upload Error", { description: `Error uploading files: ${error.message}`, }); } }; - const mainVariant = { - initial: { - x: 0, - y: 0, - }, - animate: { - x: 20, - y: -20, - opacity: 0.9, - }, - }; - - const secondaryVariant = { - initial: { - opacity: 0, - }, - animate: { - opacity: 1, - }, + const getTotalFileSize = () => { + return files.reduce((total, file) => total + file.size, 0); }; const containerVariants = { @@ -326,251 +319,252 @@ export default function FileUploader() { return (
- - + {/* Header Card */} + + + + + + Upload Documents + + + Upload your files to make them searchable and accessible through AI-powered + conversations. + + + + + + + Maximum file size: 50MB per file. Supported formats vary based on your ETL service + configuration. + + + + + + + {/* Upload Area Card */} + + {/* Grid background pattern */} -
+
-
- {/* Dropzone area */} -
- - -

- Upload files -

-

- Drag or drop your files here or click to upload -

- -
- {!files.length && ( - - {isDragActive ? ( - - Drop it - - - ) : ( - - )} - - )} - - {!files.length && ( - - )} -
-
-
- - - {/* File list section */} - - {files.length > 0 && ( - +
-
-

Selected Files ({files.length})

-
+
+ + +
-
- - {files.map((file, index) => ( - -
- - {file.name} - + {/* File List Card */} + + {files.length > 0 && ( + + + +
+
+ Selected Files ({files.length}) + + Total size: {formatFileSize(getTotalFileSize())} + +
+ +
+
+ +
+ + {files.map((file, index) => ( + +
+
+ +
+
+

{file.name}

+
+ + {formatFileSize(file.size)} + + + {file.type || "Unknown type"} + +
+
+
- - {formatFileSize(file.size)} -
-
- -
- - - {file.type || "Unknown type"} - - - - - modified {new Date(file.lastModified).toLocaleDateString()} - -
-
- ))} -
-
- - - - -
- )} -
+ ))} + +
- {/* File type information */} - -
-
- -

Supported file types:

-
+ {isUploading && ( + + +
+
+ Uploading files... + {Math.round(uploadProgress)}% +
+ +
+
+ )} + + + + + + + + )} + + + {/* Supported File Types Card */} + + + + + + Supported File Types + + + These file types are supported based on your current ETL service configuration. + + +
{supportedExtensions.map((ext) => ( - + {ext} - + ))}
-
-
+ +