cbrowser 18.78.0 → 18.80.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/dist/analysis/accessibility-empathy.d.ts.map +1 -1
- package/dist/analysis/accessibility-empathy.js +395 -66
- package/dist/analysis/accessibility-empathy.js.map +1 -1
- package/dist/analysis/agent-ready-audit.d.ts.map +1 -1
- package/dist/analysis/agent-ready-audit.js +78 -0
- package/dist/analysis/agent-ready-audit.js.map +1 -1
- package/dist/analysis/page-understanding.d.ts +139 -1
- package/dist/analysis/page-understanding.d.ts.map +1 -1
- package/dist/analysis/page-understanding.js +179 -12
- package/dist/analysis/page-understanding.js.map +1 -1
- package/dist/artifact-store.d.ts +19 -5
- package/dist/artifact-store.d.ts.map +1 -1
- package/dist/artifact-store.js +41 -11
- package/dist/artifact-store.js.map +1 -1
- package/dist/browser.d.ts +9 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +81 -5
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +55 -10
- package/dist/cli.js.map +1 -1
- package/dist/cognitive/index.d.ts.map +1 -1
- package/dist/cognitive/index.js +3 -2
- package/dist/cognitive/index.js.map +1 -1
- package/dist/config.d.ts +57 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +234 -7
- package/dist/config.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server-remote.d.ts.map +1 -1
- package/dist/mcp-server-remote.js +108 -4
- package/dist/mcp-server-remote.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +62 -938
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-tools/account-personas.d.ts +41 -0
- package/dist/mcp-tools/account-personas.d.ts.map +1 -0
- package/dist/mcp-tools/account-personas.js +98 -0
- package/dist/mcp-tools/account-personas.js.map +1 -0
- package/dist/mcp-tools/base/analysis-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/analysis-tools.js +12 -2
- package/dist/mcp-tools/base/analysis-tools.js.map +1 -1
- package/dist/mcp-tools/base/artifact-tools.d.ts +3 -0
- package/dist/mcp-tools/base/artifact-tools.d.ts.map +1 -0
- package/dist/mcp-tools/base/artifact-tools.js +78 -0
- package/dist/mcp-tools/base/artifact-tools.js.map +1 -0
- package/dist/mcp-tools/base/audit-tools.d.ts +11 -0
- package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/audit-tools.js +595 -340
- package/dist/mcp-tools/base/audit-tools.js.map +1 -1
- package/dist/mcp-tools/base/browser-management-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/browser-management-tools.js +18 -0
- package/dist/mcp-tools/base/browser-management-tools.js.map +1 -1
- package/dist/mcp-tools/base/bug-analysis-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/bug-analysis-tools.js +59 -3
- package/dist/mcp-tools/base/bug-analysis-tools.js.map +1 -1
- package/dist/mcp-tools/base/capture-tools.d.ts +37 -0
- package/dist/mcp-tools/base/capture-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/capture-tools.js +267 -6
- package/dist/mcp-tools/base/capture-tools.js.map +1 -1
- package/dist/mcp-tools/base/cognitive-tools.d.ts +12 -0
- package/dist/mcp-tools/base/cognitive-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/cognitive-tools.js +63 -15
- package/dist/mcp-tools/base/cognitive-tools.js.map +1 -1
- package/dist/mcp-tools/base/extraction-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/extraction-tools.js +33 -2
- package/dist/mcp-tools/base/extraction-tools.js.map +1 -1
- package/dist/mcp-tools/base/index.d.ts +2 -0
- package/dist/mcp-tools/base/index.d.ts.map +1 -1
- package/dist/mcp-tools/base/index.js +9 -0
- package/dist/mcp-tools/base/index.js.map +1 -1
- package/dist/mcp-tools/base/marketing-tools.js +3 -3
- package/dist/mcp-tools/base/marketing-tools.js.map +1 -1
- package/dist/mcp-tools/base/persona-comparison-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/persona-comparison-tools.js +529 -40
- package/dist/mcp-tools/base/persona-comparison-tools.js.map +1 -1
- package/dist/mcp-tools/base/site-knowledge-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/site-knowledge-tools.js +36 -3
- package/dist/mcp-tools/base/site-knowledge-tools.js.map +1 -1
- package/dist/mcp-tools/base/values-tools.d.ts +113 -0
- package/dist/mcp-tools/base/values-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/values-tools.js +868 -78
- package/dist/mcp-tools/base/values-tools.js.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.js +130 -10
- package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
- package/dist/mcp-tools/index.d.ts +3 -1
- package/dist/mcp-tools/index.d.ts.map +1 -1
- package/dist/mcp-tools/index.js +3 -28
- package/dist/mcp-tools/index.js.map +1 -1
- package/dist/mcp-tools/persona-creation-tools.d.ts.map +1 -1
- package/dist/mcp-tools/persona-creation-tools.js +363 -20
- package/dist/mcp-tools/persona-creation-tools.js.map +1 -1
- package/dist/mcp-tools/persona-lifecycle-tools.d.ts +3 -0
- package/dist/mcp-tools/persona-lifecycle-tools.d.ts.map +1 -0
- package/dist/mcp-tools/persona-lifecycle-tools.js +406 -0
- package/dist/mcp-tools/persona-lifecycle-tools.js.map +1 -0
- package/dist/mcp-tools/screenshot-utils.d.ts.map +1 -1
- package/dist/mcp-tools/screenshot-utils.js +56 -10
- package/dist/mcp-tools/screenshot-utils.js.map +1 -1
- package/dist/mcp-tools/tier-gate.js +2 -2
- package/dist/mcp-tools/tier-gate.js.map +1 -1
- package/dist/mcp-tools/tool-categories.d.ts.map +1 -1
- package/dist/mcp-tools/tool-categories.js +4 -1
- package/dist/mcp-tools/tool-categories.js.map +1 -1
- package/dist/mcp-tools/ui-resources.d.ts +167 -0
- package/dist/mcp-tools/ui-resources.d.ts.map +1 -0
- package/dist/mcp-tools/ui-resources.js +707 -0
- package/dist/mcp-tools/ui-resources.js.map +1 -0
- package/dist/mcp-tools/widget-kit.d.ts +299 -0
- package/dist/mcp-tools/widget-kit.d.ts.map +1 -0
- package/dist/mcp-tools/widget-kit.js +2034 -0
- package/dist/mcp-tools/widget-kit.js.map +1 -0
- package/dist/mcp-ui-resources.d.ts.map +1 -1
- package/dist/mcp-ui-resources.js +14 -4
- package/dist/mcp-ui-resources.js.map +1 -1
- package/dist/persona-questionnaire.d.ts +69 -1
- package/dist/persona-questionnaire.d.ts.map +1 -1
- package/dist/persona-questionnaire.js +177 -76
- package/dist/persona-questionnaire.js.map +1 -1
- package/dist/persona-scope.d.ts +20 -0
- package/dist/persona-scope.d.ts.map +1 -0
- package/dist/persona-scope.js +59 -0
- package/dist/persona-scope.js.map +1 -0
- package/dist/personas.d.ts +51 -17
- package/dist/personas.d.ts.map +1 -1
- package/dist/personas.js +947 -286
- package/dist/personas.js.map +1 -1
- package/dist/recording/capture-player.d.ts +68 -0
- package/dist/recording/capture-player.d.ts.map +1 -0
- package/dist/recording/capture-player.js +298 -0
- package/dist/recording/capture-player.js.map +1 -0
- package/dist/recording/engine.d.ts +52 -0
- package/dist/recording/engine.d.ts.map +1 -1
- package/dist/recording/engine.js +241 -3
- package/dist/recording/engine.js.map +1 -1
- package/dist/recording/inline-player.d.ts +39 -0
- package/dist/recording/inline-player.d.ts.map +1 -0
- package/dist/recording/inline-player.js +272 -0
- package/dist/recording/inline-player.js.map +1 -0
- package/dist/recording/saliency-overlay.d.ts +133 -0
- package/dist/recording/saliency-overlay.d.ts.map +1 -0
- package/dist/recording/saliency-overlay.js +360 -0
- package/dist/recording/saliency-overlay.js.map +1 -0
- package/dist/recording/types.d.ts +32 -0
- package/dist/recording/types.d.ts.map +1 -1
- package/dist/recording/types.js +46 -0
- package/dist/recording/types.js.map +1 -1
- package/dist/site-model/manager.d.ts +20 -0
- package/dist/site-model/manager.d.ts.map +1 -1
- package/dist/site-model/manager.js +52 -0
- package/dist/site-model/manager.js.map +1 -1
- package/dist/trait-reference.d.ts +26 -2
- package/dist/trait-reference.d.ts.map +1 -1
- package/dist/trait-reference.js +129 -19
- package/dist/trait-reference.js.map +1 -1
- package/dist/types.d.ts +105 -18
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/url-resolution.d.ts +58 -0
- package/dist/url-resolution.d.ts.map +1 -0
- package/dist/url-resolution.js +143 -0
- package/dist/url-resolution.js.map +1 -0
- package/dist/values/big-five-inference.d.ts +121 -0
- package/dist/values/big-five-inference.d.ts.map +1 -0
- package/dist/values/big-five-inference.js +241 -0
- package/dist/values/big-five-inference.js.map +1 -0
- package/dist/values/big-five-values.d.ts +76 -0
- package/dist/values/big-five-values.d.ts.map +1 -0
- package/dist/values/big-five-values.js +152 -0
- package/dist/values/big-five-values.js.map +1 -0
- package/dist/values/index.d.ts +1 -1
- package/dist/values/index.d.ts.map +1 -1
- package/dist/values/index.js +1 -1
- package/dist/values/index.js.map +1 -1
- package/dist/values/persona-completeness.d.ts +91 -0
- package/dist/values/persona-completeness.d.ts.map +1 -0
- package/dist/values/persona-completeness.js +114 -0
- package/dist/values/persona-completeness.js.map +1 -0
- package/dist/values/persona-diff.d.ts +50 -0
- package/dist/values/persona-diff.d.ts.map +1 -0
- package/dist/values/persona-diff.js +126 -0
- package/dist/values/persona-diff.js.map +1 -0
- package/dist/values/persona-values.d.ts +16 -0
- package/dist/values/persona-values.d.ts.map +1 -1
- package/dist/values/persona-values.js +33 -0
- package/dist/values/persona-values.js.map +1 -1
- package/dist/values/schwartz-values.d.ts +9 -1
- package/dist/values/schwartz-values.d.ts.map +1 -1
- package/dist/values/schwartz-values.js +10 -2
- package/dist/values/schwartz-values.js.map +1 -1
- package/dist/values/value-mappings.d.ts +41 -2
- package/dist/values/value-mappings.d.ts.map +1 -1
- package/dist/values/value-mappings.js +65 -8
- package/dist/values/value-mappings.js.map +1 -1
- package/dist/visual/attention-quality.d.ts +27 -0
- package/dist/visual/attention-quality.d.ts.map +1 -1
- package/dist/visual/attention-quality.js +164 -2
- package/dist/visual/attention-quality.js.map +1 -1
- package/dist/visual/attention-transport.d.ts +41 -2
- package/dist/visual/attention-transport.d.ts.map +1 -1
- package/dist/visual/attention-transport.js +98 -26
- package/dist/visual/attention-transport.js.map +1 -1
- package/dist/visual/barrier-overlay-html.d.ts +10 -0
- package/dist/visual/barrier-overlay-html.d.ts.map +1 -1
- package/dist/visual/barrier-overlay-html.js.map +1 -1
- package/dist/visual/cognitive-models.d.ts +109 -13
- package/dist/visual/cognitive-models.d.ts.map +1 -1
- package/dist/visual/cognitive-models.js +166 -3
- package/dist/visual/cognitive-models.js.map +1 -1
- package/dist/visual/cognitive-transport-chain.d.ts +149 -5
- package/dist/visual/cognitive-transport-chain.d.ts.map +1 -1
- package/dist/visual/cognitive-transport-chain.js +558 -30
- package/dist/visual/cognitive-transport-chain.js.map +1 -1
- package/dist/visual/cognitive-transport.d.ts +45 -3
- package/dist/visual/cognitive-transport.d.ts.map +1 -1
- package/dist/visual/cognitive-transport.js +126 -12
- package/dist/visual/cognitive-transport.js.map +1 -1
- package/dist/visual/fixation-corpus.d.ts +52 -0
- package/dist/visual/fixation-corpus.d.ts.map +1 -1
- package/dist/visual/fixation-corpus.js +95 -0
- package/dist/visual/fixation-corpus.js.map +1 -1
- package/dist/visual/llm-relevance.d.ts +193 -0
- package/dist/visual/llm-relevance.d.ts.map +1 -0
- package/dist/visual/llm-relevance.js +480 -0
- package/dist/visual/llm-relevance.js.map +1 -0
- package/dist/visual/perceptual-transport.d.ts +60 -0
- package/dist/visual/perceptual-transport.d.ts.map +1 -1
- package/dist/visual/perceptual-transport.js +228 -19
- package/dist/visual/perceptual-transport.js.map +1 -1
- package/dist/visual/persona-context.d.ts +23 -0
- package/dist/visual/persona-context.d.ts.map +1 -0
- package/dist/visual/persona-context.js +44 -0
- package/dist/visual/persona-context.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-contained capture player for MCP Apps inline rendering.
|
|
3
|
+
*
|
|
4
|
+
* Differs from capture-player.ts in one way that drives every other decision:
|
|
5
|
+
* that page is served over HTTP from cbrowser.ai and can reference its own
|
|
6
|
+
* artifacts by URL. This one runs inside the host's widget sandbox, whose CSP
|
|
7
|
+
* allowlists a fixed set of origins that does not include cbrowser.ai. So it
|
|
8
|
+
* can fetch nothing. Every pixel it shows has to be embedded.
|
|
9
|
+
*
|
|
10
|
+
* data: is permitted in img-src (confirmed against the live sandbox), which is
|
|
11
|
+
* what makes this possible at all -- and it is also the binding constraint,
|
|
12
|
+
* because a capture GIF measured between 500KB and 1.4MB and base64 adds a
|
|
13
|
+
* third on top. So frames are selected rather than included, and re-encoded to
|
|
14
|
+
* fit a byte budget rather than shipped at capture resolution.
|
|
15
|
+
*
|
|
16
|
+
* The full-resolution artifacts stay reachable as links. A link is a navigation,
|
|
17
|
+
* not a subresource fetch, so the allowlist does not apply to it.
|
|
18
|
+
*/
|
|
19
|
+
import sharp from "sharp";
|
|
20
|
+
import { existsSync } from "node:fs";
|
|
21
|
+
/** Total base64 budget for embedded frames. */
|
|
22
|
+
const INLINE_BUDGET_B64 = 700_000;
|
|
23
|
+
/** Never embed more than this many frames regardless of budget. */
|
|
24
|
+
const MAX_FRAMES = 10;
|
|
25
|
+
/** Widest an embedded frame is rendered; capture width is far more than needed. */
|
|
26
|
+
const FRAME_WIDTH = 720;
|
|
27
|
+
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
|
28
|
+
.replace(/"/g, """).replace(/'/g, "'");
|
|
29
|
+
const fmtTime = (ms) => `${(ms / 1000).toFixed(1)}s`;
|
|
30
|
+
/**
|
|
31
|
+
* Choose which frames to embed.
|
|
32
|
+
*
|
|
33
|
+
* Judged moments and interactions are the frames that carry information -- a
|
|
34
|
+
* frame is a moment precisely because the interface changed enough to re-judge,
|
|
35
|
+
* and a frame is an interaction because something happened on it. Sampling
|
|
36
|
+
* evenly would spend the budget on the still stretches between them. Evenly
|
|
37
|
+
* spaced frames only fill whatever room is left.
|
|
38
|
+
*/
|
|
39
|
+
function selectFrames(data) {
|
|
40
|
+
const n = data.framePaths.length;
|
|
41
|
+
if (n === 0)
|
|
42
|
+
return [];
|
|
43
|
+
const picked = new Set([0]);
|
|
44
|
+
for (const m of data.moments ?? []) {
|
|
45
|
+
if (m.frameIndex >= 0 && m.frameIndex < n)
|
|
46
|
+
picked.add(m.frameIndex);
|
|
47
|
+
}
|
|
48
|
+
for (const it of data.interactions ?? []) {
|
|
49
|
+
// The frame that shows the click is the first one at or after it.
|
|
50
|
+
const idx = data.frameTimesMs.findIndex((t) => t >= it.atMs);
|
|
51
|
+
if (idx >= 0)
|
|
52
|
+
picked.add(idx);
|
|
53
|
+
}
|
|
54
|
+
picked.add(n - 1);
|
|
55
|
+
const chosen = [...picked].filter((i) => i >= 0 && i < n).sort((a, b) => a - b);
|
|
56
|
+
if (chosen.length >= MAX_FRAMES)
|
|
57
|
+
return chosen.slice(0, MAX_FRAMES);
|
|
58
|
+
// Fill remaining slots with an even spread, skipping anything already picked.
|
|
59
|
+
const want = MAX_FRAMES - chosen.length;
|
|
60
|
+
for (let k = 1; k <= want && chosen.length < MAX_FRAMES; k++) {
|
|
61
|
+
const idx = Math.round((k / (want + 1)) * (n - 1));
|
|
62
|
+
if (!picked.has(idx)) {
|
|
63
|
+
picked.add(idx);
|
|
64
|
+
chosen.push(idx);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return [...picked].sort((a, b) => a - b);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Encode one frame to a data URI under a byte ceiling.
|
|
71
|
+
*
|
|
72
|
+
* Steps quality down rather than dimensions: the frames carry an attention
|
|
73
|
+
* overlay whose whole content is where colour sits on the page, and shrinking
|
|
74
|
+
* the raster blurs that away faster than JPEG artefacts do.
|
|
75
|
+
*/
|
|
76
|
+
async function encodeFrame(path, ceilingB64) {
|
|
77
|
+
if (!existsSync(path))
|
|
78
|
+
return undefined;
|
|
79
|
+
for (const quality of [72, 58, 46, 36, 28, 20]) {
|
|
80
|
+
try {
|
|
81
|
+
const buf = await sharp(path).resize({ width: FRAME_WIDTH, withoutEnlargement: true })
|
|
82
|
+
.jpeg({ quality }).toBuffer();
|
|
83
|
+
if (Math.ceil(buf.length * 4 / 3) <= ceilingB64) {
|
|
84
|
+
return `data:image/jpeg;base64,${buf.toString("base64")}`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
export async function buildInlineCapturePlayer(data) {
|
|
94
|
+
const idxs = selectFrames(data);
|
|
95
|
+
const perFrame = idxs.length > 0 ? Math.floor(INLINE_BUDGET_B64 / idxs.length) : 0;
|
|
96
|
+
const frames = [];
|
|
97
|
+
for (const i of idxs) {
|
|
98
|
+
const p = data.framePaths[i];
|
|
99
|
+
if (!p)
|
|
100
|
+
continue;
|
|
101
|
+
const uri = await encodeFrame(p, perFrame);
|
|
102
|
+
if (uri)
|
|
103
|
+
frames.push({ idx: i, tMs: data.frameTimesMs[i] ?? 0, uri });
|
|
104
|
+
}
|
|
105
|
+
const acts = (data.interactions ?? []).slice().sort((a, b) => a.atMs - b.atMs);
|
|
106
|
+
const dur = Math.max(1, data.durationMs);
|
|
107
|
+
const momentBy = new Map((data.moments ?? []).map((m) => [m.frameIndex, m]));
|
|
108
|
+
const embeddedBytes = frames.reduce((n, f) => n + f.uri.length, 0);
|
|
109
|
+
const head = `
|
|
110
|
+
<header>
|
|
111
|
+
<p class="eyebrow">CBrowser capture</p>
|
|
112
|
+
<h1>${esc(data.targetUrl ?? data.slug)}</h1>
|
|
113
|
+
<p class="meta">${fmtTime(data.durationMs)} · ${data.framePaths.length} frames ·
|
|
114
|
+
${data.actualFps.toFixed(1)} fps${data.personaName ? ` · persona: ${esc(data.personaName)}` : ""}${data.goal ? ` · goal: ${esc(data.goal)}` : ""}</p>
|
|
115
|
+
${data.overlay ? `<p class="pill ${data.overlay.quantity === "predicted-attention" ? "ok" : "warn"}">${data.overlay.quantity === "predicted-attention"
|
|
116
|
+
? `predicted attention · ${data.overlay.domElements} elements`
|
|
117
|
+
: "visual contrast only · no DOM"}</p>` : ""}
|
|
118
|
+
</header>`;
|
|
119
|
+
const viewer = frames.length === 0
|
|
120
|
+
? `<p class="empty">No frames could be embedded for this capture.</p>`
|
|
121
|
+
: `
|
|
122
|
+
<div class="viewer">
|
|
123
|
+
${frames.map((f, i) => `<img class="fr${i === 0 ? " on" : ""}" data-i="${i}" src="${f.uri}" alt="Frame ${f.idx} at ${fmtTime(f.tMs)}">`).join("")}
|
|
124
|
+
</div>
|
|
125
|
+
<div class="controls">
|
|
126
|
+
<button type="button" id="prev" aria-label="Previous frame">◂</button>
|
|
127
|
+
<button type="button" id="play" aria-label="Play">▶</button>
|
|
128
|
+
<button type="button" id="next" aria-label="Next frame">▸</button>
|
|
129
|
+
<input type="range" id="scrub" min="0" max="${frames.length - 1}" value="0" step="1" aria-label="Frame">
|
|
130
|
+
<span class="tc" id="tc">${fmtTime(frames[0]?.tMs ?? 0)}</span>
|
|
131
|
+
</div>
|
|
132
|
+
<p class="note">${frames.length} of ${data.framePaths.length} frames embedded, chosen at judged moments and interactions.
|
|
133
|
+
${(embeddedBytes / 1024).toFixed(0)} KB inline.</p>`;
|
|
134
|
+
const track = acts.length === 0 ? "" : `
|
|
135
|
+
<section>
|
|
136
|
+
<h2>Interactions</h2>
|
|
137
|
+
<div class="track">
|
|
138
|
+
${acts.map((a) => `<button type="button" class="mark" data-at="${a.atMs}" style="--x:${((a.atMs / dur) * 100).toFixed(2)}%" title="${esc(a.type)}: ${esc(a.label.slice(0, 60))}"><span class="dot" data-type="${esc(a.type)}"></span></button>`).join("")}
|
|
139
|
+
</div>
|
|
140
|
+
<ol class="actlist">
|
|
141
|
+
${acts.map((a) => `<li><button type="button" data-at="${a.atMs}"><span class="t">${fmtTime(a.atMs)}</span><span class="kind" data-type="${esc(a.type)}">${esc(a.type)}</span><span class="lab">${esc(a.label.slice(0, 80) || "(no label)")}</span></button></li>`).join("")}
|
|
142
|
+
</ol>
|
|
143
|
+
</section>`;
|
|
144
|
+
const timeline = (data.moments ?? []).length === 0 ? "" : `
|
|
145
|
+
<section>
|
|
146
|
+
<h2>Attention timeline</h2>
|
|
147
|
+
<ol class="moments">
|
|
148
|
+
${(data.moments ?? []).map((m) => `
|
|
149
|
+
<li>
|
|
150
|
+
<div class="t">${fmtTime(m.tMs)}<span class="frame">frame ${m.frameIndex}</span></div>
|
|
151
|
+
<div class="body">
|
|
152
|
+
${m.topElements.length > 0 ? `<ul class="els">${m.topElements.slice(0, 5).map((e) => `
|
|
153
|
+
<li><span class="bar" style="--w:${Math.round(e.score * 100)}%"></span><span class="score">${e.score.toFixed(2)}</span><span class="txt">${esc(e.text.slice(0, 70))}</span></li>`).join("")}</ul>` : ""}
|
|
154
|
+
${m.reasoning ? `<p class="why">${esc(m.reasoning)}</p>` : ""}
|
|
155
|
+
</div>
|
|
156
|
+
</li>`).join("")}
|
|
157
|
+
</ol>
|
|
158
|
+
</section>`;
|
|
159
|
+
const summary = data.summary ? `<section class="summary"><h2>How this went</h2><p>${esc(data.summary)}</p></section>` : "";
|
|
160
|
+
// Links, not embeds. A link is a navigation and the origin allowlist does not
|
|
161
|
+
// govern it, so full-resolution artifacts stay one click away even though the
|
|
162
|
+
// sandbox will not fetch them into this frame.
|
|
163
|
+
const links = [
|
|
164
|
+
...(data.playerUrl ? [[`full player`, data.playerUrl]] : []),
|
|
165
|
+
...Object.entries(data.artifactUrls ?? {}),
|
|
166
|
+
];
|
|
167
|
+
const dl = links.length === 0 ? "" : `
|
|
168
|
+
<section><h2>Full artifacts</h2><p class="lede">Open outside this panel — the sandbox will not load them inline.</p>
|
|
169
|
+
<div class="dl">${links.map(([k, u]) => `<a href="${esc(u)}" target="_blank" rel="noopener">${esc(k)}</a>`).join("")}</div></section>`;
|
|
170
|
+
const frameTimes = JSON.stringify(frames.map((f) => f.tMs));
|
|
171
|
+
return `<!doctype html><meta charset="utf-8">
|
|
172
|
+
<style>
|
|
173
|
+
:root{color-scheme:light dark;--ink:#14161a;--ground:#fff;--muted:#666;--rule:#d9dce1;--accent:#2f6f4f;--card:#fff}
|
|
174
|
+
@media (prefers-color-scheme:dark){:root{--ink:#e8eaed;--ground:#16181c;--muted:#9aa0a8;--rule:#31353b;--accent:#6fbf8f;--card:#1d2025}}
|
|
175
|
+
*{box-sizing:border-box}
|
|
176
|
+
body{margin:0;padding:16px;font:15px/1.55 ui-sans-serif,system-ui,-apple-system,sans-serif;color:var(--ink);background:var(--ground)}
|
|
177
|
+
.eyebrow{font:600 .68rem/1 ui-monospace,monospace;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin:0 0 .45rem}
|
|
178
|
+
h1{font-size:1.12rem;margin:0 0 .35rem;word-break:break-all;text-wrap:balance}
|
|
179
|
+
h2{font-size:.95rem;margin:1.4rem 0 .5rem}
|
|
180
|
+
.meta{font:.78rem/1.4 ui-monospace,monospace;color:var(--muted);margin:0 0 .5rem}
|
|
181
|
+
.pill{display:inline-block;font:.68rem/1 ui-monospace,monospace;letter-spacing:.06em;text-transform:uppercase;padding:.3rem .5rem;border-radius:4px;margin:0}
|
|
182
|
+
.pill.ok{background:color-mix(in srgb,var(--accent) 18%,transparent);color:var(--accent)}
|
|
183
|
+
.pill.warn{background:color-mix(in srgb,#c98a2b 20%,transparent)}
|
|
184
|
+
.viewer{position:relative;margin:.9rem 0 .5rem;border:1px solid var(--rule);border-radius:8px;overflow:hidden;background:var(--card)}
|
|
185
|
+
.viewer img{display:none;width:100%;height:auto}
|
|
186
|
+
.viewer img.on{display:block}
|
|
187
|
+
.controls{display:flex;align-items:center;gap:.5rem}
|
|
188
|
+
.controls button{font-size:.9rem;padding:.25rem .55rem;border:1px solid var(--rule);border-radius:5px;background:var(--card);color:inherit;cursor:pointer}
|
|
189
|
+
.controls button:hover{border-color:var(--accent)}
|
|
190
|
+
.controls button:focus-visible,.mark:focus-visible,.actlist button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
191
|
+
#scrub{flex:1;accent-color:var(--accent);min-width:60px}
|
|
192
|
+
.tc{font:.75rem/1 ui-monospace,monospace;color:var(--muted);font-variant-numeric:tabular-nums;flex:0 0 auto}
|
|
193
|
+
.note,.lede{font-size:.78rem;color:var(--muted);margin:.4rem 0 0}
|
|
194
|
+
.track{position:relative;height:24px;margin:.3rem 0 .7rem;border:1px solid var(--rule);border-radius:4px;background:color-mix(in srgb,var(--rule) 30%,transparent)}
|
|
195
|
+
.mark{position:absolute;left:var(--x);top:0;height:100%;width:18px;transform:translateX(-9px);background:none;border:0;padding:0;cursor:pointer;display:grid;place-items:center}
|
|
196
|
+
.mark .dot{width:10px;height:10px;border-radius:50%;background:var(--accent);border:2px solid var(--card);box-shadow:0 0 0 1px var(--accent)}
|
|
197
|
+
.mark .dot[data-type="submit"]{background:#d1495b;box-shadow:0 0 0 1px #d1495b}
|
|
198
|
+
.mark .dot[data-type="input"]{background:#e0a458;box-shadow:0 0 0 1px #e0a458}
|
|
199
|
+
.actlist,.moments{list-style:none;padding:0;margin:0}
|
|
200
|
+
.actlist button{display:flex;gap:.55rem;align-items:baseline;width:100%;text-align:left;background:none;border:0;border-radius:4px;padding:.28rem .4rem;cursor:pointer;color:inherit;font:inherit}
|
|
201
|
+
.actlist button:hover{background:color-mix(in srgb,var(--accent) 10%,transparent)}
|
|
202
|
+
.actlist .t,.moments .t{font:.73rem/1 ui-monospace,monospace;color:var(--muted);flex:0 0 3.2rem;font-variant-numeric:tabular-nums}
|
|
203
|
+
.kind{font:.63rem/1 ui-monospace,monospace;text-transform:uppercase;letter-spacing:.07em;padding:.1rem .32rem;border-radius:3px;background:color-mix(in srgb,var(--accent) 16%,transparent)}
|
|
204
|
+
.kind[data-type="submit"]{background:color-mix(in srgb,#d1495b 18%,transparent)}
|
|
205
|
+
.kind[data-type="input"]{background:color-mix(in srgb,#e0a458 22%,transparent)}
|
|
206
|
+
.lab,.txt{font-size:.84rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
207
|
+
.moments li{display:flex;gap:.7rem;padding:.5rem 0;border-top:1px solid var(--rule)}
|
|
208
|
+
.moments .frame{display:block;font-size:.63rem;opacity:.7}
|
|
209
|
+
.els{list-style:none;padding:0;margin:0 0 .35rem}
|
|
210
|
+
.els li{display:grid;grid-template-columns:3ch 1fr;gap:.4rem;align-items:center;position:relative;padding:.12rem 0}
|
|
211
|
+
.bar{position:absolute;left:0;top:0;bottom:0;width:var(--w);background:color-mix(in srgb,var(--accent) 15%,transparent);border-radius:3px;z-index:0}
|
|
212
|
+
.score,.txt{position:relative;z-index:1}
|
|
213
|
+
.score{font:.7rem/1 ui-monospace,monospace;color:var(--muted);font-variant-numeric:tabular-nums}
|
|
214
|
+
.why{font-size:.84rem;margin:.3rem 0 0;color:var(--muted)}
|
|
215
|
+
.summary{border-left:3px solid var(--accent);padding:.1rem 0 .1rem .8rem;margin-top:1.4rem}
|
|
216
|
+
.dl{display:flex;flex-wrap:wrap;gap:.4rem;margin-top:.4rem}
|
|
217
|
+
.dl a{font:.78rem/1 ui-monospace,monospace;padding:.34rem .6rem;border:1px solid var(--rule);border-radius:5px;text-decoration:none;color:var(--accent)}
|
|
218
|
+
.dl a:hover{border-color:var(--accent)}
|
|
219
|
+
.empty{color:var(--muted);font-size:.88rem}
|
|
220
|
+
@media (prefers-reduced-motion:reduce){*{transition:none!important}}
|
|
221
|
+
</style>
|
|
222
|
+
${head}
|
|
223
|
+
${viewer}
|
|
224
|
+
${track}
|
|
225
|
+
${summary}
|
|
226
|
+
${timeline}
|
|
227
|
+
${dl}
|
|
228
|
+
<script>
|
|
229
|
+
(function(){
|
|
230
|
+
var imgs = Array.prototype.slice.call(document.querySelectorAll(".viewer .fr"));
|
|
231
|
+
if (!imgs.length) return;
|
|
232
|
+
var times = ${frameTimes};
|
|
233
|
+
var scrub = document.getElementById("scrub");
|
|
234
|
+
var tc = document.getElementById("tc");
|
|
235
|
+
var playBtn = document.getElementById("play");
|
|
236
|
+
var cur = 0, timer = null;
|
|
237
|
+
|
|
238
|
+
function show(i){
|
|
239
|
+
cur = Math.max(0, Math.min(imgs.length - 1, i));
|
|
240
|
+
imgs.forEach(function(im, k){ im.classList.toggle("on", k === cur); });
|
|
241
|
+
scrub.value = String(cur);
|
|
242
|
+
tc.textContent = ((times[cur] || 0) / 1000).toFixed(1) + "s";
|
|
243
|
+
}
|
|
244
|
+
function stop(){ if (timer) { clearInterval(timer); timer = null; playBtn.innerHTML = "▶"; } }
|
|
245
|
+
function play(){
|
|
246
|
+
if (timer) return stop();
|
|
247
|
+
playBtn.innerHTML = "❙❙";
|
|
248
|
+
timer = setInterval(function(){
|
|
249
|
+
if (cur >= imgs.length - 1) { show(0); } else { show(cur + 1); }
|
|
250
|
+
}, 700);
|
|
251
|
+
}
|
|
252
|
+
document.getElementById("prev").addEventListener("click", function(){ stop(); show(cur - 1); });
|
|
253
|
+
document.getElementById("next").addEventListener("click", function(){ stop(); show(cur + 1); });
|
|
254
|
+
playBtn.addEventListener("click", play);
|
|
255
|
+
scrub.addEventListener("input", function(){ stop(); show(parseInt(scrub.value, 10)); });
|
|
256
|
+
|
|
257
|
+
// Interaction markers seek to the embedded frame nearest that moment. Only a
|
|
258
|
+
// subset of frames is embedded, so this lands on the closest one rather than
|
|
259
|
+
// pretending every timestamp has a frame behind it.
|
|
260
|
+
document.querySelectorAll("[data-at]").forEach(function(b){
|
|
261
|
+
b.addEventListener("click", function(){
|
|
262
|
+
var at = parseInt(b.getAttribute("data-at"), 10);
|
|
263
|
+
var best = 0, bestD = Infinity;
|
|
264
|
+
times.forEach(function(t, k){ var d = Math.abs(t - at); if (d < bestD) { bestD = d; best = k; } });
|
|
265
|
+
stop(); show(best);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
show(0);
|
|
269
|
+
})();
|
|
270
|
+
</script>`;
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=inline-player.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline-player.js","sourceRoot":"","sources":["../../src/recording/inline-player.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,+CAA+C;AAC/C,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAClC,mEAAmE;AACnE,MAAM,UAAU,GAAG,EAAE,CAAC;AACtB,mFAAmF;AACnF,MAAM,WAAW,GAAG,GAAG,CAAC;AAuBxB,MAAM,GAAG,GAAG,CAAC,CAAS,EAAU,EAAE,CAChC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;KACjE,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAEpD,MAAM,OAAO,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AAErE;;;;;;;;GAQG;AACH,SAAS,YAAY,CAAC,IAAsB;IAC1C,MAAM,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;IACjC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACvB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IAEpC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,GAAG,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IACtE,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;QACzC,kEAAkE;QAClE,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC;QAC7D,IAAI,GAAG,IAAI,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAElB,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAChF,IAAI,MAAM,CAAC,MAAM,IAAI,UAAU;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAEpE,8EAA8E;IAC9E,MAAM,IAAI,GAAG,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,IAAI,MAAM,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACnD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAAC,CAAC;IAC9D,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,UAAkB;IACzD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACxC,KAAK,MAAM,OAAO,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;QAC/C,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC;iBACnF,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;YAChC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,UAAU,EAAE,CAAC;gBAChD,OAAO,0BAA0B,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5D,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,SAAS,CAAC;QAAC,CAAC;IAC/B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,IAAsB;IACnE,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEnF,MAAM,MAAM,GAAqD,EAAE,CAAC;IACpE,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC3C,IAAI,GAAG;YAAE,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IAC/E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7E,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAEnE,MAAM,IAAI,GAAG;;;UAGL,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC;sBACpB,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,IAAI,CAAC,UAAU,CAAC,MAAM;QACzE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,sBAAsB,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAmB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;MAC9J,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB,IAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,KAChG,IAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,qBAAqB;QAC7C,CAAC,CAAC,gCAAgC,IAAI,CAAC,OAAO,CAAC,WAAW,WAAW;QACrE,CAAC,CAAC,sCAAsC,MAAM,CAAC,CAAC,CAAC,EAAE;YAC/C,CAAC;IAEX,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC;QAChC,CAAC,CAAC,oEAAoE;QACtE,CAAC,CAAC;;MAEA,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;;;;;;kDAMnG,MAAM,CAAC,MAAM,GAAG,CAAC;+BACpC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;;oBAEvC,MAAM,CAAC,MAAM,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM;MACxD,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC;IAE9D,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;;;;QAIjC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,+CAA+C,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,kCAAkC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;;;QAGvP,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,sCAAsC,CAAC,CAAC,IAAI,qBAAqB,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,wCAAwC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,4BAA4B,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,YAAY,CAAC,uBAAuB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;;aAEpQ,CAAC;IAEZ,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;;;;QAIpD,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;;yBAEf,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC,UAAU;;YAEpE,CAAC,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;+CAChD,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,iCAAiC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,4BAA4B,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YACvM,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,kBAAkB,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;YAE3D,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;;aAET,CAAC;IAEZ,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,qDAAqD,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;IAE3H,8EAA8E;IAC9E,8EAA8E;IAC9E,+CAA+C;IAC/C,MAAM,KAAK,GAAG;QACZ,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC;KAChB,CAAC;IAC7B,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;;oBAEnB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,oCAAoC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,kBAAkB,CAAC;IAEvI,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAE5D,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmDP,IAAI;EACJ,MAAM;EACN,KAAK;EACL,OAAO;EACP,QAAQ;EACR,EAAE;;;;;gBAKY,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAsChB,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attention overlay for captured frames.
|
|
3
|
+
*
|
|
4
|
+
* Renders the model's predicted-attention map over each captured frame so a
|
|
5
|
+
* recording can be watched with the attention structure visible, then handed to
|
|
6
|
+
* the normal encoders as an ordinary frame sequence.
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS USES THE DOM. Capture runs against a LIVE page, so the DOM is right
|
|
9
|
+
* there — and `analyzeAttention` blends 35% bottom-up colour contrast with 65%
|
|
10
|
+
* DOM semantics (element type x persona priority x goal relevance). Overlaying
|
|
11
|
+
* the bottom-up half alone would paint the one component that has been
|
|
12
|
+
* benchmarked and LOST: on 495 web pages against real human fixations it scored
|
|
13
|
+
* AUC 0.585 versus a plain centre-bias baseline's 0.658. Passing DOM elements
|
|
14
|
+
* makes this the actual production attention model rather than the weakest
|
|
15
|
+
* quarter of it. Falls back to visual-only when no DOM is supplied, and says so
|
|
16
|
+
* in the result.
|
|
17
|
+
*
|
|
18
|
+
* WHAT IT DOES AND DOES NOT ESTABLISH. With DOM this is the product's attention
|
|
19
|
+
* prediction — legitimately what cbrowser computes and sells. It is not a
|
|
20
|
+
* *validated* gaze prediction: the semantic layer carrying most of the blend has
|
|
21
|
+
* never been tested against human eye movements in either direction, which is
|
|
22
|
+
* what the primary collection study is being built to settle. So "predicted
|
|
23
|
+
* attention" is accurate; "where users look" asserts an accuracy nobody has
|
|
24
|
+
* measured yet. (2026-07-30)
|
|
25
|
+
*
|
|
26
|
+
* Cost: one attention pass per frame, roughly 0.5-1s each at typical viewport
|
|
27
|
+
* sizes. A 5s capture at 10fps is ~50 frames, so budget 30-60s. That is why
|
|
28
|
+
* this is opt-in and never the default.
|
|
29
|
+
*/
|
|
30
|
+
import { type DOMAttentionElement } from "../visual/attention-transport.js";
|
|
31
|
+
import { type JudgedMoment, type RelevanceContext } from "../visual/llm-relevance.js";
|
|
32
|
+
export interface AttentionOverlayOptions {
|
|
33
|
+
/** 0-1 opacity of the heat layer. Default 0.55 — readable without hiding the page. */
|
|
34
|
+
opacity?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Cells below this fraction of the frame's peak stay fully transparent, so the
|
|
37
|
+
* overlay marks hot regions rather than tinting everything. Default 0.35.
|
|
38
|
+
*/
|
|
39
|
+
floor?: number;
|
|
40
|
+
/** Analysis cell size in px. Default 16, matching the shipped attention model. */
|
|
41
|
+
cellSize?: number;
|
|
42
|
+
/** Persona whose priorities weight the semantic layer. Default "first-timer". */
|
|
43
|
+
persona?: string;
|
|
44
|
+
/**
|
|
45
|
+
* DOM elements as the attention model consumes them. Supply these and the
|
|
46
|
+
* overlay reflects the full 35/65 blend; omit them and it degrades to
|
|
47
|
+
* bottom-up contrast only.
|
|
48
|
+
*/
|
|
49
|
+
domElements?: DOMAttentionElement[];
|
|
50
|
+
/** Goal string, which weights goal-relevance in the semantic layer. */
|
|
51
|
+
goal?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Frame indices where the interface changed enough to warrant re-judging.
|
|
54
|
+
*
|
|
55
|
+
* The capture engine already computes this as `key_frames` via SSIM, and it is
|
|
56
|
+
* the right trigger precisely because it does not saturate the way
|
|
57
|
+
* `change_points` does. Re-judging every frame would cost one model call per
|
|
58
|
+
* frame; re-judging only where the page actually changed costs a handful per
|
|
59
|
+
* recording and produces the same map, because between keyframes the page —
|
|
60
|
+
* and therefore the judgement — is unchanged.
|
|
61
|
+
*/
|
|
62
|
+
keyFrames?: number[];
|
|
63
|
+
/** Per-frame timestamps, used to place moments on the recording timeline. */
|
|
64
|
+
frameTimesMs?: number[];
|
|
65
|
+
/**
|
|
66
|
+
* DOM as it stood at points in time, captured on navigation during recording.
|
|
67
|
+
*
|
|
68
|
+
* A judged frame uses the snapshot in effect AT ITS TIMESTAMP. Without this,
|
|
69
|
+
* every frame was scored against the page the capture ended on — so a frame
|
|
70
|
+
* on the pricing page got ranked against a post-click verification screen,
|
|
71
|
+
* every moment came back identical, and the summary read that uniformity as a
|
|
72
|
+
* UX finding rather than as the artifact it was.
|
|
73
|
+
*/
|
|
74
|
+
domTimeline?: Array<{
|
|
75
|
+
atMs: number;
|
|
76
|
+
scrollY?: number;
|
|
77
|
+
elements: DOMAttentionElement[];
|
|
78
|
+
}>;
|
|
79
|
+
/** Scroll position over time, used to correct rects for drift since capture. */
|
|
80
|
+
scrollTrack?: Array<{
|
|
81
|
+
atMs: number;
|
|
82
|
+
scrollY: number;
|
|
83
|
+
}>;
|
|
84
|
+
/** Real interactions, for the click pulse and for the summary's action log. */
|
|
85
|
+
interactions?: Array<{
|
|
86
|
+
atMs: number;
|
|
87
|
+
type: string;
|
|
88
|
+
x: number;
|
|
89
|
+
y: number;
|
|
90
|
+
label: string;
|
|
91
|
+
}>;
|
|
92
|
+
/** Persona context for the relevance judge and the closing summary. */
|
|
93
|
+
relevanceContext?: Omit<RelevanceContext, "screenshot">;
|
|
94
|
+
/** Produce a narrative of the whole recording after the frames are overlaid. */
|
|
95
|
+
summarize?: boolean;
|
|
96
|
+
/** Resolves the Anthropic key. Absent means no judging and no summary. */
|
|
97
|
+
getApiKey?: () => string | null;
|
|
98
|
+
}
|
|
99
|
+
export interface OverlayResult {
|
|
100
|
+
frames: string[];
|
|
101
|
+
dir: string;
|
|
102
|
+
/** Judged moments, one per keyframe the judge was re-run on. */
|
|
103
|
+
moments?: JudgedMoment[];
|
|
104
|
+
/** Narrative of the whole recording, when a summary was requested. */
|
|
105
|
+
summary?: string;
|
|
106
|
+
/** Frames that could not be overlaid and fell back to the original. */
|
|
107
|
+
failed: number;
|
|
108
|
+
/** True when DOM elements were supplied, i.e. the full model ran. */
|
|
109
|
+
usedDom: boolean;
|
|
110
|
+
/** The quantity that was painted, for callers that label the artifact. */
|
|
111
|
+
quantity: "predicted-attention" | "visual-contrast-only";
|
|
112
|
+
/**
|
|
113
|
+
* Whether the requested persona actually exists.
|
|
114
|
+
*
|
|
115
|
+
* An unknown name does not error — it falls through keyword inference to a
|
|
116
|
+
* default, which is right for custom personas and silent for typos. A caller
|
|
117
|
+
* who asks for "alexa-eden" and gets a generic profile has no way to tell,
|
|
118
|
+
* and every number downstream is then attributed to a persona that never ran.
|
|
119
|
+
*/
|
|
120
|
+
personaResolved?: boolean;
|
|
121
|
+
/** The persona actually used, which may differ from the one requested. */
|
|
122
|
+
personaUsed?: string;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Overlay predicted attention onto every frame, writing a parallel set beside
|
|
126
|
+
* the originals.
|
|
127
|
+
*
|
|
128
|
+
* A frame that fails falls back to the ORIGINAL frame rather than being
|
|
129
|
+
* dropped: a recording with one un-overlaid frame is still usable, while a
|
|
130
|
+
* missing frame corrupts the timing of every frame after it.
|
|
131
|
+
*/
|
|
132
|
+
export declare function overlayAttentionOnFrames(framePaths: string[], outDir: string, options?: AttentionOverlayOptions): Promise<OverlayResult>;
|
|
133
|
+
//# sourceMappingURL=saliency-overlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"saliency-overlay.d.ts","sourceRoot":"","sources":["../../src/recording/saliency-overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAKH,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC9F,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,gBAAgB,EAEtB,MAAM,4BAA4B,CAAC;AAEpC,MAAM,WAAW,uBAAuB;IACtC,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACpC,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,mBAAmB,EAAE,CAAA;KAAE,CAAC,CAAC;IACzF,gFAAgF;IAChF,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvD,+EAA+E;IAC/E,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1F,uEAAuE;IACvE,gBAAgB,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAC;IACxD,gFAAgF;IAChF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,gEAAgE;IAChE,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAC;IACjB,0EAA0E;IAC1E,QAAQ,EAAE,qBAAqB,GAAG,sBAAsB,CAAC;IACzD;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAwID;;;;;;;GAOG;AACH,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,MAAM,EAAE,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,aAAa,CAAC,CAoMxB"}
|