Spaces:
Sleeping
Sleeping
| // src/components/GradCamViewer.jsx | |
| export default function GradCamViewer({ gradcamImage, filename }) { | |
| const hasImage = gradcamImage && gradcamImage.length > 0; | |
| return ( | |
| <div | |
| className="gradcam-card glass-card" | |
| role="figure" | |
| aria-label="Grad-CAM saliency heatmap" | |
| > | |
| <h3>Grad-CAM Heatmap</h3> | |
| <p className="gradcam-subtitle"> | |
| Gradient-weighted Class Activation Mapping — highlights regions influencing the deepfake decision | |
| </p> | |
| <div className="gradcam-image-wrap"> | |
| {hasImage ? ( | |
| <img | |
| className="gradcam-image" | |
| src={`data:image/png;base64,${gradcamImage}`} | |
| alt="Grad-CAM saliency heatmap highlighting manipulated facial regions" | |
| /> | |
| ) : ( | |
| <div className="gradcam-placeholder" aria-label="No heatmap available"> | |
| <div | |
| className="icon" | |
| aria-hidden="true" | |
| style={{ fontSize: '3rem', opacity: 0.35 }} | |
| > | |
| 🌡️ | |
| </div> | |
| <p> | |
| {filename === 'demo_video.mp4' | |
| ? 'Grad-CAM heatmap is generated from real video frames. Run the backend with an actual video file to see facial manipulation highlighting.' | |
| : 'No heatmap data returned by the server. The backend may not have Grad-CAM enabled.'} | |
| </p> | |
| {/* Placeholder visual illustration */} | |
| <div | |
| aria-hidden="true" | |
| style={{ | |
| width: '100%', | |
| maxWidth: 320, | |
| height: 180, | |
| borderRadius: '8px', | |
| background: ` | |
| radial-gradient(ellipse 60% 50% at 50% 45%, rgba(239,68,68,0.25) 0%, transparent 60%), | |
| radial-gradient(ellipse 30% 20% at 35% 35%, rgba(239,68,68,0.4) 0%, transparent 40%), | |
| radial-gradient(ellipse 30% 20% at 65% 35%, rgba(239,68,68,0.4) 0%, transparent 40%), | |
| radial-gradient(ellipse 20% 15% at 50% 60%, rgba(239,68,68,0.3) 0%, transparent 40%), | |
| linear-gradient(135deg, #1a1030 0%, #0d1520 100%) | |
| `, | |
| position: 'relative', | |
| overflow: 'hidden', | |
| border: '1px solid rgba(239,68,68,0.15)', | |
| display: 'flex', | |
| alignItems: 'center', | |
| justifyContent: 'center', | |
| }} | |
| > | |
| {/* Face outline placeholder */} | |
| <svg width="80" height="100" viewBox="0 0 80 100" fill="none" opacity="0.25"> | |
| <ellipse cx="40" cy="45" rx="28" ry="35" stroke="#ef4444" strokeWidth="1.5" /> | |
| <ellipse cx="28" cy="38" rx="5" ry="4" stroke="#ef4444" strokeWidth="1" /> | |
| <ellipse cx="52" cy="38" rx="5" ry="4" stroke="#ef4444" strokeWidth="1" /> | |
| <path d="M30 62 Q40 70 50 62" stroke="#ef4444" strokeWidth="1.5" strokeLinecap="round" /> | |
| </svg> | |
| <span | |
| style={{ | |
| position: 'absolute', | |
| bottom: 8, | |
| left: '50%', | |
| transform: 'translateX(-50%)', | |
| fontSize: '0.65rem', | |
| color: 'rgba(239,68,68,0.5)', | |
| whiteSpace: 'nowrap', | |
| }} | |
| > | |
| Placeholder — not real heatmap | |
| </span> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| {/* Legend */} | |
| {hasImage && ( | |
| <div className="gradcam-legend"> | |
| <div className="legend-item"> | |
| <div className="legend-dot" style={{ background: '#ef4444' }} /> | |
| <span>High activation (suspicious)</span> | |
| </div> | |
| <div className="legend-item"> | |
| <div className="legend-dot" style={{ background: '#f97316' }} /> | |
| <span>Medium activation</span> | |
| </div> | |
| <div className="legend-item"> | |
| <div className="legend-dot" style={{ background: '#3b82f6' }} /> | |
| <span>Low activation</span> | |
| </div> | |
| </div> | |
| )} | |
| {!hasImage && ( | |
| <p | |
| style={{ | |
| marginTop: '12px', | |
| fontSize: '0.75rem', | |
| color: 'var(--text-muted)', | |
| textAlign: 'center', | |
| }} | |
| > | |
| Heatmaps available when backend returns <code style={{ color: 'var(--accent-cyan-light)', fontSize: '0.7rem' }}>gradcam_image</code> in response | |
| </p> | |
| )} | |
| </div> | |
| ); | |
| } | |