bee-ladybug 0.1.0 → 0.2.0
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/LICENSE +21 -0
- package/README.md +102 -14
- package/package.json +40 -7
- package/src/adapters/python/PythonBridge.js +217 -15
- package/src/core/AiAssistant.js +60 -0
- package/src/core/BeeLadybugCore.js +91 -2
- package/src/core/UIOverlay.js +9 -2
- package/src/index.d.ts +272 -0
- package/src/index.js +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Antonio Prosperi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -27,22 +27,24 @@ funziona sopra un gioco, sopra una pagina, o da solo in una sandbox.
|
|
|
27
27
|
bee-ladybug/
|
|
28
28
|
├── src/
|
|
29
29
|
│ ├── index.js # API pubblica
|
|
30
|
+
│ ├── index.d.ts # tipi npm / tsc
|
|
30
31
|
│ ├── core/
|
|
31
|
-
│ │ ├── BeeLadybugCore.js
|
|
32
|
-
│ │ └── UIOverlay.js
|
|
32
|
+
│ │ ├── BeeLadybugCore.js
|
|
33
|
+
│ │ └── UIOverlay.js
|
|
33
34
|
│ └── adapters/
|
|
34
|
-
│ ├── CanvasAdapter.js
|
|
35
|
-
│ ├── WebDOMAdapter.js
|
|
36
|
-
│ └── python/
|
|
37
|
-
│ └── PythonBridge.js # futuro: WebSocket → sendData
|
|
35
|
+
│ ├── CanvasAdapter.js
|
|
36
|
+
│ ├── WebDOMAdapter.js
|
|
37
|
+
│ └── python/PythonBridge.js
|
|
38
38
|
├── examples/
|
|
39
|
-
│ ├── core-sandbox.js # demo senza adapter
|
|
40
39
|
│ ├── core.html
|
|
41
|
-
│ ├── canvas-sandbox.js
|
|
42
|
-
│ ├── dom
|
|
43
|
-
│
|
|
44
|
-
|
|
40
|
+
│ ├── canvas-sandbox.js
|
|
41
|
+
│ ├── dom.html
|
|
42
|
+
│ ├── python.html
|
|
43
|
+
│ └── python/sender.py
|
|
44
|
+
├── tsconfig.json
|
|
45
45
|
├── package.json
|
|
46
|
+
├── LICENSE
|
|
47
|
+
├── index.html
|
|
46
48
|
└── README.md
|
|
47
49
|
```
|
|
48
50
|
|
|
@@ -165,6 +167,91 @@ adapter.attach();
|
|
|
165
167
|
|
|
166
168
|
Traduce il DOM in pacchetti: nodo sotto il puntatore, box model (margin / padding / content), conteggio nodi, mutazioni (`+n -n attr`). `F4` congela l'hover. `F2` nasconde overlay e highlight. Il Core non sa che esiste HTML.
|
|
167
169
|
|
|
170
|
+
## PythonBridge
|
|
171
|
+
|
|
172
|
+
Python non vede l'overlay. Manda JSON. Il browser lo srotola in `sendData()`.
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
import { BeeLadybugCore, PythonBridge } from './src/index.js';
|
|
176
|
+
|
|
177
|
+
const bee = new BeeLadybugCore();
|
|
178
|
+
const bridge = new PythonBridge(bee, { url: 'ws://127.0.0.1:8765' });
|
|
179
|
+
bridge.connect();
|
|
180
|
+
|
|
181
|
+
// stesso parser, senza socket:
|
|
182
|
+
bridge.ingest({ type: 'metric', payload: { name: 'loss', value: 0.12 } });
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Forma del filo:
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{ "type": "log", "payload": { "message": "epoch 3" } }
|
|
189
|
+
{ "type": "error", "message": "CUDA OOM" }
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Sender di esempio (solo stdlib, nessun pip):
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
python examples/python/sender.py
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Poi aprire `http://localhost:8080/examples/python.html` e Connect. I pulsanti mock funzionano anche a processo spento.
|
|
199
|
+
|
|
200
|
+
## npm / TypeScript
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
npm install
|
|
204
|
+
npm run typecheck
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
import { BeeLadybugCore } from 'bee-ladybug';
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Il runtime resta JavaScript ESM. I tipi stanno in `src/index.d.ts` (`package.json` → `"types"`).
|
|
212
|
+
Keyword e repository puntano a **BeeLadybug-universal**, non a BeeEngine.
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
npm run typecheck
|
|
216
|
+
npm run lint
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Versione runtime e pacchetto: **0.2.0** (`BEE_LADYBUG_VERSION` e `package.json` devono restare uguali). Su npm ora risulta `0.1.0`: il prossimo `npm publish` sarà `0.2.0`.
|
|
220
|
+
|
|
221
|
+
## Assistente AI (gancio, nessun vendor)
|
|
222
|
+
|
|
223
|
+
BeeLadybug **non** chiama OpenAI, Claude o Ollama. Lo sviluppatore innesta il proprio modello:
|
|
224
|
+
|
|
225
|
+
```js
|
|
226
|
+
bee.setAssistant({
|
|
227
|
+
name: 'ollama',
|
|
228
|
+
async complete({ question, snapshot }) {
|
|
229
|
+
const res = await fetch('http://127.0.0.1:11434/api/generate', {
|
|
230
|
+
method: 'POST',
|
|
231
|
+
headers: { 'Content-Type': 'application/json' },
|
|
232
|
+
body: JSON.stringify({
|
|
233
|
+
model: 'llama3',
|
|
234
|
+
prompt: `${question}\n${JSON.stringify(snapshot)}`,
|
|
235
|
+
stream: false
|
|
236
|
+
})
|
|
237
|
+
});
|
|
238
|
+
const data = await res.json();
|
|
239
|
+
return data.response;
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
await bee.ask('perché il loss sale?');
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Il Core manda un **snapshot** (log recenti, HUD, metriche) a `complete()`. Chiavi tipo `token`/`password` vengono redacted. In overlay: pulsante **ASK AI**. Senza `setAssistant`, il bottone spiega come collegarlo.
|
|
247
|
+
|
|
248
|
+
## API Core
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
npm login
|
|
252
|
+
npm publish --access public
|
|
253
|
+
```
|
|
254
|
+
|
|
168
255
|
## API Core
|
|
169
256
|
|
|
170
257
|
```js
|
|
@@ -190,14 +277,15 @@ bee.getHud();
|
|
|
190
277
|
bee.show(); bee.hide(); bee.toggle();
|
|
191
278
|
bee.freeze(); bee.unfreeze(); bee.toggleFreeze();
|
|
192
279
|
bee.applySlowMo(); bee.restoreRealtime();
|
|
280
|
+
bee.setAssistant({ name: 'mock', complete: async ({ snapshot }) => 'ok' });
|
|
281
|
+
await bee.ask('why fps drop?');
|
|
193
282
|
bee.destroy();
|
|
194
283
|
```
|
|
195
284
|
|
|
196
285
|
## Roadmap
|
|
197
286
|
|
|
198
|
-
1. **Ora** — Core
|
|
199
|
-
2. **
|
|
200
|
-
3. **Pacchetto npm** — `import { BeeLadybugCore } from 'bee-ladybug'`.
|
|
287
|
+
1. **Ora** — Core, adapter, PythonBridge, tipi, ESLint, gancio AI. Fatto.
|
|
288
|
+
2. **npm 0.2.0** — `npm publish` (registry è ancora 0.1.0).
|
|
201
289
|
|
|
202
290
|
## Come contribuire
|
|
203
291
|
|
package/package.json
CHANGED
|
@@ -1,23 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bee-ladybug",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Universal telemetry and visual inspection overlay for Canvas, DOM,
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Universal telemetry and visual inspection overlay for Canvas, DOM, Python and AI flows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"module": "src/index.js",
|
|
8
|
+
"types": "src/index.d.ts",
|
|
8
9
|
"exports": {
|
|
9
|
-
".":
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./src/index.d.ts",
|
|
12
|
+
"import": "./src/index.js",
|
|
13
|
+
"default": "./src/index.js"
|
|
14
|
+
}
|
|
10
15
|
},
|
|
11
16
|
"files": [
|
|
12
17
|
"src"
|
|
13
18
|
],
|
|
14
|
-
|
|
19
|
+
"scripts": {
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"lint": "eslint src examples",
|
|
22
|
+
"test": "node --test tests/**/*.test.js"
|
|
23
|
+
},
|
|
15
24
|
"keywords": [
|
|
16
25
|
"debug",
|
|
17
26
|
"telemetry",
|
|
18
27
|
"overlay",
|
|
19
28
|
"observability",
|
|
29
|
+
"devtools",
|
|
20
30
|
"canvas",
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
"dom",
|
|
32
|
+
"python",
|
|
33
|
+
"websocket",
|
|
34
|
+
"ai",
|
|
35
|
+
"esm"
|
|
36
|
+
],
|
|
37
|
+
"author": "Antonio Prosperi",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/antonioprosperi2-svg/BeeLadybug-universal.git"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/antonioprosperi2-svg/BeeLadybug-universal/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/antonioprosperi2-svg/BeeLadybug-universal#readme",
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@eslint/js": "^9.36.0",
|
|
52
|
+
"eslint": "^10.8.1",
|
|
53
|
+
"globals": "^16.0.0",
|
|
54
|
+
"typescript": "^5.7.0"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -1,40 +1,242 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PythonBridge —
|
|
2
|
+
* PythonBridge — inbound channel for Python / AI / headless processes.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Python never talks to the overlay directly.
|
|
4
|
+
* Speaks WebSocket JSON and calls core.sendData(type, payload).
|
|
5
|
+
* The core stays browser-side; Python never talks to the overlay.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
* { "type": "
|
|
7
|
+
* Wire shape (either form):
|
|
8
|
+
* { "type": "metric", "payload": { "name": "loss", "value": 0.12 } }
|
|
9
|
+
* { "type": "log", "message": "epoch 3" }
|
|
10
|
+
*
|
|
11
|
+
* ingest() is the same parser used by the socket, so tests and the mock
|
|
12
|
+
* demo do not need a live Python process.
|
|
10
13
|
*/
|
|
14
|
+
|
|
15
|
+
export const PYTHON_BRIDGE_DEFAULTS = Object.freeze({
|
|
16
|
+
url: 'ws://127.0.0.1:8765',
|
|
17
|
+
autoReconnect: true,
|
|
18
|
+
reconnectMs: 1500,
|
|
19
|
+
source: 'python'
|
|
20
|
+
});
|
|
21
|
+
|
|
11
22
|
export class PythonBridge {
|
|
12
23
|
/**
|
|
13
24
|
* @param {import('../../core/BeeLadybugCore.js').BeeLadybugCore} core
|
|
14
|
-
* @param {
|
|
25
|
+
* @param {Partial<typeof PYTHON_BRIDGE_DEFAULTS>} [options]
|
|
15
26
|
*/
|
|
16
27
|
constructor(core, options = {}) {
|
|
28
|
+
const cfg = { ...PYTHON_BRIDGE_DEFAULTS, ...options };
|
|
17
29
|
this.core = core;
|
|
18
|
-
this.url =
|
|
30
|
+
this.url = cfg.url;
|
|
31
|
+
this.autoReconnect = cfg.autoReconnect;
|
|
32
|
+
this.reconnectMs = cfg.reconnectMs;
|
|
33
|
+
this.source = cfg.source;
|
|
34
|
+
this.status = 'idle';
|
|
35
|
+
|
|
19
36
|
this.#socket = null;
|
|
37
|
+
this.#wanted = false;
|
|
38
|
+
this.#timer = 0;
|
|
39
|
+
this.#packets = 0;
|
|
40
|
+
this.#retries = 0;
|
|
20
41
|
}
|
|
21
42
|
|
|
22
43
|
#socket;
|
|
44
|
+
#wanted;
|
|
45
|
+
#timer;
|
|
46
|
+
#packets;
|
|
47
|
+
#retries;
|
|
23
48
|
|
|
24
|
-
|
|
25
|
-
this.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
get connected() {
|
|
50
|
+
return this.status === 'open';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
connect(url) {
|
|
54
|
+
if (url) this.url = url;
|
|
55
|
+
this.#wanted = true;
|
|
56
|
+
this.#retries = 0;
|
|
57
|
+
this.#open();
|
|
30
58
|
return this;
|
|
31
59
|
}
|
|
32
60
|
|
|
33
61
|
disconnect() {
|
|
62
|
+
this.#wanted = false;
|
|
63
|
+
this.#clearTimer();
|
|
34
64
|
if (this.#socket) {
|
|
35
|
-
|
|
65
|
+
try {
|
|
66
|
+
this.#socket.close();
|
|
67
|
+
} catch {
|
|
68
|
+
/* already closing */
|
|
69
|
+
}
|
|
36
70
|
this.#socket = null;
|
|
37
71
|
}
|
|
72
|
+
this.#setStatus('idle');
|
|
73
|
+
this.core?.sendData('log', {
|
|
74
|
+
source: this.source,
|
|
75
|
+
message: 'PythonBridge disconnected.'
|
|
76
|
+
});
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parse one wire message (object, JSON string, or array) into sendData().
|
|
82
|
+
* @param {unknown} raw
|
|
83
|
+
*/
|
|
84
|
+
ingest(raw) {
|
|
85
|
+
if (!this.core) return this;
|
|
86
|
+
if (raw == null) return this;
|
|
87
|
+
|
|
88
|
+
let data = raw;
|
|
89
|
+
if (typeof raw === 'string') {
|
|
90
|
+
const text = raw.trim();
|
|
91
|
+
if (!text) return this;
|
|
92
|
+
try {
|
|
93
|
+
data = JSON.parse(text);
|
|
94
|
+
} catch {
|
|
95
|
+
this.core.sendData('error', {
|
|
96
|
+
source: this.source,
|
|
97
|
+
message: `bad JSON: ${text.slice(0, 96)}`
|
|
98
|
+
});
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (Array.isArray(data)) {
|
|
104
|
+
for (let i = 0; i < data.length; i++) this.ingest(data[i]);
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (typeof data !== 'object') return this;
|
|
109
|
+
|
|
110
|
+
const type = data.type || data.channel;
|
|
111
|
+
if (typeof type !== 'string' || !type) {
|
|
112
|
+
this.core.sendData('warn', {
|
|
113
|
+
source: this.source,
|
|
114
|
+
message: 'packet missing type'
|
|
115
|
+
});
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const payload = unpackPayload(data, this.source);
|
|
120
|
+
this.#packets += 1;
|
|
121
|
+
this.core.sendData(type, payload);
|
|
122
|
+
this.core.sendData('state', {
|
|
123
|
+
source: this.source,
|
|
124
|
+
key: 'py.packets',
|
|
125
|
+
value: this.#packets
|
|
126
|
+
});
|
|
38
127
|
return this;
|
|
39
128
|
}
|
|
129
|
+
|
|
130
|
+
#open() {
|
|
131
|
+
if (!this.#wanted) return;
|
|
132
|
+
if (typeof WebSocket === 'undefined') {
|
|
133
|
+
this.core?.sendData('error', {
|
|
134
|
+
source: this.source,
|
|
135
|
+
message: 'WebSocket API missing in this runtime.'
|
|
136
|
+
});
|
|
137
|
+
this.#setStatus('closed');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.#clearTimer();
|
|
142
|
+
if (this.#socket) {
|
|
143
|
+
try {
|
|
144
|
+
this.#socket.close();
|
|
145
|
+
} catch {
|
|
146
|
+
/* ignore */
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.#setStatus('connecting');
|
|
151
|
+
if (this.#retries === 0) {
|
|
152
|
+
this.core?.sendData('log', {
|
|
153
|
+
source: this.source,
|
|
154
|
+
message: `PythonBridge connecting ${this.url}`
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let socket;
|
|
159
|
+
try {
|
|
160
|
+
socket = new WebSocket(this.url);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
this.core?.sendData('error', {
|
|
163
|
+
source: this.source,
|
|
164
|
+
message: `connect failed: ${err instanceof Error ? err.message : String(err)}`
|
|
165
|
+
});
|
|
166
|
+
this.#setStatus('closed');
|
|
167
|
+
this.#scheduleReconnect();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this.#socket = socket;
|
|
172
|
+
socket.addEventListener('open', () => {
|
|
173
|
+
if (socket !== this.#socket) return;
|
|
174
|
+
this.#setStatus('open');
|
|
175
|
+
this.#retries = 0;
|
|
176
|
+
this.core?.sendData('log', {
|
|
177
|
+
source: this.source,
|
|
178
|
+
message: `PythonBridge open (${this.url})`
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
socket.addEventListener('message', (event) => {
|
|
182
|
+
if (socket !== this.#socket) return;
|
|
183
|
+
this.ingest(event.data);
|
|
184
|
+
});
|
|
185
|
+
socket.addEventListener('close', () => {
|
|
186
|
+
if (socket !== this.#socket) return;
|
|
187
|
+
this.#socket = null;
|
|
188
|
+
this.#setStatus('closed');
|
|
189
|
+
if (this.#retries === 0) {
|
|
190
|
+
this.core?.sendData('warn', {
|
|
191
|
+
source: this.source,
|
|
192
|
+
message: 'PythonBridge socket closed.'
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
this.#retries += 1;
|
|
196
|
+
this.#scheduleReconnect();
|
|
197
|
+
});
|
|
198
|
+
socket.addEventListener('error', () => {
|
|
199
|
+
if (this.#retries > 0) return;
|
|
200
|
+
this.core?.sendData('error', {
|
|
201
|
+
source: this.source,
|
|
202
|
+
message: `PythonBridge socket error (${this.url})`
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#scheduleReconnect() {
|
|
208
|
+
if (!this.#wanted || !this.autoReconnect) return;
|
|
209
|
+
this.#clearTimer();
|
|
210
|
+
this.#timer = setTimeout(() => this.#open(), this.reconnectMs);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#clearTimer() {
|
|
214
|
+
if (this.#timer && typeof clearTimeout === 'function') clearTimeout(this.#timer);
|
|
215
|
+
this.#timer = 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
#setStatus(status) {
|
|
219
|
+
this.status = status;
|
|
220
|
+
this.core?.sendData('state', {
|
|
221
|
+
source: this.source,
|
|
222
|
+
key: 'py.status',
|
|
223
|
+
value: status
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function unpackPayload(data, source) {
|
|
229
|
+
const body = data.payload != null && typeof data.payload === 'object' && !Array.isArray(data.payload)
|
|
230
|
+
? { ...data.payload }
|
|
231
|
+
: omitType(data);
|
|
232
|
+
if (!body.source) body.source = source;
|
|
233
|
+
return body;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function omitType(data) {
|
|
237
|
+
const out = { ...data };
|
|
238
|
+
delete out.type;
|
|
239
|
+
delete out.channel;
|
|
240
|
+
delete out.payload;
|
|
241
|
+
return out;
|
|
40
242
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vendor-agnostic AI hook. BeeLadybug never ships an API key or a model:
|
|
3
|
+
* the host app injects complete({ question, snapshot }).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const SECRET_KEY = /key|token|secret|password|authorization|cookie|apikey/i;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {import('./BeeLadybugCore.js').BeeLadybugCore} core
|
|
10
|
+
* @param {string} question
|
|
11
|
+
*/
|
|
12
|
+
export function buildAssistantSnapshot(core, question) {
|
|
13
|
+
const logs = core.getLogs().slice(-40).map((packet) => ({
|
|
14
|
+
type: packet.type,
|
|
15
|
+
level: packet.level,
|
|
16
|
+
source: packet.source,
|
|
17
|
+
message: String(packet.payload?.message ?? packet.payload?.value ?? packet.type),
|
|
18
|
+
frame: packet.frame
|
|
19
|
+
}));
|
|
20
|
+
const history = core.getPrimaryHistory();
|
|
21
|
+
return {
|
|
22
|
+
question: String(question || ''),
|
|
23
|
+
version: core.version,
|
|
24
|
+
state: core.getState(),
|
|
25
|
+
hud: scrub(core.getHud()),
|
|
26
|
+
logs,
|
|
27
|
+
history: {
|
|
28
|
+
name: history.name,
|
|
29
|
+
min: history.min,
|
|
30
|
+
max: history.max,
|
|
31
|
+
avg: history.avg,
|
|
32
|
+
last: history.last,
|
|
33
|
+
samples: history.values.slice(-24)
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function normalizeAssistant(provider) {
|
|
39
|
+
if (!provider || typeof provider.complete !== 'function') return null;
|
|
40
|
+
const name = typeof provider.name === 'string' && provider.name
|
|
41
|
+
? provider.name
|
|
42
|
+
: 'custom';
|
|
43
|
+
return { name, complete: provider.complete.bind(provider) };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function scrub(value, depth = 0) {
|
|
47
|
+
if (value == null || typeof value !== 'object') {
|
|
48
|
+
if (typeof value === 'string' && value.length > 400) return `${value.slice(0, 400)}…`;
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
if (depth > 4) return '[…]';
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
return value.slice(0, 24).map((item) => scrub(item, depth + 1));
|
|
54
|
+
}
|
|
55
|
+
const out = {};
|
|
56
|
+
for (const key of Object.keys(value)) {
|
|
57
|
+
out[key] = SECRET_KEY.test(key) ? '[redacted]' : scrub(value[key], depth + 1);
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { UIOverlay } from './UIOverlay.js';
|
|
10
|
+
import { buildAssistantSnapshot, normalizeAssistant } from './AiAssistant.js';
|
|
10
11
|
|
|
11
|
-
export const BEE_LADYBUG_VERSION = '0.
|
|
12
|
+
export const BEE_LADYBUG_VERSION = '0.2.0';
|
|
12
13
|
|
|
13
14
|
export const CORE_DEFAULTS = Object.freeze({
|
|
14
15
|
toggleKey: 'F2',
|
|
@@ -92,6 +93,8 @@ export class BeeLadybugCore {
|
|
|
92
93
|
this.#onKeyDown = (event) => this.#handleKey(event);
|
|
93
94
|
this.#onTick = (now) => this.#tick(now);
|
|
94
95
|
this.#started = false;
|
|
96
|
+
this.#assistant = null;
|
|
97
|
+
this.#askBusy = false;
|
|
95
98
|
|
|
96
99
|
this.ui = null;
|
|
97
100
|
if (cfg.ui !== false && typeof document !== 'undefined') {
|
|
@@ -121,6 +124,8 @@ export class BeeLadybugCore {
|
|
|
121
124
|
#onKeyDown;
|
|
122
125
|
#onTick;
|
|
123
126
|
#started;
|
|
127
|
+
#assistant;
|
|
128
|
+
#askBusy;
|
|
124
129
|
|
|
125
130
|
/**
|
|
126
131
|
* Public adapter contract. `type` is a channel name; `payload` is raw data.
|
|
@@ -153,6 +158,87 @@ export class BeeLadybugCore {
|
|
|
153
158
|
return () => this.#listeners.delete(handler);
|
|
154
159
|
}
|
|
155
160
|
|
|
161
|
+
get hasAssistant() {
|
|
162
|
+
return Boolean(this.#assistant);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
get assistantBusy() {
|
|
166
|
+
return this.#askBusy;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Plug in any model. BeeLadybug does not call OpenAI/Ollama itself.
|
|
171
|
+
* @param {{ name?: string, complete: (input: { question: string, snapshot: object }) => unknown }} provider
|
|
172
|
+
*/
|
|
173
|
+
setAssistant(provider) {
|
|
174
|
+
const next = normalizeAssistant(provider);
|
|
175
|
+
if (!next) {
|
|
176
|
+
this.sendData('warn', {
|
|
177
|
+
source: 'ai',
|
|
178
|
+
message: 'setAssistant() needs { complete(ctx) }. No vendor is bundled.'
|
|
179
|
+
});
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
this.#assistant = next;
|
|
183
|
+
this.sendData('state', { source: 'ai', key: 'ai.assist', value: next.name });
|
|
184
|
+
this.sendData('log', {
|
|
185
|
+
source: 'ai',
|
|
186
|
+
message: `assistant ready (${next.name})`
|
|
187
|
+
});
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
clearAssistant() {
|
|
192
|
+
this.#assistant = null;
|
|
193
|
+
this.sendData('state', { source: 'ai', key: 'ai.assist', value: 'off' });
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Send recent telemetry to the registered assistant and print the answer.
|
|
199
|
+
* @param {string} [question]
|
|
200
|
+
* @returns {Promise<string|null>}
|
|
201
|
+
*/
|
|
202
|
+
async ask(question = 'Diagnose the current telemetry.') {
|
|
203
|
+
if (!this.#assistant) {
|
|
204
|
+
this.sendData('warn', {
|
|
205
|
+
source: 'ai',
|
|
206
|
+
message: 'No assistant. bee.setAssistant({ name, complete }) then ASK AI.'
|
|
207
|
+
});
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
if (this.#askBusy) {
|
|
211
|
+
this.sendData('warn', { source: 'ai', message: 'assistant busy' });
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
const q = String(question || 'Diagnose the current telemetry.');
|
|
215
|
+
this.#askBusy = true;
|
|
216
|
+
this.sendData('state', { source: 'ai', key: 'ai.assist', value: 'thinking' });
|
|
217
|
+
this.sendData('log', { source: 'ai', message: `ask: ${q}` });
|
|
218
|
+
try {
|
|
219
|
+
const snapshot = buildAssistantSnapshot(this, q);
|
|
220
|
+
const raw = await this.#assistant.complete({ question: q, snapshot });
|
|
221
|
+
const text = String(raw ?? '').trim() || '(empty answer)';
|
|
222
|
+
this.sendData('log', { source: 'ai', message: text });
|
|
223
|
+
this.sendData('state', { source: 'ai', key: 'ai.assist', value: this.#assistant.name });
|
|
224
|
+
return text;
|
|
225
|
+
} catch (err) {
|
|
226
|
+
this.sendData('error', {
|
|
227
|
+
source: 'ai',
|
|
228
|
+
message: err instanceof Error ? err.message : String(err)
|
|
229
|
+
});
|
|
230
|
+
this.sendData('state', {
|
|
231
|
+
source: 'ai',
|
|
232
|
+
key: 'ai.assist',
|
|
233
|
+
value: this.#assistant?.name ?? 'error'
|
|
234
|
+
});
|
|
235
|
+
return null;
|
|
236
|
+
} finally {
|
|
237
|
+
this.#askBusy = false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
|
|
156
242
|
start() {
|
|
157
243
|
if (this.#started) return this;
|
|
158
244
|
this.#started = true;
|
|
@@ -194,6 +280,8 @@ export class BeeLadybugCore {
|
|
|
194
280
|
this.#history.clear();
|
|
195
281
|
this.#hud.clear();
|
|
196
282
|
this.#latest.clear();
|
|
283
|
+
this.#assistant = null;
|
|
284
|
+
this.#askBusy = false;
|
|
197
285
|
this.ui?.destroy();
|
|
198
286
|
this.ui = null;
|
|
199
287
|
return this;
|
|
@@ -276,7 +364,8 @@ export class BeeLadybugCore {
|
|
|
276
364
|
coreFps: this.coreFps,
|
|
277
365
|
logCount: this.#logs.length,
|
|
278
366
|
hud: this.getHud(),
|
|
279
|
-
mode: this.#modeLabel()
|
|
367
|
+
mode: this.#modeLabel(),
|
|
368
|
+
assistant: this.#assistant ? this.#assistant.name : null
|
|
280
369
|
};
|
|
281
370
|
}
|
|
282
371
|
|
package/src/core/UIOverlay.js
CHANGED
|
@@ -169,6 +169,7 @@ const STYLES = `
|
|
|
169
169
|
.bl-foot {
|
|
170
170
|
display: flex;
|
|
171
171
|
align-items: center;
|
|
172
|
+
flex-wrap: wrap;
|
|
172
173
|
gap: 6px;
|
|
173
174
|
padding: 8px 10px;
|
|
174
175
|
border-top: 1px solid rgba(255, 59, 78, 0.22);
|
|
@@ -205,7 +206,7 @@ export class UIOverlay {
|
|
|
205
206
|
*/
|
|
206
207
|
constructor(core, options = {}) {
|
|
207
208
|
this.core = core;
|
|
208
|
-
|
|
209
|
+
void options;
|
|
209
210
|
this.#host = null;
|
|
210
211
|
this.#root = null;
|
|
211
212
|
this.#els = null;
|
|
@@ -214,7 +215,6 @@ export class UIOverlay {
|
|
|
214
215
|
this.#mounted = false;
|
|
215
216
|
}
|
|
216
217
|
|
|
217
|
-
#options;
|
|
218
218
|
#host;
|
|
219
219
|
#root;
|
|
220
220
|
#els;
|
|
@@ -268,6 +268,7 @@ export class UIOverlay {
|
|
|
268
268
|
<button class="bl-btn" data-act="slow" type="button">F3 SLOW</button>
|
|
269
269
|
<button class="bl-btn" data-act="freeze" type="button">F4 STOP</button>
|
|
270
270
|
<button class="bl-btn" data-act="live" type="button">1x LIVE</button>
|
|
271
|
+
<button class="bl-btn" data-act="ask" type="button">ASK AI</button>
|
|
271
272
|
<span class="bl-hint">F2 overlay</span>
|
|
272
273
|
</div>
|
|
273
274
|
</div>
|
|
@@ -295,6 +296,7 @@ export class UIOverlay {
|
|
|
295
296
|
if (act === 'slow') this.core.applySlowMo();
|
|
296
297
|
else if (act === 'freeze') this.core.toggleFreeze();
|
|
297
298
|
else if (act === 'live') this.core.restoreRealtime();
|
|
299
|
+
else if (act === 'ask') this.core.ask();
|
|
298
300
|
});
|
|
299
301
|
|
|
300
302
|
this.#els.console.addEventListener('scroll', () => {
|
|
@@ -415,6 +417,11 @@ export class UIOverlay {
|
|
|
415
417
|
freeze.dataset.hot = state.frozen ? 'true' : 'false';
|
|
416
418
|
slow.dataset.hot = !state.frozen && state.timeScale !== 1 ? 'true' : 'false';
|
|
417
419
|
live.dataset.hot = !state.frozen && state.timeScale === 1 ? 'true' : 'false';
|
|
420
|
+
const ask = this.#root.querySelector('[data-act="ask"]');
|
|
421
|
+
if (ask) {
|
|
422
|
+
ask.dataset.hot = this.core.hasAssistant ? 'true' : 'false';
|
|
423
|
+
ask.disabled = Boolean(this.core.assistantBusy);
|
|
424
|
+
}
|
|
418
425
|
}
|
|
419
426
|
|
|
420
427
|
#paintGraph(series) {
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public TypeScript surface for bee-ladybug.
|
|
3
|
+
* Runtime is still plain ESM JavaScript in src/*.js.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const BEE_LADYBUG_VERSION: string;
|
|
7
|
+
|
|
8
|
+
export const WELL_KNOWN_TYPES: readonly [
|
|
9
|
+
'fps',
|
|
10
|
+
'metric',
|
|
11
|
+
'coord',
|
|
12
|
+
'log',
|
|
13
|
+
'warn',
|
|
14
|
+
'error',
|
|
15
|
+
'state',
|
|
16
|
+
'sys',
|
|
17
|
+
'clear'
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
21
|
+
export type WellKnownType = (typeof WELL_KNOWN_TYPES)[number];
|
|
22
|
+
|
|
23
|
+
export interface BeePacket {
|
|
24
|
+
id: number;
|
|
25
|
+
type: string;
|
|
26
|
+
payload: Record<string, unknown>;
|
|
27
|
+
wallTime: number;
|
|
28
|
+
simTime: number;
|
|
29
|
+
frame: number;
|
|
30
|
+
source: string;
|
|
31
|
+
level: LogLevel | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface HudEntry {
|
|
35
|
+
value: unknown;
|
|
36
|
+
unit?: string;
|
|
37
|
+
type: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface MetricHistory {
|
|
41
|
+
name: string;
|
|
42
|
+
values: number[];
|
|
43
|
+
min: number;
|
|
44
|
+
max: number;
|
|
45
|
+
avg: number;
|
|
46
|
+
last: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface CoreState {
|
|
50
|
+
version: string;
|
|
51
|
+
visible: boolean;
|
|
52
|
+
frozen: boolean;
|
|
53
|
+
timeScale: number;
|
|
54
|
+
simTime: number;
|
|
55
|
+
frameCount: number;
|
|
56
|
+
coreFps: number;
|
|
57
|
+
logCount: number;
|
|
58
|
+
hud: Record<string, HudEntry>;
|
|
59
|
+
mode: string;
|
|
60
|
+
assistant: string | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface CoreOptions {
|
|
64
|
+
toggleKey?: string;
|
|
65
|
+
slowKey?: string;
|
|
66
|
+
freezeKey?: string;
|
|
67
|
+
slowScale?: number;
|
|
68
|
+
maxLogs?: number;
|
|
69
|
+
historySize?: number;
|
|
70
|
+
mount?: 'auto' | false | HTMLElement;
|
|
71
|
+
ui?: boolean;
|
|
72
|
+
autoAttach?: boolean;
|
|
73
|
+
autoStart?: boolean;
|
|
74
|
+
overlay?: Record<string, unknown>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const CORE_DEFAULTS: Required<Omit<CoreOptions, 'overlay' | 'mount'>> & {
|
|
78
|
+
mount: 'auto';
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export interface AssistantSnapshot {
|
|
82
|
+
question: string;
|
|
83
|
+
version: string;
|
|
84
|
+
state: CoreState;
|
|
85
|
+
hud: Record<string, HudEntry>;
|
|
86
|
+
logs: Array<{
|
|
87
|
+
type: string;
|
|
88
|
+
level: LogLevel | null;
|
|
89
|
+
source: string;
|
|
90
|
+
message: string;
|
|
91
|
+
frame: number;
|
|
92
|
+
}>;
|
|
93
|
+
history: {
|
|
94
|
+
name: string;
|
|
95
|
+
min: number;
|
|
96
|
+
max: number;
|
|
97
|
+
avg: number;
|
|
98
|
+
last: number;
|
|
99
|
+
samples: number[];
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface AssistantProvider {
|
|
104
|
+
name?: string;
|
|
105
|
+
complete(input: { question: string; snapshot: AssistantSnapshot }): unknown;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function formatSimTime(ms: number): string;
|
|
109
|
+
|
|
110
|
+
export class BeeLadybugCore {
|
|
111
|
+
readonly version: string;
|
|
112
|
+
toggleKey: string;
|
|
113
|
+
slowKey: string;
|
|
114
|
+
freezeKey: string;
|
|
115
|
+
slowScale: number;
|
|
116
|
+
maxLogs: number;
|
|
117
|
+
historySize: number;
|
|
118
|
+
visible: boolean;
|
|
119
|
+
frozen: boolean;
|
|
120
|
+
timeScale: number;
|
|
121
|
+
simTime: number;
|
|
122
|
+
frameCount: number;
|
|
123
|
+
coreFps: number;
|
|
124
|
+
ui: UIOverlay | null;
|
|
125
|
+
|
|
126
|
+
constructor(options?: CoreOptions);
|
|
127
|
+
sendData(type: string, payload?: unknown): BeePacket | null;
|
|
128
|
+
subscribe(handler: (packet: BeePacket) => void): () => void;
|
|
129
|
+
start(): this;
|
|
130
|
+
stop(): this;
|
|
131
|
+
attach(): this;
|
|
132
|
+
detach(): this;
|
|
133
|
+
destroy(): this;
|
|
134
|
+
show(): this;
|
|
135
|
+
hide(): this;
|
|
136
|
+
toggle(): this;
|
|
137
|
+
freeze(): this;
|
|
138
|
+
unfreeze(): this;
|
|
139
|
+
toggleFreeze(): this;
|
|
140
|
+
setTimeScale(scale: number): this;
|
|
141
|
+
applySlowMo(): this;
|
|
142
|
+
restoreRealtime(): this;
|
|
143
|
+
getState(): CoreState;
|
|
144
|
+
getLogs(): BeePacket[];
|
|
145
|
+
getHistory(name: string): MetricHistory;
|
|
146
|
+
getPrimaryHistory(): MetricHistory;
|
|
147
|
+
getHud(): Record<string, HudEntry>;
|
|
148
|
+
getLatest(type: string): BeePacket | null;
|
|
149
|
+
readonly hasAssistant: boolean;
|
|
150
|
+
readonly assistantBusy: boolean;
|
|
151
|
+
setAssistant(provider: AssistantProvider): this;
|
|
152
|
+
clearAssistant(): this;
|
|
153
|
+
ask(question?: string): Promise<string | null>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export class UIOverlay {
|
|
157
|
+
core: BeeLadybugCore | null;
|
|
158
|
+
constructor(core: BeeLadybugCore, options?: Record<string, unknown>);
|
|
159
|
+
mount(target?: 'auto' | HTMLElement): this;
|
|
160
|
+
unmount(): this;
|
|
161
|
+
destroy(): this;
|
|
162
|
+
setVisible(visible: boolean): this;
|
|
163
|
+
onPacket(packet: BeePacket): void;
|
|
164
|
+
onClear(channel?: string): void;
|
|
165
|
+
render(): void;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface CanvasEntity {
|
|
169
|
+
name?: string;
|
|
170
|
+
label?: string;
|
|
171
|
+
id?: string | number;
|
|
172
|
+
role?: string;
|
|
173
|
+
primary?: boolean;
|
|
174
|
+
x?: number;
|
|
175
|
+
y?: number;
|
|
176
|
+
worldX?: number;
|
|
177
|
+
worldY?: number;
|
|
178
|
+
width?: number;
|
|
179
|
+
height?: number;
|
|
180
|
+
w?: number;
|
|
181
|
+
h?: number;
|
|
182
|
+
active?: boolean;
|
|
183
|
+
colliding?: boolean;
|
|
184
|
+
[key: string]: unknown;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface CanvasAdapterOptions {
|
|
188
|
+
canvas?: HTMLCanvasElement | null;
|
|
189
|
+
entities?: CanvasEntity[];
|
|
190
|
+
overlay?: boolean;
|
|
191
|
+
computeCollisions?: boolean;
|
|
192
|
+
colorActive?: string;
|
|
193
|
+
colorColliding?: string;
|
|
194
|
+
colorInactive?: string;
|
|
195
|
+
source?: string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const CANVAS_ADAPTER_DEFAULTS: Required<
|
|
199
|
+
Omit<CanvasAdapterOptions, 'canvas' | 'entities'>
|
|
200
|
+
>;
|
|
201
|
+
|
|
202
|
+
export class CanvasAdapter {
|
|
203
|
+
core: BeeLadybugCore | null;
|
|
204
|
+
canvas: HTMLCanvasElement | null;
|
|
205
|
+
entities: CanvasEntity[];
|
|
206
|
+
overlayEnabled: boolean;
|
|
207
|
+
computeCollisions: boolean;
|
|
208
|
+
frozen: boolean;
|
|
209
|
+
timeScale: number;
|
|
210
|
+
constructor(core: BeeLadybugCore, options?: CanvasAdapterOptions);
|
|
211
|
+
attach(): this;
|
|
212
|
+
detach(): this;
|
|
213
|
+
setCanvas(canvas: HTMLCanvasElement | null): this;
|
|
214
|
+
setEntities(entities: CanvasEntity[]): this;
|
|
215
|
+
pump(ctx?: CanvasRenderingContext2D): this;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface WebDOMAdapterOptions {
|
|
219
|
+
root?: ParentNode | null;
|
|
220
|
+
overlay?: boolean;
|
|
221
|
+
autoTick?: boolean;
|
|
222
|
+
watch?: string[];
|
|
223
|
+
source?: string;
|
|
224
|
+
colorHover?: string;
|
|
225
|
+
colorWatch?: string;
|
|
226
|
+
colorMargin?: string;
|
|
227
|
+
colorPadding?: string;
|
|
228
|
+
colorContent?: string;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export const WEB_DOM_ADAPTER_DEFAULTS: Required<Omit<WebDOMAdapterOptions, 'root'>>;
|
|
232
|
+
|
|
233
|
+
export class WebDOMAdapter {
|
|
234
|
+
core: BeeLadybugCore | null;
|
|
235
|
+
root: ParentNode | null;
|
|
236
|
+
overlayEnabled: boolean;
|
|
237
|
+
autoTick: boolean;
|
|
238
|
+
watch: string[];
|
|
239
|
+
frozen: boolean;
|
|
240
|
+
timeScale: number;
|
|
241
|
+
hover: Element | null;
|
|
242
|
+
constructor(core: BeeLadybugCore, options?: WebDOMAdapterOptions);
|
|
243
|
+
attach(): this;
|
|
244
|
+
detach(): this;
|
|
245
|
+
setRoot(root: ParentNode | null): this;
|
|
246
|
+
pump(): this;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export type PythonBridgeStatus = 'idle' | 'connecting' | 'open' | 'closed';
|
|
250
|
+
|
|
251
|
+
export interface PythonBridgeOptions {
|
|
252
|
+
url?: string;
|
|
253
|
+
autoReconnect?: boolean;
|
|
254
|
+
reconnectMs?: number;
|
|
255
|
+
source?: string;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export const PYTHON_BRIDGE_DEFAULTS: Required<PythonBridgeOptions>;
|
|
259
|
+
|
|
260
|
+
export class PythonBridge {
|
|
261
|
+
core: BeeLadybugCore | null;
|
|
262
|
+
url: string;
|
|
263
|
+
autoReconnect: boolean;
|
|
264
|
+
reconnectMs: number;
|
|
265
|
+
source: string;
|
|
266
|
+
status: PythonBridgeStatus;
|
|
267
|
+
readonly connected: boolean;
|
|
268
|
+
constructor(core: BeeLadybugCore, options?: PythonBridgeOptions);
|
|
269
|
+
connect(url?: string): this;
|
|
270
|
+
disconnect(): this;
|
|
271
|
+
ingest(raw: unknown): this;
|
|
272
|
+
}
|
package/src/index.js
CHANGED
|
@@ -2,3 +2,4 @@ export { BeeLadybugCore, BEE_LADYBUG_VERSION, CORE_DEFAULTS, WELL_KNOWN_TYPES, f
|
|
|
2
2
|
export { UIOverlay } from './core/UIOverlay.js';
|
|
3
3
|
export { CanvasAdapter, CANVAS_ADAPTER_DEFAULTS } from './adapters/CanvasAdapter.js';
|
|
4
4
|
export { WebDOMAdapter, WEB_DOM_ADAPTER_DEFAULTS } from './adapters/WebDOMAdapter.js';
|
|
5
|
+
export { PythonBridge, PYTHON_BRIDGE_DEFAULTS } from './adapters/python/PythonBridge.js';
|