@aalis/plugin-draw 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ace Nyan <ace@acenyan.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,95 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import type { Logger } from '@aalis/core';
3
+ import { type CanvasPlan } from './plan.js';
4
+ type Page = {
5
+ setJavaScriptEnabled(v: boolean): Promise<void>;
6
+ setRequestInterception(v: boolean): Promise<void>;
7
+ on(event: 'request', handler: (req: InterceptedRequest) => void): void;
8
+ setViewport(v: {
9
+ width: number;
10
+ height: number;
11
+ deviceScaleFactor: number;
12
+ }): Promise<void>;
13
+ setContent(html: string, opts?: {
14
+ waitUntil?: string;
15
+ timeout?: number;
16
+ }): Promise<void>;
17
+ evaluate<T>(fn: string | ((...a: never[]) => T), ...args: unknown[]): Promise<T>;
18
+ screenshot(opts: {
19
+ type: 'png';
20
+ clip?: {
21
+ x: number;
22
+ y: number;
23
+ width: number;
24
+ height: number;
25
+ };
26
+ }): Promise<Uint8Array>;
27
+ close(): Promise<void>;
28
+ };
29
+ type InterceptedRequest = {
30
+ url(): string;
31
+ continue(): Promise<void>;
32
+ abort(reason?: string): Promise<void>;
33
+ };
34
+ interface EngineConfig {
35
+ headless: boolean;
36
+ executablePath?: string;
37
+ /** 空闲多少毫秒后关停 Chromium(0=不关停) */
38
+ idleShutdownMs: number;
39
+ /** 单次 setContent/渲染步骤超时 */
40
+ stepTimeoutMs: number;
41
+ /** 同时在渲染的 page 数上限(防群内并发画图起无界 page + ffmpeg) */
42
+ maxConcurrency: number;
43
+ }
44
+ export declare class DrawEngine {
45
+ private logger;
46
+ private cfg;
47
+ private browser;
48
+ private launching;
49
+ private idleTimer;
50
+ private disposed;
51
+ private active;
52
+ private waiters;
53
+ constructor(logger: Logger, cfg: EngineConfig);
54
+ private ensureBrowser;
55
+ /** 给 post-load 步骤(evaluate/screenshot——不吃 puppeteer timeout)套墙钟硬上限,防极端 CSS 慢渲染吊死渲染槽。 */
56
+ private withTimeout;
57
+ private acquireSlot;
58
+ private releaseSlot;
59
+ private touchIdle;
60
+ /** 开一张硬化后的独立 page,执行 fn 后必关。 */
61
+ withPage<T>(plan: CanvasPlan, viewportHeight: number, fn: (page: Page) => Promise<T>): Promise<T>;
62
+ /** 静态渲染:定界(HTML 模式实测内容高)→ 设缩放视口 → clip 截图。 */
63
+ renderPng(plan: CanvasPlan, scale: number, maxPixels: number): Promise<{
64
+ png: Buffer;
65
+ width: number;
66
+ height: number;
67
+ }>;
68
+ /**
69
+ * 动画渲染:同一页内完成「暂停时钟 → 时长探测 → 逐帧定格截图」。
70
+ * 双机制必须并用(调研实测 getAnimations() 不暴露 SMIL):
71
+ * SMIL → svg.pauseAnimations() + svg.setCurrentTime(t)
72
+ * CSS/WAAPI → document.getAnimations() 逐个 pause() + currentTime=t*1000
73
+ * 步进与截图之间无真实时间依赖,帧序列字节级确定(调研实测)。
74
+ * 时长:显式参数 > 文档声明探测(CSS computedTiming / SMIL getSimpleDuration,
75
+ * 无穷循环按单轮计)> 默认值;帧数受 maxFrames 收口(超出按帧数反推有效时长)。
76
+ */
77
+ renderAnimation(plan: CanvasPlan, opts: {
78
+ fps: number;
79
+ requestedDurationMs?: number;
80
+ defaultDurationMs: number;
81
+ maxDurationMs: number;
82
+ maxFrames: number;
83
+ scale: number;
84
+ maxPixels: number;
85
+ }): Promise<{
86
+ frames: Buffer[];
87
+ width: number;
88
+ height: number;
89
+ animationCount: number;
90
+ durationMs: number;
91
+ }>;
92
+ dispose(): Promise<void>;
93
+ }
94
+ export {};
95
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
package/dist/engine.js ADDED
@@ -0,0 +1,205 @@
1
+ // ============================================================
2
+ // engine.ts — 硬化渲染引擎(独立 Chromium 实例,懒启动 + 空闲关停)
3
+ //
4
+ // 刻意不复用 plugin-tool-browser 的共享页面池:那里承载可能带登录态的
5
+ // 真实浏览页,本插件渲染的是 LLM 生成的不可信标记,二者不同进程隔离。
6
+ // 每次渲染开独立 page,硬化三件套:
7
+ // 1) setJavaScriptEnabled(false) —— 标记里的 <script> 一律不执行;
8
+ // 2) 请求拦截 default-deny —— 只放 about:blank 与 data:(见 plan.allowRequest);
9
+ // 3) 视口即画布 —— 定宽定高 + clip 截图,杜绝内联 SVG 随视口伸缩。
10
+ // ============================================================
11
+ import { Buffer } from 'node:buffer';
12
+ import { allowRequest } from './plan.js';
13
+ export class DrawEngine {
14
+ logger;
15
+ cfg;
16
+ browser = null;
17
+ launching = null;
18
+ idleTimer = null;
19
+ disposed = false;
20
+ active = 0;
21
+ waiters = [];
22
+ constructor(logger, cfg) {
23
+ this.logger = logger;
24
+ this.cfg = cfg;
25
+ }
26
+ async ensureBrowser() {
27
+ if (this.disposed)
28
+ throw new Error('渲染引擎已停用');
29
+ if (this.browser?.connected)
30
+ return this.browser;
31
+ if (!this.launching) {
32
+ this.launching = (async () => {
33
+ const { default: puppeteer } = (await import('puppeteer'));
34
+ const browser = await puppeteer.launch({
35
+ headless: this.cfg.headless,
36
+ ...(this.cfg.executablePath ? { executablePath: this.cfg.executablePath } : {}),
37
+ args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
38
+ });
39
+ this.logger.info('绘图引擎 Chromium 已启动(独立实例,不与 browser 工具共享)');
40
+ return browser;
41
+ })().finally(() => {
42
+ this.launching = null;
43
+ });
44
+ this.browser = await this.launching;
45
+ return this.browser;
46
+ }
47
+ return this.launching;
48
+ }
49
+ /** 给 post-load 步骤(evaluate/screenshot——不吃 puppeteer timeout)套墙钟硬上限,防极端 CSS 慢渲染吊死渲染槽。 */
50
+ withTimeout(p, label) {
51
+ return Promise.race([
52
+ p,
53
+ new Promise((_r, reject) => setTimeout(() => reject(new Error(`渲染步骤超时(${label})`)), this.cfg.stepTimeoutMs).unref?.()),
54
+ ]);
55
+ }
56
+ async acquireSlot() {
57
+ if (this.active < this.cfg.maxConcurrency) {
58
+ this.active++;
59
+ return;
60
+ }
61
+ await new Promise(resolve => this.waiters.push(resolve));
62
+ this.active++;
63
+ }
64
+ releaseSlot() {
65
+ this.active--;
66
+ const next = this.waiters.shift();
67
+ if (next)
68
+ next();
69
+ }
70
+ touchIdle() {
71
+ if (this.idleTimer)
72
+ clearTimeout(this.idleTimer);
73
+ if (this.cfg.idleShutdownMs <= 0)
74
+ return;
75
+ this.idleTimer = setTimeout(() => {
76
+ const b = this.browser;
77
+ this.browser = null;
78
+ if (b) {
79
+ b.close().catch(() => { });
80
+ this.logger.info('绘图引擎空闲,Chromium 已关停');
81
+ }
82
+ }, this.cfg.idleShutdownMs);
83
+ // 不阻止进程退出
84
+ this.idleTimer.unref?.();
85
+ }
86
+ /** 开一张硬化后的独立 page,执行 fn 后必关。 */
87
+ async withPage(plan, viewportHeight, fn) {
88
+ await this.acquireSlot();
89
+ let page = null;
90
+ try {
91
+ const browser = await this.ensureBrowser();
92
+ page = await browser.newPage();
93
+ await page.setJavaScriptEnabled(false);
94
+ await page.setRequestInterception(true);
95
+ page.on('request', req => {
96
+ if (allowRequest(req.url()))
97
+ req.continue().catch(() => { });
98
+ else
99
+ req.abort('blockedbyclient').catch(() => { });
100
+ });
101
+ await page.setViewport({ width: plan.width, height: viewportHeight, deviceScaleFactor: 1 });
102
+ await page.setContent(plan.html, { waitUntil: 'load', timeout: this.cfg.stepTimeoutMs });
103
+ // 等字体就绪(外链字体被拦时会快速 resolve 并回退系统字体,不会挂死——实测行为)
104
+ await this.withTimeout(page.evaluate('document.fonts ? document.fonts.ready.then(() => true) : true'), 'fonts');
105
+ return await fn(page);
106
+ }
107
+ finally {
108
+ if (page)
109
+ await page.close().catch(() => { });
110
+ this.releaseSlot();
111
+ this.touchIdle();
112
+ }
113
+ }
114
+ /** 静态渲染:定界(HTML 模式实测内容高)→ 设缩放视口 → clip 截图。 */
115
+ async renderPng(plan, scale, maxPixels) {
116
+ const guessHeight = plan.height === 'auto' ? 600 : plan.height;
117
+ return this.withPage(plan, guessHeight, async (page) => {
118
+ let height = plan.height;
119
+ if (height === 'auto') {
120
+ const measured = await this.withTimeout(page.evaluate('Math.ceil(document.getElementById("aalis-draw").getBoundingClientRect().height)'), 'measure');
121
+ height = Math.max(16, Math.min(measured || 16, Math.floor(maxPixels / plan.width)));
122
+ }
123
+ await page.setViewport({ width: plan.width, height, deviceScaleFactor: scale });
124
+ const shot = await this.withTimeout(page.screenshot({ type: 'png', clip: { x: 0, y: 0, width: plan.width, height } }), 'screenshot');
125
+ return { png: Buffer.from(shot), width: plan.width, height };
126
+ });
127
+ }
128
+ /**
129
+ * 动画渲染:同一页内完成「暂停时钟 → 时长探测 → 逐帧定格截图」。
130
+ * 双机制必须并用(调研实测 getAnimations() 不暴露 SMIL):
131
+ * SMIL → svg.pauseAnimations() + svg.setCurrentTime(t)
132
+ * CSS/WAAPI → document.getAnimations() 逐个 pause() + currentTime=t*1000
133
+ * 步进与截图之间无真实时间依赖,帧序列字节级确定(调研实测)。
134
+ * 时长:显式参数 > 文档声明探测(CSS computedTiming / SMIL getSimpleDuration,
135
+ * 无穷循环按单轮计)> 默认值;帧数受 maxFrames 收口(超出按帧数反推有效时长)。
136
+ */
137
+ async renderAnimation(plan, opts) {
138
+ const guessHeight = plan.height === 'auto' ? 600 : plan.height;
139
+ return this.withPage(plan, guessHeight, async (page) => {
140
+ let height = plan.height;
141
+ if (height === 'auto') {
142
+ const measured = await this.withTimeout(page.evaluate('Math.ceil(document.getElementById("aalis-draw").getBoundingClientRect().height)'), 'measure');
143
+ height = Math.max(16, Math.min(measured || 16, Math.floor(opts.maxPixels / plan.width)));
144
+ }
145
+ await page.setViewport({ width: plan.width, height, deviceScaleFactor: opts.scale });
146
+ // 暂停两套动画时钟;顺带数动画数与声明时长(0 动画 = 调用方该提示"这是静态图")
147
+ const probe = await page.evaluate(`(() => {
148
+ let count = 0;
149
+ let max = 0;
150
+ for (const svg of document.querySelectorAll('svg')) {
151
+ try { svg.pauseAnimations(); } catch {}
152
+ for (const el of svg.querySelectorAll('animate,animateTransform,animateMotion,set')) {
153
+ count += 1;
154
+ try {
155
+ const d = el.getSimpleDuration();
156
+ if (Number.isFinite(d)) max = Math.max(max, d * 1000);
157
+ } catch {}
158
+ }
159
+ }
160
+ for (const a of document.getAnimations()) {
161
+ try {
162
+ a.pause();
163
+ count += 1;
164
+ const t = a.effect.getComputedTiming();
165
+ const iters = Number.isFinite(t.iterations) ? t.iterations : 1;
166
+ max = Math.max(max, (t.delay || 0) + (Number(t.duration) || 0) * iters);
167
+ } catch {}
168
+ }
169
+ return { count, durationMs: Math.round(max) };
170
+ })()`);
171
+ let durationMs = opts.requestedDurationMs ?? (probe.durationMs > 0 ? probe.durationMs : opts.defaultDurationMs);
172
+ durationMs = Math.min(durationMs, opts.maxDurationMs);
173
+ let frameCount = Math.max(2, Math.round((durationMs / 1000) * opts.fps));
174
+ if (frameCount > opts.maxFrames) {
175
+ frameCount = opts.maxFrames;
176
+ durationMs = Math.round((frameCount / opts.fps) * 1000);
177
+ }
178
+ const frames = [];
179
+ for (let i = 0; i < frameCount; i++) {
180
+ const t = (i / opts.fps).toFixed(6);
181
+ await page.evaluate(`((t) => {
182
+ for (const svg of document.querySelectorAll('svg')) {
183
+ try { svg.setCurrentTime(t); } catch {}
184
+ }
185
+ for (const a of document.getAnimations()) {
186
+ try { a.pause(); a.currentTime = t * 1000; } catch {}
187
+ }
188
+ })(${t})`);
189
+ const shot = await this.withTimeout(page.screenshot({ type: 'png', clip: { x: 0, y: 0, width: plan.width, height } }), `frame ${i}`);
190
+ frames.push(Buffer.from(shot));
191
+ }
192
+ return { frames, width: plan.width, height, animationCount: probe.count, durationMs };
193
+ });
194
+ }
195
+ async dispose() {
196
+ this.disposed = true;
197
+ if (this.idleTimer)
198
+ clearTimeout(this.idleTimer);
199
+ const b = this.browser ?? (this.launching ? await this.launching.catch(() => null) : null);
200
+ this.browser = null;
201
+ if (b)
202
+ await b.close().catch(() => { });
203
+ }
204
+ }
205
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
package/dist/gif.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { Buffer } from 'node:buffer';
2
+ import type { ProcessService } from '@aalis/api-process';
3
+ import type { StorageService } from '@aalis/api-storage';
4
+ /** palettegen / paletteuse 两遍的 ffmpeg 参数(纯函数,供测试断言) */
5
+ export declare function ffmpegPaletteArgs(fps: number, framePattern: string, palettePath: string): string[];
6
+ export declare function ffmpegEncodeArgs(fps: number, framePattern: string, palettePath: string, outPath: string): string[];
7
+ /** 帧序列编码为 GIF。失败抛错(调用方转用户可读错误)。 */
8
+ export declare function framesToGif(proc: ProcessService, storage: StorageService, frames: Buffer[], fps: number): Promise<Buffer>;
9
+ //# sourceMappingURL=gif.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gif.d.ts","sourceRoot":"","sources":["../src/gif.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,uDAAuD;AACvD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAWlG;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAelH;AAED,mCAAmC;AACnC,wBAAsB,WAAW,CAC/B,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,MAAM,EAAE,EAChB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CAkBjB"}
package/dist/gif.js ADDED
@@ -0,0 +1,60 @@
1
+ // ============================================================
2
+ // gif.ts — 帧序列 → GIF(ffmpeg 调色板两遍编码)
3
+ //
4
+ // 走 api-process 的 makeTempDir + execFile 范式(与 plugin-media/ffmpeg.ts 同规):
5
+ // 二进制不过 stdout(ExecResult.stdout 是 utf-8 字符串,会被解码破坏),
6
+ // 一律写临时文件再经 storage 读回。两遍编码:palettegen 按帧间差分建 256 色
7
+ // 调色板,paletteuse 带抖动回贴——文字清晰、体积小(调研实测 2s@20fps≈133KB)。
8
+ // ============================================================
9
+ /** palettegen / paletteuse 两遍的 ffmpeg 参数(纯函数,供测试断言) */
10
+ export function ffmpegPaletteArgs(fps, framePattern, palettePath) {
11
+ return [
12
+ '-y',
13
+ '-framerate',
14
+ String(fps),
15
+ '-i',
16
+ framePattern,
17
+ '-vf',
18
+ 'palettegen=max_colors=256:stats_mode=diff',
19
+ palettePath,
20
+ ];
21
+ }
22
+ export function ffmpegEncodeArgs(fps, framePattern, palettePath, outPath) {
23
+ return [
24
+ '-y',
25
+ '-framerate',
26
+ String(fps),
27
+ '-i',
28
+ framePattern,
29
+ '-i',
30
+ palettePath,
31
+ '-lavfi',
32
+ 'paletteuse=dither=sierra2_4a',
33
+ '-loop',
34
+ '0',
35
+ outPath,
36
+ ];
37
+ }
38
+ /** 帧序列编码为 GIF。失败抛错(调用方转用户可读错误)。 */
39
+ export async function framesToGif(proc, storage, frames, fps) {
40
+ if (frames.length === 0)
41
+ throw new Error('无帧可编码');
42
+ const tmp = await proc.makeTempDir('draw-gif');
43
+ try {
44
+ for (let i = 0; i < frames.length; i++) {
45
+ await storage.writeFile(`${tmp.uri}/frame_${String(i).padStart(4, '0')}.png`, frames[i]);
46
+ }
47
+ const framePattern = `${tmp.path}/frame_%04d.png`;
48
+ const palettePath = `${tmp.path}/palette.png`;
49
+ const outPath = `${tmp.path}/out.gif`;
50
+ await proc.execFile('ffmpeg', ffmpegPaletteArgs(fps, framePattern, palettePath), { timeout: 60_000 });
51
+ await proc.execFile('ffmpeg', ffmpegEncodeArgs(fps, framePattern, palettePath, outPath), { timeout: 60_000 });
52
+ const raw = (await storage.readFile(`${tmp.uri}/out.gif`));
53
+ const { Buffer: NodeBuffer } = await import('node:buffer');
54
+ return NodeBuffer.from(raw);
55
+ }
56
+ finally {
57
+ await tmp.cleanup();
58
+ }
59
+ }
60
+ //# sourceMappingURL=gif.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gif.js","sourceRoot":"","sources":["../src/gif.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,qCAAqC;AACrC,EAAE;AACF,0EAA0E;AAC1E,sDAAsD;AACtD,oDAAoD;AACpD,uDAAuD;AACvD,+DAA+D;AAM/D,uDAAuD;AACvD,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,YAAoB,EAAE,WAAmB;IACtF,OAAO;QACL,IAAI;QACJ,YAAY;QACZ,MAAM,CAAC,GAAG,CAAC;QACX,IAAI;QACJ,YAAY;QACZ,KAAK;QACL,2CAA2C;QAC3C,WAAW;KACZ,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAW,EAAE,YAAoB,EAAE,WAAmB,EAAE,OAAe;IACtG,OAAO;QACL,IAAI;QACJ,YAAY;QACZ,MAAM,CAAC,GAAG,CAAC;QACX,IAAI;QACJ,YAAY;QACZ,IAAI;QACJ,WAAW;QACX,QAAQ;QACR,8BAA8B;QAC9B,OAAO;QACP,GAAG;QACH,OAAO;KACR,CAAC;AACJ,CAAC;AAED,mCAAmC;AACnC,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAoB,EACpB,OAAuB,EACvB,MAAgB,EAChB,GAAW;IAEX,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;IAC/C,IAAI,CAAC;QACH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACvC,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,GAAG,UAAU,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3F,CAAC;QACD,MAAM,YAAY,GAAG,GAAG,GAAG,CAAC,IAAI,iBAAiB,CAAC;QAClD,MAAM,WAAW,GAAG,GAAG,GAAG,CAAC,IAAI,cAAc,CAAC;QAC9C,MAAM,OAAO,GAAG,GAAG,GAAG,CAAC,IAAI,UAAU,CAAC;QACtC,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC,GAAG,EAAE,YAAY,EAAE,WAAW,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACtG,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC,GAAG,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9G,MAAM,GAAG,GAAG,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,GAAG,UAAU,CAAC,CAAe,CAAC;QACzE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QAC3D,OAAO,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC9B,CAAC;YAAS,CAAC;QACT,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;AACH,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { Context } from '@aalis/core';
2
+ import type { ConfigSchema } from '@aalis/schema-config';
3
+ export declare const name = "@aalis/plugin-draw";
4
+ export declare const displayName = "\u7ED8\u56FE";
5
+ export declare const subsystem = "tools";
6
+ export declare const inject: {
7
+ optional: string[];
8
+ };
9
+ export declare const configSchema: ConfigSchema;
10
+ export declare function apply(ctx: Context, rawConfig: Record<string, unknown>): void;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAOzD,eAAO,MAAM,IAAI,uBAAuB,CAAC;AACzC,eAAO,MAAM,WAAW,iBAAO,CAAC;AAChC,eAAO,MAAM,SAAS,UAAU,CAAC;AACjC,eAAO,MAAM,MAAM;;CAAgD,CAAC;AAEpE,eAAO,MAAM,YAAY,EAAE,YAiF1B,CAAC;AA6CF,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAkM5E"}
package/dist/index.js ADDED
@@ -0,0 +1,318 @@
1
+ // ============================================================
2
+ // @aalis/plugin-draw — 让纯文本模型画图
3
+ //
4
+ // LLM 写标记(SVG 或 HTML+内联 CSS),本插件用硬化的无头浏览器渲染成
5
+ // PNG(后续:CSS/SMIL 动画 → 逐帧 → GIF),落盘 data:/images/ 后由
6
+ // send_attachment 投递进聊天。引擎与安全设计见 engine.ts 头注。
7
+ //
8
+ // 格式分工(工具描述同步教给模型):
9
+ // 图形/图标/梗图/动画 → SVG(viewBox 定界精确、声明式动画现成)
10
+ // 图文卡片/表格/海报 → HTML+内联 CSS(排版引擎管折行布局,可内嵌 SVG)
11
+ // ============================================================
12
+ import { Buffer } from 'node:buffer';
13
+ import { createProcessGateway } from '@aalis/api-process';
14
+ import { createStorageGateway } from '@aalis/api-storage';
15
+ import { useToolService } from '@aalis/api-tools';
16
+ import { DrawEngine } from './engine.js';
17
+ import { framesToGif } from './gif.js';
18
+ import { lintAnimationSource, resolveCanvas } from './plan.js';
19
+ // ===== 插件元数据 =====
20
+ export const name = '@aalis/plugin-draw';
21
+ export const displayName = '绘图';
22
+ export const subsystem = 'tools';
23
+ export const inject = { optional: ['tools', 'storage', 'process'] };
24
+ export const configSchema = {
25
+ defaultWidth: {
26
+ type: 'number',
27
+ label: '默认画布宽 (px)',
28
+ default: 800,
29
+ description: 'HTML 模式与未声明宽度的 SVG 使用的画布宽度',
30
+ },
31
+ maxWidth: {
32
+ type: 'number',
33
+ label: '画布宽上限 (px)',
34
+ default: 1600,
35
+ description: '请求宽与标记声明宽都会被收口到该值',
36
+ },
37
+ maxPixels: {
38
+ type: 'number',
39
+ label: '画布 CSS 像素上限',
40
+ default: 4000000,
41
+ description: 'width×height(CSS 像素)上限,默认 4MP。注意实际光栅内存 = 本值 × scale²(scale 默认 2 即 4 倍);' +
42
+ '静态图按 scale 截图,故设备像素天花板是本值的 scale² 倍——调高前算好内存',
43
+ },
44
+ maxSourceKB: {
45
+ type: 'number',
46
+ label: '输入标记上限 (KB)',
47
+ default: 256,
48
+ description: 'source 参数的大小上限',
49
+ },
50
+ scale: {
51
+ type: 'number',
52
+ label: '截图缩放倍率',
53
+ default: 2,
54
+ description: 'deviceScaleFactor:2 = 视网膜清晰度(像素翻倍,文件更大)',
55
+ },
56
+ headless: {
57
+ type: 'boolean',
58
+ label: '无头模式',
59
+ default: true,
60
+ description: '调试时可关闭以观察渲染页面',
61
+ },
62
+ executablePath: {
63
+ type: 'string',
64
+ label: 'Chrome 路径(留空自动探测)',
65
+ default: '',
66
+ description: '留空使用 puppeteer 缓存的 Chrome(与浏览器工具共用同一份二进制,进程独立)',
67
+ },
68
+ idleShutdownSec: {
69
+ type: 'number',
70
+ label: '空闲关停 (秒)',
71
+ default: 300,
72
+ description: '渲染引擎空闲该时长后关停 Chromium 释放内存;0 = 常驻',
73
+ },
74
+ maxConcurrency: {
75
+ type: 'number',
76
+ label: '渲染并发上限',
77
+ default: 2,
78
+ description: '同时进行的渲染任务数上限;群内多人并发画图时超出的排队,防无界 page + ffmpeg 拖垮机器',
79
+ },
80
+ animMaxDurationSec: {
81
+ type: 'number',
82
+ label: '动图时长上限 (秒)',
83
+ default: 8,
84
+ description: 'draw_animation 的动画时长硬上限',
85
+ },
86
+ animDefaultFps: {
87
+ type: 'number',
88
+ label: '动图默认帧率',
89
+ default: 15,
90
+ description: '未显式指定 fps 时使用;上限 25',
91
+ },
92
+ animMaxFrames: {
93
+ type: 'number',
94
+ label: '动图帧数上限',
95
+ default: 160,
96
+ description: '时长×帧率超出时按帧数反推有效时长',
97
+ },
98
+ animMaxOutputMB: {
99
+ type: 'number',
100
+ label: 'GIF 体积上限 (MB)',
101
+ default: 9,
102
+ description: '超出即报错(OneBot 内联投递上限 10MB,留余量)',
103
+ },
104
+ };
105
+ function resolveConfig(config) {
106
+ const num = (v, dflt, lo, hi) => {
107
+ const n = Number(v);
108
+ return Number.isFinite(n) && n >= lo ? Math.min(hi, Math.floor(n)) : dflt;
109
+ };
110
+ return {
111
+ defaultWidth: num(config.defaultWidth, 800, 16, 4096),
112
+ maxWidth: num(config.maxWidth, 1600, 16, 4096),
113
+ maxPixels: num(config.maxPixels, 4_000_000, 65536, 16_000_000),
114
+ maxSourceBytes: num(config.maxSourceKB, 256, 1, 4096) * 1024,
115
+ scale: num(config.scale, 2, 1, 3),
116
+ headless: config.headless ?? true,
117
+ executablePath: config.executablePath ?? '',
118
+ idleShutdownSec: num(config.idleShutdownSec, 300, 0, 86400),
119
+ maxConcurrency: num(config.maxConcurrency, 2, 1, 8),
120
+ animMaxDurationSec: num(config.animMaxDurationSec, 8, 1, 30),
121
+ animDefaultFps: num(config.animDefaultFps, 15, 1, 25),
122
+ animMaxFrames: num(config.animMaxFrames, 160, 2, 600),
123
+ animMaxOutputMB: num(config.animMaxOutputMB, 9, 1, 9),
124
+ };
125
+ }
126
+ /** sessionId → 文件系统安全目录名(与 adapter 附件缓存同规,另中和 .. 与前导点做纵深防御)。 */
127
+ function safeSessionDir(sessionId) {
128
+ return (sessionId
129
+ .replace(/[:/\\]/g, '_')
130
+ .replace(/\.\./g, '_')
131
+ .replace(/^\.+/, '_') || 'nosession');
132
+ }
133
+ export function apply(ctx, rawConfig) {
134
+ const cfg = resolveConfig(rawConfig);
135
+ const logger = ctx.logger.child('draw');
136
+ const storage = createStorageGateway(ctx);
137
+ const proc = createProcessGateway(ctx);
138
+ const engine = new DrawEngine(logger, {
139
+ headless: cfg.headless,
140
+ executablePath: cfg.executablePath || undefined,
141
+ idleShutdownMs: cfg.idleShutdownSec * 1000,
142
+ stepTimeoutMs: 15_000,
143
+ maxConcurrency: cfg.maxConcurrency,
144
+ });
145
+ ctx.onDispose(() => engine.dispose());
146
+ const tools = useToolService(ctx);
147
+ tools.registerGroup({
148
+ name: 'draw',
149
+ label: '绘图',
150
+ description: '把模型编写的 SVG/HTML 标记渲染为图片,配合 send_attachment 发进聊天',
151
+ });
152
+ tools.register({
153
+ // 刻意 public:绘图是人人可要求的能力(用户产品决定)——群里谁都能让 bot 画图。
154
+ // 抗滥用不靠权限档而靠资源治理:单次成本被 maxPixels/maxFrames/体积封顶,
155
+ // 跨会话并发由引擎信号量排队(不拒绝、只削峰),机器不会被刷图打垮。
156
+ groups: ['draw'],
157
+ definition: {
158
+ type: 'function',
159
+ function: {
160
+ name: 'draw_image',
161
+ description: '把你编写的 SVG 或 HTML 标记渲染成 PNG 图片,落盘后返回 storage_uri(用 send_attachment 的 storage_uri 参数发送到聊天)。\n' +
162
+ '**选型**:图形/图标/梗图/示意图 → 写完整 SVG 文档(**必须带 viewBox**,建议同时带 width/height);' +
163
+ '图文卡片/表格/排版类 → 写 HTML 片段+内联 CSS(可内嵌 SVG 图形;不要写 <html>/<body> 外壳)。\n' +
164
+ '**硬约束**:渲染页禁用脚本且拦截全部网络请求——<script> 不会执行,外链图片/字体/CSS 一律加载失败,' +
165
+ '资源只能内联(data: URI);中文与 emoji 直接可用(系统字体栈兜底)。' +
166
+ '画布定界:SVG 按 viewBox/width 精确定界;HTML 按 width 参数定宽、内容自动量高。',
167
+ parameters: {
168
+ type: 'object',
169
+ properties: {
170
+ source: { type: 'string', description: 'SVG 文档或 HTML 片段' },
171
+ width: { type: 'number', description: '画布宽 px(可选;SVG 自带尺寸时按比例覆盖,HTML 默认 800)' },
172
+ },
173
+ required: ['source'],
174
+ additionalProperties: false,
175
+ },
176
+ },
177
+ },
178
+ handler: async (args, callCtx) => {
179
+ try {
180
+ const source = String(args.source ?? '');
181
+ if (!source.trim())
182
+ return JSON.stringify({ error: 'source 为空' });
183
+ if (Buffer.byteLength(source) > cfg.maxSourceBytes) {
184
+ return JSON.stringify({ error: `source 超过大小上限 ${Math.floor(cfg.maxSourceBytes / 1024)}KB` });
185
+ }
186
+ // 按 scale² 折算:resolveCanvas 收的是 CSS 像素,但真实光栅 = CSS 像素 × scale²,
187
+ // 用折算后的 CSS 像素预算让设备像素峰值不超过 maxPixels 的既定内存含义
188
+ const cssPixelBudget = Math.floor(cfg.maxPixels / (cfg.scale * cfg.scale));
189
+ const plan = resolveCanvas(source, args.width, { ...cfg, maxPixels: cssPixelBudget });
190
+ const { png, width, height } = await engine.renderPng(plan, cfg.scale, cssPixelBudget);
191
+ const digest = await crypto.subtle.digest('SHA-256', new Uint8Array(png));
192
+ const hash = Buffer.from(digest).toString('hex').slice(0, 16);
193
+ const dir = safeSessionDir(callCtx.sessionId || 'nosession');
194
+ const uri = `data:/images/${dir}/draw-${hash}.png`;
195
+ await storage.writeFile(uri, png);
196
+ logger.info(`draw_image 渲染完成 mode=${plan.mode} ${width}x${height}@${cfg.scale}x → ${uri}`);
197
+ return JSON.stringify({
198
+ uri,
199
+ width,
200
+ height,
201
+ mode: plan.mode,
202
+ message: '已渲染并落盘。用 send_attachment({ kind: "image", storage_uri: uri }) 发送到聊天。',
203
+ });
204
+ }
205
+ catch (err) {
206
+ const message = err instanceof Error ? err.message : String(err);
207
+ logger.warn(`draw_image 失败: ${message}`);
208
+ return JSON.stringify({ error: message });
209
+ }
210
+ },
211
+ });
212
+ tools.register({
213
+ // public 同 draw_image:人人可画,抗滥用靠资源治理不靠权限档。
214
+ groups: ['draw'],
215
+ definition: {
216
+ type: 'function',
217
+ function: {
218
+ name: 'draw_animation',
219
+ description: '把你编写的带动画的 SVG/HTML 标记渲染成 GIF 动图,落盘后返回 storage_uri(用 send_attachment 发送)。\n' +
220
+ '**动画只能用声明式**:SVG 用 SMIL(<animate>/<animateTransform>/<animateMotion>)或 CSS ' +
221
+ '@keyframes;脚本驱动的动画不会生效(渲染页禁用 JS)。建议动画时长 2-5 秒并做无缝循环' +
222
+ '(首尾状态一致),GIF 默认无限循环播放。\n' +
223
+ '**避坑**:同一元素不要同时挂 animateMotion 与位移类 animateTransform(位移复合会漂移)。\n' +
224
+ '渲染成功即可**直接 send_attachment 发送**,不需要额外核对。结果里的 check_frames(首帧/中帧)' +
225
+ '仅在你对形态没把握时可选地用 analyze_image 看一眼——但视觉识别较慢,通常无必要,别默认调用;' +
226
+ '即便核对,核对失败或超时也**不影响发送,直接发 GIF**,不要因核对不成而放弃发送。\n' +
227
+ '选型与硬约束同 draw_image:图形动画写 SVG(必须带 viewBox);外链资源一律被拦,只能内联。',
228
+ parameters: {
229
+ type: 'object',
230
+ properties: {
231
+ source: { type: 'string', description: '带 SMIL/CSS 动画的 SVG 文档或 HTML 片段' },
232
+ width: { type: 'number', description: '画布宽 px(可选,动图建议 ≤600 控制体积)' },
233
+ duration_seconds: { type: 'number', description: '动画时长秒(可选;缺省自动探测声明时长)' },
234
+ fps: { type: 'number', description: '帧率(可选,默认 15,上限 25)' },
235
+ },
236
+ required: ['source'],
237
+ additionalProperties: false,
238
+ },
239
+ },
240
+ },
241
+ handler: async (args, callCtx) => {
242
+ try {
243
+ const source = String(args.source ?? '');
244
+ if (!source.trim())
245
+ return JSON.stringify({ error: 'source 为空' });
246
+ if (Buffer.byteLength(source) > cfg.maxSourceBytes) {
247
+ return JSON.stringify({ error: `source 超过大小上限 ${Math.floor(cfg.maxSourceBytes / 1024)}KB` });
248
+ }
249
+ const plan = resolveCanvas(source, args.width, cfg);
250
+ const reqDur = Number(args.duration_seconds);
251
+ const reqFps = Number(args.fps);
252
+ const fps = Number.isFinite(reqFps) && reqFps > 0 ? Math.min(Math.floor(reqFps), 25) : cfg.animDefaultFps;
253
+ const r = await engine.renderAnimation(plan, {
254
+ fps,
255
+ requestedDurationMs: Number.isFinite(reqDur) && reqDur > 0 ? Math.round(reqDur * 1000) : undefined,
256
+ defaultDurationMs: 3000,
257
+ maxDurationMs: cfg.animMaxDurationSec * 1000,
258
+ maxFrames: cfg.animMaxFrames,
259
+ scale: 1, // 动图按 1x:帧数×像素才是体积主宰,清晰度靠画布宽
260
+ maxPixels: cfg.maxPixels,
261
+ });
262
+ const gif = await framesToGif(proc, storage, r.frames, fps);
263
+ if (gif.byteLength > cfg.animMaxOutputMB * 1024 * 1024) {
264
+ return JSON.stringify({
265
+ error: `GIF ${(gif.byteLength / 1048576).toFixed(1)}MB 超过上限 ${cfg.animMaxOutputMB}MB——` +
266
+ '请缩小画布宽、降低 fps 或缩短时长后重试',
267
+ });
268
+ }
269
+ const digest = await crypto.subtle.digest('SHA-256', new Uint8Array(gif));
270
+ const hash = Buffer.from(digest).toString('hex').slice(0, 16);
271
+ const dir = safeSessionDir(callCtx.sessionId || 'nosession');
272
+ const uri = `data:/images/${dir}/draw-${hash}.gif`;
273
+ await storage.writeFile(uri, gif);
274
+ // 检查帧:首帧+中帧落盘,供 agent 在没把握时**可选**用 analyze_image 核对——
275
+ // 不是默认路径。本地视觉推理慢(33b 单张可达数分钟),默认自检会把 2 秒的动图
276
+ // 拖成几分钟、还挤占入站识别的产能(实测已踩),故降级为提示、由模型按需取用。
277
+ const midIdx = Math.floor(r.frames.length / 2);
278
+ const checkFrames = [];
279
+ for (const [tag, idx] of [
280
+ ['f0', 0],
281
+ ['fmid', midIdx],
282
+ ]) {
283
+ const fUri = `data:/images/${dir}/draw-${hash}-${tag}.png`;
284
+ await storage.writeFile(fUri, r.frames[idx]);
285
+ checkFrames.push(fUri);
286
+ }
287
+ const lintWarnings = lintAnimationSource(source);
288
+ logger.info(`draw_animation 渲染完成 mode=${plan.mode} ${r.width}x${r.height} ${r.frames.length}帧@${fps}fps ` +
289
+ `${(gif.byteLength / 1024).toFixed(0)}KB anims=${r.animationCount} → ${uri}`);
290
+ const warnings = [
291
+ ...(r.animationCount === 0
292
+ ? ['未检测到任何声明式动画——产物是静态画面的重复帧。动画请用 SMIL 或 CSS @keyframes。']
293
+ : []),
294
+ ...lintWarnings,
295
+ ];
296
+ return JSON.stringify({
297
+ uri,
298
+ width: r.width,
299
+ height: r.height,
300
+ frames: r.frames.length,
301
+ fps,
302
+ duration_seconds: r.durationMs / 1000,
303
+ size_kb: Math.round(gif.byteLength / 1024),
304
+ check_frames: checkFrames,
305
+ ...(warnings.length > 0 ? { warnings } : {}),
306
+ message: '已渲染并落盘,可直接用 send_attachment({ kind: "image", storage_uri: uri }) 发送到聊天。' +
307
+ 'check_frames 仅供没把握时可选核对(analyze_image 较慢,通常无需调用)。',
308
+ });
309
+ }
310
+ catch (err) {
311
+ const message = err instanceof Error ? err.message : String(err);
312
+ logger.warn(`draw_animation 失败: ${message}`);
313
+ return JSON.stringify({ error: message });
314
+ }
315
+ },
316
+ });
317
+ }
318
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/plan.d.ts ADDED
@@ -0,0 +1,62 @@
1
+ export interface DrawCaps {
2
+ /** HTML 模式与无宽度 SVG 的默认画布宽(px) */
3
+ defaultWidth: number;
4
+ /** 画布宽上限(px) */
5
+ maxWidth: number;
6
+ /** 画布总像素上限(width × height,防巨图拖垮渲染与投递) */
7
+ maxPixels: number;
8
+ /** 输入标记大小上限(字节) */
9
+ maxSourceBytes: number;
10
+ /** 截图缩放(deviceScaleFactor) */
11
+ scale: number;
12
+ }
13
+ export type SourceMode = 'svg' | 'html';
14
+ export interface CanvasPlan {
15
+ mode: SourceMode;
16
+ /** 画布宽(CSS px,已 clamp) */
17
+ width: number;
18
+ /** 画布高:SVG 按尺寸语义先验可知;HTML 需渲染后实测 */
19
+ height: number | 'auto';
20
+ /** 交给浏览器 setContent 的完整文档 */
21
+ html: string;
22
+ }
23
+ /**
24
+ * 源分类:剥掉 BOM/空白/XML 序言/DOCTYPE/前导注释后,根元素是 <svg> 即 SVG 文档,
25
+ * 否则一律按 HTML 片段处理(含"HTML 里嵌 SVG"的混合写法)。
26
+ */
27
+ export declare function classifySource(source: string): SourceMode;
28
+ /** 从 SVG 根元素上解析尺寸语义(width/height 属性优先,viewBox 兜底)。 */
29
+ export declare function parseSvgCanvas(svg: string): {
30
+ width?: number;
31
+ height?: number;
32
+ aspect?: number;
33
+ };
34
+ /**
35
+ * 画布定界:
36
+ * - SVG:width/height 属性直取;只有 viewBox 时按纵横比配默认宽;都没有则默认宽配 3:2。
37
+ * (内联 SVG 不定界会随视口伸缩——实测坑,必须显式定死。)
38
+ * - HTML:宽 = 请求宽或默认宽,高渲染后实测。
39
+ * 全部经 maxWidth 与 maxPixels 收口。
40
+ */
41
+ export declare function resolveCanvas(source: string, requestedWidth: number | undefined, caps: DrawCaps): CanvasPlan;
42
+ /**
43
+ * 外壳文档:body 归零边距;SVG 模式用定宽定高容器把根 SVG 撑满(杜绝随视口伸缩);
44
+ * HTML 模式定宽、高度自流。字体栈兜底中文与 emoji(部署机 macOS;模型可在标记内自定覆盖)。
45
+ */
46
+ export declare function buildShell(source: string, mode: SourceMode, width: number, height: number | 'auto'): string;
47
+ /**
48
+ * 动画标记 lint:静态检出已知的"作者级"错误类,作为 warning 返回给模型自纠。
49
+ * 渲染层只能忠实执行写错的动画——已知错误类在这里拦,未知错误类靠检查帧+视觉自检。
50
+ * 规则刻意窄(宁漏勿噪):
51
+ * R1 同一叶子元素同时挂 animateMotion 与位移类 animateTransform(rotate/translate)
52
+ * —— 位移复合,轨迹漂移(本插件首个演示就犯过的错,实证类)。
53
+ * R2 @keyframes 无限循环但 0% 与 100% 声明不一致 —— 循环处跳变。
54
+ */
55
+ export declare function lintAnimationSource(source: string): string[];
56
+ /**
57
+ * 请求放行判定(default-deny):渲染页只允许内联资源。
58
+ * LLM 生成的标记是不可信输入——实测 <script> 会执行、外链会真发请求(SSRF/外泄口),
59
+ * 引擎层禁 JS + 本判定拦一切网络面:只放 about:blank(setContent 的初始导航)与 data:。
60
+ */
61
+ export declare function allowRequest(url: string): boolean;
62
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,QAAQ;IACvB,iCAAiC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,CAAC;AAExC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,UAAU,CAAC;IACjB,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAID;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAyBzD;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAehG;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,QAAQ,GAAG,UAAU,CA4B5G;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAiB3G;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAiC5D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEjD"}
package/dist/plan.js ADDED
@@ -0,0 +1,159 @@
1
+ // ============================================================
2
+ // plan.ts — 渲染计划(纯函数层:源分类 / 画布定界 / 外壳文档 / 请求放行判定)
3
+ //
4
+ // 引擎是无头浏览器,SVG 与 HTML 对它是同一件事——真正需要区分的只有
5
+ // 「画布怎么定界」:SVG 有 viewBox/width 的精确尺寸语义,直接算;
6
+ // HTML 交给排版引擎流式布局,宽度取参数、高度渲染后实测。
7
+ // 本文件零 IO 零浏览器依赖,全部可单测。
8
+ // ============================================================
9
+ const clampInt = (v, lo, hi) => Math.max(lo, Math.min(hi, Math.floor(v)));
10
+ /**
11
+ * 源分类:剥掉 BOM/空白/XML 序言/DOCTYPE/前导注释后,根元素是 <svg> 即 SVG 文档,
12
+ * 否则一律按 HTML 片段处理(含"HTML 里嵌 SVG"的混合写法)。
13
+ */
14
+ export function classifySource(source) {
15
+ let s = source.replace(/^/, '');
16
+ for (;;) {
17
+ const t = s.trimStart();
18
+ if (t.startsWith('<!--')) {
19
+ const end = t.indexOf('-->');
20
+ if (end < 0)
21
+ break;
22
+ s = t.slice(end + 3);
23
+ continue;
24
+ }
25
+ if (/^<\?xml/i.test(t)) {
26
+ const end = t.indexOf('?>');
27
+ if (end < 0)
28
+ break;
29
+ s = t.slice(end + 2);
30
+ continue;
31
+ }
32
+ if (/^<!doctype/i.test(t)) {
33
+ const end = t.indexOf('>');
34
+ if (end < 0)
35
+ break;
36
+ s = t.slice(end + 1);
37
+ continue;
38
+ }
39
+ return /^<svg[\s/>]/i.test(t) ? 'svg' : 'html';
40
+ }
41
+ return 'html';
42
+ }
43
+ /** 从 SVG 根元素上解析尺寸语义(width/height 属性优先,viewBox 兜底)。 */
44
+ export function parseSvgCanvas(svg) {
45
+ const root = svg.match(/<svg[^>]*>/i)?.[0] ?? '';
46
+ const num = (attr) => {
47
+ // 只认纯数字与 px(em/%/pt 等相对单位对画布定界无意义,忽略走 viewBox)
48
+ const m = root.match(new RegExp(`\\b${attr}\\s*=\\s*["']\\s*([0-9.]+)\\s*(?:px)?\\s*["']`, 'i'));
49
+ const v = m ? Number(m[1]) : Number.NaN;
50
+ return Number.isFinite(v) && v > 0 ? v : undefined;
51
+ };
52
+ const width = num('width');
53
+ const height = num('height');
54
+ const vb = root.match(/\bviewBox\s*=\s*["']\s*([-0-9.]+)[\s,]+([-0-9.]+)[\s,]+([0-9.]+)[\s,]+([0-9.]+)\s*["']/i);
55
+ const vbW = vb ? Number(vb[3]) : Number.NaN;
56
+ const vbH = vb ? Number(vb[4]) : Number.NaN;
57
+ const aspect = Number.isFinite(vbW) && Number.isFinite(vbH) && vbW > 0 && vbH > 0 ? vbH / vbW : undefined;
58
+ return { width, height, aspect };
59
+ }
60
+ /**
61
+ * 画布定界:
62
+ * - SVG:width/height 属性直取;只有 viewBox 时按纵横比配默认宽;都没有则默认宽配 3:2。
63
+ * (内联 SVG 不定界会随视口伸缩——实测坑,必须显式定死。)
64
+ * - HTML:宽 = 请求宽或默认宽,高渲染后实测。
65
+ * 全部经 maxWidth 与 maxPixels 收口。
66
+ */
67
+ export function resolveCanvas(source, requestedWidth, caps) {
68
+ const mode = classifySource(source);
69
+ const reqW = requestedWidth && requestedWidth > 0 ? requestedWidth : undefined;
70
+ let width;
71
+ let height;
72
+ if (mode === 'svg') {
73
+ const c = parseSvgCanvas(source);
74
+ if (c.width && c.height) {
75
+ width = clampInt(reqW ?? c.width, 16, caps.maxWidth);
76
+ height = Math.max(16, Math.round(width * (c.height / c.width)));
77
+ }
78
+ else {
79
+ const aspect = c.aspect ?? 2 / 3;
80
+ width = clampInt(reqW ?? caps.defaultWidth, 16, caps.maxWidth);
81
+ height = Math.max(16, Math.round(width * aspect));
82
+ }
83
+ // 总像素收口:等比缩小
84
+ if (width * height > caps.maxPixels) {
85
+ const k = Math.sqrt(caps.maxPixels / (width * height));
86
+ width = Math.max(16, Math.floor(width * k));
87
+ height = Math.max(16, Math.floor(height * k));
88
+ }
89
+ }
90
+ else {
91
+ width = clampInt(reqW ?? caps.defaultWidth, 16, caps.maxWidth);
92
+ height = 'auto';
93
+ }
94
+ return { mode, width, height, html: buildShell(source, mode, width, height) };
95
+ }
96
+ /**
97
+ * 外壳文档:body 归零边距;SVG 模式用定宽定高容器把根 SVG 撑满(杜绝随视口伸缩);
98
+ * HTML 模式定宽、高度自流。字体栈兜底中文与 emoji(部署机 macOS;模型可在标记内自定覆盖)。
99
+ */
100
+ export function buildShell(source, mode, width, height) {
101
+ const base = 'html,body{margin:0;padding:0}' +
102
+ 'body{font-family:"PingFang SC","Hiragino Sans GB","Noto Sans CJK SC","Apple Color Emoji",sans-serif}';
103
+ if (mode === 'svg') {
104
+ return (`<!DOCTYPE html><html><head><meta charset="utf-8"><style>${base}` +
105
+ `#aalis-draw{width:${width}px;height:${height}px;overflow:hidden}` +
106
+ `#aalis-draw>svg{display:block;width:100%;height:100%}</style></head>` +
107
+ `<body><div id="aalis-draw">${source}</div></body></html>`);
108
+ }
109
+ return (`<!DOCTYPE html><html><head><meta charset="utf-8"><style>${base}` +
110
+ `#aalis-draw{width:${width}px}</style></head>` +
111
+ `<body><div id="aalis-draw">${source}</div></body></html>`);
112
+ }
113
+ /**
114
+ * 动画标记 lint:静态检出已知的"作者级"错误类,作为 warning 返回给模型自纠。
115
+ * 渲染层只能忠实执行写错的动画——已知错误类在这里拦,未知错误类靠检查帧+视觉自检。
116
+ * 规则刻意窄(宁漏勿噪):
117
+ * R1 同一叶子元素同时挂 animateMotion 与位移类 animateTransform(rotate/translate)
118
+ * —— 位移复合,轨迹漂移(本插件首个演示就犯过的错,实证类)。
119
+ * R2 @keyframes 无限循环但 0% 与 100% 声明不一致 —— 循环处跳变。
120
+ */
121
+ export function lintAnimationSource(source) {
122
+ const warnings = [];
123
+ // R1:叶子图形元素(不含嵌套容器,避免"g 下两个子元素各挂一种"的误报)
124
+ const leafRe = /<(circle|rect|ellipse|path|polygon|polyline|line|text|image|use)\b[^>]*>([\s\S]*?)<\/\1>/gi;
125
+ for (const m of source.matchAll(leafRe)) {
126
+ const inner = m[2];
127
+ const hasMotion = /<animateMotion\b/i.test(inner);
128
+ const hasShift = /<animateTransform\b[^>]*type\s*=\s*["'](?:rotate|translate)["']/i.test(inner);
129
+ if (hasMotion && hasShift) {
130
+ warnings.push(`<${m[1]}> 同时挂了 animateMotion 与位移类 animateTransform——两者位移会复合导致轨迹漂移,` +
131
+ '沿路径运动只保留 animateMotion 即可');
132
+ break; // 同类只报一次
133
+ }
134
+ }
135
+ // R2:无限循环 keyframes 首尾不一致
136
+ const kfRe = /@keyframes\s+([\w-]+)\s*\{([\s\S]*?)\}\s*(?=@|\.|<|$)/g;
137
+ const norm = (v) => v.replace(/\s+/g, '').replace(/;$/, '');
138
+ for (const m of source.matchAll(kfRe)) {
139
+ const nameUsedInfinite = new RegExp(`animation[^;]*\\b${m[1]}\\b[^;]*infinite`, 'i').test(source);
140
+ if (!nameUsedInfinite)
141
+ continue;
142
+ const first = m[2].match(/(?:^|\})\s*(?:0%|from)\s*\{([^}]*)\}/);
143
+ const last = m[2].match(/(?:^|\})\s*(?:100%|to)\s*\{([^}]*)\}/);
144
+ if (first && last && norm(first[1]) !== norm(last[1])) {
145
+ warnings.push(`@keyframes ${m[1]} 是无限循环但 0% 与 100% 状态不一致——循环处会跳变,无缝循环需首尾一致`);
146
+ break;
147
+ }
148
+ }
149
+ return warnings;
150
+ }
151
+ /**
152
+ * 请求放行判定(default-deny):渲染页只允许内联资源。
153
+ * LLM 生成的标记是不可信输入——实测 <script> 会执行、外链会真发请求(SSRF/外泄口),
154
+ * 引擎层禁 JS + 本判定拦一切网络面:只放 about:blank(setContent 的初始导航)与 data:。
155
+ */
156
+ export function allowRequest(url) {
157
+ return url === 'about:blank' || url.startsWith('data:');
158
+ }
159
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@aalis/plugin-draw",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/AalisLabs/Aalis.git",
8
+ "directory": "packages/plugin-draw"
9
+ },
10
+ "author": "Ace Nyan <ace@acenyan.com>",
11
+ "bugs": {
12
+ "url": "https://github.com/AalisLabs/Aalis/issues"
13
+ },
14
+ "homepage": "https://github.com/AalisLabs/Aalis#readme",
15
+ "description": "让纯文本模型画图:SVG/HTML 标记渲染为图片与 GIF 动图(无头浏览器 + ffmpeg)",
16
+ "type": "module",
17
+ "keywords": [
18
+ "aalis",
19
+ "aalis-plugin"
20
+ ],
21
+ "main": "dist/index.js",
22
+ "types": "dist/index.d.ts",
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "dependencies": {
27
+ "@aalis/schema-config": ">=0.11.0 <1.0.0",
28
+ "@aalis/api-process": ">=0.5.1 <1.0.0",
29
+ "@aalis/api-storage": ">=0.5.5 <1.0.0",
30
+ "@aalis/api-tools": ">=0.6.0 <1.0.0",
31
+ "puppeteer": "^24.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^22.0.0",
35
+ "typescript": "^5.7.0",
36
+ "@aalis/core": "0.12.0"
37
+ },
38
+ "peerDependencies": {
39
+ "@aalis/core": ">=0.12.0 <1.0.0"
40
+ },
41
+ "aalis": {
42
+ "service": {
43
+ "optional": [
44
+ "tools",
45
+ "storage",
46
+ "process"
47
+ ]
48
+ }
49
+ },
50
+ "scripts": {
51
+ "build": "tsc",
52
+ "dev": "tsc --watch"
53
+ }
54
+ }