feat(*): 短剧创作工作台 - 完善章节分镜、资产管理、技能运行与生成任务恢复

This commit is contained in:
ddCat
2026-08-29 23:34:46 +08:00
parent 4f07daae9e
commit 2f6832f99c
113 changed files with 7016 additions and 723 deletions

View File

@@ -0,0 +1,87 @@
import { describe, expect, test } from "bun:test";
import { canvasResourceMentionToken } from "@/lib/canvas/canvas-resource-references";
import type { ProjectAsset, ProjectDetail } from "@/services/api/projects";
import { buildShotAssetReferenceContext, resolveShotAssetMentionPrompt } from "@/pages/projects/detail/workflow-shot-references";
describe("workflow shot asset references", () => {
test("builds ordered image references for bound media and characters", () => {
const context = buildShotAssetReferenceContext(detail(), "shot-1");
expect(context.mentionReferences.map((reference) => reference.label)).toEqual(["雨夜街道", "林默"]);
expect(context.referenceImages.map((image) => image.id)).toEqual(["asset-scene", "asset-character"]);
expect(context.referenceImages[1]?.storageKey).toBe("resource:character-cover");
});
test("resolves asset tokens to the submitted reference image order", () => {
const context = buildShotAssetReferenceContext(detail(), "shot-1");
const [scene, character] = context.mentionReferences;
const prompt = `让 ${canvasResourceMentionToken(character)} 从 ${canvasResourceMentionToken(scene)} 走近镜头`;
expect(resolveShotAssetMentionPrompt(prompt, context)).toBe("让 图片2 从 图片1 走近镜头");
});
test("rejects stale mentions after an asset is unbound", () => {
const context = buildShotAssetReferenceContext(detail(), "shot-1");
expect(() => resolveShotAssetMentionPrompt("跟随 @[asset:missing]", context)).toThrow("未绑定到当前镜头");
});
});
function detail(): ProjectDetail {
const scene = {
id: "asset-scene",
title: "雨夜街道",
mediaType: "image",
category: "environment",
status: "confirmed",
primaryVersionId: "version-scene",
versionCount: 1,
usages: [],
position: 0,
storageKey: "resource:scene-image",
updatedAt: "2026-08-29T00:00:00Z",
} satisfies ProjectAsset;
const character = {
id: "asset-character",
title: "林默",
mediaType: "entity",
category: "character",
status: "confirmed",
primaryVersionId: "version-character",
versionCount: 1,
usages: [],
position: 1,
updatedAt: "2026-08-29T00:00:00Z",
character: {
versionId: "version-character",
version: 1,
definition: {},
representations: [{ id: "representation-1", resourceId: "character-cover", mediaType: "image", role: "primary" }],
visualStatus: "ready",
voiceStatus: "missing",
},
} satisfies ProjectAsset;
const audio = {
id: "asset-audio",
title: "雨声",
mediaType: "audio",
category: "other",
status: "confirmed",
primaryVersionId: "version-audio",
versionCount: 1,
usages: [],
position: 2,
storageKey: "resource:rain-audio",
updatedAt: "2026-08-29T00:00:00Z",
} satisfies ProjectAsset;
return {
assets: [scene, character, audio],
shotReferences: [
{ id: "reference-scene", shotId: "shot-1", assetVersionId: "version-scene", role: "reference", status: "linked", createdAt: "2026-08-29T00:00:00Z" },
{ id: "reference-character", shotId: "shot-1", assetVersionId: "version-character", role: "reference", status: "linked", createdAt: "2026-08-29T00:00:01Z" },
{ id: "reference-audio", shotId: "shot-1", assetVersionId: "version-audio", role: "reference", status: "linked", createdAt: "2026-08-29T00:00:02Z" },
{ id: "reference-other-shot", shotId: "shot-2", assetVersionId: "version-scene", role: "reference", status: "linked", createdAt: "2026-08-29T00:00:03Z" },
],
} as ProjectDetail;
}