UAIDE / src /components /UploadZone.jsx
ATS-27's picture
Upload folder using huggingface_hub
af980d7 verified
Raw
History Blame Contribute Delete
7.76 kB
import { useRef, useState, useCallback } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Upload, FileImage, FileVideo, AlertCircle, Sparkles } from 'lucide-react';
import CameraCapture from './CameraCapture';
import styles from './UploadZone.module.css';
const MAX_SIZE_MB = 200;
const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/tiff', 'image/avif'];
const ACCEPTED_VIDEO_TYPES = ['video/mp4', 'video/webm', 'video/quicktime', 'video/x-msvideo', 'video/avi'];
export default function UploadZone({ onFileSelect, error }) {
const inputRef = useRef(null);
const [isDragging, setIsDragging] = useState(false);
const [dragType, setDragType] = useState(null);
const [validationError, setValidationError] = useState(null);
const validateFile = useCallback((file) => {
if (!file) return 'No file selected';
const isImage = ACCEPTED_IMAGE_TYPES.includes(file.type) || file.type.startsWith('image/');
const isVideo = ACCEPTED_VIDEO_TYPES.includes(file.type) || file.type.startsWith('video/');
if (!isImage && !isVideo) return 'Unsupported file format. Please use an image or video file.';
if (file.size > MAX_SIZE_MB * 1024 * 1024) return `File is too large. Maximum size is ${MAX_SIZE_MB} MB.`;
return null;
}, []);
const handleDragOver = useCallback((event) => {
event.preventDefault();
const file = event.dataTransfer?.items?.[0];
if (!file) return;
setIsDragging(true);
if (file.type.startsWith('image/')) setDragType('image');
else if (file.type.startsWith('video/')) setDragType('video');
else setDragType('invalid');
}, []);
const handleDragLeave = useCallback((event) => {
event.preventDefault();
setIsDragging(false);
setDragType(null);
}, []);
const handleDrop = useCallback((event) => {
event.preventDefault();
setIsDragging(false);
setDragType(null);
const file = event.dataTransfer?.files?.[0];
if (!file) return;
const validation = validateFile(file);
if (validation) {
setValidationError(validation);
return;
}
setValidationError(null);
onFileSelect(file);
}, [validateFile, onFileSelect]);
const handleInputChange = useCallback((event) => {
const file = event.target.files?.[0];
if (!file) return;
const validation = validateFile(file);
if (validation) {
setValidationError(validation);
return;
}
setValidationError(null);
onFileSelect(file);
}, [validateFile, onFileSelect]);
const handleClick = () => {
setValidationError(null);
inputRef.current?.click();
};
return (
<div className={styles.wrapper}>
<motion.div
className={styles.hero}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<div className={styles.heroBadge}>
<Sparkles size={13} />
<span>Forensic-Grade AI Detection 路 Explainable Results</span>
</div>
<h1 className={styles.heroTitle}>
Is this media <span className={styles.heroAccent}>AI-generated?</span>
</h1>
<p className={styles.heroSub}>
Upload an image or video for multi-model forensic analysis. UAIDE detects deepfakes,
GAN artifacts, and AI-generated content using Grad-CAM heatmaps and frequency analysis.
</p>
</motion.div>
<motion.div
className={`${styles.dropzone} ${isDragging ? styles.dragging : ''} ${dragType === 'invalid' ? styles.invalid : ''}`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={handleClick}
role="button"
tabIndex={0}
aria-label="Upload media file for analysis"
onKeyDown={(event) => event.key === 'Enter' || event.key === ' ' ? handleClick() : null}
initial={{ opacity: 0, scale: 0.97 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.4, delay: 0.15 }}
whileHover={{ scale: 1.005 }}
>
<input
ref={inputRef}
type="file"
accept="image/*,video/*"
onChange={handleInputChange}
className={styles.hiddenInput}
aria-hidden="true"
/>
<AnimatePresence mode="wait">
{isDragging ? (
<motion.div
key="dragging"
className={styles.dropContent}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
transition={{ duration: 0.15 }}
>
{dragType === 'invalid' ? (
<>
<div className={`${styles.iconRing} ${styles.iconRingError}`}>
<AlertCircle size={32} />
</div>
<p className={styles.dropLabel} style={{ color: 'var(--ai-generated)' }}>Unsupported format</p>
<p className={styles.dropHint}>Drop images or video files only</p>
</>
) : (
<>
<div className={`${styles.iconRing} ${styles.iconRingActive}`}>
{dragType === 'video' ? <FileVideo size={32} /> : <FileImage size={32} />}
</div>
<p className={styles.dropLabel}>Release to analyse</p>
<p className={styles.dropHint}>{dragType === 'video' ? 'Video file detected' : 'Image file detected'}</p>
</>
)}
</motion.div>
) : (
<motion.div key="idle" className={styles.dropContent} initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}>
<div className={styles.iconGroup}>
<div className={styles.iconRing}>
<Upload size={28} />
</div>
</div>
<p className={styles.dropLabel}>Drag & drop your media here</p>
<p className={styles.dropHint}>or <span className={styles.browseLink}>browse files</span></p>
<div className={styles.supportedTypes}>
<div className={styles.typeChip}>
<FileImage size={12} />
<span>Images</span>
<span className={styles.typeFormats}>JPEG 路 PNG 路 WebP 路 GIF 路 TIFF 路 AVIF</span>
</div>
<div className={styles.typeDivider} />
<div className={styles.typeChip}>
<FileVideo size={12} />
<span>Videos</span>
<span className={styles.typeFormats}>MP4 路 WebM 路 MOV 路 AVI</span>
</div>
</div>
<p className={styles.sizeNote}>Max file size: {MAX_SIZE_MB} MB</p>
</motion.div>
)}
</AnimatePresence>
</motion.div>
<CameraCapture onCapture={onFileSelect} />
<AnimatePresence>
{(validationError || error) && (
<motion.div className={styles.errorBanner} initial={{ opacity: 0, y: -8 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0 }}>
<AlertCircle size={15} />
<span>{validationError || error}</span>
</motion.div>
)}
</AnimatePresence>
<motion.div className={styles.capabilities} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.35 }}>
{['GAN Detection', 'Diffusion Model ID', 'Grad-CAM Heatmap', 'FFT Analysis', 'Temporal Analysis', 'Deepfake Detection', 'Live Capture'].map((cap) => (
<span key={cap} className={styles.capPill}>{cap}</span>
))}
</motion.div>
</div>
);
}