@ai-gui/image 0.34.0 → 0.35.1
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 +4 -2
- package/dist/index.cjs +59 -7
- package/dist/index.d.cts +13 -3
- package/dist/index.d.ts +13 -3
- package/dist/index.js +59 -7
- package/dist/page/entry.js +52156 -3010
- package/package.json +12 -7
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
|
|
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 (
|
|
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:
|
|
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
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
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 (
|
|
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:
|
|
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
|
}
|