diff --git a/admin.html b/admin.html index 578f4d3..ae298e3 100644 --- a/admin.html +++ b/admin.html @@ -70,6 +70,7 @@ 名称(英) Prompt 视频地址 + 溯源(人物×衣服×参考视频) 时长 点赞 来源 @@ -96,6 +97,9 @@ window.fetch = async (...args) => { }; let library = []; +let characters = []; +let garments = []; +let danceRefs = []; let editingId = null; let sortKey = "createdAt"; let sortDir = -1; @@ -107,11 +111,34 @@ function escapeHtml(s) { } async function loadLibrary() { - const res = await fetch("/api/library?sort=newest"); - library = await res.json(); + const [libRes, charRes, garmentRes, danceRefRes] = await Promise.all([ + fetch("/api/library?sort=newest"), + fetch("/api/admin/characters"), + fetch("/api/admin/garments"), + fetch("/api/admin/dance-refs"), + ]); + library = await libRes.json(); + characters = await charRes.json(); + garments = await garmentRes.json(); + danceRefs = await danceRefRes.json(); render(); } +function nameOf(list, id) { + if (!id) return null; + const item = list.find((x) => x.id === id); + return item ? item.name : id; +} + +// T10: 一步法下没有 composedImageId/合成图,溯源只有这三个字段(M.7)。 +function traceability(c) { + const characterName = nameOf(characters, c.characterId); + const garmentName = nameOf(garments, c.garmentId); + const danceRefName = nameOf(danceRefs, c.refDanceVideoId); + if (!characterName && !garmentName && !danceRefName) return "-"; + return [characterName, garmentName, danceRefName].map((n) => n || "?").join(" × "); +} + function filteredSorted() { const q = document.getElementById("searchInput").value.trim().toLowerCase(); const source = document.getElementById("sourceFilter").value; @@ -148,6 +175,7 @@ function render() { + ${escapeHtml(traceability(c))} ${c.duration ?? "-"}s ${c.likes || 0} ${c.source} @@ -165,6 +193,7 @@ function render() { ${escapeHtml(c.promptEn || "")} ${escapeHtml((c.fullPrompt || "").slice(0, 80))}${(c.fullPrompt || "").length > 80 ? "…" : ""} ${videoHref(c)} + ${escapeHtml(traceability(c))} ${c.duration ?? "-"}s ${c.likes || 0} ${c.source} diff --git a/dressup-generate.test.js b/dressup-generate.test.js index bb821a3..26a2cb1 100644 --- a/dressup-generate.test.js +++ b/dressup-generate.test.js @@ -188,6 +188,19 @@ test("generate: full happy path lands a clip with character/garment/dance-ref tr assert.equal(clip.refDanceVideoId, danceRef.id); assert.equal(clip.channel, "dance"); + // T10's admin.html traceability column joins clip.characterId/garmentId/ + // refDanceVideoId back against the three admin lists to show "谁的脸 + + // 谁的衣服 + 哪条参考视频" — confirm that join actually resolves to the + // real names, not just matching ids. + const [charList, garmentList, danceRefList] = await Promise.all([ + fetch(`${BASE}/api/admin/characters`, authed()).then((r) => r.json()), + fetch(`${BASE}/api/admin/garments`, authed()).then((r) => r.json()), + fetch(`${BASE}/api/admin/dance-refs`, authed()).then((r) => r.json()), + ]); + assert.equal(charList.find((x) => x.id === clip.characterId).name, "小美"); + assert.equal(garmentList.find((x) => x.id === clip.garmentId).name, "红旗袍"); + assert.equal(danceRefList.find((x) => x.id === clip.refDanceVideoId).name, "拉丁舞"); + // Re-submitting the same character x garment combo should hit the M.7 // dedupe cache instead of generating again. const repeatRes = await fetch(