begeniux 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/README.md +278 -189
- package/dist/copilotkit.cjs +138 -0
- package/dist/copilotkit.cjs.map +1 -0
- package/dist/copilotkit.d.cts +20 -0
- package/dist/copilotkit.d.ts +20 -0
- package/dist/copilotkit.js +102 -0
- package/dist/copilotkit.js.map +1 -0
- package/dist/index.cjs +656 -320
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +266 -47
- package/dist/index.d.ts +266 -47
- package/dist/index.js +652 -315
- package/dist/index.js.map +1 -1
- package/package.json +20 -5
package/dist/index.cjs
CHANGED
|
@@ -28,67 +28,93 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
|
|
30
30
|
// src/index.ts
|
|
31
|
-
var
|
|
32
|
-
__export(
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
AdaptationEngine: () => AdaptationEngine,
|
|
33
34
|
BeGenProvider: () => BeGenProvider,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
createGeminiClassifier: () => createGeminiClassifier,
|
|
37
|
-
createHeuristicClassifier: () => createHeuristicClassifier,
|
|
35
|
+
createHttpAdapter: () => createHttpAdapter,
|
|
36
|
+
snapshotVisibleSelectors: () => snapshotVisibleSelectors,
|
|
38
37
|
useBeGenContext: () => useBeGenContext,
|
|
39
38
|
useBehaviorTracker: () => useBehaviorTracker
|
|
40
39
|
});
|
|
41
|
-
module.exports = __toCommonJS(
|
|
40
|
+
module.exports = __toCommonJS(src_exports);
|
|
42
41
|
|
|
43
42
|
// src/BeGenProvider.tsx
|
|
44
43
|
var React = __toESM(require("react"), 1);
|
|
45
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
46
|
-
var BeGenContext = React.createContext(null);
|
|
47
|
-
function BeGenProvider({ children }) {
|
|
48
|
-
const [variant, setVariant] = React.useState("neutral");
|
|
49
|
-
const [directive, setDirective] = React.useState(null);
|
|
50
|
-
const [summary, setSummary] = React.useState(null);
|
|
51
|
-
const value = React.useMemo(
|
|
52
|
-
() => ({ variant, directive, summary, setVariant, setDirective, setSummary }),
|
|
53
|
-
[variant, directive, summary]
|
|
54
|
-
);
|
|
55
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BeGenContext.Provider, { value, children });
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// src/BeGenSurface.tsx
|
|
59
|
-
var React2 = __toESM(require("react"), 1);
|
|
60
44
|
|
|
61
45
|
// src/useBehaviorTracker.ts
|
|
62
46
|
var import_react = require("react");
|
|
63
|
-
var RECENT_EVENTS_CAP =
|
|
47
|
+
var RECENT_EVENTS_CAP = 10;
|
|
64
48
|
var HOVER_MIN_MS = 200;
|
|
49
|
+
var RAGE_CLICK_WINDOW_MS = 1e3;
|
|
50
|
+
var RAGE_CLICK_THRESHOLD = 3;
|
|
51
|
+
var INPUT_THROTTLE_MS = 1e3;
|
|
65
52
|
function targetLabel(el) {
|
|
66
53
|
if (!(el instanceof HTMLElement)) return "unknown";
|
|
67
|
-
|
|
54
|
+
const begenId = el.getAttribute("data-begen-id");
|
|
55
|
+
if (begenId) return `[data-begen-id="${begenId}"]`;
|
|
56
|
+
if (el.id) return `#${el.id}`;
|
|
57
|
+
const testId = el.getAttribute("data-testid");
|
|
58
|
+
if (testId) return `[data-testid="${testId}"]`;
|
|
59
|
+
return el.tagName.toLowerCase();
|
|
68
60
|
}
|
|
69
|
-
function computeSummary(buffer, bufferSize, pageContext) {
|
|
61
|
+
function computeSummary(buffer, bufferSize, pageContext, custom) {
|
|
70
62
|
const now = Date.now();
|
|
71
63
|
const windowStart = now - 6e4;
|
|
72
64
|
let clicksLastMin = 0;
|
|
65
|
+
let rageClicks = 0;
|
|
73
66
|
let dwellSum = 0;
|
|
74
67
|
let dwellCount = 0;
|
|
75
68
|
let maxScroll = 0;
|
|
69
|
+
let formInteractions = 0;
|
|
70
|
+
let errorsSeen = 0;
|
|
76
71
|
const hoverTargets = /* @__PURE__ */ new Set();
|
|
72
|
+
let viewportW = typeof window !== "undefined" ? window.innerWidth || 0 : 0;
|
|
73
|
+
let viewportH = typeof window !== "undefined" ? window.innerHeight || 0 : 0;
|
|
77
74
|
for (const ev of buffer) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
switch (ev.kind) {
|
|
76
|
+
case "click":
|
|
77
|
+
if (ev.t >= windowStart) clicksLastMin += 1;
|
|
78
|
+
break;
|
|
79
|
+
case "rage-click":
|
|
80
|
+
rageClicks += 1;
|
|
81
|
+
break;
|
|
82
|
+
case "dwell":
|
|
83
|
+
dwellSum += ev.durationMs;
|
|
84
|
+
dwellCount += 1;
|
|
85
|
+
break;
|
|
86
|
+
case "scroll":
|
|
87
|
+
if (ev.depth > maxScroll) maxScroll = ev.depth;
|
|
88
|
+
break;
|
|
89
|
+
case "hover":
|
|
90
|
+
hoverTargets.add(ev.target);
|
|
91
|
+
break;
|
|
92
|
+
case "input":
|
|
93
|
+
case "submit":
|
|
94
|
+
formInteractions += 1;
|
|
95
|
+
break;
|
|
96
|
+
case "error":
|
|
97
|
+
errorsSeen += 1;
|
|
98
|
+
break;
|
|
99
|
+
case "viewport-change":
|
|
100
|
+
viewportW = ev.width;
|
|
101
|
+
viewportH = ev.height;
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
break;
|
|
82
105
|
}
|
|
83
|
-
if (ev.kind === "scroll" && ev.depth > maxScroll) maxScroll = ev.depth;
|
|
84
|
-
if (ev.kind === "hover") hoverTargets.add(ev.target);
|
|
85
106
|
}
|
|
86
107
|
return {
|
|
87
108
|
clicks_per_min: clicksLastMin,
|
|
109
|
+
rage_clicks: rageClicks,
|
|
88
110
|
avg_dwell_ms: dwellCount === 0 ? 0 : Math.round(dwellSum / dwellCount),
|
|
89
111
|
scroll_depth: Math.max(0, Math.min(1, maxScroll)),
|
|
90
112
|
hover_count: hoverTargets.size,
|
|
113
|
+
form_interactions: formInteractions,
|
|
114
|
+
errors_seen: errorsSeen,
|
|
91
115
|
events_seen: Math.min(buffer.length, bufferSize),
|
|
116
|
+
viewport: { width: viewportW, height: viewportH },
|
|
117
|
+
custom,
|
|
92
118
|
page_context: pageContext
|
|
93
119
|
};
|
|
94
120
|
}
|
|
@@ -100,13 +126,19 @@ function useBehaviorTracker(opts) {
|
|
|
100
126
|
bufferSize = 50,
|
|
101
127
|
onFlush,
|
|
102
128
|
pageContext,
|
|
103
|
-
seedTrace
|
|
129
|
+
seedTrace,
|
|
130
|
+
customListeners
|
|
104
131
|
} = opts;
|
|
105
132
|
const bufferRef = (0, import_react.useRef)([]);
|
|
106
133
|
const sinceFlushRef = (0, import_react.useRef)(0);
|
|
107
134
|
const lastFlushAtRef = (0, import_react.useRef)(Date.now());
|
|
108
135
|
const hoverStartRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
|
|
136
|
+
const focusStartRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
|
|
137
|
+
const lastInputAtRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
|
|
138
|
+
const recentClicksRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
|
|
109
139
|
const scrollRafRef = (0, import_react.useRef)(null);
|
|
140
|
+
const resizeRafRef = (0, import_react.useRef)(null);
|
|
141
|
+
const customSlotRef = (0, import_react.useRef)({});
|
|
110
142
|
const onFlushRef = (0, import_react.useRef)(onFlush);
|
|
111
143
|
const pageContextRef = (0, import_react.useRef)(pageContext);
|
|
112
144
|
(0, import_react.useEffect)(() => {
|
|
@@ -123,7 +155,9 @@ function useBehaviorTracker(opts) {
|
|
|
123
155
|
sinceFlushRef.current += 1;
|
|
124
156
|
setRecentEvents((prev) => {
|
|
125
157
|
const next = [...prev, ev];
|
|
126
|
-
if (next.length > RECENT_EVENTS_CAP)
|
|
158
|
+
if (next.length > RECENT_EVENTS_CAP) {
|
|
159
|
+
next.splice(0, next.length - RECENT_EVENTS_CAP);
|
|
160
|
+
}
|
|
127
161
|
return next;
|
|
128
162
|
});
|
|
129
163
|
if (sinceFlushRef.current >= flushEveryEvents) {
|
|
@@ -131,31 +165,58 @@ function useBehaviorTracker(opts) {
|
|
|
131
165
|
}
|
|
132
166
|
};
|
|
133
167
|
const flush = () => {
|
|
134
|
-
const summary = computeSummary(
|
|
168
|
+
const summary = computeSummary(
|
|
169
|
+
bufferRef.current,
|
|
170
|
+
bufferSize,
|
|
171
|
+
pageContextRef.current,
|
|
172
|
+
customSlotRef.current
|
|
173
|
+
);
|
|
135
174
|
sinceFlushRef.current = 0;
|
|
136
175
|
lastFlushAtRef.current = Date.now();
|
|
137
176
|
onFlushRef.current(summary);
|
|
138
177
|
};
|
|
139
178
|
(0, import_react.useEffect)(() => {
|
|
140
|
-
if (seedTrace
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
179
|
+
if (!seedTrace || seedTrace.length === 0) return;
|
|
180
|
+
const buf = bufferRef.current;
|
|
181
|
+
const maxT = seedTrace.reduce((m, e) => e.t > m ? e.t : m, -Infinity);
|
|
182
|
+
const now = Date.now();
|
|
183
|
+
const offset = Number.isFinite(maxT) ? now - maxT : 0;
|
|
184
|
+
const rebased = seedTrace.map((e) => ({ ...e, t: e.t + offset }));
|
|
185
|
+
buf.push(...rebased);
|
|
186
|
+
if (buf.length > bufferSize) buf.splice(0, buf.length - bufferSize);
|
|
187
|
+
const summary = computeSummary(
|
|
188
|
+
buf,
|
|
189
|
+
bufferSize,
|
|
190
|
+
pageContextRef.current,
|
|
191
|
+
customSlotRef.current
|
|
192
|
+
);
|
|
193
|
+
sinceFlushRef.current = 0;
|
|
194
|
+
lastFlushAtRef.current = Date.now();
|
|
195
|
+
onFlushRef.current(summary);
|
|
153
196
|
}, []);
|
|
154
197
|
(0, import_react.useEffect)(() => {
|
|
155
|
-
|
|
198
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
199
|
+
const el = containerRef?.current ?? (typeof document !== "undefined" ? document.body : null);
|
|
156
200
|
if (!el) return;
|
|
157
201
|
const onClick = (e) => {
|
|
158
|
-
|
|
202
|
+
const label = targetLabel(e.target);
|
|
203
|
+
const t = Date.now();
|
|
204
|
+
pushEvent({ kind: "click", target: label, t });
|
|
205
|
+
const recent = recentClicksRef.current.get(label) ?? [];
|
|
206
|
+
const filtered = recent.filter(
|
|
207
|
+
(then) => t - then < RAGE_CLICK_WINDOW_MS
|
|
208
|
+
);
|
|
209
|
+
filtered.push(t);
|
|
210
|
+
recentClicksRef.current.set(label, filtered);
|
|
211
|
+
if (filtered.length >= RAGE_CLICK_THRESHOLD) {
|
|
212
|
+
pushEvent({
|
|
213
|
+
kind: "rage-click",
|
|
214
|
+
target: label,
|
|
215
|
+
count: filtered.length,
|
|
216
|
+
t
|
|
217
|
+
});
|
|
218
|
+
recentClicksRef.current.set(label, []);
|
|
219
|
+
}
|
|
159
220
|
};
|
|
160
221
|
const onMouseOver = (e) => {
|
|
161
222
|
const label = targetLabel(e.target);
|
|
@@ -174,11 +235,46 @@ function useBehaviorTracker(opts) {
|
|
|
174
235
|
pushEvent({ kind: "hover", target: label, durationMs, t });
|
|
175
236
|
pushEvent({ kind: "dwell", target: label, durationMs, t });
|
|
176
237
|
};
|
|
238
|
+
const onFocusIn = (e) => {
|
|
239
|
+
const label = targetLabel(e.target);
|
|
240
|
+
focusStartRef.current.set(label, Date.now());
|
|
241
|
+
pushEvent({ kind: "focus", target: label, t: Date.now() });
|
|
242
|
+
};
|
|
243
|
+
const onFocusOut = (e) => {
|
|
244
|
+
const label = targetLabel(e.target);
|
|
245
|
+
const started = focusStartRef.current.get(label);
|
|
246
|
+
if (started == null) return;
|
|
247
|
+
focusStartRef.current.delete(label);
|
|
248
|
+
const durationMs = Date.now() - started;
|
|
249
|
+
if (durationMs < HOVER_MIN_MS) return;
|
|
250
|
+
pushEvent({
|
|
251
|
+
kind: "blur",
|
|
252
|
+
target: label,
|
|
253
|
+
durationMs,
|
|
254
|
+
t: Date.now()
|
|
255
|
+
});
|
|
256
|
+
};
|
|
257
|
+
const onInput = (e) => {
|
|
258
|
+
const target = e.target;
|
|
259
|
+
if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement || target instanceof HTMLElement && target.isContentEditable)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const label = targetLabel(target);
|
|
263
|
+
const now = Date.now();
|
|
264
|
+
const last = lastInputAtRef.current.get(label) ?? 0;
|
|
265
|
+
if (now - last < INPUT_THROTTLE_MS) return;
|
|
266
|
+
lastInputAtRef.current.set(label, now);
|
|
267
|
+
pushEvent({ kind: "input", target: label, t: now });
|
|
268
|
+
};
|
|
269
|
+
const onSubmit = (e) => {
|
|
270
|
+
const label = targetLabel(e.target);
|
|
271
|
+
pushEvent({ kind: "submit", target: label, t: Date.now() });
|
|
272
|
+
};
|
|
177
273
|
const computeScrollDepth = () => {
|
|
178
274
|
const rect = el.getBoundingClientRect();
|
|
179
|
-
const
|
|
275
|
+
const viewportHCalc = window.innerHeight || 1;
|
|
180
276
|
const elementH = el.scrollHeight || rect.height || 1;
|
|
181
|
-
const visibleBottom = Math.min(rect.bottom,
|
|
277
|
+
const visibleBottom = Math.min(rect.bottom, viewportHCalc);
|
|
182
278
|
const scrolledPast = Math.max(0, visibleBottom - rect.top);
|
|
183
279
|
return Math.max(0, Math.min(1, scrolledPast / elementH));
|
|
184
280
|
};
|
|
@@ -186,13 +282,60 @@ function useBehaviorTracker(opts) {
|
|
|
186
282
|
if (scrollRafRef.current != null) return;
|
|
187
283
|
scrollRafRef.current = requestAnimationFrame(() => {
|
|
188
284
|
scrollRafRef.current = null;
|
|
189
|
-
pushEvent({
|
|
285
|
+
pushEvent({
|
|
286
|
+
kind: "scroll",
|
|
287
|
+
depth: computeScrollDepth(),
|
|
288
|
+
t: Date.now()
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
};
|
|
292
|
+
const onResize = () => {
|
|
293
|
+
if (resizeRafRef.current != null) return;
|
|
294
|
+
resizeRafRef.current = requestAnimationFrame(() => {
|
|
295
|
+
resizeRafRef.current = null;
|
|
296
|
+
pushEvent({
|
|
297
|
+
kind: "viewport-change",
|
|
298
|
+
width: window.innerWidth,
|
|
299
|
+
height: window.innerHeight,
|
|
300
|
+
t: Date.now()
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
const onError = (e) => {
|
|
305
|
+
pushEvent({
|
|
306
|
+
kind: "error",
|
|
307
|
+
message: String(e.message ?? "unknown"),
|
|
308
|
+
t: Date.now()
|
|
309
|
+
});
|
|
310
|
+
};
|
|
311
|
+
const onUnhandledRejection = (e) => {
|
|
312
|
+
pushEvent({
|
|
313
|
+
kind: "error",
|
|
314
|
+
message: `unhandled-rejection: ${String(e.reason ?? "unknown")}`,
|
|
315
|
+
t: Date.now()
|
|
190
316
|
});
|
|
191
317
|
};
|
|
192
318
|
el.addEventListener("click", onClick);
|
|
193
319
|
el.addEventListener("mouseover", onMouseOver);
|
|
194
320
|
el.addEventListener("mouseout", onMouseOut);
|
|
321
|
+
el.addEventListener("focusin", onFocusIn);
|
|
322
|
+
el.addEventListener("focusout", onFocusOut);
|
|
323
|
+
el.addEventListener("input", onInput);
|
|
324
|
+
el.addEventListener("submit", onSubmit);
|
|
195
325
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
326
|
+
window.addEventListener("resize", onResize);
|
|
327
|
+
window.addEventListener("error", onError);
|
|
328
|
+
window.addEventListener("unhandledrejection", onUnhandledRejection);
|
|
329
|
+
const cleanups = [];
|
|
330
|
+
if (customListeners) {
|
|
331
|
+
for (const listener of customListeners) {
|
|
332
|
+
try {
|
|
333
|
+
const cleanup = listener.attach(el, pushEvent);
|
|
334
|
+
cleanups.push(cleanup);
|
|
335
|
+
} catch {
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
196
339
|
const interval = window.setInterval(() => {
|
|
197
340
|
if (Date.now() - lastFlushAtRef.current >= flushAfterMs) {
|
|
198
341
|
flush();
|
|
@@ -202,321 +345,514 @@ function useBehaviorTracker(opts) {
|
|
|
202
345
|
el.removeEventListener("click", onClick);
|
|
203
346
|
el.removeEventListener("mouseover", onMouseOver);
|
|
204
347
|
el.removeEventListener("mouseout", onMouseOut);
|
|
348
|
+
el.removeEventListener("focusin", onFocusIn);
|
|
349
|
+
el.removeEventListener("focusout", onFocusOut);
|
|
350
|
+
el.removeEventListener("input", onInput);
|
|
351
|
+
el.removeEventListener("submit", onSubmit);
|
|
205
352
|
window.removeEventListener("scroll", onScroll);
|
|
353
|
+
window.removeEventListener("resize", onResize);
|
|
354
|
+
window.removeEventListener("error", onError);
|
|
355
|
+
window.removeEventListener("unhandledrejection", onUnhandledRejection);
|
|
206
356
|
window.clearInterval(interval);
|
|
207
|
-
if (scrollRafRef.current != null)
|
|
357
|
+
if (scrollRafRef.current != null) {
|
|
358
|
+
cancelAnimationFrame(scrollRafRef.current);
|
|
359
|
+
}
|
|
360
|
+
if (resizeRafRef.current != null) {
|
|
361
|
+
cancelAnimationFrame(resizeRafRef.current);
|
|
362
|
+
}
|
|
363
|
+
for (const cleanup of cleanups) {
|
|
364
|
+
try {
|
|
365
|
+
cleanup();
|
|
366
|
+
} catch {
|
|
367
|
+
}
|
|
368
|
+
}
|
|
208
369
|
};
|
|
209
370
|
}, [containerRef, flushAfterMs, flushEveryEvents, bufferSize]);
|
|
210
371
|
return { recentEvents };
|
|
211
372
|
}
|
|
212
373
|
|
|
213
|
-
// src/
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
374
|
+
// src/AdaptationEngine.ts
|
|
375
|
+
var STRUCTURAL_PROPERTY_DENY = /* @__PURE__ */ new Set([
|
|
376
|
+
"display",
|
|
377
|
+
"position",
|
|
378
|
+
"visibility",
|
|
379
|
+
"float",
|
|
380
|
+
"clear",
|
|
381
|
+
"z-index",
|
|
382
|
+
"overflow",
|
|
383
|
+
"overflow-x",
|
|
384
|
+
"overflow-y",
|
|
385
|
+
"transform-origin"
|
|
386
|
+
]);
|
|
387
|
+
var AdaptationEngine = class {
|
|
388
|
+
constructor(opts) {
|
|
389
|
+
this.revertLog = [];
|
|
390
|
+
this.appliedSnapshot = [];
|
|
391
|
+
this.root = opts.root;
|
|
392
|
+
this.scope = opts.scope ?? {};
|
|
393
|
+
this.onEvent = opts.onEvent;
|
|
221
394
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
t
|
|
229
|
-
});
|
|
395
|
+
/** Apply a plan. Reverts the previous plan first so mutations don't accumulate. */
|
|
396
|
+
apply(plan) {
|
|
397
|
+
this.revertAll();
|
|
398
|
+
for (const adaptation of plan.adaptations) {
|
|
399
|
+
this.applyOne(adaptation);
|
|
400
|
+
}
|
|
230
401
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
402
|
+
/** Roll back every adaptation applied since the last revertAll. */
|
|
403
|
+
revertAll() {
|
|
404
|
+
if (this.revertLog.length === 0) return;
|
|
405
|
+
const count = this.revertLog.length;
|
|
406
|
+
for (let i = this.revertLog.length - 1; i >= 0; i--) {
|
|
407
|
+
try {
|
|
408
|
+
this.revertLog[i]();
|
|
409
|
+
} catch {
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
this.revertLog = [];
|
|
413
|
+
this.appliedSnapshot = [];
|
|
414
|
+
this.onEvent?.({ kind: "reverted", count });
|
|
415
|
+
}
|
|
416
|
+
/** Read-only view of currently applied adaptations (for telemetry). */
|
|
417
|
+
getApplied() {
|
|
418
|
+
return this.appliedSnapshot;
|
|
238
419
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
420
|
+
// ── Internals ──────────────────────────────────────────────────────
|
|
421
|
+
applyOne(adaptation) {
|
|
422
|
+
const elements = this.resolveTargets(adaptation);
|
|
423
|
+
if (elements === null) return;
|
|
424
|
+
if (elements.length === 0) {
|
|
425
|
+
this.onEvent?.({ kind: "skipped", adaptation, reason: "no-match" });
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (adaptation.kind === "set-style" && STRUCTURAL_PROPERTY_DENY.has(adaptation.property.toLowerCase())) {
|
|
429
|
+
this.onEvent?.({
|
|
430
|
+
kind: "skipped",
|
|
431
|
+
adaptation,
|
|
432
|
+
reason: `structural-property-denied:${adaptation.property}`
|
|
433
|
+
});
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
for (const el of elements) {
|
|
437
|
+
const revert = this.applyToElement(el, adaptation);
|
|
438
|
+
if (revert) this.revertLog.push(revert);
|
|
439
|
+
}
|
|
440
|
+
this.appliedSnapshot.push(adaptation);
|
|
441
|
+
this.onEvent?.({
|
|
442
|
+
kind: "applied",
|
|
443
|
+
adaptation,
|
|
444
|
+
matched: elements.length
|
|
246
445
|
});
|
|
247
446
|
}
|
|
248
|
-
|
|
447
|
+
resolveTargets(adaptation) {
|
|
448
|
+
const sel = adaptation.selector;
|
|
449
|
+
if (!sel || typeof sel !== "string") {
|
|
450
|
+
this.onEvent?.({
|
|
451
|
+
kind: "skipped",
|
|
452
|
+
adaptation,
|
|
453
|
+
reason: "invalid-selector"
|
|
454
|
+
});
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
if (this.scope.deny?.some((d) => this.selectorMatchesPattern(sel, d))) {
|
|
458
|
+
this.onEvent?.({ kind: "skipped", adaptation, reason: "scope-deny" });
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
if (this.scope.allow && !this.scope.allow.some((a) => this.selectorMatchesPattern(sel, a))) {
|
|
462
|
+
this.onEvent?.({
|
|
463
|
+
kind: "skipped",
|
|
464
|
+
adaptation,
|
|
465
|
+
reason: "scope-not-in-allow"
|
|
466
|
+
});
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
let nodes;
|
|
470
|
+
try {
|
|
471
|
+
if (sel === ":root" || sel === "html") {
|
|
472
|
+
return [document.documentElement];
|
|
473
|
+
}
|
|
474
|
+
nodes = this.root.querySelectorAll(sel);
|
|
475
|
+
} catch {
|
|
476
|
+
this.onEvent?.({
|
|
477
|
+
kind: "skipped",
|
|
478
|
+
adaptation,
|
|
479
|
+
reason: "selector-syntax-error"
|
|
480
|
+
});
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
return Array.from(nodes).filter(
|
|
484
|
+
(n) => n instanceof HTMLElement
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
selectorMatchesPattern(selector, pattern) {
|
|
488
|
+
if (selector === pattern) return true;
|
|
489
|
+
return selector.includes(pattern);
|
|
490
|
+
}
|
|
491
|
+
applyToElement(el, adaptation) {
|
|
492
|
+
switch (adaptation.kind) {
|
|
493
|
+
case "set-css-var": {
|
|
494
|
+
const prev = el.style.getPropertyValue(adaptation.name);
|
|
495
|
+
const prevPriority = el.style.getPropertyPriority(adaptation.name);
|
|
496
|
+
el.style.setProperty(adaptation.name, adaptation.value);
|
|
497
|
+
return () => {
|
|
498
|
+
if (prev) el.style.setProperty(adaptation.name, prev, prevPriority);
|
|
499
|
+
else el.style.removeProperty(adaptation.name);
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
case "add-class": {
|
|
503
|
+
if (el.classList.contains(adaptation.className)) return null;
|
|
504
|
+
el.classList.add(adaptation.className);
|
|
505
|
+
return () => el.classList.remove(adaptation.className);
|
|
506
|
+
}
|
|
507
|
+
case "remove-class": {
|
|
508
|
+
if (!el.classList.contains(adaptation.className)) return null;
|
|
509
|
+
el.classList.remove(adaptation.className);
|
|
510
|
+
return () => el.classList.add(adaptation.className);
|
|
511
|
+
}
|
|
512
|
+
case "set-style": {
|
|
513
|
+
const prop = adaptation.property;
|
|
514
|
+
const prev = el.style.getPropertyValue(prop);
|
|
515
|
+
const prevPriority = el.style.getPropertyPriority(prop);
|
|
516
|
+
el.style.setProperty(prop, adaptation.value);
|
|
517
|
+
return () => {
|
|
518
|
+
if (prev) el.style.setProperty(prop, prev, prevPriority);
|
|
519
|
+
else el.style.removeProperty(prop);
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
case "set-attribute": {
|
|
523
|
+
const had = el.hasAttribute(adaptation.name);
|
|
524
|
+
const prev = el.getAttribute(adaptation.name);
|
|
525
|
+
el.setAttribute(adaptation.name, adaptation.value);
|
|
526
|
+
return () => {
|
|
527
|
+
if (had && prev !== null) el.setAttribute(adaptation.name, prev);
|
|
528
|
+
else el.removeAttribute(adaptation.name);
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
case "set-aria-label": {
|
|
532
|
+
const had = el.hasAttribute("aria-label");
|
|
533
|
+
const prev = el.getAttribute("aria-label");
|
|
534
|
+
el.setAttribute("aria-label", adaptation.value);
|
|
535
|
+
return () => {
|
|
536
|
+
if (had && prev !== null) el.setAttribute("aria-label", prev);
|
|
537
|
+
else el.removeAttribute("aria-label");
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
default: {
|
|
541
|
+
const _exhaustive = adaptation;
|
|
542
|
+
void _exhaustive;
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
// src/domSnapshot.ts
|
|
550
|
+
function snapshotVisibleSelectors(root, opts = {}) {
|
|
551
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
552
|
+
return [];
|
|
553
|
+
}
|
|
554
|
+
const max = opts.maxSelectors ?? 50;
|
|
555
|
+
const seen = /* @__PURE__ */ new Set();
|
|
556
|
+
const out = [];
|
|
557
|
+
const viewportH = window.innerHeight || 1;
|
|
558
|
+
const viewportW = window.innerWidth || 1;
|
|
559
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
|
|
560
|
+
acceptNode: (node2) => {
|
|
561
|
+
if (!(node2 instanceof HTMLElement)) return NodeFilter.FILTER_SKIP;
|
|
562
|
+
const rect = node2.getBoundingClientRect();
|
|
563
|
+
if (rect.width === 0 || rect.height === 0) return NodeFilter.FILTER_SKIP;
|
|
564
|
+
if (rect.bottom < 0 || rect.top > viewportH) return NodeFilter.FILTER_SKIP;
|
|
565
|
+
if (rect.right < 0 || rect.left > viewportW) return NodeFilter.FILTER_SKIP;
|
|
566
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
let node = walker.nextNode();
|
|
570
|
+
while (node && out.length < max) {
|
|
571
|
+
if (node instanceof HTMLElement) {
|
|
572
|
+
const sel = pickSelector(node);
|
|
573
|
+
if (sel && !seen.has(sel) && passesScope(sel, opts.scope)) {
|
|
574
|
+
seen.add(sel);
|
|
575
|
+
out.push(sel);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
node = walker.nextNode();
|
|
579
|
+
}
|
|
580
|
+
return out;
|
|
249
581
|
}
|
|
250
|
-
function
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
582
|
+
function pickSelector(el) {
|
|
583
|
+
if (el.id) return `#${cssEscape(el.id)}`;
|
|
584
|
+
const begenId = el.getAttribute("data-begen-id");
|
|
585
|
+
if (begenId) return `[data-begen-id="${attrEscape(begenId)}"]`;
|
|
586
|
+
const testId = el.getAttribute("data-testid");
|
|
587
|
+
if (testId) return `[data-testid="${attrEscape(testId)}"]`;
|
|
588
|
+
const role = el.getAttribute("role");
|
|
589
|
+
if (role) return `${el.tagName.toLowerCase()}[role="${attrEscape(role)}"]`;
|
|
590
|
+
const semantic = SEMANTIC_TAGS.has(el.tagName.toLowerCase());
|
|
591
|
+
if (semantic) {
|
|
592
|
+
const aria = el.getAttribute("aria-label");
|
|
593
|
+
if (aria) {
|
|
594
|
+
return `${el.tagName.toLowerCase()}[aria-label="${attrEscape(aria)}"]`;
|
|
595
|
+
}
|
|
596
|
+
return el.tagName.toLowerCase();
|
|
265
597
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
598
|
+
const cls = pickStableClass(el);
|
|
599
|
+
if (cls) return `.${cssEscape(cls)}`;
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
var SEMANTIC_TAGS = /* @__PURE__ */ new Set([
|
|
603
|
+
"header",
|
|
604
|
+
"main",
|
|
605
|
+
"nav",
|
|
606
|
+
"footer",
|
|
607
|
+
"aside",
|
|
608
|
+
"article",
|
|
609
|
+
"section",
|
|
610
|
+
"form",
|
|
611
|
+
"dialog"
|
|
612
|
+
]);
|
|
613
|
+
function pickStableClass(el) {
|
|
614
|
+
for (const cls of el.classList) {
|
|
615
|
+
if (cls.includes(":")) continue;
|
|
616
|
+
if (/^[a-z]+-[a-z0-9-]+$/.test(cls)) continue;
|
|
617
|
+
if (cls.length < 3) continue;
|
|
618
|
+
return cls;
|
|
269
619
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
});
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
function passesScope(selector, scope) {
|
|
623
|
+
if (!scope) return true;
|
|
624
|
+
if (scope.deny?.some((d) => selector === d || selector.includes(d))) {
|
|
625
|
+
return false;
|
|
277
626
|
}
|
|
278
|
-
|
|
627
|
+
if (scope.allow && !scope.allow.some((a) => selector === a || selector.includes(a))) {
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
return true;
|
|
279
631
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
return
|
|
283
|
-
},
|
|
284
|
-
get deliberate() {
|
|
285
|
-
return buildDeliberate();
|
|
632
|
+
function cssEscape(s) {
|
|
633
|
+
if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
|
|
634
|
+
return CSS.escape(s);
|
|
286
635
|
}
|
|
287
|
-
|
|
636
|
+
return s.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
|
|
637
|
+
}
|
|
638
|
+
function attrEscape(s) {
|
|
639
|
+
return s.replace(/"/g, '\\"');
|
|
640
|
+
}
|
|
288
641
|
|
|
289
|
-
// src/
|
|
290
|
-
var
|
|
291
|
-
|
|
642
|
+
// src/BeGenProvider.tsx
|
|
643
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
644
|
+
var BeGenContext = React.createContext(null);
|
|
645
|
+
function useBeGenContext() {
|
|
646
|
+
const ctx = React.useContext(BeGenContext);
|
|
647
|
+
if (!ctx) {
|
|
648
|
+
throw new Error("useBeGenContext must be used inside <BeGenProvider>");
|
|
649
|
+
}
|
|
650
|
+
return ctx;
|
|
651
|
+
}
|
|
652
|
+
function BeGenProvider(props) {
|
|
292
653
|
const {
|
|
293
|
-
|
|
294
|
-
classify,
|
|
295
|
-
variantProps,
|
|
654
|
+
designSystem,
|
|
296
655
|
pageContext,
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
656
|
+
classify,
|
|
657
|
+
scope,
|
|
658
|
+
rateLimitMs = 5e3,
|
|
659
|
+
triggerEveryEvents = 5,
|
|
660
|
+
flushAfterMs = 5e3,
|
|
661
|
+
flushEveryEvents = 10,
|
|
662
|
+
bufferSize = 50,
|
|
663
|
+
customListeners,
|
|
664
|
+
containerRef,
|
|
665
|
+
seedTrace,
|
|
666
|
+
children
|
|
301
667
|
} = props;
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
const [
|
|
305
|
-
const [
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
|
|
668
|
+
const engineRef = React.useRef(null);
|
|
669
|
+
const [summary, setSummary] = React.useState(null);
|
|
670
|
+
const [lastPlan, setLastPlan] = React.useState(null);
|
|
671
|
+
const [appliedAdaptations, setAppliedAdaptations] = React.useState([]);
|
|
672
|
+
const lastClassifyAtRef = React.useRef(0);
|
|
673
|
+
const inFlightRef = React.useRef(false);
|
|
674
|
+
const eventsSinceClassifyRef = React.useRef(0);
|
|
675
|
+
const lastPlanHashRef = React.useRef(null);
|
|
676
|
+
const classifyRef = React.useRef(classify);
|
|
677
|
+
React.useEffect(() => {
|
|
311
678
|
classifyRef.current = classify;
|
|
312
679
|
}, [classify]);
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}, [
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
680
|
+
const designSystemRef = React.useRef(designSystem);
|
|
681
|
+
React.useEffect(() => {
|
|
682
|
+
designSystemRef.current = designSystem;
|
|
683
|
+
}, [designSystem]);
|
|
684
|
+
const pageContextRef = React.useRef(pageContext);
|
|
685
|
+
React.useEffect(() => {
|
|
686
|
+
pageContextRef.current = pageContext;
|
|
687
|
+
}, [pageContext]);
|
|
688
|
+
const scopeRef = React.useRef(scope);
|
|
689
|
+
React.useEffect(() => {
|
|
690
|
+
scopeRef.current = scope;
|
|
691
|
+
}, [scope]);
|
|
692
|
+
React.useEffect(() => {
|
|
693
|
+
if (typeof document === "undefined") return;
|
|
694
|
+
const root = containerRef?.current ?? document.body;
|
|
695
|
+
if (!root) return;
|
|
696
|
+
engineRef.current = new AdaptationEngine({
|
|
697
|
+
root,
|
|
698
|
+
scope: scopeRef.current,
|
|
699
|
+
onEvent: () => {
|
|
700
|
+
if (engineRef.current) {
|
|
701
|
+
setAppliedAdaptations(engineRef.current.getApplied().slice());
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
return () => {
|
|
706
|
+
engineRef.current?.revertAll();
|
|
707
|
+
engineRef.current = null;
|
|
708
|
+
};
|
|
709
|
+
}, []);
|
|
710
|
+
const handleFlush = React.useCallback(
|
|
711
|
+
async (s) => {
|
|
712
|
+
setSummary(s);
|
|
713
|
+
eventsSinceClassifyRef.current += 1;
|
|
714
|
+
const fn = classifyRef.current;
|
|
715
|
+
if (!fn) return;
|
|
716
|
+
if (inFlightRef.current) return;
|
|
717
|
+
const now = Date.now();
|
|
718
|
+
if (now - lastClassifyAtRef.current < rateLimitMs) return;
|
|
719
|
+
if (eventsSinceClassifyRef.current < triggerEveryEvents) return;
|
|
720
|
+
lastClassifyAtRef.current = now;
|
|
721
|
+
eventsSinceClassifyRef.current = 0;
|
|
722
|
+
inFlightRef.current = true;
|
|
723
|
+
try {
|
|
724
|
+
const root = containerRef?.current ?? (typeof document !== "undefined" ? document.body : null) ?? null;
|
|
725
|
+
const visibleSelectors = root ? snapshotVisibleSelectors(root, { scope: scopeRef.current }) : [];
|
|
726
|
+
const route = s.page_context?.route ?? "/";
|
|
727
|
+
const input = {
|
|
728
|
+
summary: s,
|
|
729
|
+
designSystem: designSystemRef.current,
|
|
730
|
+
dom: { visibleSelectors, route }
|
|
731
|
+
};
|
|
732
|
+
const plan = await fn(input);
|
|
733
|
+
if (!plan || !Array.isArray(plan.adaptations)) return;
|
|
734
|
+
const hash = hashPlan(plan);
|
|
735
|
+
if (hash === lastPlanHashRef.current) return;
|
|
736
|
+
lastPlanHashRef.current = hash;
|
|
737
|
+
setLastPlan(plan);
|
|
738
|
+
engineRef.current?.apply(plan);
|
|
739
|
+
} catch {
|
|
740
|
+
} finally {
|
|
741
|
+
inFlightRef.current = false;
|
|
742
|
+
}
|
|
743
|
+
},
|
|
744
|
+
[rateLimitMs, triggerEveryEvents, containerRef]
|
|
745
|
+
);
|
|
337
746
|
useBehaviorTracker({
|
|
338
747
|
containerRef,
|
|
339
748
|
onFlush: handleFlush,
|
|
340
749
|
pageContext,
|
|
341
|
-
seedTrace
|
|
750
|
+
seedTrace,
|
|
751
|
+
customListeners,
|
|
752
|
+
flushAfterMs,
|
|
753
|
+
flushEveryEvents,
|
|
754
|
+
bufferSize
|
|
342
755
|
});
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
"data-begen-variant": currentVariant,
|
|
352
|
-
children: [
|
|
353
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
354
|
-
"div",
|
|
355
|
-
{
|
|
356
|
-
style: {
|
|
357
|
-
opacity: 1,
|
|
358
|
-
transition: "opacity 200ms ease",
|
|
359
|
-
animation: "begen-fade-in 200ms ease"
|
|
360
|
-
},
|
|
361
|
-
children: ActiveComponent ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ActiveComponent, { ...variantProps ?? {} }) : null
|
|
362
|
-
},
|
|
363
|
-
currentVariant
|
|
364
|
-
),
|
|
365
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BeGenSurfaceStyles, {}),
|
|
366
|
-
lastDirective && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
367
|
-
"span",
|
|
368
|
-
{
|
|
369
|
-
style: { display: "none" },
|
|
370
|
-
"data-begen-confidence": lastDirective.confidence,
|
|
371
|
-
"data-begen-reasoning": lastDirective.reasoning,
|
|
372
|
-
"data-begen-events": lastSummary?.events_seen ?? 0
|
|
373
|
-
}
|
|
374
|
-
)
|
|
375
|
-
]
|
|
376
|
-
}
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
var STYLE_INJECTED = "__begen_styles__";
|
|
380
|
-
function BeGenSurfaceStyles() {
|
|
381
|
-
React2.useEffect(() => {
|
|
382
|
-
if (typeof document === "undefined") return;
|
|
383
|
-
if (document[STYLE_INJECTED]) return;
|
|
384
|
-
const tag = document.createElement("style");
|
|
385
|
-
tag.setAttribute("data-begen-styles", "");
|
|
386
|
-
tag.textContent = `@keyframes begen-fade-in { from { opacity: 0 } to { opacity: 1 } }`;
|
|
387
|
-
document.head.appendChild(tag);
|
|
388
|
-
document[STYLE_INJECTED] = true;
|
|
756
|
+
const recentEventsRef = React.useRef([]);
|
|
757
|
+
const applyPlan = React.useCallback((plan) => {
|
|
758
|
+
if (!plan || !Array.isArray(plan.adaptations)) return;
|
|
759
|
+
const hash = hashPlan(plan);
|
|
760
|
+
if (hash === lastPlanHashRef.current) return;
|
|
761
|
+
lastPlanHashRef.current = hash;
|
|
762
|
+
setLastPlan(plan);
|
|
763
|
+
engineRef.current?.apply(plan);
|
|
389
764
|
}, []);
|
|
390
|
-
|
|
765
|
+
const getDesignSystem = React.useCallback(
|
|
766
|
+
() => designSystemRef.current,
|
|
767
|
+
[]
|
|
768
|
+
);
|
|
769
|
+
const value = React.useMemo(
|
|
770
|
+
() => ({
|
|
771
|
+
summary,
|
|
772
|
+
lastPlan,
|
|
773
|
+
appliedAdaptations,
|
|
774
|
+
recentEvents: recentEventsRef.current,
|
|
775
|
+
applyPlan,
|
|
776
|
+
getDesignSystem
|
|
777
|
+
}),
|
|
778
|
+
[summary, lastPlan, appliedAdaptations, applyPlan, getDesignSystem]
|
|
779
|
+
);
|
|
780
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BeGenContext.Provider, { value, children });
|
|
391
781
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
782
|
+
function hashPlan(plan) {
|
|
783
|
+
const parts = plan.adaptations.map((a) => {
|
|
784
|
+
switch (a.kind) {
|
|
785
|
+
case "set-css-var":
|
|
786
|
+
return `v|${a.selector}|${a.name}|${a.value}`;
|
|
787
|
+
case "add-class":
|
|
788
|
+
return `+|${a.selector}|${a.className}`;
|
|
789
|
+
case "remove-class":
|
|
790
|
+
return `-|${a.selector}|${a.className}`;
|
|
791
|
+
case "set-style":
|
|
792
|
+
return `s|${a.selector}|${a.property}|${a.value}`;
|
|
793
|
+
case "set-attribute":
|
|
794
|
+
return `a|${a.selector}|${a.name}|${a.value}`;
|
|
795
|
+
case "set-aria-label":
|
|
796
|
+
return `l|${a.selector}|${a.value}`;
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
return parts.join("\n");
|
|
401
800
|
}
|
|
402
801
|
|
|
403
|
-
// src/
|
|
404
|
-
var
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
You receive a JSON object describing a user's recent interaction pattern on
|
|
408
|
-
a product listing page. Decide which UI variant best serves them right now.
|
|
409
|
-
|
|
410
|
-
Variants:
|
|
411
|
-
- "decisive": user knows what they want; minimize friction. Dense grid,
|
|
412
|
-
prominent prices, fast paths to cart, no recommendations.
|
|
413
|
-
Signals: high clicks/min, low dwell, high scroll depth, few hovers.
|
|
414
|
-
- "deliberate": user is researching; help them compare. Larger cards,
|
|
415
|
-
reviews surfaced inline, "people also viewed", expandable detail.
|
|
416
|
-
Signals: low clicks/min, high dwell, hovers across multiple products.
|
|
417
|
-
- "neutral": insufficient signal yet, or pattern is mixed. Baseline grid.
|
|
418
|
-
Default for first ~10 events.
|
|
419
|
-
|
|
420
|
-
Examples:
|
|
421
|
-
Input: {"clicks_per_min":14,"avg_dwell_ms":820,"scroll_depth":0.91,"hover_count":2,"events_seen":18}
|
|
422
|
-
Output: {"variant":"decisive","confidence":0.86,"reasoning":"Fast clicks, low dwell, deep scroll \u2014 purposeful navigation."}
|
|
423
|
-
|
|
424
|
-
Input: {"clicks_per_min":3,"avg_dwell_ms":7400,"scroll_depth":0.42,"hover_count":4,"events_seen":22}
|
|
425
|
-
Output: {"variant":"deliberate","confidence":0.81,"reasoning":"Slow pace, long dwell, multi-product hover \u2014 comparing options."}
|
|
426
|
-
|
|
427
|
-
Input: {"clicks_per_min":5,"avg_dwell_ms":2100,"scroll_depth":0.55,"hover_count":2,"events_seen":7}
|
|
428
|
-
Output: {"variant":"neutral","confidence":0.6,"reasoning":"Not enough events yet to commit to a mode."}
|
|
429
|
-
|
|
430
|
-
Return ONLY a JSON object. No prose, no markdown.
|
|
431
|
-
`.trim();
|
|
432
|
-
var VALID_VARIANTS = /* @__PURE__ */ new Set(["decisive", "deliberate", "neutral"]);
|
|
433
|
-
var FALLBACK = {
|
|
434
|
-
variant: "neutral",
|
|
802
|
+
// src/adapters/http.ts
|
|
803
|
+
var FALLBACK_PLAN = {
|
|
804
|
+
adaptations: [],
|
|
435
805
|
confidence: 0,
|
|
436
|
-
reasoning: "
|
|
806
|
+
reasoning: "HTTP adapter error."
|
|
437
807
|
};
|
|
438
|
-
function
|
|
439
|
-
const {
|
|
440
|
-
|
|
808
|
+
function createHttpAdapter(opts) {
|
|
809
|
+
const {
|
|
810
|
+
url,
|
|
811
|
+
headers,
|
|
812
|
+
fetchImpl,
|
|
813
|
+
timeoutMs = 8e3,
|
|
814
|
+
bodyTransform,
|
|
815
|
+
responseTransform
|
|
816
|
+
} = opts;
|
|
441
817
|
const fetcher = fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : null);
|
|
442
|
-
return async (
|
|
443
|
-
if (!fetcher) return
|
|
818
|
+
return async (input) => {
|
|
819
|
+
if (!fetcher) return FALLBACK_PLAN;
|
|
820
|
+
const ac = new AbortController();
|
|
821
|
+
const timer = setTimeout(() => ac.abort(), timeoutMs);
|
|
444
822
|
try {
|
|
445
|
-
const
|
|
823
|
+
const body = bodyTransform ? bodyTransform(input) : input;
|
|
824
|
+
const res = await fetcher(url, {
|
|
446
825
|
method: "POST",
|
|
447
|
-
headers: { "content-type": "application/json" },
|
|
448
|
-
body: JSON.stringify(
|
|
449
|
-
|
|
450
|
-
contents: [
|
|
451
|
-
{
|
|
452
|
-
role: "user",
|
|
453
|
-
parts: [{ text: JSON.stringify(summary) }]
|
|
454
|
-
}
|
|
455
|
-
],
|
|
456
|
-
generationConfig: {
|
|
457
|
-
responseMimeType: "application/json",
|
|
458
|
-
temperature: 0.2
|
|
459
|
-
}
|
|
460
|
-
})
|
|
826
|
+
headers: { "content-type": "application/json", ...headers },
|
|
827
|
+
body: JSON.stringify(body),
|
|
828
|
+
signal: ac.signal
|
|
461
829
|
});
|
|
462
|
-
if (!res.ok) return
|
|
463
|
-
const
|
|
464
|
-
const
|
|
465
|
-
if (!
|
|
466
|
-
|
|
467
|
-
if (!parsed || typeof parsed !== "object" || typeof parsed.variant !== "string" || !VALID_VARIANTS.has(parsed.variant) || typeof parsed.reasoning !== "string") {
|
|
468
|
-
return FALLBACK;
|
|
469
|
-
}
|
|
470
|
-
const confidence = typeof parsed.confidence === "number" && Number.isFinite(parsed.confidence) ? Math.max(0, Math.min(1, parsed.confidence)) : 0.5;
|
|
471
|
-
return {
|
|
472
|
-
variant: parsed.variant,
|
|
473
|
-
confidence,
|
|
474
|
-
reasoning: parsed.reasoning
|
|
475
|
-
};
|
|
830
|
+
if (!res.ok) return FALLBACK_PLAN;
|
|
831
|
+
const raw = await res.json();
|
|
832
|
+
const plan = responseTransform ? responseTransform(raw) : raw;
|
|
833
|
+
if (!isValidPlan(plan)) return FALLBACK_PLAN;
|
|
834
|
+
return plan;
|
|
476
835
|
} catch {
|
|
477
|
-
return
|
|
836
|
+
return FALLBACK_PLAN;
|
|
837
|
+
} finally {
|
|
838
|
+
clearTimeout(timer);
|
|
478
839
|
}
|
|
479
840
|
};
|
|
480
841
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
confidence: 0.5,
|
|
489
|
-
reasoning: "Insufficient events."
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
if (summary.clicks_per_min > 8 && summary.avg_dwell_ms < 2e3) {
|
|
493
|
-
return {
|
|
494
|
-
variant: "decisive",
|
|
495
|
-
confidence: 0.8,
|
|
496
|
-
reasoning: "Fast pace, low dwell."
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
if (summary.avg_dwell_ms > 4e3 && summary.hover_count > 2) {
|
|
500
|
-
return {
|
|
501
|
-
variant: "deliberate",
|
|
502
|
-
confidence: 0.8,
|
|
503
|
-
reasoning: "Slow pace, multi-hover."
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
return {
|
|
507
|
-
variant: "neutral",
|
|
508
|
-
confidence: 0.6,
|
|
509
|
-
reasoning: "Mixed signals."
|
|
510
|
-
};
|
|
511
|
-
};
|
|
842
|
+
function isValidPlan(p) {
|
|
843
|
+
if (!p || typeof p !== "object") return false;
|
|
844
|
+
const plan = p;
|
|
845
|
+
if (!Array.isArray(plan.adaptations)) return false;
|
|
846
|
+
if (typeof plan.confidence !== "number") return false;
|
|
847
|
+
if (typeof plan.reasoning !== "string") return false;
|
|
848
|
+
return true;
|
|
512
849
|
}
|
|
513
850
|
// Annotate the CommonJS export names for ESM import in node:
|
|
514
851
|
0 && (module.exports = {
|
|
852
|
+
AdaptationEngine,
|
|
515
853
|
BeGenProvider,
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
createGeminiClassifier,
|
|
519
|
-
createHeuristicClassifier,
|
|
854
|
+
createHttpAdapter,
|
|
855
|
+
snapshotVisibleSelectors,
|
|
520
856
|
useBeGenContext,
|
|
521
857
|
useBehaviorTracker
|
|
522
858
|
});
|