198 lines
5.1 KiB
TypeScript
198 lines
5.1 KiB
TypeScript
// Shared fixtures for the frozen-bundle tests: a small but realistic courseware
|
|
// (slide + quiz + interactive scenes, one agent, narration audio, media) and a
|
|
// helper that packages it with the production packager.
|
|
|
|
import type { GeneratedAgentConfig, Scene, Stage } from '@/lib/types/stage';
|
|
import type { MediaFileRecord } from '@/lib/utils/database';
|
|
import { packageCourseware } from '@/lib/bundle/packager';
|
|
|
|
export const AGENT: GeneratedAgentConfig = {
|
|
id: 'agent-1',
|
|
name: '苏老师',
|
|
role: 'teacher',
|
|
persona: '耐心、循循善诱的生物学老师',
|
|
avatar: '',
|
|
color: '#4caf50',
|
|
priority: 1,
|
|
};
|
|
|
|
export function textElement(content: string): unknown {
|
|
return {
|
|
type: 'text',
|
|
content,
|
|
id: `el-${content.length}`,
|
|
left: 0,
|
|
top: 0,
|
|
width: 100,
|
|
height: 20,
|
|
rotate: 0,
|
|
defaultFontName: 'sans-serif',
|
|
defaultColor: '#000',
|
|
};
|
|
}
|
|
|
|
export function slideScene(id: string, order: number, title: string): Scene {
|
|
return {
|
|
id,
|
|
stageId: 'cw-1',
|
|
title,
|
|
order,
|
|
content: {
|
|
type: 'slide',
|
|
canvas: {
|
|
id: `canvas-${id}`,
|
|
viewportSize: 100,
|
|
viewportRatio: 1.6,
|
|
theme: { color: '#fff', backgroundColor: '#000', fontName: 'sans-serif' },
|
|
elements: [
|
|
textElement('<h1>光合作用</h1>'),
|
|
textElement('<p>叶绿体是光合作用的场所。</p>'),
|
|
],
|
|
},
|
|
},
|
|
actions: [
|
|
{
|
|
id: `act-${id}`,
|
|
type: 'speech',
|
|
text: '今天我们来学习光合作用。',
|
|
audioId: 'aud-1',
|
|
voice: 'v1',
|
|
},
|
|
],
|
|
} as unknown as Scene;
|
|
}
|
|
|
|
export function quizScene(id: string, order: number, title: string): Scene {
|
|
return {
|
|
id,
|
|
stageId: 'cw-1',
|
|
title,
|
|
order,
|
|
content: {
|
|
type: 'quiz',
|
|
questions: [
|
|
{
|
|
id: 'q1',
|
|
type: 'single',
|
|
question: '光合作用发生在细胞的哪个结构?',
|
|
options: [
|
|
{ label: '线粒体', value: 'A' },
|
|
{ label: '叶绿体', value: 'B' },
|
|
{ label: '细胞核', value: 'C' },
|
|
],
|
|
answer: ['B'],
|
|
analysis: '叶绿体是光合作用的场所。',
|
|
hasAnswer: true,
|
|
points: 1,
|
|
},
|
|
{
|
|
id: 'q2',
|
|
type: 'short_answer',
|
|
question: '简述光合作用的意义。',
|
|
commentPrompt: '围绕能量转换和氧气生成作答',
|
|
hasAnswer: false,
|
|
points: 2,
|
|
},
|
|
],
|
|
},
|
|
} as unknown as Scene;
|
|
}
|
|
|
|
export function interactiveScene(id: string, order: number, title: string): Scene {
|
|
return {
|
|
id,
|
|
stageId: 'cw-1',
|
|
title,
|
|
order,
|
|
content: {
|
|
type: 'interactive',
|
|
html: '<div id="app">虚拟光合实验</div><script>window.__secret = 1;</script>',
|
|
},
|
|
} as unknown as Scene;
|
|
}
|
|
|
|
export function makeStage(): Stage {
|
|
return {
|
|
id: 'cw-1',
|
|
name: '示例课件:光合作用',
|
|
description: '面向初中生的光合作用入门课',
|
|
createdAt: 1700000000000,
|
|
updatedAt: 1700000000001,
|
|
languageDirective: 'zh-CN',
|
|
style: 'light',
|
|
generatedAgentConfigs: [AGENT],
|
|
} as Stage;
|
|
}
|
|
|
|
export function makeScenes(): Scene[] {
|
|
return [
|
|
slideScene('sc-1', 1, '光合作用概述'),
|
|
quizScene('sc-2', 2, '小测验'),
|
|
interactiveScene('sc-3', 3, '模拟实验'),
|
|
];
|
|
}
|
|
|
|
export function mediaRecord(
|
|
id: string,
|
|
type: 'image' | 'video',
|
|
mimeType: string,
|
|
withPoster = false,
|
|
): MediaFileRecord {
|
|
return {
|
|
id,
|
|
stageId: 'cw-1',
|
|
type,
|
|
blob: new Blob([new Uint8Array([1, 2, 3])], { type: mimeType }),
|
|
mimeType,
|
|
size: 3,
|
|
prompt: '一张叶绿体示意图',
|
|
params: '{}',
|
|
...(withPoster
|
|
? { poster: new Blob([new Uint8Array([9, 8, 7])], { type: 'image/jpeg' }) }
|
|
: {}),
|
|
createdAt: 1700000000000,
|
|
} as MediaFileRecord;
|
|
}
|
|
|
|
export const AUDIO_BYTES = new Blob([new Uint8Array([4, 5, 6])], { type: 'audio/mpeg' });
|
|
|
|
export const FIXED_PUBLISHED_AT = '2026-08-15T00:00:00.000Z';
|
|
|
|
export interface PackageSampleOptions {
|
|
coursewareId?: string;
|
|
version?: number;
|
|
withMedia?: boolean;
|
|
missingAudio?: boolean;
|
|
requireComplete?: boolean;
|
|
}
|
|
|
|
/** Package the standard fixture courseware with the production packager. */
|
|
export function packageSampleBundle(options: PackageSampleOptions = {}) {
|
|
const {
|
|
coursewareId = 'cw-1',
|
|
version = 1,
|
|
withMedia = true,
|
|
missingAudio = false,
|
|
requireComplete = false,
|
|
} = options;
|
|
return packageCourseware({
|
|
coursewareId,
|
|
version,
|
|
stage: makeStage(),
|
|
scenes: makeScenes(),
|
|
mediaRecords: withMedia
|
|
? [mediaRecord('cw-1:img-1', 'image', 'image/png'), mediaRecord('cw-1:vid-1', 'video', 'video/mp4', true)]
|
|
: [],
|
|
resolveAudioBytes: missingAudio
|
|
? async () => null
|
|
: async (audioId) => (audioId === 'aud-1' ? AUDIO_BYTES : null),
|
|
resolveMediaBytes: async (record) =>
|
|
record.id.includes('img')
|
|
? new Blob([new Uint8Array([7, 7, 7])], { type: 'image/png' })
|
|
: new Blob([new Uint8Array([8, 8, 8])], { type: 'video/mp4' }),
|
|
publishedAt: FIXED_PUBLISHED_AT,
|
|
appVersion: 'test',
|
|
requireComplete,
|
|
});
|
|
}
|