Increase VectorEngine timeouts and add image UI

Add VectorEngine image generation config and raise request timeouts (env + scripts) from 180000 to 1000000ms. Introduce a reusable CreativeImageInputPanel component with tests and wire up mobile keyboard-focus helpers; update generation views and related tests (CustomWorldGenerationView, BarkBattle editor, Match3D, Puzzle flows). Improve API error handling / VectorEngine request guidance (packages/shared http.ts and docs), and apply multiple backend/frontend fixes for puzzle/match3d/prompt handling. Also include extensive docs and decision-log updates describing UI/UX decisions and verification steps.
This commit is contained in:
2026-05-15 02:40:59 +08:00
parent 4642855fd0
commit 74fd9a33ac
87 changed files with 5508 additions and 1261 deletions

View File

@@ -165,6 +165,10 @@ function createSession(
return session;
}
function openPuzzleLevelsTab() {
fireEvent.click(screen.getByRole('button', { name: '拼图关卡' }));
}
describe('PuzzleResultView', () => {
test('renders level list and work info tabs', () => {
render(
@@ -176,14 +180,21 @@ describe('PuzzleResultView', () => {
/>,
);
expect(screen.getByRole('button', { name: '拼图关卡' })).toBeTruthy();
expect(screen.getByRole('button', { name: '作品信息' })).toBeTruthy();
expect(screen.getByRole('button', { name: '素材配置' })).toBeTruthy();
const workInfoTab = screen.getByRole('button', { name: '作品信息' });
const levelsTab = screen.getByRole('button', { name: '拼图关卡' });
const assetsTab = screen.getByRole('button', { name: '素材配置' });
expect(workInfoTab).toBeTruthy();
expect(levelsTab).toBeTruthy();
expect(assetsTab).toBeTruthy();
expect(
workInfoTab.compareDocumentPosition(levelsTab) &
Node.DOCUMENT_POSITION_FOLLOWING,
).toBeTruthy();
expect(
levelsTab.compareDocumentPosition(assetsTab) &
Node.DOCUMENT_POSITION_FOLLOWING,
).toBeTruthy();
expect(screen.queryByRole('button', { name: '音乐' })).toBeNull();
expect(screen.getByText('雨夜猫街')).toBeTruthy();
expect(screen.getByText('获得更多积分激励')).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: '作品信息' }));
expect(screen.getByLabelText('作品名称')).toHaveProperty(
'value',
'暖灯猫街作品',
@@ -192,6 +203,10 @@ describe('PuzzleResultView', () => {
'value',
'一套雨夜猫街主题拼图。',
);
openPuzzleLevelsTab();
expect(screen.getByText('雨夜猫街')).toBeTruthy();
expect(screen.getByText('获得更多积分激励')).toBeTruthy();
});
test('result action bar restores draft trial entry', () => {
@@ -276,6 +291,7 @@ describe('PuzzleResultView', () => {
/>,
);
openPuzzleLevelsTab();
fireEvent.click(screen.getByText('雨夜猫街'));
const dialog = screen.getByRole('dialog', { name: '关卡详情' });
fireEvent.change(within(dialog).getByLabelText('关卡名称'), {
@@ -366,6 +382,7 @@ describe('PuzzleResultView', () => {
/>,
);
openPuzzleLevelsTab();
fireEvent.click(screen.getByRole('button', { name: /新增关卡/u }));
const dialog = screen.getByRole('dialog', { name: '关卡详情' });
expect(
@@ -427,6 +444,7 @@ describe('PuzzleResultView', () => {
/>,
);
openPuzzleLevelsTab();
fireEvent.click(screen.getByRole('button', { name: /新增关卡/u }));
const dialog = screen.getByRole('dialog', { name: '关卡详情' });
fireEvent.change(within(dialog).getByLabelText('画面描述'), {
@@ -478,6 +496,7 @@ describe('PuzzleResultView', () => {
/>,
);
openPuzzleLevelsTab();
fireEvent.click(screen.getByText('雨夜猫街'));
fireEvent.click(screen.getByRole('button', { name: /重新生成画面/u }));
fireEvent.click(
@@ -514,6 +533,7 @@ describe('PuzzleResultView', () => {
/>,
);
openPuzzleLevelsTab();
fireEvent.click(screen.getByText('雨夜猫街'));
fireEvent.click(screen.getByRole('button', { name: /重新生成画面/u }));
fireEvent.click(
@@ -534,6 +554,7 @@ describe('PuzzleResultView', () => {
);
fireEvent.click(screen.getByLabelText('关闭'));
openPuzzleLevelsTab();
fireEvent.click(screen.getByRole('button', { name: /新增关卡/u }));
expect(screen.getByRole('dialog', { name: '关卡详情' })).toBeTruthy();
fireEvent.click(screen.getByLabelText('关闭'));
@@ -976,8 +997,8 @@ describe('PuzzleResultView', () => {
ownerLabel: '账号 user-1',
profileId: null,
entityId: 'puzzle-session-1',
createdAt: '2026-04-27T10:00:00.000Z',
updatedAt: '2026-04-27T10:00:00.000Z',
createdAt: '1713686400.000000Z',
updatedAt: '1713686400.000000Z',
},
]);
@@ -989,6 +1010,7 @@ describe('PuzzleResultView', () => {
/>,
);
openPuzzleLevelsTab();
fireEvent.click(screen.getByText('雨夜猫街'));
const dialog = screen.getByRole('dialog', { name: '关卡详情' });
const uploadInput = within(dialog).getByLabelText('上传参考图', {
@@ -1004,13 +1026,17 @@ describe('PuzzleResultView', () => {
const picker = await screen.findByRole('dialog', {
name: '选择历史图片',
});
expect(await within(picker).findByText('image.png')).toBeTruthy();
expect(await within(picker).findByText(/2024\/04\/21/u)).toBeTruthy();
expect(within(picker).queryByText('账号 user-1')).toBeNull();
fireEvent.click(
await within(picker).findByRole('button', { name: /账号 user-1/u }),
await within(picker).findByRole('button', { name: /image\.png/u }),
);
await waitFor(() => {
expect(screen.queryByRole('dialog', { name: '选择历史图片' })).toBeNull();
});
expect(screen.getByText('历史素材 · image.png')).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: /重新生成画面/u }));
fireEvent.click(
@@ -1057,6 +1083,7 @@ describe('PuzzleResultView', () => {
/>,
);
openPuzzleLevelsTab();
fireEvent.click(screen.getByText('雨夜猫街'));
fireEvent.click(screen.getByRole('button', { name: /重新生成画面/u }));
fireEvent.click(
@@ -1087,6 +1114,7 @@ describe('PuzzleResultView', () => {
/>,
);
openPuzzleLevelsTab();
fireEvent.click(screen.getByText('雨夜猫街'));
const dialog = screen.getByRole('dialog', { name: '关卡详情' });
fireEvent.click(within(dialog).getByRole('button', { name: '图片模型' }));

View File

@@ -25,6 +25,7 @@ import type {
} from '../../../packages/shared/src/contracts/puzzleAgentDraft';
import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession';
import { updatePuzzleWork } from '../../services/puzzle-works';
import { getPuzzleHistoryAssetReferenceLabel } from '../../services/puzzle-works/puzzleHistoryAsset';
import { resolvePuzzleUiBackgroundSource } from '../../services/puzzle-runtime/puzzleUiBackgroundSource';
import { readPuzzleReferenceImageAsDataUrl } from '../../services/puzzleReferenceImage';
import { useAuthUi } from '../auth/AuthUiContext';
@@ -75,8 +76,8 @@ const PUZZLE_UI_BACKGROUND_REFERENCE_SRC =
'/ui-previews/puzzle-image-compact-ui-2026-05-08.png';
const PUZZLE_RESULT_TABS: Array<{ id: PuzzleResultTab; label: string }> = [
{ id: 'levels', label: '拼图关卡' },
{ id: 'work', label: '作品信息' },
{ id: 'levels', label: '拼图关卡' },
{ id: 'assets', label: '素材配置' },
];
@@ -1006,7 +1007,7 @@ function PuzzleLevelDetailDialog({
onSelect={(asset) => {
setReferenceImageSrc(asset.imageSrc);
setReferenceImageLabel(
`历史素材 · ${asset.ownerLabel || '未记录账号'}`,
getPuzzleHistoryAssetReferenceLabel(asset.imageSrc),
);
setReferenceImageError(null);
setIsHistoryPickerOpen(false);
@@ -1815,7 +1816,7 @@ export function PuzzleResultView({
creativeDraftEdit = null,
}: PuzzleResultViewProps) {
const draft = session.draft;
const [activeTab, setActiveTab] = useState<PuzzleResultTab>('levels');
const [activeTab, setActiveTab] = useState<PuzzleResultTab>('work');
const [activeAssetConfigTab, setActiveAssetConfigTab] =
useState<PuzzleAssetConfigTabId>('ui');
const [activeLevelId, setActiveLevelId] = useState<string | null>(null);