hh-shipments 0.3.14 → 0.3.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/app.js +30 -13
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -175,12 +175,13 @@ async function ocrFile(file, statusEl) {
|
|
|
175
175
|
async function handlePhotoFile(f) {
|
|
176
176
|
const target = $('photo-result');
|
|
177
177
|
try {
|
|
178
|
+
const orig = f;
|
|
178
179
|
f = await shrinkImage(f);
|
|
179
180
|
const url = URL.createObjectURL(f);
|
|
180
181
|
target.innerHTML = `<div class="review"><img src="${url}" style="max-width:100%;border-radius:10px"><p class="muted" id="ocr-live-photo">📷 Scanning barcode…</p></div>`;
|
|
181
182
|
const live = $('ocr-live-photo');
|
|
182
183
|
const barcode = await decodeBarcode(f);
|
|
183
|
-
let text, preset;
|
|
184
|
+
let text, preset, topText = null;
|
|
184
185
|
if (barcode) {
|
|
185
186
|
text = barcode;
|
|
186
187
|
if (live) live.textContent = '📷 Barcode read — reading sender…';
|
|
@@ -188,7 +189,7 @@ async function handlePhotoFile(f) {
|
|
|
188
189
|
fd.append('image', f);
|
|
189
190
|
const upPromise = fetchTimeout('/api/intake/image', { method: 'POST', body: fd }, 60000)
|
|
190
191
|
.catch(() => { throw new Error('upload timed out — check your connection and try again'); });
|
|
191
|
-
|
|
192
|
+
topText = await ocrTopStrip(orig, $('ocr-status'));
|
|
192
193
|
const up = await upPromise;
|
|
193
194
|
if (!up.ok) throw new Error('upload failed');
|
|
194
195
|
preset = topText ? { labelText: topText } : undefined;
|
|
@@ -204,6 +205,7 @@ async function handlePhotoFile(f) {
|
|
|
204
205
|
if (!up.ok) throw new Error('upload failed');
|
|
205
206
|
}
|
|
206
207
|
const r = await runQuick(text, $('photo-result'), preset);
|
|
208
|
+
if (looksLikeAppScreenshot(topText || text)) $('photo-result').insertAdjacentHTML('afterbegin', '<p class="warn">⚠️ This looks like a screenshot of the app itself — choose the label photo directly for best results.</p>');
|
|
207
209
|
if (barcode && r && r.added.length) $('photo-result').insertAdjacentHTML('afterbegin', '<p class="muted">📷 Read from the barcode — checksum verified.</p>');
|
|
208
210
|
if (r && !r.added.length && !r.needs_review.length && !r.duplicates.length) showOcrRead($('photo-result'), text);
|
|
209
211
|
loadPending();
|
|
@@ -274,17 +276,21 @@ function loadZxing() {
|
|
|
274
276
|
}
|
|
275
277
|
|
|
276
278
|
// OCR just the top strip of a label (sender block) for the seller name.
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
+
// Works from the ORIGINAL full-resolution image, upscaled — small text
|
|
280
|
+
// reads far better than on the shrunk copy. Returns text or null.
|
|
281
|
+
async function ocrTopStrip(origFile, statusEl) {
|
|
279
282
|
try {
|
|
280
|
-
const bmp = await createImageBitmap(
|
|
281
|
-
const sh = Math.max(60, Math.round(bmp.height * 0.
|
|
283
|
+
const bmp = await createImageBitmap(origFile);
|
|
284
|
+
const sw = bmp.width, sh = Math.max(60, Math.round(bmp.height * 0.35));
|
|
285
|
+
const targetW = 1400, scale = targetW / sw;
|
|
282
286
|
const canvas = document.createElement('canvas');
|
|
283
|
-
canvas.width =
|
|
284
|
-
canvas.height = sh;
|
|
285
|
-
canvas.getContext('2d')
|
|
287
|
+
canvas.width = targetW;
|
|
288
|
+
canvas.height = Math.max(60, Math.round(sh * scale));
|
|
289
|
+
const ctx = canvas.getContext('2d');
|
|
290
|
+
ctx.imageSmoothingEnabled = true;
|
|
291
|
+
ctx.drawImage(bmp, 0, 0, sw, sh, 0, 0, targetW, canvas.height);
|
|
286
292
|
bmp.close();
|
|
287
|
-
const blob = await new Promise((res) => canvas.toBlob(res, 'image/jpeg', 0.
|
|
293
|
+
const blob = await new Promise((res) => canvas.toBlob(res, 'image/jpeg', 0.92));
|
|
288
294
|
if (!blob) return null;
|
|
289
295
|
const p = ocrFile(new File([blob], 'top.jpg', { type: 'image/jpeg' }), statusEl);
|
|
290
296
|
return await Promise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error('top-strip timeout')), 25000))]);
|
|
@@ -342,10 +348,19 @@ async function decodeBarcode(file) {
|
|
|
342
348
|
} finally { bmp.close(); }
|
|
343
349
|
return null;
|
|
344
350
|
}
|
|
351
|
+
// Detect when the pasted image is a screenshot OF THE APP itself (its own
|
|
352
|
+
// header/buttons/tab bar get OCR'd) — the label inside is too small to read
|
|
353
|
+
// well, and UI words pollute the results.
|
|
354
|
+
function looksLikeAppScreenshot(text) {
|
|
355
|
+
const t = String(text || '').toLowerCase();
|
|
356
|
+
return /paste a screenshot|extract from text|finding tracking number|reading screenshot|mark received|open carrier tracking|hh shipments/.test(t);
|
|
357
|
+
}
|
|
358
|
+
|
|
345
359
|
async function handlePastedImageFile(f) {
|
|
346
360
|
const target = $('quick-result');
|
|
347
361
|
try {
|
|
348
362
|
// Instant feedback: show a thumbnail + progress the moment the image lands
|
|
363
|
+
const orig = f;
|
|
349
364
|
f = await shrinkImage(f);
|
|
350
365
|
const url = URL.createObjectURL(f);
|
|
351
366
|
target.innerHTML = `<div class="review"><img src="${url}" style="max-width:100%;border-radius:10px"><p class="muted" id="ocr-live">📷 Scanning barcode…</p></div>`;
|
|
@@ -356,17 +371,18 @@ async function handlePastedImageFile(f) {
|
|
|
356
371
|
if (barcode) {
|
|
357
372
|
text = barcode;
|
|
358
373
|
if (live) live.textContent = '📷 Barcode read — reading sender…';
|
|
359
|
-
// Number is verified; OCR
|
|
360
|
-
// name, in parallel with the upload.
|
|
374
|
+
// Number is verified; OCR the top strip of the ORIGINAL full-res
|
|
375
|
+
// image (sender block) for the name, in parallel with the upload.
|
|
361
376
|
const fd = new FormData();
|
|
362
377
|
fd.append('image', f);
|
|
363
378
|
const upPromise = fetchTimeout('/api/intake/image', { method: 'POST', body: fd }, 60000)
|
|
364
379
|
.catch(() => { throw new Error('upload timed out — check your connection and try again'); });
|
|
365
|
-
const topText = await ocrTopStrip(
|
|
380
|
+
const topText = await ocrTopStrip(orig, $('ocr-status-paste'));
|
|
366
381
|
const up = await upPromise;
|
|
367
382
|
if (!up.ok) throw new Error('upload failed');
|
|
368
383
|
if (live) live.textContent = '🔍 Finding tracking number…';
|
|
369
384
|
const r = await runQuick(text, target, topText ? { labelText: topText } : undefined);
|
|
385
|
+
if (looksLikeAppScreenshot(topText)) target.insertAdjacentHTML('afterbegin', '<p class="warn">⚠️ This looks like a screenshot of the app itself — paste the label photo directly for best results.</p>');
|
|
370
386
|
if (r && r.added.length) target.insertAdjacentHTML('afterbegin', '<p class="muted">📷 Read from the barcode — checksum verified.</p>');
|
|
371
387
|
if (r && !r.added.length && !r.needs_review.length && !r.duplicates.length) showOcrRead(target, text);
|
|
372
388
|
return;
|
|
@@ -383,6 +399,7 @@ async function handlePastedImageFile(f) {
|
|
|
383
399
|
if (!up.ok) throw new Error('upload failed');
|
|
384
400
|
if (live) live.textContent = '🔍 Finding tracking number…';
|
|
385
401
|
const r = await runQuick(text, target);
|
|
402
|
+
if (looksLikeAppScreenshot(text)) target.insertAdjacentHTML('afterbegin', '<p class="warn">⚠️ This looks like a screenshot of the app itself — paste the label photo directly for best results.</p>');
|
|
386
403
|
if (r && !r.added.length && !r.needs_review.length && !r.duplicates.length) showOcrRead(target, text);
|
|
387
404
|
} catch (err) { $('ocr-status-paste').textContent = ''; target.innerHTML = `<p class="error">${esc(err.message)}</p>`; }
|
|
388
405
|
}
|