PixelNavigation commited on
Commit
287c8f5
·
1 Parent(s): 48d336a

Improved the frontend to support gradcam

Browse files
frontend/src/Components/UploadBox.js CHANGED
@@ -6,6 +6,9 @@ export default function UploadBox() {
6
  const [dragActive, setDragActive] = useState(false);
7
  const [uploading, setUploading] = useState(false);
8
  const [result, setResult] = useState(null);
 
 
 
9
  const fileInputRef = useRef(null);
10
 
11
  const handleDragOver = e => {
@@ -40,20 +43,45 @@ export default function UploadBox() {
40
  const uploadFile = async (file) => {
41
  setUploading(true);
42
  setResult(null);
 
 
43
 
44
  const formData = new FormData();
45
- formData.append('video', file);
46
 
47
  try {
48
- // Connect to the Flask API running on port 5000
49
- const response = await fetch('http://localhost:5000/api/detect', {
 
 
 
 
50
  method: 'POST',
51
  body: formData,
52
  });
53
 
54
  if (response.ok) {
55
  const data = await response.json();
56
- setResult(data);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  } else {
58
  const errorData = await response.json();
59
  setResult({ error: errorData.error || 'Detection failed' });
@@ -61,13 +89,86 @@ export default function UploadBox() {
61
  } catch (error) {
62
  console.error('Backend error:', error);
63
  setResult({
64
- error: 'Backend connection failed. Please ensure the Flask server is running on port 5000.'
65
  });
66
  } finally {
67
  setUploading(false);
68
  }
69
  };
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  return (
72
  <div className={styles.container}>
73
  <div
@@ -125,30 +226,59 @@ export default function UploadBox() {
125
  </p>
126
  <p><strong>Confidence:</strong> {(result.confidence * 100).toFixed(1)}%</p>
127
  <p><strong>Fake Probability:</strong> {(result.fake_probability * 100).toFixed(1)}%</p>
 
128
  </div>
129
 
130
- {result.faces_detected && (
131
- <div className={styles.details}>
132
- <h4>📊 Analysis Details</h4>
133
- <p><strong>Faces Detected:</strong> {result.faces_detected}</p>
134
- <p><strong>Processing Time:</strong> {result.processing_time}s</p>
135
- {result.models_used && (
136
- <p><strong>Models Used:</strong> {result.models_used.join(', ')}</p>
137
- )}
 
 
 
 
 
 
138
  </div>
139
  )}
140
-
141
- {result.individual_predictions && (
142
- <div className={styles.modelResults}>
143
- <h4>🤖 Individual Model Results</h4>
144
- {Object.entries(result.individual_predictions).map(([model, pred]) => (
145
- <div key={model} className={styles.modelResult}>
146
- <span className={styles.modelName}>{model}:</span>
147
- <span className={pred.is_fake ? styles.fake : styles.real}>
148
- {pred.is_fake ? 'Fake' : 'Real'} ({(pred.confidence * 100).toFixed(1)}%)
149
- </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  </div>
151
- ))}
152
  </div>
153
  )}
154
  </div>
 
6
  const [dragActive, setDragActive] = useState(false);
7
  const [uploading, setUploading] = useState(false);
8
  const [result, setResult] = useState(null);
9
+ const [explanation, setExplanation] = useState(null);
10
+ const [gradcamImage, setGradcamImage] = useState(null);
11
+ const [loadingExplanation, setLoadingExplanation] = useState(false);
12
  const fileInputRef = useRef(null);
13
 
14
  const handleDragOver = e => {
 
43
  const uploadFile = async (file) => {
44
  setUploading(true);
45
  setResult(null);
46
+ setExplanation(null);
47
+ setGradcamImage(null);
48
 
49
  const formData = new FormData();
50
+ formData.append('file', file);
51
 
52
  try {
53
+ // Determine endpoint based on file type
54
+ const isVideo = file.type.startsWith('video/');
55
+ const endpoint = isVideo ? 'analyze/video' : 'analyze/image';
56
+
57
+ // Connect to the Flask API running on port 8000
58
+ const response = await fetch(`http://localhost:8000/${endpoint}`, {
59
  method: 'POST',
60
  body: formData,
61
  });
62
 
63
  if (response.ok) {
64
  const data = await response.json();
65
+ // Transform the response to match the frontend's expected format
66
+ const transformedResult = {
67
+ is_deepfake: data.label === 'fake',
68
+ confidence: data.label === 'fake' ? data.confidence : (1 - data.confidence), // How confident we are in the prediction
69
+ fake_probability: data.confidence, // Raw fake probability from model
70
+ faces_detected: data.faces_detected || 1,
71
+ processing_time: data.processing_time || 'N/A',
72
+ models_used: [`${data.details}`],
73
+ file_type: isVideo ? 'video' : 'image',
74
+ fake_frames: data.fake_frames || [], // Frames detected as fake
75
+ gradcam_image: data.gradcam_image || null // Base64 encoded Grad-CAM image
76
+ };
77
+ setResult(transformedResult);
78
+
79
+ if (data.gradcam_image) {
80
+ setGradcamImage(data.gradcam_image);
81
+ }
82
+
83
+ // Get AI explanation after getting the result
84
+ generateExplanation(transformedResult);
85
  } else {
86
  const errorData = await response.json();
87
  setResult({ error: errorData.error || 'Detection failed' });
 
89
  } catch (error) {
90
  console.error('Backend error:', error);
91
  setResult({
92
+ error: 'Backend connection failed. Please ensure the Flask server is running on port 8000.'
93
  });
94
  } finally {
95
  setUploading(false);
96
  }
97
  };
98
 
99
+ const generateExplanation = async (analysisResult) => {
100
+ setLoadingExplanation(true);
101
+ try {
102
+ 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'}:
103
+
104
+ - Status: ${analysisResult.is_deepfake ? 'DEEPFAKE DETECTED' : 'APPEARS AUTHENTIC'}
105
+ - Confidence: ${(analysisResult.confidence * 100).toFixed(1)}%
106
+ - Fake Probability: ${(analysisResult.fake_probability * 100).toFixed(1)}%
107
+ - File Type: ${analysisResult.file_type}
108
+ ${analysisResult.fake_frames.length > 0 ? `- Suspicious Frames: ${analysisResult.fake_frames.join(', ')}` : ''}
109
+
110
+ Please explain in simple terms what factors likely contributed to this classification and what users should look for when detecting deepfakes.`;
111
+
112
+ const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
113
+ method: 'POST',
114
+ headers: {
115
+ 'Authorization': `Bearer ${process.env.NEXT_PUBLIC_OPENROUTER_API_KEY}`,
116
+ 'HTTP-Referer': window.location.origin,
117
+ 'X-Title': 'UnMask Deepfake Detector',
118
+ 'Content-Type': 'application/json',
119
+ },
120
+ body: JSON.stringify({
121
+ model: 'meta-llama/llama-3.2-3b-instruct:free',
122
+ messages: [
123
+ {
124
+ role: 'system',
125
+ content: 'You are an AI expert in deepfake detection. Provide clear, educational explanations about deepfake analysis results in 2-3 sentences.'
126
+ },
127
+ {
128
+ role: 'user',
129
+ content: prompt
130
+ }
131
+ ],
132
+ max_tokens: 150,
133
+ temperature: 0.7,
134
+ }),
135
+ });
136
+
137
+ console.log('OpenRouter response status:', response.status);
138
+
139
+ if (response.ok) {
140
+ const data = await response.json();
141
+ console.log('OpenRouter full response:', JSON.stringify(data, null, 2));
142
+
143
+ if (data.choices && Array.isArray(data.choices) && data.choices.length > 0) {
144
+ const firstChoice = data.choices[0];
145
+ console.log('First choice:', firstChoice);
146
+
147
+ if (firstChoice.message && firstChoice.message.content) {
148
+ const content = firstChoice.message.content.trim();
149
+ console.log('Extracted content:', content);
150
+ setExplanation(content);
151
+ } else {
152
+ console.error('Missing message content in choice:', firstChoice);
153
+ setExplanation('AI explanation received but content was empty.');
154
+ }
155
+ } else {
156
+ console.error('No choices in response:', data);
157
+ setExplanation('AI explanation service returned no results.');
158
+ }
159
+ } else {
160
+ const errorData = await response.text();
161
+ console.error('OpenRouter error:', response.status, errorData);
162
+ setExplanation(`Unable to generate AI explanation (Error ${response.status}). Please check your API key.`);
163
+ }
164
+ } catch (error) {
165
+ console.error('Explanation generation error:', error);
166
+ setExplanation('Unable to generate AI explanation at this time.');
167
+ } finally {
168
+ setLoadingExplanation(false);
169
+ }
170
+ };
171
+
172
  return (
173
  <div className={styles.container}>
174
  <div
 
226
  </p>
227
  <p><strong>Confidence:</strong> {(result.confidence * 100).toFixed(1)}%</p>
228
  <p><strong>Fake Probability:</strong> {(result.fake_probability * 100).toFixed(1)}%</p>
229
+ <p><strong>File Type:</strong> {result.file_type}</p>
230
  </div>
231
 
232
+
233
+
234
+ {/* Frame Analysis for Videos */}
235
+ {result.file_type === 'video' && result.fake_frames && result.fake_frames.length > 0 && (
236
+ <div className={styles.frameAnalysis}>
237
+ <h4>🎬 Suspicious Frames Detected</h4>
238
+ <p>The following frames were flagged as potentially manipulated:</p>
239
+ <div className={styles.frameList}>
240
+ {result.fake_frames.map((frame, index) => (
241
+ <div key={index} className={styles.frameItem}>
242
+ Frame {frame}
243
+ </div>
244
+ ))}
245
+ </div>
246
  </div>
247
  )}
248
+
249
+ {/* Grad-CAM Visualization with AI Explanation */}
250
+ {gradcamImage && (
251
+ <div className={styles.gradcam}>
252
+ <h4>🔍 Visual Explanation (Grad-CAM)</h4>
253
+ <div style={{ display: 'flex', alignItems: 'flex-start', gap: '32px' }}>
254
+ <div>
255
+ <img
256
+ src={`data:image/png;base64,${gradcamImage}`}
257
+ alt="Grad-CAM visualization"
258
+ className={styles.gradcamImage}
259
+ />
260
+ <p style={{ marginTop: '8px', color: '#92400e', fontWeight: 500 }}>
261
+ <span style={{ fontWeight: 600 }}>Red areas</span> show regions the AI focused on when making its decision.
262
+ </p>
263
+ </div>
264
+ <div style={{ flex: 1 }}>
265
+ <h4 style={{ margin: '0 0 8px 0', color: '#0c4a6e', fontSize: '1.05rem', fontWeight: 600 }}>🤖 AI Explanation</h4>
266
+ {loadingExplanation ? (
267
+ <div className={styles.loadingExplain}>
268
+ <div className={styles.loadingSpinner}></div>
269
+ <span>Generating explanation...</span>
270
+ </div>
271
+ ) : explanation ? (
272
+ <div style={{ background: '#f0f9ff', padding: '12px 16px', borderRadius: '8px', border: '1px solid #0ea5e9', color: '#164e63', fontSize: '0.98rem', fontWeight: 500, lineHeight: 1.7 }}>
273
+ {explanation.split(/\n|\r/).map((line, idx) => (
274
+ <p key={idx} style={{ margin: '8px 0' }}>{line.trim()}</p>
275
+ ))}
276
+ </div>
277
+ ) : (
278
+ <p>AI explanation will appear here after analysis.</p>
279
+ )}
280
  </div>
281
+ </div>
282
  </div>
283
  )}
284
  </div>
frontend/src/Components/UploadBox.module.css CHANGED
@@ -102,73 +102,156 @@
102
  padding: 20px;
103
  border-radius: 8px;
104
  border: 1px solid #e5e7eb;
 
105
  }
106
 
107
  .mainResult p {
108
- margin: 8px 0;
109
  font-size: 1.1rem;
 
 
110
  }
111
 
112
- .details {
113
- background: #f8fafc;
114
- padding: 16px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  border-radius: 8px;
116
- border: 1px solid #e2e8f0;
 
 
117
  }
118
 
119
- .details h4 {
120
  margin: 0 0 12px 0;
121
- color: #374151;
 
 
 
122
  }
123
 
124
- .details p {
125
- margin: 6px 0;
 
 
126
  font-size: 0.95rem;
127
- color: #6b7280;
128
  }
129
 
130
- .modelResults {
131
- background: #fefefe;
132
  padding: 16px;
133
  border-radius: 8px;
134
- border: 1px solid #e5e7eb;
 
135
  }
136
 
137
- .modelResults h4 {
138
  margin: 0 0 12px 0;
139
- color: #374151;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  }
141
 
142
- .modelResult {
 
 
143
  display: flex;
144
- justify-content: space-between;
145
  align-items: center;
146
- padding: 8px 0;
147
- border-bottom: 1px solid #f3f4f6;
 
148
  }
149
 
150
- .modelResult:last-child {
151
- border-bottom: none;
 
 
 
 
152
  }
153
 
154
- .modelName {
155
- font-weight: 500;
156
- color: #4b5563;
157
- text-transform: capitalize;
 
158
  }
159
 
160
- .fake {
161
- color: #dc2626;
162
- font-weight: 600;
163
  background: #fee2e2;
164
- padding: 4px 8px;
165
- border-radius: 4px;
 
 
 
 
166
  }
167
 
168
- .real {
169
- color: #16a34a;
170
- font-weight: 600;
171
- background: #dcfce7;
172
- padding: 4px 8px;
173
- border-radius: 4px;
 
 
 
 
 
 
 
 
 
174
  }
 
102
  padding: 20px;
103
  border-radius: 8px;
104
  border: 1px solid #e5e7eb;
105
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
106
  }
107
 
108
  .mainResult p {
109
+ margin: 12px 0;
110
  font-size: 1.1rem;
111
+ color: #1f2937;
112
+ font-weight: 500;
113
  }
114
 
115
+ .mainResult strong {
116
+ color: #111827;
117
+ font-weight: 600;
118
+ }
119
+
120
+
121
+
122
+ .fake {
123
+ color: #dc2626;
124
+ font-weight: 600;
125
+ background: #fee2e2;
126
+ padding: 4px 8px;
127
+ border-radius: 4px;
128
+ }
129
+
130
+ .real {
131
+ color: #16a34a;
132
+ font-weight: 600;
133
+ background: #dcfce7;
134
+ padding: 4px 8px;
135
+ border-radius: 4px;
136
+ }
137
+
138
+ /* New styles for explainability and frame analysis */
139
+ .explainability {
140
+ background: #f0f9ff;
141
+ padding: 20px;
142
  border-radius: 8px;
143
+ border: 1px solid #0ea5e9;
144
+ margin-top: 16px;
145
+ box-shadow: 0 2px 8px rgba(14, 165, 233, 0.1);
146
  }
147
 
148
+ .explainability h4 {
149
  margin: 0 0 12px 0;
150
+ color: #0c4a6e;
151
+ display: flex;
152
+ align-items: center;
153
+ gap: 8px;
154
  }
155
 
156
+ .explainability p {
157
+ margin: 8px 0;
158
+ color: #164e63;
159
+ line-height: 1.6;
160
  font-size: 0.95rem;
161
+ font-weight: 500;
162
  }
163
 
164
+ .gradcam {
165
+ background: #fefce8;
166
  padding: 16px;
167
  border-radius: 8px;
168
+ border: 1px solid #eab308;
169
+ margin-top: 16px;
170
  }
171
 
172
+ .gradcam h4 {
173
  margin: 0 0 12px 0;
174
+ color: #92400e;
175
+ display: flex;
176
+ align-items: center;
177
+ gap: 8px;
178
+ font-weight: 600;
179
+ font-size: 1.1rem;
180
+ }
181
+
182
+ .gradcam p {
183
+ margin: 8px 0;
184
+ color: #92400e;
185
+ line-height: 1.6;
186
+ font-size: 0.95rem;
187
+ font-weight: 500;
188
+ }
189
+
190
+ .gradcamImage {
191
+ width: 100%;
192
+ max-width: 400px;
193
+ height: auto;
194
+ border-radius: 8px;
195
+ border: 2px solid #d97706;
196
+ margin: 12px 0;
197
+ }
198
+
199
+ .frameAnalysis {
200
+ background: #fef3f2;
201
+ padding: 16px;
202
+ border-radius: 8px;
203
+ border: 1px solid #f87171;
204
+ margin-top: 16px;
205
  }
206
 
207
+ .frameAnalysis h4 {
208
+ margin: 0 0 12px 0;
209
+ color: #991b1b;
210
  display: flex;
 
211
  align-items: center;
212
+ gap: 8px;
213
+ font-weight: 600;
214
+ font-size: 1.1rem;
215
  }
216
 
217
+ .frameAnalysis p {
218
+ margin: 8px 0;
219
+ color: #991b1b;
220
+ line-height: 1.6;
221
+ font-size: 0.95rem;
222
+ font-weight: 500;
223
  }
224
 
225
+ .frameList {
226
+ display: flex;
227
+ flex-wrap: wrap;
228
+ gap: 8px;
229
+ margin-top: 12px;
230
  }
231
 
232
+ .frameItem {
 
 
233
  background: #fee2e2;
234
+ color: #991b1b;
235
+ padding: 6px 12px;
236
+ border-radius: 20px;
237
+ font-size: 0.85rem;
238
+ font-weight: 500;
239
+ border: 1px solid #fecaca;
240
  }
241
 
242
+ .loadingExplain {
243
+ display: flex;
244
+ align-items: center;
245
+ gap: 12px;
246
+ color: #6b7280;
247
+ font-style: italic;
248
+ }
249
+
250
+ .loadingSpinner {
251
+ width: 16px;
252
+ height: 16px;
253
+ border: 2px solid #f3f3f3;
254
+ border-top: 2px solid #0070f3;
255
+ border-radius: 50%;
256
+ animation: spin 1s linear infinite;
257
  }