import React, { useState, useEffect } from 'react'; import { Container, Box, Button, Typography, CircularProgress, Paper, Grid, Card, CardContent, LinearProgress, FormControl, IconButton, Alert, Snackbar, useMediaQuery } from '@mui/material'; import { createTheme, ThemeProvider, styled, alpha } from '@mui/material/styles'; import MicIcon from '@mui/icons-material/Mic'; import StopIcon from '@mui/icons-material/Stop'; import UploadFileIcon from '@mui/icons-material/UploadFile'; import CloudUploadIcon from '@mui/icons-material/CloudUpload'; import AudiotrackIcon from '@mui/icons-material/Audiotrack'; import VolumeUpIcon from '@mui/icons-material/VolumeUp'; import SecurityIcon from '@mui/icons-material/Security'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; import { motion } from 'framer-motion'; // API endpoint const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000'; // Custom theme const theme = createTheme({ palette: { primary: { main: '#3a86ff', light: '#83b9ff', dark: '#0056cb', }, secondary: { main: '#ff006e', light: '#ff5b9e', dark: '#c50052', }, success: { main: '#38b000', light: '#70e000', dark: '#008000', contrastText: '#ffffff', }, error: { main: '#d00000', light: '#ff5c4d', dark: '#9d0208', contrastText: '#ffffff', }, background: { default: '#f8f9fa', paper: '#ffffff', }, }, typography: { fontFamily: "'Poppins', 'Roboto', 'Helvetica', 'Arial', sans-serif", h3: { fontWeight: 700, letterSpacing: '-0.5px', }, h6: { fontWeight: 600, }, subtitle1: { fontWeight: 500, } }, shape: { borderRadius: 12, }, components: { MuiButton: { styleOverrides: { root: { textTransform: 'none', borderRadius: 8, padding: '10px 16px', boxShadow: 'none', fontWeight: 600, }, containedPrimary: { '&:hover': { boxShadow: '0 6px 20px rgba(58, 134, 255, 0.3)', }, }, }, }, MuiPaper: { styleOverrides: { root: { boxShadow: '0 8px 40px rgba(0, 0, 0, 0.08)', }, }, }, MuiCard: { styleOverrides: { root: { overflow: 'visible', }, }, }, }, }); // Styled components const VisuallyHiddenInput = styled('input')({ clip: 'rect(0 0 0 0)', clipPath: 'inset(50%)', height: 1, overflow: 'hidden', position: 'absolute', bottom: 0, left: 0, whiteSpace: 'nowrap', width: 1, }); const StyledCard = styled(Card)(({ theme }) => ({ height: '100%', display: 'flex', flexDirection: 'column', transition: 'transform 0.3s ease-in-out, box-shadow 0.3s ease-in-out', '&:hover': { transform: 'translateY(-5px)', boxShadow: '0 12px 50px rgba(0, 0, 0, 0.1)', }, })); const ResultCard = styled(Card)(({ theme, prediction }) => ({ backgroundColor: prediction === 'Real' ? alpha(theme.palette.success.light, 0.3) : prediction === 'Deepfake' ? alpha(theme.palette.error.light, 0.3) : theme.palette.grey[100], borderLeft: `8px solid ${ prediction === 'Real' ? theme.palette.success.main : prediction === 'Deepfake' ? theme.palette.error.main : theme.palette.grey[300] }`, backdropFilter: 'blur(10px)', transition: 'all 0.3s ease', })); const GradientHeader = styled(Box)(({ theme }) => ({ background: `linear-gradient(135deg, ${theme.palette.primary.dark} 0%, ${theme.palette.primary.main} 100%)`, color: '#ffffff', padding: theme.spacing(6, 2, 8), borderRadius: '0 0 24px 24px', marginBottom: -theme.spacing(6), })); const GlassCard = styled(Card)(({ theme }) => ({ backgroundColor: alpha(theme.palette.background.paper, 0.8), backdropFilter: 'blur(10px)', border: `1px solid ${alpha('#fff', 0.2)}`, })); const RecordButton = styled(Button)(({ theme, isrecording }) => ({ borderRadius: '50%', minWidth: '64px', width: '64px', height: '64px', padding: 0, boxShadow: isrecording === 'true' ? `0 0 0 4px ${alpha(theme.palette.error.main, 0.3)}, 0 0 0 8px ${alpha(theme.palette.error.main, 0.15)}` : `0 0 0 4px ${alpha(theme.palette.primary.main, 0.3)}, 0 0 0 8px ${alpha(theme.palette.primary.main, 0.15)}`, animation: isrecording === 'true' ? 'pulse 1.5s infinite' : 'none', '@keyframes pulse': { '0%': { boxShadow: `0 0 0 0 ${alpha(theme.palette.error.main, 0.7)}` }, '70%': { boxShadow: `0 0 0 15px ${alpha(theme.palette.error.main, 0)}` }, '100%': { boxShadow: `0 0 0 0 ${alpha(theme.palette.error.main, 0)}` } } })); const AudioWaveAnimation = styled(Box)(({ theme, isplaying }) => ({ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '3px', height: '40px', opacity: isplaying === 'true' ? 1 : 0.3, transition: 'opacity 0.3s ease', '& .bar': { width: '3px', backgroundColor: theme.palette.primary.main, borderRadius: '3px', animation: isplaying === 'true' ? 'soundwave 1s infinite' : 'none', }, '@keyframes soundwave': { '0%': { height: '10%' }, '50%': { height: '100%' }, '100%': { height: '10%' } } })); function App() { const [file, setFile] = useState(null); const [audioUrl, setAudioUrl] = useState(null); const [isRecording, setIsRecording] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [recorder, setRecorder] = useState(null); const [isLoading, setIsLoading] = useState(false); const [result, setResult] = useState(null); const [error, setError] = useState(null); const [modelInfo, setModelInfo] = useState(null); const [openSnackbar, setOpenSnackbar] = useState(false); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); // Create audio wave bars for animation const audioBars = Array.from({ length: 10 }, (_, i) => { const randomHeight = Math.floor(Math.random() * 100) + 1; const randomDelay = Math.random(); return ( ); }); // Audio player logic const audioRef = React.useRef(null); const handlePlayPause = () => { if (audioRef.current) { if (isPlaying) { audioRef.current.pause(); } else { audioRef.current.play(); } setIsPlaying(!isPlaying); } }; // Fetch model info on component mount useEffect(() => { fetch(`${API_URL}/model-info/`) .then(response => response.json()) .then(data => setModelInfo(data)) .catch(err => console.error("Error fetching model info:", err)); }, []); // Handle audio events useEffect(() => { const audioElement = audioRef.current; if (audioElement) { const handleEnded = () => setIsPlaying(false); audioElement.addEventListener('ended', handleEnded); return () => { audioElement.removeEventListener('ended', handleEnded); }; } }, [audioUrl]); // Handle file selection const handleFileChange = (event) => { const selectedFile = event.target.files[0]; if (selectedFile) { setFile(selectedFile); setAudioUrl(URL.createObjectURL(selectedFile)); setIsPlaying(false); setResult(null); // Clear previous results } }; // Start audio recording const startRecording = async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const mediaRecorder = new MediaRecorder(stream); const audioChunks = []; mediaRecorder.addEventListener("dataavailable", event => { audioChunks.push(event.data); }); mediaRecorder.addEventListener("stop", () => { const audioBlob = new Blob(audioChunks, { type: 'audio/wav' }); const audioFile = new File([audioBlob], "recorded-audio.wav", { type: 'audio/wav' }); setFile(audioFile); setAudioUrl(URL.createObjectURL(audioBlob)); setIsPlaying(false); setResult(null); // Clear previous results }); mediaRecorder.start(); setIsRecording(true); setRecorder(mediaRecorder); } catch (err) { setError("Could not access microphone. Please check permissions."); setOpenSnackbar(true); console.error("Error accessing microphone:", err); } }; // Stop audio recording const stopRecording = () => { if (recorder && recorder.state !== "inactive") { recorder.stop(); setIsRecording(false); } }; // Submit audio for analysis const handleSubmit = async () => { if (!file) { setError("Please upload or record an audio file first."); setOpenSnackbar(true); return; } setIsLoading(true); setError(null); const formData = new FormData(); formData.append('file', file); try { const response = await fetch(`${API_URL}/detect/`, { method: 'POST', body: formData, }); if (!response.ok) { throw new Error(`Server responded with status: ${response.status}`); } const data = await response.json(); setResult(data); } catch (err) { setError(`Error analyzing audio: ${err.message}`); setOpenSnackbar(true); console.error("Error analyzing audio:", err); } finally { setIsLoading(false); } }; // Reset everything const handleReset = () => { setFile(null); setAudioUrl(null); setResult(null); setError(null); setIsPlaying(false); }; // Format chart data const getChartData = () => { if (!result || !result.probabilities) return []; return Object.entries(result.probabilities).map(([name, value]) => ({ name, value: parseFloat((value * 100).toFixed(2)) })); }; // Handle snackbar close const handleCloseSnackbar = (event, reason) => { if (reason === 'clickaway') { return; } setOpenSnackbar(false); }; // Animation variants const fadeIn = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0, transition: { duration: 0.6 } } }; return ( Deepfake Voice Detector Upload or record audio to instantly verify if it's authentic or AI-generated {modelInfo && ( Using model: {modelInfo.model_id} | Accuracy: {(modelInfo.performance.accuracy * 100).toFixed(2)}% )} Upload Audio Or record audio directly {!isRecording ? ( ) : ( )} {isRecording ? 'Recording...' : 'Tap to record'} {audioUrl ? ( <> Audio Preview ) : ( No audio selected Upload or record to analyze )} {isLoading && ( Analyzing audio... )} {result && ( {result.prediction === 'Real' ? '✓ Authentic Voice' : '⚠ Deepfake Detected'} Confidence: {(result.confidence * 100).toFixed(2)}% {result.prediction === 'Real' ? 'Human Voice' : 'AI-Generated'} Probability Distribution [`${value}%`, 'Probability']} contentStyle={{ borderRadius: 8, border: 'none', boxShadow: '0 4px 20px rgba(0,0,0,0.1)', backgroundColor: alpha('#fff', 0.95) }} /> entry.name === 'Real' ? theme.palette.success.main : theme.palette.error.main} radius={[8, 8, 0, 0]} label={{ position: 'top', formatter: (value) => `${value}%`, fill: theme.palette.text.secondary, fontSize: 12, fontWeight: 600 }} /> Note: This model claims {modelInfo ? (modelInfo.performance.accuracy * 100).toFixed(2) : ''}% accuracy, but results may vary depending on audio quality. )} {error} ); } export default App;