Spaces:
Running
Running
| // frontend/src/pages/Dashboard.jsx | |
| import React, { useState } from "react"; | |
| import { motion } from "framer-motion"; | |
| import { Sparkles, Zap, FileText, TrendingUp, Clock, AlertCircle } from "lucide-react"; | |
| import { Button } from "@/components/ui/button"; | |
| import UploadZone from "@/components/ocr/UploadZone"; | |
| import DocumentPreview from "@/components/ocr/DocumentPreview"; | |
| import ExtractionOutput from "@/components/ocr/ExtractionOutput"; | |
| import ExportButtons from "@/components/ExportButtons"; | |
| import ProcessingStatus from "@/components/ocr/ProcessingStatus"; | |
| import { extractDocument } from "@/services/api"; | |
| export default function Dashboard() { | |
| const [selectedFile, setSelectedFile] = useState(null); | |
| const [isProcessing, setIsProcessing] = useState(false); | |
| const [isComplete, setIsComplete] = useState(false); | |
| const [extractionResult, setExtractionResult] = useState(null); | |
| const [error, setError] = useState(null); | |
| const handleFileSelect = (file) => { | |
| setSelectedFile(file); | |
| setIsComplete(false); | |
| setExtractionResult(null); | |
| setError(null); | |
| }; | |
| const handleClear = () => { | |
| setSelectedFile(null); | |
| setIsProcessing(false); | |
| setIsComplete(false); | |
| setExtractionResult(null); | |
| setError(null); | |
| }; | |
| const handleExtract = async () => { | |
| if (!selectedFile) return; | |
| setIsProcessing(true); | |
| setIsComplete(false); | |
| setError(null); | |
| setExtractionResult(null); | |
| try { | |
| const result = await extractDocument(selectedFile); | |
| setExtractionResult(result); | |
| setIsComplete(true); | |
| } catch (err) { | |
| console.error("Extraction error:", err); | |
| setError(err.message || "Failed to extract document. Please try again."); | |
| setIsComplete(false); | |
| } finally { | |
| setIsProcessing(false); | |
| } | |
| }; | |
| return ( | |
| <div className="min-h-screen bg-[#FAFAFA]"> | |
| {/* Header */} | |
| <header className="bg-white border-b border-slate-200/80 sticky top-0 z-40"> | |
| <div className="px-8 py-4 flex items-center justify-between"> | |
| <div> | |
| <h1 className="text-xl font-bold text-slate-900 tracking-tight"> | |
| Document Extraction | |
| </h1> | |
| <p className="text-sm text-slate-500 mt-0.5"> | |
| Upload any document and extract structured data with AI | |
| </p> | |
| </div> | |
| <div className="flex items-center gap-3"> | |
| {/* Stats Pills */} | |
| <div className="hidden lg:flex items-center gap-2"> | |
| <div className="flex items-center gap-2 px-3 py-1.5 bg-slate-100 rounded-lg"> | |
| <FileText className="h-4 w-4 text-slate-500" /> | |
| <span className="text-sm font-medium text-slate-700"> | |
| 247 Extracted | |
| </span> | |
| </div> | |
| <div className="flex items-center gap-2 px-3 py-1.5 bg-emerald-50 rounded-lg"> | |
| <TrendingUp className="h-4 w-4 text-emerald-600" /> | |
| <span className="text-sm font-medium text-emerald-700"> | |
| 98.5% Accuracy | |
| </span> | |
| </div> | |
| </div> | |
| <ExportButtons isComplete={isComplete} extractionResult={extractionResult} /> | |
| </div> | |
| </div> | |
| </header> | |
| {/* Main Content */} | |
| <div className="p-8"> | |
| {/* Upload Section */} | |
| <motion.div | |
| initial={{ opacity: 0, y: 20 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| className="max-w-3xl mx-auto mb-4" | |
| > | |
| <UploadZone | |
| onFileSelect={handleFileSelect} | |
| selectedFile={selectedFile} | |
| onClear={handleClear} | |
| /> | |
| {/* Extract Button */} | |
| {selectedFile && !isProcessing && !isComplete && ( | |
| <motion.div | |
| initial={{ opacity: 0, y: 10 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| className="mt-4 flex justify-center" | |
| > | |
| <Button | |
| onClick={handleExtract} | |
| size="lg" | |
| className="h-14 px-8 rounded-2xl font-semibold text-base bg-gradient-to-r from-indigo-600 to-violet-600 hover:from-indigo-700 hover:to-violet-700 shadow-xl shadow-indigo-500/25 hover:shadow-2xl hover:shadow-indigo-500/30 transition-all duration-300 hover:-translate-y-0.5" | |
| > | |
| <Sparkles className="h-5 w-5 mr-2" /> | |
| Start Extraction | |
| <Zap className="h-4 w-4 ml-2 opacity-70" /> | |
| </Button> | |
| </motion.div> | |
| )} | |
| </motion.div> | |
| {/* Error Message */} | |
| {error && ( | |
| <motion.div | |
| initial={{ opacity: 0, y: -10 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| className="max-w-3xl mx-auto mb-6" | |
| > | |
| <div className="bg-red-50 border border-red-200 rounded-2xl p-4 flex items-start gap-3"> | |
| <AlertCircle className="h-5 w-5 text-red-600 flex-shrink-0 mt-0.5" /> | |
| <div className="flex-1"> | |
| <h3 className="font-semibold text-red-900 mb-1">Extraction Failed</h3> | |
| <p className="text-sm text-red-700">{error}</p> | |
| </div> | |
| <button | |
| onClick={() => setError(null)} | |
| className="text-red-400 hover:text-red-600 transition-colors" | |
| > | |
| × | |
| </button> | |
| </div> | |
| </motion.div> | |
| )} | |
| {/* Processing Status */} | |
| {(isProcessing || isComplete) && ( | |
| <div className="max-w-3xl mx-auto mb-4"> | |
| <ProcessingStatus | |
| isProcessing={isProcessing} | |
| isComplete={isComplete} | |
| /> | |
| </div> | |
| )} | |
| {/* Split View */} | |
| {selectedFile && ( | |
| <motion.div | |
| initial={{ opacity: 0, y: 20 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| transition={{ delay: 0.2 }} | |
| className="grid grid-cols-1 lg:grid-cols-2 gap-4" | |
| style={{ height: "calc(100vh - 320px)", minHeight: "450px" }} | |
| > | |
| <DocumentPreview file={selectedFile} isProcessing={isProcessing} /> | |
| <ExtractionOutput | |
| hasFile={!!selectedFile} | |
| isProcessing={isProcessing} | |
| isComplete={isComplete} | |
| extractionResult={extractionResult} | |
| /> | |
| </motion.div> | |
| )} | |
| {/* Empty State Features */} | |
| {!selectedFile && ( | |
| <motion.div | |
| initial={{ opacity: 0 }} | |
| animate={{ opacity: 1 }} | |
| transition={{ delay: 0.3 }} | |
| className="max-w-5xl mx-auto mt-12" | |
| > | |
| <div className="text-center mb-10"> | |
| <h2 className="text-2xl font-bold text-slate-900 mb-2"> | |
| Powered by Advanced AI | |
| </h2> | |
| <p className="text-slate-500"> | |
| Extract structured data from any document | |
| </p> | |
| </div> | |
| <div className="grid grid-cols-1 md:grid-cols-3 gap-6"> | |
| {[ | |
| { | |
| icon: Zap, | |
| title: "Lightning Fast", | |
| description: | |
| "Process documents faster with our optimized AI pipeline", | |
| color: "amber", | |
| }, | |
| { | |
| icon: Sparkles, | |
| title: "98.5% Accuracy", | |
| description: | |
| "Industry-leading extraction accuracy", | |
| color: "indigo", | |
| }, | |
| { | |
| icon: Clock, | |
| title: "Any Format", | |
| description: | |
| "Support for PDF, images, spreadsheets, and scanned documents", | |
| color: "emerald", | |
| }, | |
| ].map((feature, index) => ( | |
| <motion.div | |
| key={feature.title} | |
| initial={{ opacity: 0, y: 20 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| transition={{ delay: 0.4 + index * 0.1 }} | |
| className="group bg-white rounded-2xl border border-slate-200 p-6 hover:shadow-xl hover:shadow-slate-200/50 transition-all duration-300 hover:-translate-y-1" | |
| > | |
| <div | |
| className={`h-12 w-12 rounded-xl bg-${feature.color}-50 flex items-center justify-center mb-4 group-hover:scale-110 transition-transform duration-300`} | |
| > | |
| <feature.icon | |
| className={`h-6 w-6 text-${feature.color}-600`} | |
| /> | |
| </div> | |
| <h3 className="font-semibold text-slate-900 mb-2"> | |
| {feature.title} | |
| </h3> | |
| <p className="text-sm text-slate-500 leading-relaxed"> | |
| {feature.description} | |
| </p> | |
| </motion.div> | |
| ))} | |
| </div> | |
| {/* Supported Formats */} | |
| <div className="mt-12 text-center"> | |
| <p className="text-xs text-slate-400 uppercase tracking-wider mb-4 font-medium"> | |
| Supported Formats | |
| </p> | |
| <div className="flex items-center justify-center gap-6 flex-wrap"> | |
| {["PDF", "PNG", "JPG", "TIFF", "DOCX", "XLSX"].map((format) => ( | |
| <div | |
| key={format} | |
| className="flex items-center gap-2 text-slate-400" | |
| > | |
| <FileText className="h-4 w-4" /> | |
| <span className="text-sm font-medium">{format}</span> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| </motion.div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |