新增画布完美像素功能

在图片工具栏接入完美像素交互、占位流程与历史保护

新增严格像素网格检测和登录态同步处理接口

补齐素材类型校验、持久化边界、并发限制及技术文档

修复 Windows rlib 门禁长文件名解析
This commit is contained in:
2026-07-30 11:42:45 +00:00
parent b512430e53
commit ee2cad377f
25 changed files with 2469 additions and 64 deletions
@@ -25,6 +25,7 @@ import {
removeEditorImageBackground,
renameEditorProject,
saveEditorProjectLayout,
snapEditorImageToPixelArt,
splitEditorIconSpritesheet,
submitEditorAssetShowcase,
toggleEditorShowcaseAssetLike,
@@ -1741,4 +1742,103 @@ describe('editorProjectClient', () => {
}),
);
});
it('submits a stable perfect-pixel source without unsafe POST retries', async () => {
requestJsonMock.mockResolvedValueOnce({
imageSrc: '/generated-images/editor/perfect-pixel.png',
objectKey: 'generated-images/editor/perfect-pixel.png',
assetObjectId: 'asset-object-perfect-pixel',
width: 1024,
height: 768,
sourceType: 'generated',
taskId: 'perfect-pixel-1',
elapsedMs: 321,
provider: 'Genarrative',
resource: { resourceId: 'resource-perfect-pixel' },
asset: { assetId: 'asset-perfect-pixel' },
project: null,
});
const result = await snapEditorImageToPixelArt({
sourceImageSrc: 'generated-images/editor/source.png',
projectId: 'editor-project-1',
sourceResourceId: 'resource-source',
assetKind: 'character',
generationInputs: {
fields: [{ title: '角色设定', value: '红发骑士' }],
references: [],
},
assetFolderId: 'project',
assetLabel: '源图 · 完美像素',
canvasCompletion: {
dialogId: 'generation-dialog-perfect-pixel',
title: '源图 · 完美像素',
placeholder: {
x: 472,
y: 140,
width: 320,
height: 240,
originalWidth: 320,
originalHeight: 240,
},
},
});
expect(result.taskId).toBe('perfect-pixel-1');
expect(requestJsonMock).toHaveBeenCalledWith(
'/api/editor/images/pixel-art-snaps',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sourceImageSrc: 'generated-images/editor/source.png',
projectId: 'editor-project-1',
sourceResourceId: 'resource-source',
assetKind: 'character',
generationInputs: {
fields: [{ title: '角色设定', value: '红发骑士' }],
references: [],
},
assetFolderId: 'project',
assetLabel: '源图 · 完美像素',
canvasCompletion: {
dialogId: 'generation-dialog-perfect-pixel',
title: '源图 · 完美像素',
placeholder: {
x: 472,
y: 140,
width: 320,
height: 240,
originalWidth: 320,
originalHeight: 240,
},
},
}),
},
'完美像素处理失败',
{ timeoutMs: 120_000 },
);
});
it('rejects inline perfect-pixel media before sending the request', async () => {
await expect(
snapEditorImageToPixelArt({
sourceImageSrc: 'data:image/png;base64,source',
projectId: 'editor-project-1',
canvasCompletion: {
dialogId: 'generation-dialog-perfect-pixel',
title: '源图 · 完美像素',
placeholder: {
x: 0,
y: 0,
width: 320,
height: 240,
originalWidth: 320,
originalHeight: 240,
},
},
}),
).rejects.toThrow('待完美像素处理图片必须先上传 OSS');
expect(requestJsonMock).not.toHaveBeenCalled();
});
});
@@ -10,6 +10,7 @@ const EDITOR_PROJECT_RESOURCE_API_BASE = '/api/editor/project-resources';
const EDITOR_IMAGE_GENERATION_API = '/api/editor/images/generations';
const EDITOR_IMAGE_EDIT_API = '/api/editor/images/edits';
const EDITOR_BACKGROUND_REMOVAL_API = '/api/editor/images/background-removals';
const EDITOR_PIXEL_ART_SNAP_API = '/api/editor/images/pixel-art-snaps';
const EDITOR_ICON_SPRITESHEET_GENERATION_API =
'/api/editor/icon-spritesheets/generations';
const EDITOR_ICON_SPRITESHEET_SLICE_API =
@@ -294,6 +295,19 @@ export type EditorBackgroundRemovalInput = {
canvasCompletion?: EditorCanvasGenerationCompletionInput | null;
};
export type EditorPixelArtSnapInput = {
sourceImageSrc: string;
projectId: string;
sourceResourceId?: string | null;
assetKind?: string | null;
generationInputs?: EditorAssetGenerationInputs | null;
assetFolderId?: string | null;
assetLabel?: string | null;
canvasCompletion: EditorCanvasGenerationCompletionInput & {
dialogId: string;
};
};
export type EditorImageGenerationResult = {
imageSrc: string;
objectKey?: string | null;
@@ -317,6 +331,21 @@ export type EditorBackgroundRemovalResult = {
queueState: ExternalGenerationJobStatusRecord;
};
export type EditorPixelArtSnapResult = {
imageSrc: string;
objectKey: string;
assetObjectId: string;
width: number;
height: number;
sourceType: 'generated';
taskId: string;
elapsedMs: number;
provider: 'Genarrative';
resource: EditorProjectResourceSnapshot;
asset: EditorAssetSnapshot;
project: EditorProjectSnapshot | null;
};
export type EditorIconSpritesheetIconResult = {
name: string;
imageSrc: string;
@@ -659,6 +688,7 @@ type EditorShowcaseAssetResponse = {
type EditorImageGenerationResponse = EditorImageGenerationResult;
type EditorBackgroundRemovalResponse = EditorBackgroundRemovalResult;
type EditorPixelArtSnapResponse = EditorPixelArtSnapResult;
type EditorIconSpritesheetGenerationResponse =
EditorIconSpritesheetGenerationResult;
type EditorVideoGenerationResponse = EditorVideoGenerationResult;
@@ -1114,6 +1144,33 @@ export async function removeEditorImageBackground(
);
}
export async function snapEditorImageToPixelArt(
input: EditorPixelArtSnapInput,
) {
assertStableEditorMediaReference(input.sourceImageSrc, '待完美像素处理图片');
return requestJson<EditorPixelArtSnapResponse>(
EDITOR_PIXEL_ART_SNAP_API,
jsonRequest('POST', {
sourceImageSrc: input.sourceImageSrc,
projectId: input.projectId,
...(input.sourceResourceId
? { sourceResourceId: input.sourceResourceId }
: {}),
...(input.assetKind ? { assetKind: input.assetKind } : {}),
...(input.generationInputs
? { generationInputs: input.generationInputs }
: {}),
...(input.assetFolderId ? { assetFolderId: input.assetFolderId } : {}),
...(input.assetLabel ? { assetLabel: input.assetLabel } : {}),
canvasCompletion: input.canvasCompletion,
}),
'完美像素处理失败',
{
timeoutMs: 120_000,
},
);
}
export async function generateEditorCharacterAnimation(
input: EditorCharacterAnimationGenerationInput,
) {