bloby-bot 0.22.2 → 0.22.6
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/supervisor/chat/src/components/Chat/BlobyImageCard.tsx +63 -0
- package/supervisor/chat/src/components/Chat/ImageLightbox.tsx +33 -8
- package/supervisor/chat/src/components/Chat/MessageBubble.tsx +48 -30
- package/supervisor/index.ts +2 -4
- package/workspace/skills/chrome-extension/.claude-plugin/plugin.json +0 -6
- package/workspace/skills/chrome-extension/SKILL.md +0 -181
- package/workspace/skills/chrome-extension/background.js +0 -182
- package/workspace/skills/chrome-extension/content-script.js +0 -309
- package/workspace/skills/chrome-extension/content-style.css +0 -86
- package/workspace/skills/chrome-extension/icons/icon-128.png +0 -0
- package/workspace/skills/chrome-extension/icons/icon-16.png +0 -0
- package/workspace/skills/chrome-extension/icons/icon-48.png +0 -0
- package/workspace/skills/chrome-extension/manifest.json +0 -47
- package/workspace/skills/chrome-extension/panel/panel.html +0 -36
- package/workspace/skills/chrome-extension/panel/panel.js +0 -123
- package/workspace/skills/chrome-extension/popup/popup.css +0 -124
- package/workspace/skills/chrome-extension/popup/popup.html +0 -47
- package/workspace/skills/chrome-extension/popup/popup.js +0 -115
- package/workspace/skills/chrome-extension/skill.json +0 -15
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bloby Chrome Extension — Content Script
|
|
3
|
-
*
|
|
4
|
-
* Injected into every page. Responsibilities:
|
|
5
|
-
* - Renders the Bloby bubble (bottom-right corner)
|
|
6
|
-
* - Opens/closes the chat panel (iframe to extension's panel.html)
|
|
7
|
-
* - Extracts page context (URL, title, selection, structured data)
|
|
8
|
-
* - Handles ExtensionAction commands from the agent (screenshot, fill form, type)
|
|
9
|
-
* - Bridges messages between the panel iframe and the background worker
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
(function () {
|
|
13
|
-
// Guard: don't inject on extension pages or Bloby's own domain
|
|
14
|
-
if (document.getElementById('bloby-ext-bubble')) return;
|
|
15
|
-
if (location.hostname.endsWith('bloby.bot')) return;
|
|
16
|
-
|
|
17
|
-
let panelOpen = false;
|
|
18
|
-
let paired = false;
|
|
19
|
-
|
|
20
|
-
// ── Check if paired ────────────────────────────────────────────────────────
|
|
21
|
-
|
|
22
|
-
chrome.runtime.sendMessage({ type: 'bloby:get-state' }, (state) => {
|
|
23
|
-
if (chrome.runtime.lastError) return;
|
|
24
|
-
if (state?.paired) {
|
|
25
|
-
paired = true;
|
|
26
|
-
createBubble();
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// Listen for pairing from background worker
|
|
31
|
-
chrome.runtime.onMessage.addListener((msg) => {
|
|
32
|
-
if (msg.type === 'bloby:paired') {
|
|
33
|
-
console.log('[bloby-ext] Paired! Showing bubble.');
|
|
34
|
-
paired = true;
|
|
35
|
-
if (!document.getElementById('bloby-ext-bubble')) createBubble();
|
|
36
|
-
}
|
|
37
|
-
if (msg.type === 'bloby:unpaired') {
|
|
38
|
-
paired = false;
|
|
39
|
-
removeBubble();
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// Listen for pairing changes via storage
|
|
44
|
-
chrome.storage.onChanged.addListener((changes) => {
|
|
45
|
-
if (changes.serverUrl) {
|
|
46
|
-
if (changes.serverUrl.newValue) {
|
|
47
|
-
paired = true;
|
|
48
|
-
if (!document.getElementById('bloby-ext-bubble')) createBubble();
|
|
49
|
-
} else {
|
|
50
|
-
paired = false;
|
|
51
|
-
removeBubble();
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// ── Bubble ─────────────────────────────────────────────────────────────────
|
|
57
|
-
|
|
58
|
-
function createBubble() {
|
|
59
|
-
const bubble = document.createElement('div');
|
|
60
|
-
bubble.id = 'bloby-ext-bubble';
|
|
61
|
-
bubble.title = 'Chat with Bloby';
|
|
62
|
-
bubble.addEventListener('click', togglePanel);
|
|
63
|
-
document.body.appendChild(bubble);
|
|
64
|
-
|
|
65
|
-
const dot = document.createElement('div');
|
|
66
|
-
dot.className = 'bloby-ext-bubble-dot';
|
|
67
|
-
bubble.appendChild(dot);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function removeBubble() {
|
|
71
|
-
document.getElementById('bloby-ext-bubble')?.remove();
|
|
72
|
-
document.getElementById('bloby-ext-panel')?.remove();
|
|
73
|
-
document.getElementById('bloby-ext-backdrop')?.remove();
|
|
74
|
-
panelOpen = false;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ── Panel ──────────────────────────────────────────────────────────────────
|
|
78
|
-
|
|
79
|
-
function togglePanel() {
|
|
80
|
-
panelOpen ? closePanel() : openPanel();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function openPanel() {
|
|
84
|
-
if (document.getElementById('bloby-ext-panel')) {
|
|
85
|
-
document.getElementById('bloby-ext-panel').classList.add('bloby-ext-panel-open');
|
|
86
|
-
panelOpen = true;
|
|
87
|
-
showBackdrop();
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
showBackdrop();
|
|
92
|
-
|
|
93
|
-
const panel = document.createElement('div');
|
|
94
|
-
panel.id = 'bloby-ext-panel';
|
|
95
|
-
|
|
96
|
-
const iframe = document.createElement('iframe');
|
|
97
|
-
iframe.id = 'bloby-ext-iframe';
|
|
98
|
-
iframe.src = chrome.runtime.getURL('panel/panel.html');
|
|
99
|
-
iframe.allow = 'microphone';
|
|
100
|
-
panel.appendChild(iframe);
|
|
101
|
-
|
|
102
|
-
document.body.appendChild(panel);
|
|
103
|
-
|
|
104
|
-
requestAnimationFrame(() => {
|
|
105
|
-
panel.classList.add('bloby-ext-panel-open');
|
|
106
|
-
panelOpen = true;
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
window.addEventListener('message', handlePanelMessage);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function closePanel() {
|
|
113
|
-
const panel = document.getElementById('bloby-ext-panel');
|
|
114
|
-
if (panel) {
|
|
115
|
-
panel.classList.remove('bloby-ext-panel-open');
|
|
116
|
-
panelOpen = false;
|
|
117
|
-
hideBackdrop();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function showBackdrop() {
|
|
122
|
-
let backdrop = document.getElementById('bloby-ext-backdrop');
|
|
123
|
-
if (!backdrop) {
|
|
124
|
-
backdrop = document.createElement('div');
|
|
125
|
-
backdrop.id = 'bloby-ext-backdrop';
|
|
126
|
-
backdrop.addEventListener('click', closePanel);
|
|
127
|
-
document.body.appendChild(backdrop);
|
|
128
|
-
}
|
|
129
|
-
requestAnimationFrame(() => backdrop.classList.add('bloby-ext-backdrop-visible'));
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function hideBackdrop() {
|
|
133
|
-
const backdrop = document.getElementById('bloby-ext-backdrop');
|
|
134
|
-
if (backdrop) backdrop.classList.remove('bloby-ext-backdrop-visible');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// ── Message Bridge (panel ↔ content script) ────────────────────────────────
|
|
138
|
-
|
|
139
|
-
function handlePanelMessage(event) {
|
|
140
|
-
const iframe = document.getElementById('bloby-ext-iframe');
|
|
141
|
-
if (!iframe || event.source !== iframe.contentWindow) return;
|
|
142
|
-
|
|
143
|
-
const msg = event.data;
|
|
144
|
-
if (!msg?.type) return;
|
|
145
|
-
|
|
146
|
-
if (msg.type === 'bloby:close') {
|
|
147
|
-
closePanel();
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Panel requests page context
|
|
152
|
-
if (msg.type === 'bloby:get-page-context') {
|
|
153
|
-
const context = getPageContext();
|
|
154
|
-
iframe.contentWindow.postMessage({ type: 'bloby:page-context', context }, '*');
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Agent requests an action on the page
|
|
159
|
-
if (msg.type === 'bloby:extension-action') {
|
|
160
|
-
handleExtensionAction(msg.action, msg.data);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// ── Page Context Extraction ────────────────────────────────────────────────
|
|
166
|
-
|
|
167
|
-
function getPageContext() {
|
|
168
|
-
const context = {
|
|
169
|
-
url: location.href,
|
|
170
|
-
title: document.title,
|
|
171
|
-
selection: window.getSelection()?.toString()?.slice(0, 2000) || '',
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
// Extract structured data (JSON-LD) — product info, articles, etc.
|
|
175
|
-
try {
|
|
176
|
-
const scripts = document.querySelectorAll('script[type="application/ld+json"]');
|
|
177
|
-
for (const script of scripts) {
|
|
178
|
-
const data = JSON.parse(script.textContent);
|
|
179
|
-
// Handle arrays (some sites wrap in array)
|
|
180
|
-
const items = Array.isArray(data) ? data : [data];
|
|
181
|
-
for (const item of items) {
|
|
182
|
-
if (item.name && !context.productName) context.productName = item.name;
|
|
183
|
-
if (item.offers?.price) context.price = item.offers.price;
|
|
184
|
-
if (item.offers?.priceCurrency) context.currency = item.offers.priceCurrency;
|
|
185
|
-
if (item.description && !context.description) context.description = item.description?.slice(0, 500);
|
|
186
|
-
if (item['@type'] === 'VideoObject' && item.name) context.videoTitle = item.name;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
} catch { /* ignore */ }
|
|
190
|
-
|
|
191
|
-
// Meta description fallback
|
|
192
|
-
if (!context.description) {
|
|
193
|
-
const meta = document.querySelector('meta[name="description"]');
|
|
194
|
-
if (meta) context.description = meta.content?.slice(0, 500);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Detect forms on the page
|
|
198
|
-
const forms = document.querySelectorAll('form');
|
|
199
|
-
if (forms.length > 0) {
|
|
200
|
-
context.hasForms = true;
|
|
201
|
-
context.formCount = forms.length;
|
|
202
|
-
// Extract visible form field labels/names
|
|
203
|
-
const fields = [];
|
|
204
|
-
forms.forEach((form) => {
|
|
205
|
-
form.querySelectorAll('input, select, textarea').forEach((el) => {
|
|
206
|
-
const name = el.getAttribute('name') || el.getAttribute('id') || el.getAttribute('placeholder') || '';
|
|
207
|
-
const type = el.getAttribute('type') || el.tagName.toLowerCase();
|
|
208
|
-
if (name && type !== 'hidden' && type !== 'submit') {
|
|
209
|
-
fields.push({ name, type });
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
if (fields.length > 0) context.formFields = fields.slice(0, 20); // cap at 20
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
console.log('[bloby-ext] Page context:', context);
|
|
217
|
-
return context;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// ── Extension Actions (agent → page) ───────────────────────────────────────
|
|
221
|
-
|
|
222
|
-
function handleExtensionAction(action, data) {
|
|
223
|
-
console.log(`[bloby-ext] Action: ${action}`, data);
|
|
224
|
-
const iframe = document.getElementById('bloby-ext-iframe');
|
|
225
|
-
|
|
226
|
-
switch (action) {
|
|
227
|
-
case 'screenshot': {
|
|
228
|
-
// Request screenshot via background worker (needs chrome.tabs API)
|
|
229
|
-
chrome.runtime.sendMessage({ type: 'bloby:screenshot' }, (response) => {
|
|
230
|
-
if (response?.dataUrl && iframe?.contentWindow) {
|
|
231
|
-
iframe.contentWindow.postMessage({
|
|
232
|
-
type: 'bloby:action-result',
|
|
233
|
-
action: 'screenshot',
|
|
234
|
-
result: response.dataUrl,
|
|
235
|
-
}, '*');
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
break;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
case 'read-page': {
|
|
242
|
-
// Extract main text content from the page
|
|
243
|
-
const text = document.body.innerText?.slice(0, 10000) || '';
|
|
244
|
-
if (iframe?.contentWindow) {
|
|
245
|
-
iframe.contentWindow.postMessage({
|
|
246
|
-
type: 'bloby:action-result',
|
|
247
|
-
action: 'read-page',
|
|
248
|
-
result: text,
|
|
249
|
-
}, '*');
|
|
250
|
-
}
|
|
251
|
-
break;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
case 'fill-form': {
|
|
255
|
-
// Fill form fields from agent-provided data
|
|
256
|
-
if (data?.fields && typeof data.fields === 'object') {
|
|
257
|
-
for (const [selector, value] of Object.entries(data.fields)) {
|
|
258
|
-
const el = document.querySelector(selector) ||
|
|
259
|
-
document.querySelector(`[name="${selector}"]`) ||
|
|
260
|
-
document.querySelector(`[id="${selector}"]`);
|
|
261
|
-
if (el) {
|
|
262
|
-
el.value = value;
|
|
263
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
264
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
if (iframe?.contentWindow) {
|
|
268
|
-
iframe.contentWindow.postMessage({
|
|
269
|
-
type: 'bloby:action-result',
|
|
270
|
-
action: 'fill-form',
|
|
271
|
-
result: 'done',
|
|
272
|
-
}, '*');
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
break;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
case 'click': {
|
|
279
|
-
// Click an element by selector
|
|
280
|
-
if (data?.selector) {
|
|
281
|
-
const el = document.querySelector(data.selector);
|
|
282
|
-
if (el) el.click();
|
|
283
|
-
}
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
case 'type': {
|
|
288
|
-
// Type into the currently focused input or a targeted selector
|
|
289
|
-
const target = data?.selector
|
|
290
|
-
? document.querySelector(data.selector)
|
|
291
|
-
: document.activeElement;
|
|
292
|
-
if (target && data?.text) {
|
|
293
|
-
target.value = data.text;
|
|
294
|
-
target.dispatchEvent(new Event('input', { bubbles: true }));
|
|
295
|
-
}
|
|
296
|
-
break;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
default:
|
|
300
|
-
console.warn(`[bloby-ext] Unknown action: ${action}`);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// ── Keyboard shortcut ──────────────────────────────────────────────────────
|
|
305
|
-
|
|
306
|
-
document.addEventListener('keydown', (e) => {
|
|
307
|
-
if (e.key === 'Escape' && panelOpen) closePanel();
|
|
308
|
-
});
|
|
309
|
-
})();
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/* Bloby Chrome Extension — Bubble & Panel Styles */
|
|
2
|
-
|
|
3
|
-
#bloby-ext-bubble {
|
|
4
|
-
position: fixed;
|
|
5
|
-
bottom: 24px;
|
|
6
|
-
right: 24px;
|
|
7
|
-
width: 60px;
|
|
8
|
-
height: 60px;
|
|
9
|
-
border-radius: 50%;
|
|
10
|
-
background: linear-gradient(135deg, #04D1FE, #AF27E3, #FB4072);
|
|
11
|
-
cursor: pointer;
|
|
12
|
-
z-index: 2147483646;
|
|
13
|
-
display: flex;
|
|
14
|
-
align-items: center;
|
|
15
|
-
justify-content: center;
|
|
16
|
-
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
|
17
|
-
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
#bloby-ext-bubble:hover {
|
|
21
|
-
transform: scale(1.08);
|
|
22
|
-
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
#bloby-ext-bubble:active {
|
|
26
|
-
transform: scale(0.95);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.bloby-ext-bubble-dot {
|
|
30
|
-
width: 24px;
|
|
31
|
-
height: 24px;
|
|
32
|
-
border-radius: 50%;
|
|
33
|
-
background: rgba(255, 255, 255, 0.9);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/* Panel */
|
|
37
|
-
#bloby-ext-panel {
|
|
38
|
-
position: fixed;
|
|
39
|
-
top: 0;
|
|
40
|
-
right: 0;
|
|
41
|
-
bottom: 0;
|
|
42
|
-
width: 420px;
|
|
43
|
-
z-index: 2147483647;
|
|
44
|
-
transform: translateX(100%);
|
|
45
|
-
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
46
|
-
box-shadow: -4px 0 24px rgba(0, 0, 0, 0.3);
|
|
47
|
-
border-left: 1px solid #3a3a3a;
|
|
48
|
-
background: #212121;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
#bloby-ext-panel.bloby-ext-panel-open {
|
|
52
|
-
transform: translateX(0);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/* Mobile: full width */
|
|
56
|
-
@media (max-width: 480px) {
|
|
57
|
-
#bloby-ext-panel {
|
|
58
|
-
width: 100vw;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
#bloby-ext-iframe {
|
|
63
|
-
width: 100%;
|
|
64
|
-
height: 100%;
|
|
65
|
-
border: none;
|
|
66
|
-
background: #212121;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/* Backdrop */
|
|
70
|
-
#bloby-ext-backdrop {
|
|
71
|
-
position: fixed;
|
|
72
|
-
top: 0;
|
|
73
|
-
left: 0;
|
|
74
|
-
right: 0;
|
|
75
|
-
bottom: 0;
|
|
76
|
-
background: rgba(0, 0, 0, 0.4);
|
|
77
|
-
z-index: 2147483645;
|
|
78
|
-
opacity: 0;
|
|
79
|
-
pointer-events: none;
|
|
80
|
-
transition: opacity 0.25s ease;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
#bloby-ext-backdrop.bloby-ext-backdrop-visible {
|
|
84
|
-
opacity: 1;
|
|
85
|
-
pointer-events: auto;
|
|
86
|
-
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"manifest_version": 3,
|
|
3
|
-
"name": "Bloby",
|
|
4
|
-
"version": "0.1.0",
|
|
5
|
-
"description": "Your AI agent, on every page. Chat with Bloby from anywhere.",
|
|
6
|
-
"permissions": [
|
|
7
|
-
"storage",
|
|
8
|
-
"activeTab",
|
|
9
|
-
"declarativeNetRequest"
|
|
10
|
-
],
|
|
11
|
-
"host_permissions": [
|
|
12
|
-
"https://*.bloby.bot/*",
|
|
13
|
-
"https://*.my.bloby.bot/*",
|
|
14
|
-
"https://*.trycloudflare.com/*"
|
|
15
|
-
],
|
|
16
|
-
"action": {
|
|
17
|
-
"default_popup": "popup/popup.html",
|
|
18
|
-
"default_icon": {
|
|
19
|
-
"16": "icons/icon-16.png",
|
|
20
|
-
"48": "icons/icon-48.png",
|
|
21
|
-
"128": "icons/icon-128.png"
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
"background": {
|
|
25
|
-
"service_worker": "background.js",
|
|
26
|
-
"type": "module"
|
|
27
|
-
},
|
|
28
|
-
"content_scripts": [
|
|
29
|
-
{
|
|
30
|
-
"matches": ["<all_urls>"],
|
|
31
|
-
"js": ["content-script.js"],
|
|
32
|
-
"css": ["content-style.css"],
|
|
33
|
-
"run_at": "document_idle"
|
|
34
|
-
}
|
|
35
|
-
],
|
|
36
|
-
"web_accessible_resources": [
|
|
37
|
-
{
|
|
38
|
-
"resources": ["panel/panel.html", "panel/panel.js", "assets/*", "icons/*"],
|
|
39
|
-
"matches": ["<all_urls>"]
|
|
40
|
-
}
|
|
41
|
-
],
|
|
42
|
-
"icons": {
|
|
43
|
-
"16": "icons/icon-16.png",
|
|
44
|
-
"48": "icons/icon-48.png",
|
|
45
|
-
"128": "icons/icon-128.png"
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
6
|
-
<title>Bloby</title>
|
|
7
|
-
<style>
|
|
8
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
-
body { background: #212121; width: 100%; height: 100vh; overflow: hidden; }
|
|
10
|
-
iframe { width: 100%; height: 100%; border: none; }
|
|
11
|
-
.loading {
|
|
12
|
-
display: flex; align-items: center; justify-content: center;
|
|
13
|
-
height: 100vh; color: #a1a1aa; font-family: system-ui, sans-serif;
|
|
14
|
-
font-size: 14px; flex-direction: column; gap: 8px;
|
|
15
|
-
}
|
|
16
|
-
.loading .dots span {
|
|
17
|
-
display: inline-block; width: 6px; height: 6px; border-radius: 50%;
|
|
18
|
-
background: #a1a1aa; margin: 0 3px; animation: bounce 1s infinite;
|
|
19
|
-
}
|
|
20
|
-
.loading .dots span:nth-child(2) { animation-delay: 0.15s; }
|
|
21
|
-
.loading .dots span:nth-child(3) { animation-delay: 0.3s; }
|
|
22
|
-
@keyframes bounce {
|
|
23
|
-
0%, 80%, 100% { transform: scale(0); }
|
|
24
|
-
40% { transform: scale(1); }
|
|
25
|
-
}
|
|
26
|
-
.debug { font-size: 11px; color: #52525b; max-width: 90%; word-break: break-all; }
|
|
27
|
-
</style>
|
|
28
|
-
</head>
|
|
29
|
-
<body>
|
|
30
|
-
<div class="loading" id="loading">
|
|
31
|
-
<div class="dots"><span></span><span></span><span></span></div>
|
|
32
|
-
<div class="debug" id="debug"></div>
|
|
33
|
-
</div>
|
|
34
|
-
<script src="panel.js"></script>
|
|
35
|
-
</body>
|
|
36
|
-
</html>
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Panel page — loads the Bloby chat in an iframe and bridges page context.
|
|
3
|
-
*
|
|
4
|
-
* Architecture:
|
|
5
|
-
* Content script (host page) ↔ Panel (this page) ↔ Chat iframe (Bloby server)
|
|
6
|
-
*
|
|
7
|
-
* The panel:
|
|
8
|
-
* 1. Loads the Bloby chat in an iframe
|
|
9
|
-
* 2. Requests page context from the content script
|
|
10
|
-
* 3. Injects context into outgoing chat messages
|
|
11
|
-
* 4. Forwards ExtensionAction commands from chat to content script
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const debug = document.getElementById('debug');
|
|
15
|
-
function log(msg) {
|
|
16
|
-
console.log('[bloby-panel] ' + msg);
|
|
17
|
-
if (debug) debug.textContent = msg;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
let chatIframe = null;
|
|
21
|
-
let currentPageContext = null;
|
|
22
|
-
|
|
23
|
-
(async () => {
|
|
24
|
-
try {
|
|
25
|
-
log('Reading config...');
|
|
26
|
-
const data = await chrome.storage.local.get(['serverUrl', 'authToken']);
|
|
27
|
-
|
|
28
|
-
if (!data.serverUrl) {
|
|
29
|
-
document.getElementById('loading').innerHTML =
|
|
30
|
-
'<span style="color:#ef4444">Not paired. Click the Bloby icon in the toolbar to set up.</span>';
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
let chatUrl = data.serverUrl + '/bloby/';
|
|
35
|
-
if (data.authToken) {
|
|
36
|
-
chatUrl += '?token=' + data.authToken;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Add a marker so the chat app knows it's inside the extension
|
|
40
|
-
chatUrl += (chatUrl.includes('?') ? '&' : '?') + 'ext=1';
|
|
41
|
-
|
|
42
|
-
log('Loading: ' + chatUrl);
|
|
43
|
-
|
|
44
|
-
chatIframe = document.createElement('iframe');
|
|
45
|
-
chatIframe.id = 'bloby-chat-iframe';
|
|
46
|
-
chatIframe.src = chatUrl;
|
|
47
|
-
chatIframe.allow = 'microphone';
|
|
48
|
-
chatIframe.style.cssText = 'width:100%;height:100%;border:none;';
|
|
49
|
-
|
|
50
|
-
chatIframe.onload = () => {
|
|
51
|
-
log('Chat loaded!');
|
|
52
|
-
document.getElementById('loading').style.display = 'none';
|
|
53
|
-
// Request initial page context from content script
|
|
54
|
-
requestPageContext();
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
document.body.appendChild(chatIframe);
|
|
58
|
-
|
|
59
|
-
setTimeout(() => {
|
|
60
|
-
if (document.getElementById('loading').style.display !== 'none') {
|
|
61
|
-
log('Timeout loading chat');
|
|
62
|
-
}
|
|
63
|
-
}, 15000);
|
|
64
|
-
|
|
65
|
-
} catch (err) {
|
|
66
|
-
log('Error: ' + err.message);
|
|
67
|
-
}
|
|
68
|
-
})();
|
|
69
|
-
|
|
70
|
-
// ── Page Context ───────────────────────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
function requestPageContext() {
|
|
73
|
-
// Ask the content script (parent of this panel) for page context
|
|
74
|
-
window.parent.postMessage({ type: 'bloby:get-page-context' }, '*');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ── Message Handling ───────────────────────────────────────────────────────
|
|
78
|
-
|
|
79
|
-
window.addEventListener('message', (event) => {
|
|
80
|
-
const msg = event.data;
|
|
81
|
-
if (!msg?.type) return;
|
|
82
|
-
|
|
83
|
-
// Context response from content script
|
|
84
|
-
if (msg.type === 'bloby:page-context') {
|
|
85
|
-
currentPageContext = msg.context;
|
|
86
|
-
log('Page context received: ' + (msg.context?.url || 'none'));
|
|
87
|
-
|
|
88
|
-
// Forward to chat iframe so it can use it
|
|
89
|
-
if (chatIframe?.contentWindow) {
|
|
90
|
-
chatIframe.contentWindow.postMessage({
|
|
91
|
-
type: 'bloby:page-context',
|
|
92
|
-
context: msg.context,
|
|
93
|
-
}, '*');
|
|
94
|
-
}
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Action result from content script → forward to chat
|
|
99
|
-
if (msg.type === 'bloby:action-result') {
|
|
100
|
-
if (chatIframe?.contentWindow) {
|
|
101
|
-
chatIframe.contentWindow.postMessage(msg, '*');
|
|
102
|
-
}
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Close request from chat iframe → forward to content script
|
|
107
|
-
if (msg.type === 'bloby:close') {
|
|
108
|
-
window.parent.postMessage({ type: 'bloby:close' }, '*');
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Extension action from chat iframe → forward to content script
|
|
113
|
-
if (msg.type === 'bloby:extension-action') {
|
|
114
|
-
window.parent.postMessage(msg, '*');
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Chat requests fresh page context
|
|
119
|
-
if (msg.type === 'bloby:request-context') {
|
|
120
|
-
requestPageContext();
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
});
|