91 lines
5.4 KiB
TypeScript
91 lines
5.4 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
||
import { requestCreativeAnalysis, type CreativeAnalysisModel } from "@/components/canvas/tvstar/creativeAnalysisClient";
|
||
import { buildTVstarRevisionPrompt, tvstarAllowsGeneration, tvstarRequestsRunExisting, tvstarWantsDraftNodes } from "@/components/canvas/tvstar/session-context";
|
||
import { resolveTVstarGenerationReferences } from "@/components/canvas/tvstar/generation-references";
|
||
import { tvstarTiming } from "@/lib/video-canvas/tvstar-timing";
|
||
|
||
const models: CreativeAnalysisModel[] = [
|
||
{ id: "broken", label: "Broken provider", modelId: "agnes-2.0-flash", providerType: "custom", supportsVision: true },
|
||
{ id: "official", label: "Qwen Max", modelId: "qwen3.8-max", providerType: "alibaba", supportsVision: true },
|
||
{ id: "backup", label: "Qwen Omni", modelId: "qwen3.8-omni-flash", providerType: "alibaba", supportsVision: true },
|
||
];
|
||
|
||
test("TVstar tries the verified official model first and retries authentication failures in auto mode", async () => {
|
||
const originalFetch = globalThis.fetch;
|
||
const requestedModels: string[] = [];
|
||
globalThis.fetch = async (_url, init) => {
|
||
const body = JSON.parse(String(init?.body || "{}"));
|
||
requestedModels.push(body.preferredModelRef);
|
||
return Response.json(body.preferredModelRef === "official"
|
||
? { success: false, error: "Invalid Authentication" }
|
||
: { success: true, data: { summary: "创意规划已生成" } },
|
||
{ status: body.preferredModelRef === "official" ? 500 : 200 });
|
||
};
|
||
try {
|
||
const result = await requestCreativeAnalysis({ mode: "agent-plan", prompt: "三镜头广告", nodeTitle: "TVstar", models });
|
||
expect(result.summary).toBe("创意规划已生成");
|
||
expect(requestedModels).toEqual(["official", "backup"]);
|
||
} finally {
|
||
globalThis.fetch = originalFetch;
|
||
}
|
||
});
|
||
|
||
test("TVstar keeps the current storyboard and the latest instruction in one conversation", () => {
|
||
const prompt = buildTVstarRevisionPrompt({
|
||
goal: "做一支 4 秒毛笔广告",
|
||
plan: { summary: "暖光书法短片" },
|
||
shots: [{ shot: "01", duration: "4s", sceneDescription: "毛笔写字", cameraMovement: "缓推" }],
|
||
messages: [{ id: "a", role: "user", content: "请保持暖光", createdAt: 1 }],
|
||
}, "改成 6 秒,增加蘸墨特写");
|
||
expect(prompt).toContain("当前分镜:");
|
||
expect(prompt).toContain("毛笔写字");
|
||
expect(prompt).toContain("最新用户指令:改成 6 秒,增加蘸墨特写");
|
||
expect(prompt).toContain("请保持暖光");
|
||
});
|
||
|
||
test("TVstar obeys planning-only instructions even in automatic creation mode", () => {
|
||
expect(tvstarAllowsGeneration("请只分析当前空画布可用的创作步骤,不提交生成任务")).toBe(false);
|
||
expect(tvstarAllowsGeneration("先规划一支 15 秒广告,暂不生成视频")).toBe(false);
|
||
expect(tvstarAllowsGeneration("不要扣费,只写方案")).toBe(false);
|
||
expect(tvstarAllowsGeneration("请在画布创建待运行节点,不要执行任何图片、视频或音频生成")).toBe(false);
|
||
expect(tvstarAllowsGeneration("先搭建未运行的分镜工作流")).toBe(false);
|
||
expect(tvstarAllowsGeneration("做一支 15 秒毛笔广告并生成成片")).toBe(true);
|
||
expect(tvstarAllowsGeneration("规划已经确认,可以开始生成视频")).toBe(true);
|
||
expect(tvstarWantsDraftNodes("请在画布创建待运行的毛笔广告分镜节点,不提交生成任务")).toBe(true);
|
||
expect(tvstarWantsDraftNodes("只分析当前空画布的创作步骤,不提交生成任务")).toBe(false);
|
||
expect(tvstarWantsDraftNodes("不要创建画布节点,只写方案")).toBe(false);
|
||
expect(tvstarRequestsRunExisting("现在开始制作视频")).toBe(true);
|
||
expect(tvstarRequestsRunExisting("请运行画布工作流")).toBe(true);
|
||
expect(tvstarRequestsRunExisting("把第二镜头修改后开始制作视频")).toBe(false);
|
||
expect(tvstarRequestsRunExisting("先不要执行生成任务")).toBe(false);
|
||
});
|
||
|
||
test("TVstar keeps whole-film timing when only a single shot duration changes", () => {
|
||
const shots = [{ duration: "8s" }, { duration: "7s" }];
|
||
expect(tvstarTiming("做一支 15 秒广告\n最新修改:镜头 2 改成 3 秒", shots).targetDuration).toBe(15);
|
||
expect(tvstarTiming("做一支 15 秒广告\n最新修改:整片改成 20 秒,镜头 2 保持 3 秒", shots).targetDuration).toBe(20);
|
||
});
|
||
|
||
test("TVstar sends image and video references into generation, extracting video frames one at a time", async () => {
|
||
let active = 0;
|
||
let maximum = 0;
|
||
const calls: string[] = [];
|
||
const fetcher = (async (_url: unknown, init?: RequestInit) => {
|
||
active++;
|
||
maximum = Math.max(maximum, active);
|
||
const body = JSON.parse(String(init?.body));
|
||
calls.push(body.url);
|
||
await new Promise(resolve => setTimeout(resolve, 1));
|
||
active--;
|
||
return Response.json({ success: true, data: { url: `${body.url}.png` } });
|
||
}) as typeof fetch;
|
||
const images = await resolveTVstarGenerationReferences([
|
||
{ type: "image", title: "角色图", url: "https://media.test/role.png" },
|
||
{ type: "video", title: "参考片 A", url: "https://media.test/a.mp4" },
|
||
{ type: "video", title: "参考片 B", url: "https://media.test/b.mp4" },
|
||
], fetcher);
|
||
expect(images).toEqual(["https://media.test/role.png", "https://media.test/a.mp4.png", "https://media.test/b.mp4.png"]);
|
||
expect(calls).toEqual(["https://media.test/a.mp4", "https://media.test/b.mp4"]);
|
||
expect(maximum).toBe(1);
|
||
});
|