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:
@@ -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: '图片模型' }));
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user