修复重复生成占位的对账判定
收集项目快照中全部同 ID 的生成占位原始记录。 通用队列链在任一重复占位未收口时执行第二次项目读取。 完美像素遇到重复操作占位时失败关闭为冲突。 补充两条缺陷定向测试并同步技术方案与决策记录。
This commit is contained in:
@@ -791,19 +791,20 @@ function isCanvasSettingsLayoutItem(
|
||||
}
|
||||
|
||||
/**
|
||||
* 中文注释:从快照里取出指定 id 的生成占位原始记录,取不到返回 null。
|
||||
* 中文注释:从快照里取出指定 id 的全部生成占位原始记录,取不到返回空数组。
|
||||
*
|
||||
* 用原始 record 而不是 hydrate:调用方要判的是服务端写了什么,hydrate 会给缺失字段补默认值
|
||||
* (例如 status 缺失时补 `idle`),把「服务端没写」和「服务端写了 idle」混成一种。
|
||||
*/
|
||||
export function findCanvasGenerationDialogRecord(
|
||||
export function findCanvasGenerationDialogRecords(
|
||||
project: EditorProjectSnapshot,
|
||||
dialogId: string | null | undefined,
|
||||
): Record<string, unknown> | null {
|
||||
): Record<string, unknown>[] {
|
||||
const normalizedDialogId = dialogId?.trim();
|
||||
if (!normalizedDialogId) {
|
||||
return null;
|
||||
return [];
|
||||
}
|
||||
const matchingDialogs: Record<string, unknown>[] = [];
|
||||
for (const item of project.layers) {
|
||||
if (item.itemType !== 'generation-dialog') {
|
||||
continue;
|
||||
@@ -814,10 +815,10 @@ export function findCanvasGenerationDialogRecord(
|
||||
? ((item as { dialog?: unknown }).dialog as Record<string, unknown>)
|
||||
: null;
|
||||
if (dialog?.id === normalizedDialogId) {
|
||||
return dialog;
|
||||
matchingDialogs.push(dialog);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return matchingDialogs;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2009,7 +2009,7 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('reloads a queued project snapshot again when the completion dialog is still unresolved', async () => {
|
||||
it('reloads a queued project snapshot when a later duplicate completion dialog is unresolved', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const applyProjectSnapshot = vi.fn();
|
||||
@@ -2020,6 +2020,16 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
title: '队列项目',
|
||||
viewport: { x: 0, y: 0, scale: 1 },
|
||||
layers: [
|
||||
{
|
||||
layerId: 'generation-dialog-background-removal-completed',
|
||||
resourceId: 'generation-dialog-background-removal-completed',
|
||||
itemType: 'generation-dialog',
|
||||
dialog: {
|
||||
id: 'dialog-background-removal',
|
||||
status: 'idle',
|
||||
generatedLayerId: 'layer-background-removal-result',
|
||||
},
|
||||
},
|
||||
{
|
||||
layerId: 'generation-dialog-background-removal',
|
||||
resourceId: 'generation-dialog-background-removal',
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
loadEditorProject,
|
||||
} from '../../services/image-editor/editorProjectClient';
|
||||
import {
|
||||
findCanvasGenerationDialogRecord,
|
||||
findCanvasGenerationDialogRecords,
|
||||
isUnresolvedCanvasGenerationDialogRecord,
|
||||
} from './ImageCanvasEditorModel';
|
||||
import type {
|
||||
@@ -456,8 +456,8 @@ function projectHasUnresolvedGenerationDialog(
|
||||
project: EditorProjectSnapshot,
|
||||
dialogId: string | null | undefined,
|
||||
) {
|
||||
return isUnresolvedCanvasGenerationDialogRecord(
|
||||
findCanvasGenerationDialogRecord(project, dialogId),
|
||||
return findCanvasGenerationDialogRecords(project, dialogId).some(
|
||||
isUnresolvedCanvasGenerationDialogRecord,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1254,6 +1254,34 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
).toMatchObject({ kind: 'pending', project });
|
||||
});
|
||||
|
||||
it('rejects duplicate perfect-pixel dialog ids even when the first record appears applied', () => {
|
||||
const operationId = 'perfect-pixel-duplicate-dialog';
|
||||
const project = createPerfectPixelProject(operationId, 'applied');
|
||||
project.layers.push({
|
||||
itemType: 'generation-dialog',
|
||||
layerId: `generation-dialog:${operationId}:duplicate`,
|
||||
resourceId: `generation-dialog:${operationId}:duplicate`,
|
||||
dialog: {
|
||||
id: operationId,
|
||||
mode: 'quick-edit',
|
||||
prompt: '完美像素',
|
||||
status: 'generating',
|
||||
composerOpen: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
inspectPerfectPixelProjectSnapshot(project, {
|
||||
operationId,
|
||||
taskId: `pixel-art-snap-${operationId}`,
|
||||
}),
|
||||
).toMatchObject({
|
||||
kind: 'conflict',
|
||||
project,
|
||||
message: '权威项目中存在重复的完美像素占位,无法自动确认结果。',
|
||||
});
|
||||
});
|
||||
|
||||
it('requires a matching task resource before a missing dialog can prove asset-only success', () => {
|
||||
const operationId = 'perfect-pixel-missing-dialog';
|
||||
const matchingProject = createPerfectPixelProject(
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
} from '../../services/image-editor/editorProjectClient';
|
||||
import { resizeCropExpandFrame } from './ImageCanvasCropExpandModel';
|
||||
import {
|
||||
findCanvasGenerationDialogRecord,
|
||||
findCanvasGenerationDialogRecords,
|
||||
isUnresolvedCanvasGenerationDialogRecord,
|
||||
PERFECT_PIXEL_RECONCILIATION_WINDOW_MS,
|
||||
} from './ImageCanvasEditorModel';
|
||||
@@ -267,7 +267,7 @@ export function inspectPerfectPixelProjectSnapshot(
|
||||
const matchingResources = project.resources.filter(
|
||||
(resource) => resource.taskId?.trim() === operation.taskId,
|
||||
);
|
||||
const dialog = findCanvasGenerationDialogRecord(
|
||||
const matchingDialogs = findCanvasGenerationDialogRecords(
|
||||
project,
|
||||
operation.operationId,
|
||||
);
|
||||
@@ -279,6 +279,14 @@ export function inspectPerfectPixelProjectSnapshot(
|
||||
message: '权威项目中存在重复的完美像素任务资源,无法自动确认结果。',
|
||||
};
|
||||
}
|
||||
if (matchingDialogs.length > 1) {
|
||||
return {
|
||||
kind: 'conflict',
|
||||
project,
|
||||
message: '权威项目中存在重复的完美像素占位,无法自动确认结果。',
|
||||
};
|
||||
}
|
||||
const dialog = matchingDialogs[0] ?? null;
|
||||
const resource = matchingResources[0] ?? null;
|
||||
if (!resource) {
|
||||
if (!dialog || isUnresolvedCanvasGenerationDialogRecord(dialog)) {
|
||||
|
||||
Reference in New Issue
Block a user