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>