@ninkch/dsh-infinite-canvas 0.18.5 → 0.18.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/assets/canvas-layout/SKILL.md +5 -0
- package/assets/canvas-studio/SKILL.md +6 -0
- package/client.js +188 -33
- package/package.json +1 -1
|
@@ -27,6 +27,11 @@ description: 画布节点布局纪律:一类型一列、竖向排列、绝不
|
|
|
27
27
|
便于在长列里定位。
|
|
28
28
|
6. **类型多时子列**:资产图较多(角色/场景/道具混在一列会很长)时,可按
|
|
29
29
|
「角色图列 / 场景图列 / 道具图列」再分列——但每个子列内部仍遵守竖向排列。
|
|
30
|
+
7. **操作按钮在节点末尾左下角**:凡是**作用于已生成结果**的按钮(铺开到场景 / 存为资产 /
|
|
31
|
+
复制全部 / 铺开到画布 / 重新生成 / 下载 / 扩写…),一律排在节点内容的**最后**、左对齐——
|
|
32
|
+
用户读完结果顺手就能操作。**绝不把这类按钮夹在输入区与结果区之间**(剧本节点曾把
|
|
33
|
+
「铺开到场景 / 存为资产」放在生成按钮下方、正文之前,用户要找很久)。
|
|
34
|
+
输入类操作(上传、选模型、清空)不受此限,留在输入区。
|
|
30
35
|
|
|
31
36
|
## 二、标准建点节奏(保持列干净的写法)
|
|
32
37
|
|
|
@@ -154,6 +154,12 @@ description: 用无限画布(Infinite Canvas)做短剧/视频创作时使用
|
|
|
154
154
|
**按顺序触发**:上一段 `canvas_node_status` 为 done 后再触发下一段(自动续帧要读上一段成片)。
|
|
155
155
|
分镜表节点上的「铺开到画布」按钮会**自动把上一段的 `tailpick`/`last` 接到本段 `frame`**
|
|
156
156
|
(按上面同一条「找上一段」规则),所以铺开 → 逐段按顺序生成即可,通常不必手工连 frame。
|
|
157
|
+
**资产参考图按需就地补齐**:铺开时对每个角色/场景/道具先查资产库——
|
|
158
|
+
已有图 → 直接挂 `图片资产` 节点并接到视频的 `参考图` 口;
|
|
159
|
+
**没有图 → 建「图片生成」节点**,标签按命名规范(`角色-<名> 设定图` / `场景-<名> 设定图` /
|
|
160
|
+
`道具-<名> 设定图`),提示词已按 canvas-craft「资产设定图版式」写好(角色六段式 / 场景主图+环绕小图 /
|
|
161
|
+
道具 45° 主图+细节+色卡,2K 横版),设定集的「外观:…」自动注入,**不留空壳占位节点**。
|
|
162
|
+
所以铺开后先按拓扑顺序把资产图生成掉(或直接跑工作流),再生成视频;同一分镜表重复铺开不会叠加重复资产节点。
|
|
157
163
|
- **【声音一致(可选)】**视频接口配置里的「生成同步音频」勾选后才发送 `generate_audio:true`——
|
|
158
164
|
模型生成**自带音轨**的视频(对白/环境声与画面同步);不勾 = 无声视频(声音另行 TTS 配音)。
|
|
159
165
|
要让同一角色**跨视频保持音色**:用视频/成片节点的「提取声音参考」按钮——
|
package/client.js
CHANGED
|
@@ -338,6 +338,8 @@ window.__ModuleLoader__.load({
|
|
|
338
338
|
'allProjects': '全部项目',
|
|
339
339
|
'currentProject': '当前项目',
|
|
340
340
|
'expandDone': '已铺开为一次多镜头生成:{n} 镜合成为一条视频(时长 = 各镜之和)',
|
|
341
|
+
'expandGenCreated': '已为 {n} 个缺失资产新建图片生成节点(设定图提示词已按技能规范写好)——先生成资产图,再生成视频',
|
|
342
|
+
'expandAssetReused': '{n} 个直接复用资产库已有图片',
|
|
341
343
|
'multiShotHint': '多镜头模式:提示词由上游分镜表组合(全部镜头按顺序+时长合成一条提示词),一次性生成整段视频',
|
|
342
344
|
'aiNeedScript': '分镜节点还没有剧本来源:连线上游剧本节点,或在节点里粘贴剧本',
|
|
343
345
|
'upstreamStillRunning': '上游剧本节点还没生成完成(有连线时内容只从连线取);等它完成后再生成分镜',
|
|
@@ -792,6 +794,8 @@ window.__ModuleLoader__.load({
|
|
|
792
794
|
'allProjects': 'All projects',
|
|
793
795
|
'currentProject': 'Current project',
|
|
794
796
|
'expandDone': 'Expanded to one multi-shot generation: {n} shots composed into a single video',
|
|
797
|
+
'expandGenCreated': 'Created {n} image-generation node(s) for assets that had no image yet, with sheet prompts already written — generate the asset images first, then the video',
|
|
798
|
+
'expandAssetReused': '{n} reused from the asset library',
|
|
795
799
|
'multiShotHint': 'Multi-shot mode: the prompt is composed from the upstream storyboard (all shots, in order, with durations) and generated as one clip',
|
|
796
800
|
'aiNeedScript': 'The storyboard has no script source: connect an upstream script node, or paste a script into the node',
|
|
797
801
|
'upstreamStillRunning': 'The upstream script node is still generating (with a wire, content comes only from the wire); wait for it before splitting shots',
|
|
@@ -2234,6 +2238,108 @@ window.__ModuleLoader__.load({
|
|
|
2234
2238
|
}
|
|
2235
2239
|
return outs.join('\n');
|
|
2236
2240
|
}
|
|
2241
|
+
/**
|
|
2242
|
+
* 资产设定图提示词生成器(纯函数):按 canvas-craft「资产设定图版式」的规范,从设定集条目
|
|
2243
|
+
* 组装专业设定集排版提示词——角色走「六段式」,场景走「主图 + 环绕补充小图」,道具走
|
|
2244
|
+
* 「标题 + 斜 45° 主图 + 细节特写 + 侧视图 + 圆形色卡」。
|
|
2245
|
+
* 共同铁律(技能实测结论):版面里绝不渲染成段文字(文字型模型画长文本必乱码),
|
|
2246
|
+
* 只允许 ≤4 字短标签;同一资产在所有分格中外观完全一致。
|
|
2247
|
+
* @param kind 'character' | 'scene' | 'prop'
|
|
2248
|
+
* @param name 资产名(设定集条目名)
|
|
2249
|
+
* @param desc 设定集描述(含「外观:…」时只取该片段作为外观事实)
|
|
2250
|
+
* @param bible 设定集(style 提供风格基调)
|
|
2251
|
+
* @param zh 中文提示词(false → 英文)
|
|
2252
|
+
*/
|
|
2253
|
+
function buildAssetSheetPrompt(kind, name, desc, bible, zh) {
|
|
2254
|
+
const nm = String(name || '').trim() || (zh ? '未命名资产' : 'Unnamed asset');
|
|
2255
|
+
const raw = String(desc || '').trim();
|
|
2256
|
+
// 外观要点:优先「外观:…」片段(技能的写法约定),否则整段描述;去掉「声音:…」(画图用不到)
|
|
2257
|
+
const look = (() => {
|
|
2258
|
+
const ap = /外观[::]\s*([^。;\n]+)/.exec(raw);
|
|
2259
|
+
const seg = ap ? ap[1] : raw.replace(/声音[::][^。;\n]*/g, '').trim();
|
|
2260
|
+
return seg.replace(/\s+/g, ' ').trim();
|
|
2261
|
+
})();
|
|
2262
|
+
const style = String((bible && bible.style) || '').trim() || (zh ? '写实电影感' : 'realistic cinematic');
|
|
2263
|
+
const lookLine = look ? look : (zh ? '(设定集未提供外观描述:按名称与常识绘制,保持朴素克制)' : '(no appearance notes in the bible: keep it plain and coherent)');
|
|
2264
|
+
if (kind === 'scene') {
|
|
2265
|
+
return zh
|
|
2266
|
+
? [
|
|
2267
|
+
'【总起】' + style + ' 的场景设定图:一张主场景大图占画面主体,周围环绕补充小图;所有格子的光线方向、天气与色调统一,等大长条格一律不要。',
|
|
2268
|
+
'【主体·主场景】全景建立镜头,交代空间结构、纵深与光线方向(主光来自哪一侧要能看出来):' + lookLine + '。',
|
|
2269
|
+
'【环绕补充小图(3–6 张,位置与数量不拘)】右侧一列:多角度图(俯瞰/贴地/第一人称视线等其他机位)+ 其他时间图(夜景/雨雾/逆光等同一场景的不同时段天气);底部一排:细节格(标志物/入口/材质纹理/关键道具在场景中的位置)。',
|
|
2270
|
+
'【标注】每张小图下方一行短标注,不超过 6 字(如「入口·夜」「雨中·逆光」)。',
|
|
2271
|
+
'【文字规则】图内只允许上述短标注:黑色细体、笔画清楚;不写大标题、不写说明段或长句。',
|
|
2272
|
+
'【画面白名单】画面内容只来自该场景与它的固有陈设;不出现剧情人物的面部特写、不出现与该场景无关的器物、没有文字水印。',
|
|
2273
|
+
].join('\n')
|
|
2274
|
+
: [
|
|
2275
|
+
'[Overall] A ' + style + ' location sheet: one large establishing image dominates the frame, ringed by supporting detail images; every panel shares the same light direction, weather and colour grade. No equal-width strip panels.',
|
|
2276
|
+
'[Main image] A wide establishing shot that explains the space, its depth and where the key light comes from: ' + lookLine + '.',
|
|
2277
|
+
'[Supporting images (3-6, placement free)] Right column: alternative angles (overhead / ground level / first-person sightline) plus other times of day (night / rain / backlit); bottom row: detail panels (landmark, entrance, material texture, where the key prop sits).',
|
|
2278
|
+
'[Captions] One short caption under each small image, 6 characters max (e.g. "entrance·night").',
|
|
2279
|
+
'[Text rules] Only those short captions: thin dark strokes, clearly drawn; no headline, no paragraph, no long sentence.',
|
|
2280
|
+
'[Whitelist] Everything in frame comes from this location and its fixed dressing; no character close-ups, no unrelated objects, no watermark.',
|
|
2281
|
+
].join('\n');
|
|
2282
|
+
}
|
|
2283
|
+
if (kind === 'prop') {
|
|
2284
|
+
return zh
|
|
2285
|
+
? [
|
|
2286
|
+
'【总起】' + style + ' 的道具设定图:深色简洁背景,同一道具在所有分格中的材质、比例与配色完全一致。',
|
|
2287
|
+
'【标题】顶部道具名大标题(不超过 6 字):' + nm + '。',
|
|
2288
|
+
'【中央主图】道具斜 45° 悬浮展示,带氛围光与细微粒子,占画面主体:' + lookLine + '。',
|
|
2289
|
+
'【细节特写 2–3 格】材质纹理、关键部件,格内不写字。',
|
|
2290
|
+
'【侧栏侧视图】同一道具的正侧面完整形态(交代厚度与结构)。',
|
|
2291
|
+
'【底部颜色参考】3–5 个圆形色卡,每个下方标注颜色名(不超过 3 字,如「赤红」「熔金」)。',
|
|
2292
|
+
'【文字规则】图内文字只有标题、颜色名与短标签:黑色细体、笔画清楚、字要写大;禁止铭文、短诗、说明文字(必然乱码)。',
|
|
2293
|
+
'【画面白名单】画面中只有该道具本体与上述分格内容;不出现人物、动物、场景陈设、其他道具、文字水印。',
|
|
2294
|
+
].join('\n')
|
|
2295
|
+
: [
|
|
2296
|
+
'[Overall] A ' + style + ' prop sheet: dark plain background, identical material, scale and palette in every panel.',
|
|
2297
|
+
'[Title] One large prop-name heading at the top (6 characters max): ' + nm + '.',
|
|
2298
|
+
'[Main image] The prop floating at a 45 degree angle with ambient light and fine particles, filling the frame: ' + lookLine + '.',
|
|
2299
|
+
'[Detail close-ups, 2-3 panels] Material texture and key components, no text inside them.',
|
|
2300
|
+
'[Side view] A clean front/side view of the same prop (thickness and construction visible).',
|
|
2301
|
+
'[Colour reference] 3-5 round swatches along the bottom, each labelled with a colour name (3 characters max).',
|
|
2302
|
+
'[Text rules] Only the heading, colour names and short labels: thin dark strokes, clearly drawn, large enough; no inscriptions, no verse, no paragraphs.',
|
|
2303
|
+
'[Whitelist] Only this prop and the panels above; no people, no animals, no set dressing, no other props, no watermark.',
|
|
2304
|
+
].join('\n');
|
|
2305
|
+
}
|
|
2306
|
+
// 角色(默认):六段式
|
|
2307
|
+
return zh
|
|
2308
|
+
? [
|
|
2309
|
+
'【总起】16:9 横版专业角色设定集排版:深色简洁背景,' + style + ',同一角色在所有分格中外观完全一致。',
|
|
2310
|
+
'【身份锚】本图没有参考照片:以下外观描述是唯一形象事实,所有分格严格按它绘制,不得自行换装、改体型或改变配色。',
|
|
2311
|
+
'【主体】中央大幅全身立绘(正面自然站姿):' + lookLine + '。姿态为自然空手站立——四肢自然着地或下垂,口部闭合或自然吐舌,不抱、不拿、不叼、不踩、不坐任何外物。',
|
|
2312
|
+
'【主图下方·三视图(必须项)】正面/侧面/背面三格全身站姿——同一角色只换角度,交代侧背形态(后续视频一致性靠它)。',
|
|
2313
|
+
'【右侧窄栏·自上而下分块,每块内容全图只出现一次】① 脸部特写一格 + 一行不超过 12 字的五官短标注;② 表情格:6 个头部小头像分两行(每行 3 个),每个下方只标两字表情名。',
|
|
2314
|
+
'【文字规则】图内文字极少:只保留两三个字的短标签,黑色细体、笔画清楚、字要写大;不写大标题、说明段或长句。',
|
|
2315
|
+
'【画面白名单】画面中只有该角色本体与它的固定配饰(如项圈/挂牌),以及上述分格内容;没有任何被抱、被拿、被叼、被踩或被坐着的物品——纸箱、包裹、篮子、玩具、食物、乐器、器材、道具、家具一律不出现;没有其他人物、没有其他动物、没有场景陈设、没有文字水印。',
|
|
2316
|
+
].join('\n')
|
|
2317
|
+
: [
|
|
2318
|
+
'[Overall] A 16:9 landscape professional character sheet: dark plain background, ' + style + ', the same character looking identical in every panel.',
|
|
2319
|
+
'[Identity anchor] There is no reference photo: the appearance description below is the only source of truth - every panel follows it exactly; no wardrobe change, no body change, no palette drift.',
|
|
2320
|
+
'[Main figure] A large full-body standing figure in the centre (front, natural stance): ' + lookLine + '. Empty-handed stance - limbs resting naturally, mouth closed or tongue relaxed; nothing held, carried, bitten, stood on or sat on.',
|
|
2321
|
+
'[Turnaround, required] Front / side / back full-body panels below the main figure - same character, only the angle changes (this carries video consistency later).',
|
|
2322
|
+
'[Right column, stacked, each block drawn only once in the whole sheet] (1) one face close-up with a short feature note (12 characters max); (2) six small head icons for expressions in two rows of three, each labelled with a two-character expression name.',
|
|
2323
|
+
'[Text rules] Very little text: two or three character labels only, thin dark strokes, clearly drawn and large; no headline, no paragraph, no long sentence.',
|
|
2324
|
+
'[Whitelist] Only this character and its fixed accessories (collar, tag), plus the panels above; nothing is being held, carried, bitten, stood on or sat on - no boxes, parcels, baskets, toys, food, instruments, equipment, props or furniture; no other people, no other animals, no set dressing, no watermark.',
|
|
2325
|
+
].join('\n');
|
|
2326
|
+
}
|
|
2327
|
+
/** 资产设定图节点的规格:命名走技能约定(角色-<名> 设定图)、画幅/清晰度按版式(设定集排版需要细节)。
|
|
2328
|
+
* 标签前缀恒用中文「角色-/场景-/道具-」——这是**数据契约**而非界面文案:categoryFromName 与
|
|
2329
|
+
* assetNameFor 都靠它把生成出来的资产自动归到正确品类(英文界面同样如此)。 */
|
|
2330
|
+
function assetSheetNodeSpec(kind, name, bible, zh) {
|
|
2331
|
+
const k = String(kind || '');
|
|
2332
|
+
const label = entityLabelOf(k) + '-' + String(name || '').trim() + ' 设定图';
|
|
2333
|
+
const ent = matchBibleEntity(name, bible);
|
|
2334
|
+
const desc = (ent && ent.desc) || '';
|
|
2335
|
+
return {
|
|
2336
|
+
kind: k,
|
|
2337
|
+
label,
|
|
2338
|
+
prompt: buildAssetSheetPrompt(k, name, desc, bible, zh),
|
|
2339
|
+
size: '2K',
|
|
2340
|
+
ratio: '16:9', // 设定集排版(角色三视图 + 侧栏)横版信息量最大
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2237
2343
|
// <<<MENTION_PURE_END>>>
|
|
2238
2344
|
|
|
2239
2345
|
/** 解析提示词里的 @引用:返回命中实体 + 其名下的参考图资产与**声音参考资产**(按实体出现顺序、各实体内按生成时间)。
|
|
@@ -8473,23 +8579,6 @@ window.__ModuleLoader__.load({
|
|
|
8473
8579
|
? h('div', { key: 'stream', className: 'dshic-stream', title: aiStreamLine(n.id) },
|
|
8474
8580
|
h('span', { className: 'dshic-stream-dot' }), aiStreamLine(n.id))
|
|
8475
8581
|
: null,
|
|
8476
|
-
h('div', { key: 'btns', className: 'dshic-node-row' },
|
|
8477
|
-
h('div', { className: 'dshic-flex' }),
|
|
8478
|
-
n.data.output ? h('button', {
|
|
8479
|
-
type: 'button', className: 'dshic-mini is-ghost', title: T('expandScenes') + '(每场 → 文本+分镜表 组合)',
|
|
8480
|
-
onPointerDown: (e) => e.stopPropagation(),
|
|
8481
|
-
onClick: () => expandScriptToScenes(n),
|
|
8482
|
-
}, Ic('expandAll', 11), T('expandScenes')) : null,
|
|
8483
|
-
n.data.output ? h('button', {
|
|
8484
|
-
type: 'button', className: 'dshic-mini is-ghost', title: T('saveTextAsset'),
|
|
8485
|
-
onPointerDown: (e) => e.stopPropagation(),
|
|
8486
|
-
onClick: async () => {
|
|
8487
|
-
await saveTextAssetRecord(S.activeId, { name: (String(n.data.label || '').trim() || T('scriptAssetPrefix')) + ' ' + nowStamp(), text: n.data.output, category: 'script', prompt: n.data.idea });
|
|
8488
|
-
update((s) => { s.assetsTick++; });
|
|
8489
|
-
toast(T('savedOk'));
|
|
8490
|
-
},
|
|
8491
|
-
}, Ic('download', 11), T('saveTextAsset')) : null,
|
|
8492
|
-
),
|
|
8493
8582
|
n.data.aiError ? h('div', { key: 'err', className: 'dshic-err' }, n.data.aiError) : null,
|
|
8494
8583
|
h('span', { key: 'l2', className: 'dshic-shot-label' }, T('scriptOutputLabel')),
|
|
8495
8584
|
h(PromptArea, {
|
|
@@ -8498,6 +8587,26 @@ window.__ModuleLoader__.load({
|
|
|
8498
8587
|
placeholder: running ? T('aiRunning') : '',
|
|
8499
8588
|
onChange: (v) => update((s) => { const nd = s.graph.nodes[n.id]; if (nd) nd.data.output = v; }),
|
|
8500
8589
|
}),
|
|
8590
|
+
// 结果相关操作放节点末尾左下角(与分镜节点同一规范:内容之后 + alignSelf:flex-start)——
|
|
8591
|
+
// 这些按钮作用于「已经生成出来的剧本」,不该夹在生成按钮与输出正文之间。
|
|
8592
|
+
n.data.output
|
|
8593
|
+
? h('div', { key: 'acts', className: 'dshic-node-row', style: { alignSelf: 'flex-start' } },
|
|
8594
|
+
h('button', {
|
|
8595
|
+
type: 'button', className: 'dshic-mini is-ghost', title: T('saveTextAsset'),
|
|
8596
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
8597
|
+
onClick: async () => {
|
|
8598
|
+
await saveTextAssetRecord(S.activeId, { name: (String(n.data.label || '').trim() || T('scriptAssetPrefix')) + ' ' + nowStamp(), text: n.data.output, category: 'script', prompt: n.data.idea });
|
|
8599
|
+
update((s) => { s.assetsTick++; });
|
|
8600
|
+
toast(T('savedOk'));
|
|
8601
|
+
},
|
|
8602
|
+
}, Ic('download', 11), T('saveTextAsset')),
|
|
8603
|
+
h('button', {
|
|
8604
|
+
type: 'button', className: 'dshic-mini', title: T('expandScenes') + '(每场 → 文本+分镜表 组合)',
|
|
8605
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
8606
|
+
onClick: () => expandScriptToScenes(n),
|
|
8607
|
+
}, Ic('expandAll', 11), T('expandScenes')),
|
|
8608
|
+
)
|
|
8609
|
+
: null,
|
|
8501
8610
|
];
|
|
8502
8611
|
}
|
|
8503
8612
|
|
|
@@ -8699,26 +8808,46 @@ window.__ModuleLoader__.load({
|
|
|
8699
8808
|
// 项目资产名称索引(用于把角色/场景/道具连接到已有资产节点)
|
|
8700
8809
|
const recs = await idbByProject(S.activeId).catch(() => []);
|
|
8701
8810
|
const norm = (s) => String(s || '').trim().toLowerCase();
|
|
8702
|
-
const nameIndex = recs.filter((r) => r.kind === 'image').map((r) => ({ id: r.id, name: norm(r.name), category: r.category }));
|
|
8703
|
-
//
|
|
8704
|
-
const
|
|
8705
|
-
const
|
|
8811
|
+
const nameIndex = recs.filter((r) => r.kind === 'image').map((r) => ({ id: r.id, name: norm(r.name), raw: String(r.name || ''), category: r.category }));
|
|
8812
|
+
// 跨镜去重的实体清单:所有镜头共用的角色/场景/道具各一个参考节点(带品类——决定用哪种设定图版式)
|
|
8813
|
+
const entities = [];
|
|
8814
|
+
const pushEntity = (nm, kind) => {
|
|
8706
8815
|
const q = String(nm || '').trim();
|
|
8707
|
-
if (
|
|
8816
|
+
if (!q) return;
|
|
8817
|
+
if (entities.some((x) => x.name.toLowerCase() === q.toLowerCase())) return;
|
|
8818
|
+
entities.push({ name: q, kind: kind || 'character' });
|
|
8708
8819
|
};
|
|
8709
8820
|
for (const sh of shots) {
|
|
8710
|
-
(sh.characters || []).forEach(
|
|
8711
|
-
|
|
8712
|
-
(sh.props || []).forEach(
|
|
8821
|
+
(sh.characters || []).forEach((nm) => pushEntity(nm, 'character'));
|
|
8822
|
+
pushEntity(sh.scene, 'scene');
|
|
8823
|
+
(sh.props || []).forEach((nm) => pushEntity(nm, 'prop'));
|
|
8713
8824
|
}
|
|
8825
|
+
const zh = effLang() !== 'en';
|
|
8826
|
+
const bible = loadBible(S.activeId);
|
|
8827
|
+
let reuseAssets = 0; // 资产库已有 → 只挂节点
|
|
8828
|
+
let newGens = 0; // 库中没有 → 新建带提示词的图片生成节点
|
|
8714
8829
|
update((s) => {
|
|
8715
|
-
|
|
8830
|
+
// 资产库命中(名称相等优先,其次包含匹配)——有图就直接挂资产节点,绝不重复生成
|
|
8831
|
+
const findLibraryAsset = (name) => {
|
|
8716
8832
|
const q = norm(name);
|
|
8717
8833
|
if (!q) return null;
|
|
8718
|
-
|
|
8719
|
-
|
|
8834
|
+
return nameIndex.find((x) => x.name === q)
|
|
8835
|
+
|| nameIndex.find((x) => x.name.includes(q) || q.includes(x.name))
|
|
8836
|
+
|| null;
|
|
8837
|
+
};
|
|
8838
|
+
const findAssetNodeById = (assetId) => {
|
|
8839
|
+
if (!assetId) return null;
|
|
8840
|
+
for (const nd of Object.values(s.graph.nodes)) {
|
|
8841
|
+
if (nd.type === 'image-asset' && nd.data.assetId === assetId) return nd;
|
|
8842
|
+
}
|
|
8843
|
+
return null;
|
|
8844
|
+
};
|
|
8845
|
+
// 同标签的图片生成节点(上次铺开已建过)→ 复用,避免每点一次铺开又叠一层
|
|
8846
|
+
const findGenNodeByLabel = (label) => {
|
|
8847
|
+
const q = norm(label);
|
|
8848
|
+
if (!q) return null;
|
|
8720
8849
|
for (const nd of Object.values(s.graph.nodes)) {
|
|
8721
|
-
if (nd.type === 'image-
|
|
8850
|
+
if (nd.type === 'image-gen' && norm(nd.data && nd.data.label) === q) return nd;
|
|
8722
8851
|
}
|
|
8723
8852
|
return null;
|
|
8724
8853
|
};
|
|
@@ -8742,10 +8871,25 @@ window.__ModuleLoader__.load({
|
|
|
8742
8871
|
const total = shots.reduce((a, sh) => a + Math.max(1, Number(sh.duration) || 3), 0);
|
|
8743
8872
|
const videoGen = mk('video-gen', baseX + 360, baseY + 20, { prompt: '', duration: Math.max(1, Math.round(total)) });
|
|
8744
8873
|
const videoAsset = mk('video-asset', baseX + 720, baseY + 20);
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8874
|
+
entities.forEach((ent, i) => {
|
|
8875
|
+
const y = baseY + i * 150;
|
|
8876
|
+
const hit = findLibraryAsset(ent.name);
|
|
8877
|
+
if (hit) {
|
|
8878
|
+
let an = findAssetNodeById(hit.id);
|
|
8879
|
+
if (!an) an = mk('image-asset', baseX, y, { assetId: hit.id }, hit.raw || hit.name);
|
|
8880
|
+
linkRaw(an, 'out', videoGen, 'ref');
|
|
8881
|
+
reuseAssets++;
|
|
8882
|
+
return;
|
|
8883
|
+
}
|
|
8884
|
+
// 资产库没有该实体的图:建「图片生成」节点,并按 canvas-craft 资产设定图版式写好提示词
|
|
8885
|
+
//(角色六段式 / 场景主图+环绕小图 / 道具标题+45°主图+细节+侧视+色卡)——生成后自动成为本段参考图
|
|
8886
|
+
const spec = assetSheetNodeSpec(ent.kind, ent.name, bible, zh);
|
|
8887
|
+
let gn = findGenNodeByLabel(spec.label);
|
|
8888
|
+
if (!gn) {
|
|
8889
|
+
gn = mk('image-gen', baseX, y, { prompt: spec.prompt, size: spec.size, ratio: spec.ratio }, spec.label);
|
|
8890
|
+
newGens++;
|
|
8891
|
+
}
|
|
8892
|
+
linkRaw(gn, 'out', videoGen, 'ref');
|
|
8749
8893
|
});
|
|
8750
8894
|
linkRaw(n, 'out', videoGen, 'prompt'); // 多镜头提示词来自上游分镜表(不复制文本)
|
|
8751
8895
|
linkRaw(videoGen, 'out', videoAsset, 'in');
|
|
@@ -8762,6 +8906,11 @@ window.__ModuleLoader__.load({
|
|
|
8762
8906
|
pushHistory();
|
|
8763
8907
|
flushSave(); // 批量铺开立即推送,Host 侧立即可见
|
|
8764
8908
|
toast(T('expandDone').replace('{n}', String(shots.length)));
|
|
8909
|
+
// 铺开结果如实告知:哪些实体资产库已有、哪些新建了待生成的资产节点(提示词已写好)
|
|
8910
|
+
if (newGens) {
|
|
8911
|
+
setTimeout(() => toast(T('expandGenCreated').replace('{n}', String(newGens))
|
|
8912
|
+
+ (reuseAssets ? ' · ' + T('expandAssetReused').replace('{n}', String(reuseAssets)) : '')), 400);
|
|
8913
|
+
}
|
|
8765
8914
|
setTimeout(() => fitView(), 60);
|
|
8766
8915
|
}
|
|
8767
8916
|
|
|
@@ -11241,6 +11390,8 @@ window.__ModuleLoader__.load({
|
|
|
11241
11390
|
// 直接暴露各页签组件与同步入口,让测试能逐个渲染/驱动,抓白屏与同步断链问题
|
|
11242
11391
|
if (typeof window !== 'undefined' && window.__DSH_IC_SMOKE__) {
|
|
11243
11392
|
window.__DSH_IC_TABS__ = { ProjectsTab, AssetsTab, BibleTab, SettingsTab, CanvasPage, ShortcutsPanel, CanvasHostedApp };
|
|
11393
|
+
// 节点体组件出口:供 scripts/test-node-actions.mjs 断言「结果操作按钮必须在节点末尾左下角」的排版规范
|
|
11394
|
+
window.__DSH_IC_NODES__ = { ScriptNodeBody, StoryboardNodeBody, GenNodeBody, FinalCutNodeBody, AssetNodeBody, TextNodeBody, ShotNodeBody };
|
|
11244
11395
|
// 成片合成(composeFinalCut)纯函数级出口:浏览器录制链路无法在 Node 端跑,
|
|
11245
11396
|
// 但合成主循环(分段推进/裁剪/转场/字幕)可以用假的 video/MediaRecorder 真实执行——
|
|
11246
11397
|
// 这正是历史上一处 "dur is not defined" 在真机上才炸的漏洞所在。
|
|
@@ -11261,6 +11412,10 @@ window.__ModuleLoader__.load({
|
|
|
11261
11412
|
// 冒烟测试用:切换「快捷键面板」展开态,让展开后的列表也走一次真实渲染
|
|
11262
11413
|
setKeysOpen: (v) => { S.keysOpen = !!v; },
|
|
11263
11414
|
state: () => ({ stopped: hostSyncStopped, busy: hostSyncBusy, projects: S.projects.map((p) => p.id), activeId: S.activeId, ready: S.ready }),
|
|
11415
|
+
// 铺开到画布(缺资产 → 建带设定图提示词的图片生成节点)供 test-expand-assets 驱动
|
|
11416
|
+
expandStoryboard,
|
|
11417
|
+
// 纯函数出口:资产设定图提示词生成器(不依赖 DOM,可直接断言)
|
|
11418
|
+
buildAssetSheetPrompt, assetSheetNodeSpec,
|
|
11264
11419
|
};
|
|
11265
11420
|
// IC-01:下载侧扩展名判定的纯函数出口(供 scripts/test-download-ext.mjs 直接断言)
|
|
11266
11421
|
window.__DSH_IC_MEDIA__ = { extForDownload };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ninkch/dsh-infinite-canvas",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.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",
|