|
| 1 | +class ImageClassifierContent { |
| 2 | + constructor() { |
| 3 | + this.listenForMessages(); |
| 4 | + } |
| 5 | + |
| 6 | + listenForMessages() { |
| 7 | + chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { |
| 8 | + if (request.action === 'classify-image' && request.imageUrl) { |
| 9 | + try { |
| 10 | + this.showNotification('Classifying image...', 'info'); |
| 11 | + // Fetch the image as a blob |
| 12 | + const response = await fetch(request.imageUrl); |
| 13 | + const blob = await response.blob(); |
| 14 | + const img = await createImageBitmap(blob); |
| 15 | + |
| 16 | + // Preprocess image to [1, 3, 224, 224] Float32Array |
| 17 | + const floatData = await this.preprocessImage(img); |
| 18 | + // Send tensor data to background for inference |
| 19 | + chrome.runtime.sendMessage({ |
| 20 | + action: 'run-inference', |
| 21 | + tensorData: Array.from(floatData), |
| 22 | + tensorShape: [1, 3, 224, 224] |
| 23 | + }); |
| 24 | + } catch (e) { |
| 25 | + this.showNotification('Error preparing image: ' + e, 'error'); |
| 26 | + } |
| 27 | + } else if (request.action === 'inference-result') { |
| 28 | + if (request.error) { |
| 29 | + this.showNotification('Inference error: ' + request.error, 'error'); |
| 30 | + } else { |
| 31 | + // Expecting result: { label: string, confidence: number } |
| 32 | + const result = request.result; |
| 33 | + if (result && result.label && typeof result.confidence === 'number') { |
| 34 | + this.showNotification(`This appears to be: ${result.label} (${(result.confidence * 100).toFixed(2)}% confidence)`, 'success'); |
| 35 | + } else { |
| 36 | + this.showNotification('Classification result: ' + JSON.stringify(result), 'success'); |
| 37 | + } |
| 38 | + } |
| 39 | + } |
| 40 | + }); |
| 41 | + } |
| 42 | + |
| 43 | + async preprocessImage(img) { |
| 44 | + const canvas = new OffscreenCanvas(224, 224); |
| 45 | + const ctx = canvas.getContext('2d'); |
| 46 | + ctx.drawImage(img, 0, 0, 224, 224); |
| 47 | + const imageData = ctx.getImageData(0, 0, 224, 224).data; |
| 48 | + const floatData = new Float32Array(1 * 3 * 224 * 224); |
| 49 | + for (let i = 0; i < 224 * 224; i++) { |
| 50 | + floatData[i] = imageData[i * 4] / 255.0; // R |
| 51 | + floatData[i + 224 * 224] = imageData[i * 4 + 1] / 255.0; // G |
| 52 | + floatData[i + 2 * 224 * 224] = imageData[i * 4 + 2] / 255.0; // B |
| 53 | + } |
| 54 | + return floatData; |
| 55 | + } |
| 56 | + |
| 57 | + showNotification(message, type) { |
| 58 | + const old = document.getElementById('image-classifier-notification'); |
| 59 | + if (old) old.remove(); |
| 60 | + const div = document.createElement('div'); |
| 61 | + div.id = 'image-classifier-notification'; |
| 62 | + div.textContent = message; |
| 63 | + div.style.position = 'fixed'; |
| 64 | + div.style.top = '32px'; |
| 65 | + div.style.right = '32px'; |
| 66 | + div.style.zIndex = 99999; |
| 67 | + if (type === 'success') { |
| 68 | + div.style.background = '#27ae60'; |
| 69 | + } else if (type === 'error') { |
| 70 | + div.style.background = '#ff4d4f'; |
| 71 | + } else { |
| 72 | + div.style.background = '#222'; |
| 73 | + } |
| 74 | + div.style.color = '#fff'; |
| 75 | + div.style.padding = '16px 24px'; |
| 76 | + div.style.borderRadius = '8px'; |
| 77 | + div.style.boxShadow = '0 2px 12px rgba(0,0,0,0.2)'; |
| 78 | + div.style.fontSize = '16px'; |
| 79 | + div.style.fontFamily = 'sans-serif'; |
| 80 | + div.style.cursor = 'pointer'; |
| 81 | + div.style.transition = 'opacity 0.3s'; |
| 82 | + div.onclick = () => div.remove(); |
| 83 | + document.body.appendChild(div); |
| 84 | + setTimeout(() => { |
| 85 | + div.style.opacity = '0'; |
| 86 | + setTimeout(() => div.remove(), 300); |
| 87 | + }, 4000); |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +new ImageClassifierContent(); |
0 commit comments