"use client"; import styles from "./UploadBox.module.css"; import React, { useRef, useState, useEffect, useCallback } from "react"; import Image from "next/image"; export default function UploadBox() { const [dragActive, setDragActive] = useState(false); const [uploading, setUploading] = useState(false); const [result, setResult] = useState(null); const [explanation, setExplanation] = useState(null); const [gradcamImage, setGradcamImage] = useState(null); const [originalImage, setOriginalImage] = useState(null); const [loadingExplanation, setLoadingExplanation] = useState(false); const fileInputRef = useRef(null); // Memory cleanup function const cleanupMemory = useCallback(() => { // Reset file input if (fileInputRef.current) { fileInputRef.current.value = ''; } // Force garbage collection if available (browser dependent) if (window.gc) { window.gc(); } // Clear any cached URLs if (gradcamImage) { // If gradcamImage was a blob URL, revoke it if (gradcamImage.startsWith('blob:')) { URL.revokeObjectURL(gradcamImage); } } if (originalImage) { // If originalImage was a blob URL, revoke it if (originalImage.startsWith('blob:')) { URL.revokeObjectURL(originalImage); } } }, [gradcamImage, originalImage]); // Cleanup effect when component unmounts useEffect(() => { return () => { cleanupMemory(); }; }, [cleanupMemory]); const handleDragOver = e => { e.preventDefault(); setDragActive(true); }; const handleDragLeave = e => { setDragActive(false); }; const handleDrop = e => { e.preventDefault(); setDragActive(false); const files = e.dataTransfer.files; if (files[0]) { uploadFile(files[0]); } }; const handleClick = () => { fileInputRef.current.click(); }; const handleChange = e => { const file = e.target.files[0]; if (file) { uploadFile(file); } }; const uploadFile = async (file) => { // Clean up previous analysis memory cleanupMemory(); setUploading(true); setResult(null); setExplanation(null); setGradcamImage(null); setOriginalImage(null); const formData = new FormData(); formData.append('file', file); try { // Determine endpoint based on file type const isVideo = file.type.startsWith('video/'); const endpoint = isVideo ? 'analyze/video' : 'analyze/image'; // Derive backend base URL with better dev detection: // 1) NEXT_PUBLIC_API_BASE (explicit) // 2) If running in Next dev (localhost:3000) prefer local backend at http://localhost:8000 // 3) Otherwise use same-origin (useful when frontend is served by backend container) // 4) Final fallback to http://localhost:8000 let apiBase = process.env.NEXT_PUBLIC_API_BASE; if (!apiBase) { if (typeof window !== 'undefined' && window.location) { const { hostname, port, origin } = window.location; const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1'; // If running frontend dev (common port 3000) -> use backend local port 8000 if (isLocalhost && port && port !== '' && port !== '8000' && port !== '7860') { apiBase = 'http://localhost:8000'; } else { apiBase = origin; } } else { apiBase = 'http://localhost:8000'; } } const response = await fetch(`${apiBase.replace(/\/$/, '')}/${endpoint}`, { method: 'POST', body: formData, }); if (response.ok) { const data = await response.json(); // Transform the response to match the frontend's expected format const transformedResult = { is_deepfake: data.label === 'fake', confidence: data.label === 'fake' ? data.confidence : (1 - data.confidence), // How confident we are in the prediction fake_probability: data.confidence, // Raw fake probability from model faces_detected: data.faces_detected || 1, processing_time: data.processing_time || 'N/A', models_used: [`${data.details}`], file_type: isVideo ? 'video' : 'image', fake_frames: data.fake_frames || [], // Frames detected as fake gradcam_image: data.gradcam_image || null, // Base64 encoded Grad-CAM image original_image: data.original_image || null // Base64 encoded original image }; setResult(transformedResult); if (data.gradcam_image) { setGradcamImage(data.gradcam_image); } if (data.original_image) { setOriginalImage(data.original_image); } // Get AI explanation after getting the result generateExplanation(transformedResult); } else { const errorData = await response.json(); setResult({ error: errorData.error || 'Detection failed' }); } } catch (error) { console.error('Backend error:', error); setResult({ error: 'Backend connection failed. Ensure API container is up (check port 7860 or set NEXT_PUBLIC_API_BASE).' }); } finally { setUploading(false); // Additional cleanup after upload completes setTimeout(() => { if (window.gc) { window.gc(); } }, 1000); } }; const generateExplanation = async (analysisResult) => { setLoadingExplanation(true); try { const prompt = `Based on this deepfake analysis result, provide a clear explanation of why this ${analysisResult.file_type} was classified as ${analysisResult.is_deepfake ? 'fake' : 'real'}: - Status: ${analysisResult.is_deepfake ? 'DEEPFAKE DETECTED' : 'APPEARS AUTHENTIC'} - Confidence: ${(analysisResult.confidence * 100).toFixed(1)}% - Fake Probability: ${(analysisResult.fake_probability * 100).toFixed(1)}% - File Type: ${analysisResult.file_type} ${analysisResult.fake_frames.length > 0 ? `- Suspicious Frames: ${analysisResult.fake_frames.join(', ')}` : ''} Please explain in simple terms what factors likely contributed to this classification and what users should look for when detecting deepfakes.`; const response = await fetch('https://openrouter.ai/api/v1/chat/completions', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.NEXT_PUBLIC_OPENROUTER_API_KEY}`, 'HTTP-Referer': window.location.origin, 'X-Title': 'UnMask Deepfake Detector', 'Content-Type': 'application/json', }, body: JSON.stringify({ model: 'meta-llama/llama-3.3-70b-instruct:free', messages: [ { role: 'system', content: 'You are an AI expert in deepfake detection. Provide clear, educational explanations about deepfake analysis results in 2-3 sentences.' }, { role: 'user', content: prompt } ], max_tokens: 150, temperature: 0.7, }), }); console.log('OpenRouter response status:', response.status); if (response.ok) { const data = await response.json(); console.log('OpenRouter full response:', JSON.stringify(data, null, 2)); if (data.choices && Array.isArray(data.choices) && data.choices.length > 0) { const firstChoice = data.choices[0]; console.log('First choice:', firstChoice); if (firstChoice.message && firstChoice.message.content) { const content = firstChoice.message.content.trim(); console.log('Extracted content:', content); setExplanation(content); } else { console.error('Missing message content in choice:', firstChoice); setExplanation('AI explanation received but content was empty.'); } } else { console.error('No choices in response:', data); setExplanation('AI explanation service returned no results.'); } } else { const errorData = await response.text(); console.error('OpenRouter error:', response.status, errorData); setExplanation(`Unable to generate AI explanation (Error ${response.status}). Please check your API key.`); } } catch (error) { console.error('Explanation generation error:', error); setExplanation('Unable to generate AI explanation at this time.'); } finally { setLoadingExplanation(false); } }; const clearResults = () => { cleanupMemory(); setResult(null); setExplanation(null); setGradcamImage(null); setOriginalImage(null); }; return (
Analyzing file...
Drag & drop a file here
or click to browse
> )}{result.error}
Status: {result.is_deepfake ? '🚨 DEEPFAKE DETECTED' : '✅ APPEARS AUTHENTIC'}
Confidence: {(result.confidence * 100).toFixed(1)}%
Fake Probability: {(result.fake_probability * 100).toFixed(1)}%
File Type: {result.file_type}
Faces Detected: {result.faces_detected}
Processing Time: {result.processing_time}
Red areas in the Grad-CAM show regions the AI focused on when making its decision.
{line.trim()}
))}AI explanation will appear here after analysis.
)}{line.trim()}
))}AI explanation will appear here after analysis.
)}The following frames were flagged as potentially manipulated: