import { useState, useRef } from 'react'
import './App.css'
// Same-origin '/api' in production (FastAPI serves the built app); the Vite dev
// server proxies '/api' to the backend. Override with VITE_API_URL if needed.
const API = import.meta.env.VITE_API_URL || '/api'
const VERDICT = {
'AUTHENTIC': { icon: 'β', tone: 'good', note: 'No significant tampering signals detected.' },
'TAMPERED': { icon: '!', tone: 'bad', note: 'This document shows signs of manipulation.' },
'AI-GENERATED': { icon: 'β', tone: 'warn', note: 'This image appears to be AI-generated.' },
}
const DET_ICON = {
ela: 'π¬', noise: 'π‘', copy_move: 'π§¬', double_jpeg: 'ποΈ',
font: 'π€', metadata: 'π·οΈ', ai_generated: 'π€', model: 'π§ ',
}
function Dropzone({ onFile }) {
const [drag, setDrag] = useState(false)
const ref = useRef()
return (
ref.current.click()}
onDragOver={e => { e.preventDefault(); setDrag(true) }}
onDragLeave={() => setDrag(false)}
onDrop={e => { e.preventDefault(); setDrag(false); e.dataTransfer.files[0] && onFile(e.dataTransfer.files[0]) }}
>
e.target.files[0] && onFile(e.target.files[0])} />
β¬
Drop a document or image
JPG Β· PNG Β· TIFF Β· PDF β up to 20 MB
Choose file
)
}
function Gauge({ value, tone }) {
const pct = Math.round(value * 100)
const r = 34, c = 2 * Math.PI * r
const dash = (value * c)
return (
)
}
function Bar({ name, score }) {
const pct = Math.round(score * 100)
const tone = score > 0.6 ? 'bad' : score > 0.35 ? 'warn' : 'good'
return (
{DET_ICON[name] || 'β’'}
{name.replace(/_/g, ' ')}
{pct}%
)
}
export default function App() {
const [file, setFile] = useState(null)
const [state, setState] = useState('idle')
const [res, setRes] = useState(null)
const [err, setErr] = useState('')
const [view, setView] = useState('heatmap')
async function run(f) {
setFile(f); setState('loading'); setRes(null); setView('heatmap')
const form = new FormData(); form.append('file', f)
try {
const r = await fetch(`${API}/analyze`, { method: 'POST', body: form })
if (!r.ok) throw new Error((await r.json().catch(() => ({}))).detail || `Server error ${r.status}`)
setRes(await r.json()); setState('done')
} catch (e) { setErr(e.message); setState('error') }
}
const reset = () => { setFile(null); setRes(null); setState('idle') }
const v = res ? (VERDICT[res.label] || VERDICT.AUTHENTIC) : null
return (
{state === 'idle' && (
Document forensics
Is this document real?
Upload an image or PDF. Eight forensic detectors and a trained CNN
inspect it for forgery, cloning, recompression, and AI generation.
𧬠Copy-moveπ¬ Error-level analysis
π€ AI detectionπ§ Neural localization
)}
{state === 'loading' && (
Analyzing {file?.name}
Running detectors + CNN modelβ¦
)}
{state === 'error' && (
)}
{state === 'done' && res && (
{v.icon}
{res.label}
{v.note}
Evidence map
{file &&
})
}
{view === 'heatmap' && res.heatmap_base64 &&

}
Brighter / red areas indicate higher suspicion.
Detector breakdown
{res.per_detector.map(d => )}
{res.evidence?.length > 0 && (
Findings
{res.evidence.map((e, i) => - {e}
)}
)}
)}
)
}