Spaces:
Running
Running
Update index.html
Browse files- index.html +51 -25
index.html
CHANGED
|
@@ -15,7 +15,7 @@ img{max-width:100%;border-radius:10px;margin-top:8px}
|
|
| 15 |
h2{margin:0 0 6px;font-size:24px}
|
| 16 |
h3{margin:16px 0 4px;font-size:15px;color:#93a3b1;font-weight:600}
|
| 17 |
#sub{color:#c5d0da;margin:0}
|
| 18 |
-
.bar{height:12px;background:#2c3844;border-radius:6px;overflow:hidden;margin:
|
| 19 |
.bar div{height:100%;background:#6f93ff;width:0}
|
| 20 |
li{color:#f0b04a;margin:4px 0}
|
| 21 |
small{color:#93a3b1}
|
|
@@ -24,7 +24,7 @@ small{color:#93a3b1}
|
|
| 24 |
<body>
|
| 25 |
<main>
|
| 26 |
<h1>Was this image made by AI?</h1>
|
| 27 |
-
<p>Runs on your phone. Your image is never uploaded. The first check downloads
|
| 28 |
<input id="f" type="file" accept="image/*">
|
| 29 |
<img id="p" alt="">
|
| 30 |
<div id="res">
|
|
@@ -32,22 +32,31 @@ small{color:#93a3b1}
|
|
| 32 |
<p id="sub"></p>
|
| 33 |
<h3>Hidden labels</h3>
|
| 34 |
<ul id="m"></ul>
|
| 35 |
-
<h3>Pixel check</h3>
|
| 36 |
-
<div id="
|
| 37 |
-
<div class="bar"><div id="
|
|
|
|
|
|
|
|
|
|
| 38 |
<small id="raw"></small><br><br>
|
| 39 |
<small>A probability, not proof. Detectors can be wrong, especially on new generators and screenshots.</small>
|
| 40 |
</div>
|
| 41 |
</main>
|
| 42 |
<script type="module">
|
| 43 |
-
import { AutoProcessor, AutoModelForImageClassification, RawImage } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3';
|
| 44 |
const $ = id => document.getElementById(id);
|
| 45 |
-
const
|
| 46 |
-
|
| 47 |
-
|
|
|
|
| 48 |
if(model) return;
|
| 49 |
-
processor = await AutoProcessor.from_pretrained(
|
| 50 |
-
model = await AutoModelForImageClassification.from_pretrained(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
}
|
| 52 |
const TOOLS = [[/midjourney/i,'Midjourney'],[/dall[\s\-路]?e|openai/i,'OpenAI / DALL路E'],[/stable\s?diffusion|sdxl|automatic1111/i,'Stable Diffusion'],[/firefly|generative ?fill/i,'Adobe Firefly'],[/imagen|gemini|synthid/i,'Google AI'],[/leonardo\.?ai/i,'Leonardo AI'],[/comfyui/i,'ComfyUI'],[/ideogram/i,'Ideogram']];
|
| 53 |
function markers(bytes){
|
|
@@ -59,44 +68,61 @@ function markers(bytes){
|
|
| 59 |
return out;
|
| 60 |
}
|
| 61 |
function li(text, color){ const e = document.createElement('li'); e.textContent = text; if(color) e.style.color = color; return e; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
$('f').onchange = async e => {
|
| 63 |
const file = e.target.files[0]; if(!file) return;
|
| 64 |
const url = URL.createObjectURL(file);
|
| 65 |
$('p').src = url;
|
| 66 |
$('res').style.display = 'block';
|
| 67 |
-
$('v').textContent = 'Loading
|
| 68 |
-
$('sub').textContent = ''; $('
|
|
|
|
| 69 |
try {
|
| 70 |
const mk = markers(new Uint8Array(await file.arrayBuffer()));
|
| 71 |
-
await
|
| 72 |
$('v').textContent = 'Checking...';
|
| 73 |
const image = await RawImage.read(url);
|
| 74 |
const inputs = await processor(image);
|
| 75 |
const { logits } = await model(inputs);
|
| 76 |
const z = Array.from(logits.data);
|
| 77 |
-
let
|
| 78 |
-
if(z.length === 1){
|
| 79 |
-
else { const m = Math.max(...z), ex = z.map(v => Math.exp(v - m)), s = ex.reduce((a,b) => a + b, 0);
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
if(mk.length){ mk.forEach(t => $('m').appendChild(li(t))); }
|
| 82 |
else { $('m').appendChild(li('None found. Screenshots and forwarded images often lose their labels.', '#93a3b1')); }
|
|
|
|
| 83 |
if(mk.length){
|
| 84 |
$('v').textContent = 'AI markers found';
|
| 85 |
$('sub').textContent = ai >= 0.5
|
| 86 |
-
? "The file's labels say AI, and
|
| 87 |
-
: "The file's labels say AI.
|
| 88 |
} else if(ai >= 0.85){
|
| 89 |
$('v').textContent = 'Likely AI-generated';
|
| 90 |
-
$('sub').textContent = 'No labels found, but
|
| 91 |
} else if(ai >= 0.5){
|
| 92 |
$('v').textContent = 'Possibly AI';
|
| 93 |
-
$('sub').textContent = 'No labels found and the pixel
|
| 94 |
} else {
|
| 95 |
$('v').textContent = 'No AI signs found';
|
| 96 |
-
$('sub').textContent = 'No labels found and the pixel
|
| 97 |
}
|
| 98 |
-
$('pct').textContent = 'Chance it is AI: ' + Math.round(ai*100) + '%';
|
| 99 |
-
$('b').style.width = Math.round(ai*100) + '%';
|
| 100 |
} catch(err){
|
| 101 |
$('v').textContent = 'Could not analyze this image.';
|
| 102 |
$('raw').textContent = String(err);
|
|
|
|
| 15 |
h2{margin:0 0 6px;font-size:24px}
|
| 16 |
h3{margin:16px 0 4px;font-size:15px;color:#93a3b1;font-weight:600}
|
| 17 |
#sub{color:#c5d0da;margin:0}
|
| 18 |
+
.bar{height:12px;background:#2c3844;border-radius:6px;overflow:hidden;margin:6px 0 10px}
|
| 19 |
.bar div{height:100%;background:#6f93ff;width:0}
|
| 20 |
li{color:#f0b04a;margin:4px 0}
|
| 21 |
small{color:#93a3b1}
|
|
|
|
| 24 |
<body>
|
| 25 |
<main>
|
| 26 |
<h1>Was this image made by AI?</h1>
|
| 27 |
+
<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>
|
| 28 |
<input id="f" type="file" accept="image/*">
|
| 29 |
<img id="p" alt="">
|
| 30 |
<div id="res">
|
|
|
|
| 32 |
<p id="sub"></p>
|
| 33 |
<h3>Hidden labels</h3>
|
| 34 |
<ul id="m"></ul>
|
| 35 |
+
<h3>Pixel check 1</h3>
|
| 36 |
+
<div id="pct1"></div>
|
| 37 |
+
<div class="bar"><div id="b1"></div></div>
|
| 38 |
+
<h3>Pixel check 2</h3>
|
| 39 |
+
<div id="pct2"></div>
|
| 40 |
+
<div class="bar"><div id="b2"></div></div>
|
| 41 |
<small id="raw"></small><br><br>
|
| 42 |
<small>A probability, not proof. Detectors can be wrong, especially on new generators and screenshots.</small>
|
| 43 |
</div>
|
| 44 |
</main>
|
| 45 |
<script type="module">
|
| 46 |
+
import { pipeline, AutoProcessor, AutoModelForImageClassification, RawImage } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3';
|
| 47 |
const $ = id => document.getElementById(id);
|
| 48 |
+
const MODEL1 = 'buildborderless/CommunityForensics-DeepfakeDet-ViT';
|
| 49 |
+
const MODEL2 = 'LPX55/detection-model-1-ONNX';
|
| 50 |
+
let processor, model, clf2, fail2 = '';
|
| 51 |
+
async function load1(){
|
| 52 |
if(model) return;
|
| 53 |
+
processor = await AutoProcessor.from_pretrained(MODEL1);
|
| 54 |
+
model = await AutoModelForImageClassification.from_pretrained(MODEL1, { dtype: 'fp32' });
|
| 55 |
+
}
|
| 56 |
+
async function load2(){
|
| 57 |
+
if(clf2 || fail2) return;
|
| 58 |
+
try { clf2 = await pipeline('image-classification', MODEL2, { dtype: 'fp32' }); }
|
| 59 |
+
catch(e){ fail2 = String(e); }
|
| 60 |
}
|
| 61 |
const TOOLS = [[/midjourney/i,'Midjourney'],[/dall[\s\-路]?e|openai/i,'OpenAI / DALL路E'],[/stable\s?diffusion|sdxl|automatic1111/i,'Stable Diffusion'],[/firefly|generative ?fill/i,'Adobe Firefly'],[/imagen|gemini|synthid/i,'Google AI'],[/leonardo\.?ai/i,'Leonardo AI'],[/comfyui/i,'ComfyUI'],[/ideogram/i,'Ideogram']];
|
| 62 |
function markers(bytes){
|
|
|
|
| 68 |
return out;
|
| 69 |
}
|
| 70 |
function li(text, color){ const e = document.createElement('li'); e.textContent = text; if(color) e.style.color = color; return e; }
|
| 71 |
+
function show(n, ai){
|
| 72 |
+
if(ai === null){ $('pct'+n).textContent = 'Not available'; $('b'+n).style.width = '0'; return; }
|
| 73 |
+
$('pct'+n).textContent = 'Chance it is AI: ' + Math.round(ai*100) + '%';
|
| 74 |
+
$('b'+n).style.width = Math.round(ai*100) + '%';
|
| 75 |
+
}
|
| 76 |
$('f').onchange = async e => {
|
| 77 |
const file = e.target.files[0]; if(!file) return;
|
| 78 |
const url = URL.createObjectURL(file);
|
| 79 |
$('p').src = url;
|
| 80 |
$('res').style.display = 'block';
|
| 81 |
+
$('v').textContent = 'Loading models, please wait...';
|
| 82 |
+
$('sub').textContent = ''; $('m').innerHTML = ''; $('raw').textContent = '';
|
| 83 |
+
['1','2'].forEach(n => { $('pct'+n).textContent = ''; $('b'+n).style.width = '0'; });
|
| 84 |
try {
|
| 85 |
const mk = markers(new Uint8Array(await file.arrayBuffer()));
|
| 86 |
+
await load1();
|
| 87 |
$('v').textContent = 'Checking...';
|
| 88 |
const image = await RawImage.read(url);
|
| 89 |
const inputs = await processor(image);
|
| 90 |
const { logits } = await model(inputs);
|
| 91 |
const z = Array.from(logits.data);
|
| 92 |
+
let ai1;
|
| 93 |
+
if(z.length === 1){ ai1 = 1 / (1 + Math.exp(-z[0])); }
|
| 94 |
+
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; }
|
| 95 |
+
show('1', ai1);
|
| 96 |
+
$('v').textContent = 'Loading second check...';
|
| 97 |
+
await load2();
|
| 98 |
+
let ai2 = null, raw2 = '';
|
| 99 |
+
if(clf2){
|
| 100 |
+
const r = await clf2(url, { top_k: 5 });
|
| 101 |
+
raw2 = r.map(x => x.label + ' ' + Math.round(x.score*100) + '%').join(', ');
|
| 102 |
+
const hit = r.find(x => /artificial|fake|synth|generat|\bai\b/i.test(x.label));
|
| 103 |
+
const real = r.find(x => /real|human|natural|authentic/i.test(x.label));
|
| 104 |
+
if(hit) ai2 = hit.score; else if(real) ai2 = 1 - real.score;
|
| 105 |
+
}
|
| 106 |
+
show('2', ai2);
|
| 107 |
+
$('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));
|
| 108 |
if(mk.length){ mk.forEach(t => $('m').appendChild(li(t))); }
|
| 109 |
else { $('m').appendChild(li('None found. Screenshots and forwarded images often lose their labels.', '#93a3b1')); }
|
| 110 |
+
const ai = Math.max(ai1, ai2 === null ? 0 : ai2);
|
| 111 |
if(mk.length){
|
| 112 |
$('v').textContent = 'AI markers found';
|
| 113 |
$('sub').textContent = ai >= 0.5
|
| 114 |
+
? "The file's labels say AI, and at least one pixel check agrees."
|
| 115 |
+
: "The file's labels say AI. Both pixel checks disagreed, which can happen with newer generators, so trust the labels.";
|
| 116 |
} else if(ai >= 0.85){
|
| 117 |
$('v').textContent = 'Likely AI-generated';
|
| 118 |
+
$('sub').textContent = 'No labels found, but a pixel check says AI.';
|
| 119 |
} else if(ai >= 0.5){
|
| 120 |
$('v').textContent = 'Possibly AI';
|
| 121 |
+
$('sub').textContent = 'No labels found and the pixel checks are unsure.';
|
| 122 |
} else {
|
| 123 |
$('v').textContent = 'No AI signs found';
|
| 124 |
+
$('sub').textContent = 'No labels found and the pixel checks say real. These models can miss newer AI images, so this is not proof.';
|
| 125 |
}
|
|
|
|
|
|
|
| 126 |
} catch(err){
|
| 127 |
$('v').textContent = 'Could not analyze this image.';
|
| 128 |
$('raw').textContent = String(err);
|