@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.
Files changed (323) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +233 -2
  3. package/dist/assistant-ui/entry.d.ts +34 -0
  4. package/dist/assistant-ui/entry.d.ts.map +1 -0
  5. package/dist/assistant-ui/entry.js +50 -0
  6. package/dist/assistant-ui/entry.js.map +1 -0
  7. package/dist/assistant-ui/extension.d.ts +18 -0
  8. package/dist/assistant-ui/extension.d.ts.map +1 -0
  9. package/dist/assistant-ui/extension.js +0 -0
  10. package/dist/assistant-ui/history.d.ts +15 -0
  11. package/dist/assistant-ui/history.d.ts.map +1 -0
  12. package/dist/assistant-ui/history.js +39 -0
  13. package/dist/assistant-ui/history.js.map +1 -0
  14. package/dist/assistant-ui/modelContext.d.ts +45 -0
  15. package/dist/assistant-ui/modelContext.d.ts.map +1 -0
  16. package/dist/assistant-ui/modelContext.js +88 -0
  17. package/dist/assistant-ui/modelContext.js.map +1 -0
  18. package/dist/assistant-ui/snapshotSaver.d.ts +11 -0
  19. package/dist/assistant-ui/snapshotSaver.d.ts.map +1 -0
  20. package/dist/assistant-ui/snapshotSaver.js +83 -0
  21. package/dist/assistant-ui/snapshotSaver.js.map +1 -0
  22. package/dist/assistant-ui/tool-calls.d.ts +21 -0
  23. package/dist/assistant-ui/tool-calls.d.ts.map +1 -0
  24. package/dist/assistant-ui/tool-calls.js +65 -0
  25. package/dist/assistant-ui/tool-calls.js.map +1 -0
  26. package/dist/assistant-ui/toolkit.d.ts +96 -0
  27. package/dist/assistant-ui/toolkit.d.ts.map +1 -0
  28. package/dist/assistant-ui/toolkit.js +456 -0
  29. package/dist/assistant-ui/toolkit.js.map +1 -0
  30. package/dist/assistant-ui/useSharedContext.d.ts +14 -0
  31. package/dist/assistant-ui/useSharedContext.d.ts.map +1 -0
  32. package/dist/assistant-ui/useSharedContext.js +27 -0
  33. package/dist/assistant-ui/useSharedContext.js.map +1 -0
  34. package/dist/assistant-ui.d.ts +3 -0
  35. package/dist/assistant-ui.d.ts.map +1 -0
  36. package/dist/assistant-ui.js +1 -0
  37. package/dist/bootstrap.d.ts +23 -0
  38. package/dist/bootstrap.d.ts.map +1 -0
  39. package/dist/bootstrap.js +78 -0
  40. package/dist/bootstrap.js.map +1 -0
  41. package/dist/csp.d.ts +25 -0
  42. package/dist/csp.d.ts.map +1 -0
  43. package/dist/csp.js +78 -0
  44. package/dist/csp.js.map +1 -0
  45. package/dist/index.d.ts +6 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +5 -0
  48. package/dist/json-schema.d.ts +70 -0
  49. package/dist/json-schema.d.ts.map +1 -0
  50. package/dist/json-schema.js +163 -0
  51. package/dist/json-schema.js.map +1 -0
  52. package/dist/preview.d.ts +30 -0
  53. package/dist/preview.d.ts.map +1 -0
  54. package/dist/preview.js +60 -0
  55. package/dist/preview.js.map +1 -0
  56. package/dist/prompts/guidance.d.ts +41 -0
  57. package/dist/prompts/guidance.d.ts.map +1 -0
  58. package/dist/prompts/guidance.js +268 -0
  59. package/dist/prompts/guidance.js.map +1 -0
  60. package/dist/prompts.d.ts +2 -0
  61. package/dist/prompts.d.ts.map +1 -0
  62. package/dist/prompts.js +1 -0
  63. package/dist/protocol.d.ts +114 -0
  64. package/dist/protocol.d.ts.map +1 -0
  65. package/dist/protocol.js +49 -0
  66. package/dist/protocol.js.map +1 -0
  67. package/dist/repair/repair.d.ts +72 -0
  68. package/dist/repair/repair.d.ts.map +1 -0
  69. package/dist/repair/repair.js +103 -0
  70. package/dist/repair/repair.js.map +1 -0
  71. package/dist/repair.d.ts +2 -0
  72. package/dist/repair.d.ts.map +1 -0
  73. package/dist/repair.js +1 -0
  74. package/dist/rpc.d.ts +60 -0
  75. package/dist/rpc.d.ts.map +1 -0
  76. package/dist/rpc.js +127 -0
  77. package/dist/rpc.js.map +1 -0
  78. package/dist/runtime/diagnostics.d.ts +17 -0
  79. package/dist/runtime/diagnostics.d.ts.map +1 -0
  80. package/dist/runtime/diagnostics.js +128 -0
  81. package/dist/runtime/diagnostics.js.map +1 -0
  82. package/dist/runtime/generated.d.ts +2 -0
  83. package/dist/runtime/generated.d.ts.map +1 -0
  84. package/dist/runtime/generated.js +5 -0
  85. package/dist/runtime/generated.js.map +1 -0
  86. package/dist/runtime/index.d.ts +7 -0
  87. package/dist/runtime/index.d.ts.map +1 -0
  88. package/dist/runtime/index.js +264 -0
  89. package/dist/runtime/index.js.map +1 -0
  90. package/dist/runtime/morph.d.ts +51 -0
  91. package/dist/runtime/morph.d.ts.map +1 -0
  92. package/dist/runtime/morph.js +251 -0
  93. package/dist/runtime/morph.js.map +1 -0
  94. package/dist/runtime/screenshot.d.ts +23 -0
  95. package/dist/runtime/screenshot.d.ts.map +1 -0
  96. package/dist/runtime/screenshot.js +188 -0
  97. package/dist/runtime/screenshot.js.map +1 -0
  98. package/dist/runtime/size.d.ts +10 -0
  99. package/dist/runtime/size.d.ts.map +1 -0
  100. package/dist/runtime/size.js +21 -0
  101. package/dist/runtime/size.js.map +1 -0
  102. package/dist/runtime/storage.d.ts +4 -0
  103. package/dist/runtime/storage.d.ts.map +1 -0
  104. package/dist/runtime/storage.js +38 -0
  105. package/dist/runtime/storage.js.map +1 -0
  106. package/dist/spec/actions.d.ts +40 -0
  107. package/dist/spec/actions.d.ts.map +1 -0
  108. package/dist/spec/actions.js +53 -0
  109. package/dist/spec/actions.js.map +1 -0
  110. package/dist/spec/assistant-ui/thread.d.ts +66 -0
  111. package/dist/spec/assistant-ui/thread.d.ts.map +1 -0
  112. package/dist/spec/assistant-ui/thread.js +252 -0
  113. package/dist/spec/assistant-ui/thread.js.map +1 -0
  114. package/dist/spec/assistant-ui/toolkit.d.ts +33 -0
  115. package/dist/spec/assistant-ui/toolkit.d.ts.map +1 -0
  116. package/dist/spec/assistant-ui/toolkit.js +142 -0
  117. package/dist/spec/assistant-ui/toolkit.js.map +1 -0
  118. package/dist/spec/assistant-ui/web.d.ts +6 -0
  119. package/dist/spec/assistant-ui/web.d.ts.map +1 -0
  120. package/dist/spec/assistant-ui/web.js +48 -0
  121. package/dist/spec/assistant-ui/web.js.map +1 -0
  122. package/dist/spec/assistant-ui.d.ts +3 -0
  123. package/dist/spec/assistant-ui.d.ts.map +1 -0
  124. package/dist/spec/assistant-ui.js +1 -0
  125. package/dist/spec/calc.d.ts +8 -0
  126. package/dist/spec/calc.d.ts.map +1 -0
  127. package/dist/spec/calc.js +498 -0
  128. package/dist/spec/calc.js.map +1 -0
  129. package/dist/spec/catalog.d.ts +52 -0
  130. package/dist/spec/catalog.d.ts.map +1 -0
  131. package/dist/spec/catalog.js +104 -0
  132. package/dist/spec/catalog.js.map +1 -0
  133. package/dist/spec/expressions.d.ts +45 -0
  134. package/dist/spec/expressions.d.ts.map +1 -0
  135. package/dist/spec/expressions.js +199 -0
  136. package/dist/spec/expressions.js.map +1 -0
  137. package/dist/spec/patch.d.ts +20 -0
  138. package/dist/spec/patch.d.ts.map +1 -0
  139. package/dist/spec/patch.js +77 -0
  140. package/dist/spec/patch.js.map +1 -0
  141. package/dist/spec/pending.d.ts +15 -0
  142. package/dist/spec/pending.d.ts.map +1 -0
  143. package/dist/spec/pending.js +33 -0
  144. package/dist/spec/pending.js.map +1 -0
  145. package/dist/spec/pointer.d.ts +17 -0
  146. package/dist/spec/pointer.d.ts.map +1 -0
  147. package/dist/spec/pointer.js +88 -0
  148. package/dist/spec/pointer.js.map +1 -0
  149. package/dist/spec/prompt.d.ts +29 -0
  150. package/dist/spec/prompt.d.ts.map +1 -0
  151. package/dist/spec/prompt.js +145 -0
  152. package/dist/spec/prompt.js.map +1 -0
  153. package/dist/spec/render-spec.d.ts +50 -0
  154. package/dist/spec/render-spec.d.ts.map +1 -0
  155. package/dist/spec/render-spec.js +104 -0
  156. package/dist/spec/render-spec.js.map +1 -0
  157. package/dist/spec/resolve.d.ts +39 -0
  158. package/dist/spec/resolve.d.ts.map +1 -0
  159. package/dist/spec/resolve.js +51 -0
  160. package/dist/spec/resolve.js.map +1 -0
  161. package/dist/spec/resolved.d.ts +3 -0
  162. package/dist/spec/resolved.d.ts.map +1 -0
  163. package/dist/spec/resolved.js +36 -0
  164. package/dist/spec/resolved.js.map +1 -0
  165. package/dist/spec/state.d.ts +34 -0
  166. package/dist/spec/state.d.ts.map +1 -0
  167. package/dist/spec/state.js +111 -0
  168. package/dist/spec/state.js.map +1 -0
  169. package/dist/spec/stream.d.ts +50 -0
  170. package/dist/spec/stream.d.ts.map +1 -0
  171. package/dist/spec/stream.js +168 -0
  172. package/dist/spec/stream.js.map +1 -0
  173. package/dist/spec/tools.d.ts +3 -0
  174. package/dist/spec/tools.d.ts.map +1 -0
  175. package/dist/spec/tools.js +3 -0
  176. package/dist/spec/tree.d.ts +21 -0
  177. package/dist/spec/tree.d.ts.map +1 -0
  178. package/dist/spec/tree.js +27 -0
  179. package/dist/spec/tree.js.map +1 -0
  180. package/dist/spec/types.d.ts +66 -0
  181. package/dist/spec/types.d.ts.map +1 -0
  182. package/dist/spec/types.js +9 -0
  183. package/dist/spec/types.js.map +1 -0
  184. package/dist/spec/validate.d.ts +32 -0
  185. package/dist/spec/validate.d.ts.map +1 -0
  186. package/dist/spec/validate.js +264 -0
  187. package/dist/spec/validate.js.map +1 -0
  188. package/dist/spec/vue/SpecRenderer.d.ts +43 -0
  189. package/dist/spec/vue/SpecRenderer.d.ts.map +1 -0
  190. package/dist/spec/vue/SpecRenderer.js +357 -0
  191. package/dist/spec/vue/SpecRenderer.js.map +1 -0
  192. package/dist/spec/vue/useSpecStream.d.ts +19 -0
  193. package/dist/spec/vue/useSpecStream.d.ts.map +1 -0
  194. package/dist/spec/vue/useSpecStream.js +115 -0
  195. package/dist/spec/vue/useSpecStream.js.map +1 -0
  196. package/dist/spec/vue.d.ts +3 -0
  197. package/dist/spec/vue.d.ts.map +1 -0
  198. package/dist/spec/vue.js +3 -0
  199. package/dist/spec.d.ts +12 -0
  200. package/dist/spec.d.ts.map +1 -0
  201. package/dist/spec.js +8 -0
  202. package/dist/theme.d.ts +38 -0
  203. package/dist/theme.d.ts.map +1 -0
  204. package/dist/theme.js +370 -0
  205. package/dist/theme.js.map +1 -0
  206. package/dist/tools/define.d.ts +43 -0
  207. package/dist/tools/define.d.ts.map +1 -0
  208. package/dist/tools/define.js +31 -0
  209. package/dist/tools/define.js.map +1 -0
  210. package/dist/tools/edits.d.ts +19 -0
  211. package/dist/tools/edits.d.ts.map +1 -0
  212. package/dist/tools/edits.js +55 -0
  213. package/dist/tools/edits.js.map +1 -0
  214. package/dist/tools/registry.d.ts +19 -0
  215. package/dist/tools/registry.d.ts.map +1 -0
  216. package/dist/tools/registry.js +33 -0
  217. package/dist/tools/registry.js.map +1 -0
  218. package/dist/tools/tools.d.ts +88 -0
  219. package/dist/tools/tools.d.ts.map +1 -0
  220. package/dist/tools/tools.js +235 -0
  221. package/dist/tools/tools.js.map +1 -0
  222. package/dist/tools.d.ts +5 -0
  223. package/dist/tools.d.ts.map +1 -0
  224. package/dist/tools.js +5 -0
  225. package/dist/version.d.ts +2 -0
  226. package/dist/version.d.ts.map +1 -0
  227. package/dist/version.js +5 -0
  228. package/dist/version.js.map +1 -0
  229. package/dist/vue/Widget.d.ts +16 -0
  230. package/dist/vue/Widget.d.ts.map +1 -0
  231. package/dist/vue/Widget.js +58 -0
  232. package/dist/vue/Widget.js.map +1 -0
  233. package/dist/vue/sync.d.ts +19 -0
  234. package/dist/vue/sync.d.ts.map +1 -0
  235. package/dist/vue/sync.js +31 -0
  236. package/dist/vue/sync.js.map +1 -0
  237. package/dist/vue/themeStore.d.ts +23 -0
  238. package/dist/vue/themeStore.d.ts.map +1 -0
  239. package/dist/vue/themeStore.js +115 -0
  240. package/dist/vue/themeStore.js.map +1 -0
  241. package/dist/vue/useThemeTokens.d.ts +6 -0
  242. package/dist/vue/useThemeTokens.d.ts.map +1 -0
  243. package/dist/vue/useThemeTokens.js +49 -0
  244. package/dist/vue/useThemeTokens.js.map +1 -0
  245. package/dist/vue/useWidget.d.ts +10 -0
  246. package/dist/vue/useWidget.d.ts.map +1 -0
  247. package/dist/vue/useWidget.js +81 -0
  248. package/dist/vue/useWidget.js.map +1 -0
  249. package/dist/vue.d.ts +5 -0
  250. package/dist/vue.d.ts.map +1 -0
  251. package/dist/vue.js +4 -0
  252. package/dist/widget.d.ts +129 -0
  253. package/dist/widget.d.ts.map +1 -0
  254. package/dist/widget.js +655 -0
  255. package/dist/widget.js.map +1 -0
  256. package/package.json +125 -3
  257. package/src/assistant-ui/entry.ts +124 -0
  258. package/src/assistant-ui/extension.ts +19 -0
  259. package/src/assistant-ui/history.ts +63 -0
  260. package/src/assistant-ui/modelContext.ts +162 -0
  261. package/src/assistant-ui/snapshotSaver.ts +116 -0
  262. package/src/assistant-ui/tool-calls.ts +115 -0
  263. package/src/assistant-ui/toolkit.tsx +747 -0
  264. package/src/assistant-ui/useSharedContext.ts +40 -0
  265. package/src/assistant-ui.ts +14 -0
  266. package/src/bootstrap.ts +112 -0
  267. package/src/csp.ts +96 -0
  268. package/src/index.ts +44 -0
  269. package/src/json-schema.ts +353 -0
  270. package/src/preview.ts +120 -0
  271. package/src/prompts/guidance.ts +369 -0
  272. package/src/prompts.ts +8 -0
  273. package/src/protocol.ts +141 -0
  274. package/src/repair/repair.ts +202 -0
  275. package/src/repair.ts +11 -0
  276. package/src/rpc.ts +228 -0
  277. package/src/runtime/diagnostics.ts +165 -0
  278. package/src/runtime/generated.ts +2 -0
  279. package/src/runtime/index.ts +343 -0
  280. package/src/runtime/morph.ts +352 -0
  281. package/src/runtime/screenshot.ts +288 -0
  282. package/src/runtime/size.ts +17 -0
  283. package/src/runtime/storage.ts +49 -0
  284. package/src/spec/actions.ts +111 -0
  285. package/src/spec/assistant-ui/thread.ts +381 -0
  286. package/src/spec/assistant-ui/toolkit.tsx +295 -0
  287. package/src/spec/assistant-ui/web.tsx +63 -0
  288. package/src/spec/assistant-ui.ts +2 -0
  289. package/src/spec/calc.ts +598 -0
  290. package/src/spec/catalog.ts +196 -0
  291. package/src/spec/expressions.ts +301 -0
  292. package/src/spec/patch.ts +112 -0
  293. package/src/spec/pending.ts +36 -0
  294. package/src/spec/pointer.ts +170 -0
  295. package/src/spec/prompt.ts +243 -0
  296. package/src/spec/render-spec.ts +144 -0
  297. package/src/spec/resolve.ts +123 -0
  298. package/src/spec/resolved.ts +53 -0
  299. package/src/spec/state.ts +193 -0
  300. package/src/spec/stream.ts +230 -0
  301. package/src/spec/tools.ts +17 -0
  302. package/src/spec/tree.ts +37 -0
  303. package/src/spec/types.ts +51 -0
  304. package/src/spec/validate.ts +429 -0
  305. package/src/spec/vue/SpecRenderer.tsx +544 -0
  306. package/src/spec/vue/__tests__/harness.ts +50 -0
  307. package/src/spec/vue/useSpecStream.ts +162 -0
  308. package/src/spec/vue.ts +13 -0
  309. package/src/spec.ts +55 -0
  310. package/src/theme.ts +423 -0
  311. package/src/tools/define.ts +65 -0
  312. package/src/tools/edits.ts +73 -0
  313. package/src/tools/registry.ts +44 -0
  314. package/src/tools/tools.ts +361 -0
  315. package/src/tools.ts +32 -0
  316. package/src/version.ts +4 -0
  317. package/src/vue/Widget.tsx +74 -0
  318. package/src/vue/sync.ts +36 -0
  319. package/src/vue/themeStore.ts +157 -0
  320. package/src/vue/useThemeTokens.ts +71 -0
  321. package/src/vue/useWidget.ts +117 -0
  322. package/src/vue.ts +8 -0
  323. 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
+ }