@ninkch/dsh-infinite-canvas 0.17.15 → 0.17.17
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-craft/SKILL.md +44 -0
- package/assets/canvas-studio/SKILL.md +11 -3
- package/client.js +65 -9
- package/package.json +1 -1
|
@@ -31,6 +31,50 @@ B:……
|
|
|
31
31
|
- **一致性**:人名、地名、道具名与设定集**逐字一致**,禁止擅自改名或新增设定。
|
|
32
32
|
- **输出格式**:直接给可用剧本正文,不要解释、不要提纲、不要"待生成"占位。
|
|
33
33
|
|
|
34
|
+
|
|
35
|
+
## 二·五、资产设定图版式(角色/道具/场景的专业排版,拒绝简单三视图)
|
|
36
|
+
|
|
37
|
+
简单三视图信息量不足——设定图要做成**专业设定集排版**:文字信息 + 主图 + 特写 + 表情/色卡
|
|
38
|
+
分区呈现,一图读懂资产的全部视觉设定。所有面板文字一律**简体中文、清晰可读**;
|
|
39
|
+
同一资产在所有分格中外观**完全一致**(发型/服装/配色逐格复用设定集关键词)。
|
|
40
|
+
|
|
41
|
+
### 角色设定图(16:9 横版)
|
|
42
|
+
- **左侧信息面板**:「核心人物信息」——姓名 / 性别 / 年龄 / 身份 / 性格 / 配色(逐行列出);
|
|
43
|
+
旁边「角色设定」小面板:两三句背景设定。
|
|
44
|
+
- **中央主图**:全身立绘,正面站姿,完整呈现体型与服装(占画面约 1/3 宽)。
|
|
45
|
+
- **右上脸部特征**:正面特写 + 侧面特写并排,下方 3–4 行文字标注五官要点
|
|
46
|
+
(如「杏眼温润 / 鼻梁秀挺 / 唇色自然 / 侧颜柔和」)。
|
|
47
|
+
- **右上服饰特征**:服装要点逐行标注(如「月白浅交领长裙 / 绣兰草纹样 / 腰间系青玉佩」)。
|
|
48
|
+
- **左下细节展示**:2×3 六格特写——发饰 / 耳饰 / 腰带 / 袖口纹样 / 下摆纹样 / 随身道具,
|
|
49
|
+
每格下方一行小字标注名称(如「发饰细节:兰花银饰」)。
|
|
50
|
+
- **右下表情排列**:2×5 共 10 个头像素描格,逐格标注表情名
|
|
51
|
+
(参考序列:温柔、微笑、沉思、惊讶、端庄、凛然、悠然、羞涩、坚定、冷淡——按角色性格取舍)。
|
|
52
|
+
- 提示词结构示例:「角色人设图(16:9 横版专业设定集排版):左侧『核心人物信息』文字面板
|
|
53
|
+
(姓名:沈清蘅 / 性别:女 / 年龄:28 / 身份:兰衣医修 / 性格:温柔沉静 / 配色:月白浅绿青玉);
|
|
54
|
+
中央全身立绘……;右上脸部特征……;左下细节展示六格……;右下表情排列十格……」
|
|
55
|
+
——面板名与标注文字全部写进提示词,模型才能排出完整版式。
|
|
56
|
+
|
|
57
|
+
### 道具设定图(横竖皆可——按道具特性选,不限制)
|
|
58
|
+
- **画幅选择**:长条形/单件道具(剑、箫、杖)竖版更能展示完整形态;
|
|
59
|
+
组合道具、带场景感或横向结构的道具用横版能并列更多细节与色卡——排版逻辑不变,画幅自由。
|
|
60
|
+
- **顶部**:道具名大标题 + 一行类别副标题(如「火属性上古仙兵」)。
|
|
61
|
+
- **中央主图**:道具斜 45° 悬浮展示,带氛围光/粒子效果,占画面主体。
|
|
62
|
+
- **细节特写 2–3 格**:材质纹理、关键部件(如「箫身细节:赤玉雕金」),各配一行小字。
|
|
63
|
+
- **侧栏侧视图**:同一道具的正侧面完整形态,标题「侧视图」。
|
|
64
|
+
- **底部颜色参考**:3–5 个圆形色卡,每个下方标注颜色名(如「赤红 / 熔金 / 绛紫」)。
|
|
65
|
+
- 点缀一两句道具铭文/短诗式文案(如「一簫起,万火归墟」)。
|
|
66
|
+
|
|
67
|
+
### 场景设定图(主场景图 + 环绕补充小图)
|
|
68
|
+
- **主场景图占画面主体**(全景建立镜头,交代空间结构与光线方向)——不要切成几张等大的长条格,
|
|
69
|
+
那样每格都是局部长图,场景信息反而看不全。
|
|
70
|
+
- **环绕补充小图(3–6 张,位置与数量不拘)**,参考版式:
|
|
71
|
+
- 右侧一列:**多角度图**(俯瞰、贴地、第一人称视线等其他机位)+ **其他时间图**(夜景 / 雨雾 /
|
|
72
|
+
逆光等不同时间段或天气下的同一场景);
|
|
73
|
+
- 底部一排:**细节格**(标志物 / 入口 / 材质纹理 / 关键道具在场景中的位置)。
|
|
74
|
+
- 每张小图下方一行标注(机位 / 时间段 / 内容,如「入口 · 夜」「雨中 · 逆光」)。
|
|
75
|
+
- 上述布局是大致参考、不强制;所有格子的光线方向、天气、色调须与主图统一
|
|
76
|
+
(展示不同时间段时在标注中说明)。
|
|
77
|
+
|
|
34
78
|
### 画面感要点
|
|
35
79
|
- 用一个**具体的可视细节**替代形容词:不写"他很悲伤",写"他把没送出的信折了三次,塞回口袋"。
|
|
36
80
|
- 每场至少一个"能被记住的画面"(记忆点)。
|
|
@@ -137,9 +137,17 @@ description: 用无限画布(Infinite Canvas)做短剧/视频创作时使用
|
|
|
137
137
|
时间窗归属靠分镜的 `dialogue` 标注(对白逐字来自剧本、按镜头分配,一镜尽量单说话人);
|
|
138
138
|
分镜缺少台词标注时按钮会降级为手动模式并提示重新生成分镜。
|
|
139
139
|
- 触发:`canvas_run_node` → 轮询;整段视频一次生成完成
|
|
140
|
-
5.
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
5. **生成角色/场景/道具参考图**:为设定集里的每个条目创建 image-gen 节点
|
|
141
|
+
(命名规范「角色-<名> 设定图」/「场景-<名> 设定图」/「道具-<名> 设定图」),
|
|
142
|
+
**prompt 必须按 `canvas-craft`「资产设定图版式」一节的排版模板写详细版式提示词,
|
|
143
|
+
禁止只写一句三视图/外观关键词**——信息量不足,撑不起后续视频的角色一致性。要点(完整模板见 canvas-craft):
|
|
144
|
+
- **角色设定图**(16:9 横版):左侧文字信息面板(姓名/性别/年龄/身份/性格/配色)+ 中央全身立绘
|
|
145
|
+
+ 脸部特征(正面+侧面特写带文字标注)+ 服饰特征标注 + 细节展示(6 格特写各配名称小字)
|
|
146
|
+
+ 表情排列(2×5 十格逐格标注表情名);所有分格中同一角色外观完全一致
|
|
147
|
+
- **道具设定图**(横竖皆可,按道具特性选):顶部道具名大标题+类别副标题;中央主图(斜 45° 悬浮+氛围光);
|
|
148
|
+
材质/部件细节特写带名称小字;侧栏侧视图;底部圆形色卡 3–5 枚带颜色名
|
|
149
|
+
- **场景设定图**(主图+环绕小图):一张主场景大图占主体,周边环绕补充小图(多角度 / 其他时间段 / 细节格,3–6 张各配标注)——大致参考不强约束
|
|
150
|
+
- 面板文字一律简体中文且清晰可读;设定集外观关键词逐格复用,保证跨格一致
|
|
143
151
|
- 需要补连任意两节点时用 `canvas_connect({ projectId, fromNode, toNode })`(端口类型必须匹配;
|
|
144
152
|
连分镜表时系统按语义自动选口:剧本/文本 → `script`,分镜表 → `prev`,不用手写端口)
|
|
145
153
|
- **布局乱了/节点重叠**:`canvas_relayout({ projectId })` 一键重排——横向数据流决定列
|
package/client.js
CHANGED
|
@@ -247,7 +247,7 @@ window.__ModuleLoader__.load({
|
|
|
247
247
|
'delProjectWarn': '将删除该项目及其全部资产(共 {n} 个),且无法恢复。请输入项目名以确认:',
|
|
248
248
|
'delProjectInput': '项目名',
|
|
249
249
|
'imgMode': '参考图模式',
|
|
250
|
-
'modeGenerations': '
|
|
250
|
+
'modeGenerations': '生图 /images/generations(支持多参考图)',
|
|
251
251
|
'modeEdits': '参考图编辑 /images/edits(多参考图上传)',
|
|
252
252
|
'catMismatch': '提示:资产分类为「{a}」,端口期望「{b}」,已保留连接',
|
|
253
253
|
'nStoryboard': '分镜表',
|
|
@@ -504,6 +504,9 @@ window.__ModuleLoader__.load({
|
|
|
504
504
|
'bitrate': '码率',
|
|
505
505
|
'bitrateAuto': '自动(浏览器默认)',
|
|
506
506
|
'runRequestFailed': '对话触发的生成未能执行',
|
|
507
|
+
'stoppedNote': '已手动停止',
|
|
508
|
+
'stoppedTaskKept': '视频任务 id 已保留,可凭 id 回查结果',
|
|
509
|
+
'stoppedToast': '已手动停止生成',
|
|
507
510
|
'apiTimeout': '请求超时(毫秒)',
|
|
508
511
|
'uploadFailed': '导入失败',
|
|
509
512
|
'syncFailed': '与 Host 同步失败',
|
|
@@ -651,7 +654,7 @@ window.__ModuleLoader__.load({
|
|
|
651
654
|
'delProjectWarn': 'This deletes the project and all its assets ({n}). Type the project name to confirm:',
|
|
652
655
|
'delProjectInput': 'Project name',
|
|
653
656
|
'imgMode': 'Reference mode',
|
|
654
|
-
'modeGenerations': '
|
|
657
|
+
'modeGenerations': 'image /images/generations (with references)',
|
|
655
658
|
'modeEdits': 'image edit /images/edits (multi-reference upload)',
|
|
656
659
|
'catMismatch': 'Note: asset category is "{a}", port expects "{b}". Connected anyway.',
|
|
657
660
|
'nStoryboard': 'Storyboard',
|
|
@@ -908,6 +911,9 @@ window.__ModuleLoader__.load({
|
|
|
908
911
|
'bitrate': 'Bitrate',
|
|
909
912
|
'bitrateAuto': 'Auto (browser default)',
|
|
910
913
|
'runRequestFailed': 'Chat-triggered generation failed',
|
|
914
|
+
'stoppedNote': 'Manually stopped',
|
|
915
|
+
'stoppedTaskKept': 'video task id kept for lookup',
|
|
916
|
+
'stoppedToast': 'Generation stopped',
|
|
911
917
|
'apiTimeout': 'Request timeout (ms)',
|
|
912
918
|
'uploadFailed': 'Import failed',
|
|
913
919
|
'syncFailed': 'Failed to sync with Host',
|
|
@@ -2086,7 +2092,8 @@ window.__ModuleLoader__.load({
|
|
|
2086
2092
|
fd.append('prompt', prompt);
|
|
2087
2093
|
fd.append('n', '1');
|
|
2088
2094
|
if (s.size) fd.append('size', s.size);
|
|
2089
|
-
|
|
2095
|
+
fd.append('watermark', 'false'); // 稳定默认:去水印优先(与 generations 路径一致)
|
|
2096
|
+
refs.forEach((b, i) => fd.append('image[]', b.blob || b, 'ref-' + i + '.png'));
|
|
2090
2097
|
// FormData 经 Response 序列化为 multipart 字节(含 boundary)→ base64 经代理原样转发
|
|
2091
2098
|
const serialized = new Response(fd);
|
|
2092
2099
|
const bodyRaw = arrayBufferToBase64(await serialized.arrayBuffer());
|
|
@@ -2107,7 +2114,16 @@ window.__ModuleLoader__.load({
|
|
|
2107
2114
|
const body = {
|
|
2108
2115
|
...(s.model ? { model: s.model } : {}),
|
|
2109
2116
|
prompt, n: 1,
|
|
2117
|
+
// 稳定默认(总是发送):去水印优先——多数图片厂商该参数默认 true 会加水印;扩展参数可覆盖
|
|
2118
|
+
watermark: false,
|
|
2110
2119
|
};
|
|
2120
|
+
// 多参考生图:generations 模式同样携带参考图(主流约定 image 数组 + data:URL,按 collectRefs 顺序对应提示词里的 [图N])。
|
|
2121
|
+
// 厂商不支持该字段时会明确报错——此时换 edits 模式或在扩展参数里调整字段名。
|
|
2122
|
+
if (refs.length) {
|
|
2123
|
+
const imgs = [];
|
|
2124
|
+
for (const b of refs) imgs.push(await blobToDataUrlScaled(b.blob || b));
|
|
2125
|
+
body.image = imgs;
|
|
2126
|
+
}
|
|
2111
2127
|
// 响应格式:留空 = 用 API 默认(响应解析同时支持 b64_json 与 url)
|
|
2112
2128
|
if (String(s.responseFormat || '').trim()) body.response_format = String(s.responseFormat).trim();
|
|
2113
2129
|
// 尺寸:留空 = 按节点上的「清晰度 × 比例」计算;填了就用你配置的值
|
|
@@ -2244,6 +2260,7 @@ window.__ModuleLoader__.load({
|
|
|
2244
2260
|
const deadline = Date.now() + 120 * 60 * 1000; // 2 小时:视频任务含排队可能很久,查询层绝不先放弃
|
|
2245
2261
|
while (Date.now() < deadline) {
|
|
2246
2262
|
await sleep(10000);
|
|
2263
|
+
if (genAborted()) throw new Error(T('stoppedNote'));
|
|
2247
2264
|
let r2 = null;
|
|
2248
2265
|
try { r2 = await fetchViaProxy(base + '/' + taskId, { method: 'GET', headers, timeoutMs: 60000, label: '查询 Seedance 任务' }); } catch { /* 网络抖动重试 */ }
|
|
2249
2266
|
if (!r2 || !r2.ok) continue; // 查询层任何失败(网关 5xx/429/断连)都不算任务失败——只等生成任务自身的终态
|
|
@@ -2294,6 +2311,7 @@ window.__ModuleLoader__.load({
|
|
|
2294
2311
|
const deadline = Date.now() + 120 * 60 * 1000; // 2 小时:查询层绝不先放弃
|
|
2295
2312
|
while (Date.now() < deadline) {
|
|
2296
2313
|
await sleep(6000);
|
|
2314
|
+
if (genAborted()) throw new Error(T('stoppedNote'));
|
|
2297
2315
|
let r2 = null;
|
|
2298
2316
|
try { r2 = await fetchViaProxy(queryEp + '?task_id=' + encodeURIComponent(taskId), { method: 'GET', headers, timeoutMs: 60000, label: '查询视频任务' }); } catch { /* 网络抖动重试 */ }
|
|
2299
2317
|
if (!r2 || !r2.ok) continue; // 查询层失败 ≠ 任务失败,只等生成任务自身的终态
|
|
@@ -2330,6 +2348,7 @@ window.__ModuleLoader__.load({
|
|
|
2330
2348
|
const deadline = Date.now() + 120 * 60 * 1000; // 2 小时:查询层绝不先放弃
|
|
2331
2349
|
while (Date.now() < deadline) {
|
|
2332
2350
|
await sleep(6000);
|
|
2351
|
+
if (genAborted()) throw new Error(T('stoppedNote'));
|
|
2333
2352
|
let r2 = null;
|
|
2334
2353
|
try { r2 = await fetchViaProxy(ep + '/' + taskId, { method: 'GET', headers, timeoutMs: 60000, label: '查询视频任务' }); } catch { /* 网络抖动重试 */ }
|
|
2335
2354
|
if (!r2 || !r2.ok) continue; // 查询层失败 ≠ 任务失败,只等生成任务自身的终态
|
|
@@ -2470,6 +2489,7 @@ window.__ModuleLoader__.load({
|
|
|
2470
2489
|
const deadline = Date.now() + 120 * 60 * 1000;
|
|
2471
2490
|
while (Date.now() < deadline) {
|
|
2472
2491
|
await sleep(step);
|
|
2492
|
+
if (genAborted()) throw new Error(T('stoppedNote'));
|
|
2473
2493
|
let r = null;
|
|
2474
2494
|
try { r = await query(); } catch { continue; }
|
|
2475
2495
|
if (!r) continue;
|
|
@@ -2903,6 +2923,7 @@ window.__ModuleLoader__.load({
|
|
|
2903
2923
|
const deadline = Date.now() + 120 * 60 * 1000; // 2 小时:查询层绝不先放弃
|
|
2904
2924
|
while (Date.now() < deadline) {
|
|
2905
2925
|
await sleep(3000);
|
|
2926
|
+
if (genAborted()) throw new Error(T('stoppedNote'));
|
|
2906
2927
|
let r2 = null;
|
|
2907
2928
|
try { r2 = await fetchViaProxy(base + '/' + taskId, { method: 'GET', headers, timeoutMs: 60000, label: '查询视频任务' }); } catch { /* 网络抖动重试 */ }
|
|
2908
2929
|
if (!r2 || !r2.ok) continue; // 查询层失败 ≠ 任务失败,只等生成任务自身的终态
|
|
@@ -3960,8 +3981,10 @@ window.__ModuleLoader__.load({
|
|
|
3960
3981
|
method: 'POST',
|
|
3961
3982
|
headers: { 'content-type': 'application/json' },
|
|
3962
3983
|
body: JSON.stringify(args),
|
|
3984
|
+
signal: genAbort.ctl ? genAbort.ctl.signal : undefined, // 停止按钮:立即中断流式读取
|
|
3963
3985
|
});
|
|
3964
3986
|
} catch (e) {
|
|
3987
|
+
if (genAborted()) throw e; // 手动打断:保持 abort 语义供上层识别
|
|
3965
3988
|
throw new Error(T('connectFailed') + ':' + String((e && e.message) || e));
|
|
3966
3989
|
}
|
|
3967
3990
|
if (res.status === 404 || !res.body) return await aiComplete({ system, user, model }); // 旧 Host 回退
|
|
@@ -3989,6 +4012,7 @@ window.__ModuleLoader__.load({
|
|
|
3989
4012
|
else if (msg.t === 'fail') throw new Error(String(msg.d || T('rpcFail')));
|
|
3990
4013
|
}
|
|
3991
4014
|
}
|
|
4015
|
+
if (genAborted()) throw new Error(T('stoppedNote')); // 流结束后仍被打断 → 走打断路径
|
|
3992
4016
|
if (!final) throw new Error(T('rpcFail'));
|
|
3993
4017
|
return final.text || '';
|
|
3994
4018
|
}
|
|
@@ -4199,6 +4223,7 @@ window.__ModuleLoader__.load({
|
|
|
4199
4223
|
onReasoning: (d) => onDelta && onDelta('reasoning', d),
|
|
4200
4224
|
onText: (d) => onDelta && onDelta('text', d),
|
|
4201
4225
|
});
|
|
4226
|
+
if (genAborted()) throw new Error(T('stoppedNote'));
|
|
4202
4227
|
// 解析 + 修复重试:模型常把剧本里的引用文字(如 喷漆"拆"字)原样写进 JSON 字符串值,
|
|
4203
4228
|
// 半角引号未转义 → JSON 非法。修复策略:把"闭合引号后不跟 JSON 结构符"的引号对换成「」。
|
|
4204
4229
|
let parsed = parseJsonBalanced(text);
|
|
@@ -4867,6 +4892,13 @@ window.__ModuleLoader__.load({
|
|
|
4867
4892
|
// 工作流执行
|
|
4868
4893
|
// ============================================================
|
|
4869
4894
|
const cancelRef = { cancelled: false };
|
|
4895
|
+
const genAbort = { ctl: null }; // 当前生成的 AbortController(停止按钮真正中断底层流/轮询)
|
|
4896
|
+
let genActive = 0; // 活跃生成计数(工作流与单节点触发统一计数)
|
|
4897
|
+
function genAborted() { return !!(genAbort.ctl && genAbort.ctl.signal && genAbort.ctl.signal.aborted); }
|
|
4898
|
+
function stopActiveGen() {
|
|
4899
|
+
cancelRef.cancelled = true;
|
|
4900
|
+
if (genAbort.ctl) { try { genAbort.ctl.abort(); } catch { /* 忽略 */ } }
|
|
4901
|
+
}
|
|
4870
4902
|
|
|
4871
4903
|
/** 分镜节点的剧本来源:**连线上游与手动粘贴二选一**——
|
|
4872
4904
|
* 连了线就只用连线(上游没生成完会明确报错,绝不偷用粘贴的文本混着来)。 */
|
|
@@ -5052,9 +5084,13 @@ window.__ModuleLoader__.load({
|
|
|
5052
5084
|
async function runGenNode(n) {
|
|
5053
5085
|
const type = n.type;
|
|
5054
5086
|
const settings = loadSettings(); // 模型解析(供应商+模型)用
|
|
5087
|
+
const ctl = new AbortController();
|
|
5088
|
+
genAbort.ctl = ctl;
|
|
5089
|
+
genActive++;
|
|
5055
5090
|
update((s) => {
|
|
5056
5091
|
const nd = s.graph.nodes[n.id];
|
|
5057
|
-
if (nd) { nd.data.status = 'running'; nd.data.error = null; }
|
|
5092
|
+
if (nd) { nd.data.status = 'running'; nd.data.error = null; nd.data.stopNote = null; }
|
|
5093
|
+
s.running = true; // 单节点触发(对话/节点按钮)也显示停止按钮
|
|
5058
5094
|
});
|
|
5059
5095
|
flushSave(); // 立刻落盘 + 推送 Host:切走画面/对话查询都能看到"运行中"
|
|
5060
5096
|
try {
|
|
@@ -5142,12 +5178,28 @@ window.__ModuleLoader__.load({
|
|
|
5142
5178
|
}
|
|
5143
5179
|
return true;
|
|
5144
5180
|
} catch (err) {
|
|
5181
|
+
const stopped = !!(ctl.signal && ctl.signal.aborted);
|
|
5145
5182
|
update((s) => {
|
|
5146
5183
|
const nd = s.graph.nodes[n.id];
|
|
5147
|
-
if (nd)
|
|
5184
|
+
if (!nd) return;
|
|
5185
|
+
if (stopped) {
|
|
5186
|
+
// 手动打断:回到待命而不是报错;视频任务 id 已持久化,可凭 id 回查结果
|
|
5187
|
+
nd.data.status = 'idle';
|
|
5188
|
+
nd.data.error = null;
|
|
5189
|
+
if (nd.type === 'script' || nd.type === 'storyboard') nd.data.aiStatus = 'idle';
|
|
5190
|
+
nd.data.stopNote = T('stoppedNote') + (nd.data.taskId ? ';' + T('stoppedTaskKept') : '');
|
|
5191
|
+
} else {
|
|
5192
|
+
nd.data.status = 'error';
|
|
5193
|
+
nd.data.error = String((err && err.message) || err);
|
|
5194
|
+
}
|
|
5148
5195
|
});
|
|
5149
5196
|
flushSave(); // 让对话侧的 canvas_node_status 也能读到失败原因
|
|
5197
|
+
if (stopped) toast(T('stoppedToast'));
|
|
5150
5198
|
return false;
|
|
5199
|
+
} finally {
|
|
5200
|
+
genActive = Math.max(0, genActive - 1);
|
|
5201
|
+
if (genActive === 0) update((s) => { s.running = false; });
|
|
5202
|
+
if (genAbort.ctl === ctl) genAbort.ctl = null;
|
|
5151
5203
|
}
|
|
5152
5204
|
}
|
|
5153
5205
|
function commitGenResult(nodeId, assetId, demo, kind) {
|
|
@@ -5199,7 +5251,10 @@ window.__ModuleLoader__.load({
|
|
|
5199
5251
|
flushSave();
|
|
5200
5252
|
}
|
|
5201
5253
|
}
|
|
5202
|
-
function stopRun() {
|
|
5254
|
+
function stopRun() {
|
|
5255
|
+
stopActiveGen();
|
|
5256
|
+
toast(T('stoppedToast'));
|
|
5257
|
+
}
|
|
5203
5258
|
|
|
5204
5259
|
// ============================================================
|
|
5205
5260
|
// 轻量提示
|
|
@@ -6201,10 +6256,10 @@ window.__ModuleLoader__.load({
|
|
|
6201
6256
|
}),
|
|
6202
6257
|
h(GenSettingsRow, { key: 'set', n, kind }),
|
|
6203
6258
|
h('button', {
|
|
6204
|
-
key: 'gen', type: 'button', className: 'dshic-gen',
|
|
6259
|
+
key: 'gen', type: 'button', className: 'dshic-gen',
|
|
6205
6260
|
onPointerDown: (e) => e.stopPropagation(),
|
|
6206
|
-
onClick: () => { runGenNode(n); },
|
|
6207
|
-
}, n.data.status === 'running' ?
|
|
6261
|
+
onClick: () => { if (n.data.status === 'running') { stopActiveGen(); toast(T('stoppedToast')); } else { runGenNode(n); } },
|
|
6262
|
+
}, n.data.status === 'running' ? [Ic('stop', 12), T('stop')] : [Ic('play', 12), T('generate')]),
|
|
6208
6263
|
h('div', { key: 'r', className: 'dshic-node-row' },
|
|
6209
6264
|
h('div', { className: 'dshic-flex' }),
|
|
6210
6265
|
n.data.demo && n.data.assetId ? h('span', { className: 'dshic-tag demo' }, T('demoTag')) : null,
|
|
@@ -6233,6 +6288,7 @@ window.__ModuleLoader__.load({
|
|
|
6233
6288
|
h(RefRows, { key: 'refrows', n, portId: 'ref' }),
|
|
6234
6289
|
h(MentionInfo, { key: 'mn', n }),
|
|
6235
6290
|
n.data.genWarn ? h('div', { key: 'gw', className: 'dshic-hint', style: { color: 'var(--dsw-alias-state-warning-primary, #d08700)' } }, '⚠ ' + n.data.genWarn) : null,
|
|
6291
|
+
n.data.stopNote ? h('div', { key: 'sn', className: 'dshic-hint' }, '⏹ ' + n.data.stopNote) : null,
|
|
6236
6292
|
n.data.error ? h('div', { key: 'err', className: 'dshic-err' }, n.data.error) : null,
|
|
6237
6293
|
assetUrlV && kind === 'image'
|
|
6238
6294
|
? h('img', { key: 'img', className: 'dshic-preview', src: assetUrlV, draggable: false, 'data-no-drag': '1', style: { cursor: 'zoom-in' }, onPointerDown: (e) => e.stopPropagation(), onClick: (e) => { e.stopPropagation(); openLightbox('image', assetUrlV, n.data.label); } })
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ninkch/dsh-infinite-canvas",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.17",
|
|
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",
|