@ninkch/dsh-infinite-canvas 0.18.1 → 0.18.3
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 +25 -13
- package/assets/canvas-studio/SKILL.md +4 -2
- package/client.js +100 -27
- package/index.js +7 -1
- package/package.json +1 -1
|
@@ -39,19 +39,31 @@ B:……
|
|
|
39
39
|
面板文字只允许 ≤4 字短标签(面板名 / 表情名 / 颜色名)。同一资产在所有分格中外观**完全一致**——
|
|
40
40
|
**已连接参考图时,参考图形象是唯一事实**,逐格严格复制;未连接参考图时逐格复用设定集外观关键词。
|
|
41
41
|
|
|
42
|
-
### 角色设定图(16:9
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
42
|
+
### 角色设定图(16:9 横版——按「六段式」结构写提示词,实测出图最稳)
|
|
43
|
+
|
|
44
|
+
提示词按以下六段组织,**方括号段名照写**(模型对显式分段结构的遵循度显著更好):
|
|
45
|
+
|
|
46
|
+
- **【总起】**一句话交代:16:9 横版专业设定集排版、深色简洁背景、风格基调(如 写实电影感)、
|
|
47
|
+
同一角色在所有分格中外观完全一致。
|
|
48
|
+
- **【身份锚】**声明参考图是同一角色的真实照片:严格保持脸型 / 眼型 / 耳形 / 毛量 / 口鼻比例 / 体型,
|
|
49
|
+
只改变姿势与视角;**忽略照片里的临时配饰**(狗绳、挂牌等),配饰一律按提示词文字描述绘制。
|
|
50
|
+
绑定几张参考图就写几个 [图N]。
|
|
51
|
+
- **【主体】**中央大幅全身立绘(正面站姿):体型写量化并加反例(如「约 3 公斤、肩高 20 厘米;
|
|
52
|
+
不是中型犬,不是萨摩耶/银狐/柴犬」——易混淆品种必须显式排除)、毛色毛发、五官、四肢尾巴,
|
|
53
|
+
以及固定配饰的完整描述。
|
|
54
|
+
- **【主图下方·三视图(必须项)】**正面/侧面/背面三格全身站姿——同一角色只换角度,
|
|
55
|
+
交代侧背形态(后续视频一致性靠它)。
|
|
56
|
+
- **【右侧窄栏·自上而下分块;每块内容全图只出现一次】**
|
|
57
|
+
① 脸部特写一格 + 一行 ≤12 字的五官短标注;
|
|
58
|
+
② 配饰格(有固定配饰时):正 / 背两张小图并排,可刻短字,并注明「该物在本图内只出现这一处」;
|
|
59
|
+
③ 表情格:6 个头部小头像分两行(每行 3 个),每个下方只标两字表情名。
|
|
60
|
+
- **【文字规则】**图内文字极少:只保留两三个字的短标签,黑色细体、笔画清楚、字要写大;
|
|
61
|
+
不写任何大标题、说明段或长句。
|
|
62
|
+
- **【画面白名单】**明确写出「画面中只有这一只角色和上述分格内容;没有人物、没有其他动物、
|
|
63
|
+
没有乐器、没有文字水印」——反幻觉白名单实测能压住乱入元素。
|
|
64
|
+
|
|
65
|
+
要点:**每块内容全图只出现一次**(配饰在配饰格画过,主图描述就不再重复强调它);
|
|
66
|
+
反例约束有效——易混淆的品种 / 风格务必写进【主体】的排除句。
|
|
55
67
|
|
|
56
68
|
### 道具设定图(横竖皆可——按道具特性选,不限制)
|
|
57
69
|
- **画幅选择**:长条形/单件道具(剑、箫、杖)竖版更能展示完整形态;
|
|
@@ -141,8 +141,10 @@ description: 用无限画布(Infinite Canvas)做短剧/视频创作时使用
|
|
|
141
141
|
(命名规范「角色-<名> 设定图」/「场景-<名> 设定图」/「道具-<名> 设定图」),
|
|
142
142
|
**prompt 必须按 `canvas-craft`「资产设定图版式」一节的排版模板写详细版式提示词,
|
|
143
143
|
禁止只写一句三视图/外观关键词**——信息量不足,撑不起后续视频的角色一致性。要点(完整模板见 canvas-craft):
|
|
144
|
-
- **角色设定图**(16:9
|
|
145
|
-
|
|
144
|
+
- **角色设定图**(16:9 横版):按 canvas-craft 的「六段式」结构写——【总起】(版式/背景/风格/
|
|
145
|
+
全格一致)→【身份锚】(参考图=唯一形象事实,忽略照片配饰)→【主体】(量化体型+反例排除)→
|
|
146
|
+
【三视图·必须项】→【右侧窄栏:脸部特写 / 配饰格 / 表情两行×3,每格只标两字标签,
|
|
147
|
+
每块内容全图只出现一次】→【文字规则 + 画面白名单】
|
|
146
148
|
- **道具设定图**(横竖皆可,按道具特性选):顶部道具名大标题(≤6 字);中央主图(斜 45° 悬浮+氛围光);
|
|
147
149
|
细节特写 2–3 格(格内无文字);侧栏侧视图;底部圆形色卡 3–5 枚(色名 ≤3 字)
|
|
148
150
|
- **场景设定图**(主图+环绕小图):一张主场景大图占主体,周边环绕补充小图(多角度 / 其他时间段 / 细节格,3–6 张各配 ≤6 字短标注)——大致参考不强约束
|
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') {
|
|
@@ -2229,12 +2243,20 @@ window.__ModuleLoader__.load({
|
|
|
2229
2243
|
// 参考图走三步上传(upload_urls → PUT 签名直传 → assets 登记)换 asset_id;
|
|
2230
2244
|
// 登记结果缓存在画布资产元数据(compAssetIdCache):同一张 CAS 资产不重复上传。
|
|
2231
2245
|
const compAssetIdCache = new Map(); // 画布资产 id → 优云 asset_id
|
|
2232
|
-
/**
|
|
2246
|
+
/** 参考图上传前置:魔数嗅探真实编码(平台仅收 JPG/PNG/WebP,且校验声明 MIME 与实际字节一致——
|
|
2247
|
+
* 改后缀的 AVIF/HEIC 会在登记时报 "Params [Width/Height] not available")+ 测真实像素尺寸。 */
|
|
2233
2248
|
async function imageBlobMeta(input) {
|
|
2234
2249
|
const blob = input.blob || input;
|
|
2235
|
-
const
|
|
2236
|
-
const
|
|
2237
|
-
const
|
|
2250
|
+
const head = new Uint8Array(await blob.slice(0, 16).arrayBuffer());
|
|
2251
|
+
const isPng = head[0] === 0x89 && head[1] === 0x50 && head[2] === 0x4e && head[3] === 0x47;
|
|
2252
|
+
const isJpg = head[0] === 0xff && head[1] === 0xd8 && head[2] === 0xff;
|
|
2253
|
+
const isWebp = head[0] === 0x52 && head[1] === 0x49 && head[2] === 0x46 && head[3] === 0x46
|
|
2254
|
+
&& head[8] === 0x57 && head[9] === 0x45 && head[10] === 0x42 && head[11] === 0x50;
|
|
2255
|
+
if (!isPng && !isJpg && !isWebp) {
|
|
2256
|
+
throw new Error('参考图编码不受支持:平台仅接受 JPG/PNG/WebP 原生编码(当前文件的实际字节不是这三类,常见于改后缀的 AVIF/HEIC)——请转成 JPG/PNG 后重新挂参考图');
|
|
2257
|
+
}
|
|
2258
|
+
const mime = isPng ? 'image/png' : isJpg ? 'image/jpeg' : 'image/webp';
|
|
2259
|
+
const ext = isPng ? 'png' : isJpg ? 'jpg' : 'webp';
|
|
2238
2260
|
let width = 0;
|
|
2239
2261
|
let height = 0;
|
|
2240
2262
|
try {
|
|
@@ -2243,7 +2265,7 @@ window.__ModuleLoader__.load({
|
|
|
2243
2265
|
height = bmp.height;
|
|
2244
2266
|
try { bmp.close && bmp.close(); } catch { /* 忽略 */ }
|
|
2245
2267
|
} catch { /* 测量失败交由下方显式报错 */ }
|
|
2246
|
-
if (!width || !height) throw new Error('
|
|
2268
|
+
if (!width || !height) throw new Error('参考图尺寸测量失败');
|
|
2247
2269
|
return { blob, mime, ext, width, height };
|
|
2248
2270
|
}
|
|
2249
2271
|
async function compshareImageTask(prompt, refs, s, opts) {
|
|
@@ -2251,17 +2273,17 @@ window.__ModuleLoader__.load({
|
|
|
2251
2273
|
let base = String(s.baseUrl || '').trim().replace(/\/+$/, '');
|
|
2252
2274
|
base = base.replace(/\/image\/v1$/i, ''); // 粘贴了完整路径前缀也兼容
|
|
2253
2275
|
if (!base) throw new Error('未配置图片 API 地址(优云智算:https://cp.compshare.cn)');
|
|
2254
|
-
const headers = { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: 'Bearer ' + (s.apiKey || '') };
|
|
2276
|
+
const headers = { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: 'Bearer ' + String(s.apiKey || '').trim() };
|
|
2255
2277
|
const extra = parseExtraParams(s.extra); // 透传:optimize_prompt / use_context_ir 等任务级字段
|
|
2256
2278
|
const api = async (method, path, body, idem) => {
|
|
2257
2279
|
const h = { ...headers };
|
|
2258
2280
|
if (idem) h['Idempotency-Key'] = idem;
|
|
2259
2281
|
const r = await fetchViaProxy(base + path, { method, headers: h, ...(body !== undefined ? { body } : {}), timeoutMs: 120000, label: '优云图片 ' + method });
|
|
2260
|
-
if (!r.ok) throw new Error(
|
|
2261
|
-
let j = null; try { j = JSON.parse(r.text); } catch { throw new Error(
|
|
2282
|
+
if (!r.ok) throw new Error(`优云图片 API ${method} ${path} HTTP ${r.status}:` + r.text.slice(0, 300));
|
|
2283
|
+
let j = null; try { j = JSON.parse(r.text); } catch { throw new Error(`优云图片 API ${method} ${path} 响应非 JSON:` + r.text.slice(0, 200)); }
|
|
2262
2284
|
// 网关业务错误可能 HTTP 200:RetCode≠0 或 {type:'error'} 都算失败
|
|
2263
2285
|
if ((j.RetCode != null && Number(j.RetCode) !== 0) || j.type === 'error') {
|
|
2264
|
-
throw new Error(
|
|
2286
|
+
throw new Error(`优云图片 API ${method} ${path} 失败:` + ((j.error && j.error.message) || j.Message || JSON.stringify(j).slice(0, 200)));
|
|
2265
2287
|
}
|
|
2266
2288
|
return j;
|
|
2267
2289
|
};
|
|
@@ -2821,7 +2843,7 @@ window.__ModuleLoader__.load({
|
|
|
2821
2843
|
const ex = (() => { try { return parseExtraParams(s.extra) || {}; } catch (e) { throw new Error('扩展参数配置有误:' + ((e && e.message) || e)); } })();
|
|
2822
2844
|
// 路径风格:优云智算网关挂 /minimax/v2/...,MiniMax 官方裸 /v2/...;扩展参数 pathPrefix 可显式覆盖
|
|
2823
2845
|
const prefix = String(ex.pathPrefix || (/compshare/i.test(base) ? '/minimax' : ''));
|
|
2824
|
-
const headers = { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: 'Bearer ' + (s.apiKey || '') };
|
|
2846
|
+
const headers = { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: 'Bearer ' + String(s.apiKey || '').trim() };
|
|
2825
2847
|
// 分辨率大小写敏感(480P/768P/1080P/2K/4K):画布的 720P 语义等同原生 768P,显式映射不依赖厂商静默降级
|
|
2826
2848
|
const RESO = { '480P': '480P', '720P': '768P', '768P': '768P', '1080P': '1080P', '2K': '2K', '4K': '4K' };
|
|
2827
2849
|
const resolution = String(ex.resolution || RESO[String(o.resolution || '').toUpperCase()] || '768P');
|
|
@@ -3066,6 +3088,14 @@ window.__ModuleLoader__.load({
|
|
|
3066
3088
|
};
|
|
3067
3089
|
async function apiVideo(prompt, frameBlob, s, opts) {
|
|
3068
3090
|
const o = opts || {};
|
|
3091
|
+
// 节点级扩展参数(手动参数框)与供应商扩展参数合并(节点优先),各 runner 的 mergeExtra 自动生效
|
|
3092
|
+
const nodeExtra = String(o.nodeExtra || '').trim();
|
|
3093
|
+
if (nodeExtra) {
|
|
3094
|
+
try {
|
|
3095
|
+
const baseExtra = String(s.extra || '').trim();
|
|
3096
|
+
s = { ...s, extra: JSON.stringify({ ...(baseExtra ? JSON.parse(baseExtra) : {}), ...JSON.parse(nodeExtra) }) };
|
|
3097
|
+
} catch (e) { throw new Error('节点扩展参数需为 JSON 对象:' + ((e && e.message) || e)); }
|
|
3098
|
+
}
|
|
3069
3099
|
// 衔接模式:auto=尾帧降级为参考图(保留实体参考图);hard=硬首帧(放弃参考图,画面绝对承接);
|
|
3070
3100
|
// off=忽略首帧。网关禁止 first_frame 与 reference_image 混用,故 hard 模式必须放弃参考图。
|
|
3071
3101
|
if (o.frameMode === 'off') frameBlob = null;
|
|
@@ -5362,7 +5392,7 @@ window.__ModuleLoader__.load({
|
|
|
5362
5392
|
// 模型解析:节点选择优先,未指定路由到星标默认模型;都没有 = 明确报错(不再静默走演示模式)
|
|
5363
5393
|
const mcfg = resolveImageModelCfg(settings, nodeImageModelRef(n));
|
|
5364
5394
|
if (!mcfg) throw new Error(T('errNoImageModel'));
|
|
5365
|
-
const blob = await apiImage(p, metas, mcfg, { size: n.data.size, ratio: n.data.ratio });
|
|
5395
|
+
const blob = await apiImage(p, metas, mcfg, { size: n.data.size, ratio: n.data.ratio, nodeExtra: n.data.imgExtra });
|
|
5366
5396
|
const rec = await saveAssetRecord(S.activeId, 'image', blob, {
|
|
5367
5397
|
name: assetNameFor(n, entities, 'image'),
|
|
5368
5398
|
prompt: stripMentionMarks(prompt), effectivePrompt: p,
|
|
@@ -5385,6 +5415,7 @@ window.__ModuleLoader__.load({
|
|
|
5385
5415
|
frameMode: n.data.frameMode || 'auto', // auto=尾帧降级为参考图 / hard=硬首帧(放弃参考图) / off=忽略首帧
|
|
5386
5416
|
refs: refs.map((r) => r.blob),
|
|
5387
5417
|
refNames: refs.map((r) => r.entity || r.name || ''), // 跨镜衔接标注用(图2..N 的实体名)
|
|
5418
|
+
nodeExtra: n.data.vidExtra, // 节点级扩展参数(手动参数框,如 {"use_context_ir":true})
|
|
5388
5419
|
// 任务提交成功即持久化 task id:轮询中断/页面关闭后仍可凭 id 回查结果(钱花了就必须能找回)
|
|
5389
5420
|
onTask: (tid, qurl) => {
|
|
5390
5421
|
update((s) => { const nd = s.graph.nodes[n.id]; if (nd) { nd.data.taskId = String(tid || ''); nd.data.taskQueryUrl = String(qurl || ''); } });
|
|
@@ -6311,6 +6342,17 @@ window.__ModuleLoader__.load({
|
|
|
6311
6342
|
],
|
|
6312
6343
|
h('div', { className: 'dshic-flex' }),
|
|
6313
6344
|
),
|
|
6345
|
+
h('input', {
|
|
6346
|
+
key: 'nextra', className: 'dshic-input', value: (kind === 'video' ? n.data.vidExtra : n.data.imgExtra) || '',
|
|
6347
|
+
placeholder: '扩展参数 JSON,如 {"use_context_ir":true}',
|
|
6348
|
+
title: '节点级扩展参数:合并进供应商扩展参数,节点值优先',
|
|
6349
|
+
onChange: (e) => update((s) => {
|
|
6350
|
+
const nd = s.graph.nodes[n.id];
|
|
6351
|
+
if (!nd) return;
|
|
6352
|
+
if (kind === 'video') nd.data.vidExtra = e.target.value; else nd.data.imgExtra = e.target.value;
|
|
6353
|
+
}),
|
|
6354
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
6355
|
+
}),
|
|
6314
6356
|
);
|
|
6315
6357
|
}
|
|
6316
6358
|
|
|
@@ -6573,6 +6615,36 @@ window.__ModuleLoader__.load({
|
|
|
6573
6615
|
})();
|
|
6574
6616
|
return () => { cancelled = true; };
|
|
6575
6617
|
}, [kind, n.data.assetId, n.data.label, S.assetsTick]);
|
|
6618
|
+
// 历史条自愈:已被删除的资产(渲染成 … 的幽灵格)自动从节点 history 剔除;
|
|
6619
|
+
// 服务端不可达时跳过本轮(宁可不修剪,绝不误删有效历史)
|
|
6620
|
+
useEffect(() => {
|
|
6621
|
+
const histList = n.data.history || [];
|
|
6622
|
+
if (!histList.length) return;
|
|
6623
|
+
let ok = true;
|
|
6624
|
+
(async () => {
|
|
6625
|
+
let alive = null;
|
|
6626
|
+
if (S.store) {
|
|
6627
|
+
const r = await hostRpc('assetsGet', { ids: histList.map((x) => x.assetId) }).catch(() => null);
|
|
6628
|
+
if (!r) return;
|
|
6629
|
+
alive = new Set((r.items || []).map((x) => x.id));
|
|
6630
|
+
} else {
|
|
6631
|
+
const recs = await Promise.all(histList.map((x) => idbGetRaw(x.assetId).catch(() => null)));
|
|
6632
|
+
alive = new Set(histList.filter((_, i) => recs[i]).map((x) => x.assetId));
|
|
6633
|
+
}
|
|
6634
|
+
if (!ok) return;
|
|
6635
|
+
const dead = histList.filter((x) => !alive.has(x.assetId)).map((x) => x.assetId);
|
|
6636
|
+
if (!dead.length) return;
|
|
6637
|
+
update((s) => {
|
|
6638
|
+
const nd = s.graph.nodes[n.id];
|
|
6639
|
+
if (!nd || !Array.isArray(nd.data.history)) return;
|
|
6640
|
+
const next = nd.data.history.filter((x) => !dead.includes(x.assetId));
|
|
6641
|
+
if (next.length === nd.data.history.length) return;
|
|
6642
|
+
nd.data.history = next;
|
|
6643
|
+
if (nd.data.assetId && dead.includes(nd.data.assetId)) nd.data.assetId = next.length ? next[next.length - 1].assetId : null;
|
|
6644
|
+
});
|
|
6645
|
+
})();
|
|
6646
|
+
return () => { ok = false; };
|
|
6647
|
+
}, [n.id, S.assetsTick, S.store]);
|
|
6576
6648
|
const promptEdge = S.graph.edges.find((x) => x.toNode === n.id && x.toPort === 'prompt');
|
|
6577
6649
|
const promptSrc = promptEdge && S.graph.nodes[promptEdge.fromNode];
|
|
6578
6650
|
const fromStoryboard = !!(promptSrc && promptSrc.type === 'storyboard');
|
|
@@ -8043,7 +8115,11 @@ window.__ModuleLoader__.load({
|
|
|
8043
8115
|
assetMem.delete(a.id);
|
|
8044
8116
|
update((s) => {
|
|
8045
8117
|
for (const nd of Object.values(s.graph.nodes)) {
|
|
8046
|
-
|
|
8118
|
+
const d = nd.data;
|
|
8119
|
+
if (!d) continue;
|
|
8120
|
+
if (d.assetId === a.id) { d.assetId = null; d.status = 'idle'; }
|
|
8121
|
+
if (d.lastAssetId === a.id) d.lastAssetId = null; // 尾帧引用同步清理
|
|
8122
|
+
if (Array.isArray(d.history) && d.history.some((x) => x.assetId === a.id)) d.history = d.history.filter((x) => x.assetId !== a.id); // 历史条同步清理
|
|
8047
8123
|
}
|
|
8048
8124
|
s.assetsTick++;
|
|
8049
8125
|
});
|
|
@@ -8356,12 +8432,12 @@ window.__ModuleLoader__.load({
|
|
|
8356
8432
|
}
|
|
8357
8433
|
|
|
8358
8434
|
/** 图片/视频模型多配置列表:名称 / 地址 / Key / 模型(/模式),可增删、设默认。 */
|
|
8359
|
-
function Field({ label, value, onChange, placeholder, password }) {
|
|
8435
|
+
function Field({ label, value, onChange, placeholder, password, tip }) {
|
|
8360
8436
|
// 敏感字段(API Key):默认脱敏,点击小眼睛切换明文显示
|
|
8361
8437
|
const [reveal, setReveal] = useState(false);
|
|
8362
8438
|
if (!password) {
|
|
8363
8439
|
return h('div', { className: 'dshic-field' },
|
|
8364
|
-
h('span', { className: 'dshic-field-label' }, label),
|
|
8440
|
+
h('span', { className: 'dshic-field-label' }, label, tip ? InfoTip(tip) : null),
|
|
8365
8441
|
h('input', {
|
|
8366
8442
|
className: 'dshic-input', type: 'text', value, placeholder: placeholder || '',
|
|
8367
8443
|
onChange: (e) => onChange(e.target.value),
|
|
@@ -8451,14 +8527,15 @@ window.__ModuleLoader__.load({
|
|
|
8451
8527
|
),
|
|
8452
8528
|
open ? [
|
|
8453
8529
|
kind === 'video' ? h('div', { key: 'vendor', className: 'dshic-field' },
|
|
8454
|
-
h('
|
|
8530
|
+
h('div', { className: 'dshic-row' },
|
|
8531
|
+
h('span', { className: 'dshic-field-label' }, T('vendorLabel')),
|
|
8532
|
+
InfoTip((VIDEO_VENDORS[detectVideoVendor(p)] || {}).note || '')),
|
|
8455
8533
|
h('select', {
|
|
8456
8534
|
className: 'dshic-select', value: detectVideoVendor(p),
|
|
8457
8535
|
onPointerDown: (e) => e.stopPropagation(),
|
|
8458
8536
|
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
8537
|
},
|
|
8460
8538
|
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
8539
|
) : null,
|
|
8463
8540
|
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
8541
|
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 }),
|
|
@@ -8477,9 +8554,8 @@ window.__ModuleLoader__.load({
|
|
|
8477
8554
|
h('input', { type: 'checkbox', checked: p.generateAudio === true, onChange: (e) => patchProvider(p.id, 'generateAudio', e.target.checked), onPointerDown: (e) => e.stopPropagation() }),
|
|
8478
8555
|
h('span', {}, T('vidAudioOpt'))) : null,
|
|
8479
8556
|
],
|
|
8480
|
-
(kind === 'image' ||
|
|
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')),
|
|
8557
|
+
(kind === 'image' || kind === 'video') ? [
|
|
8558
|
+
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
8559
|
] : null,
|
|
8484
8560
|
h('div', { className: 'dshic-row', style: { marginTop: 4 } },
|
|
8485
8561
|
h('span', { className: 'dshic-field-label', style: { flex: 1, minWidth: 0 } }, T('modelsLabel')),
|
|
@@ -8640,10 +8716,7 @@ window.__ModuleLoader__.load({
|
|
|
8640
8716
|
h('span', {}, T('seedanceWatermark')))
|
|
8641
8717
|
: null,
|
|
8642
8718
|
(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'))
|
|
8719
|
+
? 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
8720
|
: null,
|
|
8648
8721
|
kind === 'tts'
|
|
8649
8722
|
? 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.3",
|
|
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",
|