feat(projects): add interactive AI app scaffold skill

This commit is contained in:
2026-09-04 20:36:28 +08:00
parent 336e0bb0ca
commit 959b6333b5
40 changed files with 2647 additions and 65 deletions

View File

@@ -110,7 +110,7 @@ describe('ProjectPublishAction', () => {
it('creates a new project with a cover DTO and locks after Builder succeeds', async () => {
fetchCurrentWorksProjectStatusMock.mockResolvedValue(projectStatus('succeeded'));
render(<ProjectPublishAction project={project} projectType="mini_game" />);
render(<ProjectPublishAction project={project} projectType="interactive_ai_app" />);
await submitProjectMetadata();
expect(screen.getByRole('button', { name: '正在等待云端检查…' })).toBeDisabled();
@@ -130,7 +130,7 @@ describe('ProjectPublishAction', () => {
cover_url: null,
creator_name: '小明',
creator_age: 12,
category: 'mini_game',
category: 'interactive_ai_app',
age_band: null,
difficulty: null,
},
@@ -153,7 +153,7 @@ describe('ProjectPublishAction', () => {
});
it('blocks first submission until a cover is selected', async () => {
render(<ProjectPublishAction project={project} projectType="mini_game" />);
render(<ProjectPublishAction project={project} projectType="interactive_ai_app" />);
await submitProjectMetadata({ includeCover: false });
@@ -164,7 +164,7 @@ describe('ProjectPublishAction', () => {
it('rejects unsupported and oversized cover files before confirmation', async () => {
fetchCurrentWorksProjectStatusMock.mockRejectedValue(Object.assign(new Error('missing'), { statusCode: 404 }));
render(<ProjectPublishAction project={project} projectType="mini_game" />);
render(<ProjectPublishAction project={project} projectType="interactive_ai_app" />);
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: '一键提交审核' }));
await Promise.resolve();
@@ -185,7 +185,7 @@ describe('ProjectPublishAction', () => {
fetchCurrentWorksProjectStatusMock.mockResolvedValue(
projectStatus('failed', 'BROWSER_SMOKE_FAILED'),
);
render(<ProjectPublishAction project={project} projectType="mini_game" />);
render(<ProjectPublishAction project={project} projectType="interactive_ai_app" />);
await submitProjectMetadata();
await flushSubmission();
@@ -204,7 +204,7 @@ describe('ProjectPublishAction', () => {
fetchCurrentWorksProjectStatusMock.mockRejectedValue(
new Error('socket closed at /srv/private/status.py'),
);
render(<ProjectPublishAction project={project} projectType="mini_game" />);
render(<ProjectPublishAction project={project} projectType="interactive_ai_app" />);
await submitProjectMetadata();
await flushSubmission();
@@ -227,7 +227,7 @@ describe('ProjectPublishAction', () => {
},
});
fetchCurrentWorksProjectStatusMock.mockResolvedValue(projectStatus('succeeded'));
render(<ProjectPublishAction project={project} projectType="mini_game" />);
render(<ProjectPublishAction project={project} projectType="interactive_ai_app" />);
await submitProjectMetadata();
await flushSubmission();
@@ -248,7 +248,7 @@ describe('ProjectPublishAction', () => {
const status = projectStatus('queued');
status.latest_version.id = 'another-version';
fetchCurrentWorksProjectStatusMock.mockResolvedValue(status);
render(<ProjectPublishAction project={project} projectType="mini_game" />);
render(<ProjectPublishAction project={project} projectType="interactive_ai_app" />);
await submitProjectMetadata();
await flushSubmission();
@@ -268,7 +268,7 @@ describe('ProjectPublishAction', () => {
new Error('Traceback: failed at /srv/private/package.ts token=secret'),
{ code: 'INTERNAL_DATABASE_FAILURE', statusCode: 503 },
));
render(<ProjectPublishAction project={project} projectType="mini_game" />);
render(<ProjectPublishAction project={project} projectType="interactive_ai_app" />);
await submitProjectMetadata();
await flushSubmission();
@@ -285,7 +285,7 @@ describe('ProjectPublishAction', () => {
new Error('private current metadata token=secret'),
{ code: 'PROJECT_METADATA_CONFLICT', statusCode: 409 },
));
render(<ProjectPublishAction project={project} projectType="mini_game" />);
render(<ProjectPublishAction project={project} projectType="interactive_ai_app" />);
await submitProjectMetadata();
await flushSubmission();
@@ -302,7 +302,7 @@ describe('ProjectPublishAction', () => {
new Error('D:/repo/space-cleaner/package-lock.json does not exist'),
{ code: 'PROJECT_FILE_MISSING' },
));
render(<ProjectPublishAction project={project} projectType="mini_game" />);
render(<ProjectPublishAction project={project} projectType="interactive_ai_app" />);
await submitProjectMetadata();
await flushSubmission();
@@ -317,7 +317,7 @@ describe('ProjectPublishAction', () => {
it('keeps generated metadata within the server limits', async () => {
const longName = `作品-${'x'.repeat(250)}`;
render(<ProjectPublishAction project={{ ...project, name: longName }} projectType="mini_program" />);
render(<ProjectPublishAction project={{ ...project, name: longName }} projectType="interactive_ai_app" />);
await submitProjectMetadata();
await flushSubmission();
@@ -328,7 +328,7 @@ describe('ProjectPublishAction', () => {
expect(input.project.summary).toBe('这是一个太空清洁小游戏。');
expect(input.project.creator_name).toBe('小明');
expect(input.project.creator_age).toBe(12);
expect(input.project.category).toBe('mini_program');
expect(input.project.category).toBe('interactive_ai_app');
});
it('submits an existing draft as version-only without editable metadata or replacement cover', async () => {
@@ -339,10 +339,10 @@ describe('ProjectPublishAction', () => {
cover_url: 'coverimage/draft.jpg',
creator_name: '草稿作者',
creator_age: 10,
category: 'mini_game',
category: 'interactive_ai_app',
});
fetchCurrentWorksProjectStatusMock.mockResolvedValue(draftStatus);
render(<ProjectPublishAction project={project} projectType="mini_game" />);
render(<ProjectPublishAction project={project} projectType="interactive_ai_app" />);
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: '一键提交审核' }));
@@ -384,12 +384,12 @@ describe('ProjectPublishAction', () => {
cover_url: 'coverimage/existing.jpg',
creator_name: '原作者',
creator_age: 11,
category: 'mini_game',
category: 'interactive_ai_app',
age_band: '6-12岁',
difficulty: '入门',
});
fetchCurrentWorksProjectStatusMock.mockResolvedValue(publishedStatus);
render(<ProjectPublishAction project={project} projectType="mini_game" />);
render(<ProjectPublishAction project={project} projectType="interactive_ai_app" />);
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: '一键提交审核' }));