修正手动去背景入队响应契约

统一手动去背景客户端类型为 queueState 响应

移除前端同步完成分支并更新队列测试

修正文档中的接口响应说明
This commit is contained in:
2026-07-15 09:29:40 +00:00
parent 72cbac30e7
commit 4033b23149
5 changed files with 44 additions and 227 deletions
File diff suppressed because one or more lines are too long
@@ -1868,21 +1868,19 @@ describe('useImageCanvasGenerationWorkflow', () => {
expect(screen.getByTestId('sidebar').textContent).toBe('layers');
});
it('resolves private character image before removing background', async () => {
it('queues background removal for private character images', async () => {
resolveEditorImageReferenceDataUrlMock.mockResolvedValueOnce(
'data:image/png;base64,resolved-character',
);
removeImageBackgroundMock.mockResolvedValueOnce({
imageSrc:
'/generated-character-drafts/editor/background-removal/result.png',
objectKey:
'generated-character-drafts/editor/background-removal/result.png',
assetObjectId: 'asset-object-background-removal',
width: 512,
height: 768,
taskId: 'background-removal-task',
elapsedMs: 1234,
provider: 'BgFilter',
queueState: {
operationId: 'background-removal-task',
status: 'completed',
phaseLabel: '已完成',
phaseDetail: '已完成',
progress: 100,
updatedAtMicros: 1,
},
});
render(
<GenerationWorkflowHarness
@@ -1912,19 +1910,20 @@ describe('useImageCanvasGenerationWorkflow', () => {
sourceResourceId: 'resource-source',
});
});
await waitFor(() => {
expect(screen.getByTestId('task-refresh-key').textContent).toBe('1');
});
expect(screen.getByTestId('layers').textContent).toContain(
'layer-source:源图:resource-source:character:/generated-character-drafts/editor/background-removal/result.png:background-removal-task',
'/api/assets/read-url?objectKey=generated-character-drafts/editor/private.png',
);
expect(screen.getByTestId('layers').textContent).not.toContain(
'background-removal-task',
);
expect(screen.getByTestId('task-refresh-key').textContent).toBe('0');
});
it('opens a generating canvas placeholder when removing background in a project', async () => {
let resolveBackgroundRemoval: ((value: unknown) => void) | null = null;
removeImageBackgroundMock.mockImplementationOnce(
() =>
new Promise((resolve) => {
resolveBackgroundRemoval = resolve;
}),
() => new Promise(() => {}),
);
render(
@@ -1960,136 +1959,6 @@ describe('useImageCanvasGenerationWorkflow', () => {
}),
);
expect(resolveEditorImageReferenceDataUrlMock).not.toHaveBeenCalled();
await act(async () => {
resolveBackgroundRemoval?.({
imageSrc:
'/generated-character-drafts/editor/background-removal/project-result.png',
objectKey:
'generated-character-drafts/editor/background-removal/project-result.png',
assetObjectId: 'asset-object-background-removal',
width: 512,
height: 768,
taskId: 'background-removal-project-task',
elapsedMs: 1234,
provider: 'BgFilter',
project: null,
});
});
expect(screen.getByTestId('layers').textContent).not.toContain(
'project-result.png',
);
expect(screen.getByTestId('layers').textContent).not.toContain(
'background-removal-project-task',
);
});
it('preserves a just-created crop layer when applying a background removal project snapshot', async () => {
const applyProjectSnapshot = vi.fn();
let resolveBackgroundRemoval: ((value: unknown) => void) | null = null;
removeImageBackgroundMock.mockImplementationOnce(
() =>
new Promise((resolve) => {
resolveBackgroundRemoval = resolve;
}),
);
render(
<GenerationWorkflowHarness
projectId="project-1"
applyProjectSnapshot={applyProjectSnapshot}
initialLayers={[
createLayer({
id: 'layer-crop-expand-1',
resourceId: 'resource-crop-expand',
title: '源图 裁扩',
src: '/generated-character-drafts/editor/crop-expand/project-1/result.png',
objectKey:
'generated-character-drafts/editor/crop-expand/project-1/result.png',
assetObjectId: 'asset-object-crop-expand',
sourceType: 'generated',
sourceResourceId: 'resource-source',
}),
]}
/>,
);
fireEvent.click(screen.getByRole('button', { name: '去除背景' }));
await waitFor(() => expect(removeImageBackgroundMock).toHaveBeenCalled());
await act(async () => {
resolveBackgroundRemoval?.({
imageSrc:
'/generated-character-drafts/editor/background-removal/project-result.png',
objectKey:
'generated-character-drafts/editor/background-removal/project-result.png',
assetObjectId: 'asset-object-background-removal',
width: 512,
height: 768,
taskId: 'background-removal-project-task',
elapsedMs: 1234,
provider: 'BgFilter',
project: {
projectId: 'project-1',
title: '队列项目',
viewport: { x: 0, y: 0, scale: 1 },
layers: [
{
layerId: 'layer-background-removal-result',
resourceId: 'resource-background-removal',
title: '源图 裁扩 去背景',
x: 480,
y: 140,
width: 512,
height: 768,
originalWidth: 512,
originalHeight: 768,
zIndex: 9,
sourceType: 'generated',
},
],
resources: [
{
resourceId: 'resource-background-removal',
projectId: 'project-1',
imageSrc:
'/generated-character-drafts/editor/background-removal/project-result.png',
objectKey:
'generated-character-drafts/editor/background-removal/project-result.png',
assetObjectId: 'asset-object-background-removal',
width: 512,
height: 768,
sourceType: 'generated',
sourceResourceId: 'resource-crop-expand',
},
],
updatedAt: '2026-06-23T08:00:01.000Z',
},
});
});
const appliedProject = applyProjectSnapshot.mock.calls[0]?.[0];
expect(appliedProject?.layers).toEqual(
expect.arrayContaining([
expect.objectContaining({
layerId: 'layer-crop-expand-1',
resourceId: 'resource-crop-expand',
title: '源图 裁扩',
}),
expect.objectContaining({
layerId: 'layer-background-removal-result',
resourceId: 'resource-background-removal',
}),
]),
);
expect(appliedProject?.resources).toEqual(
expect.arrayContaining([
expect.objectContaining({
resourceId: 'resource-crop-expand',
objectKey:
'generated-character-drafts/editor/crop-expand/project-1/result.png',
assetObjectId: 'asset-object-crop-expand',
}),
]),
);
});
it('opens UI design extraction as a mark selection state before submitting', () => {
@@ -1613,58 +1613,19 @@ export function useImageCanvasGenerationWorkflow({
}
: {}),
});
if (
await applyQueuedEditorGenerationProject(
result,
projectId,
applyProjectSnapshot,
refreshTaskListForQueuedGeneration,
onWalletBalanceMayHaveChanged,
setGenerationWarning,
backgroundRemovalDialogId,
applyBackgroundRemovalProjectSnapshot
? (project) =>
preserveSourceLayerInProjectSnapshot(project, sourceLayer)
: undefined,
)
) {
return;
}
if (result.project && applyBackgroundRemovalProjectSnapshot) {
applyBackgroundRemovalProjectSnapshot(result.project);
return;
}
if (backgroundRemovalPlacement?.placeholder && applyProjectSnapshot) {
return;
}
updateSourceLayer(sourceLayer.id, (layer) => ({
...layer,
resourceId: result.resource?.resourceId ?? layer.resourceId,
src: result.imageSrc,
width: result.width,
height: result.height,
originalWidth: result.width,
originalHeight: result.height,
sourceType: result.sourceType ?? 'generated',
provider: result.provider ?? 'BgFilter',
taskId: result.taskId ?? null,
objectKey: result.resource?.objectKey ?? result.objectKey ?? null,
assetObjectId:
result.resource?.assetObjectId ?? result.assetObjectId ?? null,
sourceResourceId:
result.resource?.sourceResourceId ?? sourceLayer.resourceId,
sourceAssetId: null,
prompt: result.resource?.prompt ?? layer.prompt,
actualPrompt: result.resource?.actualPrompt ?? layer.actualPrompt,
model: result.resource?.model ?? layer.model,
assetKind:
(result.resource?.assetKind as CanvasLayer['assetKind']) ??
layer.assetKind,
generationInputs:
result.resource?.generationInputs ?? layer.generationInputs,
generatedAssetSnapshot: result.asset ?? layer.generatedAssetSnapshot,
}));
setActiveSidebarPanel('layers');
await applyQueuedEditorGenerationProject(
result,
projectId,
applyProjectSnapshot,
refreshTaskListForQueuedGeneration,
onWalletBalanceMayHaveChanged,
setGenerationWarning,
backgroundRemovalDialogId,
applyBackgroundRemovalProjectSnapshot
? (project) =>
preserveSourceLayerInProjectSnapshot(project, sourceLayer)
: undefined,
);
} catch (error) {
if (backgroundRemovalDialogId) {
updateCanvasGenerationDialogById(
@@ -1689,11 +1650,9 @@ export function useImageCanvasGenerationWorkflow({
openPlacedCanvasGenerationDialog,
projectId,
refreshTaskListForQueuedGeneration,
setActiveSidebarPanel,
setImageContextMenu,
setMetadataLayer,
updateCanvasGenerationDialogById,
updateSourceLayer,
],
);
@@ -1618,18 +1618,17 @@ describe('editorProjectClient', () => {
it('passes canvas completion context to background removal', async () => {
requestJsonMock.mockResolvedValueOnce({
imageSrc: 'data:image/png;base64,cutout',
width: 512,
height: 512,
objectKey: 'generated-character-drafts/editor-cutouts/cutout.png',
assetObjectId: 'asset-object-cutout',
taskId: 'background-removal-1',
elapsedMs: 1200,
provider: 'BgFilter',
project: null,
queueState: {
operationId: 'background-removal-1',
status: 'queued',
phaseLabel: '排队中',
phaseDetail: '排队中',
progress: 0,
updatedAtMicros: 1,
},
});
await removeEditorImageBackground({
const response = await removeEditorImageBackground({
sourceImageSrc: 'data:image/png;base64,source',
projectId: 'editor-project-1',
targetLayerId: 'layer-source',
@@ -1647,6 +1646,8 @@ describe('editorProjectClient', () => {
},
});
expect(response.queueState.operationId).toBe('background-removal-1');
expect(requestJsonMock).toHaveBeenCalledWith(
'/api/editor/images/background-removals',
expect.objectContaining({
@@ -277,19 +277,7 @@ export type EditorImageGenerationResult = {
};
export type EditorBackgroundRemovalResult = {
imageSrc: string;
objectKey?: string | null;
assetObjectId?: string | null;
width: number;
height: number;
sourceType?: 'generated';
taskId?: string | null;
elapsedMs?: number;
provider?: string;
resource?: EditorProjectResourceSnapshot | null;
asset?: EditorAssetSnapshot | null;
project?: EditorProjectSnapshot | null;
queueState?: ExternalGenerationJobStatusRecord | null;
queueState: ExternalGenerationJobStatusRecord;
};
export type EditorIconSpritesheetIconResult = {