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