browser-assistant 0.0.0-stage → 2.8.2

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.
@@ -0,0 +1,19 @@
1
+ // Popup health check script
2
+ const el = document.getElementById('server-status');
3
+ new Promise((resolve, reject) => {
4
+ chrome.runtime.sendMessage({ action: 'apiRequest', path: '/health', method: 'GET' }, (response) => {
5
+ if (chrome.runtime.lastError || !response || response.error || !response.ok) {
6
+ reject(new Error(response?.error || 'Server unavailable'));
7
+ return;
8
+ }
9
+ resolve(response.data);
10
+ });
11
+ })
12
+ .then(d => {
13
+ el.textContent = d.groq_configured ? 'online (Groq ✓)' : 'online (no Groq key)';
14
+ el.className = 'online';
15
+ })
16
+ .catch(() => {
17
+ el.textContent = 'offline';
18
+ el.className = 'offline';
19
+ });
@@ -0,0 +1,82 @@
1
+ // Read complete question illustrations, including images outside the viewport.
2
+ // Never substitute a partial screenshot or a text-only guess for a failed read.
3
+ (function () {
4
+ function nodes(question) {
5
+ const found = new Set();
6
+ for (const region of [question.visualRegion, ...question.options.map(option => option.node)]) {
7
+ if (!region)
8
+ continue;
9
+ const candidates = region.matches('img,svg,canvas') ? [region] : region.querySelectorAll('img,svg,canvas');
10
+ for (const node of candidates) {
11
+ if (node.closest('#assistant-root,nav,aside,button') || !node.getClientRects().length)
12
+ continue;
13
+ if (node.parentElement?.closest('svg'))
14
+ continue;
15
+ const width = node.naturalWidth || node.width?.baseVal?.value || Number(node.getAttribute('width'));
16
+ const height = node.naturalHeight || node.height?.baseVal?.value || Number(node.getAttribute('height'));
17
+ if (width > 0 && height > 0 && width <= 32 && height <= 32)
18
+ continue;
19
+ found.add(node);
20
+ }
21
+ }
22
+ return [...found];
23
+ }
24
+ function source(node) {
25
+ if (node.tagName.toLowerCase() === 'canvas')
26
+ return node.toDataURL('image/png');
27
+ if (node.tagName.toLowerCase() === 'svg')
28
+ return new XMLSerializer().serializeToString(node);
29
+ return node.currentSrc || node.src || node.getAttribute('src') || '';
30
+ }
31
+ function fingerprint(question) { return JSON.stringify(nodes(question).map(source)); }
32
+ async function capture(question, sendMessage) {
33
+ const visuals = nodes(question);
34
+ if (visuals.length > 5)
35
+ throw new Error('Question has more than five illustrations; review them manually.');
36
+ const before = fingerprint(question);
37
+ const images = [];
38
+ for (const node of visuals) {
39
+ let data = source(node);
40
+ if (!data)
41
+ throw new Error('Question illustration has not loaded. No answer was selected.');
42
+ if (node.tagName.toLowerCase() === 'svg')
43
+ data = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(data)}`;
44
+ else if (!data.startsWith('data:')) {
45
+ const response = await sendMessage({ action: 'questionImage', url: new URL(data, location.href).href });
46
+ if (!response?.dataUrl)
47
+ throw new Error(response?.error || 'Question illustration could not be read.');
48
+ data = response.dataUrl;
49
+ }
50
+ const picture = await new Promise((resolve, reject) => {
51
+ const img = new Image();
52
+ const timer = window.setTimeout(() => { img.src = ''; reject(new Error('Question illustration loading timed out.')); }, 10000);
53
+ img.onload = () => { window.clearTimeout(timer); resolve(img); };
54
+ img.onerror = () => { window.clearTimeout(timer); reject(new Error('Question illustration could not be decoded.')); };
55
+ img.src = data;
56
+ });
57
+ const canvas = document.createElement('canvas');
58
+ const scale = Math.min(1, 2400 / Math.max(picture.naturalWidth, picture.naturalHeight));
59
+ canvas.width = Math.max(1, Math.round(picture.naturalWidth * scale));
60
+ canvas.height = Math.max(1, Math.round(picture.naturalHeight * scale));
61
+ const context = canvas.getContext('2d');
62
+ context.fillStyle = 'white';
63
+ context.fillRect(0, 0, canvas.width, canvas.height);
64
+ context.drawImage(picture, 0, 0, canvas.width, canvas.height);
65
+ // Keep chart labels and code crisp; the backend compresses once.
66
+ images.push(canvas.toDataURL('image/png'));
67
+ }
68
+ if (before !== fingerprint(question))
69
+ throw new Error('Question illustration changed while loading. Please restart.');
70
+ return { images, fingerprint: before };
71
+ }
72
+ async function requireBackend(apiRequest, imageCount = 1) {
73
+ const health = await apiRequest('/health', 'GET');
74
+ if (!health?.ok || health.data?.vision_question_context !== true) {
75
+ throw new Error('Restart the Browser Assistant server to enable chart questions (build 2.6.50). No answer was selected.');
76
+ }
77
+ if (imageCount > 3 && health.data.vision_batching !== true) {
78
+ throw new Error('Update and restart the Browser Assistant backend to enable all five images. No partial image request was sent.');
79
+ }
80
+ }
81
+ window.BrowserAssistantQuestionVisuals = Object.freeze({ nodes, fingerprint, capture, requireBackend });
82
+ })();