291724a984
同步主分支的角色动作序列、精选展示与外部编辑器契约更新 保留画布生成输入 V2 契约并合并视频、音效时长元数据 修复精选视频对数值型 V2 时长元数据的兼容
744 lines
22 KiB
TypeScript
744 lines
22 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import type { EditorProjectResourceSnapshot } from '../../services/image-editor/editorProjectClient';
|
|
import { calculateEditorVideoPrice } from '../image-editor/ImageCanvasGenerationModel';
|
|
import { buildCreationShowcaseItems } from './creationShowcaseModel';
|
|
|
|
function createResource(
|
|
overrides: Partial<EditorProjectResourceSnapshot> & Record<string, unknown>,
|
|
): EditorProjectResourceSnapshot {
|
|
return {
|
|
resourceId: 'resource-1',
|
|
projectId: 'project-1',
|
|
label: '素材',
|
|
imageSrc: 'data:image/png;base64,asset',
|
|
width: 512,
|
|
height: 512,
|
|
sourceType: 'generated',
|
|
prompt: null,
|
|
actualPrompt: null,
|
|
model: null,
|
|
provider: null,
|
|
taskId: null,
|
|
...overrides,
|
|
} as EditorProjectResourceSnapshot;
|
|
}
|
|
|
|
describe('creationShowcaseModel', () => {
|
|
it('only builds showcase items from generated project resources', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'all',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'generated-1',
|
|
label: '画布生成素材',
|
|
sourceType: 'generated',
|
|
prompt: '真实画布生成',
|
|
}),
|
|
createResource({
|
|
resourceId: 'uploaded-1',
|
|
label: '上传素材',
|
|
sourceType: 'uploaded',
|
|
prompt: '上传不应展示',
|
|
}),
|
|
createResource({
|
|
resourceId: 'mock-1',
|
|
label: 'Mock 素材',
|
|
sourceType: 'mock_generated',
|
|
prompt: 'Mock 不应展示',
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items).toHaveLength(1);
|
|
expect(items[0]?.label).toBe('画布生成素材');
|
|
});
|
|
|
|
it('filters out generated showcase records with display disabled', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'all',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'public-generated',
|
|
label: '公开素材',
|
|
sourceType: 'generated',
|
|
publicShowcaseEnabled: true,
|
|
}),
|
|
createResource({
|
|
resourceId: 'private-generated',
|
|
label: '不公开素材',
|
|
sourceType: 'generated',
|
|
publicShowcaseEnabled: false,
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items).toHaveLength(1);
|
|
expect(items[0]?.label).toBe('公开素材');
|
|
});
|
|
|
|
it('prepends enabled campaign cards and keeps showcase metrics', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'all',
|
|
campaign: {
|
|
enabled: true,
|
|
title: '活动精选',
|
|
imageSrc: '/campaign.png',
|
|
imageWidth: 900,
|
|
imageHeight: 1200,
|
|
prompt: '活动提示词',
|
|
author: '运营',
|
|
costText: '50泥点',
|
|
},
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'showcase-1',
|
|
showcaseId: 'showcase-1',
|
|
assetId: 'asset-1',
|
|
label: '审核通过素材',
|
|
sourceType: 'generated',
|
|
publicShowcaseEnabled: true,
|
|
likeCount: 12,
|
|
generationCostMudPoints: 20,
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items).toHaveLength(2);
|
|
expect(items[0]).toMatchObject({
|
|
id: 'campaign:global',
|
|
label: '活动精选',
|
|
campaign: true,
|
|
cost: '50泥点',
|
|
});
|
|
expect(items[0]?.previews[0]).toMatchObject({
|
|
width: 900,
|
|
height: 1200,
|
|
});
|
|
expect(items[1]).toMatchObject({
|
|
id: 'asset:asset-1',
|
|
label: '审核通过素材',
|
|
likeCount: 12,
|
|
showcaseId: 'showcase-1',
|
|
cost: '20泥点',
|
|
});
|
|
});
|
|
|
|
it('uses manually assigned showcase categories before asset kind grouping', () => {
|
|
const projectResources = [
|
|
createResource({
|
|
resourceId: 'manual-ui-category',
|
|
showcaseId: 'showcase-ui',
|
|
label: '人工分类 UI',
|
|
sourceType: 'generated',
|
|
assetKind: 'character',
|
|
showcaseCategory: 'ui',
|
|
generationCostMudPoints: 20,
|
|
}),
|
|
];
|
|
|
|
expect(
|
|
buildCreationShowcaseItems({
|
|
activeTab: 'ui',
|
|
projectResources,
|
|
}).map((item) => item.label),
|
|
).toEqual(['人工分类 UI']);
|
|
expect(
|
|
buildCreationShowcaseItems({
|
|
activeTab: 'characters',
|
|
projectResources,
|
|
}),
|
|
).toHaveLength(0);
|
|
});
|
|
|
|
it('uses public author fields without falling back to internal owner user id', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'characters',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'display-author-resource',
|
|
label: '展示名作者素材',
|
|
assetKind: 'character',
|
|
ownerUserId: 'user-author-1',
|
|
authorDisplayName: '作者昵称',
|
|
authorPublicUserCode: 'SY-00000001',
|
|
}),
|
|
createResource({
|
|
resourceId: 'code-author-resource',
|
|
label: '陶泥号作者素材',
|
|
assetKind: 'character',
|
|
ownerUserId: 'user-author-2',
|
|
authorPublicUserCode: 'SY-00000002',
|
|
}),
|
|
createResource({
|
|
resourceId: 'owner-only-resource',
|
|
label: '内部id素材',
|
|
assetKind: 'character',
|
|
ownerUserId: 'user-author-3',
|
|
authorName: 'user-author-3',
|
|
ownerName: 'user-author-3',
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items.map((item) => item.author)).toEqual([
|
|
'作者昵称',
|
|
'SY-00000002',
|
|
'-',
|
|
]);
|
|
});
|
|
|
|
it('deduplicates canvas copies that point back to the same generated resource', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'characters',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'resource-original',
|
|
label: '原始角色图',
|
|
imageSrc: '/generated/character.png',
|
|
objectKey: 'generated/character.png',
|
|
assetObjectId: 'asset-object-character',
|
|
assetKind: 'character',
|
|
sourceType: 'generated',
|
|
publicShowcaseEnabled: true,
|
|
}),
|
|
createResource({
|
|
resourceId: 'resource-copy',
|
|
label: '拖拽副本角色图',
|
|
imageSrc:
|
|
'https://oss.example.test/generated/character.png?x-oss-signature=1',
|
|
objectKey: '/generated/character.png',
|
|
assetObjectId: 'asset-object-character',
|
|
assetKind: 'character',
|
|
sourceType: 'generated',
|
|
sourceResourceId: 'resource-original',
|
|
publicShowcaseEnabled: true,
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items).toHaveLength(1);
|
|
expect(items[0]?.previews).toHaveLength(1);
|
|
expect(items[0]?.previews[0]?.id).toBe('resource-original');
|
|
});
|
|
|
|
it('shows every real asset in the all tab without requiring a category', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'all',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'spec-1',
|
|
label: '角色规范图',
|
|
assetKind: 'spec',
|
|
prompt: '规范提示词',
|
|
priceMudPoints: 5,
|
|
authorDisplayName: '创作者A',
|
|
}),
|
|
createResource({
|
|
resourceId: 'character-1',
|
|
label: '角色主图',
|
|
assetKind: 'character',
|
|
priceMudPoints: 12,
|
|
generationInputs: {
|
|
fields: [{ title: '角色设定', value: '角色提示词' }],
|
|
references: [
|
|
{
|
|
title: '角色规范',
|
|
label: '角色规范图',
|
|
refType: 'project-resource',
|
|
refId: 'spec-1',
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items.map((item) => item.label)).toEqual(['角色规范图', '角色主图']);
|
|
expect(items[0]?.prompt).toBe('规范提示词');
|
|
expect(items[0]?.author).toBe('创作者A');
|
|
expect(items[0]?.cost).toBe('5泥点');
|
|
expect(items[1]?.cost).toBe('12泥点');
|
|
});
|
|
|
|
it('prefers user generation inputs over internal assembled prompts', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'all',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'character-1',
|
|
label: '角色主图',
|
|
assetKind: 'character',
|
|
prompt:
|
|
'【系统内置】完整拼接提示词:用户的角色设定,自动添加视角、构图和抠图背景规则。',
|
|
actualPrompt:
|
|
'【系统内置】真正发送给模型的完整提示词,包含更多模型约束。',
|
|
generationInputs: {
|
|
fields: [
|
|
{ title: '角色设定', value: '用户的角色设定' },
|
|
{ title: '抠图背景色', value: '暖浅桃色 #FFD6C2' },
|
|
{ title: 'model', value: 'gpt-image-2' },
|
|
],
|
|
references: [],
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items).toHaveLength(1);
|
|
expect(items[0]?.prompt).toBe('用户的角色设定');
|
|
});
|
|
|
|
it('does not expose built-in extraction prompts as showcase descriptions', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'all',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'icon-sheet-1',
|
|
label: '图标素材图集',
|
|
assetKind: 'icon-spritesheet',
|
|
prompt: '【系统内置】固定素材提取提示词',
|
|
generationInputs: {
|
|
fields: [
|
|
{
|
|
title: '提取提示词',
|
|
value:
|
|
'仅提取被红色框框选的素材并整理成spritesheet,图集背景必须使用所选单一纯色抠图背景。',
|
|
},
|
|
{ title: 'model', value: 'gpt-image-2' },
|
|
],
|
|
references: [],
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items).toHaveLength(1);
|
|
expect(items[0]?.prompt).toBe('-');
|
|
});
|
|
|
|
it('keeps unset category visible in all tab and out of concrete category tabs', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'all',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'spec-1',
|
|
label: '未设置分类素材',
|
|
assetKind: 'spec',
|
|
showcaseCategory: null,
|
|
}),
|
|
createResource({
|
|
resourceId: 'ui-1',
|
|
label: '人工 UI 素材',
|
|
assetKind: 'character',
|
|
showcaseCategory: 'ui',
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items.map((item) => item.label)).toEqual([
|
|
'未设置分类素材',
|
|
'人工 UI 素材',
|
|
]);
|
|
expect(
|
|
buildCreationShowcaseItems({
|
|
activeTab: 'ui',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'spec-1',
|
|
label: '未设置分类素材',
|
|
assetKind: 'spec',
|
|
showcaseCategory: null,
|
|
}),
|
|
createResource({
|
|
resourceId: 'ui-1',
|
|
label: '人工 UI 素材',
|
|
assetKind: 'character',
|
|
showcaseCategory: 'ui',
|
|
}),
|
|
],
|
|
}).map((item) => item.label),
|
|
).toEqual(['人工 UI 素材']);
|
|
});
|
|
|
|
it('groups character animations under the referenced character image', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'characters',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'character-1',
|
|
label: '游戏角色图',
|
|
assetKind: 'character',
|
|
prompt: '角色提示词',
|
|
}),
|
|
createResource({
|
|
resourceId: 'animation-1',
|
|
label: '待机动画',
|
|
assetKind: 'character-animation',
|
|
imageSrc: 'data:image/png;base64,animation',
|
|
imageSequenceFrames: [
|
|
{
|
|
imageSrc: 'data:image/png;base64,animation-1',
|
|
width: 512,
|
|
height: 512,
|
|
},
|
|
{
|
|
imageSrc: 'data:image/png;base64,animation-2',
|
|
width: 512,
|
|
height: 512,
|
|
},
|
|
],
|
|
imageSequenceDurationMs: 4_000,
|
|
generationInputs: {
|
|
fields: [{ title: '动作描述', value: '待机动作' }],
|
|
references: [
|
|
{
|
|
title: '角色图片',
|
|
label: '游戏角色图',
|
|
refType: 'project-resource',
|
|
refId: 'character-1',
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items).toHaveLength(1);
|
|
expect(items[0]?.label).toBe('游戏角色图');
|
|
expect(items[0]?.previews.map((preview) => preview.label)).toEqual([
|
|
'游戏角色图',
|
|
'待机动画',
|
|
]);
|
|
});
|
|
|
|
it('groups UI design extraction assets under the referenced UI image', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'ui',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'ui-design-1',
|
|
label: '游戏UI图',
|
|
assetKind: 'ui-design',
|
|
prompt: 'UI提示词',
|
|
}),
|
|
createResource({
|
|
resourceId: 'icon-sheet-1',
|
|
label: 'UI子素材图集',
|
|
assetKind: 'icon-spritesheet',
|
|
generationInputs: {
|
|
fields: [{ title: '提取提示词', value: '提取UI子素材' }],
|
|
references: [
|
|
{
|
|
title: 'UI设计图',
|
|
label: '游戏UI图',
|
|
refType: 'project-resource',
|
|
refId: 'ui-design-1',
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
createResource({
|
|
resourceId: 'icon-1',
|
|
label: '开始按钮',
|
|
assetKind: 'icon',
|
|
generationInputs: {
|
|
fields: [{ title: '提取提示词', value: '提取UI子素材' }],
|
|
references: [
|
|
{
|
|
title: 'UI设计图',
|
|
label: '游戏UI图',
|
|
refType: 'project-resource',
|
|
refId: 'ui-design-1',
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items).toHaveLength(1);
|
|
expect(items[0]?.label).toBe('游戏UI图');
|
|
expect(items[0]?.previews.map((preview) => preview.label)).toEqual([
|
|
'游戏UI图',
|
|
'UI子素材图集',
|
|
'开始按钮',
|
|
]);
|
|
});
|
|
|
|
it('counts a spritesheet task only once when UI children share the same task', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'ui',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'ui-design-1',
|
|
label: '游戏UI图',
|
|
assetKind: 'ui-design',
|
|
model: 'gemini-3.1-flash-image-preview',
|
|
taskId: 'task-ui-design',
|
|
}),
|
|
createResource({
|
|
resourceId: 'icon-sheet-1',
|
|
label: 'UI子素材图集',
|
|
assetKind: 'icon-spritesheet',
|
|
model: 'gpt-image-2',
|
|
taskId: 'task-ui-extract',
|
|
generationInputs: {
|
|
fields: [{ title: '提取提示词', value: '提取UI子素材' }],
|
|
references: [
|
|
{
|
|
title: 'UI设计图',
|
|
label: '游戏UI图',
|
|
refType: 'project-resource',
|
|
refId: 'ui-design-1',
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
createResource({
|
|
resourceId: 'icon-1',
|
|
label: '开始按钮',
|
|
assetKind: 'icon',
|
|
model: 'gpt-image-2',
|
|
taskId: 'task-ui-extract',
|
|
generationInputs: {
|
|
fields: [{ title: '提取提示词', value: '提取UI子素材' }],
|
|
references: [
|
|
{
|
|
title: 'UI设计图',
|
|
label: '游戏UI图',
|
|
refType: 'project-resource',
|
|
refId: 'ui-design-1',
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items).toHaveLength(1);
|
|
expect(items[0]?.cost).toBe('15泥点');
|
|
});
|
|
|
|
it('keeps music assets as audio preview items', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'music',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'music-1',
|
|
label: '背景音乐',
|
|
imageSrc: '/generated-editor-audios/music.mp3',
|
|
assetKind: 'background-music',
|
|
prompt: '舒缓背景音乐',
|
|
priceMudPoints: '5',
|
|
}),
|
|
],
|
|
fallbackAuthorName: '当前用户',
|
|
});
|
|
|
|
expect(items).toHaveLength(1);
|
|
expect(items[0]?.previews[0]?.mediaType).toBe('audio');
|
|
expect(items[0]?.previews[0]?.coverSrc).toBe(
|
|
'/creation-home/audio-asset-cover.png',
|
|
);
|
|
expect(items[0]?.author).toBe('当前用户');
|
|
expect(items[0]?.cost).toBe('5泥点');
|
|
});
|
|
|
|
it('infers video cost from the duration field instead of legacy top-level metadata', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'all',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'video-1',
|
|
label: '角色宣传片',
|
|
assetKind: 'video',
|
|
imageSrc: '/generated-editor-videos/video.mp4',
|
|
model: 'seedance2.0-fast',
|
|
generationInputs: {
|
|
fields: [
|
|
{ title: '清晰度', value: '720p' },
|
|
{ title: '时长', value: '6秒' },
|
|
],
|
|
references: [],
|
|
durationSeconds: 4,
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items).toHaveLength(1);
|
|
expect(items[0]?.cost).toBe(
|
|
`${calculateEditorVideoPrice('seedance2.0-fast', '720p', 6)}泥点`,
|
|
);
|
|
});
|
|
|
|
it('infers video cost from typed V2 duration metadata', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'all',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'video-v2',
|
|
label: 'V2 角色宣传片',
|
|
assetKind: 'video',
|
|
imageSrc: '/generated-editor-videos/video-v2.mp4',
|
|
model: 'seedance2.0-fast',
|
|
generationInputs: {
|
|
version: 2,
|
|
action: 'video.generate',
|
|
fields: [
|
|
{ id: 'resolution', title: '清晰度', value: '720p' },
|
|
{ id: 'durationSeconds', title: '时长', value: 6 },
|
|
],
|
|
references: [],
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items).toHaveLength(1);
|
|
expect(items[0]?.cost).toBe(
|
|
`${calculateEditorVideoPrice('seedance2.0-fast', '720p', 6)}泥点`,
|
|
);
|
|
});
|
|
|
|
it('keeps character animation videos out of the marketing tab', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'marketing',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'animation-1',
|
|
label: '角色动画',
|
|
assetKind: 'character-animation',
|
|
imageSrc: '/generated-editor-videos/animation.mp4',
|
|
}),
|
|
createResource({
|
|
resourceId: 'poster-1',
|
|
label: '上线美宣视频',
|
|
assetKind: 'publication-material',
|
|
imageSrc: '/generated-editor-videos/poster.mp4',
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items).toHaveLength(1);
|
|
expect(items[0]?.label).toBe('上线美宣视频');
|
|
expect(items[0]?.previews[0]?.mediaType).toBe('video');
|
|
});
|
|
|
|
it.each([
|
|
{ frameCount: 32, durationMs: 4_000 },
|
|
{ frameCount: 40, durationMs: 5_000 },
|
|
{ frameCount: 48, durationMs: 6_000 },
|
|
])(
|
|
'carries a $frameCount-frame/$durationMs ms character action into the sequence preview',
|
|
({ frameCount, durationMs }) => {
|
|
const frames = Array.from({ length: frameCount }, (_, index) => ({
|
|
imageSrc: `/generated/action/frame-${index + 1}.png`,
|
|
objectKey: `generated/action/frame-${index + 1}.png`,
|
|
assetObjectId: `asset-object-frame-${index + 1}`,
|
|
width: 192,
|
|
height: 256,
|
|
}));
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'all',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'animation-1',
|
|
label: '待机动作',
|
|
assetKind: 'character-animation',
|
|
imageSrc: frames[0]?.imageSrc,
|
|
objectKey: frames[0]?.objectKey,
|
|
imageSequenceFrames: frames,
|
|
imageSequenceDurationMs: durationMs,
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items).toHaveLength(1);
|
|
expect(items[0]?.previews[0]).toMatchObject({
|
|
mediaType: 'image-sequence',
|
|
imageSequenceDurationMs: durationMs,
|
|
});
|
|
expect(items[0]?.previews[0]?.imageSequenceFrames).toHaveLength(
|
|
frameCount,
|
|
);
|
|
},
|
|
);
|
|
|
|
it('omits a damaged character action instead of falling back to its first frame', () => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'all',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'animation-broken',
|
|
label: '损坏动作',
|
|
assetKind: 'character-animation',
|
|
imageSrc: '/generated/action/frame-01.png',
|
|
objectKey: 'generated/action/frame-01.png',
|
|
imageSequenceFrames: [
|
|
{
|
|
imageSrc: '/generated/action/frame-01.png',
|
|
objectKey: 'generated/action/frame-01.png',
|
|
width: 192,
|
|
height: 256,
|
|
},
|
|
],
|
|
imageSequenceDurationMs: 5_000,
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items).toEqual([]);
|
|
});
|
|
|
|
it.each([
|
|
{ durationMs: 5_000, expectedCost: '103泥点' },
|
|
{ durationMs: 6_000, expectedCost: '123泥点' },
|
|
])(
|
|
'infers costs from $durationMs ms character actions when stored price is absent',
|
|
({ durationMs, expectedCost }) => {
|
|
const items = buildCreationShowcaseItems({
|
|
activeTab: 'characters',
|
|
projectResources: [
|
|
createResource({
|
|
resourceId: 'character-1',
|
|
label: '游戏角色图',
|
|
imageSrc: '/generated-editor-assets/character.png',
|
|
objectKey: 'generated-editor-assets/character.png',
|
|
assetKind: 'character',
|
|
model: 'gpt-image-2',
|
|
prompt: '角色提示词',
|
|
}),
|
|
createResource({
|
|
resourceId: 'animation-1',
|
|
label: '待机动画',
|
|
assetKind: 'character-animation',
|
|
imageSrc: '/generated-editor-videos/animation.mp4',
|
|
objectKey: 'generated-editor-videos/animation.mp4',
|
|
model: 'seedance2.0-fast',
|
|
imageSequenceDurationMs: durationMs,
|
|
generationInputs: {
|
|
fields: [{ title: '清晰度', value: '720p' }],
|
|
references: [
|
|
{
|
|
title: '角色图片',
|
|
label: '游戏角色图',
|
|
refType: 'project-resource',
|
|
refId: 'character-1',
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
expect(items).toHaveLength(1);
|
|
expect(items[0]?.cost).toBe(expectedCost);
|
|
expect(items[0]?.previews[0]?.objectKey).toBe(
|
|
'generated-editor-assets/character.png',
|
|
);
|
|
},
|
|
);
|
|
});
|