@ninkch/dsh-infinite-canvas 0.18.3 → 0.18.4
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 +68 -5
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -2243,8 +2243,38 @@ window.__ModuleLoader__.load({
|
|
|
2243
2243
|
// 参考图走三步上传(upload_urls → PUT 签名直传 → assets 登记)换 asset_id;
|
|
2244
2244
|
// 登记结果缓存在画布资产元数据(compAssetIdCache):同一张 CAS 资产不重复上传。
|
|
2245
2245
|
const compAssetIdCache = new Map(); // 画布资产 id → 优云 asset_id
|
|
2246
|
+
/** 从文件头字节解析像素尺寸(PNG IHDR / JPEG SOF)——与平台服务端解析口径一致:
|
|
2247
|
+
* createImageBitmap 会按 EXIF 方向旋转并交换宽高,平台读原始文件,两者不一致时登记会被拒。 */
|
|
2248
|
+
function rawImageDims(u8) {
|
|
2249
|
+
try {
|
|
2250
|
+
// PNG:签名 8 字节 + 长度 4 + 'IHDR' → 偏移 16 起是宽高(大端 32 位)
|
|
2251
|
+
if (u8[0] === 0x89 && u8[1] === 0x50) {
|
|
2252
|
+
return {
|
|
2253
|
+
w: ((u8[16] << 24) | (u8[17] << 16) | (u8[18] << 8) | u8[19]) >>> 0,
|
|
2254
|
+
h: ((u8[20] << 24) | (u8[21] << 16) | (u8[22] << 8) | u8[23]) >>> 0,
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
2257
|
+
// JPEG:顺序扫描段,命中 SOF0..SOF15(排除 DHT/DAC/RSTn)后取段内高/宽
|
|
2258
|
+
if (u8[0] === 0xff && u8[1] === 0xd8) {
|
|
2259
|
+
let i = 2;
|
|
2260
|
+
while (i + 9 < u8.length) {
|
|
2261
|
+
if (u8[i] !== 0xff) { i++; continue; }
|
|
2262
|
+
const marker = u8[i + 1];
|
|
2263
|
+
if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) { i += 2; continue; }
|
|
2264
|
+
const len = (u8[i + 2] << 8) | u8[i + 3];
|
|
2265
|
+
if (len < 2) break;
|
|
2266
|
+
if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
|
|
2267
|
+
return { w: (u8[i + 7] << 8) | u8[i + 8], h: (u8[i + 5] << 8) | u8[i + 6] };
|
|
2268
|
+
}
|
|
2269
|
+
i += 2 + len;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
} catch { /* 解析失败返回 null */ }
|
|
2273
|
+
return null;
|
|
2274
|
+
}
|
|
2246
2275
|
/** 参考图上传前置:魔数嗅探真实编码(平台仅收 JPG/PNG/WebP,且校验声明 MIME 与实际字节一致——
|
|
2247
|
-
* 改后缀的 AVIF/HEIC 会在登记时报 "Params [Width/Height] not available")+
|
|
2276
|
+
* 改后缀的 AVIF/HEIC 会在登记时报 "Params [Width/Height] not available")+ 测像素尺寸
|
|
2277
|
+
* (同时给出解码尺寸与文件原始尺寸,登记不一致时用原始尺寸重试)。 */
|
|
2248
2278
|
async function imageBlobMeta(input) {
|
|
2249
2279
|
const blob = input.blob || input;
|
|
2250
2280
|
const head = new Uint8Array(await blob.slice(0, 16).arrayBuffer());
|
|
@@ -2257,6 +2287,8 @@ window.__ModuleLoader__.load({
|
|
|
2257
2287
|
}
|
|
2258
2288
|
const mime = isPng ? 'image/png' : isJpg ? 'image/jpeg' : 'image/webp';
|
|
2259
2289
|
const ext = isPng ? 'png' : isJpg ? 'jpg' : 'webp';
|
|
2290
|
+
let raw = null;
|
|
2291
|
+
try { raw = rawImageDims(new Uint8Array(await blob.slice(0, 262144).arrayBuffer())); } catch { /* 忽略 */ }
|
|
2260
2292
|
let width = 0;
|
|
2261
2293
|
let height = 0;
|
|
2262
2294
|
try {
|
|
@@ -2264,9 +2296,10 @@ window.__ModuleLoader__.load({
|
|
|
2264
2296
|
width = bmp.width;
|
|
2265
2297
|
height = bmp.height;
|
|
2266
2298
|
try { bmp.close && bmp.close(); } catch { /* 忽略 */ }
|
|
2267
|
-
} catch { /*
|
|
2299
|
+
} catch { /* 解码失败时退回文件头尺寸 */ }
|
|
2300
|
+
if (!width || !height) { if (raw) { width = raw.w; height = raw.h; } }
|
|
2268
2301
|
if (!width || !height) throw new Error('参考图尺寸测量失败');
|
|
2269
|
-
return { blob, mime, ext, width, height };
|
|
2302
|
+
return { blob, mime, ext, width, height, rawWidth: raw ? raw.w : width, rawHeight: raw ? raw.h : height };
|
|
2270
2303
|
}
|
|
2271
2304
|
async function compshareImageTask(prompt, refs, s, opts) {
|
|
2272
2305
|
const o = opts || {};
|
|
@@ -2275,6 +2308,19 @@ window.__ModuleLoader__.load({
|
|
|
2275
2308
|
if (!base) throw new Error('未配置图片 API 地址(优云智算:https://cp.compshare.cn)');
|
|
2276
2309
|
const headers = { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: 'Bearer ' + String(s.apiKey || '').trim() };
|
|
2277
2310
|
const extra = parseExtraParams(s.extra); // 透传:optimize_prompt / use_context_ir 等任务级字段
|
|
2311
|
+
// 报错附带实际发出的参数:登记/提交被拒时一眼看到缺什么、值是多少(不再靠猜)
|
|
2312
|
+
const bodySummary = (b) => {
|
|
2313
|
+
if (!b || typeof b !== 'object') return '';
|
|
2314
|
+
const parts = [];
|
|
2315
|
+
for (const [k, v] of Object.entries(b)) {
|
|
2316
|
+
if (v === undefined) { parts.push(k + '=undefined'); continue; }
|
|
2317
|
+
if (v === null) { parts.push(k + '=null'); continue; }
|
|
2318
|
+
if (typeof v === 'object') { parts.push(k + '=[' + (Array.isArray(v) ? v.length + ' 项' : '对象') + ']'); continue; }
|
|
2319
|
+
const t = String(v);
|
|
2320
|
+
parts.push(k + '=' + (t.length > 40 ? t.slice(0, 40) + '…' : t));
|
|
2321
|
+
}
|
|
2322
|
+
return parts.join(' ');
|
|
2323
|
+
};
|
|
2278
2324
|
const api = async (method, path, body, idem) => {
|
|
2279
2325
|
const h = { ...headers };
|
|
2280
2326
|
if (idem) h['Idempotency-Key'] = idem;
|
|
@@ -2283,7 +2329,8 @@ window.__ModuleLoader__.load({
|
|
|
2283
2329
|
let j = null; try { j = JSON.parse(r.text); } catch { throw new Error(`优云图片 API ${method} ${path} 响应非 JSON:` + r.text.slice(0, 200)); }
|
|
2284
2330
|
// 网关业务错误可能 HTTP 200:RetCode≠0 或 {type:'error'} 都算失败
|
|
2285
2331
|
if ((j.RetCode != null && Number(j.RetCode) !== 0) || j.type === 'error') {
|
|
2286
|
-
throw new Error(`优云图片 API ${method} ${path} 失败:` + ((j.error && j.error.message) || j.Message || JSON.stringify(j).slice(0, 200))
|
|
2332
|
+
throw new Error(`优云图片 API ${method} ${path} 失败:` + ((j.error && j.error.message) || j.Message || JSON.stringify(j).slice(0, 200))
|
|
2333
|
+
+ (body ? '|已发送参数:' + bodySummary(body) : ''));
|
|
2287
2334
|
}
|
|
2288
2335
|
return j;
|
|
2289
2336
|
};
|
|
@@ -2304,7 +2351,23 @@ window.__ModuleLoader__.load({
|
|
|
2304
2351
|
label: '参考图上传',
|
|
2305
2352
|
});
|
|
2306
2353
|
if (!put.ok) throw new Error('参考图上传失败 HTTP ' + put.status + ':' + put.text.slice(0, 200));
|
|
2307
|
-
|
|
2354
|
+
// 登记:宽高必须与平台解析出的文件尺寸一致。浏览器解码尺寸与文件原始尺寸在
|
|
2355
|
+
// EXIF 旋转图上不同 → 先用解码尺寸,被拒(Params [Width/Height] not available)时用原始尺寸重试一次
|
|
2356
|
+
const regPayload = (w, h) => ({ object_key: up.object_key, file_name: fileName, content_type: meta.mime, file_size: meta.blob.size, width: w, height: h });
|
|
2357
|
+
let reg = null;
|
|
2358
|
+
try {
|
|
2359
|
+
reg = await api('POST', '/image/v1/assets', regPayload(meta.width, meta.height));
|
|
2360
|
+
} catch (e) {
|
|
2361
|
+
const msg = String((e && e.message) || e);
|
|
2362
|
+
const alt = meta.rawWidth && meta.rawHeight && (meta.rawWidth !== meta.width || meta.rawHeight !== meta.height);
|
|
2363
|
+
if (!/width\/?height/i.test(msg) || !alt) throw e;
|
|
2364
|
+
try {
|
|
2365
|
+
reg = await api('POST', '/image/v1/assets', regPayload(meta.rawWidth, meta.rawHeight));
|
|
2366
|
+
console.info('[infinite-canvas] 参考图登记改用文件原始尺寸成功:' + meta.rawWidth + '×' + meta.rawHeight);
|
|
2367
|
+
} catch (e2) {
|
|
2368
|
+
throw new Error(msg + ';改用文件原始尺寸 ' + meta.rawWidth + '×' + meta.rawHeight + ' 重试仍失败:' + ((e2 && e2.message) || e2));
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2308
2371
|
const aid = reg && reg.reference && reg.reference.asset_id;
|
|
2309
2372
|
if (!aid) throw new Error('参考图登记失败:响应中没有 reference.asset_id');
|
|
2310
2373
|
if (ref.assetId) compAssetIdCache.set(ref.assetId, aid);
|
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.4",
|
|
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",
|