@ninkch/dsh-infinite-canvas 0.18.0 → 0.18.2
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 +20 -19
- package/assets/canvas-studio/SKILL.md +5 -6
- package/client.js +92 -24
- package/index.js +7 -1
- package/package.json +1 -1
|
@@ -34,34 +34,34 @@ B:……
|
|
|
34
34
|
|
|
35
35
|
## 二·五、资产设定图版式(角色/道具/场景的专业排版,拒绝简单三视图)
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
简单三视图信息量不足——设定图要做成**专业设定集排版**:主图 + 特写 + 表情/色卡分区呈现,
|
|
38
|
+
一图读懂资产的全部视觉设定。**版面里绝不渲染成段文字**:文字渲染型模型画长文本必然乱码(实测),
|
|
39
|
+
面板文字只允许 ≤4 字短标签(面板名 / 表情名 / 颜色名)。同一资产在所有分格中外观**完全一致**——
|
|
40
|
+
**已连接参考图时,参考图形象是唯一事实**,逐格严格复制;未连接参考图时逐格复用设定集外观关键词。
|
|
40
41
|
|
|
41
42
|
### 角色设定图(16:9 横版——主图与三视图占大版面)
|
|
42
43
|
- **版面主体(约三分之二)给画面**:中央大幅全身立绘主图(正面站姿),主图下方一排
|
|
43
44
|
**三视图(正面 / 侧面 / 背面 全身)**——三视图是必须项,后续视频要靠它保证侧背面的准确形态。
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
面板文字一律简体中文清晰可读。
|
|
45
|
+
- **右侧窄栏做纯视觉辅助(不渲染文字段落)**:
|
|
46
|
+
- 脸部特征:一格正面特写(或正 / 侧两小格);
|
|
47
|
+
- 表情条:一行 4–6 个小头像,每格下方只标表情名(两字,按角色性格取舍,如 温柔 / 惊讶 / 坚定);
|
|
48
|
+
- 细节特写(可选):1–2 格(项圈 / 挂牌 / 发饰),格内无文字。
|
|
49
|
+
- **禁止**:姓名 / 性别 / 年龄等属性文本、服饰说明文字、旁白或诗句——这些属于设定集,
|
|
50
|
+
画进图里必成乱码,且会挤出角色形象的一致性注意力。
|
|
51
|
+
- 所有分格中同一角色外观完全一致(以参考图形象为准逐格复制)。
|
|
52
52
|
- 提示词结构示例:「角色人设图(16:9 横版设定集排版):中央大幅全身立绘,主图下方三视图一排
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
(正面/侧面/背面全身);右侧窄栏:脸部特写一格、表情小头像一排(每格下方只标两字表情名);
|
|
54
|
+
画面中不出现任何成段文字。」
|
|
55
55
|
|
|
56
56
|
### 道具设定图(横竖皆可——按道具特性选,不限制)
|
|
57
57
|
- **画幅选择**:长条形/单件道具(剑、箫、杖)竖版更能展示完整形态;
|
|
58
58
|
组合道具、带场景感或横向结构的道具用横版能并列更多细节与色卡——排版逻辑不变,画幅自由。
|
|
59
|
-
-
|
|
59
|
+
- **顶部**:道具名大标题(≤6 字)。
|
|
60
60
|
- **中央主图**:道具斜 45° 悬浮展示,带氛围光/粒子效果,占画面主体。
|
|
61
|
-
- **细节特写 2–3
|
|
62
|
-
-
|
|
63
|
-
- **底部颜色参考**:3–5
|
|
64
|
-
-
|
|
61
|
+
- **细节特写 2–3 格**:材质纹理、关键部件,格内无文字。
|
|
62
|
+
- **侧栏侧视图**:同一道具的正侧面完整形态。
|
|
63
|
+
- **底部颜色参考**:3–5 个圆形色卡,每个下方标注颜色名(≤3 字,如「赤红 / 熔金 / 绛紫」)。
|
|
64
|
+
- **禁止**:铭文 / 短诗 / 说明文字渲染(易成乱码)。
|
|
65
65
|
|
|
66
66
|
### 场景设定图(主场景图 + 环绕补充小图)
|
|
67
67
|
- **主场景图占画面主体**(全景建立镜头,交代空间结构与光线方向)——不要切成几张等大的长条格,
|
|
@@ -70,9 +70,10 @@ B:……
|
|
|
70
70
|
- 右侧一列:**多角度图**(俯瞰、贴地、第一人称视线等其他机位)+ **其他时间图**(夜景 / 雨雾 /
|
|
71
71
|
逆光等不同时间段或天气下的同一场景);
|
|
72
72
|
- 底部一排:**细节格**(标志物 / 入口 / 材质纹理 / 关键道具在场景中的位置)。
|
|
73
|
-
-
|
|
73
|
+
- 每张小图下方一行**短标注(≤6 字**,如「入口·夜」「雨中·逆光」)。
|
|
74
74
|
- 上述布局是大致参考、不强制;所有格子的光线方向、天气、色调须与主图统一
|
|
75
75
|
(展示不同时间段时在标注中说明)。
|
|
76
|
+
- **禁止**:成段说明文字。
|
|
76
77
|
|
|
77
78
|
### 画面感要点
|
|
78
79
|
- 用一个**具体的可视细节**替代形容词:不写"他很悲伤",写"他把没送出的信折了三次,塞回口袋"。
|
|
@@ -142,12 +142,11 @@ description: 用无限画布(Infinite Canvas)做短剧/视频创作时使用
|
|
|
142
142
|
**prompt 必须按 `canvas-craft`「资产设定图版式」一节的排版模板写详细版式提示词,
|
|
143
143
|
禁止只写一句三视图/外观关键词**——信息量不足,撑不起后续视频的角色一致性。要点(完整模板见 canvas-craft):
|
|
144
144
|
- **角色设定图**(16:9 横版):**主图 + 三视图一排(正/侧/背全身,必须项)占大版面(约 2/3)**;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
-
|
|
150
|
-
- 面板文字一律简体中文且清晰可读;设定集外观关键词逐格复用,保证跨格一致
|
|
145
|
+
右窄栏纯视觉辅助(脸部特写一格 / 表情条一行小头像,每格只标两字表情名);细节格可选
|
|
146
|
+
- **道具设定图**(横竖皆可,按道具特性选):顶部道具名大标题(≤6 字);中央主图(斜 45° 悬浮+氛围光);
|
|
147
|
+
细节特写 2–3 格(格内无文字);侧栏侧视图;底部圆形色卡 3–5 枚(色名 ≤3 字)
|
|
148
|
+
- **场景设定图**(主图+环绕小图):一张主场景大图占主体,周边环绕补充小图(多角度 / 其他时间段 / 细节格,3–6 张各配 ≤6 字短标注)——大致参考不强约束
|
|
149
|
+
- **面板内绝不渲染成段文字**(长文本必乱码):只允许 ≤4 字短标签;已连接参考图时参考图形象是唯一事实,逐格严格复制
|
|
151
150
|
- 需要补连任意两节点时用 `canvas_connect({ projectId, fromNode, toNode })`(端口类型必须匹配;
|
|
152
151
|
连分镜表时系统按语义自动选口:剧本/文本 → `script`,分镜表 → `prev`,不用手写端口)
|
|
153
152
|
- **布局乱了/节点重叠**:`canvas_relayout({ projectId })` 一键重排——横向数据流决定列
|
package/client.js
CHANGED
|
@@ -1377,7 +1377,10 @@ window.__ModuleLoader__.load({
|
|
|
1377
1377
|
}
|
|
1378
1378
|
return out;
|
|
1379
1379
|
}
|
|
1380
|
-
function saveSettings(s) {
|
|
1380
|
+
function saveSettings(s) {
|
|
1381
|
+
lsSet(LS_SETTINGS, s);
|
|
1382
|
+
notify(); // 设置即改即存(无保存按钮),但必须通知画布重渲染——否则节点上的模型下拉/星标一直是旧快照
|
|
1383
|
+
}
|
|
1381
1384
|
|
|
1382
1385
|
// ---------- 供应商 + 模型(图片/视频):扁平视图与解析 ----------
|
|
1383
1386
|
let legacyImageApiRefMap = {}; // 旧扁平配置 id → 'providerId|modelId'(节点字段懒迁移用)
|
|
@@ -1600,18 +1603,21 @@ window.__ModuleLoader__.load({
|
|
|
1600
1603
|
const rest = nm.slice(pref.length);
|
|
1601
1604
|
return rest === '' || !/[\p{L}\p{N}]/u.test(rest[0]);
|
|
1602
1605
|
}
|
|
1603
|
-
/** 生成用提示词:@标记去掉 + [图N] 绑定 + 命中实体的设定描述。
|
|
1604
|
-
|
|
1606
|
+
/** 生成用提示词:@标记去掉 + [图N] 绑定 + 命中实体的设定描述。
|
|
1607
|
+
* light=true(图片生成专用):只保留风格行与 [图N] 名称绑定——参考图本身就是外观事实源,
|
|
1608
|
+
* 内联设定全文会稀释参考图约束,且文字渲染型模型(Qwen-Image)会把追加文本直接画进画面(用户实测:
|
|
1609
|
+
* 设定集全文被渲染成画面右下角的乱码文字块、剧情词凭空生成道具)。视频路径不用 light(不渲染文字,上下文有益)。 */
|
|
1610
|
+
function buildEffectivePrompt(prompt, refs, entities, bible, light) {
|
|
1605
1611
|
const zh = effLang() !== 'en';
|
|
1606
1612
|
const bind = refs.map((r, i) => {
|
|
1607
1613
|
if (r.entity) {
|
|
1608
1614
|
const ent = matchBibleEntity(r.entity, bible);
|
|
1609
|
-
const desc =
|
|
1610
|
-
return `[图${i + 1}] ${entityLabelOf(ent
|
|
1615
|
+
const desc = light ? '' : ((ent && ent.desc) || '');
|
|
1616
|
+
return `[图${i + 1}] ${entityLabelOf((ent || {}).kind || 'prop')} ${r.entity}${desc ? '(' + desc + ')' : ''}`;
|
|
1611
1617
|
}
|
|
1612
1618
|
return `[图${i + 1}] ${catLabel(r.category)}:${r.name}`;
|
|
1613
1619
|
}).join(';');
|
|
1614
|
-
const entLines = entities.map((e) => `${entityLabelOf(e.kind)} ${e.name}:${e.desc}`);
|
|
1620
|
+
const entLines = light ? [] : entities.map((e) => `${entityLabelOf(e.kind)} ${e.name}:${e.desc}`);
|
|
1615
1621
|
return [
|
|
1616
1622
|
(bible.style || '').trim() ? (zh ? '风格:' : 'Style: ') + bible.style.trim() : '',
|
|
1617
1623
|
bind ? (zh ? '参考图 → ' : 'Refs -> ') + bind : '',
|
|
@@ -2151,10 +2157,21 @@ window.__ModuleLoader__.load({
|
|
|
2151
2157
|
let base = s.baseUrl.replace(/\/+$/, '').replace(/\/images\/generations$/, '');
|
|
2152
2158
|
if (!base.replace(/^https?:\/\//i, '').includes('/')) base = base + '/v1';
|
|
2153
2159
|
const refs = refBlobs || [];
|
|
2154
|
-
const auth = s.apiKey ? { Authorization: 'Bearer ' + s.apiKey } : {};
|
|
2155
|
-
|
|
2160
|
+
const auth = s.apiKey ? { Authorization: 'Bearer ' + String(s.apiKey).trim() } : {};
|
|
2161
|
+
// 节点级扩展参数(手动参数框)与供应商扩展参数合并(节点优先)
|
|
2162
|
+
const nodeExtra = String((opts && opts.nodeExtra) || '').trim();
|
|
2163
|
+
let extraSrc = String(s.extra || '');
|
|
2164
|
+
if (nodeExtra) {
|
|
2165
|
+
try {
|
|
2166
|
+
extraSrc = JSON.stringify({ ...(extraSrc.trim() ? JSON.parse(extraSrc) : {}), ...JSON.parse(nodeExtra) });
|
|
2167
|
+
} catch (e) { throw new Error('节点扩展参数需为 JSON 对象:' + ((e && e.message) || e)); }
|
|
2168
|
+
}
|
|
2169
|
+
const extra = parseExtraParams(extraSrc); // 厂商专属参数(留空不发送)
|
|
2156
2170
|
// 优云智算(compshare)任务式:Qwen-Image 异步任务(提交 → 轮询 → output_url 下载)
|
|
2157
|
-
if (s.mode === 'compshare') return await compshareImageTask(prompt, refs, s, opts);
|
|
2171
|
+
if (s.mode === 'compshare') return await compshareImageTask(prompt, refs, { ...s, extra: extraSrc }, opts);
|
|
2172
|
+
// 协议自动路由:优云智算的图片 API 只有任务式(/image/v1/tasks)——误配成通用模式
|
|
2173
|
+
// 会在 /v1/images/generations 上拿到 401 "invalid api key"(该面不存在),按域名自动切任务式
|
|
2174
|
+
try { if (/compshare\.cn$/i.test(new URL(base).hostname)) return await compshareImageTask(prompt, refs, { ...s, extra: extraSrc }, opts); } catch { /* URL 异常由后续流程报错 */ }
|
|
2158
2175
|
const upstreamTimeout = Number(s.timeoutMs) > 0 ? Number(s.timeoutMs) : 120000; // 商汤等同步接口 20s+,默认 120s
|
|
2159
2176
|
// 参考图编辑模式:multipart 上传到 /images/edits(OpenAI gpt-image-1 兼容)
|
|
2160
2177
|
if (refs.length && s.mode === 'edits') {
|
|
@@ -2248,7 +2265,7 @@ window.__ModuleLoader__.load({
|
|
|
2248
2265
|
let base = String(s.baseUrl || '').trim().replace(/\/+$/, '');
|
|
2249
2266
|
base = base.replace(/\/image\/v1$/i, ''); // 粘贴了完整路径前缀也兼容
|
|
2250
2267
|
if (!base) throw new Error('未配置图片 API 地址(优云智算:https://cp.compshare.cn)');
|
|
2251
|
-
const headers = { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: 'Bearer ' + (s.apiKey || '') };
|
|
2268
|
+
const headers = { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: 'Bearer ' + String(s.apiKey || '').trim() };
|
|
2252
2269
|
const extra = parseExtraParams(s.extra); // 透传:optimize_prompt / use_context_ir 等任务级字段
|
|
2253
2270
|
const api = async (method, path, body, idem) => {
|
|
2254
2271
|
const h = { ...headers };
|
|
@@ -2818,7 +2835,7 @@ window.__ModuleLoader__.load({
|
|
|
2818
2835
|
const ex = (() => { try { return parseExtraParams(s.extra) || {}; } catch (e) { throw new Error('扩展参数配置有误:' + ((e && e.message) || e)); } })();
|
|
2819
2836
|
// 路径风格:优云智算网关挂 /minimax/v2/...,MiniMax 官方裸 /v2/...;扩展参数 pathPrefix 可显式覆盖
|
|
2820
2837
|
const prefix = String(ex.pathPrefix || (/compshare/i.test(base) ? '/minimax' : ''));
|
|
2821
|
-
const headers = { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: 'Bearer ' + (s.apiKey || '') };
|
|
2838
|
+
const headers = { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: 'Bearer ' + String(s.apiKey || '').trim() };
|
|
2822
2839
|
// 分辨率大小写敏感(480P/768P/1080P/2K/4K):画布的 720P 语义等同原生 768P,显式映射不依赖厂商静默降级
|
|
2823
2840
|
const RESO = { '480P': '480P', '720P': '768P', '768P': '768P', '1080P': '1080P', '2K': '2K', '4K': '4K' };
|
|
2824
2841
|
const resolution = String(ex.resolution || RESO[String(o.resolution || '').toUpperCase()] || '768P');
|
|
@@ -3063,6 +3080,14 @@ window.__ModuleLoader__.load({
|
|
|
3063
3080
|
};
|
|
3064
3081
|
async function apiVideo(prompt, frameBlob, s, opts) {
|
|
3065
3082
|
const o = opts || {};
|
|
3083
|
+
// 节点级扩展参数(手动参数框)与供应商扩展参数合并(节点优先),各 runner 的 mergeExtra 自动生效
|
|
3084
|
+
const nodeExtra = String(o.nodeExtra || '').trim();
|
|
3085
|
+
if (nodeExtra) {
|
|
3086
|
+
try {
|
|
3087
|
+
const baseExtra = String(s.extra || '').trim();
|
|
3088
|
+
s = { ...s, extra: JSON.stringify({ ...(baseExtra ? JSON.parse(baseExtra) : {}), ...JSON.parse(nodeExtra) }) };
|
|
3089
|
+
} catch (e) { throw new Error('节点扩展参数需为 JSON 对象:' + ((e && e.message) || e)); }
|
|
3090
|
+
}
|
|
3066
3091
|
// 衔接模式:auto=尾帧降级为参考图(保留实体参考图);hard=硬首帧(放弃参考图,画面绝对承接);
|
|
3067
3092
|
// off=忽略首帧。网关禁止 first_frame 与 reference_image 混用,故 hard 模式必须放弃参考图。
|
|
3068
3093
|
if (o.frameMode === 'off') frameBlob = null;
|
|
@@ -5355,11 +5380,11 @@ window.__ModuleLoader__.load({
|
|
|
5355
5380
|
// 参考图 = 连线(用户顺序)+ @引用命中的设定集资产(按出现顺序)
|
|
5356
5381
|
const { refs, entities } = await collectRefs(n, prompt, bible);
|
|
5357
5382
|
const metas = refs.map((r) => ({ blob: r.blob, name: r.name, category: r.category, assetId: r.assetId }));
|
|
5358
|
-
const p = buildEffectivePrompt(prompt, refs, entities, bible);
|
|
5383
|
+
const p = buildEffectivePrompt(prompt, refs, entities, bible, true); // light:图片不内联设定全文(防文字渲染污染 + 参考图约束稀释)
|
|
5359
5384
|
// 模型解析:节点选择优先,未指定路由到星标默认模型;都没有 = 明确报错(不再静默走演示模式)
|
|
5360
5385
|
const mcfg = resolveImageModelCfg(settings, nodeImageModelRef(n));
|
|
5361
5386
|
if (!mcfg) throw new Error(T('errNoImageModel'));
|
|
5362
|
-
const blob = await apiImage(p, metas, mcfg, { size: n.data.size, ratio: n.data.ratio });
|
|
5387
|
+
const blob = await apiImage(p, metas, mcfg, { size: n.data.size, ratio: n.data.ratio, nodeExtra: n.data.imgExtra });
|
|
5363
5388
|
const rec = await saveAssetRecord(S.activeId, 'image', blob, {
|
|
5364
5389
|
name: assetNameFor(n, entities, 'image'),
|
|
5365
5390
|
prompt: stripMentionMarks(prompt), effectivePrompt: p,
|
|
@@ -5382,6 +5407,7 @@ window.__ModuleLoader__.load({
|
|
|
5382
5407
|
frameMode: n.data.frameMode || 'auto', // auto=尾帧降级为参考图 / hard=硬首帧(放弃参考图) / off=忽略首帧
|
|
5383
5408
|
refs: refs.map((r) => r.blob),
|
|
5384
5409
|
refNames: refs.map((r) => r.entity || r.name || ''), // 跨镜衔接标注用(图2..N 的实体名)
|
|
5410
|
+
nodeExtra: n.data.vidExtra, // 节点级扩展参数(手动参数框,如 {"use_context_ir":true})
|
|
5385
5411
|
// 任务提交成功即持久化 task id:轮询中断/页面关闭后仍可凭 id 回查结果(钱花了就必须能找回)
|
|
5386
5412
|
onTask: (tid, qurl) => {
|
|
5387
5413
|
update((s) => { const nd = s.graph.nodes[n.id]; if (nd) { nd.data.taskId = String(tid || ''); nd.data.taskQueryUrl = String(qurl || ''); } });
|
|
@@ -6308,6 +6334,17 @@ window.__ModuleLoader__.load({
|
|
|
6308
6334
|
],
|
|
6309
6335
|
h('div', { className: 'dshic-flex' }),
|
|
6310
6336
|
),
|
|
6337
|
+
h('input', {
|
|
6338
|
+
key: 'nextra', className: 'dshic-input', value: (kind === 'video' ? n.data.vidExtra : n.data.imgExtra) || '',
|
|
6339
|
+
placeholder: '扩展参数 JSON,如 {"use_context_ir":true}',
|
|
6340
|
+
title: '节点级扩展参数:合并进供应商扩展参数,节点值优先',
|
|
6341
|
+
onChange: (e) => update((s) => {
|
|
6342
|
+
const nd = s.graph.nodes[n.id];
|
|
6343
|
+
if (!nd) return;
|
|
6344
|
+
if (kind === 'video') nd.data.vidExtra = e.target.value; else nd.data.imgExtra = e.target.value;
|
|
6345
|
+
}),
|
|
6346
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
6347
|
+
}),
|
|
6311
6348
|
);
|
|
6312
6349
|
}
|
|
6313
6350
|
|
|
@@ -6570,6 +6607,36 @@ window.__ModuleLoader__.load({
|
|
|
6570
6607
|
})();
|
|
6571
6608
|
return () => { cancelled = true; };
|
|
6572
6609
|
}, [kind, n.data.assetId, n.data.label, S.assetsTick]);
|
|
6610
|
+
// 历史条自愈:已被删除的资产(渲染成 … 的幽灵格)自动从节点 history 剔除;
|
|
6611
|
+
// 服务端不可达时跳过本轮(宁可不修剪,绝不误删有效历史)
|
|
6612
|
+
useEffect(() => {
|
|
6613
|
+
const histList = n.data.history || [];
|
|
6614
|
+
if (!histList.length) return;
|
|
6615
|
+
let ok = true;
|
|
6616
|
+
(async () => {
|
|
6617
|
+
let alive = null;
|
|
6618
|
+
if (S.store) {
|
|
6619
|
+
const r = await hostRpc('assetsGet', { ids: histList.map((x) => x.assetId) }).catch(() => null);
|
|
6620
|
+
if (!r) return;
|
|
6621
|
+
alive = new Set((r.items || []).map((x) => x.id));
|
|
6622
|
+
} else {
|
|
6623
|
+
const recs = await Promise.all(histList.map((x) => idbGetRaw(x.assetId).catch(() => null)));
|
|
6624
|
+
alive = new Set(histList.filter((_, i) => recs[i]).map((x) => x.assetId));
|
|
6625
|
+
}
|
|
6626
|
+
if (!ok) return;
|
|
6627
|
+
const dead = histList.filter((x) => !alive.has(x.assetId)).map((x) => x.assetId);
|
|
6628
|
+
if (!dead.length) return;
|
|
6629
|
+
update((s) => {
|
|
6630
|
+
const nd = s.graph.nodes[n.id];
|
|
6631
|
+
if (!nd || !Array.isArray(nd.data.history)) return;
|
|
6632
|
+
const next = nd.data.history.filter((x) => !dead.includes(x.assetId));
|
|
6633
|
+
if (next.length === nd.data.history.length) return;
|
|
6634
|
+
nd.data.history = next;
|
|
6635
|
+
if (nd.data.assetId && dead.includes(nd.data.assetId)) nd.data.assetId = next.length ? next[next.length - 1].assetId : null;
|
|
6636
|
+
});
|
|
6637
|
+
})();
|
|
6638
|
+
return () => { ok = false; };
|
|
6639
|
+
}, [n.id, S.assetsTick, S.store]);
|
|
6573
6640
|
const promptEdge = S.graph.edges.find((x) => x.toNode === n.id && x.toPort === 'prompt');
|
|
6574
6641
|
const promptSrc = promptEdge && S.graph.nodes[promptEdge.fromNode];
|
|
6575
6642
|
const fromStoryboard = !!(promptSrc && promptSrc.type === 'storyboard');
|
|
@@ -8040,7 +8107,11 @@ window.__ModuleLoader__.load({
|
|
|
8040
8107
|
assetMem.delete(a.id);
|
|
8041
8108
|
update((s) => {
|
|
8042
8109
|
for (const nd of Object.values(s.graph.nodes)) {
|
|
8043
|
-
|
|
8110
|
+
const d = nd.data;
|
|
8111
|
+
if (!d) continue;
|
|
8112
|
+
if (d.assetId === a.id) { d.assetId = null; d.status = 'idle'; }
|
|
8113
|
+
if (d.lastAssetId === a.id) d.lastAssetId = null; // 尾帧引用同步清理
|
|
8114
|
+
if (Array.isArray(d.history) && d.history.some((x) => x.assetId === a.id)) d.history = d.history.filter((x) => x.assetId !== a.id); // 历史条同步清理
|
|
8044
8115
|
}
|
|
8045
8116
|
s.assetsTick++;
|
|
8046
8117
|
});
|
|
@@ -8353,12 +8424,12 @@ window.__ModuleLoader__.load({
|
|
|
8353
8424
|
}
|
|
8354
8425
|
|
|
8355
8426
|
/** 图片/视频模型多配置列表:名称 / 地址 / Key / 模型(/模式),可增删、设默认。 */
|
|
8356
|
-
function Field({ label, value, onChange, placeholder, password }) {
|
|
8427
|
+
function Field({ label, value, onChange, placeholder, password, tip }) {
|
|
8357
8428
|
// 敏感字段(API Key):默认脱敏,点击小眼睛切换明文显示
|
|
8358
8429
|
const [reveal, setReveal] = useState(false);
|
|
8359
8430
|
if (!password) {
|
|
8360
8431
|
return h('div', { className: 'dshic-field' },
|
|
8361
|
-
h('span', { className: 'dshic-field-label' }, label),
|
|
8432
|
+
h('span', { className: 'dshic-field-label' }, label, tip ? InfoTip(tip) : null),
|
|
8362
8433
|
h('input', {
|
|
8363
8434
|
className: 'dshic-input', type: 'text', value, placeholder: placeholder || '',
|
|
8364
8435
|
onChange: (e) => onChange(e.target.value),
|
|
@@ -8448,14 +8519,15 @@ window.__ModuleLoader__.load({
|
|
|
8448
8519
|
),
|
|
8449
8520
|
open ? [
|
|
8450
8521
|
kind === 'video' ? h('div', { key: 'vendor', className: 'dshic-field' },
|
|
8451
|
-
h('
|
|
8522
|
+
h('div', { className: 'dshic-row' },
|
|
8523
|
+
h('span', { className: 'dshic-field-label' }, T('vendorLabel')),
|
|
8524
|
+
InfoTip((VIDEO_VENDORS[detectVideoVendor(p)] || {}).note || '')),
|
|
8452
8525
|
h('select', {
|
|
8453
8526
|
className: 'dshic-select', value: detectVideoVendor(p),
|
|
8454
8527
|
onPointerDown: (e) => e.stopPropagation(),
|
|
8455
8528
|
onChange: (e) => { const v = e.target.value; const vd = VIDEO_VENDORS[v]; patchProvider(p.id, 'vendor', v); if (vd && vd.base && !p[urlKey]) patchProvider(p.id, urlKey, vd.base); },
|
|
8456
8529
|
},
|
|
8457
8530
|
Object.entries(VIDEO_VENDORS).map(([id, vd]) => h('option', { key: id, value: id }, vd.label))),
|
|
8458
|
-
h('div', { className: 'dshic-hint' }, (VIDEO_VENDORS[detectVideoVendor(p)] || {}).note || ''),
|
|
8459
8531
|
) : null,
|
|
8460
8532
|
h(Field, { key: 'u', label: T('baseUrl'), value: p[urlKey] || '', onChange: (v) => patchProvider(p.id, urlKey, v), placeholder: kind === 'video' ? ((VIDEO_VENDORS[detectVideoVendor(p)] || {}).base || urlPh) : urlPh }),
|
|
8461
8533
|
h(Field, { key: 'k', label: T('apiKey'), value: p.apiKey || '', onChange: (v) => patchProvider(p.id, 'apiKey', v), placeholder: kind === 'video' ? ((VIDEO_VENDORS[detectVideoVendor(p)] || {}).keyPh || 'sk-…') : 'sk-…', password: true }),
|
|
@@ -8475,8 +8547,7 @@ window.__ModuleLoader__.load({
|
|
|
8475
8547
|
h('span', {}, T('vidAudioOpt'))) : null,
|
|
8476
8548
|
],
|
|
8477
8549
|
(kind === 'image' || detectVideoVendor(p) === 'openai' || detectVideoVendor(p) === 'seedance') ? [
|
|
8478
|
-
h(Field, { key: 'extra', label: T('cfgExtra'), value: p.extra || '', onChange: (v) => patchProvider(p.id, 'extra', v), placeholder: '{"watermark":false,"prompt_extend":false}' }),
|
|
8479
|
-
h('div', { className: 'dshic-hint' }, T('cfgExtraHint')),
|
|
8550
|
+
h(Field, { key: 'extra', label: T('cfgExtra'), value: p.extra || '', onChange: (v) => patchProvider(p.id, 'extra', v), placeholder: '{"watermark":false,"prompt_extend":false}', tip: T('cfgExtraHint') }),
|
|
8480
8551
|
] : null,
|
|
8481
8552
|
h('div', { className: 'dshic-row', style: { marginTop: 4 } },
|
|
8482
8553
|
h('span', { className: 'dshic-field-label', style: { flex: 1, minWidth: 0 } }, T('modelsLabel')),
|
|
@@ -8637,10 +8708,7 @@ window.__ModuleLoader__.load({
|
|
|
8637
8708
|
h('span', {}, T('seedanceWatermark')))
|
|
8638
8709
|
: null,
|
|
8639
8710
|
(kind === 'image' || kind === 'video')
|
|
8640
|
-
? h(Field, { key: 'extra', label: T('cfgExtra'), value: cfg.extra || '', onChange: (v) => patch(cfg.id, 'extra', v), placeholder: '{"watermark":false,"prompt_extend":false}' })
|
|
8641
|
-
: null,
|
|
8642
|
-
(kind === 'image' || kind === 'video')
|
|
8643
|
-
? h('div', { className: 'dshic-hint' }, T('cfgExtraHint'))
|
|
8711
|
+
? h(Field, { key: 'extra', label: T('cfgExtra'), value: cfg.extra || '', onChange: (v) => patch(cfg.id, 'extra', v), placeholder: '{"watermark":false,"prompt_extend":false}', tip: T('cfgExtraHint') })
|
|
8644
8712
|
: null,
|
|
8645
8713
|
kind === 'tts'
|
|
8646
8714
|
? h(Field, { key: 'voice', label: T('voice'), value: cfg.voice || '', onChange: (v) => patch(cfg.id, 'voice', v), placeholder: 'alloy' })
|
package/index.js
CHANGED
|
@@ -1117,7 +1117,13 @@ async function fetchProxyForward(reqBody) {
|
|
|
1117
1117
|
const url = String((reqBody && reqBody.url) || '');
|
|
1118
1118
|
if (!/^https?:\/\//i.test(url)) throw new Error('需要 http(s) 接口 url');
|
|
1119
1119
|
const method = String((reqBody && reqBody.method) || 'POST').toUpperCase();
|
|
1120
|
-
|
|
1120
|
+
// 头部统一小写:客户端与默认补值可能各自携带不同大小写的同名头(Content-Type 与 content-type),
|
|
1121
|
+
// 混用会作为重复头转发,严格网关(优云智算)直接 400 "Content-Type must be application/json"
|
|
1122
|
+
const headers = {};
|
|
1123
|
+
for (const [hk, hv] of Object.entries((reqBody && reqBody.headers && typeof reqBody.headers === 'object') ? reqBody.headers : {})) {
|
|
1124
|
+
if (hv === undefined || hv === null) continue;
|
|
1125
|
+
headers[String(hk).toLowerCase()] = String(hv);
|
|
1126
|
+
}
|
|
1121
1127
|
let body;
|
|
1122
1128
|
if (method !== 'GET' && method !== 'HEAD') {
|
|
1123
1129
|
if (reqBody && reqBody.bodyRaw != null) {
|
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.2",
|
|
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",
|