ai-chatbot/tests/routes/video-canvas-config.test.ts

464 lines
15 KiB
TypeScript

import { expect, test } from "@playwright/test";
import {
defaultVideoCanvasFunctionConfig,
hasEnabledFixedVideoCanvasPointCosts,
normalizeVideoCanvasFunctionConfig,
} from "../../lib/video-canvas/config";
import { getVideoCanvasTutorialSupportQr } from "../../lib/video-canvas/tutorial-support";
test.describe("video canvas director configuration", () => {
test("provides competitor-matched AI image import defaults", () => {
expect(defaultVideoCanvasFunctionConfig.directorDesk.defaultAspectRatio).toBe(
"9:16",
);
expect(defaultVideoCanvasFunctionConfig.directorDesk.aiImport).toEqual({
cost: 48,
enabled: true,
functionKey: "director-ai-import",
historyLimit: 24,
localSamEndpoint: "",
uploadAccept: "image/*",
});
expect(defaultVideoCanvasFunctionConfig.functions["director-ai-import"]).toMatchObject({
enabled: true,
kind: "multimodal",
route: "director-ai-import",
});
});
test("normalizes the configurable initial director aspect ratio", () => {
expect(
normalizeVideoCanvasFunctionConfig({
directorDesk: { defaultAspectRatio: "16:9" },
}).directorDesk.defaultAspectRatio,
).toBe("16:9");
expect(
normalizeVideoCanvasFunctionConfig({
directorDesk: { defaultAspectRatio: "2:1" },
}).directorDesk.defaultAspectRatio,
).toBe("9:16");
});
test("normalizes administrator AI image import settings", () => {
const config = normalizeVideoCanvasFunctionConfig({
directorDesk: {
aiImport: {
cost: 10000,
enabled: false,
functionKey: " custom-ai-import ",
historyLimit: 0,
localSamEndpoint: "javascript:alert(1)",
uploadAccept: " image/png,image/jpeg ",
},
},
});
expect(config.directorDesk.aiImport).toEqual({
cost: 9999,
enabled: false,
functionKey: "custom-ai-import",
historyLimit: 1,
localSamEndpoint: "",
uploadAccept: "image/png,image/jpeg",
});
});
test("provides competitor-matched crowd defaults", () => {
expect(defaultVideoCanvasFunctionConfig.directorDesk.crowd).toEqual({
defaultColumns: 3,
defaultRows: 3,
defaultSpacing: 1.2,
});
expect(normalizeVideoCanvasFunctionConfig({}).directorDesk.crowd).toEqual({
defaultColumns: 3,
defaultRows: 3,
defaultSpacing: 1.2,
});
});
test("normalizes administrator crowd defaults before host delivery", () => {
const config = normalizeVideoCanvasFunctionConfig({
directorDesk: {
crowd: {
defaultColumns: 0,
defaultRows: 110,
defaultSpacing: 1.234,
},
},
});
expect(config.directorDesk.crowd).toEqual({
defaultColumns: 1,
defaultRows: 100,
defaultSpacing: 1.23,
});
});
test("provides a configurable two-point video analysis cost", () => {
expect(defaultVideoCanvasFunctionConfig.functions["video-analysis"].cost).toBe(2);
expect(hasEnabledFixedVideoCanvasPointCosts(defaultVideoCanvasFunctionConfig)).toBe(true);
expect(
normalizeVideoCanvasFunctionConfig({
functions: { "video-analysis": { cost: 10_000 } },
}).functions["video-analysis"].cost,
).toBe(9999);
expect(
hasEnabledFixedVideoCanvasPointCosts(
normalizeVideoCanvasFunctionConfig({
functions: { "video-analysis": { cost: 0 } },
}),
),
).toBe(false);
});
test("keeps the element-mark generation prompt administrator-configurable", () => {
expect(
defaultVideoCanvasFunctionConfig.functions["element-mark"].generationPrompt,
).toBe("重点遵循已标记元素:{{labels}}");
expect(
normalizeVideoCanvasFunctionConfig({
functions: {
"element-mark": {
generationPrompt: "锁定这些元素:{{plainLabels}}",
},
},
}).functions["element-mark"].generationPrompt,
).toBe("锁定这些元素:{{plainLabels}}");
});
test("provides configurable smart subtitle erase defaults", () => {
expect(defaultVideoCanvasFunctionConfig.functions["video-subtitle-erase"]).toMatchObject({
cost: 0,
enabled: true,
model: "volcano-subtitle-eraser",
route: "local",
});
expect(normalizeVideoCanvasFunctionConfig({
videoSubtitleErase: { blurRadius: 18, smartSearchStartRatio: 0.62 },
}).videoSubtitleErase).toMatchObject({
blurRadius: 18,
smartSearchStartRatio: 0.62,
});
});
test("provides configurable video audio separation defaults", () => {
expect(defaultVideoCanvasFunctionConfig.functions["video-audio-separation"]).toMatchObject({
cost: 0,
enabled: true,
model: "center-channel-v1",
route: "local",
});
expect(normalizeVideoCanvasFunctionConfig({
videoAudioSeparation: { backgroundGain: 1.6, frameRate: 24 },
}).videoAudioSeparation).toMatchObject({ backgroundGain: 1.6, frameRate: 24 });
});
test("provides configurable video download watermark rules", () => {
const config = normalizeVideoCanvasFunctionConfig({
videoDownload: {
rulesTitle: "站点水印规则",
welcomeText: "欢迎使用{{siteName}}",
},
});
expect(config.videoDownload).toMatchObject({
rulesTitle: "站点水印规则",
welcomeText: "欢迎使用{{siteName}}",
});
});
test("provides normalized database-backed history asset behavior", () => {
expect(defaultVideoCanvasFunctionConfig.historyAssets).toMatchObject({
defaultZoomPercent: 100,
enabled: true,
maxItems: 300,
picker: {
maxSelection: 10,
mediaShortSide: 350,
pageSize: 15,
placementGap: 450,
},
previewAutoplay: true,
showWatermarkBadge: true,
watermarkBadgeText: "AI",
});
expect(
normalizeVideoCanvasFunctionConfig({
historyAssets: {
defaultZoomPercent: 110,
enabled: false,
maxItems: 9999,
previewAutoplay: false,
showWatermarkBadge: false,
watermarkBadgeText: " LOCAL SITE MARK ",
},
}).historyAssets,
).toMatchObject({
defaultZoomPercent: 100,
enabled: false,
maxItems: 500,
picker: {
maxSelection: 10,
pageSize: 15,
},
previewAutoplay: false,
showWatermarkBadge: false,
watermarkBadgeText: "LOCAL SITE M",
});
});
test("provides configurable tutorial and contact surfaces", () => {
expect(defaultVideoCanvasFunctionConfig.tutorialSupport).toEqual({
customerServiceUrl: "",
officialAccountQrImageUrl: "",
salesQrImageUrl: "",
tutorialUrl:
"https://resonate.feishu.cn/wiki/Loxfw6XHziYRk0kKzdjcFfp9nhb",
});
const config = normalizeVideoCanvasFunctionConfig({
tutorialSupport: {
customerServiceUrl: "/support/video-canvas",
officialAccountQrImageUrl: "javascript:alert(1)",
salesQrImageUrl: "https://assets.example.com/sales.png",
tutorialUrl: "https://docs.example.com/video-canvas",
},
}).tutorialSupport;
expect(config).toEqual({
customerServiceUrl: "/support/video-canvas",
officialAccountQrImageUrl: "",
salesQrImageUrl: "https://assets.example.com/sales.png",
tutorialUrl: "https://docs.example.com/video-canvas",
});
expect(getVideoCanvasTutorialSupportQr(config, "sales")).toEqual({
alt: "联系销售",
url: "https://assets.example.com/sales.png",
});
expect(getVideoCanvasTutorialSupportQr(config, "official")).toEqual({
alt: "关注公众号",
url: "",
});
});
test("normalizes administrator video reference rules", () => {
const config = normalizeVideoCanvasFunctionConfig({
videoReferenceSelection: {
defaultRule: { maxImages: 99, mode: "unknown" },
modelRules: [
{
autoExit: false,
hideActionAtLimit: false,
maxImages: 0,
mode: "first-frame",
modelPattern: " Custom Video Model ",
},
{ modelPattern: "" },
],
},
});
expect(config.videoReferenceSelection.defaultRule).toEqual({
autoExit: false,
hideActionAtLimit: false,
maxImages: 20,
mode: "reference-images",
});
expect(config.videoReferenceSelection.modelRules).toEqual([
{
autoExit: false,
hideActionAtLimit: false,
maxImages: 1,
mode: "first-frame",
modelPattern: "Custom Video Model",
},
]);
});
test("normalizes administrator video advanced-setting defaults", () => {
expect(defaultVideoCanvasFunctionConfig.videoAdvancedSettings).toEqual({
defaultAutoValidateMaterial: true,
defaultSearchEnabled: true,
});
expect(
normalizeVideoCanvasFunctionConfig({
videoAdvancedSettings: {
defaultAutoValidateMaterial: false,
defaultSearchEnabled: false,
},
}).videoAdvancedSettings,
).toEqual({
defaultAutoValidateMaterial: false,
defaultSearchEnabled: false,
});
});
test("delivers normalized video generation parameter configuration", () => {
expect(defaultVideoCanvasFunctionConfig.videoGenerationParameters).toMatchObject({
countOptions: [1, 2, 4],
defaultResolution: "720P",
maxDurationSeconds: 15,
minDurationSeconds: 4,
});
expect(
normalizeVideoCanvasFunctionConfig({
videoGenerationParameters: {
defaultGenerateAudio: false,
defaultResolution: "4k",
pointsPerSecondByResolution: { "4k": 155 },
},
}).videoGenerationParameters,
).toMatchObject({
defaultGenerateAudio: false,
defaultResolution: "4K",
pointsPerSecondByResolution: { "4K": 155 },
});
});
test("delivers backend-configurable new-node defaults", () => {
expect(defaultVideoCanvasFunctionConfig.nodeCreation).toMatchObject({
audio: { advancedSettingsOpen: true },
image: { height: 350, ratio: "16:9", width: 622 },
video: {
height: 350,
modelCatalogId: "seedance-1-5-pro",
resolution: "1080P",
width: 622,
},
});
expect(
normalizeVideoCanvasFunctionConfig({
nodeCreation: {
audio: { advancedSettingsOpen: false },
image: { ratio: "9:16", width: 700 },
video: { modelCatalogId: "custom-video", resolution: "4k" },
},
}).nodeCreation,
).toMatchObject({
audio: { advancedSettingsOpen: false },
image: { ratio: "9:16", width: 700 },
video: { modelCatalogId: "custom-video", resolution: "4K" },
});
});
test("provides the competitor public video role catalog in exact page order", () => {
const roles = defaultVideoCanvasFunctionConfig.rolePresets;
expect(roles).toHaveLength(23);
expect(roles.map((role) => role.id)).toEqual([
"role-003", "role-001", "role-002", "role-004", "role-005",
"role-006", "role-007", "role-010", "role-011", "role-012",
"role-013", "role-014", "role-015", "role-016", "role-018",
"role-022", "role-021", "role-008", "role-009", "role-019",
"role-020", "role-017", "role-023",
]);
expect(roles[0]).toMatchObject({
name: "甜妹/清新少女",
tags: "女 · 青年 · 现代 · 女主",
images: {
expression: expect.stringContaining("003_expression_grid_1.png"),
face: expect.stringContaining("003_face_closeup_1.png"),
full: expect.stringContaining("003_full_body_1.png"),
threeView: expect.stringContaining("003_character_sheet_1.png"),
},
});
expect(roles[1].images).toMatchObject({
expression: expect.stringContaining("001_expression_grid.png"),
face: expect.stringContaining("001_face_closeup.png"),
full: expect.stringContaining("001_full_body.png"),
threeView: expect.stringContaining("001_character_sheet.png"),
});
expect(roles.slice(10, 13).map((role) => role.name)).toEqual([
"反派长辈/势利亲戚",
"生活方式普通人",
"生活方式普通人",
]);
});
test("provides competitor-matched subject creation defaults", () => {
expect(defaultVideoCanvasFunctionConfig.subjectLibrary).toMatchObject({
descriptionMaxLength: 100,
imageAccept: "image/jpeg,image/png,image/webp",
imageMaxCount: 4,
imageMinCount: 2,
historyLibraryEnabled: true,
labels: ["无标签", "人物", "场景", "道具", "特效", "其他"],
nameMaxLength: 20,
requireClearFrontFace: true,
smartCompletion: {
candidateCount: 9,
cost: 6,
maxSelected: 3,
},
smartDescription: { cost: 1 },
unselectedCategory: "其他",
videoAccept: "video/mp4,video/quicktime",
videoMaxDurationSeconds: 8,
videoMinDurationSeconds: 3,
videoUsage: {
enabled: true,
maxSubjects: 4,
modelPatterns: ["Kling O3"],
},
voiceHelpText: "仅主体为人物时支持音色绑定和使用",
});
expect(
defaultVideoCanvasFunctionConfig.subjectLibrary.voices.map(
(voice) => voice.name,
),
).toEqual([
"无配音",
"钓系女友",
"温柔女声",
"播报男声",
"盐系少年",
"撒娇女友",
"视频原声",
]);
});
test("normalizes administrator subject creation settings", () => {
const subjectLibrary = normalizeVideoCanvasFunctionConfig({
subjectLibrary: {
descriptionMaxLength: 5000,
imageMaxCount: 1,
imageMinCount: 3,
labels: ["人物", "人物", "场景", ""],
nameMaxLength: 0,
smartDescription: {
maxTokens: 1,
prompt: " custom subject prompt ",
temperature: 2,
},
videoMaxDurationSeconds: 2,
videoMinDurationSeconds: 5,
voices: [{ id: "custom", name: "自定义预设", badge: "站点", previewUrl: "" }],
videoUsage: {
maxSubjects: 99,
modelPatterns: [" Kling O3 ", "Kling O3", "", "Custom Kling"],
promptTemplate: " 主体{{index}}={{name}} ",
},
},
}).subjectLibrary;
expect(subjectLibrary).toMatchObject({
descriptionMaxLength: 1000,
imageMaxCount: 3,
imageMinCount: 3,
labels: ["人物", "场景"],
nameMaxLength: 1,
videoMaxDurationSeconds: 5,
videoMinDurationSeconds: 5,
smartDescription: {
maxTokens: 32,
prompt: "custom subject prompt",
temperature: 1,
},
voices: [{ id: "custom", name: "自定义预设", badge: "站点", previewUrl: "" }],
videoUsage: {
maxSubjects: 12,
modelPatterns: ["Kling O3", "Custom Kling"],
promptTemplate: "主体{{index}}={{name}}",
},
});
});
});