@ninkch/dsh-infinite-canvas 0.19.5 → 0.19.6
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 -4
- package/README.zh-CN.md +6 -6
- package/assets/canvas-director/SKILL.md +14 -14
- package/assets/canvas-director/forms/interview.md +1 -1
- package/assets/canvas-director/manifest.json +8 -8
- package/assets/canvas-director/pack.js +8 -12
- package/assets/canvas-studio/SKILL.md +2 -2
- package/client.js +67 -37
- package/docs/guide.en.md +5 -5
- package/docs/guide.zh-CN.md +9 -9
- package/index-core-skill-profile.js +2 -6
- package/index.js +10 -25
- package/package.json +1 -1
- package/skill-pack.js +19 -14
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ Assets are shared with the canvas: the characters, trees, furniture and building
|
|
|
96
96
|
|
|
97
97
|
Full walkthrough — asset tabs, gizmo, keyboard map, shots and export: **[User guide → 3D Director Stage](./docs/guide.en.md#8-3d-director-stage)**.
|
|
98
98
|
|
|
99
|
-
## Craft page: director / genre /
|
|
99
|
+
## Craft page: director / genre / spec / look
|
|
100
100
|
|
|
101
101
|
The third tab of the right sidebar decides **how the whole piece is shot**. It is not a prompt template — it is a binding constraint that gets composed into every script, storyboard, image and video generation. **There are no dropdowns on the page**: every option is an icon tile; click to select, click again to clear.
|
|
102
102
|
|
|
@@ -106,12 +106,12 @@ The third tab of the right sidebar decides **how the whole piece is shot**. It i
|
|
|
106
106
|
|---|---|---|---|
|
|
107
107
|
| **Director** | 24 | Camera language, lighting, cutting rhythm, style parameters | It constrains the shot's camera fields, the keyframe, and the video's camera move at once — the least replaceable decision in the whole piece |
|
|
108
108
|
| **Genre** | 21 narrative / 10 sector | Pressure mechanics, audience payoff, scene granules, episode hooks | The same "revenge" premise is fragmented neon under Wong Kar-wai and hard-lit slow motion under Tsui Hark; folding them together poisons both |
|
|
109
|
-
| **
|
|
109
|
+
| **Spec** | 8 | Structure blocks, ASL floor, rhythm, quality bar | It decides "short drama vs cinematic texture vs commercial", which is orthogonal to genre |
|
|
110
110
|
| **Look** | 9 | Imaging base, material, render texture | 2D thick paint / guoman 3D / game CG are imaging domains, not directors; mixing them into the director layer makes "how to cut" fight "what it looks like" |
|
|
111
111
|
|
|
112
112
|
**Directors (24)**: Chinese 8 (Wong Kar-wai, Zhang Yimou, Hou Hsiao-hsien, Bi Gan, Tsui Hark, Lu Yang, Yuen Woo-ping, Wu Ershan) · Japan/Korea 2 (Kurosawa, Park Chan-wook) · Western 14 (Spielberg, Hitchcock, Kubrick, Scorsese, Fellini, Bergman, Tarkovsky, Nolan, Villeneuve, Fincher, Refn, Malick, Michael Mann, Coen Brothers).
|
|
113
113
|
|
|
114
|
-
**
|
|
114
|
+
**Specs (8)**: Short drama · Cinematic texture · Commercial · Documentary · Music video · Trailer · Explainer · Interview.
|
|
115
115
|
|
|
116
116
|
- A **short drama** is a self-contained 60–180 s unit and **covers both single episodes and multi-episode series** — a series is just episode after episode, so there is no separate "series" form.
|
|
117
117
|
- **Cinematic texture** is a *texture* spec, not a feature-length one: it constrains how each shot must be made to read as cinema (ASL floor 1.5 s, 24 fps, motivated light, real materials), and its duration tiers stop at 30–300 s. The reason is practical — **a video model emits only 3–8 s per clip** (5 s by default here), so "a 120-minute film" can only be hundreds of clips stitched together.
|
|
@@ -119,7 +119,7 @@ The third tab of the right sidebar decides **how the whole piece is shot**. It i
|
|
|
119
119
|
|
|
120
120
|
### Aspect ratio is not decided here
|
|
121
121
|
|
|
122
|
-
Vertical, horizontal, square or widescreen all work with any
|
|
122
|
+
Vertical, horizontal, square or widescreen all work with any spec — **aspect and resolution are chosen on the video node at generation time**. Vertical delivery is common for short drama, but that is a distribution choice, not a craft spec; **a short drama can equally be shot horizontal and to a cinematic standard**.
|
|
123
123
|
|
|
124
124
|
### Where it is injected
|
|
125
125
|
|
package/README.zh-CN.md
CHANGED
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
|
|
97
97
|
完整操作(资产页签、操作轴、快捷键表、分镜、导出):**[操作手册 → 3D 导演台](./docs/guide.zh-CN.md#8-3d-导演台)**。
|
|
98
98
|
|
|
99
|
-
## 技能页:导演 / 题材 /
|
|
99
|
+
## 技能页:导演 / 题材 / 规格 / 影调
|
|
100
100
|
|
|
101
101
|
右栏第三个页签「技能」决定**整部片怎么拍**。它不是提示词模板,而是会被自动拼进每一次剧本、分镜、图片、视频生成的硬约束。**页面里没有任何下拉框**,全部是图标瓦片,点一下选中、再点一下取消。
|
|
102
102
|
|
|
@@ -106,12 +106,12 @@
|
|
|
106
106
|
|---|---|---|---|
|
|
107
107
|
| **导演** | 24 | 镜头语言、灯光色彩、剪辑节奏、风格参数 | 它同时约束分镜的机位字段、图片关键帧、视频运镜——整部片最不可替换的决策 |
|
|
108
108
|
| **题材** | 叙事 21 / 行业 10 | 压力机制、观众回报、场面颗粒、集尾钩子 | 同一个「复仇打脸」,王家卫拍是抽帧暧昧,徐克拍是凌厉升格;混进导演层会互相污染 |
|
|
109
|
-
|
|
|
109
|
+
| **规格** | 8 | 结构块、平均镜长下限、节奏、质量档 | 它决定「这是短剧、电影质感还是商业片」,与题材正交 |
|
|
110
110
|
| **影调** | 9 | 成像底层、材质、渲染质感 | 2D 厚涂 / 国漫 3D / 游戏 CG 是成像域,不是导演;塞进导演会让「怎么剪」和「长什么样」打架 |
|
|
111
111
|
|
|
112
112
|
**导演(24 位)**:华语 8(王家卫、张艺谋、侯孝贤、毕赣、徐克、路阳、袁和平、乌尔善)· 日韩 2(黑泽明、朴赞郁)· 欧美 14(斯皮尔伯格、希区柯克、库布里克、斯科塞斯、费里尼、伯格曼、塔可夫斯基、诺兰、维伦纽瓦、芬奇、雷弗恩、马力克、迈克尔·曼、科恩兄弟)。
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
**规格(8 种)**:短剧 · 电影质感 · 商业片 · 纪实 · 音乐视频 · 预告 · 解说 · 访谈。
|
|
115
115
|
|
|
116
116
|
- **短剧**是单集 60–180 秒的自足单元,**同时覆盖单集与连续多集**——一部剧就是一集一集往下做,不需要单独的「连续剧」规格。
|
|
117
117
|
- **电影质感**是**质感规格,不是长片规格**:它约束每一镜怎么做才像电影(ASL 1.5 秒以上、24 帧、动机光、真材质),成片长度档只到 30–300 秒。原因很实际——**AI 视频模型单次只出 3–8 秒**(本插件默认 5 秒),所谓「一部 120 分钟的电影」只能是几百上千个片段接起来。
|
|
@@ -119,14 +119,14 @@
|
|
|
119
119
|
|
|
120
120
|
### 画幅不在技能里决定
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
横屏、竖屏、方屏、宽银幕都能用任何规格——**画幅与分辨率是视频节点生成时的选项**。短剧竖屏投放很常见,但那是发行选择,不是创作规格;**短剧同样可以横屏、可以按电影标准拍**。
|
|
123
123
|
|
|
124
124
|
### 注入到哪
|
|
125
125
|
|
|
126
126
|
| 节点 / 能力 | 拿到的内容 |
|
|
127
127
|
|---|---|
|
|
128
|
-
| 剧本节点 | 题材卡 + 导演的叙事/节奏/表演节 +
|
|
129
|
-
| 分镜表节点 | 导演的镜头语言/灯光/风格参数/拍摄清单 +
|
|
128
|
+
| 剧本节点 | 题材卡 + 导演的叙事/节奏/表演节 + 规格的结构块 |
|
|
129
|
+
| 分镜表节点 | 导演的镜头语言/灯光/风格参数/拍摄清单 + 规格 + 运镜词典 |
|
|
130
130
|
| 图片节点 | 「风格增强锁」:风格参数 + 影调成像材质 + 题材颗粒 + 三份负向词 |
|
|
131
131
|
| 视频节点 | 同上(有上游分镜表时也追加,不打断结构化字段) |
|
|
132
132
|
| 角色/场景/道具设定图 | 导演的灯光色彩 + 影调卡 |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: canvas-director
|
|
3
|
-
description: 画布导演套件:24 位导演透镜(管镜头语言)、21 个叙事题材 + 10 个行业题材(管压力机制与观众回报)、8
|
|
3
|
+
description: 画布导演套件:24 位导演透镜(管镜头语言)、21 个叙事题材 + 10 个行业题材(管压力机制与观众回报)、8 种规格(管结构块/ASL/节奏/质量档;画幅不归它管)、9 种影调质感(管成像与材质)、运镜词典与打斗节奏。在无限画布里写任何剧本、分镜、图片提示词、视频提示词之前加载本技能,用 canvas_skill_pack 取对应切片。Load before writing any script, storyboard, image or video prompt on the canvas.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# 画布导演套件
|
|
@@ -14,9 +14,9 @@ description: 画布导演套件:24 位导演透镜(管镜头语言)、21
|
|
|
14
14
|
| `canvas-studio` | 画布操作:建项目、写节点、触发生成 | 任何时候先读 |
|
|
15
15
|
| `canvas-craft` | 创作方法论:剧本四要素、分镜六要素、提示词规范 | 写剧本 / 拆分镜前 |
|
|
16
16
|
| `canvas-layout` | 节点布局纪律 | 批量建节点前 |
|
|
17
|
-
| **`canvas-director`(本技能)** | **导演透镜 / 题材 /
|
|
17
|
+
| **`canvas-director`(本技能)** | **导演透镜 / 题材 / 规格 / 影调 / 运镜** | **写任何提示词前** |
|
|
18
18
|
|
|
19
|
-
**优先级(冲突时按此裁决)**:`用户显式指令 > 导演透镜 >
|
|
19
|
+
**优先级(冲突时按此裁决)**:`用户显式指令 > 导演透镜 > 规格(仅单镜时长与结构上它压过透镜)> 题材 > 影调 > 技能默认`
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -25,26 +25,26 @@ description: 画布导演套件:24 位导演透镜(管镜头语言)、21
|
|
|
25
25
|
```
|
|
26
26
|
L1 导演透镜 单选 24 → 镜头语言 / 灯光色彩 / 剪辑节奏 / 风格参数 「怎么拍」
|
|
27
27
|
L2 题材标签 多选 → 压力机制 / 观众回报 / 场面颗粒 / 集尾钩子 「讲什么」
|
|
28
|
-
叙事题材 21
|
|
28
|
+
叙事题材 21(叙事类规格时用)
|
|
29
29
|
行业题材 10(宣传/广告/产品类时用)
|
|
30
|
-
L3
|
|
30
|
+
L3 规格 单选 8 → 结构块 / ASL 下限 / 节奏 / 质量档 「按什么规格交付」
|
|
31
31
|
L4 影调质感 单选 9 → 成像底层 / 材质 / 渲染质感(默认跟随导演) 「长什么样」
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
**八种规格**:短剧 · 电影质感 · 商业片 · 纪实 · 音乐视频 · 预告 · 解说 · 访谈。
|
|
35
35
|
|
|
36
36
|
- **短剧**=单集 60–180 秒的自足单元,**同时覆盖单集与连续多集**(一部剧就是一集一集往下做,不需要单独的「连续剧」规格)。质量要求按电影标准,不按短视频标准。
|
|
37
37
|
- **电影质感**=**质感规格,不是长片规格**。它约束每一镜怎么做才像电影(动机光、真材质、成立景深、24 帧),成片长度档只到 30–300 秒——因为 AI 单次只出 3–8 秒,写一个 120 分钟的档是假的。要长片就按集按段推进,本规格负责每段都过电影线。
|
|
38
38
|
- **商业片**=宣传片 / 广告片 / 产品片合并(相近需求,靠段位配比内部分流,不再拆三张)。
|
|
39
|
-
- **画幅不归任何一层。**
|
|
39
|
+
- **画幅不归任何一层。** 横屏、竖屏、方屏、宽银幕都能配任何规格;**画幅与分辨率在视频节点生成时选**。短剧竖屏投放是发行选择,不是创作规格——短剧同样可以横屏、可以按电影标准拍。
|
|
40
40
|
|
|
41
41
|
为什么这样分:
|
|
42
42
|
|
|
43
43
|
- **导演独占第一层**:它同时约束分镜的 camera 字段、图片关键帧、视频运镜——是整部片最不可替换的决策。
|
|
44
44
|
- **题材与导演分开**:同一个「复仇打脸」,王家卫拍是抽帧暧昧,徐克拍是凌厉升格。混一层会互相污染。
|
|
45
|
-
-
|
|
45
|
+
- **规格独立**:它决定结构块、ASL 下限与质量档,和题材是正交的两个轴。**画幅不属于任何一层**——横竖屏是视频节点生成时的选项,不是创作规格。短剧可以竖屏也可以横屏,都要按电影标准拍。
|
|
46
46
|
- **影调独立**:2D 厚涂 / 国漫 3D / 游戏 CG 是**成像域**,不是导演。塞进导演会让「怎么剪」和「长什么样」打架。
|
|
47
|
-
- **运镜不独立成层**:运镜是导演的语言,独立成层会出现「王家卫 + 摇镜」的自相矛盾。它作为卡内数据存在(`moves
|
|
47
|
+
- **运镜不独立成层**:运镜是导演的语言,独立成层会出现「王家卫 + 摇镜」的自相矛盾。它作为卡内数据存在(`moves/`),只被透镜与规格引用。
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
|
@@ -57,7 +57,7 @@ assets/canvas-director/
|
|
|
57
57
|
├── lenses/ 24 份导演透镜(每份 14 小节 + 风格参数 YAML)
|
|
58
58
|
├── genres/ 21 份叙事题材卡
|
|
59
59
|
├── industries/ 10 份行业题材卡
|
|
60
|
-
├── forms/ 8
|
|
60
|
+
├── forms/ 8 份规格卡(含 spec 数值块:durations / asl_floor_s / asl_target_s / platforms)
|
|
61
61
|
├── looks/ 9 份影调质感卡
|
|
62
62
|
└── moves/ camera-moves.md 运镜词典 / fight-rhythm.md 打斗节奏
|
|
63
63
|
```
|
|
@@ -71,7 +71,7 @@ assets/canvas-director/
|
|
|
71
71
|
**`canvas_list_skills`** —— 开始画布工作前先调它。它会告诉你:
|
|
72
72
|
- 本插件提供的 4 份技能及各自什么时候用;
|
|
73
73
|
- 四层技能包的完整清单(每层有多少张、都有哪些 id 与名称、各自一句话定位);
|
|
74
|
-
- 当前项目已经选定的配置(导演 / 题材 /
|
|
74
|
+
- 当前项目已经选定的配置(导演 / 题材 / 规格 / 影调)。
|
|
75
75
|
|
|
76
76
|
**`canvas_skill_pack`** —— 真正取切片:
|
|
77
77
|
|
|
@@ -100,8 +100,8 @@ canvas_skill_pack({ projectId, kind: 'prompt', lens: 'wong_kar_wai', genres: ['r
|
|
|
100
100
|
|
|
101
101
|
| kind | 内容 | 典型字节 |
|
|
102
102
|
|---|---|---|
|
|
103
|
-
| `script` | 题材卡全部 + 透镜的叙事/节奏/表演节 +
|
|
104
|
-
| `storyboard` | 透镜的镜头语言/灯光/风格参数/拍摄清单 +
|
|
103
|
+
| `script` | 题材卡全部 + 透镜的叙事/节奏/表演节 + 规格的结构块 | ~9–12KB |
|
|
104
|
+
| `storyboard` | 透镜的镜头语言/灯光/风格参数/拍摄清单 + 规格卡 + 运镜词典 | ~12–16KB |
|
|
105
105
|
| `prompt` | 透镜的风格参数/灯光/提示词模板 + 影调卡 + 该类型的 ASL 与负向词 | ~8–12KB |
|
|
106
106
|
| `assets` | 透镜的风格参数/灯光 + 影调卡 | ~4–6KB |
|
|
107
107
|
|
|
@@ -121,7 +121,7 @@ canvas_skill_pack({ projectId, kind: 'prompt', lens: 'wong_kar_wai', genres: ['r
|
|
|
121
121
|
|
|
122
122
|
视觉命题必须具体可测:"男孩随着房间变得更审判而变小",不是"孤独与命运"。写不成可拍摄的变化,就还不是命题。
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
非叙事片(选「商业片」规格:宣传片/广告片/产品片)**跳过本步**:用规格卡里的「观众契约」替代,等价物是"主张 + 那一个可见的证明"。不要为一只口红推导潜文本。
|
|
125
125
|
|
|
126
126
|
### 3. 节拍表
|
|
127
127
|
按**压力变化**分节拍,不按机位分。每拍写:叙事功能、进出状态、视觉动作、压力差、可能的镜头族(建立/关系/特写/插入/反应/过渡/余波/主观/揭示)。
|
|
@@ -48,7 +48,7 @@ spec:
|
|
|
48
48
|
- **慎借**:柏格曼之外的「作者性」透镜。库布里克式对称会让受访者变成标本;张艺谋式色块会让背景抢戏。
|
|
49
49
|
- **基本不要借**:王家卫式手持与前景遮挡(挡住嘴和眼睛等于毁掉访谈)、雷弗恩式仪式构图、毕赣式长镜游走(观众会以为摄影机要跑掉)。
|
|
50
50
|
- **必须放弃的透镜参数**:任何要求「手持晃动」或「主体偏心到画幅边缘」的设定。访谈的第一原则是**脸在可读位置且稳定**。
|
|
51
|
-
-
|
|
51
|
+
- **唯一的例外**:如果这部访谈片本身就是「风格化的肖像纪录片」,可以挂一个强透镜——但那时它更接近**电影**或**纪实**规格,应该换规格,而不是在访谈规格里硬套。
|
|
52
52
|
|
|
53
53
|
<!-- slice:prompt -->
|
|
54
54
|
## 类型级负向词
|
|
@@ -462,7 +462,7 @@
|
|
|
462
462
|
"file": "short-drama.md",
|
|
463
463
|
"glyph": "short-drama",
|
|
464
464
|
"oneliner": "单集或多集的短剧:60-180 秒自足单元,10 秒立钩;按电影标准拍,不是低配",
|
|
465
|
-
"group": "
|
|
465
|
+
"group": "规格"
|
|
466
466
|
},
|
|
467
467
|
{
|
|
468
468
|
"id": "cinematic",
|
|
@@ -470,7 +470,7 @@
|
|
|
470
470
|
"file": "cinematic.md",
|
|
471
471
|
"glyph": "cinematic",
|
|
472
472
|
"oneliner": "电影质感规格(不是长片):每镜都要过电影线——动机光、真材质、成立景深",
|
|
473
|
-
"group": "
|
|
473
|
+
"group": "规格"
|
|
474
474
|
},
|
|
475
475
|
{
|
|
476
476
|
"id": "commercial",
|
|
@@ -478,7 +478,7 @@
|
|
|
478
478
|
"file": "commercial.md",
|
|
479
479
|
"glyph": "commercial",
|
|
480
480
|
"oneliner": "宣传片 / 广告片 / 产品片合一:一条片只承诺一件事,证据必须看得见",
|
|
481
|
-
"group": "
|
|
481
|
+
"group": "规格"
|
|
482
482
|
},
|
|
483
483
|
{
|
|
484
484
|
"id": "documentary",
|
|
@@ -486,7 +486,7 @@
|
|
|
486
486
|
"file": "documentary.md",
|
|
487
487
|
"glyph": "documentary",
|
|
488
488
|
"oneliner": "可信优先于好看:现场光、真环境音、允许承认不知道",
|
|
489
|
-
"group": "
|
|
489
|
+
"group": "规格"
|
|
490
490
|
},
|
|
491
491
|
{
|
|
492
492
|
"id": "music-video",
|
|
@@ -494,7 +494,7 @@
|
|
|
494
494
|
"file": "music-video.md",
|
|
495
495
|
"glyph": "music-video",
|
|
496
496
|
"oneliner": "节奏单位是拍不是秒:切点对齐 BPM,全片必有一次「停」",
|
|
497
|
-
"group": "
|
|
497
|
+
"group": "规格"
|
|
498
498
|
},
|
|
499
499
|
{
|
|
500
500
|
"id": "trailer",
|
|
@@ -502,7 +502,7 @@
|
|
|
502
502
|
"file": "trailer.md",
|
|
503
503
|
"glyph": "trailer",
|
|
504
504
|
"oneliner": "设局—加压—爆发—按钮:只给一个信息,藏住结局",
|
|
505
|
-
"group": "
|
|
505
|
+
"group": "规格"
|
|
506
506
|
},
|
|
507
507
|
{
|
|
508
508
|
"id": "explainer",
|
|
@@ -510,7 +510,7 @@
|
|
|
510
510
|
"file": "explainer.md",
|
|
511
511
|
"glyph": "explainer",
|
|
512
512
|
"oneliner": "一块屏一条信息:语速 4.5-6.5 字/秒,画面必须与旁白同步",
|
|
513
|
-
"group": "
|
|
513
|
+
"group": "规格"
|
|
514
514
|
},
|
|
515
515
|
{
|
|
516
516
|
"id": "interview",
|
|
@@ -518,7 +518,7 @@
|
|
|
518
518
|
"file": "interview.md",
|
|
519
519
|
"glyph": "interview",
|
|
520
520
|
"oneliner": "脸与话是全部资产:ASL 8-20 秒,犹豫和沉默都要留着",
|
|
521
|
-
"group": "
|
|
521
|
+
"group": "规格"
|
|
522
522
|
}
|
|
523
523
|
],
|
|
524
524
|
"looks": [
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 标记缺失时回落到纯标题匹配(保证「复制过来就能用」的鲁棒性)。
|
|
10
10
|
*
|
|
11
11
|
* 术语:kind = 生成能力(script / storyboard / prompt / assets),
|
|
12
|
-
* layer = 数据层(lens 导演 / genre 叙事题材 / industry 行业题材 / form
|
|
12
|
+
* layer = 数据层(lens 导演 / genre 叙事题材 / industry 行业题材 / form 规格 / look 影调 / moves 运镜)。
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
/** Markdown 里 slice 标记的格式:`<!-- slice:story prompt -->`(一个标记可声明归属多个能力)。 */
|
|
@@ -49,7 +49,7 @@ export const SECTION_DEFAULT_SLICE = {
|
|
|
49
49
|
'常见承诺': 'story prompt',
|
|
50
50
|
'禁项': 'prompt',
|
|
51
51
|
'参考镜头': 'prompt',
|
|
52
|
-
//
|
|
52
|
+
// 规格卡
|
|
53
53
|
'交付规格': 'story shot prompt',
|
|
54
54
|
'结构块': 'story',
|
|
55
55
|
'这个类型的观众契约': 'story shot',
|
|
@@ -288,6 +288,9 @@ export function buildPackText(kind, items, opts) {
|
|
|
288
288
|
blocks.push({ title: layerLabel(it.layer, zh) + ':' + name + (meta.oneliner ? ' —— ' + meta.oneliner : ''), body });
|
|
289
289
|
used.push({ layer: it.layer, id: meta.id || '', bytes: utf8Bytes(body) });
|
|
290
290
|
}
|
|
291
|
+
// 一条切片都没切出来 → 返回空:否则调用方只会拿到一段光秃秃的头部
|
|
292
|
+
// (典型场景:kind=assets 时某层没有 assets 小节,不该产出「只有标题」的空壳)。
|
|
293
|
+
if (!blocks.length) return { text: '', bytes: 0, used: [] };
|
|
291
294
|
const head = o.head || '';
|
|
292
295
|
const r = composePackText(blocks, { head });
|
|
293
296
|
return { text: r.text, bytes: r.bytes, used };
|
|
@@ -295,8 +298,8 @@ export function buildPackText(kind, items, opts) {
|
|
|
295
298
|
|
|
296
299
|
/** 层的中文标签。 */
|
|
297
300
|
export function layerLabel(layer, zh) {
|
|
298
|
-
const ZH = { lens: '导演透镜', genre: '题材', industry: '行业题材', form: '
|
|
299
|
-
const EN = { lens: 'Director lens', genre: 'Genre', industry: 'Industry', form: '
|
|
301
|
+
const ZH = { lens: '导演透镜', genre: '题材', industry: '行业题材', form: '规格', look: '影调', moves: '运镜与节奏' };
|
|
302
|
+
const EN = { lens: 'Director lens', genre: 'Genre', industry: 'Industry', form: 'Spec', look: 'Look', moves: 'Camera & rhythm' };
|
|
300
303
|
const m = zh === false ? EN : ZH;
|
|
301
304
|
return m[layer] || layer;
|
|
302
305
|
}
|
|
@@ -305,10 +308,7 @@ export function layerLabel(layer, zh) {
|
|
|
305
308
|
export const DEFAULT_PROFILE = {
|
|
306
309
|
lens: '',
|
|
307
310
|
genres: [],
|
|
308
|
-
form: '
|
|
309
|
-
aspect: '',
|
|
310
|
-
targetSec: 0,
|
|
311
|
-
platform: '',
|
|
311
|
+
form: '',
|
|
312
312
|
look: '',
|
|
313
313
|
genreMode: 'auto',
|
|
314
314
|
};
|
|
@@ -328,14 +328,10 @@ export function normalizeProfile(raw, allowed) {
|
|
|
328
328
|
.filter((x) => !Array.isArray(a.genres) || !a.genres.length || a.genres.includes(x))
|
|
329
329
|
.slice(0, 12)
|
|
330
330
|
: [];
|
|
331
|
-
const sec = Number(b.targetSec);
|
|
332
331
|
return {
|
|
333
332
|
lens: pick(b.lens, a.lenses),
|
|
334
333
|
genres,
|
|
335
334
|
form: pick(b.form, a.forms) || DEFAULT_PROFILE.form,
|
|
336
|
-
aspect: pick(b.aspect, a.aspects),
|
|
337
|
-
targetSec: Number.isFinite(sec) && sec > 0 ? Math.round(sec) : 0,
|
|
338
|
-
platform: pick(b.platform, a.platforms),
|
|
339
335
|
look: pick(b.look, a.looks),
|
|
340
336
|
genreMode: (b.genreMode === 'narrative' || b.genreMode === 'industry') ? b.genreMode : 'auto',
|
|
341
337
|
};
|
|
@@ -17,7 +17,7 @@ description: 用无限画布(Infinite Canvas)做短剧/视频创作时使用
|
|
|
17
17
|
| `canvas-studio` | 画布操作流程:建项目 / 写节点 / 触发生成 / 查状态 | 任何时候先读(本文件) |
|
|
18
18
|
| `canvas-craft` | 创作方法论:剧本四要素、分镜六要素、提示词规范 | 写剧本或拆分镜前 |
|
|
19
19
|
| `canvas-layout` | 节点布局纪律:一类型一列、绝不重叠 | 批量建节点前 |
|
|
20
|
-
| `canvas-director` | **导演透镜 24 / 叙事题材 21 + 行业题材 10 /
|
|
20
|
+
| `canvas-director` | **导演透镜 24 / 叙事题材 21 + 行业题材 10 / 规格 8 / 影调 9 / 运镜词典** | **写任何提示词前** |
|
|
21
21
|
|
|
22
22
|
### 怎么知道有哪些可用
|
|
23
23
|
|
|
@@ -139,7 +139,7 @@ description: 用无限画布(Infinite Canvas)做短剧/视频创作时使用
|
|
|
139
139
|
- **创作硬规则只有一份:`canvas-craft` 技能正文**(六要素 / 每场 1 个大特写 /
|
|
140
140
|
motion「起点→方式→终点」/ 同场连续三镜不同运动 / 每场冲撞点 / 因果钩子 / 术语勘误,全在里面)。
|
|
141
141
|
原先另有一份「专家人设」复述同一批规则,已移除——两条并存会给出互相矛盾的指令。
|
|
142
|
-
风格与题材侧(导演 / 题材 /
|
|
142
|
+
风格与题材侧(导演 / 题材 / 规格 / 影调)由 `canvas-director` 技能包的切片提供,
|
|
143
143
|
用 `canvas_skill_pack` 取,见本节开头的技能清单。
|
|
144
144
|
|
|
145
145
|
## 二、标准工作流(对话填提示词+连线+触发,生成由节点完成)
|
package/client.js
CHANGED
|
@@ -1289,8 +1289,8 @@ window.__ModuleLoader__.load({
|
|
|
1289
1289
|
'skillGenre': "题材",
|
|
1290
1290
|
'skillIndustry': "行业题材",
|
|
1291
1291
|
'skillGenreHint': "决定压力机制与观众回报,可多选",
|
|
1292
|
-
'skillIndustryHint': "
|
|
1293
|
-
'skillForm': "
|
|
1292
|
+
'skillIndustryHint': "按规格自动切换为行业题材,可多选",
|
|
1293
|
+
'skillForm': "规格",
|
|
1294
1294
|
'skillFormHint': "决定结构块、节奏与质量档;画幅在视频生成时选",
|
|
1295
1295
|
'skillLook': "影调",
|
|
1296
1296
|
'skillLookHint': "成像与材质质感;不选则跟随导演",
|
|
@@ -2214,8 +2214,8 @@ window.__ModuleLoader__.load({
|
|
|
2214
2214
|
'skillGenre': "Genre",
|
|
2215
2215
|
'skillIndustry': "Sector",
|
|
2216
2216
|
'skillGenreHint': "Sets pressure mechanics and audience payoff; multi-select",
|
|
2217
|
-
'skillIndustryHint': "Switches to sector cards for this
|
|
2218
|
-
'skillForm': "
|
|
2217
|
+
'skillIndustryHint': "Switches to sector cards for this spec; multi-select",
|
|
2218
|
+
'skillForm': "Spec",
|
|
2219
2219
|
'skillFormHint': "Sets structure, rhythm and quality bar; aspect is chosen at video generation",
|
|
2220
2220
|
'skillLook': "Look",
|
|
2221
2221
|
'skillLookHint': "Imaging and material texture; leave empty to follow the director",
|
|
@@ -7833,6 +7833,17 @@ function queueThumb(assetId) {
|
|
|
7833
7833
|
saveBibleLocal(pid, full.bible);
|
|
7834
7834
|
if (full.bibleUpdatedAt) lsSet(LS_BIBLE_SYNC + pid, full.bibleUpdatedAt);
|
|
7835
7835
|
}
|
|
7836
|
+
// 技能配置随项目文档一起回写:对话侧用 canvas_skill_pack set:true 改的配置,
|
|
7837
|
+
// 面板要能看见(否则「两端同源」只成立一半)。比本地戳新才覆盖,防面板刚改的被旧副本冲掉。
|
|
7838
|
+
if (full.skillProfile && typeof full.skillProfile === 'object') {
|
|
7839
|
+
const hostTs = Number(full.skillUpdatedAt) || 0;
|
|
7840
|
+
const localTs = Number(lsGet(LS_SKILL_SYNC + pid, 0)) || 0;
|
|
7841
|
+
if (hostTs > localTs) {
|
|
7842
|
+
saveSkillProfileLocal(pid, loadSkillProfileFromRaw(full.skillProfile));
|
|
7843
|
+
if (hostTs) lsSet(LS_SKILL_SYNC + pid, String(hostTs));
|
|
7844
|
+
if (pid === S.activeId) update((s) => { s.skillsTick = (s.skillsTick || 0) + 1; });
|
|
7845
|
+
}
|
|
7846
|
+
}
|
|
7836
7847
|
if (isActiveCanvas) update((s) => { s.graph = { nodes, edges: keep, view: s.graph.view, tombstones: tombOut, edgeTombstones: local.edgeTombstones || [] }; s.assetsTick++; });
|
|
7837
7848
|
}
|
|
7838
7849
|
async function hostSyncTick() {
|
|
@@ -8295,7 +8306,7 @@ function queueThumb(assetId) {
|
|
|
8295
8306
|
* 统一的创作上下文:剧本 / 分镜 / 设定集 / 图片与视频提示词都从这里取。
|
|
8296
8307
|
* 两块内容,缺一不可:
|
|
8297
8308
|
* ① rules —— canvas-craft 技能全文(剧本四要素、分镜六要素、提示词规范、连续性)
|
|
8298
|
-
* ② packText —— 四层技能包切片(导演 / 题材 /
|
|
8309
|
+
* ② packText —— 四层技能包切片(导演 / 题材 / 规格 / 影调),按生成能力切
|
|
8299
8310
|
* 不再有「专家人设」:它原本只是 canvas-craft 的下位复述(四要素、六要素、大特写、
|
|
8300
8311
|
* 因果钩子、冲撞点、180° 轴线、术语勘误全都在技能里),两条并存只会给出互相矛盾的指令。
|
|
8301
8312
|
*/
|
|
@@ -9685,8 +9696,11 @@ function queueThumb(assetId) {
|
|
|
9685
9696
|
const metas = refs.map((r) => ({ blob: r.blob, name: r.name, category: r.category, assetId: r.assetId }));
|
|
9686
9697
|
let p = buildEffectivePrompt(prompt, refs, entities, bible, true); // light:图片不内联设定全文(防文字渲染污染 + 参考图约束稀释)
|
|
9687
9698
|
// 技能包的「镜头语言锁」:只追加风格参数与负向词,不内联叙事与剪辑(不污染图片提示词)
|
|
9699
|
+
// 资产设定图(label 以「设定图」结尾,由 assetSheetNodeSpec 生成)走 assets 切片:
|
|
9700
|
+
// 只要成像与材质,不要分镜/剪辑;普通图片仍走 prompt 切片。判定失败自动退化为 prompt。
|
|
9701
|
+
const lockKind = /设定图\s*$/.test(String((n.data && n.data.label) || '')) ? 'assets' : 'prompt';
|
|
9688
9702
|
try {
|
|
9689
|
-
const lock = await buildShotLockText(
|
|
9703
|
+
const lock = await buildShotLockText(lockKind);
|
|
9690
9704
|
if (lock) p = p + '\n' + lock;
|
|
9691
9705
|
} catch { /* 技能包不可用时不影响生成 */ }
|
|
9692
9706
|
// 模型解析:节点选择优先,未指定路由到星标默认模型;都没有 = 明确报错(不再静默走演示模式)
|
|
@@ -13587,11 +13601,13 @@ function queueThumb(assetId) {
|
|
|
13587
13601
|
|
|
13588
13602
|
// ============================================================
|
|
13589
13603
|
// 技能包(canvas-director):四层配置 + 按生成能力切片
|
|
13590
|
-
// L1 导演透镜 24 / L2 题材(叙事 21 + 行业 10)/ L3
|
|
13604
|
+
// L1 导演透镜 24 / L2 题材(叙事 21 + 行业 10)/ L3 规格 9 / L4 影调 9
|
|
13591
13605
|
// 配置是**项目级**:本地 localStorage 缓存 + 随项目文档同步到 Host(对话侧可读)
|
|
13592
13606
|
// 切片用 assets/canvas-director/pack.js 的纯函数(与 Host 同源,保证口径一致)
|
|
13593
13607
|
// ============================================================
|
|
13594
13608
|
const LS_SKILL_PROFILE = 'dsh-ic:skills:';
|
|
13609
|
+
// 技能配置的同步戳:对话侧写项目文档时带 skillUpdatedAt,比本地新才回写面板(双向同步用)
|
|
13610
|
+
const LS_SKILL_SYNC = 'dsh-ic:skillsync:';
|
|
13595
13611
|
const SKILL_PACK_DIR = 'assets/canvas-director/';
|
|
13596
13612
|
let PACK_MOD = null; // pack.js 模块
|
|
13597
13613
|
let PACK_MANIFEST = null; // manifest.json 解析结果
|
|
@@ -13645,24 +13661,30 @@ function queueThumb(assetId) {
|
|
|
13645
13661
|
}
|
|
13646
13662
|
|
|
13647
13663
|
/** 当前项目的技能配置(本地缓存;缺字段回落默认;非法值静默回落)。 */
|
|
13648
|
-
|
|
13649
|
-
|
|
13650
|
-
|
|
13664
|
+
const EMPTY_SKILL_PROFILE = { lens: '', genres: [], form: '', look: '', genreMode: 'auto' };
|
|
13665
|
+
/** 归一技能配置(纯函数):只接受字符串标量,数字/对象/数组一律非法回落,不抛错。
|
|
13666
|
+
* 抽出来是为了「从项目文档回写面板」也能走同一套规则(对话侧写的是原始字段)。 */
|
|
13667
|
+
function loadSkillProfileFromRaw(raw) {
|
|
13651
13668
|
const b = (raw && typeof raw === 'object' && !Array.isArray(raw)) ? raw : {};
|
|
13652
|
-
// 只接受字符串标量:数字/对象/数组一律视为非法 → 回落(与形式校验同款策略:非法即回落,不抛错)
|
|
13653
13669
|
const str = (v) => (typeof v === 'string' ? v.trim() : '');
|
|
13654
|
-
const sec = Number(b.targetSec);
|
|
13655
13670
|
return {
|
|
13656
13671
|
lens: str(b.lens),
|
|
13657
13672
|
genres: Array.isArray(b.genres) ? [...new Set(b.genres.map((x) => (typeof x === 'string' ? x.trim() : '')).filter(Boolean))].slice(0, 12) : [],
|
|
13658
|
-
form: str(b.form)
|
|
13659
|
-
aspect: str(b.aspect),
|
|
13660
|
-
targetSec: Number.isFinite(sec) && sec > 0 ? Math.round(sec) : 0,
|
|
13661
|
-
platform: str(b.platform),
|
|
13673
|
+
form: str(b.form),
|
|
13662
13674
|
look: str(b.look),
|
|
13663
13675
|
genreMode: (b.genreMode === 'narrative' || b.genreMode === 'industry') ? b.genreMode : 'auto',
|
|
13664
13676
|
};
|
|
13665
13677
|
}
|
|
13678
|
+
function loadSkillProfile(pid) {
|
|
13679
|
+
if (!pid) return { ...EMPTY_SKILL_PROFILE };
|
|
13680
|
+
return loadSkillProfileFromRaw(lsGet(LS_SKILL_PROFILE + pid, null));
|
|
13681
|
+
}
|
|
13682
|
+
/** 只写本地(用于从项目文档回写面板配置,不再推 Host —— 否则会形成推送回环)。 */
|
|
13683
|
+
function saveSkillProfileLocal(pid, profile) {
|
|
13684
|
+
if (!pid) return;
|
|
13685
|
+
lsSet(LS_SKILL_PROFILE + pid, profile);
|
|
13686
|
+
notify();
|
|
13687
|
+
}
|
|
13666
13688
|
/** 写技能配置:本地立即落盘 + 防抖推 Host(与设定集同一套思路)。 */
|
|
13667
13689
|
let skillPushTimer = null;
|
|
13668
13690
|
function saveSkillProfile(pid, profile) {
|
|
@@ -13674,7 +13696,10 @@ function queueThumb(assetId) {
|
|
|
13674
13696
|
try {
|
|
13675
13697
|
const proj = S.projects.find((p) => p.id === pid);
|
|
13676
13698
|
const deleted = defaultCanvasDeleted(pid);
|
|
13677
|
-
|
|
13699
|
+
// skillUpdatedAt:对话侧据此判断哪边更新(与 Host 的 canvas_skill_pack set:true 同一字段)
|
|
13700
|
+
const stamp = Date.now();
|
|
13701
|
+
lsSet(LS_SKILL_SYNC + pid, String(stamp));
|
|
13702
|
+
const doc = deleted ? { skillProfile: profile, skillUpdatedAt: stamp } : { ...(loadGraphDoc(pid) || emptyGraph()), skillProfile: profile, skillUpdatedAt: stamp };
|
|
13678
13703
|
hostRpc('projectsPut', {
|
|
13679
13704
|
id: pid, name: (proj && proj.name) || pid,
|
|
13680
13705
|
canvases: proj && Array.isArray(proj.canvases) ? proj.canvases : [],
|
|
@@ -13699,8 +13724,10 @@ function queueThumb(assetId) {
|
|
|
13699
13724
|
const manifest = await loadPackManifest();
|
|
13700
13725
|
if (!manifest) return { text: '', bytes: 0 };
|
|
13701
13726
|
const profile = o.profile || loadSkillProfile(pid);
|
|
13702
|
-
// 四层都没选 →
|
|
13703
|
-
|
|
13727
|
+
// 四层都没选 → 不注入(老项目行为完全不变)。**规格也算一层**:只选「规格」时同样要注入,
|
|
13728
|
+
// 否则对话侧带约束、画布侧不带,两端产出分叉。
|
|
13729
|
+
const hasAnyLayer = !!(profile.lens || profile.form || profile.look || (profile.genres || []).length);
|
|
13730
|
+
if (!hasAnyLayer && !o.force) return { text: '', bytes: 0 };
|
|
13704
13731
|
let P;
|
|
13705
13732
|
try { P = await loadPackModule(); } catch { return { text: '', bytes: 0 }; }
|
|
13706
13733
|
const zh = effLang() !== 'en';
|
|
@@ -13719,10 +13746,12 @@ function queueThumb(assetId) {
|
|
|
13719
13746
|
}
|
|
13720
13747
|
if (profile.form) await push('form', (manifest.forms || []).find((x) => x.id === profile.form));
|
|
13721
13748
|
if (profile.look) await push('look', (manifest.looks || []).find((x) => x.id === profile.look));
|
|
13722
|
-
//
|
|
13749
|
+
// 运镜词典跟着分镜与提示词走;打斗节奏只在分镜(它讲的是镜头组织手法)
|
|
13723
13750
|
if (kind === 'storyboard' || kind === 'prompt') {
|
|
13724
|
-
const
|
|
13725
|
-
|
|
13751
|
+
for (const mid of (kind === 'storyboard' ? ['camera-moves', 'fight-rhythm'] : ['camera-moves'])) {
|
|
13752
|
+
const mv = (manifest.moves || []).find((x) => x.id === mid);
|
|
13753
|
+
if (mv) await push('moves', { ...mv, oneliner: mv.oneliner || '' });
|
|
13754
|
+
}
|
|
13726
13755
|
}
|
|
13727
13756
|
if (!items.length) return { text: '', bytes: 0 };
|
|
13728
13757
|
const head = packHeadLine(kind, profile, manifest, zh);
|
|
@@ -13748,18 +13777,18 @@ function queueThumb(assetId) {
|
|
|
13748
13777
|
'【画布技能包 · ' + kl + '】',
|
|
13749
13778
|
'导演透镜:' + (nameOf('lenses', profile.lens) || '未选')
|
|
13750
13779
|
+ ' 题材:' + (genreNames.join('、') || '未选')
|
|
13751
|
-
+ '
|
|
13780
|
+
+ ' 规格:' + (nameOf('forms', profile.form) || '未选')
|
|
13752
13781
|
+ ' 影调:' + (profile.look ? nameOf('looks', profile.look) : '跟随导演'),
|
|
13753
|
-
'优先级:用户显式指令 > 导演透镜 >
|
|
13782
|
+
'优先级:用户显式指令 > 导演透镜 > 规格(仅单镜时长与结构上压过透镜;画幅由视频节点生成时决定,不在此列)> 题材 > 影调。以下为对应切片,写提示词时按它执行;与本块冲突的通用规则让位于本块。',
|
|
13754
13783
|
].join('\n');
|
|
13755
13784
|
}
|
|
13756
13785
|
return [
|
|
13757
13786
|
'[Canvas skill pack · ' + kl + ']',
|
|
13758
13787
|
'Director lens: ' + (nameOf('lenses', profile.lens) || 'none')
|
|
13759
13788
|
+ ' Genre: ' + (genreNames.join(', ') || 'none')
|
|
13760
|
-
+ '
|
|
13789
|
+
+ ' Spec: ' + (nameOf('forms', profile.form) || 'none')
|
|
13761
13790
|
+ ' Look: ' + (profile.look ? nameOf('looks', profile.look) : 'follow the lens'),
|
|
13762
|
-
'Precedence: explicit user instruction > director lens >
|
|
13791
|
+
'Precedence: explicit user instruction > director lens > spec (clip length and structure only; aspect is set on the video node) > genre > look. The slices below are binding; generic rules that disagree give way.',
|
|
13763
13792
|
].join('\n');
|
|
13764
13793
|
}
|
|
13765
13794
|
|
|
@@ -13769,11 +13798,11 @@ function queueThumb(assetId) {
|
|
|
13769
13798
|
*
|
|
13770
13799
|
* 这是整条链路最容易掉队的一环:剧本与分镜走的是 AI 生成(拿到完整切片),
|
|
13771
13800
|
* 但图片与视频是「拼装提示词」——如果只带风格参数与负向词,影调的成像/材质/光学、
|
|
13772
|
-
*
|
|
13801
|
+
* 规格的节奏、题材的场面颗粒就全丢了,于是第一镜对、后面越走越偏。
|
|
13773
13802
|
*
|
|
13774
13803
|
* 按生成能力取切片:
|
|
13775
13804
|
* kind='prompt' —— 图片与视频。风格参数 + 影调(成像/光比/色彩/介质/光学/材质/特效)
|
|
13776
|
-
* +
|
|
13805
|
+
* + 规格(ASL 与负向词)+ 题材的「常见场面颗粒」+ 三份负向词
|
|
13777
13806
|
* kind='assets' —— 角色/场景/道具设定图。影调的成像与色彩(不含叙事与剪辑)
|
|
13778
13807
|
*
|
|
13779
13808
|
* 不内联:叙事方法、剪辑节奏、人物表演(这些只对剧本/分镜有意义)。
|
|
@@ -13784,7 +13813,8 @@ function queueThumb(assetId) {
|
|
|
13784
13813
|
const manifest = await loadPackManifest();
|
|
13785
13814
|
if (!manifest) return '';
|
|
13786
13815
|
const profile = loadSkillProfile(pid);
|
|
13787
|
-
|
|
13816
|
+
// 规格也算一层(同 buildSkillPackText 说明)
|
|
13817
|
+
if (!profile.lens && !profile.look && !profile.form && !(profile.genres || []).length) return '';
|
|
13788
13818
|
let P;
|
|
13789
13819
|
try { P = await loadPackModule(); } catch { return ''; }
|
|
13790
13820
|
const k = kind === 'assets' ? 'assets' : 'prompt';
|
|
@@ -13828,7 +13858,7 @@ function queueThumb(assetId) {
|
|
|
13828
13858
|
if (granules) push((ge ? '题材的场面颗粒' : '行业视觉母题') + '(' + entry.name + '):', granules);
|
|
13829
13859
|
}
|
|
13830
13860
|
|
|
13831
|
-
// ④
|
|
13861
|
+
// ④ 规格:节奏与负向词(画幅不在这里定,由视频节点选)
|
|
13832
13862
|
const formEntry = profile.form ? (manifest.forms || []).find((x) => x.id === profile.form) : null;
|
|
13833
13863
|
if (formEntry) {
|
|
13834
13864
|
const md = await readPackCard('form', formEntry.file);
|
|
@@ -18452,7 +18482,7 @@ const DIRECTOR_KEY_HELP = [
|
|
|
18452
18482
|
|
|
18453
18483
|
// ============================================================
|
|
18454
18484
|
// 技能页(craft):四层创作控制台
|
|
18455
|
-
// L1 导演透镜(24)/ L2 题材(叙事21 + 行业10)/ L3
|
|
18485
|
+
// L1 导演透镜(24)/ L2 题材(叙事21 + 行业10)/ L3 规格(9)/ L4 影调(9)+ 专家
|
|
18456
18486
|
// 全部是**图标瓦片**,页面内不含任何 <select>(下拉框在小面板里没法扫读)
|
|
18457
18487
|
// ============================================================
|
|
18458
18488
|
|
|
@@ -18516,7 +18546,7 @@ const DIRECTOR_KEY_HELP = [
|
|
|
18516
18546
|
travel: ['M12 4l7 4v8l-7 4-7-4V8z', 'M12 8v8M8 14l4 2 4-2'],
|
|
18517
18547
|
realestate: ['M12 4l7 4v8l-7 4-7-4V8z', 'M8 15v-5l4-2 4 2v5'],
|
|
18518
18548
|
food: ['M12 4l7 4v8l-7 4-7-4V8z', 'M9 9v6M12 9v6M15 9v6'],
|
|
18519
|
-
// ── L3
|
|
18549
|
+
// ── L3 规格:不再用画幅做底形(画幅由视频生成时选),改用「用法」的符号 ──
|
|
18520
18550
|
// 短剧:手机轮廓(最常见发行形态,但不代表锁死画幅)
|
|
18521
18551
|
'short-drama': ['M9 3h6v18H9z', 'M11 6h2M11 18h2'],
|
|
18522
18552
|
// 电影质感:取景框 + 聚光(强调「每镜都过电影线」,不是「长片」)
|
|
@@ -18665,7 +18695,7 @@ const DIRECTOR_KEY_HELP = [
|
|
|
18665
18695
|
};
|
|
18666
18696
|
|
|
18667
18697
|
const m = view || { lenses: [], genres: [], industries: [], forms: [], looks: [] };
|
|
18668
|
-
//
|
|
18698
|
+
// 规格是叙事类还是商业类:决定题材卡显示哪一组
|
|
18669
18699
|
const formEntry = (m.forms || []).find((x) => x.id === profile.form);
|
|
18670
18700
|
// 商业类(宣传片/广告片/产品片已合并为 commercial)→ 题材层切到行业题材
|
|
18671
18701
|
const COMMERCIAL_FORMS = new Set(['commercial']);
|
|
@@ -18702,7 +18732,7 @@ const DIRECTOR_KEY_HELP = [
|
|
|
18702
18732
|
const names = profile.genres.map((g) => ((m.genres || []).find((x) => x.id === g) || (m.industries || []).find((x) => x.id === g) || {}).name || g);
|
|
18703
18733
|
bits.push('题材:' + names.join('、'));
|
|
18704
18734
|
}
|
|
18705
|
-
if (formEntry) bits.push('
|
|
18735
|
+
if (formEntry) bits.push('规格:' + formEntry.name);
|
|
18706
18736
|
if (profile.look) {
|
|
18707
18737
|
const lo = (m.looks || []).find((x) => x.id === profile.look);
|
|
18708
18738
|
if (lo) bits.push('影调:' + lo.name);
|
|
@@ -18714,7 +18744,7 @@ const DIRECTOR_KEY_HELP = [
|
|
|
18714
18744
|
setTick((x) => x + 1);
|
|
18715
18745
|
};
|
|
18716
18746
|
const doClear = () => {
|
|
18717
|
-
saveSkillProfile(S.activeId, { lens: '', genres: [], form: '',
|
|
18747
|
+
saveSkillProfile(S.activeId, { lens: '', genres: [], form: '', look: '', genreMode: 'auto' });
|
|
18718
18748
|
setPreview(null);
|
|
18719
18749
|
setTick((x) => x + 1);
|
|
18720
18750
|
};
|
|
@@ -18752,7 +18782,7 @@ const DIRECTOR_KEY_HELP = [
|
|
|
18752
18782
|
},
|
|
18753
18783
|
})),
|
|
18754
18784
|
|
|
18755
|
-
// ── L3
|
|
18785
|
+
// ── L3 规格 ──
|
|
18756
18786
|
h(SkillCard, {
|
|
18757
18787
|
key: 'form', id: 'form', icon: 'film', title: T('skillForm'),
|
|
18758
18788
|
summary: formEntry ? formEntry.name : T('skillUnselected'),
|
|
@@ -18818,7 +18848,7 @@ const DIRECTOR_KEY_HELP = [
|
|
|
18818
18848
|
|
|
18819
18849
|
function Rightbar() {
|
|
18820
18850
|
// 资产 / 设定集已合并进统一弹窗(左下图标列打开),右侧面板保留项目 / 技能 / 设置。
|
|
18821
|
-
// 技能页(craft)= 四层创作控制台:导演透镜 / 题材 /
|
|
18851
|
+
// 技能页(craft)= 四层创作控制台:导演透镜 / 题材 / 规格 / 影调(全图标瓦片,无下拉框)。
|
|
18822
18852
|
// 快捷键/帮助反馈/检查更新已回到右上角工具栏(纯图标按钮)——右侧面板不再有动作行。
|
|
18823
18853
|
const tabs = [
|
|
18824
18854
|
{ id: 'projects', label: T('projects'), icon: 'folder' },
|
package/docs/guide.en.md
CHANGED
|
@@ -309,22 +309,22 @@ There are **no dropdowns** on the page. Every option is an icon tile: click to s
|
|
|
309
309
|
|---|---|---|---|
|
|
310
310
|
| **Director** | 24 | Camera language, lighting, cutting rhythm, style parameters | It constrains the shot's camera fields, the keyframe and the video's camera move at once — the least replaceable decision in the whole piece |
|
|
311
311
|
| **Genre** | 21 narrative / 10 sector | Pressure mechanics, audience payoff, scene granules, episode hooks | The same "revenge" premise is fragmented neon under Wong Kar-wai and hard-lit slow motion under Tsui Hark; folding them together poisons both |
|
|
312
|
-
| **
|
|
312
|
+
| **Spec** | 8 | Structure blocks, ASL floor, rhythm, quality bar | It decides "short drama vs cinematic texture vs commercial", which is orthogonal to genre |
|
|
313
313
|
| **Look** | 9 | Imaging base, material, render texture | 2D thick paint / guoman 3D / game CG are imaging domains, not directors |
|
|
314
314
|
|
|
315
|
-
**Eight
|
|
315
|
+
**Eight specs**: Short drama · Cinematic texture · Commercial · Documentary · Music video · Trailer · Explainer · Interview.
|
|
316
316
|
|
|
317
317
|
- A **short drama** is a self-contained 60–180 s unit and **covers both single episodes and multi-episode series** — a series is just episode after episode, so there is no separate "series" form. Its quality bar is "shoot it like film", not "shoot it like a short video".
|
|
318
318
|
- **Cinematic texture** is a *texture* spec, not a feature-length one. It constrains how each shot must be made to read as cinema (ASL floor 1.5 s, 24 fps, motivated light, real materials, readable depth), and its duration tiers stop at 30–300 s. The reason is practical: **a video model emits only 3–8 s per clip** (5 s by default here), so "a 120-minute film" can only be hundreds of clips stitched together — a 120-minute tier in the spec would be fiction. For a feature, advance episode by episode; this spec makes each segment pass the cinema line.
|
|
319
319
|
- **Commercial** merges 宣传片 (brand film) / 广告片 (ad) / 产品片 (product film) into one card — near-identical needs, split internally by block ratios instead of three cards.
|
|
320
320
|
|
|
321
|
-
The genre layer **switches automatically with the
|
|
321
|
+
The genre layer **switches automatically with the spec**: pick Commercial and it shows sector cards (tech, finance, medical, beauty, auto, education, FMCG, travel, real estate, food); every other form shows narrative genres.
|
|
322
322
|
|
|
323
|
-
**Priority**: explicit user instruction > director >
|
|
323
|
+
**Priority**: explicit user instruction > director > spec (only on ASL and structure) > genre > look.
|
|
324
324
|
|
|
325
325
|
### 9.2 Aspect ratio is not decided here
|
|
326
326
|
|
|
327
|
-
Vertical, horizontal, square or widescreen all work with any
|
|
327
|
+
Vertical, horizontal, square or widescreen all work with any spec — **aspect and resolution are chosen on the video node at generation time**. Vertical delivery is common for short drama, but that is a distribution choice, not a craft spec; **a short drama can equally be shot horizontal and to a cinematic standard**. The spec constrains **structure, rhythm and quality bar**, not proportion.
|
|
328
328
|
|
|
329
329
|
### 9.3 Three buttons
|
|
330
330
|
|
package/docs/guide.zh-CN.md
CHANGED
|
@@ -313,26 +313,26 @@
|
|
|
313
313
|
|---|---|---|---|
|
|
314
314
|
| **导演** | 24 | 镜头语言、灯光色彩、剪辑节奏、风格参数 | 它同时约束分镜的机位字段、图片关键帧、视频运镜——整部片最不可替换的决策 |
|
|
315
315
|
| **题材** | 叙事 21 / 行业 10 | 压力机制、观众回报、场面颗粒、集尾钩子 | 同一个「复仇打脸」,王家卫拍是抽帧暧昧,徐克拍是凌厉升格;混进导演层会互相污染 |
|
|
316
|
-
|
|
|
316
|
+
| **规格** | 8 | 结构块、平均镜长下限、节奏、质量档 | 它决定「这是短剧、电影还是商业片」,与题材正交 |
|
|
317
317
|
| **影调** | 9 | 成像底层、材质、渲染质感 | 2D 厚涂 / 国漫 3D / 游戏 CG 是成像域,不是导演;塞进导演会让「怎么剪」和「长什么样」打架 |
|
|
318
318
|
|
|
319
|
-
|
|
319
|
+
**八种规格**:短剧 · 电影质感 · 商业片 · 纪实 · 音乐视频 · 预告 · 解说 · 访谈。
|
|
320
320
|
|
|
321
321
|
其中:
|
|
322
322
|
- **短剧**是单集 60–180 秒的自足单元,**同时覆盖单集与连续多集**——一部剧就是一集一集往下做,不需要单独的「连续剧」规格。它对质量的要求是「按电影标准拍」,不是「短视频标准」:真实光源与光比、有依据的焦段、可读的景深。
|
|
323
|
-
- **电影质感**是**质感规格,不是长片规格**。它约束每一镜怎么做才像电影(ASL 1.5 秒以上、24 帧、动机光、真材质、成立构图),成片长度档只到 30–300 秒。原因很实际:**AI 视频模型单次只出 3–8 秒**(本插件默认 5 秒),所谓「一部 120
|
|
323
|
+
- **电影质感**是**质感规格,不是长片规格**。它约束每一镜怎么做才像电影(ASL 1.5 秒以上、24 帧、动机光、真材质、成立构图),成片长度档只到 30–300 秒。原因很实际:**AI 视频模型单次只出 3–8 秒**(本插件默认 5 秒),所谓「一部 120 分钟的电影」只能是几百上千个片段接起来,把它写成规格里的一个时长档没有意义。要拍长片,按集/按段推进,本规格负责每一段都过电影线。
|
|
324
324
|
- **商业片**把「宣传片 / 广告片 / 产品片」合成一张卡——它们是相近需求,靠段位配比内部分流,不再拆成三张。
|
|
325
325
|
|
|
326
|
-
**画幅不在这里决定。**
|
|
326
|
+
**画幅不在这里决定。** 横屏、竖屏、方屏、宽银幕都能用任何规格——**画幅与分辨率是视频节点生成时的选项**。短剧竖屏投放很常见,但那是发行选择,不是创作规格;短剧同样可以横屏、可以按电影标准拍。规格约束的是**结构、节奏、质量档**,不是比例。
|
|
327
327
|
|
|
328
|
-
|
|
328
|
+
题材层会**随规格自动切换**:选「商业片」时换成行业题材(科技、金融、医疗、美妆、汽车、教育、快消、文旅、地产、餐饮);其余类型显示叙事题材。
|
|
329
329
|
|
|
330
|
-
优先级:**用户显式指令 > 导演 >
|
|
330
|
+
优先级:**用户显式指令 > 导演 > 规格(仅单镜时长与结构上压过导演)> 题材 > 影调**。
|
|
331
331
|
|
|
332
332
|
### 9.2 三个按钮
|
|
333
333
|
|
|
334
334
|
- **预览**:显示当前配置注入到剧本的切片全文与字节数。用来确认「到底塞了什么进去」。
|
|
335
|
-
- **写设定集**:把风格摘要(导演名 + 题材 +
|
|
335
|
+
- **写设定集**:把风格摘要(导演名 + 题材 + 规格 + 影调)写进设定集的「风格基调」,这样对话侧也能读到。
|
|
336
336
|
- **清空**:四层全归零,生成行为回到默认。
|
|
337
337
|
|
|
338
338
|
### 9.3 配置按项目保存
|
|
@@ -353,8 +353,8 @@
|
|
|
353
353
|
|
|
354
354
|
| 节点 / 能力 | 拿到的切片 |
|
|
355
355
|
|---|---|
|
|
356
|
-
| 剧本节点 | 题材卡 + 导演的叙事/节奏/表演节 +
|
|
357
|
-
| 分镜表节点 | 导演的镜头语言/灯光/风格参数/拍摄清单 +
|
|
356
|
+
| 剧本节点 | 题材卡 + 导演的叙事/节奏/表演节 + 规格的结构块 |
|
|
357
|
+
| 分镜表节点 | 导演的镜头语言/灯光/风格参数/拍摄清单 + 规格 + 运镜词典 |
|
|
358
358
|
| 图片节点 | 只追加「镜头语言锁」:风格参数 + 影调负向词 |
|
|
359
359
|
| 视频节点 | 同上(有上游分镜表时也追加,不打断结构化字段) |
|
|
360
360
|
| 角色/场景/道具设定图 | 导演的灯光色彩 + 影调卡 |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 技能配置(四层:导演透镜 / 题材 /
|
|
2
|
+
* 技能配置(四层:导演透镜 / 题材 / 规格 / 影调)的字段规范化。
|
|
3
3
|
*
|
|
4
4
|
* 单独成文件是为了让 index.js 与 skill-pack.js 都能引用而不产生循环依赖。
|
|
5
5
|
* 与 normalizeBible 同风格:字符串裁剪、空值剔除、非法值静默回落(不抛错,老项目零迁移)。
|
|
@@ -19,15 +19,11 @@ export function normalizeSkillProfile(raw, allowed) {
|
|
|
19
19
|
.filter((x) => !Array.isArray(a.genres) || !a.genres.length || a.genres.includes(x))
|
|
20
20
|
.slice(0, 12)
|
|
21
21
|
: [];
|
|
22
|
-
const sec = Number(b.targetSec);
|
|
23
22
|
const mode = (b.genreMode === 'narrative' || b.genreMode === 'industry') ? b.genreMode : 'auto';
|
|
24
23
|
return {
|
|
25
24
|
lens: pick(b.lens, a.lenses),
|
|
26
25
|
genres,
|
|
27
26
|
form: pick(b.form, a.forms),
|
|
28
|
-
aspect: pick(b.aspect, a.aspects),
|
|
29
|
-
targetSec: Number.isFinite(sec) && sec > 0 ? Math.min(3600, Math.round(sec)) : 0,
|
|
30
|
-
platform: pick(b.platform, a.platforms),
|
|
31
27
|
look: pick(b.look, a.looks),
|
|
32
28
|
genreMode: mode,
|
|
33
29
|
};
|
|
@@ -35,5 +31,5 @@ export function normalizeSkillProfile(raw, allowed) {
|
|
|
35
31
|
|
|
36
32
|
/** 默认配置(老项目无该字段时就是它,保证行为与未引入技能包前完全一致)。 */
|
|
37
33
|
export const DEFAULT_SKILL_PROFILE = {
|
|
38
|
-
lens: '', genres: [], form: '',
|
|
34
|
+
lens: '', genres: [], form: '', look: '', genreMode: 'auto',
|
|
39
35
|
};
|
package/index.js
CHANGED
|
@@ -934,7 +934,7 @@ function normalizeBible(raw) {
|
|
|
934
934
|
};
|
|
935
935
|
}
|
|
936
936
|
/**
|
|
937
|
-
* 技能配置(四层:导演透镜 / 题材 /
|
|
937
|
+
* 技能配置(四层:导演透镜 / 题材 / 规格 / 影调)字段规范化。
|
|
938
938
|
* 实现搬进 index-core-skill-profile.js(skill-pack.js 也要引用,避免循环依赖)。
|
|
939
939
|
*/
|
|
940
940
|
/**
|
|
@@ -1299,7 +1299,7 @@ async function projectsPut(args) {
|
|
|
1299
1299
|
out.bible = prev.bible;
|
|
1300
1300
|
out.bibleUpdatedAt = prev.bibleUpdatedAt || 0;
|
|
1301
1301
|
}
|
|
1302
|
-
//
|
|
1302
|
+
// 技能配置(四层:导演/题材/规格/影调)同样是项目级,与设定集同一套同步语义
|
|
1303
1303
|
let canvasSkillProfile = null;
|
|
1304
1304
|
if (canvasId) {
|
|
1305
1305
|
const projectDoc = readDocOrNull(id) || {};
|
|
@@ -1618,7 +1618,7 @@ function nextNodePos(doc, type, connectFromId, size) {
|
|
|
1618
1618
|
}
|
|
1619
1619
|
// ---------- 内置技能(操作规程 + 创作方法论,约束并提升对话的画布创作)----------
|
|
1620
1620
|
// canvas-studio=操作硬约束;canvas-craft=剧本/分镜/提示词创作方法论;canvas-layout=节点布局纪律;
|
|
1621
|
-
// canvas-director
|
|
1621
|
+
// canvas-director=导演透镜/题材/规格/影调/运镜(创作控制层,按生成能力切片注入)
|
|
1622
1622
|
const SKILL_NAMES = ['canvas-studio', 'canvas-craft', 'canvas-layout', 'canvas-director'];
|
|
1623
1623
|
// = @deepseek-ai/dsh-skill 的 BUNDLED_SKILL_RANK(避免 import DSH 包;registry 要求 rank 必须是有限数字)
|
|
1624
1624
|
const SKILL_RANK = 600;
|
|
@@ -3264,25 +3264,7 @@ export function apply(ctx) {
|
|
|
3264
3264
|
text: String(text ?? ((def && typeof def === 'object') ? JSON.stringify(def) : '')).slice(0, 80000),
|
|
3265
3265
|
};
|
|
3266
3266
|
},
|
|
3267
|
-
|
|
3268
|
-
* 安全:白名单前缀 + 禁 `..` + 禁绝对路径/盘符 + 只许 .md/.js/.json + 落在包目录内 + ≤256KB。 */
|
|
3269
|
-
async readSkillPack(args) {
|
|
3270
|
-
const rel = String((args && args.rel) || '').replace(/\\/g, '/').replace(/^\/+/, '').trim();
|
|
3271
|
-
if (!rel) throw new Error('缺少 rel');
|
|
3272
|
-
if (rel.includes('..')) throw new Error('非法路径: ' + rel);
|
|
3273
|
-
if (/^[a-z]:/i.test(rel) || rel.startsWith('/')) throw new Error('非法路径: ' + rel);
|
|
3274
|
-
if (!/^canvas-director\/(?:[A-Za-z0-9_.\-\u4e00-\u9fa5]+\/)*[A-Za-z0-9_.\-\u4e00-\u9fa5]+\.(?:md|js|json)$/.test(rel)) {
|
|
3275
|
-
throw new Error('非法路径: ' + rel + '(只允许 canvas-director 下的 .md/.js/.json)');
|
|
3276
|
-
}
|
|
3277
|
-
const baseDir = path.join(HERE, 'assets', 'canvas-director');
|
|
3278
|
-
const abs = path.resolve(path.join(HERE, 'assets', rel));
|
|
3279
|
-
if (abs !== baseDir && !abs.startsWith(baseDir + path.sep)) throw new Error('路径越界: ' + rel);
|
|
3280
|
-
let st;
|
|
3281
|
-
try { st = statSync(abs); } catch { throw new Error('文件不存在: ' + rel); }
|
|
3282
|
-
if (!st.isFile()) throw new Error('不是文件: ' + rel);
|
|
3283
|
-
if (st.size > 256 * 1024) throw new Error('文件过大: ' + rel + '(>' + (256 * 1024) + 'B)');
|
|
3284
|
-
return { rel, text: readFileSync(abs, 'utf8'), bytes: st.size };
|
|
3285
|
-
},
|
|
3267
|
+
|
|
3286
3268
|
};
|
|
3287
3269
|
|
|
3288
3270
|
const toolDisposers = [];
|
|
@@ -3818,7 +3800,7 @@ export function apply(ctx) {
|
|
|
3818
3800
|
// ── 技能包:把「本插件有哪些技能、各管什么」和「当前该按什么口径写提示词」交给对话侧 ──
|
|
3819
3801
|
reg({
|
|
3820
3802
|
name: 'canvas_list_skills',
|
|
3821
|
-
description: '列出本插件(无限画布)提供的全部技能,以及技能包(导演透镜 / 题材 /
|
|
3803
|
+
description: '列出本插件(无限画布)提供的全部技能,以及技能包(导演透镜 / 题材 / 规格 / 影调 / 运镜)的完整清单和当前项目的已选配置。【开始任何画布创作前先调它】:它会告诉你写剧本、分镜、图片提示词、视频提示词各自该加载哪份技能、可用哪些透镜与题材,随后用 canvas_skill_pack 取切片。',
|
|
3822
3804
|
parameters: {
|
|
3823
3805
|
type: 'object',
|
|
3824
3806
|
properties: { projectId: { type: 'string', description: '可选:项目 id(给出时一并返回该项目的已选配置)' } },
|
|
@@ -3847,7 +3829,7 @@ export function apply(ctx) {
|
|
|
3847
3829
|
});
|
|
3848
3830
|
reg({
|
|
3849
3831
|
name: 'canvas_skill_pack',
|
|
3850
|
-
description: '按生成能力取「导演透镜 + 题材 +
|
|
3832
|
+
description: '按生成能力取「导演透镜 + 题材 + 规格 + 影调 + 运镜」的已拼装切片,用于写剧本(script)/ 分镜(storyboard)/ 图片与视频提示词(prompt)/ 角色场景道具设定图(assets)。返回切片全文、字节数与生效配置,可直接拼进你的系统提示或提示词。【这是让对话写出的内容与画布自生成保持同一口径的唯一入口】;也可以用 lens/genres/form/look 临时覆盖,或传 set:true 把配置写进项目(与画布 UI 选择同源)。',
|
|
3851
3833
|
parameters: {
|
|
3852
3834
|
type: 'object',
|
|
3853
3835
|
properties: {
|
|
@@ -3855,7 +3837,7 @@ export function apply(ctx) {
|
|
|
3855
3837
|
kind: { type: 'string', enum: ['script', 'storyboard', 'prompt', 'assets'], description: '生成能力:script=剧本,storyboard=分镜,prompt=图片/视频提示词,assets=资产设定图。缺省 script' },
|
|
3856
3838
|
lens: { type: 'string', description: '可选:临时覆盖导演透镜 id(见 canvas_list_skills)' },
|
|
3857
3839
|
genres: { type: 'array', items: { type: 'string' }, description: '可选:临时覆盖题材 id 数组(叙事题材与行业题材都可)' },
|
|
3858
|
-
form: { type: 'string', description: '
|
|
3840
|
+
form: { type: 'string', description: '可选:临时覆盖规格 id' },
|
|
3859
3841
|
look: { type: 'string', description: '可选:临时覆盖影调 id;传空串表示跟随导演' },
|
|
3860
3842
|
set: { type: 'boolean', description: '可选:true = 把本次生效的配置写进项目(之后画布自生成也用同一套)' },
|
|
3861
3843
|
},
|
|
@@ -3878,6 +3860,9 @@ export function apply(ctx) {
|
|
|
3878
3860
|
if (args.look !== undefined) override.look = args.look;
|
|
3879
3861
|
const merged = normalizeSkillProfile({ ...base, ...override }, allow);
|
|
3880
3862
|
const kind = String((args && args.kind) || 'script');
|
|
3863
|
+
if (!['script', 'storyboard', 'prompt', 'assets'].includes(kind)) {
|
|
3864
|
+
throw new Error('kind 必须是 script / storyboard / prompt / assets,收到:' + kind);
|
|
3865
|
+
}
|
|
3881
3866
|
const built = await buildSkillPack({ kind, profile: merged, hereDir: HERE, zh: true });
|
|
3882
3867
|
if (args.set) {
|
|
3883
3868
|
doc.skillProfile = merged;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ninkch/dsh-infinite-canvas",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.6",
|
|
4
4
|
"description": "Node-based short-drama studio for DeepSeek Harness: script → storyboard → assets → shots → voiced, subtitled final cut on an infinite canvas.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
package/skill-pack.js
CHANGED
|
@@ -12,7 +12,8 @@ import { normalizeSkillProfile } from './index-core-skill-profile.js';
|
|
|
12
12
|
/** moves 永远随 storyboard/prompt 走(运镜是这两类生成的必需品)。 */
|
|
13
13
|
const MOVES_FOR_KIND = {
|
|
14
14
|
script: [],
|
|
15
|
-
|
|
15
|
+
// 分镜要调度动作,运镜 + 打斗节奏两张卡都带;提示词只需运镜。
|
|
16
|
+
storyboard: ['camera-moves', 'fight-rhythm'],
|
|
16
17
|
prompt: ['camera-moves'],
|
|
17
18
|
assets: [],
|
|
18
19
|
};
|
|
@@ -65,11 +66,15 @@ export async function buildSkillPack(opts) {
|
|
|
65
66
|
const md = readCard(hereDir, 'looks', lookEntry.file);
|
|
66
67
|
if (md) items.push({ layer: 'look', md, meta: lookEntry });
|
|
67
68
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
69
|
+
// 运镜只在「已经有层卡命中」时才跟随注入。
|
|
70
|
+
// 否则四层全空的项目也会被塞进 5KB 运镜词典 —— 违反「未配置不注入」(老项目零变化)。
|
|
71
|
+
if (items.length) {
|
|
72
|
+
for (const mid of (MOVES_FOR_KIND[kind] || [])) {
|
|
73
|
+
const mv = (inv.moves || []).find((x) => x.id === mid);
|
|
74
|
+
if (!mv) continue;
|
|
75
|
+
const md = readMove(hereDir, mv.file);
|
|
76
|
+
if (md) items.push({ layer: 'moves', md, meta: mv });
|
|
77
|
+
}
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
const P = await getPackModule();
|
|
@@ -97,15 +102,15 @@ function headLine(kind, profile, inv, zh) {
|
|
|
97
102
|
return [
|
|
98
103
|
'【画布技能包 · ' + kindLabel(kind) + '】',
|
|
99
104
|
'导演透镜:' + nameOf('lenses', profile.lens) + ' 题材:' + (genreNames.join('、') || '—')
|
|
100
|
-
+ '
|
|
101
|
-
'优先级:用户显式指令 > 导演透镜 >
|
|
105
|
+
+ ' 规格:' + nameOf('forms', profile.form) + ' 影调:' + (profile.look ? nameOf('looks', profile.look) : '跟随导演'),
|
|
106
|
+
'优先级:用户显式指令 > 导演透镜 > 规格(仅单镜时长与结构上压过透镜;画幅由视频节点生成时决定,不在此列)> 题材 > 影调 > 技能默认。以下为对应切片,写提示词时必须遵守;与本块冲突的通则让位于本块。',
|
|
102
107
|
].join('\n');
|
|
103
108
|
}
|
|
104
109
|
return [
|
|
105
110
|
'[Canvas skill pack · ' + kindLabel(kind, false) + ']',
|
|
106
111
|
'Director lens: ' + nameOf('lenses', profile.lens) + ' Genre: ' + (genreNames.join(', ') || '-')
|
|
107
|
-
+ '
|
|
108
|
-
'Precedence: explicit user instruction > director lens >
|
|
112
|
+
+ ' Spec: ' + nameOf('forms', profile.form) + ' Look: ' + (profile.look ? nameOf('looks', profile.look) : 'follow the lens'),
|
|
113
|
+
'Precedence: explicit user instruction > director lens > spec (only clip length and structure; aspect is chosen on the video node at generation time) > genre > look > skill defaults. The slices below are binding; where a generic rule disagrees, this block wins.',
|
|
109
114
|
].join('\n');
|
|
110
115
|
}
|
|
111
116
|
|
|
@@ -121,14 +126,14 @@ export const SKILL_CATALOG = [
|
|
|
121
126
|
{ name: 'canvas-studio', when: '任何时候先读:建项目 / 写节点 / 触发生成 / 查状态' },
|
|
122
127
|
{ name: 'canvas-craft', when: '写剧本或拆分镜前:剧本四要素、分镜六要素、提示词规范' },
|
|
123
128
|
{ name: 'canvas-layout', when: '批量建节点前:一类型一列、绝不重叠' },
|
|
124
|
-
{ name: 'canvas-director', when: '写任何提示词前:导演透镜 / 题材 /
|
|
129
|
+
{ name: 'canvas-director', when: '写任何提示词前:导演透镜 / 题材 / 规格 / 影调 / 运镜,先用 canvas_skill_pack 取切片' },
|
|
125
130
|
];
|
|
126
131
|
|
|
127
132
|
/** 层清单的可读渲染(canvas_list_skills 的正文)。 */
|
|
128
133
|
export function renderInventory(inv, profile, zh) {
|
|
129
134
|
const lines = [];
|
|
130
|
-
const ZH_LABEL = { lenses: '导演透镜', genres: '叙事题材', industries: '行业题材', forms: '
|
|
131
|
-
const EN_LABEL = { lenses: 'Director lenses', genres: 'Genres', industries: 'Industries', forms: '
|
|
135
|
+
const ZH_LABEL = { lenses: '导演透镜', genres: '叙事题材', industries: '行业题材', forms: '规格', looks: '影调质感' };
|
|
136
|
+
const EN_LABEL = { lenses: 'Director lenses', genres: 'Genres', industries: 'Industries', forms: 'Specs', looks: 'Looks' };
|
|
132
137
|
const L = zh === false ? EN_LABEL : ZH_LABEL;
|
|
133
138
|
for (const layer of ['lenses', 'genres', 'industries', 'forms', 'looks']) {
|
|
134
139
|
const list = (inv && inv[layer]) || [];
|
|
@@ -172,7 +177,7 @@ export function renderProfile(profile, inv, zh) {
|
|
|
172
177
|
}
|
|
173
178
|
return '导演=' + (nameOf('lenses', profile.lens) || '(未选)')
|
|
174
179
|
+ ' 题材=' + (g.join('、') || '(未选)')
|
|
175
|
-
+ '
|
|
180
|
+
+ ' 规格=' + (nameOf('forms', profile.form) || '(未选)')
|
|
176
181
|
+ ' 影调=' + (profile.look ? nameOf('looks', profile.look) : '跟随导演');
|
|
177
182
|
}
|
|
178
183
|
|