@ai-gui/image 0.34.0 → 0.35.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/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # @ai-gui/image
2
2
 
3
- Render [AIGUI](../../README.md) markdown blocks — ECharts charts, Mermaid diagrams, KaTeX math, tables, cards, dashboards — to PNG, by running the real `@ai-gui/vanilla` renderer in a headless Chromium and screenshotting each block.
3
+ Render [AIGUI](../../README.md) markdown blocks — ECharts charts, Mermaid diagrams, KaTeX math, tables, cards, dashboards, 3D scenes, orbits, data walls and molecules — to PNG, by running the real `@ai-gui/vanilla` renderer in a headless Chromium and screenshotting each block.
4
4
 
5
5
  Use it where a channel carries pictures but not markup: WeChat, email, an image-only webhook.
6
6
 
7
+ The 3D families (`scene`, `molecule` in 3D, the big screen's 3D and globe panels) draw with WebGL. Headless Chromium has no GPU, so the browser is launched with SwiftShader — software WebGL — enabled; it is slower than a real GPU, which is why the per-block budget is ten seconds and a canvas is given a moment to paint before the screenshot. Anything animated is drawn at its finished state: the counted number, the grown bar, the bodies at the end of their run.
8
+
7
9
  ## Install
8
10
 
9
11
  ```sh
@@ -45,7 +47,7 @@ Blocks that fail to render are left in `text` as their original source, so a bro
45
47
  ## Options
46
48
 
47
49
  - `outDir` — where PNGs are written (required).
48
- - `kinds` — which families to draw. Default: all six.
50
+ - `kinds` — which families to draw. Default: all ten — `chart`, `mermaid`, `dashboard`, `card`, `math`, `table`, `scene`, `gravity`, `bigscreen`, `molecule`.
49
51
  - `theme` — `"light"` (default) or `"dark"`.
50
52
  - `width` — viewport width in CSS pixels. Default 720.
51
53
  - `scale` — device pixels per CSS pixel. Default 2, which is what a phone screen wants.
package/dist/index.cjs CHANGED
@@ -23,10 +23,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
 
24
24
  //#endregion
25
25
  const __ai_gui_core = __toESM(require("@ai-gui/core"));
26
+ const __ai_gui_plugin_bigscreen = __toESM(require("@ai-gui/plugin-bigscreen"));
26
27
  const __ai_gui_plugin_chart = __toESM(require("@ai-gui/plugin-chart"));
27
28
  const __ai_gui_plugin_dashboard = __toESM(require("@ai-gui/plugin-dashboard"));
29
+ const __ai_gui_plugin_gravity = __toESM(require("@ai-gui/plugin-gravity"));
28
30
  const __ai_gui_plugin_katex = __toESM(require("@ai-gui/plugin-katex"));
29
31
  const __ai_gui_plugin_mermaid = __toESM(require("@ai-gui/plugin-mermaid"));
32
+ const __ai_gui_plugin_molecule = __toESM(require("@ai-gui/plugin-molecule"));
33
+ const __ai_gui_plugin_scene = __toESM(require("@ai-gui/plugin-scene"));
30
34
  const node_fs_promises = __toESM(require("node:fs/promises"));
31
35
  const node_module = __toESM(require("node:module"));
32
36
  const node_path = __toESM(require("node:path"));
@@ -40,7 +44,11 @@ const DEFAULT_KINDS = [
40
44
  "dashboard",
41
45
  "card",
42
46
  "math",
43
- "table"
47
+ "table",
48
+ "scene",
49
+ "gravity",
50
+ "bigscreen",
51
+ "molecule"
44
52
  ];
45
53
  const DEFAULT_WIDTH = 720;
46
54
  const DEFAULT_SCALE = 2;
@@ -54,7 +62,10 @@ const DEFAULT_IDLE_SHUTDOWN_MS = 3e5;
54
62
  * The plugins an image render understands.
55
63
  *
56
64
  * `interactive: false` is not a preference. It makes plugin-chart return an SSR SVG in the same
57
- * tick rather than mounting a live ECharts instance with animations to wait out.
65
+ * tick rather than mounting a live ECharts instance with animations to wait out. The same goes
66
+ * for `animate: false` on gravity and the big screen: a picture is one moment, and the moment
67
+ * wanted is the finished one — the number counted up, the bar grown, the bodies at the end of
68
+ * their run — not whichever frame the screenshot happened to land on.
58
69
  *
59
70
  * The chart is sized to the page rather than left at the plugin's 600x400 default, which would
60
71
  * otherwise sit in a 720px column with a band of dead space beside it.
@@ -69,7 +80,18 @@ function imagePlugins(width = DEFAULT_WIDTH) {
69
80
  }),
70
81
  (0, __ai_gui_plugin_mermaid.mermaid)(),
71
82
  (0, __ai_gui_plugin_katex.katex)({ css: "" }),
72
- (0, __ai_gui_plugin_dashboard.dashboard)()
83
+ (0, __ai_gui_plugin_dashboard.dashboard)(),
84
+ (0, __ai_gui_plugin_scene.scene)({ height: Math.round(inner * .6) }),
85
+ (0, __ai_gui_plugin_gravity.gravity)({
86
+ animate: false,
87
+ width: inner,
88
+ height: Math.round(inner * .625)
89
+ }),
90
+ (0, __ai_gui_plugin_bigscreen.bigscreen)({ animate: false }),
91
+ (0, __ai_gui_plugin_molecule.molecule)({
92
+ width: inner,
93
+ height: Math.round(inner * .6)
94
+ })
73
95
  ];
74
96
  }
75
97
 
@@ -87,7 +109,7 @@ function imagePlugins(width = DEFAULT_WIDTH) {
87
109
  * models write tables without leading pipes at least as often as with them, and they escalate a
88
110
  * fence to four backticks whenever the payload contains three.
89
111
  */
90
- const TRIGGER = /^ {0,3}(?:(?:`{3,}|~{3,})[ \t]*(?:chart|mermaid|dashboard|card:)|\$\$|\||:?-+:?[ \t]*\|)/m;
112
+ const TRIGGER = /^ {0,3}(?:(?:`{3,}|~{3,})[ \t]*(?:chart|mermaid|dashboard|scene|gravity|bigscreen|molecule|card:)|\$\$|\||:?-+:?[ \t]*\|)/m;
91
113
  function hasTrigger(markdown) {
92
114
  return TRIGGER.test(markdown);
93
115
  }
@@ -99,8 +121,17 @@ function hasTrigger(markdown) {
99
121
  * registering a renderer, and tables are plain markdown-it, so both arrive as generic `html`
100
122
  * nodes carrying already-rendered markup. They have to be recognised by what is in that markup.
101
123
  */
124
+ const FENCED = new Set([
125
+ "chart",
126
+ "mermaid",
127
+ "dashboard",
128
+ "scene",
129
+ "gravity",
130
+ "bigscreen",
131
+ "molecule"
132
+ ]);
102
133
  function classify(node) {
103
- if (node.type === "chart" || node.type === "mermaid" || node.type === "dashboard") return node.complete ? node.type : void 0;
134
+ if (FENCED.has(node.type)) return node.complete ? node.type : void 0;
104
135
  if (node.type === "card") return node.card?.complete && node.card.valid ? "card" : void 0;
105
136
  if (node.type !== "html") return void 0;
106
137
  const html = node.content ?? "";
@@ -156,9 +187,24 @@ var BrowserUnavailableError = class extends Error {
156
187
  this.cause = cause;
157
188
  }
158
189
  };
190
+ /**
191
+ * Chromium's flags.
192
+ *
193
+ * The 3D blocks — scenes, molecules, the big screen's bars and globes — draw with WebGL, and a
194
+ * headless browser has no GPU. SwiftShader renders it in software instead; recent Chromium keeps
195
+ * that behind `--enable-unsafe-swiftshader` and needs to be told to use it, or `getContext("webgl")`
196
+ * returns null and every 3D block falls back to its text. Hinting is off so glyphs look the same
197
+ * at every scale factor.
198
+ */
199
+ const LAUNCH_ARGS = [
200
+ "--font-render-hinting=none",
201
+ "--use-angle=swiftshader",
202
+ "--enable-unsafe-swiftshader",
203
+ "--ignore-gpu-blocklist"
204
+ ];
159
205
  const defaultLauncher = async () => {
160
206
  const playwright = await import("playwright");
161
- return await playwright.chromium.launch({ args: ["--font-render-hinting=none"] });
207
+ return await playwright.chromium.launch({ args: LAUNCH_ARGS });
162
208
  };
163
209
  let browser;
164
210
  let launching;
@@ -279,6 +325,10 @@ const THEMES = {
279
325
  * plugin animate on entry, and an animating element is a coin flip between a finished picture and
280
326
  * a half-faded one. The font stack names CJK families explicitly: a screenshot has no fallback
281
327
  * chain to fall back to at read time, so a missing face is permanent tofu in the delivered image.
328
+ *
329
+ * The molecule plugin leaves its stylesheet to the host, so it is included here, and its Reset
330
+ * button — a control for a reader who can turn the molecule — is hidden from a picture nobody can
331
+ * turn.
282
332
  */
283
333
  function pageHtml(options = {}) {
284
334
  const theme = THEMES[options.theme ?? "light"];
@@ -288,9 +338,11 @@ function pageHtml(options = {}) {
288
338
  *,*::before,*::after{animation:none!important;transition:none!important}
289
339
  html,body{margin:0;padding:0;background:${theme.bg};color:${theme.fg}}
290
340
  body{font-family:-apple-system,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Noto Sans SC",system-ui,sans-serif;font-size:16px;line-height:1.6}
291
- #root{display:inline-block;padding:16px;box-sizing:border-box;max-width:${options.width ?? 720}px}
341
+ #root{display:inline-block;padding:16px;box-sizing:border-box;width:${options.width ?? 720}px;max-width:${options.width ?? 720}px}
292
342
  ${__ai_gui_core.baseCss}
293
343
  ${katexCss()}
344
+ ${__ai_gui_plugin_molecule.moleculeCss}
345
+ [data-aigui-molecule-reset]{display:none}
294
346
  ${pluginCss}
295
347
  </style></head><body><div id="root"></div></body></html>`;
296
348
  }
package/dist/index.d.cts CHANGED
@@ -1,9 +1,19 @@
1
1
  import { CardRegistry } from "@ai-gui/core";
2
2
 
3
3
  //#region src/types.d.ts
4
- /** A block family that can be turned into a picture. */
5
- /** A block family that can be turned into a picture. */
6
- type RenderableKind = "chart" | "mermaid" | "dashboard" | "card" | "math" | "table";
4
+ /**
5
+ * A block family that can be turned into a picture.
6
+ *
7
+ * The first six are flat. `scene`, `molecule` and `bigscreen` draw on WebGL, which headless
8
+ * Chromium provides in software; `gravity` is SVG with the animation switched off.
9
+ */
10
+ /**
11
+ * A block family that can be turned into a picture.
12
+ *
13
+ * The first six are flat. `scene`, `molecule` and `bigscreen` draw on WebGL, which headless
14
+ * Chromium provides in software; `gravity` is SVG with the animation switched off.
15
+ */
16
+ type RenderableKind = "chart" | "mermaid" | "dashboard" | "card" | "math" | "table" | "scene" | "gravity" | "bigscreen" | "molecule";
7
17
  /** One block chosen for rendering, with the source range it occupies. */
8
18
  interface BlockSelection {
9
19
  kind: RenderableKind;
package/dist/index.d.ts CHANGED
@@ -1,9 +1,19 @@
1
1
  import { CardRegistry } from "@ai-gui/core";
2
2
 
3
3
  //#region src/types.d.ts
4
- /** A block family that can be turned into a picture. */
5
- /** A block family that can be turned into a picture. */
6
- type RenderableKind = "chart" | "mermaid" | "dashboard" | "card" | "math" | "table";
4
+ /**
5
+ * A block family that can be turned into a picture.
6
+ *
7
+ * The first six are flat. `scene`, `molecule` and `bigscreen` draw on WebGL, which headless
8
+ * Chromium provides in software; `gravity` is SVG with the animation switched off.
9
+ */
10
+ /**
11
+ * A block family that can be turned into a picture.
12
+ *
13
+ * The first six are flat. `scene`, `molecule` and `bigscreen` draw on WebGL, which headless
14
+ * Chromium provides in software; `gravity` is SVG with the animation switched off.
15
+ */
16
+ type RenderableKind = "chart" | "mermaid" | "dashboard" | "card" | "math" | "table" | "scene" | "gravity" | "bigscreen" | "molecule";
7
17
  /** One block chosen for rendering, with the source range it occupies. */
8
18
  interface BlockSelection {
9
19
  kind: RenderableKind;
package/dist/index.js CHANGED
@@ -1,8 +1,12 @@
1
1
  import { baseCss, collectPluginStyles, createParserWithMetadata } from "@ai-gui/core";
2
+ import { bigscreen } from "@ai-gui/plugin-bigscreen";
2
3
  import { chart } from "@ai-gui/plugin-chart";
3
4
  import { dashboard } from "@ai-gui/plugin-dashboard";
5
+ import { gravity } from "@ai-gui/plugin-gravity";
4
6
  import { katex } from "@ai-gui/plugin-katex";
5
7
  import { mermaid } from "@ai-gui/plugin-mermaid";
8
+ import { molecule, moleculeCss } from "@ai-gui/plugin-molecule";
9
+ import { scene } from "@ai-gui/plugin-scene";
6
10
  import { mkdir } from "node:fs/promises";
7
11
  import { createRequire } from "node:module";
8
12
  import { dirname, join } from "node:path";
@@ -16,7 +20,11 @@ const DEFAULT_KINDS = [
16
20
  "dashboard",
17
21
  "card",
18
22
  "math",
19
- "table"
23
+ "table",
24
+ "scene",
25
+ "gravity",
26
+ "bigscreen",
27
+ "molecule"
20
28
  ];
21
29
  const DEFAULT_WIDTH = 720;
22
30
  const DEFAULT_SCALE = 2;
@@ -30,7 +38,10 @@ const DEFAULT_IDLE_SHUTDOWN_MS = 3e5;
30
38
  * The plugins an image render understands.
31
39
  *
32
40
  * `interactive: false` is not a preference. It makes plugin-chart return an SSR SVG in the same
33
- * tick rather than mounting a live ECharts instance with animations to wait out.
41
+ * tick rather than mounting a live ECharts instance with animations to wait out. The same goes
42
+ * for `animate: false` on gravity and the big screen: a picture is one moment, and the moment
43
+ * wanted is the finished one — the number counted up, the bar grown, the bodies at the end of
44
+ * their run — not whichever frame the screenshot happened to land on.
34
45
  *
35
46
  * The chart is sized to the page rather than left at the plugin's 600x400 default, which would
36
47
  * otherwise sit in a 720px column with a band of dead space beside it.
@@ -45,7 +56,18 @@ function imagePlugins(width = DEFAULT_WIDTH) {
45
56
  }),
46
57
  mermaid(),
47
58
  katex({ css: "" }),
48
- dashboard()
59
+ dashboard(),
60
+ scene({ height: Math.round(inner * .6) }),
61
+ gravity({
62
+ animate: false,
63
+ width: inner,
64
+ height: Math.round(inner * .625)
65
+ }),
66
+ bigscreen({ animate: false }),
67
+ molecule({
68
+ width: inner,
69
+ height: Math.round(inner * .6)
70
+ })
49
71
  ];
50
72
  }
51
73
 
@@ -63,7 +85,7 @@ function imagePlugins(width = DEFAULT_WIDTH) {
63
85
  * models write tables without leading pipes at least as often as with them, and they escalate a
64
86
  * fence to four backticks whenever the payload contains three.
65
87
  */
66
- const TRIGGER = /^ {0,3}(?:(?:`{3,}|~{3,})[ \t]*(?:chart|mermaid|dashboard|card:)|\$\$|\||:?-+:?[ \t]*\|)/m;
88
+ const TRIGGER = /^ {0,3}(?:(?:`{3,}|~{3,})[ \t]*(?:chart|mermaid|dashboard|scene|gravity|bigscreen|molecule|card:)|\$\$|\||:?-+:?[ \t]*\|)/m;
67
89
  function hasTrigger(markdown) {
68
90
  return TRIGGER.test(markdown);
69
91
  }
@@ -75,8 +97,17 @@ function hasTrigger(markdown) {
75
97
  * registering a renderer, and tables are plain markdown-it, so both arrive as generic `html`
76
98
  * nodes carrying already-rendered markup. They have to be recognised by what is in that markup.
77
99
  */
100
+ const FENCED = new Set([
101
+ "chart",
102
+ "mermaid",
103
+ "dashboard",
104
+ "scene",
105
+ "gravity",
106
+ "bigscreen",
107
+ "molecule"
108
+ ]);
78
109
  function classify(node) {
79
- if (node.type === "chart" || node.type === "mermaid" || node.type === "dashboard") return node.complete ? node.type : void 0;
110
+ if (FENCED.has(node.type)) return node.complete ? node.type : void 0;
80
111
  if (node.type === "card") return node.card?.complete && node.card.valid ? "card" : void 0;
81
112
  if (node.type !== "html") return void 0;
82
113
  const html = node.content ?? "";
@@ -132,9 +163,24 @@ var BrowserUnavailableError = class extends Error {
132
163
  this.cause = cause;
133
164
  }
134
165
  };
166
+ /**
167
+ * Chromium's flags.
168
+ *
169
+ * The 3D blocks — scenes, molecules, the big screen's bars and globes — draw with WebGL, and a
170
+ * headless browser has no GPU. SwiftShader renders it in software instead; recent Chromium keeps
171
+ * that behind `--enable-unsafe-swiftshader` and needs to be told to use it, or `getContext("webgl")`
172
+ * returns null and every 3D block falls back to its text. Hinting is off so glyphs look the same
173
+ * at every scale factor.
174
+ */
175
+ const LAUNCH_ARGS = [
176
+ "--font-render-hinting=none",
177
+ "--use-angle=swiftshader",
178
+ "--enable-unsafe-swiftshader",
179
+ "--ignore-gpu-blocklist"
180
+ ];
135
181
  const defaultLauncher = async () => {
136
182
  const playwright = await import("playwright");
137
- return await playwright.chromium.launch({ args: ["--font-render-hinting=none"] });
183
+ return await playwright.chromium.launch({ args: LAUNCH_ARGS });
138
184
  };
139
185
  let browser;
140
186
  let launching;
@@ -255,6 +301,10 @@ const THEMES = {
255
301
  * plugin animate on entry, and an animating element is a coin flip between a finished picture and
256
302
  * a half-faded one. The font stack names CJK families explicitly: a screenshot has no fallback
257
303
  * chain to fall back to at read time, so a missing face is permanent tofu in the delivered image.
304
+ *
305
+ * The molecule plugin leaves its stylesheet to the host, so it is included here, and its Reset
306
+ * button — a control for a reader who can turn the molecule — is hidden from a picture nobody can
307
+ * turn.
258
308
  */
259
309
  function pageHtml(options = {}) {
260
310
  const theme = THEMES[options.theme ?? "light"];
@@ -264,9 +314,11 @@ function pageHtml(options = {}) {
264
314
  *,*::before,*::after{animation:none!important;transition:none!important}
265
315
  html,body{margin:0;padding:0;background:${theme.bg};color:${theme.fg}}
266
316
  body{font-family:-apple-system,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Noto Sans SC",system-ui,sans-serif;font-size:16px;line-height:1.6}
267
- #root{display:inline-block;padding:16px;box-sizing:border-box;max-width:${options.width ?? 720}px}
317
+ #root{display:inline-block;padding:16px;box-sizing:border-box;width:${options.width ?? 720}px;max-width:${options.width ?? 720}px}
268
318
  ${baseCss}
269
319
  ${katexCss()}
320
+ ${moleculeCss}
321
+ [data-aigui-molecule-reset]{display:none}
270
322
  ${pluginCss}
271
323
  </style></head><body><div id="root"></div></body></html>`;
272
324
  }