fakeshield-api / fakeshield /src /pages /ImageLab /MetadataInspector.tsx
Akash4911's picture
Production Deploy: Improved robustness and logging
66b6851
import { CheckCircle2, XCircle, MapPin, Camera, Cpu, Scan, AlertCircle, Info } from 'lucide-react';
interface MetadataInspectorProps {
metadata?: {
camera: string;
gps: string;
lens: string;
software: string;
dimensions: string;
};
}
type BadgeState = 'present' | 'missing' | 'ai' | 'info';
function getBadgeState(key: string, value: string): BadgeState {
if (!value || value === 'NONE' || value === '') return 'missing';
const v = value.toLowerCase();
const aiKeywords = ['stable diffusion', 'midjourney', 'dall', 'firefly', 'comfy', 'automatic1111', 'flux', 'ai'];
if (key === 'software' && aiKeywords.some(k => v.includes(k))) return 'ai';
if (key === 'gps' && v === 'present') return 'present';
return 'present';
}
const BADGE_CONFIG: Record<BadgeState, { color: string; bgColor: string; icon: React.ComponentType<any> }> = {
present: { color: '#22c55e', bgColor: 'rgba(34,197,94,0.1)', icon: CheckCircle2 },
missing: { color: '#94a3b8', bgColor: 'rgba(148,163,184,0.1)', icon: XCircle },
ai: { color: '#ef4444', bgColor: 'rgba(239,68,68,0.12)', icon: AlertCircle },
info: { color: '#0ea5e9', bgColor: 'rgba(14,165,233,0.1)', icon: Info },
};
const FIELD_ICONS: Record<string, React.ComponentType<any>> = {
camera: Camera,
gps: MapPin,
lens: Scan,
software: Cpu,
};
const FIELD_LABELS: Record<string, string> = {
camera: 'Camera Hardware',
gps: 'GPS Location Data',
lens: 'Lens Model',
software: 'Processing Software',
};
const FIELD_NOTES: Record<string, string> = {
camera: 'AI images have no camera make/model',
gps: 'Real photos often embed GPS coordinates',
lens: 'Real cameras embed lens EXIF data',
software: 'AI tools embed their name in EXIF',
};
export default function MetadataInspector({ metadata }: MetadataInspectorProps) {
if (!metadata) return null;
const fields = ['camera', 'gps', 'lens', 'software'] as const;
return (
<div className="rounded-2xl border overflow-hidden shadow-sm" style={{ borderColor: 'var(--panel-border)', background: 'var(--panel-bg)', color: 'var(--text-primary)' }}>
{/* Header */}
<div
className="px-4 py-3 border-b flex items-center justify-between"
style={{ borderColor: 'var(--panel-border)' }}
>
<span className="text-xs font-bold tracking-tight text-[var(--text-primary)] uppercase">
EXIF Metadata Guard
</span>
{metadata.dimensions && (
<span className="text-[11px] font-semibold text-[var(--text-muted)]">
{metadata.dimensions}
</span>
)}
</div>
<div className="p-3 space-y-2">
{fields.map(key => {
const value = metadata[key];
const state = getBadgeState(key, value);
const cfg = BADGE_CONFIG[state];
const Icon = FIELD_ICONS[key] || Camera;
const StatusIcon = cfg.icon;
return (
<div
key={key}
className="flex items-center gap-3 px-3 py-2.5 rounded-xl"
style={{ background: cfg.bgColor, border: `1px solid ${cfg.color}20` }}
>
{/* Icon */}
<div style={{ color: cfg.color }} className="shrink-0">
<Icon className="w-3.5 h-3.5" />
</div>
{/* Label + note */}
<div className="flex-1 min-w-0">
<div className="text-[10px] font-bold uppercase tracking-wide text-[var(--text-secondary)]">
{FIELD_LABELS[key]}
</div>
<div
className="text-xs mt-0.5 truncate font-bold"
style={{ color: state === 'missing' ? '#94a3b8' : cfg.color }}
title={value}
>
{state === 'missing' ? 'SIGNAL MISSING' : value}
</div>
</div>
{/* Badge */}
<div
className="shrink-0 w-6 h-6 rounded-full flex items-center justify-center"
style={{ background: cfg.color }}
>
<StatusIcon className="w-3.5 h-3.5 text-white" strokeWidth={3} />
</div>
</div>
);
})}
</div>
{/* Forensic note */}
<div
className="px-4 py-3 border-t text-[10px] font-medium leading-relaxed bg-[var(--btn-secondary-bg)]"
style={{ borderColor: 'var(--panel-border)', color: 'var(--text-secondary)' }}
>
<span className="font-bold text-cyan-500">FORENSIC NOTE:</span>
{" "}Camera make/model is a primary indicator. Missing GPS or shutter speed data is common in web-optimized images but remains a diagnostic factor.
</div>
</div>
);
}