dsh-drawai 0.4.3 → 0.4.4
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 +5 -4
- package/lib/client.js +142 -16
- package/lib/index.js +170 -2
- package/lib/motion/mgstyles/briefs/23-ink-wash.md +147 -0
- package/lib/motion/mgstyles/catalog.d/23-ink-wash.json +28 -0
- package/lib/motion/mgstyles/catalog.json +127 -14
- package/lib/motion/mgstyles/upstream/demos/23-ink-wash/brush.js +149 -0
- package/lib/motion/mgstyles/upstream/demos/23-ink-wash/film.js +202 -0
- package/lib/motion/mgstyles/upstream/manifest.json +28 -3
- package/lib/motion/mgstyles/upstream/prompts/23-ink-wash.md +59 -0
- package/lib/motion/project.js +5 -1
- package/lib/motion/qa.js +116 -6
- package/lib/motion/scene-kit.js +1 -1
- package/lib/motion-library.js +1 -1
- package/lib/svg-export.js +1344 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -24,10 +24,10 @@ DrawAI 在右侧栏提供画布,也向对话中的模型提供读图、改图
|
|
|
24
24
|
|
|
25
25
|
```powershell
|
|
26
26
|
# DeepSeek Harness 桌面版
|
|
27
|
-
dsh plugin --profile desktop add dsh-drawai@0.4.
|
|
27
|
+
dsh plugin --profile desktop add dsh-drawai@0.4.4
|
|
28
28
|
|
|
29
29
|
# 使用 dsh web 时,安装到 web profile
|
|
30
|
-
dsh plugin --profile web add dsh-drawai@0.4.
|
|
30
|
+
dsh plugin --profile web add dsh-drawai@0.4.4
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
桌面版和 Web 分别管理插件,只需运行需要的命令。安装完成后**完全重启对应的宿主**,然后在右侧栏找到「DrawAI 画布」。
|
|
@@ -44,6 +44,7 @@ dsh plugin --profile desktop update dsh-drawai@latest
|
|
|
44
44
|
> 0.4.1 将视频渲染依赖放到独立运行环境,安装插件时无需为 esbuild 授权构建脚本。首次使用某些视频功能时需要联网初始化,详见下文。
|
|
45
45
|
> 0.4.2 修了画布工具注册、渲染预览一直 pending、子目录画布打不开、整片风格 demo 缺依赖等一批问题,详见 [发布说明](docs/release.md)。
|
|
46
46
|
> 0.4.3 把内嵌技能拆成「画布」与「视频」两条,做视频不必先被读写画布那句挡住,详见 [发布说明](docs/release.md)。
|
|
47
|
+
> 0.4.4 新增 `diagram_export`,没有画布侧栏的通道也能在宿主侧导成 SVG / PNG;同时修了拐弯连线在预览里糊成黑块,整片风格增至 15 种,详见 [发布说明](docs/release.md)。
|
|
47
48
|
|
|
48
49
|
## 第一次使用
|
|
49
50
|
|
|
@@ -63,7 +64,7 @@ dsh plugin --profile desktop update dsh-drawai@latest
|
|
|
63
64
|
|
|
64
65
|
画布支持形状、独立文字、表格、SVG 和位图节点,以及直线、折线、曲线、自环和悬空连线。可用多选、图层、顺序、对齐和页面切换整理内容;双击文字或表格编辑。
|
|
65
66
|
|
|
66
|
-
模型通过 `diagram_read` 读取结构、样式、选区与文件版本,通过 `diagram_apply` 增删或修改节点和边。对于并发编辑,`expectRevision`
|
|
67
|
+
模型通过 `diagram_read` 读取结构、样式、选区与文件版本,通过 `diagram_apply` 增删或修改节点和边。对于并发编辑,`expectRevision` 可在文件版本不匹配时阻止写入。要成品图时用 `diagram_export` 在宿主侧直接导成 SVG / PNG —— 不需要打开画布,QQ、CLI 这类没有侧栏的通道也能用。
|
|
67
68
|
|
|
68
69
|
例如可以继续对话:
|
|
69
70
|
|
|
@@ -137,7 +138,7 @@ FFmpeg 按显式路径、系统 PATH、缓存及自动下载的顺序查找,
|
|
|
137
138
|
| 文档 | 内容 |
|
|
138
139
|
|---|---|
|
|
139
140
|
| [原生画布与兼容说明](docs/native-canvas.md) | `.drawai`、媒体目录与 `.drawio` 迁移 |
|
|
140
|
-
| [画布 AI 工具参考](docs/diagram-tools.md) | `diagram_read`、`diagram_apply` 和图片矢量化 |
|
|
141
|
+
| [画布 AI 工具参考](docs/diagram-tools.md) | `diagram_read`、`diagram_apply`、`diagram_export` 和图片矢量化 |
|
|
141
142
|
| [视频节点说明](docs/video-nodes.md) | 视频导入、复刻、后台任务和导出 |
|
|
142
143
|
| [动效视频说明](docs/motion-video.md) | 分镜、工程、素材、预览、渲染与回填 |
|
|
143
144
|
| [声音制作说明](docs/motion-audio.md) | 音轨、音效、旁白和节拍 |
|
package/lib/client.js
CHANGED
|
@@ -2201,7 +2201,7 @@ window.__ModuleLoader__.load({
|
|
|
2201
2201
|
"source": "https://github.com/Vincentwei1021/mg-styles-15",
|
|
2202
2202
|
"commit": "49052d80bfaf9fa8412cc9710530e5e95a3b8d8c",
|
|
2203
2203
|
"license": "MIT",
|
|
2204
|
-
"note": "整片风格库:
|
|
2204
|
+
"note": "整片风格库:15 种 MG 视觉语言,每种由一份上游提示词 + 一支上游成片源码 + 一份 Remotion 移植 brief 组成。上游的 04-3d-render(Blender 离线渲的 CGI 质感)没有收录,需要那种质感请走外部渲染回灌。",
|
|
2205
2205
|
"promptChars": 1258,
|
|
2206
2206
|
"styles": [
|
|
2207
2207
|
{
|
|
@@ -2293,7 +2293,8 @@ window.__ModuleLoader__.load({
|
|
|
2293
2293
|
"files": [
|
|
2294
2294
|
"demos/01-flat-vector/index.html"
|
|
2295
2295
|
]
|
|
2296
|
-
}
|
|
2296
|
+
},
|
|
2297
|
+
"origin": null
|
|
2297
2298
|
},
|
|
2298
2299
|
{
|
|
2299
2300
|
"slug": "02-line-art",
|
|
@@ -2386,7 +2387,8 @@ window.__ModuleLoader__.load({
|
|
|
2386
2387
|
"files": [
|
|
2387
2388
|
"demos/02-line-art/film.js"
|
|
2388
2389
|
]
|
|
2389
|
-
}
|
|
2390
|
+
},
|
|
2391
|
+
"origin": null
|
|
2390
2392
|
},
|
|
2391
2393
|
{
|
|
2392
2394
|
"slug": "03-isometric",
|
|
@@ -2500,7 +2502,8 @@ window.__ModuleLoader__.load({
|
|
|
2500
2502
|
"demos/03-isometric/js/ui.js",
|
|
2501
2503
|
"demos/03-isometric/js/world.js"
|
|
2502
2504
|
]
|
|
2503
|
-
}
|
|
2505
|
+
},
|
|
2506
|
+
"origin": null
|
|
2504
2507
|
},
|
|
2505
2508
|
{
|
|
2506
2509
|
"slug": "05-cel-boil",
|
|
@@ -2595,7 +2598,8 @@ window.__ModuleLoader__.load({
|
|
|
2595
2598
|
"files": [
|
|
2596
2599
|
"demos/05-cel-boil/js/scene.js"
|
|
2597
2600
|
]
|
|
2598
|
-
}
|
|
2601
|
+
},
|
|
2602
|
+
"origin": null
|
|
2599
2603
|
},
|
|
2600
2604
|
{
|
|
2601
2605
|
"slug": "06-collage",
|
|
@@ -2689,7 +2693,8 @@ window.__ModuleLoader__.load({
|
|
|
2689
2693
|
"files": [
|
|
2690
2694
|
"demos/06-collage/index.html"
|
|
2691
2695
|
]
|
|
2692
|
-
}
|
|
2696
|
+
},
|
|
2697
|
+
"origin": null
|
|
2693
2698
|
},
|
|
2694
2699
|
{
|
|
2695
2700
|
"slug": "07-liquid",
|
|
@@ -2796,7 +2801,8 @@ window.__ModuleLoader__.load({
|
|
|
2796
2801
|
"demos/07-liquid/js/shaders.js",
|
|
2797
2802
|
"demos/07-liquid/js/type.js"
|
|
2798
2803
|
]
|
|
2799
|
-
}
|
|
2804
|
+
},
|
|
2805
|
+
"origin": null
|
|
2800
2806
|
},
|
|
2801
2807
|
{
|
|
2802
2808
|
"slug": "08-morph",
|
|
@@ -2892,7 +2898,8 @@ window.__ModuleLoader__.load({
|
|
|
2892
2898
|
"files": [
|
|
2893
2899
|
"demos/08-morph/index.html"
|
|
2894
2900
|
]
|
|
2895
|
-
}
|
|
2901
|
+
},
|
|
2902
|
+
"origin": null
|
|
2896
2903
|
},
|
|
2897
2904
|
{
|
|
2898
2905
|
"slug": "09-bauhaus",
|
|
@@ -2984,7 +2991,8 @@ window.__ModuleLoader__.load({
|
|
|
2984
2991
|
"files": [
|
|
2985
2992
|
"demos/09-bauhaus/film.js"
|
|
2986
2993
|
]
|
|
2987
|
-
}
|
|
2994
|
+
},
|
|
2995
|
+
"origin": null
|
|
2988
2996
|
},
|
|
2989
2997
|
{
|
|
2990
2998
|
"slug": "10-synthwave",
|
|
@@ -3076,7 +3084,8 @@ window.__ModuleLoader__.load({
|
|
|
3076
3084
|
"files": [
|
|
3077
3085
|
"demos/10-synthwave/index.html"
|
|
3078
3086
|
]
|
|
3079
|
-
}
|
|
3087
|
+
},
|
|
3088
|
+
"origin": null
|
|
3080
3089
|
},
|
|
3081
3090
|
{
|
|
3082
3091
|
"slug": "12-aurora-glass",
|
|
@@ -3172,7 +3181,8 @@ window.__ModuleLoader__.load({
|
|
|
3172
3181
|
"demos/12-aurora-glass/main.js",
|
|
3173
3182
|
"demos/12-aurora-glass/shaders.js"
|
|
3174
3183
|
]
|
|
3175
|
-
}
|
|
3184
|
+
},
|
|
3185
|
+
"origin": null
|
|
3176
3186
|
},
|
|
3177
3187
|
{
|
|
3178
3188
|
"slug": "18-hanazi",
|
|
@@ -3263,7 +3273,8 @@ window.__ModuleLoader__.load({
|
|
|
3263
3273
|
"files": [
|
|
3264
3274
|
"demos/18-hanazi/index.html"
|
|
3265
3275
|
]
|
|
3266
|
-
}
|
|
3276
|
+
},
|
|
3277
|
+
"origin": null
|
|
3267
3278
|
},
|
|
3268
3279
|
{
|
|
3269
3280
|
"slug": "19-paperclip",
|
|
@@ -3353,7 +3364,8 @@ window.__ModuleLoader__.load({
|
|
|
3353
3364
|
"files": [
|
|
3354
3365
|
"demos/19-paperclip/index.html"
|
|
3355
3366
|
]
|
|
3356
|
-
}
|
|
3367
|
+
},
|
|
3368
|
+
"origin": null
|
|
3357
3369
|
},
|
|
3358
3370
|
{
|
|
3359
3371
|
"slug": "20-pixel",
|
|
@@ -3442,7 +3454,8 @@ window.__ModuleLoader__.load({
|
|
|
3442
3454
|
"files": [
|
|
3443
3455
|
"demos/20-pixel/film.js"
|
|
3444
3456
|
]
|
|
3445
|
-
}
|
|
3457
|
+
},
|
|
3458
|
+
"origin": null
|
|
3446
3459
|
},
|
|
3447
3460
|
{
|
|
3448
3461
|
"slug": "22-hud",
|
|
@@ -3549,6 +3562,106 @@ window.__ModuleLoader__.load({
|
|
|
3549
3562
|
"demos/22-hud/js/util.js",
|
|
3550
3563
|
"demos/22-hud/js/v5.js"
|
|
3551
3564
|
]
|
|
3565
|
+
},
|
|
3566
|
+
"origin": null
|
|
3567
|
+
},
|
|
3568
|
+
{
|
|
3569
|
+
"slug": "23-ink-wash",
|
|
3570
|
+
"name": "水墨写意",
|
|
3571
|
+
"en": "Chinese Ink Wash / Xieyi",
|
|
3572
|
+
"heading": "水墨写意 Chinese Ink Wash / Xieyi",
|
|
3573
|
+
"oneLiner": "宣纸是底、墨只加不铺:一笔一笔写出窗、竹、猫与人,墨分五色,留白是挖出来的,钤印收尾。",
|
|
3574
|
+
"shot": "huashu 17_ink · 1920×1080 · 30 fps · 10 秒 · 单镜写意房间",
|
|
3575
|
+
"tags": [
|
|
3576
|
+
"水墨",
|
|
3577
|
+
"写意",
|
|
3578
|
+
"国风",
|
|
3579
|
+
"宣纸",
|
|
3580
|
+
"毛笔",
|
|
3581
|
+
"墨分五色",
|
|
3582
|
+
"留白",
|
|
3583
|
+
"八大山人",
|
|
3584
|
+
"齐白石",
|
|
3585
|
+
"ink-wash",
|
|
3586
|
+
"xieyi",
|
|
3587
|
+
"chinese-ink"
|
|
3588
|
+
],
|
|
3589
|
+
"signature": [
|
|
3590
|
+
"纸是整幅唯一的底:#efe6d0 宣纸 + 2600 根纸纤维 + 四角暗角 α.22,没有纯白也没有纯黑背景",
|
|
3591
|
+
"每一笔都是毛笔:4px 等弧长重采样,笔压起收,逐根笔毛按噪声断开出飞白,笔尾比笔头细 30–50%",
|
|
3592
|
+
"墨只用一个色 [16,14,12],靠 tone 分五色(焦 .9/浓 .8/重 .6/淡 .35/清 .15),全片不出现第二种黑",
|
|
3593
|
+
"晕染是两遍 multiply(外晕 halo·(0.4+grow) + 本体 blur·(1.6−0.6grow)),grow 0→1 是「洇开」不是「淡入」",
|
|
3594
|
+
"留白用 destination-out 从色层里挖掉露出纸色,不是画白色;钤印 1.5→1 倍压下去、multiply 盖上"
|
|
3595
|
+
],
|
|
3596
|
+
"palette": {
|
|
3597
|
+
"page": "#EFE6D0",
|
|
3598
|
+
"surface": "#F7F1E2",
|
|
3599
|
+
"field": "#6F8AA0",
|
|
3600
|
+
"text": "#14120F",
|
|
3601
|
+
"muted": "#8A7C64",
|
|
3602
|
+
"accent": "#C4281E",
|
|
3603
|
+
"border": "#C9B58C"
|
|
3604
|
+
},
|
|
3605
|
+
"paletteSource": "brief",
|
|
3606
|
+
"colors": [
|
|
3607
|
+
"#EFE6D0",
|
|
3608
|
+
"#F7F1E2",
|
|
3609
|
+
"#14120F",
|
|
3610
|
+
"#C4281E",
|
|
3611
|
+
"#D8553F",
|
|
3612
|
+
"#C47A3C",
|
|
3613
|
+
"#6F8AA0",
|
|
3614
|
+
"#E2AA34"
|
|
3615
|
+
],
|
|
3616
|
+
"fonts": {
|
|
3617
|
+
"display": "\"Long Cang\",\"Noto Serif SC\",serif",
|
|
3618
|
+
"text": "\"Noto Serif SC\",\"HarmonyOS Sans SC\",serif",
|
|
3619
|
+
"mono": "\"IBM Plex Mono\",monospace"
|
|
3620
|
+
},
|
|
3621
|
+
"rhythm": {
|
|
3622
|
+
"bpm": 60,
|
|
3623
|
+
"fps": 30,
|
|
3624
|
+
"duration": 10,
|
|
3625
|
+
"aspect": "16:9",
|
|
3626
|
+
"cutFrames": 30
|
|
3627
|
+
},
|
|
3628
|
+
"prompt": "prompts/23-ink-wash.md",
|
|
3629
|
+
"demos": [
|
|
3630
|
+
"demos/23-ink-wash/film.js",
|
|
3631
|
+
"demos/23-ink-wash/brush.js"
|
|
3632
|
+
],
|
|
3633
|
+
"brief": "briefs/23-ink-wash.md",
|
|
3634
|
+
"engine": "canvas2d",
|
|
3635
|
+
"engines": [
|
|
3636
|
+
"canvas2d"
|
|
3637
|
+
],
|
|
3638
|
+
"engineNote": "纯 Canvas2D:宣纸纤维、笔毛飞白、墨晕两遍 multiply 全在 2D 画布上画,没有 shader,也没有 DOM 或贴图合成。",
|
|
3639
|
+
"engineFiles": [
|
|
3640
|
+
"demos/23-ink-wash/brush.js"
|
|
3641
|
+
],
|
|
3642
|
+
"motion": {
|
|
3643
|
+
"motionBlur": null,
|
|
3644
|
+
"transitions": "cut",
|
|
3645
|
+
"impact": false,
|
|
3646
|
+
"camera": false,
|
|
3647
|
+
"grain": {
|
|
3648
|
+
"size": null
|
|
3649
|
+
},
|
|
3650
|
+
"secondary": [],
|
|
3651
|
+
"feather": false,
|
|
3652
|
+
"files": [
|
|
3653
|
+
"demos/23-ink-wash/film.js"
|
|
3654
|
+
]
|
|
3655
|
+
},
|
|
3656
|
+
"origin": {
|
|
3657
|
+
"source": "https://github.com/alchaincyf/huashu-art-motion",
|
|
3658
|
+
"license": "MIT",
|
|
3659
|
+
"files": {
|
|
3660
|
+
"prompts/23-ink-wash.md": "references/风格配方/17_ink.md",
|
|
3661
|
+
"demos/23-ink-wash/film.js": "scripts/engine/scenes/17_ink.js",
|
|
3662
|
+
"demos/23-ink-wash/brush.js": "scripts/engine/lib/brush.js"
|
|
3663
|
+
},
|
|
3664
|
+
"note": "场景为节选(少女段落未收),brush.js 逐字收录;依赖上游全局 U/KIT/PAINT,只作阅读参考。"
|
|
3552
3665
|
}
|
|
3553
3666
|
}
|
|
3554
3667
|
]
|
|
@@ -5191,7 +5304,7 @@ window.__ModuleLoader__.load({
|
|
|
5191
5304
|
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', height: '100%' } },
|
|
5192
5305
|
React.createElement('header', { style: { padding: '20px 24px', borderBottom: '1px solid #8883', display: 'flex', justifyContent: 'space-between', gap: 16 } },
|
|
5193
5306
|
React.createElement('div', null, React.createElement('strong', { style: { fontSize: 20 } }, props.replaceId ? '换一种整片风格' : '整片风格库'),
|
|
5194
|
-
React.createElement('div', { style: { fontSize: 12, opacity: .65, marginTop: 6 } }, '
|
|
5307
|
+
React.createElement('div', { style: { fontSize: 12, opacity: .65, marginTop: 6 } }, '15 种 MG 视觉语言 · 一种风格管一条片子的色板、字体、节奏与动效语法 · 不是单镜动效')),
|
|
5195
5308
|
button('关闭', props.onClose)),
|
|
5196
5309
|
React.createElement('div', { style: { display: 'flex', padding: '16px 24px', gap: 12 } },
|
|
5197
5310
|
React.createElement('input', { autoFocus: true, 'aria-label': '搜索风格', placeholder: '搜索风格,例如:包豪斯、像素、HUD、拼贴', value: query,
|
|
@@ -11697,7 +11810,20 @@ window.__ModuleLoader__.load({
|
|
|
11697
11810
|
}
|
|
11698
11811
|
return props
|
|
11699
11812
|
}
|
|
11700
|
-
|
|
11813
|
+
// 命中带的观感必须**内联**,不能只写在注入页面的 CSS(.drawai-edge-hit)里:
|
|
11814
|
+
// 同一棵树除了画进 DOM,还要被序列化成独立 SVG 去光栅化("看一眼"的预览图、
|
|
11815
|
+
// 导出 PNG/SVG),而 SVG-as-image 不继承宿主页面的 CSS —— 缺省 fill 是黑的。
|
|
11816
|
+
// 直线段的填充面积为 0,看不出来;**只要路径拐了弯**,围出来的那块就变成实心多边形
|
|
11817
|
+
// (issue #32:预览里连线糊成一条黑色色带,线本身还在,只是被压在黑块底下)。
|
|
11818
|
+
// stroke 也不能省:命中的是这条 12px 的透明带子,靠 CSS 的话它就又退回 1px 实心黑线。
|
|
11819
|
+
const hitProps = edgeFace({
|
|
11820
|
+
key: 'edge-hit-' + i,
|
|
11821
|
+
className: 'drawai-edge-hit',
|
|
11822
|
+
d: pathOf(pts, 6, edgeCurved),
|
|
11823
|
+
fill: 'none',
|
|
11824
|
+
stroke: 'transparent',
|
|
11825
|
+
strokeWidth: 12,
|
|
11826
|
+
})
|
|
11701
11827
|
children.push(React.createElement('path', hitProps))
|
|
11702
11828
|
// 连线的画法全部存在文档的 style 键里:dashed/dashPattern 决定线型、endArrow/startArrow
|
|
11703
11829
|
// 决定箭头、strokeColor 决定颜色、rounded 决定拐角是否圆滑 —— 与 drawio 一致,缺省即"不画"。
|
package/lib/index.js
CHANGED
|
@@ -36,6 +36,7 @@ import { parseCanvas as parseMxfile, applyCanvas as applyDocToMxfile, buildCanva
|
|
|
36
36
|
import { emptyDocument, normalizeDocument } from './document-core.js'
|
|
37
37
|
import { createDocumentCommands } from './document-commands.js'
|
|
38
38
|
import { createOrthogonalRouter } from './routing/router.js'
|
|
39
|
+
import { buildExportSvg, rasterizeSvgToPng } from './svg-export.js'
|
|
39
40
|
import { getRoutingRuntime } from './routing/node-runtime.js'
|
|
40
41
|
import { motionInput, motionInputKey, assertMotionEditable, applyMotionResult, isMotionNode, motionNodeData, motionSettings, mergeMotionSettings, isAudioNode, audioNodeData, audioNodeSettings, motionReferenceEdges, motionReferenceRole, unlinkedShotCards } from './motion-core.js'
|
|
41
42
|
import { createAudioService } from './audio-service.js'
|
|
@@ -4310,6 +4311,171 @@ function sanitizeNewName(raw) {
|
|
|
4310
4311
|
return { ok: true, name: withExt }
|
|
4311
4312
|
}
|
|
4312
4313
|
|
|
4314
|
+
/**
|
|
4315
|
+
* `diagram_export`:宿主侧把画布直接导成 SVG / PNG —— **不需要浏览器那一半**。
|
|
4316
|
+
*
|
|
4317
|
+
* 为什么单独开一个工具,而不是给 `diagram_read {render:true}` 加 fallback:
|
|
4318
|
+
* `look` 状态里的 "ready" 含义是"浏览器真渲染过",它的用途是看压线/重叠/观感;
|
|
4319
|
+
* 把宿主这条简化渲染混进同一个状态,模型会拿它当"看过图了"的依据去下结论。
|
|
4320
|
+
* issue #23 也说得很清楚:不改现有"画布优先"的行为,只补一条没有画布时的交付路径。
|
|
4321
|
+
*
|
|
4322
|
+
* 所以两条路分开:能开画布 → `render:true`;开不了(QQ/CLI)→ 这个工具出成品图。
|
|
4323
|
+
*/
|
|
4324
|
+
const exportTool = defineTool({
|
|
4325
|
+
name: 'diagram_export',
|
|
4326
|
+
description:
|
|
4327
|
+
'把画布**在宿主侧**导出成成品图:SVG(矢量、零依赖)或 PNG(`@resvg/resvg-js` 栅格化)——' +
|
|
4328
|
+
'**不需要浏览器、不需要用户打开画布**。QQ / CLI 这类没有右侧栏的通道里,想要"把图发给用户"就用它。' +
|
|
4329
|
+
'与 `diagram_read {render:true}` 的分工:render 由浏览器真渲染、保真度最高(判断压线/重叠/观感靠它);' +
|
|
4330
|
+
'这个工具是宿主按文档**重画的近似版本**(返回 fidelity="approximate"),用来交付成品图,别拿它判断像素级观感。' +
|
|
4331
|
+
'几何照同一套规则算:形状/圆角/虚线/箭头、标签换行与"文字撑高节点"、正交走线(用文档里存的折点)、' +
|
|
4332
|
+
'连线标签的落点与"字的位置不画线";但文字量宽是按字符估算的(中文 1em、其余 0.55em),长标签可能差一行;' +
|
|
4333
|
+
'视频/卡片/音频节点只画通用形状(它们的内容在浏览器组件里)。所有降级项都在 warnings 里列出。' +
|
|
4334
|
+
'缺省写到画布旁边(同名,只换扩展名 .svg/.png),可用 out 指定;PNG 的 scale 缺省 2。',
|
|
4335
|
+
parameters: {
|
|
4336
|
+
path: {
|
|
4337
|
+
type: 'string',
|
|
4338
|
+
description: '画布路径(.drawio / .drawai)。不传 = 当前会话聚焦的画布。',
|
|
4339
|
+
},
|
|
4340
|
+
page: {
|
|
4341
|
+
type: 'number',
|
|
4342
|
+
description: '多页画布导哪一页(0 起)。不传 = 用户画布当前页(没上报过则第 1 页)。',
|
|
4343
|
+
},
|
|
4344
|
+
format: {
|
|
4345
|
+
type: 'string',
|
|
4346
|
+
enum: ['svg', 'png'],
|
|
4347
|
+
description:
|
|
4348
|
+
'svg = 矢量(零依赖、可再编辑、放大不糊);png = 位图(发给用户 / 贴进文档 / 进PPT)。缺省 svg。',
|
|
4349
|
+
},
|
|
4350
|
+
scale: {
|
|
4351
|
+
type: 'number',
|
|
4352
|
+
description: 'PNG 的缩放倍数(缺省 2,1 = 1:1)。SVG 忽略这个参数。',
|
|
4353
|
+
},
|
|
4354
|
+
out: {
|
|
4355
|
+
type: 'string',
|
|
4356
|
+
description:
|
|
4357
|
+
'输出文件路径(必须在工作区内;相对路径按**画布所在目录**解析)。缺省 = 与画布同名、只换扩展名。',
|
|
4358
|
+
},
|
|
4359
|
+
theme: {
|
|
4360
|
+
type: 'string',
|
|
4361
|
+
enum: ['light', 'dark'],
|
|
4362
|
+
description: '配色:light 白纸黑线(缺省,适合交付);dark 透明底 + 浅色字线。',
|
|
4363
|
+
},
|
|
4364
|
+
background: {
|
|
4365
|
+
type: 'string',
|
|
4366
|
+
description: '背景色:缺省 light 主题白底、dark 主题透明;也可给 #rrggbb 或 "transparent"。',
|
|
4367
|
+
},
|
|
4368
|
+
padding: {
|
|
4369
|
+
type: 'number',
|
|
4370
|
+
description: '内容四周留白(像素,缺省 24)。',
|
|
4371
|
+
},
|
|
4372
|
+
},
|
|
4373
|
+
output: {
|
|
4374
|
+
schema: {
|
|
4375
|
+
type: 'object',
|
|
4376
|
+
additionalProperties: false,
|
|
4377
|
+
properties: {
|
|
4378
|
+
summary: { type: 'string', required: true },
|
|
4379
|
+
details: { type: 'string', required: true },
|
|
4380
|
+
path: { type: 'string', required: true },
|
|
4381
|
+
absolute: { type: 'string' },
|
|
4382
|
+
format: { type: 'string', required: true },
|
|
4383
|
+
width: { type: 'number', required: true },
|
|
4384
|
+
height: { type: 'number', required: true },
|
|
4385
|
+
bytes: { type: 'number' },
|
|
4386
|
+
fidelity: { type: 'string', required: true },
|
|
4387
|
+
nodes: { type: 'number' },
|
|
4388
|
+
edges: { type: 'number' },
|
|
4389
|
+
warnings: { type: 'array', items: { type: 'string' } },
|
|
4390
|
+
},
|
|
4391
|
+
},
|
|
4392
|
+
render: (args, value) => [{ type: 'text', text: value.summary + '\n' + value.details }],
|
|
4393
|
+
},
|
|
4394
|
+
async execute(args, exec) {
|
|
4395
|
+
const sessionId = sessionIdOf(exec)
|
|
4396
|
+
const path =
|
|
4397
|
+
typeof args.path === 'string' && args.path.trim().length > 0 ? args.path : focusedPathFor(sessionId)
|
|
4398
|
+
if (!path) throw new Error('未指定画布路径且当前会话没有聚焦画布,请传入 path 或先打开目标画布')
|
|
4399
|
+
assertCanvasPath(path)
|
|
4400
|
+
// 导出会往工作区写文件:只读会话里不放行(与其它会写盘的工具同一道闸)。
|
|
4401
|
+
if (policyFor(sessionId)?.mode === 'read-only') throw new Error('当前会话为只读,不能导出文件')
|
|
4402
|
+
const pageArg = Number.isFinite(Number(args.page)) ? Number(args.page) : focusedPageFor(sessionId)
|
|
4403
|
+
const loaded = await loadDoc(path, sessionId, pageArg)
|
|
4404
|
+
if (!loaded.exists) throw new Error('画布不存在:' + loaded.absolute)
|
|
4405
|
+
const format = String(args.format || 'svg').toLowerCase() === 'png' ? 'png' : 'svg'
|
|
4406
|
+
|
|
4407
|
+
const built = buildExportSvg(loaded.doc, {
|
|
4408
|
+
theme: args.theme,
|
|
4409
|
+
background: args.background,
|
|
4410
|
+
padding: args.padding,
|
|
4411
|
+
})
|
|
4412
|
+
if (built.stats.nodes === 0 && built.stats.edges === 0) {
|
|
4413
|
+
throw new Error('画布是空的(0 个节点、0 条连线),没有可导出的内容')
|
|
4414
|
+
}
|
|
4415
|
+
|
|
4416
|
+
// 输出位置:缺省落在画布旁边、同名换扩展名 —— 用户找得到,也不污染工作区根目录。
|
|
4417
|
+
const dir = dirnamePath(loaded.absolute)
|
|
4418
|
+
const base = basenamePath(loaded.absolute).replace(/\.(drawio|drawai)$/i, '')
|
|
4419
|
+
const outArg = typeof args.out === 'string' && args.out.trim().length > 0 ? args.out.trim() : ''
|
|
4420
|
+
const target =
|
|
4421
|
+
outArg.length === 0 ? joinPath(dir, base + '.' + format) : isAbsolute(outArg) ? outArg : resolvePath(dir, outArg)
|
|
4422
|
+
// 导出写的是成品图:out 指到画布文件上等于把图覆盖掉(svg 覆盖 .drawio 之后画布就打不开了)。
|
|
4423
|
+
if (/\.(drawio|drawai)$/i.test(target)) {
|
|
4424
|
+
throw new Error('导出不能覆盖画布文件:' + target + ';out 请用 .svg / .png 结尾')
|
|
4425
|
+
}
|
|
4426
|
+
const root = await ctx.fs.resolve(workspaceRootOf(sessionId))
|
|
4427
|
+
if (!ctx.fs.contains(root, target)) throw new Error('导出路径超出工作区:' + target)
|
|
4428
|
+
await mkdir(dirnamePath(target), { recursive: true })
|
|
4429
|
+
|
|
4430
|
+
let width = built.width
|
|
4431
|
+
let height = built.height
|
|
4432
|
+
let bytes = 0
|
|
4433
|
+
if (format === 'svg') {
|
|
4434
|
+
await writeFile(target, built.svg, 'utf8')
|
|
4435
|
+
bytes = Buffer.byteLength(built.svg, 'utf8')
|
|
4436
|
+
} else {
|
|
4437
|
+
const raster = await rasterizeSvgToPng(built.svg, { scale: args.scale })
|
|
4438
|
+
await writeFile(target, raster.png)
|
|
4439
|
+
width = raster.width
|
|
4440
|
+
height = raster.height
|
|
4441
|
+
bytes = raster.png.length
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4444
|
+
const rel = relativePath(root, target)
|
|
4445
|
+
const warnings = built.warnings.slice(0, 20)
|
|
4446
|
+
const warningCount = built.warnings.length
|
|
4447
|
+
return {
|
|
4448
|
+
summary:
|
|
4449
|
+
'已导出 ' + format.toUpperCase() + ':' + rel + '(' + Math.round(width) + '×' + Math.round(height) + ')' +
|
|
4450
|
+
(warningCount > 0
|
|
4451
|
+
? ',' + warningCount + ' 条降级提示见 details' + (warningCount > warnings.length ? '(只列前 ' + warnings.length + ' 条)' : '')
|
|
4452
|
+
: ''),
|
|
4453
|
+
details: JSON.stringify({
|
|
4454
|
+
path: rel,
|
|
4455
|
+
absolute: target,
|
|
4456
|
+
format: format,
|
|
4457
|
+
width: Math.round(width),
|
|
4458
|
+
height: Math.round(height),
|
|
4459
|
+
bytes: bytes,
|
|
4460
|
+
fidelity: 'approximate',
|
|
4461
|
+
nodes: built.stats.nodes,
|
|
4462
|
+
edges: built.stats.edges,
|
|
4463
|
+
warnings: warnings,
|
|
4464
|
+
}),
|
|
4465
|
+
path: rel,
|
|
4466
|
+
absolute: target,
|
|
4467
|
+
format: format,
|
|
4468
|
+
width: Math.round(width),
|
|
4469
|
+
height: Math.round(height),
|
|
4470
|
+
bytes: bytes,
|
|
4471
|
+
fidelity: 'approximate',
|
|
4472
|
+
nodes: built.stats.nodes,
|
|
4473
|
+
edges: built.stats.edges,
|
|
4474
|
+
warnings: warnings,
|
|
4475
|
+
}
|
|
4476
|
+
},
|
|
4477
|
+
})
|
|
4478
|
+
|
|
4313
4479
|
/**
|
|
4314
4480
|
* 「画布聚焦」:记录每个会话当前**打开着**的画布路径。
|
|
4315
4481
|
*
|
|
@@ -6511,7 +6677,7 @@ function sanitizeNewName(raw) {
|
|
|
6511
6677
|
})))
|
|
6512
6678
|
ctx.effect(() => registerHostTool(defineTool({
|
|
6513
6679
|
name: 'mg_style',
|
|
6514
|
-
description: 'DrawAI 内置的
|
|
6680
|
+
description: 'DrawAI 内置的 15 种整片 MG 动效风格(mg-styles 库):扁平矢量 / 线条动画 / 等轴 2.5D / 逐帧手绘 / 拼贴 / 液态 / 形变 / 包豪斯 / 80s 复古 / 玻璃拟态 / 综艺花字 / 粗描边贴纸科普 / 像素 / 赛博 HUD / 水墨写意。上游那支 04-3d-render(C4D/Blender 质感的 CGI 软着陆)没有收录:画面是 Blender 离线渲的,插件里没有同等质感的等价物——用 CSS 滤镜凑出来的 CGI 只会更假,需要那种质感就外部渲好当素材回灌(motion_assets capture / 参考段)。这是「整片风格」(一种片子的整体视觉语言),与 shotcraft_search 的「单镜动效卡」互补、可同时用。action:"list" 列出全部;"search" 按关键词检索;"read" 读某个风格的施工包(可检验判据 + 色板 + 字体 + 节奏 + 效果配方 + 上游提示词 + Remotion 移植 brief);"source" 读上游成片源码正文;**"frames" 从上游 demo 按 cue 时间点抽参考帧**——动态、转场、冲击、颗粒这些只有看帧才知道,写场景前先取。选中后 diagram_apply 加 shape:"mgstyle" 节点并连到视频节点,motion_project 会把风格写进工程主题与 STYLE.md。',
|
|
6515
6681
|
parameters: { action: { type: 'string', enum: ['list', 'search', 'read', 'source', 'frames'], required: true },
|
|
6516
6682
|
slug: { type: 'string', description: '风格 slug,如 09-bauhaus' },
|
|
6517
6683
|
query: { type: 'string', description: 'search:关键词(中英文风格名、用途、视觉特征)' },
|
|
@@ -6622,6 +6788,8 @@ function sanitizeNewName(raw) {
|
|
|
6622
6788
|
}
|
|
6623
6789
|
})))
|
|
6624
6790
|
ctx.effect(() => registerHostTool(readTool))
|
|
6791
|
+
// 宿主侧导出:没有画布侧栏的通道(QQ/CLI)里,成品图只能这么拿。与 render:true 两条路。
|
|
6792
|
+
ctx.effect(() => registerHostTool(exportTool))
|
|
6625
6793
|
ctx.effect(() => registerHostTool(defineTool({
|
|
6626
6794
|
name: 'motion_task',
|
|
6627
6795
|
description: '画布视频节点的任务记录:list 列出任务、status 查某个任务、cancel 取消、focus 在面板定位、remove 移除记录。开始制作请走 motion_project → 改 scene-*.tsx → motion_preview → motion_render;成片回填节点用 motion_render apply。',
|
|
@@ -7200,7 +7368,7 @@ function sanitizeNewName(raw) {
|
|
|
7200
7368
|
// 帧检:把"片子有没有毛病"变成一张带帧号的清单,而不是靠眼睛。
|
|
7201
7369
|
ctx.effect(() => registerHostTool(defineTool({
|
|
7202
7370
|
name: 'motion_qa',
|
|
7203
|
-
description: '成片帧检:不靠眼睛,靠像素和响度。inspect 立刻检一遍(默认检工程里的 .out/film.mp4,也可以指定 file
|
|
7371
|
+
description: '成片帧检:不靠眼睛,靠像素和响度。inspect 立刻检一遍(默认检工程里的 .out/film.mp4,也可以指定 file):帧数对不对、有没有黑场、有没有静止太久、有没有整镜没在动(动能量,抓缓慢推镜式的翻页感)、有没有只剩底色没内容(边密度,抓空白帧抓不到的渐变空画面)、有没有单帧闪烁、有没有空白帧、画面有没有顶到边、音轨有没有削波。report 读上次的结果。每条问题都带帧号和镜头号,能直接回去改对应场景。',
|
|
7204
7372
|
parameters: {
|
|
7205
7373
|
action: { type: 'string', enum: ['inspect', 'report'], required: true },
|
|
7206
7374
|
path: { type: 'string' }, nodeId: { type: 'string' }, page: { type: 'number' },
|