- Extension DOM mode: auth-context serializer — inlines CSS + images as data URLs, freezes form state, masks [data-dp-mask], strips scripts (R3 client) - API: extractDataUrls rewrites large inline resources into content-addressed assets (R3 server; verified 939KB inline -> 82-byte row + served asset) - Lead-gate steps: config on step, player overlay form blocks advance, submits to leads endpoint (editor toggle + MCP set_lead_gate) - Step-guide export (v2 slice): authed markdown + public HTML at /p/:token/guide - Video export (v3 slice, v0): ffmpeg slideshow of image steps + optional narration mux; DOM rasterization deferred to Chromium service (R4) - Editor: reorder arrows, embed-code copy, guide link, video export - REST reorder endpoint; MCP surface now 13 tools (export_guide, set_lead_gate, render_video) - ffmpeg added to runtime image E2E verified incl. R3 extraction round-trip, lead_gate in public JSON, guide renders, MCP agent exported guide + rendered video.
78 lines
3.5 KiB
JavaScript
78 lines
3.5 KiB
JavaScript
const $ = (id) => document.getElementById(id);
|
|
|
|
chrome.storage.local.get(["base", "key", "mode"], (v) => {
|
|
if (v.base) $("base").value = v.base;
|
|
if (v.key) $("key").value = v.key;
|
|
if (v.mode) $("mode").value = v.mode;
|
|
});
|
|
$("mode").addEventListener("change", () => chrome.storage.local.set({ mode: $("mode").value }));
|
|
const save = () => chrome.storage.local.set({ base: $("base").value.trim(), key: $("key").value.trim() });
|
|
$("base").addEventListener("change", save);
|
|
$("key").addEventListener("change", save);
|
|
|
|
function refresh() {
|
|
chrome.runtime.sendMessage({ type: "status" }, (s) => {
|
|
$("status").textContent = s.active ? `Recording — ${s.count} steps captured. Click through your flow, then Finish.` : "Not recording.";
|
|
$("start").hidden = s.active;
|
|
$("finish").hidden = !s.active;
|
|
$("discard").hidden = !s.active;
|
|
});
|
|
}
|
|
refresh();
|
|
|
|
$("start").addEventListener("click", async () => {
|
|
save();
|
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
chrome.runtime.sendMessage({ type: "start", tabId: tab.id, mode: $("mode").value }, refresh);
|
|
});
|
|
|
|
$("discard").addEventListener("click", () => chrome.runtime.sendMessage({ type: "discard" }, refresh));
|
|
|
|
$("finish").addEventListener("click", () => {
|
|
chrome.runtime.sendMessage({ type: "stop" }, async ({ steps, mode }) => {
|
|
const base = $("base").value.trim().replace(/\/$/, "");
|
|
const key = $("key").value.trim();
|
|
if (!base || !key) { $("status").textContent = "Set platform URL and API key first."; return; }
|
|
if (!steps.length) { $("status").textContent = "No steps captured."; return; }
|
|
try {
|
|
$("status").textContent = "Uploading…";
|
|
const auth = { Authorization: `Bearer ${key}` };
|
|
const demo = await (await fetch(`${base}/api/demos`, {
|
|
method: "POST",
|
|
headers: { ...auth, "Content-Type": "application/json" },
|
|
body: JSON.stringify({ name: `Capture ${new Date().toLocaleString()}` }),
|
|
})).json();
|
|
for (const s of steps) {
|
|
let step;
|
|
if (mode === "dom") {
|
|
step = await (await fetch(`${base}/api/demos/${demo.id}/steps`, {
|
|
method: "POST",
|
|
headers: { ...auth, "Content-Type": "application/json" },
|
|
body: JSON.stringify({ snapshot_type: "dom", dom_html: s.html }),
|
|
})).json();
|
|
} else {
|
|
const blob = await (await fetch(s.dataUrl)).blob();
|
|
const fd = new FormData();
|
|
fd.append("file", blob, "step.png");
|
|
const asset = await (await fetch(`${base}/api/assets`, { method: "POST", headers: auth, body: fd })).json();
|
|
step = await (await fetch(`${base}/api/demos/${demo.id}/steps`, {
|
|
method: "POST",
|
|
headers: { ...auth, "Content-Type": "application/json" },
|
|
body: JSON.stringify({ snapshot_type: "image", asset_id: asset.id }),
|
|
})).json();
|
|
}
|
|
// pre-place a hotspot centred on the recorded click
|
|
await fetch(`${base}/api/steps/${step.id}`, {
|
|
method: "PATCH",
|
|
headers: { ...auth, "Content-Type": "application/json" },
|
|
body: JSON.stringify({ hotspot: { x: Math.max(0, s.xPct - 4), y: Math.max(0, s.yPct - 4), w: 8, h: 8 } }),
|
|
});
|
|
}
|
|
chrome.runtime.sendMessage({ type: "discard" }, () => {});
|
|
$("status").textContent = `Done — demo "${demo.name}" created with ${steps.length} steps. Open the editor to refine.`;
|
|
refresh();
|
|
} catch (e) {
|
|
$("status").textContent = `Upload failed: ${e}`;
|
|
}
|
|
});
|
|
});
|