File size: 4,856 Bytes
e63d782
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
const dropzone   = document.getElementById('dropzone');
const fileInput  = document.getElementById('fileInput');
const scanStage  = document.getElementById('scanStage');
const previewImg = document.getElementById('previewImg');
const scanLabel  = document.getElementById('scanLabel');
const rescanBtn  = document.getElementById('rescanBtn');
const result     = document.getElementById('result');
const errorBox   = document.getElementById('errorBox');

const verdictIcon  = document.getElementById('verdictIcon');
const verdictLabel = document.getElementById('verdictLabel');
const verdictSub   = document.getElementById('verdictSub');
const realPct      = document.getElementById('realPct');
const screenPct    = document.getElementById('screenPct');
const meterFillReal   = document.getElementById('meterFillReal');
const meterFillScreen = document.getElementById('meterFillScreen');

const MIN_SCAN_MS = 1700;
const SCAN_MESSAGES = ['Reading pixels…', 'Checking bezel shadows…', 'Measuring color temperature…', 'Scoring saturation profile…'];

dropzone.addEventListener('click', () => fileInput.click());
dropzone.addEventListener('dragover', (e) => { e.preventDefault(); dropzone.classList.add('drag-over'); });
dropzone.addEventListener('dragleave', () => dropzone.classList.remove('drag-over'));
dropzone.addEventListener('drop', (e) => {
  e.preventDefault();
  dropzone.classList.remove('drag-over');
  if (e.dataTransfer.files.length) handleFile(e.dataTransfer.files[0]);
});
fileInput.addEventListener('change', () => {
  if (fileInput.files.length) handleFile(fileInput.files[0]);
});
rescanBtn.addEventListener('click', resetUI);

function resetUI() {
  fileInput.value = '';
  dropzone.classList.remove('hidden');
  scanStage.classList.add('hidden');
  scanStage.classList.remove('scanning');
  rescanBtn.classList.add('hidden');
  result.classList.add('hidden');
  errorBox.classList.add('hidden');
  meterFillReal.style.width = '0%';
  meterFillScreen.style.width = '0%';
}

function handleFile(file) {
  if (!file.type.startsWith('image/')) {
    showError('That file does not look like an image.');
    return;
  }

  const url = URL.createObjectURL(file);
  previewImg.src = url;

  dropzone.classList.add('hidden');
  scanStage.classList.remove('hidden');
  scanStage.classList.add('scanning');
  rescanBtn.classList.add('hidden');
  result.classList.add('hidden');
  errorBox.classList.add('hidden');

  let msgIdx = 0;
  scanLabel.textContent = SCAN_MESSAGES[0];
  const msgTimer = setInterval(() => {
    msgIdx = (msgIdx + 1) % SCAN_MESSAGES.length;
    scanLabel.textContent = SCAN_MESSAGES[msgIdx];
  }, 600);

  const started = Date.now();
  const formData = new FormData();
  formData.append('file', file);

  fetch('/api/predict', { method: 'POST', body: formData })
    .then(async (res) => {
      const body = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(body.detail || 'Prediction failed.');
      return body;
    })
    .then((data) => {
      const elapsed = Date.now() - started;
      const wait = Math.max(0, MIN_SCAN_MS - elapsed);
      setTimeout(() => {
        clearInterval(msgTimer);
        finishScan();
        showResult(data);
      }, wait);
    })
    .catch((err) => {
      clearInterval(msgTimer);
      const elapsed = Date.now() - started;
      const wait = Math.max(0, 600 - elapsed);
      setTimeout(() => {
        finishScan();
        showError(err.message || 'Something went wrong while analyzing the image.');
      }, wait);
    });
}

function finishScan() {
  scanStage.classList.remove('scanning');
  rescanBtn.classList.remove('hidden');
}

function showResult(data) {
  result.classList.remove('hidden');
  const isScreen = data.label === 'screen_recapture';

  verdictIcon.className = 'verdict-icon ' + (isScreen ? 'screen' : 'real');
  verdictIcon.textContent = isScreen ? '⚠' : '✓';

  verdictLabel.className = 'verdict-label ' + (isScreen ? 'screen' : 'real');
  verdictLabel.textContent = isScreen ? 'Screen Recapture' : 'Real Photo';
  verdictSub.textContent = `${data.confidence}% confidence`;

  realPct.textContent = `${data.real_pct}%`;
  screenPct.textContent = `${data.screen_pct}%`;

  requestAnimationFrame(() => {
    meterFillReal.style.width = `${data.real_pct}%`;
    meterFillScreen.style.width = `${data.screen_pct}%`;
  });
}

function showError(message) {
  result.classList.remove('hidden');
  errorBox.textContent = message;
  errorBox.classList.remove('hidden');
  verdictIcon.className = 'verdict-icon screen';
  verdictIcon.textContent = '!';
  verdictLabel.className = 'verdict-label screen';
  verdictLabel.textContent = 'Could not analyze image';
  verdictSub.textContent = '';
  realPct.textContent = '0%';
  screenPct.textContent = '0%';
  meterFillReal.style.width = '0%';
  meterFillScreen.style.width = '0%';
}