autokap 1.0.5 → 1.0.7
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/assets/chrome/ios-statusbar-comparison-reference.jpg +0 -0
- package/assets/chrome/ios-statusbar-dark-reference.jpg +0 -0
- package/assets/chrome/ios-statusbar-light-reference.jpg +0 -0
- package/assets/devices/ipad-pro-11-m4.json +52 -0
- package/assets/devices/iphone-16-pro.json +53 -0
- package/assets/devices/macbook-air-13.json +45 -0
- package/assets/frames/MacBook Air 13.svg +242 -0
- package/assets/frames/Status bar - iPhone.png +0 -0
- Menu bar- iPad.png +0 -0
- package/assets/frames/iPad Pro M4 11_.png +0 -0
- package/assets/frames/iPhone 16 Pro.png +0 -0
- package/assets/icons/Cellular Connection.svg +3 -0
- package/assets/icons/Union.svg +6 -0
- package/assets/icons/Wifi.svg +3 -0
- package/assets/icons/battery.svg +5 -0
- package/assets/icons/battery_charging.svg +8 -0
- package/dist/abort.d.ts +5 -0
- package/dist/abort.js +44 -0
- package/dist/agent.d.ts +142 -0
- package/dist/agent.js +4511 -0
- package/dist/billing-operation-logging.d.ts +38 -0
- package/dist/billing-operation-logging.js +248 -0
- package/dist/browser-bar.d.ts +40 -0
- package/dist/browser-bar.js +147 -0
- package/dist/browser.d.ts +25 -0
- package/dist/browser.js +177 -9
- package/dist/capture-alt-text.d.ts +12 -0
- package/dist/capture-alt-text.js +51 -0
- package/dist/capture-encryption.d.ts +10 -0
- package/dist/capture-encryption.js +41 -0
- package/dist/capture-language-preflight.d.ts +41 -0
- package/dist/capture-language-preflight.js +286 -0
- package/dist/capture-llm-page-identity.d.ts +15 -0
- package/dist/capture-llm-page-identity.js +116 -0
- package/dist/capture-model-resolution.d.ts +9 -0
- package/dist/capture-model-resolution.js +21 -0
- package/dist/capture-page-identity.d.ts +9 -0
- package/dist/capture-page-identity.js +219 -0
- package/dist/capture-preset-credentials.d.ts +12 -0
- package/dist/capture-preset-credentials.js +57 -0
- package/dist/capture-request-plan.d.ts +58 -0
- package/dist/capture-request-plan.js +216 -0
- package/dist/capture-run-optimizer.d.ts +139 -0
- package/dist/capture-run-optimizer.js +848 -0
- package/dist/capture-selector-memory.d.ts +26 -0
- package/dist/capture-selector-memory.js +327 -0
- package/dist/capture-session-profile-encryption.d.ts +2 -0
- package/dist/capture-session-profile-encryption.js +22 -0
- package/dist/capture-step-timeout.d.ts +10 -0
- package/dist/capture-step-timeout.js +30 -0
- package/dist/capture-studio-sync.d.ts +22 -0
- package/dist/capture-studio-sync.js +166 -0
- package/dist/capture-variant-state.d.ts +54 -0
- package/dist/capture-variant-state.js +156 -0
- package/dist/cli.js +21 -0
- package/dist/clip-orchestrator.d.ts +148 -0
- package/dist/clip-orchestrator.js +950 -0
- package/dist/clip-postprocess.d.ts +42 -0
- package/dist/clip-postprocess.js +192 -0
- package/dist/cost-logging.d.ts +27 -0
- package/dist/cost-logging.js +128 -0
- package/dist/credential-templates.d.ts +5 -0
- package/dist/credential-templates.js +60 -0
- package/dist/element-capture.d.ts +53 -0
- package/dist/element-capture.js +766 -0
- package/dist/hybrid-navigator.d.ts +138 -0
- package/dist/hybrid-navigator.js +468 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +11 -0
- package/dist/llm-usage.d.ts +17 -0
- package/dist/llm-usage.js +45 -0
- package/dist/mockup-html.d.ts +119 -0
- package/dist/mockup-html.js +253 -0
- package/dist/mockup.d.ts +94 -0
- package/dist/mockup.js +608 -0
- package/dist/mouse-animation.d.ts +46 -0
- package/dist/mouse-animation.js +100 -0
- package/dist/overlay-utils.d.ts +14 -0
- package/dist/overlay-utils.js +13 -0
- package/dist/posthog.d.ts +4 -0
- package/dist/posthog.js +26 -0
- package/dist/prompt-cache.d.ts +10 -0
- package/dist/prompt-cache.js +24 -0
- package/dist/prompts.d.ts +167 -0
- package/dist/prompts.js +1165 -0
- package/dist/remote-browser.d.ts +191 -0
- package/dist/remote-browser.js +305 -0
- package/dist/security.d.ts +20 -0
- package/dist/security.js +569 -0
- package/dist/server-capture-runtime.d.ts +123 -0
- package/dist/server-capture-runtime.js +638 -0
- package/dist/server-credit-usage.d.ts +12 -0
- package/dist/server-credit-usage.js +41 -0
- package/dist/server-posthog.d.ts +2 -0
- package/dist/server-posthog.js +16 -0
- package/dist/server-project-webhooks.d.ts +45 -0
- package/dist/server-project-webhooks.js +97 -0
- package/dist/server-screenshot-watermark.d.ts +7 -0
- package/dist/server-screenshot-watermark.js +38 -0
- package/dist/session-profile.d.ts +86 -0
- package/dist/session-profile.js +1373 -0
- package/dist/sf-pro-fonts.d.ts +4 -0
- package/dist/sf-pro-fonts.js +7 -0
- package/dist/status-bar-l10n.d.ts +14 -0
- package/dist/status-bar-l10n.js +177 -0
- package/dist/status-bar.d.ts +44 -0
- package/dist/status-bar.js +336 -0
- package/dist/tools.d.ts +4 -0
- package/dist/tools.js +578 -0
- package/dist/video-agent.d.ts +143 -0
- package/dist/video-agent.js +4783 -0
- package/dist/video-observation.d.ts +36 -0
- package/dist/video-observation.js +192 -0
- package/dist/video-planner.d.ts +12 -0
- package/dist/video-planner.js +500 -0
- package/dist/video-prompts.d.ts +37 -0
- package/dist/video-prompts.js +554 -0
- package/dist/video-tools.d.ts +3 -0
- package/dist/video-tools.js +59 -0
- package/dist/video-variant-state.d.ts +29 -0
- package/dist/video-variant-state.js +80 -0
- package/dist/vision-model.d.ts +17 -0
- package/dist/vision-model.js +74 -0
- package/dist/ws-auth.d.ts +20 -0
- package/dist/ws-auth.js +67 -0
- package/dist/ws-handler.d.ts +10 -0
- package/dist/ws-handler.js +1663 -0
- package/dist/ws-server.d.ts +9 -0
- package/dist/ws-server.js +52 -0
- package/package.json +93 -39
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
function serializeObservationSnapshot(snapshot) {
|
|
3
|
+
if (!snapshot)
|
|
4
|
+
return '';
|
|
5
|
+
const elements = snapshot.interactiveElements.slice(0, 12).map((element) => ({
|
|
6
|
+
index: element.index,
|
|
7
|
+
tag: element.tag,
|
|
8
|
+
role: element.role,
|
|
9
|
+
text: element.text,
|
|
10
|
+
ariaLabel: element.ariaLabel,
|
|
11
|
+
href: element.href,
|
|
12
|
+
selector: element.selector,
|
|
13
|
+
visible: element.visible,
|
|
14
|
+
}));
|
|
15
|
+
return JSON.stringify({
|
|
16
|
+
coherenceKey: snapshot.coherenceKey ?? null,
|
|
17
|
+
pageIdentity: snapshot.pageIdentity ?? null,
|
|
18
|
+
pageSignals: {
|
|
19
|
+
url: snapshot.pageSignals.url,
|
|
20
|
+
title: snapshot.pageSignals.title,
|
|
21
|
+
htmlLang: snapshot.pageSignals.htmlLang,
|
|
22
|
+
headings: snapshot.pageSignals.headings.slice(0, 6),
|
|
23
|
+
navLabels: snapshot.pageSignals.navLabels.slice(0, 8),
|
|
24
|
+
breadcrumbLabels: snapshot.pageSignals.breadcrumbLabels.slice(0, 6),
|
|
25
|
+
localeHints: snapshot.pageSignals.localeHints.slice(0, 8),
|
|
26
|
+
detectedTheme: snapshot.pageSignals.detectedTheme,
|
|
27
|
+
},
|
|
28
|
+
interactiveElements: elements,
|
|
29
|
+
}, null, 2);
|
|
30
|
+
}
|
|
31
|
+
export function buildVideoPromptContentParts(params) {
|
|
32
|
+
const textPart = {
|
|
33
|
+
type: 'text',
|
|
34
|
+
text: params.text,
|
|
35
|
+
};
|
|
36
|
+
const imagePart = params.imageUrl
|
|
37
|
+
? {
|
|
38
|
+
type: 'image_url',
|
|
39
|
+
image_url: { url: params.imageUrl, detail: 'low' },
|
|
40
|
+
}
|
|
41
|
+
: null;
|
|
42
|
+
if (!imagePart)
|
|
43
|
+
return [textPart];
|
|
44
|
+
return params.cacheLayoutV2
|
|
45
|
+
? [textPart, imagePart]
|
|
46
|
+
: [imagePart, textPart];
|
|
47
|
+
}
|
|
48
|
+
// ── Step fixer prompts ────────────────────────────────────────────────
|
|
49
|
+
export function buildStepFixerSystemPrompt(videoScript) {
|
|
50
|
+
return `You are a browser automation step fixer. A step in a video demo plan failed. Your job is to produce a sequence of replacement steps that achieves the same goal.
|
|
51
|
+
|
|
52
|
+
## Overall demo goal
|
|
53
|
+
${videoScript}
|
|
54
|
+
|
|
55
|
+
## Your task
|
|
56
|
+
Given the failed step, the failure reason, a runtime page observation summary, and a screenshot of the current page, output a JSON object with a \`steps\` array containing one or more replacement steps.
|
|
57
|
+
|
|
58
|
+
## Grounding rules — CRITICAL
|
|
59
|
+
|
|
60
|
+
- The runtime page observation summary is built from the live DOM/accessibility tree after the failure.
|
|
61
|
+
- The runtime page observation summary is captured from the same verification snapshot as the screenshot unless explicitly noted otherwise.
|
|
62
|
+
- Treat the observation as ground truth for what labels, links, headings, controls, locale hints, and routes are actually present.
|
|
63
|
+
- Reuse hrefs, nav labels, button labels, breadcrumbs, and selectors supported by that observation whenever possible.
|
|
64
|
+
- Do NOT hallucinate a selector or route that is not supported by either the screenshot or the observation summary.
|
|
65
|
+
- NEVER output internal automation selectors such as \`[data-ak-*]\` or \`data-ak-interactive-index\`. Those are observation-only artifacts, not stable runtime selectors.
|
|
66
|
+
|
|
67
|
+
## Fix priority — try strategies in this order
|
|
68
|
+
|
|
69
|
+
### 0. Remove obstructive overlays first
|
|
70
|
+
If a cookie banner, modal, sticky feedback widget, newsletter popup, or consent wall is blocking the intended interaction or making the frame unusable:
|
|
71
|
+
- Add a \`dismiss_overlays\` step first
|
|
72
|
+
- Then retry the intended action or continue with the next recovery strategy
|
|
73
|
+
- Do NOT hide the product's own chat/assistant widget if the overall demo goal is to show that widget
|
|
74
|
+
|
|
75
|
+
### 1. Use direct navigation ONLY for technical preparation steps
|
|
76
|
+
Direct \`navigate\` is allowed only when at least one of these is true:
|
|
77
|
+
- the original failed step was already a \`navigate\`
|
|
78
|
+
- the step is explicitly marked \`recordingIntent: "prepare_only"\`
|
|
79
|
+
- there is no user-visible navigation requirement and the overall goal is just to land on the initial page before the video begins
|
|
80
|
+
|
|
81
|
+
If the original step is a visible click/hover/highlight in the actual demo:
|
|
82
|
+
- DO NOT replace it with a direct URL jump
|
|
83
|
+
- instead fix the selector, scroll to reveal the element, open the correct menu, or target a more stable nav element
|
|
84
|
+
- If the goal is a SPECIFIC product/page (for example "iPhone 17e"), the replacement must target that exact product/page. Do NOT downgrade it to a family selector like \`[href*="/iphone/"]\` or a generic "iPhone" nav click unless you also add the missing intermediate step(s).
|
|
85
|
+
|
|
86
|
+
### 2. Fix the selector (single step, same type)
|
|
87
|
+
The element exists on the page but the selector was wrong. Look at the screenshot and use a more specific selector:
|
|
88
|
+
- \`:has-text('Exact visible label')\` — most reliable
|
|
89
|
+
- \`[aria-label*='keyword']\`, \`[href*='keyword']\`
|
|
90
|
+
- Fallback chain: \`selector1, selector2, selector3\`
|
|
91
|
+
|
|
92
|
+
### 3. Scroll to find the element, then act (two steps)
|
|
93
|
+
The element may be below the fold. Replace the failed step with:
|
|
94
|
+
- A \`scroll\` step (direction "down", amount 400–800px)
|
|
95
|
+
- Then the original action with a corrected selector
|
|
96
|
+
Use this when the demo script mentions scrolling or the target section is not visible in the screenshot.
|
|
97
|
+
|
|
98
|
+
### 4. Wait longer then retry (two steps)
|
|
99
|
+
The page was still animating. Replace with:
|
|
100
|
+
- A \`wait\` step (waitMs 2000–4000)
|
|
101
|
+
- Then the original action with the same or corrected selector
|
|
102
|
+
|
|
103
|
+
## Key name rule — CRITICAL
|
|
104
|
+
For \`key\` steps, Playwright key names are **case-sensitive**. Always use exact capitalization:
|
|
105
|
+
- ✅ "Enter", "Tab", "Escape", "Backspace", "ArrowDown", "ArrowUp", "Control+A"
|
|
106
|
+
- ❌ "enter", "tab", "escape" — these will throw a runtime error
|
|
107
|
+
|
|
108
|
+
## Output format
|
|
109
|
+
Respond with ONLY this JSON:
|
|
110
|
+
{ "steps": [ { ...step1 }, { ...step2 }, ... ] }
|
|
111
|
+
|
|
112
|
+
Each step schema: { id, type, description, target?, selector?, coordinates?, toTarget?, toSelector?, toCoordinates?, text?, optionLabel?, optionValue?, optionIndex?, direction?, amount?, key?, durationMs?, waitMs?, postStepWaitMs?, expectedPageAfter? }
|
|
113
|
+
Allowed step types: \`navigate\`, \`dismiss_overlays\`, \`click\`, \`type\`, \`select_option\`, \`scroll\`, \`wait\`, \`hover\`, \`drag\`, \`key\`, \`highlight\`, \`assert_url\`, \`assert_text\`, \`assert_element\`, \`assert_page\`
|
|
114
|
+
Keep the original step \`id\` for the first replacement step. Preserve \`recordingIntent\` and any specific destination contract (\`expectedPageAfter\`) unless you are explicitly replacing it with an equivalent stronger contract. Name additional steps \`id + "-b"\`, \`id + "-c"\`, etc.`;
|
|
115
|
+
}
|
|
116
|
+
export function buildStepFixerUserMessage(step, failureReason, suggestion, observationSummary, observationSnapshot) {
|
|
117
|
+
return `Failed step:
|
|
118
|
+
${JSON.stringify(step, null, 2)}
|
|
119
|
+
|
|
120
|
+
Recording intent: ${step.recordingIntent ?? 'visible'}
|
|
121
|
+
|
|
122
|
+
Failure reason: ${failureReason}${suggestion ? `\nVerifier suggestion: ${suggestion}` : ''}
|
|
123
|
+
|
|
124
|
+
${observationSummary ? `Runtime page observation:\n${observationSummary}\n` : ''}${observationSnapshot ? `Structured observation snapshot:\n${serializeObservationSnapshot(observationSnapshot)}\n` : ''}
|
|
125
|
+
|
|
126
|
+
The screenshot shows the page state after the failure. Based on what you see, output a corrected step JSON.`;
|
|
127
|
+
}
|
|
128
|
+
// ── Cursor overlay script injected into every recorded page ──────────
|
|
129
|
+
// SVG files are the single source of truth — edit web/public/cursors/*.svg directly.
|
|
130
|
+
function loadCursorSvg(name) {
|
|
131
|
+
return fs.readFileSync(new URL(`../web/public/cursors/${name}.svg`, import.meta.url), 'utf-8').trim();
|
|
132
|
+
}
|
|
133
|
+
const CURSOR_THEME_SVGS = {
|
|
134
|
+
macos: loadCursorSvg('macos'),
|
|
135
|
+
windows: loadCursorSvg('windows'),
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* JavaScript injected via `context.addInitScript()` to show a visible animated
|
|
139
|
+
* cursor in Playwright video recordings (the native OS cursor is invisible).
|
|
140
|
+
*/
|
|
141
|
+
export function buildCursorOverlayScript(theme = 'minimal') {
|
|
142
|
+
const cursorSvg = theme === 'minimal' ? null : CURSOR_THEME_SVGS[theme];
|
|
143
|
+
return `
|
|
144
|
+
(function() {
|
|
145
|
+
function injectCursor() {
|
|
146
|
+
if (document.getElementById('__ak_cursor__')) return;
|
|
147
|
+
const cursorTheme = ${JSON.stringify(theme)};
|
|
148
|
+
const cursorSvg = ${JSON.stringify(cursorSvg)};
|
|
149
|
+
|
|
150
|
+
const style = document.createElement('style');
|
|
151
|
+
style.textContent = [
|
|
152
|
+
'*, *::before, *::after { cursor: none !important; }',
|
|
153
|
+
'#__ak_cursor__ { mix-blend-mode: normal; }',
|
|
154
|
+
'#__ak_cursor__ svg { width: 100%; height: 100%; display: block; overflow: visible; }',
|
|
155
|
+
'#__ak_cursor__[data-theme="macos"], #__ak_cursor__[data-theme="windows"] { width: 28px !important; height: 28px !important; background: transparent !important; border: 0 !important; border-radius: 0 !important; box-shadow: none !important; }',
|
|
156
|
+
'#__ak_cursor__.__ak_pressed { transform: translate(-50%, -50%) scale(0.72) !important; box-shadow: 0 0 0 8px rgba(37, 99, 235, 0.18), 0 4px 18px rgba(0,0,0,0.28) !important; background: rgba(255,255,255,0.98) !important; border-color: rgba(30,41,59,0.92) !important; }',
|
|
157
|
+
'#__ak_cursor__[data-theme="macos"].__ak_pressed, #__ak_cursor__[data-theme="windows"].__ak_pressed { background: transparent !important; border-color: transparent !important; box-shadow: none !important; filter: drop-shadow(0 0 0 rgba(37, 99, 235, 0)) drop-shadow(0 0 0 rgba(37, 99, 235, 0)) !important; }',
|
|
158
|
+
'#__ak_cursor__[data-theme="macos"].__ak_pressed svg, #__ak_cursor__[data-theme="windows"].__ak_pressed svg { filter: drop-shadow(0 0 0.5px rgba(255,255,255,0.9)) drop-shadow(0 0 10px rgba(37, 99, 235, 0.45)); }',
|
|
159
|
+
'#__ak_cursor_click_pulse__ { position: fixed; width: 20px; height: 20px; border-radius: 9999px; pointer-events: none; z-index: 2147483646; border: 3px solid rgba(37, 99, 235, 0.75); box-shadow: 0 0 0 1px rgba(255,255,255,0.45) inset; transform: translate(-50%, -50%) scale(0.55); opacity: 0; }',
|
|
160
|
+
'#__ak_cursor_click_pulse__.__ak_active { animation: __ak_cursor_pulse__ 340ms cubic-bezier(0.16, 1, 0.3, 1) forwards; }',
|
|
161
|
+
'@keyframes __ak_cursor_pulse__ { 0% { opacity: 0.95; transform: translate(-50%, -50%) scale(0.55); } 100% { opacity: 0; transform: translate(-50%, -50%) scale(2.6); } }',
|
|
162
|
+
].join('\\n');
|
|
163
|
+
document.head.appendChild(style);
|
|
164
|
+
|
|
165
|
+
const cursor = document.createElement('div');
|
|
166
|
+
cursor.id = '__ak_cursor__';
|
|
167
|
+
cursor.dataset.theme = cursorTheme;
|
|
168
|
+
cursor.style.cssText = [
|
|
169
|
+
'position: fixed',
|
|
170
|
+
'top: -100px',
|
|
171
|
+
'left: -100px',
|
|
172
|
+
'width: 20px',
|
|
173
|
+
'height: 20px',
|
|
174
|
+
'background: rgba(255, 255, 255, 0.95)',
|
|
175
|
+
'border: 2.5px solid rgba(0, 0, 0, 0.8)',
|
|
176
|
+
'border-radius: 50%',
|
|
177
|
+
'pointer-events: none',
|
|
178
|
+
'z-index: 2147483647',
|
|
179
|
+
'transform: translate(-50%, -50%)',
|
|
180
|
+
'box-shadow: 0 2px 8px rgba(0,0,0,0.4)',
|
|
181
|
+
'transition: transform 0.09s ease, box-shadow 0.12s ease, background 0.12s ease, border-color 0.12s ease',
|
|
182
|
+
'will-change: left, top',
|
|
183
|
+
].join(';');
|
|
184
|
+
if (cursorSvg) cursor.innerHTML = cursorSvg;
|
|
185
|
+
|
|
186
|
+
const pulse = document.createElement('div');
|
|
187
|
+
pulse.id = '__ak_cursor_click_pulse__';
|
|
188
|
+
|
|
189
|
+
document.body.appendChild(cursor);
|
|
190
|
+
document.body.appendChild(pulse);
|
|
191
|
+
|
|
192
|
+
let pulseResetTimer = null;
|
|
193
|
+
|
|
194
|
+
function setCursorPosition(x, y) {
|
|
195
|
+
cursor.style.left = x + 'px';
|
|
196
|
+
cursor.style.top = y + 'px';
|
|
197
|
+
pulse.style.left = x + 'px';
|
|
198
|
+
pulse.style.top = y + 'px';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function triggerPulse() {
|
|
202
|
+
pulse.classList.remove('__ak_active');
|
|
203
|
+
void pulse.offsetWidth;
|
|
204
|
+
pulse.classList.add('__ak_active');
|
|
205
|
+
if (pulseResetTimer) clearTimeout(pulseResetTimer);
|
|
206
|
+
pulseResetTimer = setTimeout(function() {
|
|
207
|
+
pulse.classList.remove('__ak_active');
|
|
208
|
+
}, 380);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Track mouse position via mousemove (fired by Playwright's page.mouse.move)
|
|
212
|
+
document.addEventListener('mousemove', function(e) {
|
|
213
|
+
setCursorPosition(e.clientX, e.clientY);
|
|
214
|
+
}, { passive: true });
|
|
215
|
+
|
|
216
|
+
window.addEventListener('mousedown', function(e) {
|
|
217
|
+
setCursorPosition(e.clientX, e.clientY);
|
|
218
|
+
cursor.classList.add('__ak_pressed');
|
|
219
|
+
triggerPulse();
|
|
220
|
+
}, true);
|
|
221
|
+
window.addEventListener('mouseup', function(e) {
|
|
222
|
+
setCursorPosition(e.clientX, e.clientY);
|
|
223
|
+
cursor.classList.remove('__ak_pressed');
|
|
224
|
+
}, true);
|
|
225
|
+
window.addEventListener('click', function(e) {
|
|
226
|
+
setCursorPosition(e.clientX, e.clientY);
|
|
227
|
+
triggerPulse();
|
|
228
|
+
}, true);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// addInitScript runs before DOM is parsed — wait for body to be ready
|
|
232
|
+
if (document.readyState === 'loading') {
|
|
233
|
+
document.addEventListener('DOMContentLoaded', injectCursor);
|
|
234
|
+
} else {
|
|
235
|
+
injectCursor();
|
|
236
|
+
}
|
|
237
|
+
})();
|
|
238
|
+
`;
|
|
239
|
+
}
|
|
240
|
+
// ── Video planner prompts ────────────────────────────────────────────
|
|
241
|
+
export function buildVideoPlannerSystemPrompt(options = {}) {
|
|
242
|
+
const mode = options.mode ?? 'full';
|
|
243
|
+
const modeInstructions = mode === 'variant_prefix'
|
|
244
|
+
? `## Planner mode
|
|
245
|
+
This call is in VARIANT PREFIX mode.
|
|
246
|
+
- Output ONLY the minimal steps required to activate the requested language/theme variant.
|
|
247
|
+
- Do NOT execute the main demo flow.
|
|
248
|
+
- Do NOT open the target product/page/section described by the business script in this mode. That belongs to the base plan, not the prefix plan.
|
|
249
|
+
- End with assertions proving language/theme are active.
|
|
250
|
+
- If the observed live page does NOT already match the requested language/theme, you MUST include at least one concrete activation step before any assertion.
|
|
251
|
+
- Valid activation steps are things like \`navigate\`, \`click\`, \`select_option\`, \`type\`, or \`key\`.
|
|
252
|
+
- A plan made only of \`wait\`, \`scroll\`, \`hover\`, \`highlight\`, \`dismiss_overlays\`, or assertions is INVALID when the requested variant is not yet active.
|
|
253
|
+
- Because variant prefix runs before capture, a direct \`navigate\` to the correct locale/theme URL is allowed and preferred when the observation exposes a reliable localized route (for example via \`hreflang\`, canonical links, locale links, or a stable locale path like \`/fr/\`).`
|
|
254
|
+
: mode === 'clip'
|
|
255
|
+
? `## Planner mode
|
|
256
|
+
This call is in CLIP mode.
|
|
257
|
+
You are producing a plan for a MICRO-CLIP: a very short, looping animation (2–8 seconds) that showcases a single UI interaction.
|
|
258
|
+
|
|
259
|
+
STRICT CONSTRAINTS:
|
|
260
|
+
- Maximum 4 steps (excluding dismiss_overlays and assertions). Fewer is better.
|
|
261
|
+
- Target duration: 2–8 seconds total.
|
|
262
|
+
- DO NOT include navigation steps unless the clip specifically needs to start from a different page than the current one.
|
|
263
|
+
- ALWAYS start with a \`dismiss_overlays\` step. Cookie banners, consent walls, and popups ruin clips. This step does NOT count toward the 4-step limit.
|
|
264
|
+
- Prefer postStepWaitMs (500–1500ms) after the main interaction to let animations/transitions play out before the recording ends.
|
|
265
|
+
- All the "Clean-video rules" from the general instructions apply fully to clips.
|
|
266
|
+
|
|
267
|
+
CRITICAL RULE — respect user intent, do not embellish:
|
|
268
|
+
- If the script is specific (e.g. "scroll down the page"), follow it literally. Produce exactly the steps described.
|
|
269
|
+
- If the script is vague (e.g. "show the pricing section"), use your judgment to produce the best steps — but still keep it minimal.
|
|
270
|
+
- In ALL cases: never add steps the user did not ask for. No "return to start", "reset", or "cleanup" steps unless explicitly requested.
|
|
271
|
+
- GIF looping is handled by post-processing. Never add steps to close a loop.
|
|
272
|
+
|
|
273
|
+
TYPICAL CLIP PATTERNS:
|
|
274
|
+
- Click → reaction: click a button/tab/toggle → show the resulting UI change
|
|
275
|
+
- Hover → reveal: hover an element → show tooltip/dropdown/menu
|
|
276
|
+
- Scroll → reveal: scroll to show a section appearing
|
|
277
|
+
- Type → preview: type in a search box → show suggestions appearing
|
|
278
|
+
- Toggle → state change: flip a switch → show the UI adapting
|
|
279
|
+
|
|
280
|
+
OUTPUT: Same JSON format as regular video plans, but respect the step limit.
|
|
281
|
+
- Assume variant switching already happened before this plan starts.`
|
|
282
|
+
: mode === 'base'
|
|
283
|
+
? `## Planner mode
|
|
284
|
+
This call is in BASE PLAN mode.
|
|
285
|
+
- Output ONLY the stable demo flow.
|
|
286
|
+
- Do NOT include language/theme switching steps.
|
|
287
|
+
- Assume variant switching already happened before this plan starts.`
|
|
288
|
+
: `## Planner mode
|
|
289
|
+
This call is in FULL PLAN mode.
|
|
290
|
+
- Output a complete flow and include variant switching only if needed.`;
|
|
291
|
+
return `You are a product demo video script analyzer. Your job is to read a user's natural-language video script and convert it into a precise, deterministic JSON execution plan for browser automation.
|
|
292
|
+
|
|
293
|
+
${modeInstructions}
|
|
294
|
+
|
|
295
|
+
## Output format
|
|
296
|
+
|
|
297
|
+
You MUST respond with a single valid JSON object matching this exact structure:
|
|
298
|
+
{
|
|
299
|
+
"title": "Short title for the video",
|
|
300
|
+
"estimatedDurationSec": <number>,
|
|
301
|
+
"startUrl": "https://...",
|
|
302
|
+
"steps": [
|
|
303
|
+
{
|
|
304
|
+
"id": "step-1",
|
|
305
|
+
"type": "<step type>",
|
|
306
|
+
"description": "What this step does (shown in UI)",
|
|
307
|
+
... (step-specific fields)
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
## Step types and their fields
|
|
313
|
+
|
|
314
|
+
| type | Required fields | Optional fields |
|
|
315
|
+
|------|----------------|-----------------|
|
|
316
|
+
| navigate | url | postStepWaitMs |
|
|
317
|
+
| dismiss_overlays | - | postStepWaitMs |
|
|
318
|
+
| click | target OR selector OR coordinates | postStepWaitMs |
|
|
319
|
+
| type | target OR selector OR coordinates, text | postStepWaitMs |
|
|
320
|
+
| select_option | target OR selector, and one of optionLabel/optionValue/optionIndex | postStepWaitMs |
|
|
321
|
+
| scroll | direction ("up"/"down"/"left"/"right"), amount (pixels) | target or selector (if set, centers that element in viewport — preferred over pixel amounts), postStepWaitMs |
|
|
322
|
+
| wait | waitMs | - |
|
|
323
|
+
| hover | target OR selector OR coordinates | postStepWaitMs |
|
|
324
|
+
| drag | target OR selector OR coordinates, and toTarget OR toSelector OR toCoordinates | durationMs, postStepWaitMs |
|
|
325
|
+
| key | key (Playwright key name, e.g. "Enter", "Tab", "Control+A") | postStepWaitMs |
|
|
326
|
+
| highlight | target OR coordinates OR selector | postStepWaitMs |
|
|
327
|
+
| assert_url | urlPattern | matchMode ("equals"/"contains"/"regex"), timeoutMs |
|
|
328
|
+
| assert_text | text | scopeSelector, matchMode ("equals"/"contains"/"regex"), timeoutMs |
|
|
329
|
+
| assert_element | target OR selector | state ("visible"/"attached"), timeoutMs |
|
|
330
|
+
| assert_page | pageExpectation | timeoutMs |
|
|
331
|
+
|
|
332
|
+
For \`target\` and \`toTarget\`, prefer this structured shape when the page has several similar controls:
|
|
333
|
+
\`{"label":"New preset","labelMatchMode":"exact","role":"button","tag":"button","href":null,"selector":"button:has-text('New preset')","selectorAlternates":["[role='menuitem']:has-text('New preset')"],"containerLabel":"New menu"}\`
|
|
334
|
+
Use \`coordinates\` only as a tie-breaker, never as the sole durable anchor when a label, href, role, or selector exists.
|
|
335
|
+
|
|
336
|
+
For \`assert_page\`, use a JSON object like:
|
|
337
|
+
\`{"urlPatterns":["/iphone-17e/"],"titlePatterns":["iPhone 17e"],"textPatterns":["Say hello to a good buy"],"selectors":["main","h1"],"locale":"fr","minConfidence":0.7}\`
|
|
338
|
+
|
|
339
|
+
For navigation-causing action steps such as \`navigate\`, \`click\`, \`select_option\`, \`type\`, or \`key\`, you may also add:
|
|
340
|
+
\`"expectedPageAfter": {"urlPatterns":["/iphone-17e/"],"titlePatterns":["iPhone 17e"],"locale":"fr","minConfidence":0.7}\`
|
|
341
|
+
Use this whenever the action must land on a specific destination. It is a runtime guard against clicking the wrong element.
|
|
342
|
+
|
|
343
|
+
## Assertion rules — REQUIRED
|
|
344
|
+
|
|
345
|
+
- Add assertion steps after every critical transition:
|
|
346
|
+
- page navigation
|
|
347
|
+
- language/theme switch
|
|
348
|
+
- critical UI transition (tab/modal/search/route change)
|
|
349
|
+
- For critical navigation actions, also attach \`expectedPageAfter\` to the action step itself when the destination is specific.
|
|
350
|
+
- Prefer \`assert_page\` for route/locale/page-state verification. It is multi-signal and more robust than a single naive URL substring.
|
|
351
|
+
- Assertions must be deterministic and machine-checkable.
|
|
352
|
+
- Use concrete URL/text/element/page checks, never visual wording like "looks correct".
|
|
353
|
+
|
|
354
|
+
## Demo intent coverage
|
|
355
|
+
|
|
356
|
+
The user may describe the video as a BUSINESS GOAL, not as low-level browser steps. Convert intent into deterministic browser actions.
|
|
357
|
+
|
|
358
|
+
## Grounding rules — CRITICAL
|
|
359
|
+
|
|
360
|
+
You will receive an observed page snapshot built from the live DOM/accessibility tree when available.
|
|
361
|
+
- Treat this observation as ground truth for what is currently present on the page.
|
|
362
|
+
- Prefer labels, links, controls, headings, and selectors that are supported by the observed snapshot.
|
|
363
|
+
- If the user asks for something not present in the observation, do NOT hallucinate a selector. Instead use a stable navigation path, search flow, or direct technical preparation step only when allowed by the recording rules.
|
|
364
|
+
- When the observation exposes concrete hrefs, nav labels, locale links, or breadcrumbs, reuse them in the plan.
|
|
365
|
+
- When the observation exposes likely variant controls or storage keys, use that structure to infer how locale/theme switching actually works on this site.
|
|
366
|
+
- In \`variant_prefix\` mode, if the observation still shows the wrong locale/theme, your first job is to change that state, not to assert it.
|
|
367
|
+
|
|
368
|
+
Common request categories you must support:
|
|
369
|
+
- Feature reveal: show a hero section, pricing block, testimonial strip, or feature card
|
|
370
|
+
- Product navigation: open a product page, pricing page, integrations page, docs page, or login page
|
|
371
|
+
- Authenticated demo: sign in with provided credentials, then show dashboard/settings/billing/non-destructive admin state
|
|
372
|
+
- Responsive showcase: demonstrate the mobile/tablet/desktop version of the same site in separate recordings
|
|
373
|
+
- Localization/theme demo: switch language or theme and prove the requested variant is active
|
|
374
|
+
- Search/filter exploration: type into a search field, open a filter, reveal results, and stop on a clean stable frame
|
|
375
|
+
- Comparison/slider reveal: use \`drag\` only when a visual before/after slider or handle must be moved to demonstrate the feature
|
|
376
|
+
|
|
377
|
+
Translate those requests into the SHORTEST deterministic flow that produces a clean, presentation-ready demo.
|
|
378
|
+
|
|
379
|
+
## Selector rules — CRITICAL
|
|
380
|
+
|
|
381
|
+
Always use the most specific selector possible to avoid clicking the wrong element.
|
|
382
|
+
|
|
383
|
+
1. Prefer structured targets first. When the observation shows several similar elements, emit a structured target object with label, href, role, tag, and alternate selectors instead of relying on one raw selector.
|
|
384
|
+
2. Prefer text based selectors next. Use named buttons, links, inputs, and menu items before generic classes.
|
|
385
|
+
3. Combine text, tag, and container context when several similar elements exist.
|
|
386
|
+
4. Never rely on internal automation attributes or unstable positional selectors as the only anchor.
|
|
387
|
+
|
|
388
|
+
3. **Fallback chains with commas**: Provide 2-3 fallback selectors separated by commas when you cannot express all anchors in a \`target\`.
|
|
389
|
+
- Example: \`a:has-text('iPhone 17e'), [href*=iphone-17e], [aria-label*='iPhone 17e']\`
|
|
390
|
+
|
|
391
|
+
4. **Never use positional/generic selectors alone**: \`.nav-item:nth-child(2)\` or \`li:first-child a\` are fragile and likely to select the wrong element.
|
|
392
|
+
5. **Never use internal automation selectors**: \`[data-ak-*]\` selectors are not stable across navigations or browser sessions.
|
|
393
|
+
|
|
394
|
+
## Navigation rules — CRITICAL
|
|
395
|
+
|
|
396
|
+
**NEVER click hero sliders, auto-rotating carousels, or animated banners.** These elements change content after page load. Clicking them navigates to whichever slide happens to be visible at click time — not the intended target.
|
|
397
|
+
|
|
398
|
+
**During the visible recording flow, navigate in this priority order:**
|
|
399
|
+
1. **Stable navigation element**: Use a top-nav link, dropdown, breadcrumb, footer link, or search result that a real user would click.
|
|
400
|
+
2. **Search flow**: Use a visible search or menu flow when that is how a user would naturally find the destination.
|
|
401
|
+
3. **Direct navigate step**: Use \`navigate\` only for the initial landing page or a technical preparation step with \`recordingIntent: "prepare_only"\`. Do NOT use it as a shortcut in the middle of a visible demo if the goal is to show a believable user journey.
|
|
402
|
+
|
|
403
|
+
When the user asks for a SPECIFIC product page, use exact product labels/routes/selectors. Do NOT collapse that into a family page selector like \`[href*="/iphone/"]\` if the real target is \`iPhone 17e\` or \`/iphone-17e/\`.
|
|
404
|
+
|
|
405
|
+
## Key name rules
|
|
406
|
+
|
|
407
|
+
For \`key\` steps, Playwright key names are **case-sensitive**. Always use exact capitalization:
|
|
408
|
+
- ✅ "Enter", "Tab", "Escape", "Backspace", "ArrowDown", "ArrowUp", "Control+A", "Meta+A"
|
|
409
|
+
- ❌ "enter", "tab", "escape" (will throw an error)
|
|
410
|
+
|
|
411
|
+
## Timing rules
|
|
412
|
+
|
|
413
|
+
- After each **navigate** step: add a **wait** step with \`waitMs: 1200\` to let page animations and lazy-loaded content fully render before proceeding.
|
|
414
|
+
- **postStepWaitMs** for clicks: 500–800ms (enough for the resulting UI change to settle).
|
|
415
|
+
- **postStepWaitMs** for scroll: 500–800ms.
|
|
416
|
+
- **postStepWaitMs** for type: 400–600ms.
|
|
417
|
+
- For pages with complex animations (e-commerce, marketing sites): increase wait steps to 2000ms.
|
|
418
|
+
- Keep the total demo CONCISE — avoid unnecessary waits. A snappy demo is better than a slow one.
|
|
419
|
+
|
|
420
|
+
## Clean-video rules — CRITICAL
|
|
421
|
+
|
|
422
|
+
- This is a showcase video, not a QA trace. The viewer should only see intentional actions.
|
|
423
|
+
- Use \`dismiss_overlays\` whenever a cookie banner, newsletter modal, consent wall, sticky feedback widget, or unrelated popup blocks the content.
|
|
424
|
+
- Do NOT dismiss or hide the product's own chat/assistant/support widget if the user explicitly wants to demonstrate it.
|
|
425
|
+
- Prefer stable navigation elements. Use direct navigation only for initial landing or hidden preparation.
|
|
426
|
+
- Use \`select_option\` for real dropdown controls instead of brittle click chains when possible.
|
|
427
|
+
- Keep the flow concise. Do not wander through irrelevant UI or perform redundant clicks.
|
|
428
|
+
- Before any important click, make sure the target is fully visible and not clipped by the viewport edge.
|
|
429
|
+
- End on a stable frame: no spinner, no half-open transition, no partially visible target, no obstructive overlay.
|
|
430
|
+
- Never perform destructive or side-effect-heavy actions. Stay read-only except for authentication and harmless search/filter inputs.
|
|
431
|
+
- NEVER plan uploads, file pickers, save/publish flows, or any action that sends or mutates user data.
|
|
432
|
+
- Technical setup steps that should happen before capture may use \`recordingIntent: "prepare_only"\`. These steps are allowed to be efficient and invisible in the final video.
|
|
433
|
+
|
|
434
|
+
## Other rules
|
|
435
|
+
|
|
436
|
+
- If credentials are available, NEVER put literal secrets in the plan. Use these placeholders instead:
|
|
437
|
+
- \`{{credential.loginUrl}}\`
|
|
438
|
+
- \`{{credential.email}}\`
|
|
439
|
+
- \`{{credential.password}}\`
|
|
440
|
+
- **IDs**: Use simple incrementing IDs: "step-1", "step-2", etc.
|
|
441
|
+
- **recordingIntent**: Omit it for normal visible steps. Use \`"prepare_only"\` only for technical steps that should happen before the video starts.
|
|
442
|
+
- **Descriptions**: Clear, present-tense ("Click the iPhone 17e link", not "Click link").
|
|
443
|
+
- **estimatedDurationSec**: Sum of all waits + ~800ms per animated mouse move + typing time.
|
|
444
|
+
- **Highlight steps**: Insert a \`highlight\` step before clicking to draw the viewer's eye to the target.
|
|
445
|
+
- **startUrl**: Use the provided base URL unless the script specifies a different starting page.
|
|
446
|
+
- **Scroll to a named section**: When the goal is to reveal a specific section or element (e.g. "scroll to the pricing section"), use \`scroll\` with a \`selector\` targeting that element. This centers it perfectly in the viewport. Only use direction+amount for generic scrolling with no specific target. Example: \`{"type":"scroll","selector":"section:has-text('Say hello')","description":"Scroll to the hello section"}\`
|
|
447
|
+
|
|
448
|
+
## Example
|
|
449
|
+
|
|
450
|
+
For the script: "Go to the homepage, click login, enter the provided credentials, submit"
|
|
451
|
+
|
|
452
|
+
Output:
|
|
453
|
+
{
|
|
454
|
+
"title": "Login flow demo",
|
|
455
|
+
"estimatedDurationSec": 20,
|
|
456
|
+
"startUrl": "https://example.com",
|
|
457
|
+
"steps": [
|
|
458
|
+
{"id": "step-1", "type": "navigate", "description": "Navigate to homepage", "url": "https://example.com"},
|
|
459
|
+
{"id": "step-2", "type": "wait", "description": "Wait for page animations to settle", "waitMs": 1200},
|
|
460
|
+
{"id": "step-3", "type": "highlight", "description": "Highlight the login link", "selector": "a:has-text('Login'), a:has-text('Sign in'), [aria-label*='login']", "postStepWaitMs": 400},
|
|
461
|
+
{"id": "step-4", "type": "click", "description": "Click the login link", "selector": "a:has-text('Login'), a:has-text('Sign in'), [aria-label*='login']", "postStepWaitMs": 800},
|
|
462
|
+
{"id": "step-5", "type": "wait", "description": "Wait for login page to load", "waitMs": 1200},
|
|
463
|
+
{"id": "step-6", "type": "click", "description": "Click the email field", "selector": "input[type=email], input[name=email], #email", "postStepWaitMs": 400},
|
|
464
|
+
{"id": "step-7", "type": "type", "description": "Type email address", "selector": "input[type=email], input[name=email], #email", "text": "{{credential.email}}", "postStepWaitMs": 500},
|
|
465
|
+
{"id": "step-8", "type": "click", "description": "Click the password field", "selector": "input[type=password], input[name=password], #password", "postStepWaitMs": 300},
|
|
466
|
+
{"id": "step-9", "type": "type", "description": "Type password", "selector": "input[type=password], input[name=password], #password", "text": "{{credential.password}}", "postStepWaitMs": 500},
|
|
467
|
+
{"id": "step-10", "type": "click", "description": "Submit the login form", "selector": "button[type=submit]:has-text('Sign in'), button[type=submit]:has-text('Login'), input[type=submit]", "postStepWaitMs": 800},
|
|
468
|
+
{"id": "step-11", "type": "assert_page", "description": "Verify the login destination is active", "pageExpectation": {"urlPatterns": ["/dashboard", "/app"], "titlePatterns": ["Dashboard"], "selectors": ["main", "nav"], "minConfidence": 0.65}, "timeoutMs": 6000}
|
|
469
|
+
]
|
|
470
|
+
}`;
|
|
471
|
+
}
|
|
472
|
+
export function buildVideoPlannerUserMessage(script, url, options = {}) {
|
|
473
|
+
const mode = options.mode ?? 'full';
|
|
474
|
+
const variant = options.variant;
|
|
475
|
+
const variantLines = [
|
|
476
|
+
variant?.lang ? `Requested language: ${variant.lang}` : '',
|
|
477
|
+
variant?.theme ? `Requested theme: ${variant.theme}` : '',
|
|
478
|
+
variant?.langInstructions?.trim() ? `Language switch instructions:\n${variant.langInstructions.trim()}` : '',
|
|
479
|
+
variant?.themeInstructions?.trim() ? `Theme switch instructions:\n${variant.themeInstructions.trim()}` : '',
|
|
480
|
+
options.credentials?.loginUrl ? 'Credential placeholder available: {{credential.loginUrl}}' : '',
|
|
481
|
+
options.credentials?.email ? 'Credential placeholder available: {{credential.email}}' : '',
|
|
482
|
+
options.credentials?.password ? 'Credential placeholder available: {{credential.password}}' : '',
|
|
483
|
+
]
|
|
484
|
+
.filter(Boolean)
|
|
485
|
+
.join('\n\n');
|
|
486
|
+
return `Base URL: ${url}
|
|
487
|
+
|
|
488
|
+
Planner mode: ${mode}
|
|
489
|
+
|
|
490
|
+
Video script to convert into an execution plan:
|
|
491
|
+
---
|
|
492
|
+
${script}
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
${variantLines ? `Variant context:\n${variantLines}\n\n` : ''}${options.observationSummary ? `Observed live page context:\n${options.observationSummary}\n\n` : ''}${options.observationSnapshot ? `Structured observation snapshot:\n${serializeObservationSnapshot(options.observationSnapshot)}\n\n` : ''}Convert this script into a JSON execution plan following the format described in the system prompt. Output ONLY the JSON object, no explanation.`;
|
|
496
|
+
}
|
|
497
|
+
// ── Video step verification prompts ──────────────────────────────────
|
|
498
|
+
export function buildVideoVerificationSystemPrompt(videoScript) {
|
|
499
|
+
return `You are a strict browser automation step verifier. You will receive:
|
|
500
|
+
1. The overall video script (what the user wants to demonstrate)
|
|
501
|
+
2. A description of the specific step that was just executed
|
|
502
|
+
3. Page context such as current URL/title
|
|
503
|
+
4. A runtime page observation summary from the live DOM/accessibility tree
|
|
504
|
+
5. A screenshot of the current page state after the step
|
|
505
|
+
|
|
506
|
+
The screenshot and runtime observation describe the same verification snapshot unless the message explicitly says the snapshot was stale.
|
|
507
|
+
|
|
508
|
+
Your job: determine with precision whether the step achieved its intended outcome.
|
|
509
|
+
|
|
510
|
+
## Overall video goal
|
|
511
|
+
${videoScript}
|
|
512
|
+
|
|
513
|
+
## Decision rules
|
|
514
|
+
|
|
515
|
+
Call **step_ok** only if ALL of these are true:
|
|
516
|
+
- The step's specific intent was achieved (e.g. if the step was "click iPhone 17e link", the iPhone 17e product page is now showing — NOT a different product)
|
|
517
|
+
- The page content matches what the script intends at this point in the flow
|
|
518
|
+
- No unexpected page, error, or wrong section is visible
|
|
519
|
+
|
|
520
|
+
Call **step_failed** if ANY of these is true:
|
|
521
|
+
- The page shows the WRONG content (e.g. wrong product, wrong section, different page than expected)
|
|
522
|
+
- The element was not found, not clicked, or the text was not typed
|
|
523
|
+
- An error message appeared (404, form error, network error)
|
|
524
|
+
- The page looks identical to before (nothing happened)
|
|
525
|
+
- A navigation step led to the wrong destination
|
|
526
|
+
- A cookie banner, consent wall, modal, sticky overlay, or unrelated popup is obstructing the intended content
|
|
527
|
+
- The frame is still loading or unstable (spinner, skeleton, transition, partially visible target)
|
|
528
|
+
- The requested language/theme/state is still not active when this step was supposed to activate it
|
|
529
|
+
|
|
530
|
+
Call **give_up** only if:
|
|
531
|
+
- The page is completely broken (HTTP 5xx error, infinite spinner, JS crash)
|
|
532
|
+
- There is truly no way to continue the recording
|
|
533
|
+
|
|
534
|
+
## Important
|
|
535
|
+
Be precise about CONTENT and PRESENTATION QUALITY. If the script says "iPhone 17e page" and the screenshot shows "MacBook Air" — that is a failure, not a minor visual difference. If the right page is visible but a consent modal blocks it, that is also a failure for a publication-ready video.`;
|
|
536
|
+
}
|
|
537
|
+
export function buildVideoStepVerificationUserMessage(step, stepIndex, totalSteps, pageContext, observationSummary, observationSnapshot) {
|
|
538
|
+
const details = [
|
|
539
|
+
`Step type: ${step.type}`,
|
|
540
|
+
step.target ? `Structured target: ${JSON.stringify(step.target)}` : '',
|
|
541
|
+
step.selector ? `Selector targeted: ${step.selector}` : '',
|
|
542
|
+
step.text ? `Text typed: "${step.text}"` : '',
|
|
543
|
+
step.url ? `Navigated to: ${step.url}` : '',
|
|
544
|
+
step.direction ? `Scrolled: ${step.direction} by ${step.amount ?? 400}px` : '',
|
|
545
|
+
pageContext?.currentUrl ? `Current URL after step: ${pageContext.currentUrl}` : '',
|
|
546
|
+
pageContext?.pageTitle ? `Current page title: ${pageContext.pageTitle}` : '',
|
|
547
|
+
].filter(Boolean).join('\n');
|
|
548
|
+
return `Step ${stepIndex + 1} of ${totalSteps}: "${step.description}"
|
|
549
|
+
${details}
|
|
550
|
+
|
|
551
|
+
${observationSummary ? `Runtime page observation:\n${observationSummary}\n\n` : ''}${observationSnapshot ? `Structured observation snapshot:\n${serializeObservationSnapshot(observationSnapshot)}\n\n` : ''}
|
|
552
|
+
Look at the screenshot and verify: did this step achieve its specific intent, and is the frame clean enough for a polished product video? Check both functional correctness and presentation quality.`;
|
|
553
|
+
}
|
|
554
|
+
//# sourceMappingURL=video-prompts.js.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Tools used by the video step verification LLM call */
|
|
2
|
+
export const videoVerificationTools = [
|
|
3
|
+
{
|
|
4
|
+
type: 'function',
|
|
5
|
+
function: {
|
|
6
|
+
name: 'step_ok',
|
|
7
|
+
description: 'Confirm the step executed successfully and the page is in the expected state.',
|
|
8
|
+
parameters: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
observation: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'Brief description of what you see that confirms success.',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
required: ['observation'],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: 'function',
|
|
22
|
+
function: {
|
|
23
|
+
name: 'step_failed',
|
|
24
|
+
description: 'Report that the step did not produce the expected result but recovery may be possible.',
|
|
25
|
+
parameters: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
properties: {
|
|
28
|
+
reason: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'Why the step failed.',
|
|
31
|
+
},
|
|
32
|
+
suggestion: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'Suggested fix to recover (e.g. try a different selector, increase wait time).',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: ['reason'],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: 'function',
|
|
43
|
+
function: {
|
|
44
|
+
name: 'give_up',
|
|
45
|
+
description: 'Abort the entire video plan — the page is broken beyond recovery.',
|
|
46
|
+
parameters: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: {
|
|
49
|
+
reason: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
description: 'Why the video recording cannot continue.',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
required: ['reason'],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
//# sourceMappingURL=video-tools.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Browser } from './browser.js';
|
|
2
|
+
import type { VideoPageSignals } from './types.js';
|
|
3
|
+
export interface VariantStateDetection {
|
|
4
|
+
lang: {
|
|
5
|
+
requested: string | undefined;
|
|
6
|
+
detected: string | null;
|
|
7
|
+
active: boolean;
|
|
8
|
+
ambiguous: boolean;
|
|
9
|
+
};
|
|
10
|
+
theme: {
|
|
11
|
+
requested: 'light' | 'dark' | undefined;
|
|
12
|
+
detected: 'light' | 'dark' | null;
|
|
13
|
+
active: boolean;
|
|
14
|
+
ambiguous: boolean;
|
|
15
|
+
};
|
|
16
|
+
pageSignals: VideoPageSignals;
|
|
17
|
+
}
|
|
18
|
+
export declare function scoreLocaleSignals(signals: VideoPageSignals, requestedLang?: string): {
|
|
19
|
+
score: number;
|
|
20
|
+
reasons: string[];
|
|
21
|
+
ambiguous: boolean;
|
|
22
|
+
};
|
|
23
|
+
export declare function evaluateRequestedThemeState(signals: VideoPageSignals, requestedTheme?: 'light' | 'dark'): {
|
|
24
|
+
detected: 'light' | 'dark' | null;
|
|
25
|
+
active: boolean;
|
|
26
|
+
ambiguous: boolean;
|
|
27
|
+
reason: string;
|
|
28
|
+
};
|
|
29
|
+
export declare function detectVariantStateDeterministic(browser: Browser, requestedLang?: string, requestedTheme?: 'light' | 'dark'): Promise<VariantStateDetection>;
|