ai-chatbot/tests/routes/tvstar-creative-analysis.test.ts

91 lines
5.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
});