Spaces:
Running
Running
File size: 6,621 Bytes
c3a3a24 f412e6a 4de2b70 c3a3a24 4de2b70 c3a3a24 f412e6a 4de2b70 f412e6a c3a3a24 4de2b70 c3a3a24 4de2b70 954f392 4de2b70 def45de 4de2b70 c3a3a24 70e8190 c3a3a24 70e8190 c3a3a24 f412e6a 4de2b70 c3a3a24 4de2b70 c3a3a24 4de2b70 c3a3a24 def45de 4de2b70 f412e6a 4de2b70 f412e6a 4de2b70 f412e6a 4de2b70 f412e6a 4de2b70 f412e6a 4de2b70 f412e6a c3a3a24 f1ef9e0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AI Image Detector</title>
<style>
body{margin:0;font:16px/1.5 system-ui,sans-serif;background:#0f151b;color:#e8eef3}
main{max-width:520px;margin:0 auto;padding:24px 16px}
h1{font-size:28px;margin:8px 0}
p{color:#93a3b1}
input{display:block;width:100%;margin:16px 0;padding:14px;background:#18212a;border:2px dashed #2c3844;border-radius:12px;color:#e8eef3}
img{max-width:100%;border-radius:10px;margin-top:8px}
#res{background:#18212a;border-radius:12px;padding:16px;margin-top:16px;display:none}
h2{margin:0 0 6px;font-size:24px}
h3{margin:16px 0 4px;font-size:15px;color:#93a3b1;font-weight:600}
#sub{color:#c5d0da;margin:0}
.bar{height:12px;background:#2c3844;border-radius:6px;overflow:hidden;margin:6px 0 10px}
.bar div{height:100%;background:#6f93ff;width:0}
li{color:#f0b04a;margin:4px 0}
small{color:#93a3b1}
</style>
</head>
<body>
<main>
<h1>Was this image made by AI?</h1>
<p>Runs on your phone. Your image is never uploaded. The first check downloads two models (a few hundred MB), so use Wi-Fi.</p>
<input id="f" type="file" accept="image/*">
<img id="p" alt="">
<div id="res">
<h2 id="v"></h2>
<p id="sub"></p>
<h3>Hidden labels</h3>
<ul id="m"></ul>
<h3>Pixel check 1</h3>
<div id="pct1"></div>
<div class="bar"><div id="b1"></div></div>
<h3>Pixel check 2</h3>
<div id="pct2"></div>
<div class="bar"><div id="b2"></div></div>
<small id="raw"></small><br><br>
<small>A probability, not proof. Detectors can be wrong, especially on new generators and screenshots.</small>
</div>
</main>
<script type="module">
import { pipeline, AutoProcessor, AutoModelForImageClassification, RawImage } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3';
const $ = id => document.getElementById(id);
const MODEL1 = 'buildborderless/CommunityForensics-DeepfakeDet-ViT';
const MODEL2 = 'LPX55/detection-model-2-ONNX';
let processor, model, clf2, fail2 = '';
async function load1(){
if(model) return;
processor = await AutoProcessor.from_pretrained(MODEL1);
model = await AutoModelForImageClassification.from_pretrained(MODEL1, { dtype: 'fp32' });
}
async function load2(){
if(clf2 || fail2) return;
try { clf2 = await pipeline('image-classification', MODEL2, { dtype: 'fp32' }); }
catch(e){ fail2 = String(e); }
}
const TOOLS = [
[/\bmidjourney\b/i,'Midjourney'],
[/\bopenai\b|\bdall[\s\-_·]?e\b/i,'OpenAI / DALL·E'],
[/stable[\s\-_]?diffusion|stability\.ai|automatic1111|\bsdxl\b/i,'Stable Diffusion'],
[/adobe firefly|\bfirefly\b|generative ?fill/i,'Adobe Firefly'],
[/\bimagen\b|\bgemini\b|\bsynthid\b|google ai/i,'Google AI'],
[/leonardo\.ai/i,'Leonardo AI'],
[/\bcomfyui\b/i,'ComfyUI'],
[/\bideogram\b/i,'Ideogram']
];
function markers(bytes){
const full = new TextDecoder('latin1').decode(bytes), out = [];
const runs = full.match(/[\x20-\x7e]{6,}/g);
const readable = runs ? runs.join('\n') : '';
if(/c2pa|jumbf|contentcredentials/i.test(full)) out.push('Content Credentials (C2PA) found');
if(/algorithmicMedia/.test(full)) out.push('Marked as AI media in metadata');
TOOLS.forEach(([re,n]) => { if(re.test(readable)) out.push('Names an AI tool: ' + n); });
if(/Steps:\s*\d+,\s*Sampler|negative prompt|"class_type"/i.test(readable)) out.push('Contains a generation prompt or settings');
return out;
}
function li(text, color){ const e = document.createElement('li'); e.textContent = text; if(color) e.style.color = color; return e; }
function show(n, ai){
if(ai === null){ $('pct'+n).textContent = 'Not available'; $('b'+n).style.width = '0'; return; }
$('pct'+n).textContent = 'Chance it is AI: ' + Math.round(ai*100) + '%';
$('b'+n).style.width = Math.round(ai*100) + '%';
}
$('f').onchange = async e => {
const file = e.target.files[0]; if(!file) return;
const url = URL.createObjectURL(file);
$('p').src = url;
$('res').style.display = 'block';
$('v').textContent = 'Loading models, please wait...';
$('sub').textContent = ''; $('m').innerHTML = ''; $('raw').textContent = '';
['1','2'].forEach(n => { $('pct'+n).textContent = ''; $('b'+n).style.width = '0'; });
try {
const mk = markers(new Uint8Array(await file.arrayBuffer()));
await load1();
$('v').textContent = 'Checking...';
const image = await RawImage.read(url);
const inputs = await processor(image);
const { logits } = await model(inputs);
const z = Array.from(logits.data);
let ai1;
if(z.length === 1){ ai1 = 1 / (1 + Math.exp(-z[0])); }
else { const m = Math.max(...z), ex = z.map(v => Math.exp(v - m)), s = ex.reduce((a,b) => a + b, 0); ai1 = ex[1] / s; }
show('1', ai1);
$('v').textContent = 'Loading second check...';
await load2();
let ai2 = null, raw2 = '';
if(clf2){
const r = await clf2(url, { top_k: 5 });
raw2 = r.map(x => x.label + ' ' + Math.round(x.score*100) + '%').join(', ');
const hit = r.find(x => /artificial|fake|synth|generat|\bai\b/i.test(x.label));
const real = r.find(x => /real|human|natural|authentic/i.test(x.label));
if(hit) ai2 = hit.score; else if(real) ai2 = 1 - real.score;
}
show('2', ai2);
$('raw').textContent = 'Check 1 raw score: ' + z.map(v => v.toFixed(2)).join(', ') + (clf2 ? ' | Check 2 output: ' + raw2 : ' | Check 2 could not load: ' + fail2.slice(0,120));
if(mk.length){ mk.forEach(t => $('m').appendChild(li(t))); }
else { $('m').appendChild(li('None found. Screenshots and forwarded images often lose their labels.', '#93a3b1')); }
const ai = Math.max(ai1, ai2 === null ? 0 : ai2);
if(mk.length){
$('v').textContent = 'AI markers found';
$('sub').textContent = ai >= 0.5
? "The file's labels say AI, and at least one pixel check agrees."
: "The file's labels say AI. Both pixel checks disagreed, which can happen with newer generators, so trust the labels.";
} else if(ai >= 0.85){
$('v').textContent = 'Likely AI-generated';
$('sub').textContent = 'No labels found, but a pixel check says AI.';
} else if(ai >= 0.5){
$('v').textContent = 'Possibly AI';
$('sub').textContent = 'No labels found and the pixel checks are unsure.';
} else {
$('v').textContent = 'No AI signs found';
$('sub').textContent = 'No labels found and the pixel checks say real. These models can miss newer AI images, so this is not proof.';
}
} catch(err){
$('v').textContent = 'Could not analyze this image.';
$('raw').textContent = String(err);
}
};
</script>
</body>
</html> |