Add UI-design asset extraction & spritesheet support

Introduce a new UI design asset extraction flow: add POST /api/editor/ui-designs/assets/extractions handler that always uses gpt-image-2 with the fixed prompt "提取画面中的所有独立并整理成spritesheet"; parse Data URL references, call OpenAI image edits, return a spritesheet and sliced icon assets. Increase request body limit for image Data URLs and wire the route with bearer auth. Add platform-image slicing: slice_generated_icon_spritesheet_all_by_connected_components (with auto-naming "素材 1..N", foreground estimation and heuristics) and export it. Add Rust tests for the handler, prompt constant, and slicing behavior. Update frontend client and integration tests to call the new extractEditorUiDesignAssets API and verify spritesheet + icon layers are added to the canvas and persisted. Update multiple docs to describe the extraction UI, toolbar placement, and related audio/playback pitfalls. Minor related updates: toolbar button ordering and audio duration / signed read-url notes.
This commit is contained in:
2026-06-19 10:09:19 +08:00
parent ea29887d9c
commit 097170836d
36 changed files with 1092 additions and 110 deletions
@@ -9,6 +9,7 @@ import {
deleteEditorAssetFolder,
deleteEditorProject,
editEditorImage,
extractEditorUiDesignAssets,
generateEditorBackgroundMusic,
generateEditorCharacterAnimation,
generateEditorIconSpritesheet,
@@ -666,6 +667,49 @@ describe('editorProjectClient', () => {
);
});
it('extracts UI design assets through the dedicated backend BFF', async () => {
requestJsonMock.mockResolvedValueOnce({
spritesheetImageSrc: 'data:image/png;base64,ui-sheet',
spritesheetWidth: 1024,
spritesheetHeight: 1024,
iconImageSrcs: [
{
name: '素材 1',
imageSrc: 'data:image/png;base64,asset-1',
width: 128,
height: 128,
},
],
prompt: '提取画面中的所有独立并整理成spritesheet',
actualPrompt: '提取画面中的所有独立并整理成spritesheet',
model: 'gpt-image-2',
provider: 'VectorEngine',
taskId: 'ui-asset-extraction-1',
priceMudPoints: 12,
});
const result = await extractEditorUiDesignAssets({
sourceImageSrc: 'data:image/png;base64,ui-design',
});
expect(result.taskId).toBe('ui-asset-extraction-1');
expect(requestJsonMock).toHaveBeenCalledWith(
'/api/editor/ui-designs/assets/extractions',
expect.objectContaining({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sourceImageSrc: 'data:image/png;base64,ui-design',
}),
}),
'提取UI设计图素材失败',
expect.objectContaining({
timeoutMs: 1_200_000,
retry: { maxRetries: 0 },
}),
);
});
it('passes spec generation size and kind to the backend BFF', async () => {
requestJsonMock.mockResolvedValueOnce({
imageSrc: 'data:image/png;base64,spec',
@@ -6,6 +6,8 @@ const EDITOR_IMAGE_GENERATION_API = '/api/editor/images/generations';
const EDITOR_IMAGE_EDIT_API = '/api/editor/images/edits';
const EDITOR_ICON_SPRITESHEET_GENERATION_API =
'/api/editor/icon-spritesheets/generations';
const EDITOR_UI_DESIGN_ASSET_EXTRACTION_API =
'/api/editor/ui-designs/assets/extractions';
const EDITOR_CHARACTER_ANIMATION_GENERATION_API =
'/api/editor/character-animations/generations';
const EDITOR_VIDEO_GENERATION_API = '/api/editor/videos/generations';
@@ -106,6 +108,10 @@ export type EditorIconSpritesheetGenerationInput = {
priceMudPoints: number;
};
export type EditorUiDesignAssetExtractionInput = {
sourceImageSrc: string;
};
export type EditorImageEditInput = {
prompt: string;
sourceImageSrc: string;
@@ -577,6 +583,24 @@ export async function generateEditorIconSpritesheet(
);
}
export async function extractEditorUiDesignAssets(
input: EditorUiDesignAssetExtractionInput,
) {
return requestJson<EditorIconSpritesheetGenerationResponse>(
EDITOR_UI_DESIGN_ASSET_EXTRACTION_API,
jsonRequest('POST', {
sourceImageSrc: input.sourceImageSrc,
}),
'提取UI设计图素材失败',
{
timeoutMs: 1_200_000,
retry: {
maxRetries: 0,
},
},
);
}
export async function editEditorImage(input: EditorImageEditInput) {
return requestJson<EditorImageGenerationResponse>(
EDITOR_IMAGE_EDIT_API,