@ninkch/dsh-infinite-canvas 0.18.1 → 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/client.js +83 -18
- package/index.js +7 -1
- package/package.json +1 -1
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'(节点字段懒迁移用)
|
|
@@ -2154,10 +2157,21 @@ window.__ModuleLoader__.load({
|
|
|
2154
2157
|
let base = s.baseUrl.replace(/\/+$/, '').replace(/\/images\/generations$/, '');
|
|
2155
2158
|
if (!base.replace(/^https?:\/\//i, '').includes('/')) base = base + '/v1';
|
|
2156
2159
|
const refs = refBlobs || [];
|
|
2157
|
-
const auth = s.apiKey ? { Authorization: 'Bearer ' + s.apiKey } : {};
|
|
2158
|
-
|
|
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); // 厂商专属参数(留空不发送)
|
|
2159
2170
|
// 优云智算(compshare)任务式:Qwen-Image 异步任务(提交 → 轮询 → output_url 下载)
|
|
2160
|
-
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 异常由后续流程报错 */ }
|
|
2161
2175
|
const upstreamTimeout = Number(s.timeoutMs) > 0 ? Number(s.timeoutMs) : 120000; // 商汤等同步接口 20s+,默认 120s
|
|
2162
2176
|
// 参考图编辑模式:multipart 上传到 /images/edits(OpenAI gpt-image-1 兼容)
|
|
2163
2177
|
if (refs.length && s.mode === 'edits') {
|
|
@@ -2251,7 +2265,7 @@ window.__ModuleLoader__.load({
|
|
|
2251
2265
|
let base = String(s.baseUrl || '').trim().replace(/\/+$/, '');
|
|
2252
2266
|
base = base.replace(/\/image\/v1$/i, ''); // 粘贴了完整路径前缀也兼容
|
|
2253
2267
|
if (!base) throw new Error('未配置图片 API 地址(优云智算:https://cp.compshare.cn)');
|
|
2254
|
-
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() };
|
|
2255
2269
|
const extra = parseExtraParams(s.extra); // 透传:optimize_prompt / use_context_ir 等任务级字段
|
|
2256
2270
|
const api = async (method, path, body, idem) => {
|
|
2257
2271
|
const h = { ...headers };
|
|
@@ -2821,7 +2835,7 @@ window.__ModuleLoader__.load({
|
|
|
2821
2835
|
const ex = (() => { try { return parseExtraParams(s.extra) || {}; } catch (e) { throw new Error('扩展参数配置有误:' + ((e && e.message) || e)); } })();
|
|
2822
2836
|
// 路径风格:优云智算网关挂 /minimax/v2/...,MiniMax 官方裸 /v2/...;扩展参数 pathPrefix 可显式覆盖
|
|
2823
2837
|
const prefix = String(ex.pathPrefix || (/compshare/i.test(base) ? '/minimax' : ''));
|
|
2824
|
-
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() };
|
|
2825
2839
|
// 分辨率大小写敏感(480P/768P/1080P/2K/4K):画布的 720P 语义等同原生 768P,显式映射不依赖厂商静默降级
|
|
2826
2840
|
const RESO = { '480P': '480P', '720P': '768P', '768P': '768P', '1080P': '1080P', '2K': '2K', '4K': '4K' };
|
|
2827
2841
|
const resolution = String(ex.resolution || RESO[String(o.resolution || '').toUpperCase()] || '768P');
|
|
@@ -3066,6 +3080,14 @@ window.__ModuleLoader__.load({
|
|
|
3066
3080
|
};
|
|
3067
3081
|
async function apiVideo(prompt, frameBlob, s, opts) {
|
|
3068
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
|
+
}
|
|
3069
3091
|
// 衔接模式:auto=尾帧降级为参考图(保留实体参考图);hard=硬首帧(放弃参考图,画面绝对承接);
|
|
3070
3092
|
// off=忽略首帧。网关禁止 first_frame 与 reference_image 混用,故 hard 模式必须放弃参考图。
|
|
3071
3093
|
if (o.frameMode === 'off') frameBlob = null;
|
|
@@ -5362,7 +5384,7 @@ window.__ModuleLoader__.load({
|
|
|
5362
5384
|
// 模型解析:节点选择优先,未指定路由到星标默认模型;都没有 = 明确报错(不再静默走演示模式)
|
|
5363
5385
|
const mcfg = resolveImageModelCfg(settings, nodeImageModelRef(n));
|
|
5364
5386
|
if (!mcfg) throw new Error(T('errNoImageModel'));
|
|
5365
|
-
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 });
|
|
5366
5388
|
const rec = await saveAssetRecord(S.activeId, 'image', blob, {
|
|
5367
5389
|
name: assetNameFor(n, entities, 'image'),
|
|
5368
5390
|
prompt: stripMentionMarks(prompt), effectivePrompt: p,
|
|
@@ -5385,6 +5407,7 @@ window.__ModuleLoader__.load({
|
|
|
5385
5407
|
frameMode: n.data.frameMode || 'auto', // auto=尾帧降级为参考图 / hard=硬首帧(放弃参考图) / off=忽略首帧
|
|
5386
5408
|
refs: refs.map((r) => r.blob),
|
|
5387
5409
|
refNames: refs.map((r) => r.entity || r.name || ''), // 跨镜衔接标注用(图2..N 的实体名)
|
|
5410
|
+
nodeExtra: n.data.vidExtra, // 节点级扩展参数(手动参数框,如 {"use_context_ir":true})
|
|
5388
5411
|
// 任务提交成功即持久化 task id:轮询中断/页面关闭后仍可凭 id 回查结果(钱花了就必须能找回)
|
|
5389
5412
|
onTask: (tid, qurl) => {
|
|
5390
5413
|
update((s) => { const nd = s.graph.nodes[n.id]; if (nd) { nd.data.taskId = String(tid || ''); nd.data.taskQueryUrl = String(qurl || ''); } });
|
|
@@ -6311,6 +6334,17 @@ window.__ModuleLoader__.load({
|
|
|
6311
6334
|
],
|
|
6312
6335
|
h('div', { className: 'dshic-flex' }),
|
|
6313
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
|
+
}),
|
|
6314
6348
|
);
|
|
6315
6349
|
}
|
|
6316
6350
|
|
|
@@ -6573,6 +6607,36 @@ window.__ModuleLoader__.load({
|
|
|
6573
6607
|
})();
|
|
6574
6608
|
return () => { cancelled = true; };
|
|
6575
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]);
|
|
6576
6640
|
const promptEdge = S.graph.edges.find((x) => x.toNode === n.id && x.toPort === 'prompt');
|
|
6577
6641
|
const promptSrc = promptEdge && S.graph.nodes[promptEdge.fromNode];
|
|
6578
6642
|
const fromStoryboard = !!(promptSrc && promptSrc.type === 'storyboard');
|
|
@@ -8043,7 +8107,11 @@ window.__ModuleLoader__.load({
|
|
|
8043
8107
|
assetMem.delete(a.id);
|
|
8044
8108
|
update((s) => {
|
|
8045
8109
|
for (const nd of Object.values(s.graph.nodes)) {
|
|
8046
|
-
|
|
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); // 历史条同步清理
|
|
8047
8115
|
}
|
|
8048
8116
|
s.assetsTick++;
|
|
8049
8117
|
});
|
|
@@ -8356,12 +8424,12 @@ window.__ModuleLoader__.load({
|
|
|
8356
8424
|
}
|
|
8357
8425
|
|
|
8358
8426
|
/** 图片/视频模型多配置列表:名称 / 地址 / Key / 模型(/模式),可增删、设默认。 */
|
|
8359
|
-
function Field({ label, value, onChange, placeholder, password }) {
|
|
8427
|
+
function Field({ label, value, onChange, placeholder, password, tip }) {
|
|
8360
8428
|
// 敏感字段(API Key):默认脱敏,点击小眼睛切换明文显示
|
|
8361
8429
|
const [reveal, setReveal] = useState(false);
|
|
8362
8430
|
if (!password) {
|
|
8363
8431
|
return h('div', { className: 'dshic-field' },
|
|
8364
|
-
h('span', { className: 'dshic-field-label' }, label),
|
|
8432
|
+
h('span', { className: 'dshic-field-label' }, label, tip ? InfoTip(tip) : null),
|
|
8365
8433
|
h('input', {
|
|
8366
8434
|
className: 'dshic-input', type: 'text', value, placeholder: placeholder || '',
|
|
8367
8435
|
onChange: (e) => onChange(e.target.value),
|
|
@@ -8451,14 +8519,15 @@ window.__ModuleLoader__.load({
|
|
|
8451
8519
|
),
|
|
8452
8520
|
open ? [
|
|
8453
8521
|
kind === 'video' ? h('div', { key: 'vendor', className: 'dshic-field' },
|
|
8454
|
-
h('
|
|
8522
|
+
h('div', { className: 'dshic-row' },
|
|
8523
|
+
h('span', { className: 'dshic-field-label' }, T('vendorLabel')),
|
|
8524
|
+
InfoTip((VIDEO_VENDORS[detectVideoVendor(p)] || {}).note || '')),
|
|
8455
8525
|
h('select', {
|
|
8456
8526
|
className: 'dshic-select', value: detectVideoVendor(p),
|
|
8457
8527
|
onPointerDown: (e) => e.stopPropagation(),
|
|
8458
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); },
|
|
8459
8529
|
},
|
|
8460
8530
|
Object.entries(VIDEO_VENDORS).map(([id, vd]) => h('option', { key: id, value: id }, vd.label))),
|
|
8461
|
-
h('div', { className: 'dshic-hint' }, (VIDEO_VENDORS[detectVideoVendor(p)] || {}).note || ''),
|
|
8462
8531
|
) : null,
|
|
8463
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 }),
|
|
8464
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 }),
|
|
@@ -8478,8 +8547,7 @@ window.__ModuleLoader__.load({
|
|
|
8478
8547
|
h('span', {}, T('vidAudioOpt'))) : null,
|
|
8479
8548
|
],
|
|
8480
8549
|
(kind === 'image' || detectVideoVendor(p) === 'openai' || detectVideoVendor(p) === 'seedance') ? [
|
|
8481
|
-
h(Field, { key: 'extra', label: T('cfgExtra'), value: p.extra || '', onChange: (v) => patchProvider(p.id, 'extra', v), placeholder: '{"watermark":false,"prompt_extend":false}' }),
|
|
8482
|
-
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') }),
|
|
8483
8551
|
] : null,
|
|
8484
8552
|
h('div', { className: 'dshic-row', style: { marginTop: 4 } },
|
|
8485
8553
|
h('span', { className: 'dshic-field-label', style: { flex: 1, minWidth: 0 } }, T('modelsLabel')),
|
|
@@ -8640,10 +8708,7 @@ window.__ModuleLoader__.load({
|
|
|
8640
8708
|
h('span', {}, T('seedanceWatermark')))
|
|
8641
8709
|
: null,
|
|
8642
8710
|
(kind === 'image' || kind === 'video')
|
|
8643
|
-
? h(Field, { key: 'extra', label: T('cfgExtra'), value: cfg.extra || '', onChange: (v) => patch(cfg.id, 'extra', v), placeholder: '{"watermark":false,"prompt_extend":false}' })
|
|
8644
|
-
: null,
|
|
8645
|
-
(kind === 'image' || kind === 'video')
|
|
8646
|
-
? 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') })
|
|
8647
8712
|
: null,
|
|
8648
8713
|
kind === 'tts'
|
|
8649
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",
|