321 lines
9.1 KiB
TypeScript
321 lines
9.1 KiB
TypeScript
import { expect, test } from 'vitest';
|
|
|
|
import type {
|
|
CreativeAgentSessionSnapshot,
|
|
CreativeAgentSseEvent,
|
|
} from '../../../packages/shared/src/contracts/creativeAgent';
|
|
import { buildCreativeAgentProcessItems } from './creativeAgentViewModel';
|
|
|
|
function createSession(
|
|
overrides: Partial<CreativeAgentSessionSnapshot> = {},
|
|
): CreativeAgentSessionSnapshot {
|
|
return {
|
|
sessionId: 'creative-session-1',
|
|
stage: 'target_ready',
|
|
inputSummary: {
|
|
text: '做一个生日拼图',
|
|
entryContext: 'creation_home',
|
|
images: [],
|
|
materialSummary: '做一个生日拼图',
|
|
unsupportedCapabilities: [],
|
|
},
|
|
messages: [],
|
|
puzzleTemplateCatalog: [],
|
|
puzzleTemplateSelection: null,
|
|
puzzleImageGenerationPlan: null,
|
|
targetBinding: {
|
|
playType: 'puzzle',
|
|
targetSessionId: 'puzzle-session-1',
|
|
targetStage: 'puzzle-result',
|
|
resultProfileId: 'puzzle-profile-1',
|
|
},
|
|
updatedAt: '2026-05-05T10:00:00.000Z',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
test('buildCreativeAgentProcessItems expands creative agent sse details', () => {
|
|
const eventLog: CreativeAgentSseEvent[] = [
|
|
{
|
|
event: 'stage',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
stage: 'perceiving',
|
|
},
|
|
},
|
|
{
|
|
event: 'thought_summary_delta',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
thoughtId: 'thought-1',
|
|
textDelta: '正在理解生日素材,',
|
|
},
|
|
},
|
|
{
|
|
event: 'thought_summary_delta',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
thoughtId: 'thought-1',
|
|
textDelta: '并准备转成拼图关卡。',
|
|
},
|
|
},
|
|
{
|
|
event: 'tool_started',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
toolCallId: 'tool-1',
|
|
toolName: 'retrieve_puzzle_template_catalog',
|
|
summary: '读取拼图模板',
|
|
},
|
|
},
|
|
{
|
|
event: 'tool_completed',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
toolCallId: 'tool-1',
|
|
toolName: 'retrieve_puzzle_template_catalog',
|
|
summary: '已读取拼图模板',
|
|
},
|
|
},
|
|
{
|
|
event: 'puzzle_template_catalog',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
templates: [
|
|
{
|
|
templateId: 'puzzle.default-creative',
|
|
title: '创意拼图',
|
|
summary: '把图文灵感做成拼图。',
|
|
previewImageSrc: null,
|
|
supportedLevelMode: 'single_or_multi',
|
|
minLevelCount: 1,
|
|
maxLevelCount: 6,
|
|
defaultLevelCount: 1,
|
|
costRange: {
|
|
minPoints: 2,
|
|
maxPoints: 12,
|
|
pricingUnit: 'point',
|
|
reason: '按关卡数估算',
|
|
},
|
|
requiredDraftFields: ['workTitle'],
|
|
imagePolicy: {
|
|
allowUploadedImageDirectly: true,
|
|
allowGeneratedImages: true,
|
|
allowPerLevelReferenceImage: true,
|
|
defaultCandidateCountPerLevel: 1,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
event: 'puzzle_template_selection',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
selection: {
|
|
templateId: 'puzzle.default-creative',
|
|
title: '创意拼图',
|
|
reason: '适合把生日素材做成拼图。',
|
|
costRange: {
|
|
minPoints: 2,
|
|
maxPoints: 12,
|
|
pricingUnit: 'point',
|
|
reason: '按关卡数估算',
|
|
},
|
|
supportedLevelMode: 'single_or_multi',
|
|
selectedLevelMode: 'multi_level',
|
|
plannedLevelCount: 3,
|
|
requiresUserConfirmation: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
event: 'puzzle_level_plan',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
plan: {
|
|
mode: 'multi_level',
|
|
templateId: 'puzzle.default-creative',
|
|
estimatedCostRange: {
|
|
minPoints: 2,
|
|
maxPoints: 12,
|
|
pricingUnit: 'point',
|
|
reason: '按关卡数估算',
|
|
},
|
|
levels: [
|
|
{
|
|
levelId: 'level-1',
|
|
levelName: '生日开场',
|
|
pictureDescription: '蛋糕和礼物',
|
|
imagePrompt: '蛋糕和礼物',
|
|
pictureReference: null,
|
|
candidateCount: 1,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
event: 'done',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
},
|
|
},
|
|
];
|
|
|
|
const items = buildCreativeAgentProcessItems(eventLog, createSession());
|
|
|
|
expect(items.map((item) => item.title)).toContain('素材已理解');
|
|
expect(items.map((item) => item.title)).toContain('思考摘要');
|
|
expect(items.find((item) => item.title === '思考摘要')?.detail).toBe(
|
|
'正在理解生日素材,并准备转成拼图关卡。',
|
|
);
|
|
expect(items.find((item) => item.title === '思考摘要')?.tone).toBe('done');
|
|
expect(items.map((item) => item.title)).toContain('开始:读取拼图模板');
|
|
expect(items.find((item) => item.title === '开始:读取拼图模板')?.tone).toBe(
|
|
'done',
|
|
);
|
|
expect(items.map((item) => item.title)).toContain('读取 1 个模板');
|
|
expect(items.map((item) => item.title)).toContain('选择 创意拼图');
|
|
expect(items.map((item) => item.title)).toContain('规划 1 个关卡');
|
|
expect(items.at(-1)?.detailLines).toContain('目标会话:puzzle-session-1');
|
|
expect(items.some((item) => item.tone === 'active')).toBe(false);
|
|
});
|
|
|
|
test('buildCreativeAgentProcessItems only keeps current running stage active', () => {
|
|
const eventLog: CreativeAgentSseEvent[] = [
|
|
{
|
|
event: 'stage',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
stage: 'perceiving',
|
|
},
|
|
},
|
|
{
|
|
event: 'stage',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
stage: 'thinking',
|
|
},
|
|
},
|
|
];
|
|
|
|
const items = buildCreativeAgentProcessItems(
|
|
eventLog,
|
|
createSession({
|
|
stage: 'thinking',
|
|
targetBinding: null,
|
|
}),
|
|
);
|
|
|
|
expect(items.find((item) => item.title === '素材已理解')?.tone).toBe('done');
|
|
expect(items.find((item) => item.title === '正在构思')?.tone).toBe('active');
|
|
expect(items.filter((item) => item.tone === 'active')).toHaveLength(1);
|
|
});
|
|
|
|
test('buildCreativeAgentProcessItems stops spinners after waiting confirmation', () => {
|
|
const eventLog: CreativeAgentSseEvent[] = [
|
|
{
|
|
event: 'stage',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
stage: 'perceiving',
|
|
},
|
|
},
|
|
{
|
|
event: 'stage',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
stage: 'selecting_puzzle_template',
|
|
},
|
|
},
|
|
{
|
|
event: 'thought_summary_delta',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
thoughtId: 'thought-1',
|
|
textDelta: '已经选择合适模板。',
|
|
},
|
|
},
|
|
{
|
|
event: 'tool_started',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
toolCallId: 'tool-start',
|
|
toolName: 'retrieve_puzzle_template_catalog',
|
|
summary: '读取拼图模板',
|
|
},
|
|
},
|
|
{
|
|
event: 'tool_completed',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
toolCallId: 'tool-done',
|
|
toolName: 'retrieve_puzzle_template_catalog',
|
|
summary: '已读取拼图模板',
|
|
},
|
|
},
|
|
{
|
|
event: 'stage',
|
|
data: {
|
|
sessionId: 'creative-session-1',
|
|
stage: 'waiting_template_confirmation',
|
|
},
|
|
},
|
|
];
|
|
|
|
const items = buildCreativeAgentProcessItems(
|
|
eventLog,
|
|
createSession({
|
|
stage: 'waiting_template_confirmation',
|
|
targetBinding: null,
|
|
}),
|
|
);
|
|
|
|
expect(items.find((item) => item.title === '思考摘要')?.tone).toBe('done');
|
|
expect(items.find((item) => item.title === '开始:读取拼图模板')?.tone).toBe(
|
|
'done',
|
|
);
|
|
expect(items.find((item) => item.title === '等待确认')?.tone).toBe('warning');
|
|
expect(items.some((item) => item.tone === 'active')).toBe(false);
|
|
});
|
|
|
|
test('buildCreativeAgentProcessItems falls back to session snapshots', () => {
|
|
const session = createSession({
|
|
puzzleTemplateSelection: {
|
|
templateId: 'puzzle.default-creative',
|
|
title: '创意拼图',
|
|
reason: '适合拼图创作。',
|
|
costRange: {
|
|
minPoints: 2,
|
|
maxPoints: 12,
|
|
pricingUnit: 'point',
|
|
reason: '按关卡数估算',
|
|
},
|
|
supportedLevelMode: 'single_or_multi',
|
|
selectedLevelMode: 'single_level',
|
|
plannedLevelCount: 1,
|
|
requiresUserConfirmation: true,
|
|
},
|
|
});
|
|
|
|
const items = buildCreativeAgentProcessItems([], session);
|
|
|
|
expect(items.map((item) => item.title)).toContain('选择 创意拼图');
|
|
expect(items.map((item) => item.title)).toContain('拼图草稿已绑定');
|
|
});
|
|
|
|
test('buildCreativeAgentProcessItems renders waiting session fallback as static', () => {
|
|
const items = buildCreativeAgentProcessItems(
|
|
[],
|
|
createSession({
|
|
stage: 'waiting_template_confirmation',
|
|
targetBinding: null,
|
|
}),
|
|
);
|
|
|
|
expect(items.map((item) => item.title)).toContain('等待确认');
|
|
expect(items.find((item) => item.title === '等待确认')?.tone).toBe('warning');
|
|
expect(items.some((item) => item.tone === 'active')).toBe(false);
|
|
});
|