@ulearning/ai-generative-frame 0.0.0-stage → 1.0.0-next.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/LICENSE +21 -0
- package/README.md +233 -2
- package/dist/assistant-ui/entry.d.ts +34 -0
- package/dist/assistant-ui/entry.d.ts.map +1 -0
- package/dist/assistant-ui/entry.js +50 -0
- package/dist/assistant-ui/entry.js.map +1 -0
- package/dist/assistant-ui/extension.d.ts +18 -0
- package/dist/assistant-ui/extension.d.ts.map +1 -0
- package/dist/assistant-ui/extension.js +0 -0
- package/dist/assistant-ui/history.d.ts +15 -0
- package/dist/assistant-ui/history.d.ts.map +1 -0
- package/dist/assistant-ui/history.js +39 -0
- package/dist/assistant-ui/history.js.map +1 -0
- package/dist/assistant-ui/modelContext.d.ts +45 -0
- package/dist/assistant-ui/modelContext.d.ts.map +1 -0
- package/dist/assistant-ui/modelContext.js +88 -0
- package/dist/assistant-ui/modelContext.js.map +1 -0
- package/dist/assistant-ui/snapshotSaver.d.ts +11 -0
- package/dist/assistant-ui/snapshotSaver.d.ts.map +1 -0
- package/dist/assistant-ui/snapshotSaver.js +83 -0
- package/dist/assistant-ui/snapshotSaver.js.map +1 -0
- package/dist/assistant-ui/tool-calls.d.ts +21 -0
- package/dist/assistant-ui/tool-calls.d.ts.map +1 -0
- package/dist/assistant-ui/tool-calls.js +65 -0
- package/dist/assistant-ui/tool-calls.js.map +1 -0
- package/dist/assistant-ui/toolkit.d.ts +96 -0
- package/dist/assistant-ui/toolkit.d.ts.map +1 -0
- package/dist/assistant-ui/toolkit.js +456 -0
- package/dist/assistant-ui/toolkit.js.map +1 -0
- package/dist/assistant-ui/useSharedContext.d.ts +14 -0
- package/dist/assistant-ui/useSharedContext.d.ts.map +1 -0
- package/dist/assistant-ui/useSharedContext.js +27 -0
- package/dist/assistant-ui/useSharedContext.js.map +1 -0
- package/dist/assistant-ui.d.ts +3 -0
- package/dist/assistant-ui.d.ts.map +1 -0
- package/dist/assistant-ui.js +1 -0
- package/dist/bootstrap.d.ts +23 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +78 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/csp.d.ts +25 -0
- package/dist/csp.d.ts.map +1 -0
- package/dist/csp.js +78 -0
- package/dist/csp.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/json-schema.d.ts +70 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/json-schema.js +163 -0
- package/dist/json-schema.js.map +1 -0
- package/dist/preview.d.ts +30 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +60 -0
- package/dist/preview.js.map +1 -0
- package/dist/prompts/guidance.d.ts +41 -0
- package/dist/prompts/guidance.d.ts.map +1 -0
- package/dist/prompts/guidance.js +268 -0
- package/dist/prompts/guidance.js.map +1 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +1 -0
- package/dist/protocol.d.ts +114 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +49 -0
- package/dist/protocol.js.map +1 -0
- package/dist/repair/repair.d.ts +72 -0
- package/dist/repair/repair.d.ts.map +1 -0
- package/dist/repair/repair.js +103 -0
- package/dist/repair/repair.js.map +1 -0
- package/dist/repair.d.ts +2 -0
- package/dist/repair.d.ts.map +1 -0
- package/dist/repair.js +1 -0
- package/dist/rpc.d.ts +60 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +127 -0
- package/dist/rpc.js.map +1 -0
- package/dist/runtime/diagnostics.d.ts +17 -0
- package/dist/runtime/diagnostics.d.ts.map +1 -0
- package/dist/runtime/diagnostics.js +128 -0
- package/dist/runtime/diagnostics.js.map +1 -0
- package/dist/runtime/generated.d.ts +2 -0
- package/dist/runtime/generated.d.ts.map +1 -0
- package/dist/runtime/generated.js +5 -0
- package/dist/runtime/generated.js.map +1 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +264 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/morph.d.ts +51 -0
- package/dist/runtime/morph.d.ts.map +1 -0
- package/dist/runtime/morph.js +251 -0
- package/dist/runtime/morph.js.map +1 -0
- package/dist/runtime/screenshot.d.ts +23 -0
- package/dist/runtime/screenshot.d.ts.map +1 -0
- package/dist/runtime/screenshot.js +188 -0
- package/dist/runtime/screenshot.js.map +1 -0
- package/dist/runtime/size.d.ts +10 -0
- package/dist/runtime/size.d.ts.map +1 -0
- package/dist/runtime/size.js +21 -0
- package/dist/runtime/size.js.map +1 -0
- package/dist/runtime/storage.d.ts +4 -0
- package/dist/runtime/storage.d.ts.map +1 -0
- package/dist/runtime/storage.js +38 -0
- package/dist/runtime/storage.js.map +1 -0
- package/dist/spec/actions.d.ts +40 -0
- package/dist/spec/actions.d.ts.map +1 -0
- package/dist/spec/actions.js +53 -0
- package/dist/spec/actions.js.map +1 -0
- package/dist/spec/assistant-ui/thread.d.ts +66 -0
- package/dist/spec/assistant-ui/thread.d.ts.map +1 -0
- package/dist/spec/assistant-ui/thread.js +252 -0
- package/dist/spec/assistant-ui/thread.js.map +1 -0
- package/dist/spec/assistant-ui/toolkit.d.ts +33 -0
- package/dist/spec/assistant-ui/toolkit.d.ts.map +1 -0
- package/dist/spec/assistant-ui/toolkit.js +142 -0
- package/dist/spec/assistant-ui/toolkit.js.map +1 -0
- package/dist/spec/assistant-ui/web.d.ts +6 -0
- package/dist/spec/assistant-ui/web.d.ts.map +1 -0
- package/dist/spec/assistant-ui/web.js +48 -0
- package/dist/spec/assistant-ui/web.js.map +1 -0
- package/dist/spec/assistant-ui.d.ts +3 -0
- package/dist/spec/assistant-ui.d.ts.map +1 -0
- package/dist/spec/assistant-ui.js +1 -0
- package/dist/spec/calc.d.ts +8 -0
- package/dist/spec/calc.d.ts.map +1 -0
- package/dist/spec/calc.js +498 -0
- package/dist/spec/calc.js.map +1 -0
- package/dist/spec/catalog.d.ts +52 -0
- package/dist/spec/catalog.d.ts.map +1 -0
- package/dist/spec/catalog.js +104 -0
- package/dist/spec/catalog.js.map +1 -0
- package/dist/spec/expressions.d.ts +45 -0
- package/dist/spec/expressions.d.ts.map +1 -0
- package/dist/spec/expressions.js +199 -0
- package/dist/spec/expressions.js.map +1 -0
- package/dist/spec/patch.d.ts +20 -0
- package/dist/spec/patch.d.ts.map +1 -0
- package/dist/spec/patch.js +77 -0
- package/dist/spec/patch.js.map +1 -0
- package/dist/spec/pending.d.ts +15 -0
- package/dist/spec/pending.d.ts.map +1 -0
- package/dist/spec/pending.js +33 -0
- package/dist/spec/pending.js.map +1 -0
- package/dist/spec/pointer.d.ts +17 -0
- package/dist/spec/pointer.d.ts.map +1 -0
- package/dist/spec/pointer.js +88 -0
- package/dist/spec/pointer.js.map +1 -0
- package/dist/spec/prompt.d.ts +29 -0
- package/dist/spec/prompt.d.ts.map +1 -0
- package/dist/spec/prompt.js +145 -0
- package/dist/spec/prompt.js.map +1 -0
- package/dist/spec/render-spec.d.ts +50 -0
- package/dist/spec/render-spec.d.ts.map +1 -0
- package/dist/spec/render-spec.js +104 -0
- package/dist/spec/render-spec.js.map +1 -0
- package/dist/spec/resolve.d.ts +39 -0
- package/dist/spec/resolve.d.ts.map +1 -0
- package/dist/spec/resolve.js +51 -0
- package/dist/spec/resolve.js.map +1 -0
- package/dist/spec/resolved.d.ts +3 -0
- package/dist/spec/resolved.d.ts.map +1 -0
- package/dist/spec/resolved.js +36 -0
- package/dist/spec/resolved.js.map +1 -0
- package/dist/spec/state.d.ts +34 -0
- package/dist/spec/state.d.ts.map +1 -0
- package/dist/spec/state.js +111 -0
- package/dist/spec/state.js.map +1 -0
- package/dist/spec/stream.d.ts +50 -0
- package/dist/spec/stream.d.ts.map +1 -0
- package/dist/spec/stream.js +168 -0
- package/dist/spec/stream.js.map +1 -0
- package/dist/spec/tools.d.ts +3 -0
- package/dist/spec/tools.d.ts.map +1 -0
- package/dist/spec/tools.js +3 -0
- package/dist/spec/tree.d.ts +21 -0
- package/dist/spec/tree.d.ts.map +1 -0
- package/dist/spec/tree.js +27 -0
- package/dist/spec/tree.js.map +1 -0
- package/dist/spec/types.d.ts +66 -0
- package/dist/spec/types.d.ts.map +1 -0
- package/dist/spec/types.js +9 -0
- package/dist/spec/types.js.map +1 -0
- package/dist/spec/validate.d.ts +32 -0
- package/dist/spec/validate.d.ts.map +1 -0
- package/dist/spec/validate.js +264 -0
- package/dist/spec/validate.js.map +1 -0
- package/dist/spec/vue/SpecRenderer.d.ts +43 -0
- package/dist/spec/vue/SpecRenderer.d.ts.map +1 -0
- package/dist/spec/vue/SpecRenderer.js +357 -0
- package/dist/spec/vue/SpecRenderer.js.map +1 -0
- package/dist/spec/vue/useSpecStream.d.ts +19 -0
- package/dist/spec/vue/useSpecStream.d.ts.map +1 -0
- package/dist/spec/vue/useSpecStream.js +115 -0
- package/dist/spec/vue/useSpecStream.js.map +1 -0
- package/dist/spec/vue.d.ts +3 -0
- package/dist/spec/vue.d.ts.map +1 -0
- package/dist/spec/vue.js +3 -0
- package/dist/spec.d.ts +12 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/spec.js +8 -0
- package/dist/theme.d.ts +38 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +370 -0
- package/dist/theme.js.map +1 -0
- package/dist/tools/define.d.ts +43 -0
- package/dist/tools/define.d.ts.map +1 -0
- package/dist/tools/define.js +31 -0
- package/dist/tools/define.js.map +1 -0
- package/dist/tools/edits.d.ts +19 -0
- package/dist/tools/edits.d.ts.map +1 -0
- package/dist/tools/edits.js +55 -0
- package/dist/tools/edits.js.map +1 -0
- package/dist/tools/registry.d.ts +19 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +33 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/tools.d.ts +88 -0
- package/dist/tools/tools.d.ts.map +1 -0
- package/dist/tools/tools.js +235 -0
- package/dist/tools/tools.js.map +1 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +5 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/dist/vue/Widget.d.ts +16 -0
- package/dist/vue/Widget.d.ts.map +1 -0
- package/dist/vue/Widget.js +58 -0
- package/dist/vue/Widget.js.map +1 -0
- package/dist/vue/sync.d.ts +19 -0
- package/dist/vue/sync.d.ts.map +1 -0
- package/dist/vue/sync.js +31 -0
- package/dist/vue/sync.js.map +1 -0
- package/dist/vue/themeStore.d.ts +23 -0
- package/dist/vue/themeStore.d.ts.map +1 -0
- package/dist/vue/themeStore.js +115 -0
- package/dist/vue/themeStore.js.map +1 -0
- package/dist/vue/useThemeTokens.d.ts +6 -0
- package/dist/vue/useThemeTokens.d.ts.map +1 -0
- package/dist/vue/useThemeTokens.js +49 -0
- package/dist/vue/useThemeTokens.js.map +1 -0
- package/dist/vue/useWidget.d.ts +10 -0
- package/dist/vue/useWidget.d.ts.map +1 -0
- package/dist/vue/useWidget.js +81 -0
- package/dist/vue/useWidget.js.map +1 -0
- package/dist/vue.d.ts +5 -0
- package/dist/vue.d.ts.map +1 -0
- package/dist/vue.js +4 -0
- package/dist/widget.d.ts +129 -0
- package/dist/widget.d.ts.map +1 -0
- package/dist/widget.js +655 -0
- package/dist/widget.js.map +1 -0
- package/package.json +125 -3
- package/src/assistant-ui/entry.ts +124 -0
- package/src/assistant-ui/extension.ts +19 -0
- package/src/assistant-ui/history.ts +63 -0
- package/src/assistant-ui/modelContext.ts +162 -0
- package/src/assistant-ui/snapshotSaver.ts +116 -0
- package/src/assistant-ui/tool-calls.ts +115 -0
- package/src/assistant-ui/toolkit.tsx +747 -0
- package/src/assistant-ui/useSharedContext.ts +40 -0
- package/src/assistant-ui.ts +14 -0
- package/src/bootstrap.ts +112 -0
- package/src/csp.ts +96 -0
- package/src/index.ts +44 -0
- package/src/json-schema.ts +353 -0
- package/src/preview.ts +120 -0
- package/src/prompts/guidance.ts +369 -0
- package/src/prompts.ts +8 -0
- package/src/protocol.ts +141 -0
- package/src/repair/repair.ts +202 -0
- package/src/repair.ts +11 -0
- package/src/rpc.ts +228 -0
- package/src/runtime/diagnostics.ts +165 -0
- package/src/runtime/generated.ts +2 -0
- package/src/runtime/index.ts +343 -0
- package/src/runtime/morph.ts +352 -0
- package/src/runtime/screenshot.ts +288 -0
- package/src/runtime/size.ts +17 -0
- package/src/runtime/storage.ts +49 -0
- package/src/spec/actions.ts +111 -0
- package/src/spec/assistant-ui/thread.ts +381 -0
- package/src/spec/assistant-ui/toolkit.tsx +295 -0
- package/src/spec/assistant-ui/web.tsx +63 -0
- package/src/spec/assistant-ui.ts +2 -0
- package/src/spec/calc.ts +598 -0
- package/src/spec/catalog.ts +196 -0
- package/src/spec/expressions.ts +301 -0
- package/src/spec/patch.ts +112 -0
- package/src/spec/pending.ts +36 -0
- package/src/spec/pointer.ts +170 -0
- package/src/spec/prompt.ts +243 -0
- package/src/spec/render-spec.ts +144 -0
- package/src/spec/resolve.ts +123 -0
- package/src/spec/resolved.ts +53 -0
- package/src/spec/state.ts +193 -0
- package/src/spec/stream.ts +230 -0
- package/src/spec/tools.ts +17 -0
- package/src/spec/tree.ts +37 -0
- package/src/spec/types.ts +51 -0
- package/src/spec/validate.ts +429 -0
- package/src/spec/vue/SpecRenderer.tsx +544 -0
- package/src/spec/vue/__tests__/harness.ts +50 -0
- package/src/spec/vue/useSpecStream.ts +162 -0
- package/src/spec/vue.ts +13 -0
- package/src/spec.ts +55 -0
- package/src/theme.ts +423 -0
- package/src/tools/define.ts +65 -0
- package/src/tools/edits.ts +73 -0
- package/src/tools/registry.ts +44 -0
- package/src/tools/tools.ts +361 -0
- package/src/tools.ts +32 -0
- package/src/version.ts +4 -0
- package/src/vue/Widget.tsx +74 -0
- package/src/vue/sync.ts +36 -0
- package/src/vue/themeStore.ts +157 -0
- package/src/vue/useThemeTokens.ts +71 -0
- package/src/vue/useWidget.ts +117 -0
- package/src/vue.ts +8 -0
- package/src/widget.ts +1080 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import type { ScreenshotOptions } from "../protocol";
|
|
2
|
+
|
|
3
|
+
const XHTML = "http://www.w3.org/1999/xhtml";
|
|
4
|
+
const SVG = "http://www.w3.org/2000/svg";
|
|
5
|
+
const MAX_CANVAS_SIDE = 4096;
|
|
6
|
+
const SKIP = new Set(["script", "noscript", "template"]);
|
|
7
|
+
|
|
8
|
+
export type Screenshot = {
|
|
9
|
+
dataUrl: string;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const createDefaultStyles = (doc: Document) => {
|
|
15
|
+
const cache = new Map<string, Map<string, string>>();
|
|
16
|
+
let sandbox: HTMLElement | undefined;
|
|
17
|
+
let svgSandbox: SVGSVGElement | undefined;
|
|
18
|
+
|
|
19
|
+
let host: HTMLElement | undefined;
|
|
20
|
+
|
|
21
|
+
const ensureSandbox = () => {
|
|
22
|
+
if (sandbox) return;
|
|
23
|
+
host = doc.createElement("div");
|
|
24
|
+
host.setAttribute("aria-hidden", "true");
|
|
25
|
+
host.style.cssText =
|
|
26
|
+
"all:initial;position:absolute;left:-99999px;top:0;visibility:hidden;contain:strict;width:0;height:0;";
|
|
27
|
+
// Document rules such as `*{box-sizing:border-box}` do not cross a shadow
|
|
28
|
+
// boundary, so probes inside it report the defaults the serialized image
|
|
29
|
+
// will actually render with.
|
|
30
|
+
const shadow = host.attachShadow({ mode: "closed" });
|
|
31
|
+
sandbox = doc.createElement("div");
|
|
32
|
+
svgSandbox = doc.createElementNS(SVG, "svg");
|
|
33
|
+
sandbox.appendChild(svgSandbox);
|
|
34
|
+
shadow.appendChild(sandbox);
|
|
35
|
+
doc.body.appendChild(host);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
get(el: Element): Map<string, string> {
|
|
40
|
+
const key = `${el.namespaceURI}|${el.localName}`;
|
|
41
|
+
let styles = cache.get(key);
|
|
42
|
+
if (styles) return styles;
|
|
43
|
+
ensureSandbox();
|
|
44
|
+
const probe = doc.createElementNS(el.namespaceURI ?? XHTML, el.localName);
|
|
45
|
+
(el.namespaceURI === SVG ? svgSandbox! : sandbox!).appendChild(probe);
|
|
46
|
+
const computed = doc.defaultView!.getComputedStyle(probe);
|
|
47
|
+
styles = new Map();
|
|
48
|
+
for (let i = 0; i < computed.length; i++) {
|
|
49
|
+
const name = computed.item(i);
|
|
50
|
+
styles.set(name, computed.getPropertyValue(name));
|
|
51
|
+
}
|
|
52
|
+
probe.remove();
|
|
53
|
+
cache.set(key, styles);
|
|
54
|
+
return styles;
|
|
55
|
+
},
|
|
56
|
+
dispose() {
|
|
57
|
+
host?.remove();
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const toDataUrl = (
|
|
63
|
+
source: CanvasImageSource,
|
|
64
|
+
width: number,
|
|
65
|
+
height: number,
|
|
66
|
+
) => {
|
|
67
|
+
const canvas = document.createElement("canvas");
|
|
68
|
+
canvas.width = Math.max(1, width);
|
|
69
|
+
canvas.height = Math.max(1, height);
|
|
70
|
+
canvas.getContext("2d")!.drawImage(source, 0, 0, width, height);
|
|
71
|
+
return canvas.toDataURL("image/png");
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Clones `node` with every computed style that differs from both the
|
|
76
|
+
* element's default and its parent's value written inline, because a
|
|
77
|
+
* serialized SVG image cannot see the frame's stylesheets.
|
|
78
|
+
*/
|
|
79
|
+
const cloneWithStyles = (
|
|
80
|
+
node: Node,
|
|
81
|
+
defaults: ReturnType<typeof createDefaultStyles>,
|
|
82
|
+
parentStyle: CSSStyleDeclaration | undefined,
|
|
83
|
+
): Node | null => {
|
|
84
|
+
if (node.nodeType === Node.TEXT_NODE) return node.cloneNode();
|
|
85
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return null;
|
|
86
|
+
const el = node as Element;
|
|
87
|
+
if (SKIP.has(el.localName)) return null;
|
|
88
|
+
|
|
89
|
+
const view = el.ownerDocument.defaultView!;
|
|
90
|
+
const style = view.getComputedStyle(el);
|
|
91
|
+
let clone: Element;
|
|
92
|
+
|
|
93
|
+
if (el instanceof view.HTMLCanvasElement) {
|
|
94
|
+
clone = el.ownerDocument.createElement("img");
|
|
95
|
+
try {
|
|
96
|
+
clone.setAttribute("src", el.toDataURL("image/png"));
|
|
97
|
+
} catch {
|
|
98
|
+
// A tainted canvas cannot be read; it stays empty in the capture.
|
|
99
|
+
}
|
|
100
|
+
} else if (
|
|
101
|
+
el instanceof view.HTMLImageElement &&
|
|
102
|
+
el.complete &&
|
|
103
|
+
el.naturalWidth > 0 &&
|
|
104
|
+
!el.src.startsWith("data:")
|
|
105
|
+
) {
|
|
106
|
+
clone = el.cloneNode(false) as Element;
|
|
107
|
+
try {
|
|
108
|
+
clone.setAttribute(
|
|
109
|
+
"src",
|
|
110
|
+
toDataUrl(el, el.naturalWidth, el.naturalHeight),
|
|
111
|
+
);
|
|
112
|
+
} catch {
|
|
113
|
+
// Cross-origin images without CORS cannot be inlined.
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
clone = el.cloneNode(false) as Element;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (el instanceof view.HTMLInputElement) {
|
|
120
|
+
if (el.type === "checkbox" || el.type === "radio") {
|
|
121
|
+
if (el.checked) clone.setAttribute("checked", "");
|
|
122
|
+
else clone.removeAttribute("checked");
|
|
123
|
+
} else {
|
|
124
|
+
clone.setAttribute("value", el.value);
|
|
125
|
+
}
|
|
126
|
+
} else if (el instanceof view.HTMLTextAreaElement) {
|
|
127
|
+
clone.textContent = el.value;
|
|
128
|
+
} else if (el instanceof view.HTMLSelectElement) {
|
|
129
|
+
clone.setAttribute("data-gf-value", el.value);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const base = defaults.get(el);
|
|
133
|
+
const declarations: string[] = [];
|
|
134
|
+
for (let i = 0; i < style.length; i++) {
|
|
135
|
+
const name = style.item(i);
|
|
136
|
+
if (name.startsWith("--")) continue;
|
|
137
|
+
const value = style.getPropertyValue(name);
|
|
138
|
+
if (
|
|
139
|
+
value !== base.get(name) ||
|
|
140
|
+
(parentStyle && value !== parentStyle.getPropertyValue(name))
|
|
141
|
+
) {
|
|
142
|
+
declarations.push(`${name}:${value}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (style.animationName !== "none" || style.transitionProperty !== "none") {
|
|
146
|
+
declarations.push("animation:none", "transition:none");
|
|
147
|
+
}
|
|
148
|
+
clone.setAttribute("style", declarations.join(";"));
|
|
149
|
+
|
|
150
|
+
if (!(el instanceof view.HTMLTextAreaElement)) {
|
|
151
|
+
for (const child of Array.from(el.childNodes)) {
|
|
152
|
+
const childClone = cloneWithStyles(child, defaults, style);
|
|
153
|
+
if (childClone) clone.appendChild(childClone);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (el instanceof view.HTMLSelectElement) {
|
|
157
|
+
for (const option of Array.from(clone.querySelectorAll("option"))) {
|
|
158
|
+
if (
|
|
159
|
+
option.getAttribute("value") === el.value ||
|
|
160
|
+
option.textContent === el.value
|
|
161
|
+
) {
|
|
162
|
+
option.setAttribute("selected", "");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return clone;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/** Embeds `@font-face` rules that are already self-contained (`data:` sources). */
|
|
170
|
+
const collectInlineFontFaces = (doc: Document): string => {
|
|
171
|
+
const rules: string[] = [];
|
|
172
|
+
for (const sheet of Array.from(doc.styleSheets)) {
|
|
173
|
+
let cssRules: CSSRuleList;
|
|
174
|
+
try {
|
|
175
|
+
cssRules = sheet.cssRules;
|
|
176
|
+
} catch {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
for (const rule of Array.from(cssRules)) {
|
|
180
|
+
if (
|
|
181
|
+
rule.constructor.name === "CSSFontFaceRule" &&
|
|
182
|
+
!/url\((?!\s*["']?data:)/i.test(rule.cssText)
|
|
183
|
+
) {
|
|
184
|
+
rules.push(rule.cssText);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return rules.join("\n");
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The capture box: the clone is laid out at the root's own width, and the
|
|
193
|
+
* image grows to the full scroll size so content overflowing the root is
|
|
194
|
+
* not clipped.
|
|
195
|
+
*/
|
|
196
|
+
export const measureCapture = (root: HTMLElement) => {
|
|
197
|
+
const rect = root.getBoundingClientRect();
|
|
198
|
+
const layoutWidth = Math.max(1, Math.ceil(rect.width));
|
|
199
|
+
return {
|
|
200
|
+
layoutWidth,
|
|
201
|
+
width: Math.max(layoutWidth, Math.ceil(root.scrollWidth)),
|
|
202
|
+
height: Math.max(1, Math.ceil(Math.max(rect.height, root.scrollHeight))),
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const loadImage = (src: string) =>
|
|
207
|
+
new Promise<HTMLImageElement>((resolve, reject) => {
|
|
208
|
+
const img = new Image();
|
|
209
|
+
img.onload = () => resolve(img);
|
|
210
|
+
img.onerror = () =>
|
|
211
|
+
reject(new Error("The serialized widget failed to rasterize"));
|
|
212
|
+
img.src = src;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Captures `root` as a PNG data URL by serializing a styled clone into an SVG
|
|
217
|
+
* `foreignObject` and drawing it onto a canvas. Best effort: web fonts loaded
|
|
218
|
+
* by URL, cross-origin images, and pseudo-elements are not reproduced.
|
|
219
|
+
*/
|
|
220
|
+
export async function captureScreenshot(
|
|
221
|
+
root: HTMLElement,
|
|
222
|
+
options: ScreenshotOptions = {},
|
|
223
|
+
): Promise<Screenshot> {
|
|
224
|
+
const doc = root.ownerDocument;
|
|
225
|
+
const view = doc.defaultView!;
|
|
226
|
+
await doc.fonts?.ready;
|
|
227
|
+
|
|
228
|
+
const { layoutWidth, width, height } = measureCapture(root);
|
|
229
|
+
|
|
230
|
+
const defaults = createDefaultStyles(doc);
|
|
231
|
+
let clone: Node | null;
|
|
232
|
+
try {
|
|
233
|
+
clone = cloneWithStyles(root, defaults, view.getComputedStyle(doc.body));
|
|
234
|
+
} finally {
|
|
235
|
+
defaults.dispose();
|
|
236
|
+
}
|
|
237
|
+
const wrapper = doc.createElementNS(XHTML, "div");
|
|
238
|
+
wrapper.setAttribute(
|
|
239
|
+
"style",
|
|
240
|
+
`width:${width}px;height:${height}px;overflow:hidden;`,
|
|
241
|
+
);
|
|
242
|
+
if (clone) wrapper.appendChild(clone);
|
|
243
|
+
if (clone instanceof Element) {
|
|
244
|
+
clone.setAttribute(
|
|
245
|
+
"style",
|
|
246
|
+
`${clone.getAttribute("style") ?? ""};width:${layoutWidth}px;margin:0;`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const fonts = collectInlineFontFaces(doc);
|
|
251
|
+
const markup = new XMLSerializer().serializeToString(wrapper);
|
|
252
|
+
const svg =
|
|
253
|
+
`<svg xmlns="${SVG}" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">` +
|
|
254
|
+
`<foreignObject x="0" y="0" width="100%" height="100%">` +
|
|
255
|
+
(fonts
|
|
256
|
+
? `<style xmlns="${XHTML}">${fonts.replace(/</g, "\\3c ")}</style>`
|
|
257
|
+
: "") +
|
|
258
|
+
markup +
|
|
259
|
+
`</foreignObject></svg>`;
|
|
260
|
+
|
|
261
|
+
const image = await loadImage(
|
|
262
|
+
`data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`,
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
const requested = options.scale ?? Math.min(view.devicePixelRatio || 1, 2);
|
|
266
|
+
const scale = Math.max(
|
|
267
|
+
0.1,
|
|
268
|
+
Math.min(requested, MAX_CANVAS_SIDE / width, MAX_CANVAS_SIDE / height),
|
|
269
|
+
);
|
|
270
|
+
const canvas = doc.createElement("canvas");
|
|
271
|
+
canvas.width = Math.round(width * scale);
|
|
272
|
+
canvas.height = Math.round(height * scale);
|
|
273
|
+
const context = canvas.getContext("2d");
|
|
274
|
+
if (!context) throw new Error("Canvas 2D context is unavailable");
|
|
275
|
+
const background =
|
|
276
|
+
options.background ??
|
|
277
|
+
view
|
|
278
|
+
.getComputedStyle(doc.documentElement)
|
|
279
|
+
.getPropertyValue("--color-background")
|
|
280
|
+
.trim();
|
|
281
|
+
if (background) {
|
|
282
|
+
context.fillStyle = background;
|
|
283
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
284
|
+
}
|
|
285
|
+
context.scale(scale, scale);
|
|
286
|
+
context.drawImage(image, 0, 0, width, height);
|
|
287
|
+
return { dataUrl: canvas.toDataURL("image/png"), width, height };
|
|
288
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { WidgetSize } from "../protocol";
|
|
2
|
+
|
|
3
|
+
export function measureRoot(root: Element): WidgetSize {
|
|
4
|
+
const rect = root.getBoundingClientRect();
|
|
5
|
+
return { width: Math.ceil(rect.width), height: Math.ceil(rect.height) };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The size to report to the host, or `undefined` while the root has no
|
|
10
|
+
* layout width. A frame laid out at zero width (a collapsed or hidden
|
|
11
|
+
* container, or Chrome's full-page capture) wraps its content one word per
|
|
12
|
+
* line, and its height would size the iframe far past its content.
|
|
13
|
+
*/
|
|
14
|
+
export function reportableSize(root: Element): WidgetSize | undefined {
|
|
15
|
+
const size = measureRoot(root);
|
|
16
|
+
return size.width > 0 ? size : undefined;
|
|
17
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ClearStorageResult } from "../protocol";
|
|
2
|
+
|
|
3
|
+
const EXPIRED = "expires=Thu, 01 Jan 1970 00:00:00 GMT";
|
|
4
|
+
|
|
5
|
+
/** Clears everything this frame's origin stores in the browser. */
|
|
6
|
+
export async function clearFrameStorage(
|
|
7
|
+
win: Window,
|
|
8
|
+
): Promise<ClearStorageResult> {
|
|
9
|
+
const cleared: string[] = [];
|
|
10
|
+
const attempt = async (kind: string, clear: () => unknown) => {
|
|
11
|
+
try {
|
|
12
|
+
if ((await clear()) !== false) cleared.push(kind);
|
|
13
|
+
} catch {}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
await attempt("localStorage", () => win.localStorage.clear());
|
|
17
|
+
await attempt("sessionStorage", () => win.sessionStorage.clear());
|
|
18
|
+
await attempt("indexedDB", async () => {
|
|
19
|
+
const idb = win.indexedDB;
|
|
20
|
+
if (typeof idb?.databases !== "function") return false;
|
|
21
|
+
const databases = await idb.databases();
|
|
22
|
+
await Promise.all(
|
|
23
|
+
databases.map(
|
|
24
|
+
({ name }) =>
|
|
25
|
+
name &&
|
|
26
|
+
new Promise<void>((resolve) => {
|
|
27
|
+
const request = idb.deleteDatabase(name);
|
|
28
|
+
request.onsuccess =
|
|
29
|
+
request.onerror =
|
|
30
|
+
request.onblocked =
|
|
31
|
+
() => resolve();
|
|
32
|
+
}),
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
return true;
|
|
36
|
+
});
|
|
37
|
+
await attempt("cacheStorage", async () => {
|
|
38
|
+
if (!win.caches) return false;
|
|
39
|
+
for (const key of await win.caches.keys()) await win.caches.delete(key);
|
|
40
|
+
return true;
|
|
41
|
+
});
|
|
42
|
+
await attempt("cookies", () => {
|
|
43
|
+
for (const part of win.document.cookie.split(";")) {
|
|
44
|
+
const name = part.split("=")[0]?.trim();
|
|
45
|
+
if (name) win.document.cookie = `${name}=; ${EXPIRED}; path=/`;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return { cleared };
|
|
49
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Catalog } from "./catalog";
|
|
2
|
+
import { resolveValue, type ExpressionContext } from "./expressions";
|
|
3
|
+
import type { SpecStateStore } from "./state";
|
|
4
|
+
import type { ActionBinding } from "./types";
|
|
5
|
+
|
|
6
|
+
/** Where an action was dispatched from. */
|
|
7
|
+
export type ActionSource = {
|
|
8
|
+
elementId?: string;
|
|
9
|
+
/** The event name, or the watched path for `watch`. */
|
|
10
|
+
trigger?: string;
|
|
11
|
+
/** Whether a component's event or a `watch` binding dispatched the action. */
|
|
12
|
+
kind?: "event" | "watch";
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ActionHandler = (
|
|
16
|
+
params: Record<string, unknown>,
|
|
17
|
+
context: ActionSource & { state: SpecStateStore },
|
|
18
|
+
) => unknown;
|
|
19
|
+
|
|
20
|
+
export type ActionDispatcherOptions = {
|
|
21
|
+
store: SpecStateStore;
|
|
22
|
+
catalog?: Catalog;
|
|
23
|
+
/** Handlers by action name; they take precedence over `onAction`. */
|
|
24
|
+
handlers?: Record<string, ActionHandler>;
|
|
25
|
+
/** Called for catalog actions without a handler. */
|
|
26
|
+
onAction?: (
|
|
27
|
+
name: string,
|
|
28
|
+
params: Record<string, unknown>,
|
|
29
|
+
context: ActionSource & { state: SpecStateStore },
|
|
30
|
+
) => unknown;
|
|
31
|
+
/** Reports actions that fail or are unknown instead of throwing. */
|
|
32
|
+
onError?: (error: Error, binding: ActionBinding) => void;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type ActionDispatcher = (
|
|
36
|
+
bindings: ActionBinding | readonly ActionBinding[] | undefined,
|
|
37
|
+
context: Omit<ExpressionContext, "state" | "event"> &
|
|
38
|
+
ActionSource & {
|
|
39
|
+
/** The payload the event carried, read by `{ $event }`. */
|
|
40
|
+
payload?: unknown;
|
|
41
|
+
},
|
|
42
|
+
) => Promise<unknown[]>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates the function elements use to run their bound actions: params are
|
|
46
|
+
* resolved against current state (and the event payload), `setState` writes
|
|
47
|
+
* the store, and every other action goes to a host handler. Actions in a list
|
|
48
|
+
* run in order, see state written by the ones before them, and stop at the
|
|
49
|
+
* first one that fails.
|
|
50
|
+
*/
|
|
51
|
+
export function createActionDispatcher(
|
|
52
|
+
options: ActionDispatcherOptions,
|
|
53
|
+
): ActionDispatcher {
|
|
54
|
+
const report = (error: unknown, binding: ActionBinding) => {
|
|
55
|
+
const normalized =
|
|
56
|
+
error instanceof Error ? error : new Error(String(error));
|
|
57
|
+
if (options.onError) options.onError(normalized, binding);
|
|
58
|
+
else
|
|
59
|
+
console.warn(
|
|
60
|
+
`[generative-frame] action "${binding.action}" failed:`,
|
|
61
|
+
normalized,
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return async (bindings, context) => {
|
|
66
|
+
const list =
|
|
67
|
+
bindings === undefined
|
|
68
|
+
? []
|
|
69
|
+
: Array.isArray(bindings)
|
|
70
|
+
? bindings
|
|
71
|
+
: [bindings];
|
|
72
|
+
const results: unknown[] = [];
|
|
73
|
+
for (const binding of list as ActionBinding[]) {
|
|
74
|
+
const { payload, elementId, trigger, kind, ...scope } = context;
|
|
75
|
+
const params = (resolveValue(binding.params ?? {}, {
|
|
76
|
+
...scope,
|
|
77
|
+
state: options.store.getState(),
|
|
78
|
+
event: payload,
|
|
79
|
+
}) ?? {}) as Record<string, unknown>;
|
|
80
|
+
const source = {
|
|
81
|
+
...(elementId !== undefined ? { elementId } : {}),
|
|
82
|
+
...(trigger !== undefined ? { trigger } : {}),
|
|
83
|
+
...(kind !== undefined ? { kind } : {}),
|
|
84
|
+
state: options.store,
|
|
85
|
+
};
|
|
86
|
+
try {
|
|
87
|
+
const handler = options.handlers?.[binding.action];
|
|
88
|
+
if (handler) {
|
|
89
|
+
results.push(await handler(params, source));
|
|
90
|
+
} else if (binding.action === "setState") {
|
|
91
|
+
if (typeof params["path"] !== "string") {
|
|
92
|
+
throw new Error("setState needs a string `path`");
|
|
93
|
+
}
|
|
94
|
+
options.store.set(params["path"], params["value"]);
|
|
95
|
+
results.push(undefined);
|
|
96
|
+
} else if (options.catalog && !options.catalog.action(binding.action)) {
|
|
97
|
+
throw new Error(`Unknown action "${binding.action}"`);
|
|
98
|
+
} else if (options.onAction) {
|
|
99
|
+
results.push(await options.onAction(binding.action, params, source));
|
|
100
|
+
} else {
|
|
101
|
+
throw new Error(`No handler for action "${binding.action}"`);
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
report(error, binding);
|
|
105
|
+
results.push(undefined);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return results;
|
|
110
|
+
};
|
|
111
|
+
}
|