@ninkch/dsh-infinite-canvas 0.19.2 → 0.19.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 +50 -0
- package/README.zh-CN.md +50 -0
- package/assets/canvas-director/SKILL.md +235 -0
- package/assets/canvas-director/forms/cinematic.md +56 -0
- package/assets/canvas-director/forms/commercial.md +63 -0
- package/assets/canvas-director/forms/documentary.md +57 -0
- package/assets/canvas-director/forms/explainer.md +57 -0
- package/assets/canvas-director/forms/interview.md +56 -0
- package/assets/canvas-director/forms/music-video.md +62 -0
- package/assets/canvas-director/forms/short-drama.md +60 -0
- package/assets/canvas-director/forms/trailer.md +58 -0
- package/assets/canvas-director/genres/action-mission.md +58 -0
- package/assets/canvas-director/genres/adventure.md +78 -0
- package/assets/canvas-director/genres/broken-mirror-reunion.md +112 -0
- package/assets/canvas-director/genres/counterattack.md +58 -0
- package/assets/canvas-director/genres/crime-police.md +78 -0
- package/assets/canvas-director/genres/family-relation.md +58 -0
- package/assets/canvas-director/genres/fantasy.md +82 -0
- package/assets/canvas-director/genres/hk-retro.md +80 -0
- package/assets/canvas-director/genres/identity-swap.md +58 -0
- package/assets/canvas-director/genres/modern-urban.md +78 -0
- package/assets/canvas-director/genres/parent-child-secret.md +113 -0
- package/assets/canvas-director/genres/period-power-struggle.md +58 -0
- package/assets/canvas-director/genres/rebirth-transmigration.md +58 -0
- package/assets/canvas-director/genres/revenge-face-slap.md +58 -0
- package/assets/canvas-director/genres/slice-of-life.md +58 -0
- package/assets/canvas-director/genres/suspense-rule.md +58 -0
- package/assets/canvas-director/genres/system-flow.md +58 -0
- package/assets/canvas-director/genres/wealthy-romance.md +58 -0
- package/assets/canvas-director/genres/workplace-comedy.md +58 -0
- package/assets/canvas-director/genres/wuxia.md +78 -0
- package/assets/canvas-director/genres/xianxia-cultivation.md +58 -0
- package/assets/canvas-director/industries/auto.md +44 -0
- package/assets/canvas-director/industries/education.md +44 -0
- package/assets/canvas-director/industries/fashion-beauty.md +45 -0
- package/assets/canvas-director/industries/finance.md +44 -0
- package/assets/canvas-director/industries/fmcg.md +44 -0
- package/assets/canvas-director/industries/food.md +45 -0
- package/assets/canvas-director/industries/medical.md +45 -0
- package/assets/canvas-director/industries/realestate.md +44 -0
- package/assets/canvas-director/industries/tech.md +44 -0
- package/assets/canvas-director/industries/travel.md +45 -0
- package/assets/canvas-director/lenses/bergman.md +207 -0
- package/assets/canvas-director/lenses/bi_gan.md +198 -0
- package/assets/canvas-director/lenses/coen_brothers.md +204 -0
- package/assets/canvas-director/lenses/fellini.md +207 -0
- package/assets/canvas-director/lenses/fincher.md +195 -0
- package/assets/canvas-director/lenses/hitchcock.md +203 -0
- package/assets/canvas-director/lenses/hou_hsiao_hsien.md +202 -0
- package/assets/canvas-director/lenses/kubrick.md +206 -0
- package/assets/canvas-director/lenses/kurosawa.md +206 -0
- package/assets/canvas-director/lenses/lu_yang.md +200 -0
- package/assets/canvas-director/lenses/malick.md +201 -0
- package/assets/canvas-director/lenses/michael_mann.md +202 -0
- package/assets/canvas-director/lenses/nolan.md +195 -0
- package/assets/canvas-director/lenses/park_chan_wook.md +202 -0
- package/assets/canvas-director/lenses/refn.md +196 -0
- package/assets/canvas-director/lenses/scorsese.md +205 -0
- package/assets/canvas-director/lenses/spielberg.md +202 -0
- package/assets/canvas-director/lenses/tarkovsky.md +194 -0
- package/assets/canvas-director/lenses/tsui_hark.md +201 -0
- package/assets/canvas-director/lenses/villeneuve.md +198 -0
- package/assets/canvas-director/lenses/wong_kar_wai.md +199 -0
- package/assets/canvas-director/lenses/wu_ershan.md +206 -0
- package/assets/canvas-director/lenses/yuen_woo_ping.md +200 -0
- package/assets/canvas-director/lenses/zhang_yimou.md +202 -0
- package/assets/canvas-director/looks/anime-thick-paint.md +81 -0
- package/assets/canvas-director/looks/dark-myth-cg.md +83 -0
- package/assets/canvas-director/looks/east-epic-3d.md +83 -0
- package/assets/canvas-director/looks/ghibli-watercolor.md +82 -0
- package/assets/canvas-director/looks/guoman-3d-ink.md +82 -0
- package/assets/canvas-director/looks/guoman-3d-xianxia.md +82 -0
- package/assets/canvas-director/looks/hk-retro-film.md +81 -0
- package/assets/canvas-director/looks/realistic-cinema.md +83 -0
- package/assets/canvas-director/looks/shinkai-light.md +82 -0
- package/assets/canvas-director/manifest.json +603 -0
- package/assets/canvas-director/moves/camera-moves.md +79 -0
- package/assets/canvas-director/moves/fight-rhythm.md +77 -0
- package/assets/canvas-director/pack.js +342 -0
- package/assets/canvas-studio/SKILL.md +34 -3
- package/assets/director/engine.js +17 -4
- package/client.js +19577 -18938
- package/docs/guide.en.md +66 -1
- package/docs/guide.zh-CN.md +64 -1
- package/index-core-skill-profile.js +39 -0
- package/index.js +127 -3
- package/package.json +65 -60
- package/skill-pack-loader.js +147 -0
- package/skill-pack.js +179 -0
package/docs/guide.en.md
CHANGED
|
@@ -297,7 +297,72 @@ Five tabs share one search box. Click an asset to drop it into the scene; charac
|
|
|
297
297
|
|
|
298
298
|
---
|
|
299
299
|
|
|
300
|
-
## 9.
|
|
300
|
+
## 9. Craft page: four-layer console
|
|
301
|
+
|
|
302
|
+
The third tab of the right sidebar — **Craft** — decides **how the whole piece is shot**. It is not a prompt template: it is a binding constraint composed into every script, storyboard, image and video generation.
|
|
303
|
+
|
|
304
|
+
There are **no dropdowns** on the page. Every option is an icon tile: click to select, click again to clear.
|
|
305
|
+
|
|
306
|
+
### 9.1 What each layer owns
|
|
307
|
+
|
|
308
|
+
| Layer | Count | Decides | Why it needs its own layer |
|
|
309
|
+
|---|---|---|---|
|
|
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
|
+
| **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
|
+
| **Delivery form** | 8 | Structure blocks, ASL floor, rhythm, quality bar | It decides "short drama vs cinematic texture vs commercial", which is orthogonal to genre |
|
|
313
|
+
| **Look** | 9 | Imaging base, material, render texture | 2D thick paint / guoman 3D / game CG are imaging domains, not directors |
|
|
314
|
+
|
|
315
|
+
**Eight delivery forms**: Short drama · Cinematic texture · Commercial · Documentary · Music video · Trailer · Explainer · Interview.
|
|
316
|
+
|
|
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
|
+
- **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
|
+
- **Commercial** merges 宣传片 (brand film) / 广告片 (ad) / 产品片 (product film) into one card — near-identical needs, split internally by block ratios instead of three cards.
|
|
320
|
+
|
|
321
|
+
The genre layer **switches automatically with the delivery form**: 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
|
+
|
|
323
|
+
**Priority**: explicit user instruction > director > delivery form (only on ASL and structure) > genre > look.
|
|
324
|
+
|
|
325
|
+
### 9.2 Aspect ratio is not decided here
|
|
326
|
+
|
|
327
|
+
Vertical, horizontal, square or widescreen all work with any delivery form — **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 delivery form constrains **structure, rhythm and quality bar**, not proportion.
|
|
328
|
+
|
|
329
|
+
### 9.3 Three buttons
|
|
330
|
+
|
|
331
|
+
- **Preview** — shows the assembled slice and its byte size, so you can confirm what actually goes in
|
|
332
|
+
- **To bible** — writes the style summary (director + genre + form + look) into the bible's style field; the chat side reads it from there
|
|
333
|
+
- **Clear** — resets all four layers; generation falls back to the previous behaviour
|
|
334
|
+
|
|
335
|
+
### 9.4 Configuration is per project
|
|
336
|
+
|
|
337
|
+
One piece, one configuration. It is stored **on the project document** (same sync mechanism as the series bible), so the chat side reads the same selection the panel shows, and switching projects switches the craft page with it. **Existing projects have no such field and fall back to defaults — generation behaves exactly as before.**
|
|
338
|
+
|
|
339
|
+
### 9.5 Using it from the chat
|
|
340
|
+
|
|
341
|
+
Say "shoot this in Wong Kar-wai's style" and the agent will:
|
|
342
|
+
|
|
343
|
+
1. call `canvas_list_skills` to see the plugin's four skills, the full four-layer inventory, and the current project selection;
|
|
344
|
+
2. call `canvas_skill_pack({ projectId, kind })` to pull the matching slice (`script` / `storyboard` / `prompt` / `assets`);
|
|
345
|
+
3. write against that slice — **the same slice the canvas itself uses**.
|
|
346
|
+
|
|
347
|
+
You can also have the agent change the configuration in chat (it passes `set: true` to persist it); the panel syncs on refresh.
|
|
348
|
+
|
|
349
|
+
### 9.6 Where it is injected
|
|
350
|
+
|
|
351
|
+
| Node / capability | What it receives |
|
|
352
|
+
|---|---|
|
|
353
|
+
| Script node | Genre cards + the lens's narrative/rhythm/performance sections + the form's structure blocks |
|
|
354
|
+
| Storyboard node | The lens's camera language, lighting, style parameters and shot checklist + the form + the camera-move dictionary |
|
|
355
|
+
| Image node | A style lock: style parameters + look imaging/material + genre granules + three negative lists |
|
|
356
|
+
| Video node | Same (also appended when an upstream storyboard feeds it, without breaking the structured format) |
|
|
357
|
+
| Character/scene/prop sheets | The lens's lighting and colour + the look card |
|
|
358
|
+
| Bible AI draft | Same as script |
|
|
359
|
+
|
|
360
|
+
**With all four layers empty nothing is injected.**
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## 10. Troubleshooting
|
|
365
|
+
|
|
301
366
|
|
|
302
367
|
| Symptom | Cause & fix |
|
|
303
368
|
|---|---|
|
package/docs/guide.zh-CN.md
CHANGED
|
@@ -301,7 +301,70 @@
|
|
|
301
301
|
|
|
302
302
|
---
|
|
303
303
|
|
|
304
|
-
## 9.
|
|
304
|
+
## 9. 技能页:四层创作控制台
|
|
305
|
+
|
|
306
|
+
右栏第三个页签「技能」决定**整部片怎么拍**。它不是提示词模板,而是会被自动拼进每一次剧本、分镜、图片、视频生成的硬约束。
|
|
307
|
+
|
|
308
|
+
页内**没有任何下拉框**,全部是图标瓦片:点一下选中,再点一下取消。
|
|
309
|
+
|
|
310
|
+
### 9.1 四层各管什么
|
|
311
|
+
|
|
312
|
+
| 层 | 数量 | 决定 | 为什么单独一层 |
|
|
313
|
+
|---|---|---|---|
|
|
314
|
+
| **导演** | 24 | 镜头语言、灯光色彩、剪辑节奏、风格参数 | 它同时约束分镜的机位字段、图片关键帧、视频运镜——整部片最不可替换的决策 |
|
|
315
|
+
| **题材** | 叙事 21 / 行业 10 | 压力机制、观众回报、场面颗粒、集尾钩子 | 同一个「复仇打脸」,王家卫拍是抽帧暧昧,徐克拍是凌厉升格;混进导演层会互相污染 |
|
|
316
|
+
| **类型规格** | 8 | 结构块、平均镜长下限、节奏、质量档 | 它决定「这是短剧、电影还是商业片」,与题材正交 |
|
|
317
|
+
| **影调** | 9 | 成像底层、材质、渲染质感 | 2D 厚涂 / 国漫 3D / 游戏 CG 是成像域,不是导演;塞进导演会让「怎么剪」和「长什么样」打架 |
|
|
318
|
+
|
|
319
|
+
**八种类型规格**:短剧 · 电影质感 · 商业片 · 纪实 · 音乐视频 · 预告 · 解说 · 访谈。
|
|
320
|
+
|
|
321
|
+
其中:
|
|
322
|
+
- **短剧**是单集 60–180 秒的自足单元,**同时覆盖单集与连续多集**——一部剧就是一集一集往下做,不需要单独的「连续剧」规格。它对质量的要求是「按电影标准拍」,不是「短视频标准」:真实光源与光比、有依据的焦段、可读的景深。
|
|
323
|
+
- **电影质感**是**质感规格,不是长片规格**。它约束每一镜怎么做才像电影(ASL 1.5 秒以上、24 帧、动机光、真材质、成立构图),成片长度档只到 30–300 秒。原因很实际:**AI 视频模型单次只出 3–8 秒**(本插件默认 5 秒),所谓「一部 120 分钟的电影」只能是几百上千个片段接起来,把它写成类型规格里的一个时长档没有意义。要拍长片,按集/按段推进,本规格负责每一段都过电影线。
|
|
324
|
+
- **商业片**把「宣传片 / 广告片 / 产品片」合成一张卡——它们是相近需求,靠段位配比内部分流,不再拆成三张。
|
|
325
|
+
|
|
326
|
+
**画幅不在这里决定。** 横屏、竖屏、方屏、宽银幕都能用任何类型规格——**画幅与分辨率是视频节点生成时的选项**。短剧竖屏投放很常见,但那是发行选择,不是创作规格;短剧同样可以横屏、可以按电影标准拍。类型规格约束的是**结构、节奏、质量档**,不是比例。
|
|
327
|
+
|
|
328
|
+
题材层会**随类型规格自动切换**:选「商业片」时换成行业题材(科技、金融、医疗、美妆、汽车、教育、快消、文旅、地产、餐饮);其余类型显示叙事题材。
|
|
329
|
+
|
|
330
|
+
优先级:**用户显式指令 > 导演 > 类型规格(仅单镜时长与结构上压过导演)> 题材 > 影调**。
|
|
331
|
+
|
|
332
|
+
### 9.2 三个按钮
|
|
333
|
+
|
|
334
|
+
- **预览**:显示当前配置注入到剧本的切片全文与字节数。用来确认「到底塞了什么进去」。
|
|
335
|
+
- **写设定集**:把风格摘要(导演名 + 题材 + 类型规格 + 影调)写进设定集的「风格基调」,这样对话侧也能读到。
|
|
336
|
+
- **清空**:四层全归零,生成行为回到默认。
|
|
337
|
+
|
|
338
|
+
### 9.3 配置按项目保存
|
|
339
|
+
|
|
340
|
+
一部片一套配置,存在项目文档里(与设定集同一套同步机制),所以**对话侧读得到、面板上也看得到**。切换项目时技能页跟着切换。**老项目没有这个字段,一律按默认处理,生成行为与之前完全一致。**
|
|
341
|
+
|
|
342
|
+
### 9.4 对话侧怎么用
|
|
343
|
+
|
|
344
|
+
在对话框里直接说「用王家卫的风格拍这段」,Agent 会:
|
|
345
|
+
|
|
346
|
+
1. 调 `canvas_list_skills` 看本插件有哪四份技能、四层卡片各有哪些、当前项目选了什么;
|
|
347
|
+
2. 调 `canvas_skill_pack({ projectId, kind })` 取对应切片(`script` / `storyboard` / `prompt` / `assets`);
|
|
348
|
+
3. 按切片写内容——**和画布自己生成用的是同一套口径**。
|
|
349
|
+
|
|
350
|
+
你也可以在对话里让 Agent 直接改配置(它会带上 `set: true` 写进项目),改完刷新面板即同步。
|
|
351
|
+
|
|
352
|
+
### 9.5 注入到哪
|
|
353
|
+
|
|
354
|
+
| 节点 / 能力 | 拿到的切片 |
|
|
355
|
+
|---|---|
|
|
356
|
+
| 剧本节点 | 题材卡 + 导演的叙事/节奏/表演节 + 类型规格的结构块 |
|
|
357
|
+
| 分镜表节点 | 导演的镜头语言/灯光/风格参数/拍摄清单 + 类型规格 + 运镜词典 |
|
|
358
|
+
| 图片节点 | 只追加「镜头语言锁」:风格参数 + 影调负向词 |
|
|
359
|
+
| 视频节点 | 同上(有上游分镜表时也追加,不打断结构化字段) |
|
|
360
|
+
| 角色/场景/道具设定图 | 导演的灯光色彩 + 影调卡 |
|
|
361
|
+
| 设定集 AI 草稿 | 同剧本 |
|
|
362
|
+
|
|
363
|
+
**四层全空时什么都不注入**——这是保持不变的行为。
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## 10. 常见问题
|
|
305
368
|
|
|
306
369
|
| 现象 | 原因与处理 |
|
|
307
370
|
|---|---|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 技能配置(四层:导演透镜 / 题材 / 类型规格 / 影调)的字段规范化。
|
|
3
|
+
*
|
|
4
|
+
* 单独成文件是为了让 index.js 与 skill-pack.js 都能引用而不产生循环依赖。
|
|
5
|
+
* 与 normalizeBible 同风格:字符串裁剪、空值剔除、非法值静默回落(不抛错,老项目零迁移)。
|
|
6
|
+
* 枚举白名单由调用方按 manifest 传 allowed —— 避免「清单改了要改两处」。
|
|
7
|
+
*/
|
|
8
|
+
export function normalizeSkillProfile(raw, allowed) {
|
|
9
|
+
const b = (raw && typeof raw === 'object') ? raw : {};
|
|
10
|
+
const a = allowed || {};
|
|
11
|
+
const pick = (v, list) => {
|
|
12
|
+
const s = String(v == null ? '' : v).trim();
|
|
13
|
+
if (!s) return '';
|
|
14
|
+
if (Array.isArray(list) && list.length && !list.includes(s)) return '';
|
|
15
|
+
return s.slice(0, 80);
|
|
16
|
+
};
|
|
17
|
+
const genres = Array.isArray(b.genres)
|
|
18
|
+
? [...new Set(b.genres.map((x) => String(x == null ? '' : x).trim()).filter(Boolean))]
|
|
19
|
+
.filter((x) => !Array.isArray(a.genres) || !a.genres.length || a.genres.includes(x))
|
|
20
|
+
.slice(0, 12)
|
|
21
|
+
: [];
|
|
22
|
+
const sec = Number(b.targetSec);
|
|
23
|
+
const mode = (b.genreMode === 'narrative' || b.genreMode === 'industry') ? b.genreMode : 'auto';
|
|
24
|
+
return {
|
|
25
|
+
lens: pick(b.lens, a.lenses),
|
|
26
|
+
genres,
|
|
27
|
+
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
|
+
look: pick(b.look, a.looks),
|
|
32
|
+
genreMode: mode,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 默认配置(老项目无该字段时就是它,保证行为与未引入技能包前完全一致)。 */
|
|
37
|
+
export const DEFAULT_SKILL_PROFILE = {
|
|
38
|
+
lens: '', genres: [], form: '', aspect: '', targetSec: 0, platform: '', look: '', genreMode: 'auto',
|
|
39
|
+
};
|
package/index.js
CHANGED
|
@@ -13,9 +13,13 @@ import os from 'node:os';
|
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
import { createRequire } from 'node:module';
|
|
16
|
+
import { normalizeSkillProfile, DEFAULT_SKILL_PROFILE } from './index-core-skill-profile.js';
|
|
17
|
+
import { initPackBase, packInventory, allowedIds } from './skill-pack-loader.js';
|
|
18
|
+
import { buildSkillPack, SKILL_CATALOG, renderInventory, renderProfile } from './skill-pack.js';
|
|
16
19
|
|
|
17
20
|
const NAME = 'infinite-canvas';
|
|
18
21
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
initPackBase(HERE); // 技能包加载器需要知道插件根目录
|
|
19
23
|
|
|
20
24
|
// 版本单一来源 = 当前副本的 package.json(硬编码常量曾与包版本漂移)
|
|
21
25
|
let VERSION = '0.0.0';
|
|
@@ -929,6 +933,10 @@ function normalizeBible(raw) {
|
|
|
929
933
|
props: arr(b.props),
|
|
930
934
|
};
|
|
931
935
|
}
|
|
936
|
+
/**
|
|
937
|
+
* 技能配置(四层:导演透镜 / 题材 / 类型规格 / 影调)字段规范化。
|
|
938
|
+
* 实现搬进 index-core-skill-profile.js(skill-pack.js 也要引用,避免循环依赖)。
|
|
939
|
+
*/
|
|
932
940
|
/**
|
|
933
941
|
* 把文件流安全地写进响应。
|
|
934
942
|
* createReadStream 的 open/read 错误是**异步事件**,回到不了路由的 try/catch ——
|
|
@@ -1291,6 +1299,19 @@ async function projectsPut(args) {
|
|
|
1291
1299
|
out.bible = prev.bible;
|
|
1292
1300
|
out.bibleUpdatedAt = prev.bibleUpdatedAt || 0;
|
|
1293
1301
|
}
|
|
1302
|
+
// 技能配置(四层:导演/题材/类型规格/影调)同样是项目级,与设定集同一套同步语义
|
|
1303
|
+
let canvasSkillProfile = null;
|
|
1304
|
+
if (canvasId) {
|
|
1305
|
+
const projectDoc = readDocOrNull(id) || {};
|
|
1306
|
+
if (doc.skillProfile !== undefined) canvasSkillProfile = { profile: normalizeSkillProfile(doc.skillProfile), skillUpdatedAt: Date.now() };
|
|
1307
|
+
else if (projectDoc.skillProfile !== undefined) canvasSkillProfile = { profile: projectDoc.skillProfile, skillUpdatedAt: projectDoc.skillUpdatedAt || 0 };
|
|
1308
|
+
} else if (doc.skillProfile !== undefined) {
|
|
1309
|
+
out.skillProfile = normalizeSkillProfile(doc.skillProfile);
|
|
1310
|
+
out.skillUpdatedAt = Date.now();
|
|
1311
|
+
} else if (prev.skillProfile !== undefined) {
|
|
1312
|
+
out.skillProfile = prev.skillProfile;
|
|
1313
|
+
out.skillUpdatedAt = prev.skillUpdatedAt || 0;
|
|
1314
|
+
}
|
|
1294
1315
|
// 设定集有更新时重算全部分镜表的一致性告警(客户端侧设定集编辑也走这条路径)
|
|
1295
1316
|
const bibleForWarn = canvasId ? (canvasBible && canvasBible.bible) : out.bible;
|
|
1296
1317
|
if (bibleForWarn !== undefined) {
|
|
@@ -1307,7 +1328,7 @@ async function projectsPut(args) {
|
|
|
1307
1328
|
mkdirSync(PROJECTS_DIR, { recursive: true });
|
|
1308
1329
|
writeIcDoc(targetId, out); // 统一走原子写 + 数据目录镜像(此前直写绕过了镜像与原子性)
|
|
1309
1330
|
if (canvasId) {
|
|
1310
|
-
//
|
|
1331
|
+
// 项目级字段(名称/设定集/技能配置/画布清单)始终只写项目文档:项目文档缺失时补一份最小项目
|
|
1311
1332
|
const projectDoc = readDocOrNull(id);
|
|
1312
1333
|
if (!projectDoc) {
|
|
1313
1334
|
const canvases = sanitizeCanvases((args && args.canvases) || []);
|
|
@@ -1317,10 +1338,12 @@ async function projectsPut(args) {
|
|
|
1317
1338
|
nodes: {}, edges: [], view: { x: 60, y: 40, scale: 1 }, tombstones: [], canvases,
|
|
1318
1339
|
defaultCanvasName: defaultCanvasNameOf({ defaultCanvasName: args && args.defaultCanvasName }),
|
|
1319
1340
|
...(canvasBible ? { bible: canvasBible.bible, bibleUpdatedAt: canvasBible.bibleUpdatedAt } : {}),
|
|
1341
|
+
...(canvasSkillProfile ? { skillProfile: canvasSkillProfile.profile, skillUpdatedAt: canvasSkillProfile.skillUpdatedAt } : {}),
|
|
1320
1342
|
});
|
|
1321
1343
|
} else {
|
|
1322
1344
|
const patch = {};
|
|
1323
1345
|
if (canvasBible) { patch.bible = canvasBible.bible; patch.bibleUpdatedAt = canvasBible.bibleUpdatedAt; }
|
|
1346
|
+
if (canvasSkillProfile) { patch.skillProfile = canvasSkillProfile.profile; patch.skillUpdatedAt = canvasSkillProfile.skillUpdatedAt; }
|
|
1324
1347
|
if (args && args.canvases !== undefined) patch.canvases = sanitizeCanvases(args.canvases);
|
|
1325
1348
|
if (Object.keys(patch).length) patchProjectDoc(id, patch);
|
|
1326
1349
|
}
|
|
@@ -1594,8 +1617,9 @@ function nextNodePos(doc, type, connectFromId, size) {
|
|
|
1594
1617
|
return { x, y };
|
|
1595
1618
|
}
|
|
1596
1619
|
// ---------- 内置技能(操作规程 + 创作方法论,约束并提升对话的画布创作)----------
|
|
1597
|
-
// canvas-studio=操作硬约束;canvas-craft=剧本/分镜/提示词创作方法论;canvas-layout
|
|
1598
|
-
|
|
1620
|
+
// canvas-studio=操作硬约束;canvas-craft=剧本/分镜/提示词创作方法论;canvas-layout=节点布局纪律;
|
|
1621
|
+
// canvas-director=导演透镜/题材/类型规格/影调/运镜(创作控制层,按生成能力切片注入)
|
|
1622
|
+
const SKILL_NAMES = ['canvas-studio', 'canvas-craft', 'canvas-layout', 'canvas-director'];
|
|
1599
1623
|
// = @deepseek-ai/dsh-skill 的 BUNDLED_SKILL_RANK(避免 import DSH 包;registry 要求 rank 必须是有限数字)
|
|
1600
1624
|
const SKILL_RANK = 600;
|
|
1601
1625
|
/** 解析 SKILL.md 的 YAML frontmatter(仅需 description 与正文)。 */
|
|
@@ -3240,6 +3264,25 @@ export function apply(ctx) {
|
|
|
3240
3264
|
text: String(text ?? ((def && typeof def === 'object') ? JSON.stringify(def) : '')).slice(0, 80000),
|
|
3241
3265
|
};
|
|
3242
3266
|
},
|
|
3267
|
+
/** 读 canvas-director 技能包里的任意卡文件(Client 侧切片用)。
|
|
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
|
+
},
|
|
3243
3286
|
};
|
|
3244
3287
|
|
|
3245
3288
|
const toolDisposers = [];
|
|
@@ -3772,6 +3815,87 @@ export function apply(ctx) {
|
|
|
3772
3815
|
return { ok: true, projectId: r.projectId || undefined, text: r.projectId ? ('✅ 画布即将打开并跳到项目 ' + r.projectId + '。') : '✅ 画布即将打开。' };
|
|
3773
3816
|
},
|
|
3774
3817
|
});
|
|
3818
|
+
// ── 技能包:把「本插件有哪些技能、各管什么」和「当前该按什么口径写提示词」交给对话侧 ──
|
|
3819
|
+
reg({
|
|
3820
|
+
name: 'canvas_list_skills',
|
|
3821
|
+
description: '列出本插件(无限画布)提供的全部技能,以及技能包(导演透镜 / 题材 / 类型规格 / 影调 / 运镜)的完整清单和当前项目的已选配置。【开始任何画布创作前先调它】:它会告诉你写剧本、分镜、图片提示词、视频提示词各自该加载哪份技能、可用哪些透镜与题材,随后用 canvas_skill_pack 取切片。',
|
|
3822
|
+
parameters: {
|
|
3823
|
+
type: 'object',
|
|
3824
|
+
properties: { projectId: { type: 'string', description: '可选:项目 id(给出时一并返回该项目的已选配置)' } },
|
|
3825
|
+
additionalProperties: false,
|
|
3826
|
+
},
|
|
3827
|
+
output: textOutput,
|
|
3828
|
+
timeoutMs: 30000,
|
|
3829
|
+
async execute(args) {
|
|
3830
|
+
const inv = packInventory(HERE);
|
|
3831
|
+
const pid = String((args && args.projectId) || '');
|
|
3832
|
+
const doc = pid ? readDoc(pid) : { skillProfile: DEFAULT_SKILL_PROFILE };
|
|
3833
|
+
const allow = allowedIds(inv);
|
|
3834
|
+
const profile = normalizeSkillProfile(doc.skillProfile, allow);
|
|
3835
|
+
const lines = [];
|
|
3836
|
+
lines.push('本插件共 ' + SKILL_CATALOG.length + ' 份技能:');
|
|
3837
|
+
for (const s of SKILL_CATALOG) lines.push(' · ' + s.name + ' —— ' + s.when);
|
|
3838
|
+
lines.push('');
|
|
3839
|
+
lines.push('技能包清单(用 canvas_skill_pack 按能力取切片,不要整份读):');
|
|
3840
|
+
lines.push(renderInventory(inv, profile, true));
|
|
3841
|
+
lines.push('');
|
|
3842
|
+
lines.push(pid ? ('项目 ' + pid + ' 当前配置:' + renderProfile(profile, inv, true)) : '(未指定项目,不返回配置)');
|
|
3843
|
+
lines.push('');
|
|
3844
|
+
lines.push('下一步:canvas_skill_pack({ projectId, kind }) —— kind 取 script / storyboard / prompt / assets。');
|
|
3845
|
+
return { ok: true, projectId: pid || undefined, text: lines.join('\n') };
|
|
3846
|
+
},
|
|
3847
|
+
});
|
|
3848
|
+
reg({
|
|
3849
|
+
name: 'canvas_skill_pack',
|
|
3850
|
+
description: '按生成能力取「导演透镜 + 题材 + 类型规格 + 影调 + 运镜」的已拼装切片,用于写剧本(script)/ 分镜(storyboard)/ 图片与视频提示词(prompt)/ 角色场景道具设定图(assets)。返回切片全文、字节数与生效配置,可直接拼进你的系统提示或提示词。【这是让对话写出的内容与画布自生成保持同一口径的唯一入口】;也可以用 lens/genres/form/look 临时覆盖,或传 set:true 把配置写进项目(与画布 UI 选择同源)。',
|
|
3851
|
+
parameters: {
|
|
3852
|
+
type: 'object',
|
|
3853
|
+
properties: {
|
|
3854
|
+
projectId: { type: 'string', description: '项目 id' },
|
|
3855
|
+
kind: { type: 'string', enum: ['script', 'storyboard', 'prompt', 'assets'], description: '生成能力:script=剧本,storyboard=分镜,prompt=图片/视频提示词,assets=资产设定图。缺省 script' },
|
|
3856
|
+
lens: { type: 'string', description: '可选:临时覆盖导演透镜 id(见 canvas_list_skills)' },
|
|
3857
|
+
genres: { type: 'array', items: { type: 'string' }, description: '可选:临时覆盖题材 id 数组(叙事题材与行业题材都可)' },
|
|
3858
|
+
form: { type: 'string', description: '可选:临时覆盖类型规格 id' },
|
|
3859
|
+
look: { type: 'string', description: '可选:临时覆盖影调 id;传空串表示跟随导演' },
|
|
3860
|
+
set: { type: 'boolean', description: '可选:true = 把本次生效的配置写进项目(之后画布自生成也用同一套)' },
|
|
3861
|
+
},
|
|
3862
|
+
required: ['projectId'],
|
|
3863
|
+
additionalProperties: false,
|
|
3864
|
+
},
|
|
3865
|
+
output: textOutput,
|
|
3866
|
+
timeoutMs: 60000,
|
|
3867
|
+
async execute(args) {
|
|
3868
|
+
const pid = String((args && args.projectId) || '');
|
|
3869
|
+
if (!pid) throw new Error('需要 projectId');
|
|
3870
|
+
const doc = readDoc(pid);
|
|
3871
|
+
const inv = packInventory(HERE);
|
|
3872
|
+
const allow = allowedIds(inv);
|
|
3873
|
+
const base = normalizeSkillProfile(doc.skillProfile, allow);
|
|
3874
|
+
const override = {};
|
|
3875
|
+
if (args.lens !== undefined) override.lens = args.lens;
|
|
3876
|
+
if (args.genres !== undefined) override.genres = args.genres;
|
|
3877
|
+
if (args.form !== undefined) override.form = args.form;
|
|
3878
|
+
if (args.look !== undefined) override.look = args.look;
|
|
3879
|
+
const merged = normalizeSkillProfile({ ...base, ...override }, allow);
|
|
3880
|
+
const kind = String((args && args.kind) || 'script');
|
|
3881
|
+
const built = await buildSkillPack({ kind, profile: merged, hereDir: HERE, zh: true });
|
|
3882
|
+
if (args.set) {
|
|
3883
|
+
doc.skillProfile = merged;
|
|
3884
|
+
doc.skillUpdatedAt = Date.now();
|
|
3885
|
+
doc.updatedAt = Date.now();
|
|
3886
|
+
writeIcDoc(pid, doc);
|
|
3887
|
+
markDocChanged(pid);
|
|
3888
|
+
}
|
|
3889
|
+
const head = '【' + (args.set ? '已写入项目配置。' : '') + '生效配置:' + renderProfile(merged, inv, true) + '】\n切片 ' + built.bytes + ' 字节;';
|
|
3890
|
+
return {
|
|
3891
|
+
ok: true,
|
|
3892
|
+
projectId: pid,
|
|
3893
|
+
bytes: built.bytes,
|
|
3894
|
+
profile: merged,
|
|
3895
|
+
text: head + '\n\n' + built.text,
|
|
3896
|
+
};
|
|
3897
|
+
},
|
|
3898
|
+
});
|
|
3775
3899
|
}
|
|
3776
3900
|
} catch { /* tools 服务不可用时跳过注册 */ }
|
|
3777
3901
|
|
package/package.json
CHANGED
|
@@ -1,61 +1,66 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name":
|
|
3
|
-
"version":
|
|
4
|
-
"description":
|
|
5
|
-
"keywords":
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"license":
|
|
20
|
-
"author":
|
|
21
|
-
"publishConfig":
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"repository":
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"type":
|
|
29
|
-
"icon":
|
|
30
|
-
"exports":
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
2
|
+
"name": "@ninkch/dsh-infinite-canvas",
|
|
3
|
+
"version": "0.19.4",
|
|
4
|
+
"description": "Node-based short-drama studio for DeepSeek Harness: script → storyboard → assets → shots → voiced, subtitled final cut on an infinite canvas.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dsh",
|
|
7
|
+
"deepseek-harness",
|
|
8
|
+
"plugin",
|
|
9
|
+
"infinite-canvas",
|
|
10
|
+
"storyboard",
|
|
11
|
+
"short-drama",
|
|
12
|
+
"ai-video",
|
|
13
|
+
"image-generation",
|
|
14
|
+
"video-generation",
|
|
15
|
+
"seedance",
|
|
16
|
+
"photocraft",
|
|
17
|
+
"filmcraft"
|
|
18
|
+
],
|
|
19
|
+
"license": "Apache-2.0",
|
|
20
|
+
"author": "NINKCH",
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"registry": "https://registry.npmjs.org/"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/NINKCH/dsh-infinite_canvas.git"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"icon": "./icon.svg",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./index.js",
|
|
32
|
+
"./client": "./client.js",
|
|
33
|
+
"./package.json": "./package.json",
|
|
34
|
+
"./locale/*.json": "./locale/*.json",
|
|
35
|
+
"./assets/director/*.js": "./assets/director/*.js",
|
|
36
|
+
"./assets/canvas-director/*.js": "./assets/canvas-director/*.js"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"index.js",
|
|
40
|
+
"client.js",
|
|
41
|
+
"icon.svg",
|
|
42
|
+
"cordis.patch.yml",
|
|
43
|
+
"locale/*.json",
|
|
44
|
+
"assets/*/SKILL.md",
|
|
45
|
+
"assets/support/*",
|
|
46
|
+
"assets/director/*.js",
|
|
47
|
+
"assets/director/models/*.json",
|
|
48
|
+
"README.md",
|
|
49
|
+
"README.zh-CN.md",
|
|
50
|
+
"docs/*.md",
|
|
51
|
+
"docs/images/*",
|
|
52
|
+
"skill-pack.js",
|
|
53
|
+
"skill-pack-loader.js",
|
|
54
|
+
"index-core-skill-profile.js",
|
|
55
|
+
"assets/canvas-director/**"
|
|
56
|
+
],
|
|
57
|
+
"dsh": {
|
|
58
|
+
"bundle": {
|
|
59
|
+
"patch": "./cordis.patch.yml"
|
|
60
|
+
},
|
|
61
|
+
"client": {
|
|
62
|
+
"platform": "web",
|
|
63
|
+
"immediately": true
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|