|
|
@@ -42,7 +42,7 @@ import { parseArgs } from 'node:util';
|
|
|
import {
|
|
|
TENANT_DEFAULT, SKILL_ROOT, buildIdempotencyKey, deriveAssetUrl, ensureDir, firstText, imageUrlsFrom,
|
|
|
learnAliases, learnTags, loadDictionary, newRunId, normalizeMaterialAsset, outputsDir,
|
|
|
- readJson, reorderAssets, resolvePython, saveDictionary, sha1, sortAssets, truncate, uniq, writeJson,
|
|
|
+ readJson, reorderAssets, resolvePython, saveDictionary, sortAssets, truncate, uniq, writeJson,
|
|
|
} from './lib.mjs';
|
|
|
import { readImageInfo, IMAGE_EXTENSIONS, VIDEO_EXTENSIONS, DOC_EXTENSIONS, DECK_EXTENSIONS, TEXT_EXTENSIONS } from './image-size.mjs';
|
|
|
import { applyGroupOverrides, groupMaterials, DEFAULT_ORDERING_RULE } from './grouping.mjs';
|
|
|
@@ -330,33 +330,18 @@ export async function runPlan(options = {}) {
|
|
|
* @returns {Promise<{url:string, upload:object|null, fallback:boolean, error:Error|null, reason:string}>}
|
|
|
*/
|
|
|
|
|
|
-// 文件内容指纹:避免两个不同文件落成同一个 object key 互相覆盖(读不到则退化为 0)。
|
|
|
-function contentFingerprint(filePath) {
|
|
|
- try {
|
|
|
- const buf = fs.readFileSync(filePath);
|
|
|
- return `${buf.length}-${sha1(buf.toString('base64')).slice(0, 12)}`;
|
|
|
- } catch {
|
|
|
- return '0';
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
export async function resolveAssetUrl(item, options = {}) {
|
|
|
const remote = item && item.url ? String(item.url) : '';
|
|
|
if (remote) return { url: remote, upload: null, fallback: false, error: null, reason: 'remote-url' };
|
|
|
|
|
|
const localPath = (item && item.localPath) || '';
|
|
|
let uploadError = null;
|
|
|
- // key 区分度:优先 显式 slug / 来源引用 / 标题;都没有时用「文件内容指纹」兜底。
|
|
|
- // 否则两个不同案例都会落成 case-1.png → 同日撞 key 互相覆盖(真实事故:两张不同图同 key)。
|
|
|
- let slugSeed = options.caseSlug;
|
|
|
- if (!slugSeed) {
|
|
|
- slugSeed = localPath ? `c${contentFingerprint(localPath)}` : 'case';
|
|
|
- }
|
|
|
if (localPath && options.upload) {
|
|
|
try {
|
|
|
const upload = await uploadMaterial(localPath, {
|
|
|
tenantId: options.tenantId,
|
|
|
- caseSlug: slugSeed,
|
|
|
+ // slug 区分度由 storage-bridge 统一兜底(slugify 为空时用内容指纹),这里原样传
|
|
|
+ caseSlug: options.caseSlug,
|
|
|
order: options.order,
|
|
|
});
|
|
|
if (upload && upload.url) {
|