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
}
sx={{
width: '100%',
py: 1.5,
mb: 3,
backgroundColor: theme.palette.primary.light,
'&:hover': {
backgroundColor: theme.palette.primary.main,
}
}}
>
Choose Audio File
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;