Spaces:
Sleeping
Sleeping
File size: 4,184 Bytes
0533780 | 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 | // background.js β Service worker
// Handles: tab screenshot, image crop, OCR API call, result relay
const OCR_ENDPOINT = "http://localhost:8000/ocr";
const OCR_MODE = "recognize"; // or "parse"
// ββ Listen for messages from content.js βββββββββββββββββββββββββββββββββββββ
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.type === "CAPTURE_REGION") {
handleCapture(msg.rect, sender.tab)
.then(result => sendResponse({ success: true, ...result }))
.catch(error => sendResponse({ success: false, error: error.message }));
return true; // keep channel open for async
}
if (msg.type === "PING") {
checkServer().then(ok => sendResponse({ ok }));
return true;
}
if (msg.type === "OPEN_SIDEBAR") {
// Open the sidebar as a side panel in the current tab
chrome.tabs.sendMessage(sender.tab.id, { type: "SHOW_SIDEBAR" });
return false;
}
});
// ββ Capture + crop + OCR βββββββββββββββββββββββββββββββββββββββββββββββββββββ
async function handleCapture(rect, tab) {
// 1. Capture the entire visible tab as a data URL
const dataUrl = await chrome.tabs.captureVisibleTab(tab.windowId, {
format: "png",
quality: 100,
});
// 2. Crop to the selected rect using OffscreenCanvas
const croppedBlob = await cropImage(dataUrl, rect);
// 3. Send to GLM-OCR backend
const formData = new FormData();
formData.append("file", croppedBlob, "selection.png");
formData.append("mode", OCR_MODE);
const res = await fetch(OCR_ENDPOINT, {
method: "POST",
body: formData,
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.detail || `Server returned ${res.status}`);
}
const data = await res.json();
// Also store the cropped image as a data URL for display in the sidebar
const croppedDataUrl = await blobToDataUrl(croppedBlob);
return {
text: data.text,
word_count: data.word_count,
char_count: data.char_count,
latency_ms: data.latency_ms,
mode: data.mode,
device: data.device,
imageDataUrl: croppedDataUrl,
timestamp: new Date().toISOString(),
};
}
// ββ Image cropping using OffscreenCanvas βββββββββββββββββββββββββββββββββββββ
async function cropImage(dataUrl, rect) {
// Decode the full screenshot
const res = await fetch(dataUrl);
const blob = await res.blob();
const bitmap = await createImageBitmap(blob);
// Scale rect by device pixel ratio (already baked into captureVisibleTab)
// captureVisibleTab captures at device pixel ratio already, so rect coords
// from getBoundingClientRect need to be scaled.
const dpr = rect.dpr || 1;
const sx = Math.round(rect.x * dpr);
const sy = Math.round(rect.y * dpr);
const sw = Math.round(rect.width * dpr);
const sh = Math.round(rect.height * dpr);
// Clamp to bitmap bounds
const cx = Math.max(0, sx);
const cy = Math.max(0, sy);
const cw = Math.min(sw, bitmap.width - cx);
const ch = Math.min(sh, bitmap.height - cy);
const canvas = new OffscreenCanvas(cw, ch);
const ctx = canvas.getContext("2d");
ctx.drawImage(bitmap, cx, cy, cw, ch, 0, 0, cw, ch);
return canvas.convertToBlob({ type: "image/png" });
}
function blobToDataUrl(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
// ββ Server health check βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
async function checkServer() {
try {
const r = await fetch("http://localhost:8000/health", { signal: AbortSignal.timeout(3000) });
const d = await r.json();
return d.status === "ok";
} catch {
return false;
}
}
|