合并master并保留Spine序列帧修复

合并master当前工程、后端、前端和文档更新

按已确认方案解决Spine序列帧多模态、帧数和快速编辑冲突

保留当前分支底部工具栏宽度与隐藏滚动条样式
This commit is contained in:
2026-08-14 21:33:26 +08:00
338 changed files with 69729 additions and 8828 deletions
@@ -105,7 +105,7 @@ import {
EDITOR_IMAGE_DIMENSION_OPTIONS,
IMAGE_MODEL_GPT_IMAGE_2,
isCanvasGenerationDialog,
isQuickEditUnsupportedAssetKind,
isQuickEditSupportedLayer,
normalizeEditorImageModel,
resizeGenerationPlaceholderToCharacterAnimationSelection,
resizeGenerationPlaceholderToImageSelection,
@@ -361,9 +361,14 @@ export type PerfectPixelProjectVerdict =
export function inspectPerfectPixelProjectSnapshot(
project: EditorProjectSnapshot,
operation: Pick<PerfectPixelOperationSnapshot, 'operationId' | 'taskId'>,
resultResourceId?: string | null,
): PerfectPixelProjectVerdict {
const normalizedResultResourceId = resultResourceId?.trim() ?? '';
const matchingResources = project.resources.filter(
(resource) => resource.taskId?.trim() === operation.taskId,
(resource) =>
resource.taskId?.trim() === operation.taskId ||
(normalizedResultResourceId !== '' &&
resource.resourceId.trim() === normalizedResultResourceId),
);
const matchingDialogs = findCanvasGenerationDialogRecords(
project,
@@ -396,6 +401,20 @@ export function inspectPerfectPixelProjectSnapshot(
message: '完美像素占位已收口,但权威项目缺少对应任务资源。',
};
}
if (
(normalizedResultResourceId !== '' &&
resource.resourceId.trim() !== normalizedResultResourceId) ||
resource.taskId?.trim() !== operation.taskId ||
resource.projectId !== project.projectId ||
!resource.objectKey?.trim() ||
!resource.imageSrc.trim()
) {
return {
kind: 'conflict',
project,
message: '完美像素任务资源记录不完整或不属于当前项目,无法自动确认结果。',
};
}
if (!dialog) {
return { kind: 'dialog-missing', project, resource };
}
@@ -463,7 +482,7 @@ function waitForPerfectPixelReconciliationDelay(
async function reconcilePerfectPixelProject(
projectId: string,
operation: PerfectPixelOperationSnapshot,
options: { signal?: AbortSignal } = {},
options: { signal?: AbortSignal; resultResourceId?: string | null } = {},
): Promise<PerfectPixelProjectVerdict> {
let attempt = 0;
let hasAttemptedRead = false;
@@ -505,6 +524,7 @@ async function reconcilePerfectPixelProject(
const verdict = inspectPerfectPixelProjectSnapshot(
latestProject,
operation,
options.resultResourceId,
);
if (verdict.kind !== 'pending') {
return verdict;
@@ -1825,7 +1845,7 @@ export function useImageCanvasGenerationWorkflow({
const openQuickEditPanel = useCallback(
(sourceLayer: CanvasLayer) => {
if (isQuickEditUnsupportedAssetKind(sourceLayer)) {
if (!isQuickEditSupportedLayer(sourceLayer)) {
return;
}
setImageContextMenu(null);
@@ -2861,6 +2881,11 @@ export function useImageCanvasGenerationWorkflow({
error instanceof ApiClientError &&
(error.details as { operationResultAlreadyExists?: unknown } | null)
?.operationResultAlreadyExists === true;
const existingResultResourceId =
operationResultAlreadyExists && error instanceof ApiClientError
? (error.details as { resultResourceId?: unknown } | null)
?.resultResourceId
: null;
const outcomeMayBePersisted =
perfectPixelPostAttempted &&
Boolean(perfectPixelDialogId) &&
@@ -2884,6 +2909,12 @@ export function useImageCanvasGenerationWorkflow({
const verdict = await reconcilePerfectPixelProject(
normalizedProjectId,
perfectPixelOperation,
{
resultResourceId:
typeof existingResultResourceId === 'string'
? existingResultResourceId
: null,
},
);
if (!isPerfectPixelAuthorityCurrent(operationAuthority)) {
return;
@@ -3110,6 +3141,11 @@ export function useImageCanvasGenerationWorkflow({
error instanceof ApiClientError &&
(error.details as { operationResultAlreadyExists?: unknown } | null)
?.operationResultAlreadyExists === true;
const existingResultResourceId =
operationResultAlreadyExists && error instanceof ApiClientError
? (error.details as { resultResourceId?: unknown } | null)
?.resultResourceId
: null;
const outcomeMayBePersisted =
postAttempted &&
(!(error instanceof ApiClientError) ||
@@ -3120,6 +3156,12 @@ export function useImageCanvasGenerationWorkflow({
const verdict = await reconcilePerfectPixelProject(
normalizedProjectId,
retriedOperation,
{
resultResourceId:
typeof existingResultResourceId === 'string'
? existingResultResourceId
: null,
},
);
if (!isPerfectPixelAuthorityCurrent(operationAuthority)) {
return;