bloby-bot 0.21.13 → 0.22.1

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,218 @@
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) for the agent
8
+ * - Bridges messages between the panel iframe and the background worker
9
+ */
10
+
11
+ (function () {
12
+ // Guard: don't inject on extension pages or Bloby's own domain
13
+ if (document.getElementById('bloby-ext-bubble')) return;
14
+ if (location.hostname.endsWith('bloby.bot')) return;
15
+
16
+ const BUBBLE_SIZE = 60;
17
+ const BUBBLE_MARGIN = 24;
18
+ const PANEL_WIDTH = 420;
19
+
20
+ let panelOpen = false;
21
+ let paired = false;
22
+
23
+ // ── Check if paired ────────────────────────────────────────────────────────
24
+
25
+ chrome.runtime.sendMessage({ type: 'bloby:get-state' }, (state) => {
26
+ if (chrome.runtime.lastError) return;
27
+ if (state?.paired) {
28
+ paired = true;
29
+ createBubble();
30
+ }
31
+ });
32
+
33
+ // Listen for pairing changes
34
+ chrome.storage.onChanged.addListener((changes) => {
35
+ if (changes.serverUrl) {
36
+ if (changes.serverUrl.newValue) {
37
+ paired = true;
38
+ if (!document.getElementById('bloby-ext-bubble')) createBubble();
39
+ } else {
40
+ paired = false;
41
+ removeBubble();
42
+ }
43
+ }
44
+ });
45
+
46
+ // ── Bubble ─────────────────────────────────────────────────────────────────
47
+
48
+ function createBubble() {
49
+ const bubble = document.createElement('div');
50
+ bubble.id = 'bloby-ext-bubble';
51
+ bubble.title = 'Chat with Bloby';
52
+ bubble.addEventListener('click', togglePanel);
53
+ document.body.appendChild(bubble);
54
+
55
+ // Inner dot (gradient)
56
+ const dot = document.createElement('div');
57
+ dot.className = 'bloby-ext-bubble-dot';
58
+ bubble.appendChild(dot);
59
+ }
60
+
61
+ function removeBubble() {
62
+ const bubble = document.getElementById('bloby-ext-bubble');
63
+ if (bubble) bubble.remove();
64
+ const panel = document.getElementById('bloby-ext-panel');
65
+ if (panel) panel.remove();
66
+ const backdrop = document.getElementById('bloby-ext-backdrop');
67
+ if (backdrop) backdrop.remove();
68
+ }
69
+
70
+ // ── Panel ──────────────────────────────────────────────────────────────────
71
+
72
+ function togglePanel() {
73
+ if (panelOpen) {
74
+ closePanel();
75
+ } else {
76
+ openPanel();
77
+ }
78
+ }
79
+
80
+ function openPanel() {
81
+ if (document.getElementById('bloby-ext-panel')) {
82
+ const panel = document.getElementById('bloby-ext-panel');
83
+ panel.classList.add('bloby-ext-panel-open');
84
+ panelOpen = true;
85
+ showBackdrop();
86
+ return;
87
+ }
88
+
89
+ // Create backdrop
90
+ showBackdrop();
91
+
92
+ // Create panel
93
+ const panel = document.createElement('div');
94
+ panel.id = 'bloby-ext-panel';
95
+
96
+ // Create iframe — loads the extension's own panel page
97
+ const iframe = document.createElement('iframe');
98
+ iframe.id = 'bloby-ext-iframe';
99
+ iframe.src = chrome.runtime.getURL('panel/panel.html');
100
+ iframe.allow = 'microphone';
101
+ panel.appendChild(iframe);
102
+
103
+ document.body.appendChild(panel);
104
+
105
+ // Trigger slide-in animation on next frame
106
+ requestAnimationFrame(() => {
107
+ panel.classList.add('bloby-ext-panel-open');
108
+ panelOpen = true;
109
+ });
110
+
111
+ // Listen for messages from the panel iframe
112
+ window.addEventListener('message', handlePanelMessage);
113
+ }
114
+
115
+ function closePanel() {
116
+ const panel = document.getElementById('bloby-ext-panel');
117
+ if (panel) {
118
+ panel.classList.remove('bloby-ext-panel-open');
119
+ panelOpen = false;
120
+ hideBackdrop();
121
+ }
122
+ }
123
+
124
+ function showBackdrop() {
125
+ let backdrop = document.getElementById('bloby-ext-backdrop');
126
+ if (!backdrop) {
127
+ backdrop = document.createElement('div');
128
+ backdrop.id = 'bloby-ext-backdrop';
129
+ backdrop.addEventListener('click', closePanel);
130
+ document.body.appendChild(backdrop);
131
+ }
132
+ requestAnimationFrame(() => backdrop.classList.add('bloby-ext-backdrop-visible'));
133
+ }
134
+
135
+ function hideBackdrop() {
136
+ const backdrop = document.getElementById('bloby-ext-backdrop');
137
+ if (backdrop) {
138
+ backdrop.classList.remove('bloby-ext-backdrop-visible');
139
+ }
140
+ }
141
+
142
+ // ── Panel ↔ Background Message Bridge ──────────────────────────────────────
143
+
144
+ function handlePanelMessage(event) {
145
+ if (event.source !== document.getElementById('bloby-ext-iframe')?.contentWindow) return;
146
+
147
+ const msg = event.data;
148
+ if (!msg?.type) return;
149
+
150
+ // Close panel request
151
+ if (msg.type === 'bloby:close') {
152
+ closePanel();
153
+ return;
154
+ }
155
+
156
+ // Forward chat messages to background worker → server
157
+ if (msg.type === 'bloby:send') {
158
+ chrome.runtime.sendMessage({ type: 'bloby:send', payload: msg.payload });
159
+ return;
160
+ }
161
+
162
+ // Panel requests page context
163
+ if (msg.type === 'bloby:get-page-context') {
164
+ const context = getPageContext();
165
+ const iframe = document.getElementById('bloby-ext-iframe');
166
+ if (iframe?.contentWindow) {
167
+ iframe.contentWindow.postMessage({ type: 'bloby:page-context', context }, '*');
168
+ }
169
+ return;
170
+ }
171
+ }
172
+
173
+ // Forward server messages from background to panel iframe
174
+ chrome.runtime.onMessage.addListener((msg) => {
175
+ const iframe = document.getElementById('bloby-ext-iframe');
176
+ if (iframe?.contentWindow && panelOpen) {
177
+ iframe.contentWindow.postMessage(msg, '*');
178
+ }
179
+ });
180
+
181
+ // ── Page Context Extraction ────────────────────────────────────────────────
182
+
183
+ function getPageContext() {
184
+ const context = {
185
+ url: location.href,
186
+ title: document.title,
187
+ selection: window.getSelection()?.toString()?.slice(0, 2000) || '',
188
+ };
189
+
190
+ // Try to extract structured data (JSON-LD)
191
+ const jsonLd = document.querySelector('script[type="application/ld+json"]');
192
+ if (jsonLd) {
193
+ try {
194
+ const data = JSON.parse(jsonLd.textContent);
195
+ if (data.name) context.productName = data.name;
196
+ if (data.offers?.price) context.price = data.offers.price;
197
+ if (data.offers?.priceCurrency) context.currency = data.offers.priceCurrency;
198
+ if (data.description) context.description = data.description?.slice(0, 500);
199
+ } catch { /* ignore malformed JSON-LD */ }
200
+ }
201
+
202
+ // Meta description fallback
203
+ const metaDesc = document.querySelector('meta[name="description"]');
204
+ if (metaDesc && !context.description) {
205
+ context.description = metaDesc.content?.slice(0, 500);
206
+ }
207
+
208
+ return context;
209
+ }
210
+
211
+ // ── Keyboard shortcut ──────────────────────────────────────────────────────
212
+
213
+ document.addEventListener('keydown', (e) => {
214
+ if (e.key === 'Escape' && panelOpen) {
215
+ closePanel();
216
+ }
217
+ });
218
+ })();
@@ -0,0 +1,86 @@
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
+ }
@@ -0,0 +1,46 @@
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
+ ],
10
+ "host_permissions": [
11
+ "https://*.bloby.bot/*",
12
+ "https://*.my.bloby.bot/*",
13
+ "https://*.trycloudflare.com/*"
14
+ ],
15
+ "action": {
16
+ "default_popup": "popup/popup.html",
17
+ "default_icon": {
18
+ "16": "icons/icon-16.png",
19
+ "48": "icons/icon-48.png",
20
+ "128": "icons/icon-128.png"
21
+ }
22
+ },
23
+ "background": {
24
+ "service_worker": "background.js",
25
+ "type": "module"
26
+ },
27
+ "content_scripts": [
28
+ {
29
+ "matches": ["<all_urls>"],
30
+ "js": ["content-script.js"],
31
+ "css": ["content-style.css"],
32
+ "run_at": "document_idle"
33
+ }
34
+ ],
35
+ "web_accessible_resources": [
36
+ {
37
+ "resources": ["panel/panel.html", "assets/*", "icons/*"],
38
+ "matches": ["<all_urls>"]
39
+ }
40
+ ],
41
+ "icons": {
42
+ "16": "icons/icon-16.png",
43
+ "48": "icons/icon-48.png",
44
+ "128": "icons/icon-128.png"
45
+ }
46
+ }
@@ -0,0 +1,74 @@
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;
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
+ </style>
27
+ </head>
28
+ <body>
29
+ <div class="loading" id="loading">
30
+ <div class="dots"><span></span><span></span><span></span></div>
31
+ </div>
32
+
33
+ <script>
34
+ /**
35
+ * Panel page — loads the user's Bloby chat in an iframe.
36
+ * Reads serverUrl from chrome.storage.local.
37
+ */
38
+ (async () => {
39
+ const data = await chrome.storage.local.get(['serverUrl', 'authToken']);
40
+
41
+ if (!data.serverUrl) {
42
+ document.getElementById('loading').innerHTML =
43
+ '<span style="color:#ef4444">Not paired. Click the Bloby icon in the toolbar to set up.</span>';
44
+ return;
45
+ }
46
+
47
+ // Build the chat URL — same as the dashboard chat iframe
48
+ let chatUrl = `${data.serverUrl}/bloby/`;
49
+ if (data.authToken) {
50
+ chatUrl += `?token=${data.authToken}`;
51
+ }
52
+
53
+ // Create iframe to the user's Bloby server
54
+ const iframe = document.createElement('iframe');
55
+ iframe.src = chatUrl;
56
+ iframe.allow = 'microphone';
57
+ iframe.style.cssText = 'width:100%;height:100%;border:none;';
58
+
59
+ iframe.onload = () => {
60
+ document.getElementById('loading').style.display = 'none';
61
+ };
62
+
63
+ document.body.appendChild(iframe);
64
+
65
+ // Forward close events from chat iframe to content script (parent of this panel)
66
+ window.addEventListener('message', (e) => {
67
+ if (e.data?.type === 'bloby:close') {
68
+ window.parent.postMessage({ type: 'bloby:close' }, '*');
69
+ }
70
+ });
71
+ })();
72
+ </script>
73
+ </body>
74
+ </html>
@@ -0,0 +1,124 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ width: 320px;
9
+ min-height: 280px;
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
+ background: #0a0a0b;
12
+ color: #e4e4e7;
13
+ padding: 32px 24px;
14
+ text-align: center;
15
+ }
16
+
17
+ .logo {
18
+ display: flex;
19
+ justify-content: center;
20
+ margin-bottom: 20px;
21
+ }
22
+
23
+ .logo-dot {
24
+ width: 48px;
25
+ height: 48px;
26
+ border-radius: 50%;
27
+ background: linear-gradient(135deg, #04D1FE, #AF27E3, #FB4072);
28
+ }
29
+
30
+ .logo-dot.connected {
31
+ box-shadow: 0 0 20px rgba(4, 209, 254, 0.4);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 18px;
36
+ font-weight: 600;
37
+ margin-bottom: 6px;
38
+ }
39
+
40
+ .subtitle {
41
+ font-size: 13px;
42
+ color: #71717a;
43
+ margin-bottom: 24px;
44
+ }
45
+
46
+ /* Code Input */
47
+ .code-input {
48
+ display: flex;
49
+ gap: 8px;
50
+ justify-content: center;
51
+ margin-bottom: 16px;
52
+ }
53
+
54
+ .code-input input {
55
+ width: 40px;
56
+ height: 48px;
57
+ border: 2px solid #27272a;
58
+ border-radius: 10px;
59
+ background: #18181b;
60
+ color: #e4e4e7;
61
+ font-size: 22px;
62
+ font-weight: 600;
63
+ text-align: center;
64
+ outline: none;
65
+ transition: border-color 0.15s;
66
+ caret-color: transparent;
67
+ }
68
+
69
+ .code-input input:focus {
70
+ border-color: #AF27E3;
71
+ }
72
+
73
+ .code-input input.filled {
74
+ border-color: #04D1FE;
75
+ }
76
+
77
+ .error {
78
+ color: #ef4444;
79
+ font-size: 13px;
80
+ min-height: 18px;
81
+ margin-bottom: 8px;
82
+ }
83
+
84
+ .pairing {
85
+ color: #a1a1aa;
86
+ font-size: 13px;
87
+ }
88
+
89
+ /* Connected Screen */
90
+ .status-badge {
91
+ display: inline-flex;
92
+ align-items: center;
93
+ gap: 6px;
94
+ background: #18181b;
95
+ border: 1px solid #27272a;
96
+ border-radius: 999px;
97
+ padding: 6px 14px;
98
+ font-size: 13px;
99
+ color: #a1a1aa;
100
+ margin-bottom: 20px;
101
+ }
102
+
103
+ .status-dot {
104
+ width: 8px;
105
+ height: 8px;
106
+ border-radius: 50%;
107
+ background: #22c55e;
108
+ }
109
+
110
+ .btn-disconnect {
111
+ background: none;
112
+ border: 1px solid #27272a;
113
+ border-radius: 8px;
114
+ color: #71717a;
115
+ padding: 8px 16px;
116
+ font-size: 13px;
117
+ cursor: pointer;
118
+ transition: all 0.15s;
119
+ }
120
+
121
+ .btn-disconnect:hover {
122
+ border-color: #ef4444;
123
+ color: #ef4444;
124
+ }
@@ -0,0 +1,47 @@
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">
6
+ <title>Bloby</title>
7
+ <link rel="stylesheet" href="popup.css">
8
+ </head>
9
+ <body>
10
+ <!-- Pairing Screen -->
11
+ <div id="pair-screen">
12
+ <div class="logo">
13
+ <div class="logo-dot"></div>
14
+ </div>
15
+ <h1>Connect to Bloby</h1>
16
+ <p class="subtitle">Ask your Bloby for a pairing code</p>
17
+
18
+ <div class="code-input" id="code-input">
19
+ <input type="text" maxlength="1" pattern="[0-9]" inputmode="numeric" autofocus>
20
+ <input type="text" maxlength="1" pattern="[0-9]" inputmode="numeric">
21
+ <input type="text" maxlength="1" pattern="[0-9]" inputmode="numeric">
22
+ <input type="text" maxlength="1" pattern="[0-9]" inputmode="numeric">
23
+ <input type="text" maxlength="1" pattern="[0-9]" inputmode="numeric">
24
+ <input type="text" maxlength="1" pattern="[0-9]" inputmode="numeric">
25
+ </div>
26
+
27
+ <div id="error" class="error"></div>
28
+ <div id="pairing" class="pairing" style="display:none">Connecting...</div>
29
+ </div>
30
+
31
+ <!-- Connected Screen -->
32
+ <div id="connected-screen" style="display:none">
33
+ <div class="logo">
34
+ <div class="logo-dot connected"></div>
35
+ </div>
36
+ <h1 id="connected-name">Connected</h1>
37
+ <p class="subtitle" id="connected-url"></p>
38
+ <div class="status-badge">
39
+ <span class="status-dot"></span>
40
+ <span id="status-text">Connected</span>
41
+ </div>
42
+ <button id="disconnect-btn" class="btn-disconnect">Disconnect</button>
43
+ </div>
44
+
45
+ <script src="popup.js"></script>
46
+ </body>
47
+ </html>