抽出编辑器生成提交流水线
新增 useImageCanvasGenerationSubmissionWorkflow 承载生成提交和结果落图副作用 补充生成提交流水线 hook 单测 精简 useImageCanvasGenerationWorkflow 的提交编排逻辑 更新 TRACKING.md 记录第四十三执行批次验证
This commit is contained in:
+51
-43
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,19 +7,8 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { resolveEditorImageReferenceDataUrl } from '../../services/image-editor/editorImageReference';
|
||||
import {
|
||||
editEditorImage,
|
||||
type EditorIconSpritesheetGenerationResult,
|
||||
type EditorIconSpritesheetIconResult,
|
||||
type EditorImageGenerationResult,
|
||||
generateEditorCharacterAnimation,
|
||||
generateEditorIconSpritesheet,
|
||||
generateEditorImage,
|
||||
} from '../../services/image-editor/editorProjectClient';
|
||||
import type {
|
||||
CanvasGenerationDialogState,
|
||||
CanvasGenerationInputs,
|
||||
CanvasLayer,
|
||||
CanvasTool,
|
||||
CanvasViewport,
|
||||
@@ -50,25 +39,13 @@ import {
|
||||
updateSpecFormDialogValue,
|
||||
} from './ImageCanvasGenerationDialogModel';
|
||||
import {
|
||||
createGeneratedResultLayer,
|
||||
createIconSpritesheetResultLayers,
|
||||
createQuickEditResultLayer,
|
||||
} from './ImageCanvasGenerationLayerModel';
|
||||
import {
|
||||
buildEditGenerationInputs,
|
||||
buildQuickEditModelOptions,
|
||||
buildQuickEditSizeOptions,
|
||||
calculateCharacterAnimationPrice,
|
||||
DEFAULT_ICON_DESCRIPTIONS,
|
||||
DEFAULT_IMAGE_MODEL,
|
||||
isCanvasGenerationDialog,
|
||||
resolveImageGenerationErrorMessage,
|
||||
} from './ImageCanvasGenerationModel';
|
||||
import {
|
||||
buildCharacterAnimationSubmissionPlan,
|
||||
buildIconSpritesheetGenerationSubmissionPlan,
|
||||
buildImageGenerationSubmissionPlan,
|
||||
} from './ImageCanvasGenerationSubmissionModel';
|
||||
import { useImageCanvasGenerationSubmissionWorkflow } from './useImageCanvasGenerationSubmissionWorkflow';
|
||||
|
||||
type CanvasSize = { width: number; height: number };
|
||||
|
||||
@@ -303,145 +280,6 @@ export function useImageCanvasGenerationWorkflow({
|
||||
],
|
||||
);
|
||||
|
||||
const addGeneratedResultLayer = useCallback(
|
||||
(
|
||||
generated: EditorImageGenerationResult,
|
||||
options: {
|
||||
sourceLayer?: CanvasLayer;
|
||||
frame?: GenerateDialogState['placeholder'];
|
||||
assetKind?: CanvasLayer['assetKind'];
|
||||
title?: string;
|
||||
dialogId?: string;
|
||||
generationInputs?: CanvasGenerationInputs;
|
||||
} = {},
|
||||
) => {
|
||||
layerCounterRef.current += 1;
|
||||
const generatedIndex = layerCounterRef.current;
|
||||
const nextLayer = createGeneratedResultLayer({
|
||||
generated,
|
||||
generatedIndex,
|
||||
canvasSize,
|
||||
viewport,
|
||||
sourceLayer: options.sourceLayer,
|
||||
frame: options.frame,
|
||||
assetKind: options.assetKind,
|
||||
title: options.title,
|
||||
generationInputs: options.generationInputs,
|
||||
});
|
||||
|
||||
appendCanvasLayersWithResources([nextLayer]);
|
||||
selectSingleLayer(nextLayer.id);
|
||||
setActiveSidebarPanel('layers');
|
||||
if (options.sourceLayer) {
|
||||
setGenerateDialog(null);
|
||||
setActiveTool('select');
|
||||
} else if (options.dialogId) {
|
||||
updateCanvasGenerationDialogById(options.dialogId, (currentDialog) =>
|
||||
currentDialog.mode === 'character' || currentDialog.mode === 'icon'
|
||||
? null
|
||||
: {
|
||||
...currentDialog,
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
generatedLayerId: nextLayer.id,
|
||||
placeholder: undefined,
|
||||
errorMessage: undefined,
|
||||
},
|
||||
);
|
||||
}
|
||||
if (options.sourceLayer) {
|
||||
fitLayers([options.sourceLayer, nextLayer]);
|
||||
}
|
||||
},
|
||||
[
|
||||
appendCanvasLayersWithResources,
|
||||
canvasSize,
|
||||
fitLayers,
|
||||
layerCounterRef,
|
||||
selectSingleLayer,
|
||||
setActiveSidebarPanel,
|
||||
setActiveTool,
|
||||
setGenerateDialog,
|
||||
updateCanvasGenerationDialogById,
|
||||
viewport,
|
||||
],
|
||||
);
|
||||
|
||||
const addQuickEditResultLayer = useCallback(
|
||||
(
|
||||
generated: EditorImageGenerationResult,
|
||||
sourceLayer: CanvasLayer,
|
||||
generationInputs: CanvasGenerationInputs,
|
||||
) => {
|
||||
layerCounterRef.current += 1;
|
||||
const generatedIndex = layerCounterRef.current;
|
||||
const nextLayer = createQuickEditResultLayer({
|
||||
generated,
|
||||
generatedIndex,
|
||||
sourceLayer,
|
||||
generationInputs,
|
||||
});
|
||||
|
||||
appendCanvasLayersWithResources([nextLayer]);
|
||||
selectSingleLayer(nextLayer.id);
|
||||
setActiveSidebarPanel('layers');
|
||||
setQuickEditPanel(null);
|
||||
setActiveTool('select');
|
||||
fitLayers([sourceLayer, nextLayer]);
|
||||
},
|
||||
[
|
||||
appendCanvasLayersWithResources,
|
||||
fitLayers,
|
||||
layerCounterRef,
|
||||
selectSingleLayer,
|
||||
setActiveSidebarPanel,
|
||||
setActiveTool,
|
||||
],
|
||||
);
|
||||
|
||||
const addIconSpritesheetResultLayers = useCallback(
|
||||
(
|
||||
generated: EditorIconSpritesheetGenerationResult,
|
||||
iconResults: EditorIconSpritesheetIconResult[],
|
||||
generationInputs: CanvasGenerationInputs,
|
||||
frame?: GenerateDialogState['placeholder'],
|
||||
dialogId?: string,
|
||||
) => {
|
||||
const startIndex = layerCounterRef.current + 1;
|
||||
const nextLayers = createIconSpritesheetResultLayers({
|
||||
generated,
|
||||
iconResults,
|
||||
startIndex,
|
||||
canvasSize,
|
||||
viewport,
|
||||
generationInputs,
|
||||
frame,
|
||||
});
|
||||
|
||||
if (!nextLayers.length) {
|
||||
return;
|
||||
}
|
||||
layerCounterRef.current += nextLayers.length;
|
||||
appendCanvasLayersWithResources(nextLayers);
|
||||
selectSingleLayer(nextLayers[0]?.id ?? null);
|
||||
setActiveSidebarPanel('layers');
|
||||
if (dialogId) {
|
||||
removeCanvasGenerationDialogById(dialogId);
|
||||
}
|
||||
setActiveTool('select');
|
||||
},
|
||||
[
|
||||
appendCanvasLayersWithResources,
|
||||
canvasSize,
|
||||
layerCounterRef,
|
||||
removeCanvasGenerationDialogById,
|
||||
selectSingleLayer,
|
||||
setActiveSidebarPanel,
|
||||
setActiveTool,
|
||||
viewport,
|
||||
],
|
||||
);
|
||||
|
||||
const pickCharacterSpecFromLayer = useCallback(
|
||||
(layer: CanvasLayer) => {
|
||||
setGenerateDialog((currentDialog) =>
|
||||
@@ -493,197 +331,34 @@ export function useImageCanvasGenerationWorkflow({
|
||||
setGenerateDialog(appendIconDescriptionToDialog);
|
||||
}, [setGenerateDialog]);
|
||||
|
||||
const submitIconSpritesheetGeneration = useCallback(
|
||||
async (dialog: GenerateDialogState) => {
|
||||
if (dialog.mode !== 'icon') {
|
||||
return;
|
||||
}
|
||||
const canvasDialog = isCanvasGenerationDialog(dialog) ? dialog : null;
|
||||
const setSubmittingIconDialog = (
|
||||
nextDialog: CanvasGenerationDialogState,
|
||||
) => {
|
||||
updateCanvasGenerationDialogById(nextDialog.id, () => nextDialog);
|
||||
};
|
||||
const submissionPlan =
|
||||
buildIconSpritesheetGenerationSubmissionPlan(dialog);
|
||||
if (!submissionPlan.ok) {
|
||||
if (canvasDialog) {
|
||||
setSubmittingIconDialog({
|
||||
...canvasDialog,
|
||||
status: 'failed',
|
||||
composerOpen: true,
|
||||
errorMessage: submissionPlan.errorMessage,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canvasDialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmittingIconDialog({
|
||||
...canvasDialog,
|
||||
iconDescriptions: submissionPlan.iconDescriptions,
|
||||
status: 'generating',
|
||||
composerOpen: false,
|
||||
errorMessage: undefined,
|
||||
});
|
||||
|
||||
try {
|
||||
const generated = await generateEditorIconSpritesheet(
|
||||
submissionPlan.input,
|
||||
);
|
||||
setLastImageModel(submissionPlan.rememberImageModel);
|
||||
addIconSpritesheetResultLayers(
|
||||
generated,
|
||||
generated.iconImageSrcs,
|
||||
submissionPlan.generationInputs,
|
||||
getGeneratingDialogPlaceholder(dialog),
|
||||
canvasDialog.id,
|
||||
);
|
||||
} catch (error) {
|
||||
setSubmittingIconDialog({
|
||||
...canvasDialog,
|
||||
iconDescriptions: submissionPlan.iconDescriptions,
|
||||
status: 'failed',
|
||||
composerOpen: true,
|
||||
errorMessage: resolveImageGenerationErrorMessage(error),
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
addIconSpritesheetResultLayers,
|
||||
getGeneratingDialogPlaceholder,
|
||||
updateCanvasGenerationDialogById,
|
||||
],
|
||||
);
|
||||
|
||||
const submitQuickEdit = useCallback(async () => {
|
||||
if (!quickEditPanel || !quickEditSourceLayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedPrompt = quickEditPanel.prompt.trim() || '快速编辑图片';
|
||||
setQuickEditPanel({
|
||||
...quickEditPanel,
|
||||
prompt: normalizedPrompt,
|
||||
status: 'generating',
|
||||
errorMessage: undefined,
|
||||
});
|
||||
|
||||
try {
|
||||
const referenceImageSrc = await resolveEditorImageReferenceDataUrl(
|
||||
quickEditSourceLayer.src,
|
||||
);
|
||||
const generated = await generateEditorImage({
|
||||
prompt: normalizedPrompt,
|
||||
size: quickEditPanel.size,
|
||||
kind: 'quick-edit',
|
||||
model: quickEditPanel.model,
|
||||
referenceImageSrcs: [referenceImageSrc],
|
||||
});
|
||||
addQuickEditResultLayer(
|
||||
generated,
|
||||
quickEditSourceLayer,
|
||||
buildEditGenerationInputs(
|
||||
'快速编辑提示词',
|
||||
normalizedPrompt,
|
||||
quickEditSourceLayer,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
setQuickEditPanel({
|
||||
...quickEditPanel,
|
||||
prompt: normalizedPrompt,
|
||||
status: 'failed',
|
||||
errorMessage: resolveImageGenerationErrorMessage(error),
|
||||
});
|
||||
}
|
||||
}, [addQuickEditResultLayer, quickEditPanel, quickEditSourceLayer]);
|
||||
|
||||
const submitImageGeneration = useCallback(
|
||||
async (dialog: GenerateDialogState) => {
|
||||
const normalizedPrompt =
|
||||
dialog.prompt.trim() ||
|
||||
(dialog.mode === 'edit' ? '修改当前图片' : 'AI 生成图片');
|
||||
const canvasDialog = isCanvasGenerationDialog(dialog) ? dialog : null;
|
||||
if (canvasDialog) {
|
||||
updateCanvasGenerationDialogById(canvasDialog.id, (currentDialog) => ({
|
||||
...currentDialog,
|
||||
prompt: normalizedPrompt,
|
||||
status: 'generating',
|
||||
composerOpen: false,
|
||||
}));
|
||||
} else {
|
||||
setGenerateDialog({
|
||||
...dialog,
|
||||
prompt: normalizedPrompt,
|
||||
status: 'generating',
|
||||
composerOpen: dialog.mode === 'edit',
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const submissionPlan = buildImageGenerationSubmissionPlan({
|
||||
dialog,
|
||||
layers,
|
||||
nextGeneratedIndex: layerCounterRef.current + 1,
|
||||
});
|
||||
if (submissionPlan.kind === 'edit') {
|
||||
const referenceImageSrc = await resolveEditorImageReferenceDataUrl(
|
||||
submissionPlan.sourceLayer.src,
|
||||
);
|
||||
const generated = await editEditorImage({
|
||||
prompt: submissionPlan.normalizedPrompt,
|
||||
sourceImageSrc: referenceImageSrc,
|
||||
});
|
||||
addGeneratedResultLayer(generated, {
|
||||
sourceLayer: submissionPlan.sourceLayer,
|
||||
generationInputs: submissionPlan.generationInputs,
|
||||
});
|
||||
} else {
|
||||
const generated = await generateEditorImage(submissionPlan.input);
|
||||
if (submissionPlan.rememberImageModel) {
|
||||
setLastImageModel(submissionPlan.rememberImageModel);
|
||||
}
|
||||
addGeneratedResultLayer(generated, {
|
||||
frame: getGeneratingDialogPlaceholder(dialog),
|
||||
assetKind: submissionPlan.result.assetKind,
|
||||
title: submissionPlan.result.title,
|
||||
dialogId: canvasDialog?.id,
|
||||
generationInputs: submissionPlan.result.generationInputs,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (canvasDialog) {
|
||||
updateCanvasGenerationDialogById(canvasDialog.id, () => ({
|
||||
...canvasDialog,
|
||||
prompt: normalizedPrompt,
|
||||
status: 'failed',
|
||||
composerOpen: true,
|
||||
errorMessage: resolveImageGenerationErrorMessage(error),
|
||||
}));
|
||||
} else {
|
||||
setGenerateDialog({
|
||||
...dialog,
|
||||
prompt: normalizedPrompt,
|
||||
status: 'failed',
|
||||
composerOpen: true,
|
||||
errorMessage: resolveImageGenerationErrorMessage(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
addGeneratedResultLayer,
|
||||
getGeneratingDialogPlaceholder,
|
||||
layerCounterRef,
|
||||
layers,
|
||||
setGenerateDialog,
|
||||
updateCanvasGenerationDialogById,
|
||||
],
|
||||
);
|
||||
const generationSubmissionWorkflow = useImageCanvasGenerationSubmissionWorkflow({
|
||||
layers,
|
||||
canvasSize,
|
||||
viewport,
|
||||
layerCounterRef,
|
||||
quickEditPanel,
|
||||
quickEditSourceLayer,
|
||||
setQuickEditPanel,
|
||||
characterAnimationPanel,
|
||||
characterAnimationSourceLayer,
|
||||
setCharacterAnimationPanel,
|
||||
setGenerateDialog,
|
||||
updateCanvasGenerationDialogById,
|
||||
removeCanvasGenerationDialogById,
|
||||
getGeneratingDialogPlaceholder,
|
||||
appendCanvasLayersWithResources,
|
||||
selectSingleLayer,
|
||||
fitLayers,
|
||||
setActiveTool,
|
||||
setActiveSidebarPanel,
|
||||
rememberImageModel: setLastImageModel,
|
||||
});
|
||||
const {
|
||||
submitCharacterAnimation,
|
||||
submitIconSpritesheetGeneration,
|
||||
submitImageGeneration,
|
||||
submitQuickEdit,
|
||||
} = generationSubmissionWorkflow;
|
||||
|
||||
const updateSpecFormValue = useCallback(
|
||||
(key: keyof SpecFormValues, value: string) => {
|
||||
@@ -703,52 +378,6 @@ export function useImageCanvasGenerationWorkflow({
|
||||
[],
|
||||
);
|
||||
|
||||
const submitCharacterAnimation = useCallback(async () => {
|
||||
if (!characterAnimationPanel || !characterAnimationSourceLayer) {
|
||||
return;
|
||||
}
|
||||
const submissionPlan = buildCharacterAnimationSubmissionPlan({
|
||||
panel: characterAnimationPanel,
|
||||
sourceLayer: characterAnimationSourceLayer,
|
||||
});
|
||||
const nextPanel = {
|
||||
...characterAnimationPanel,
|
||||
promptText: submissionPlan.promptText,
|
||||
status: 'generating' as const,
|
||||
errorMessage: undefined,
|
||||
result: undefined,
|
||||
};
|
||||
setCharacterAnimationPanel(nextPanel);
|
||||
|
||||
try {
|
||||
const result = await generateEditorCharacterAnimation(
|
||||
submissionPlan.input,
|
||||
);
|
||||
setCharacterAnimationPanel((currentPanel) =>
|
||||
currentPanel
|
||||
? {
|
||||
...currentPanel,
|
||||
status: 'completed',
|
||||
result,
|
||||
}
|
||||
: currentPanel,
|
||||
);
|
||||
} catch (error) {
|
||||
setCharacterAnimationPanel((currentPanel) =>
|
||||
currentPanel
|
||||
? {
|
||||
...currentPanel,
|
||||
status: 'failed',
|
||||
errorMessage:
|
||||
error instanceof Error && error.message.trim()
|
||||
? error.message
|
||||
: '生成角色动画失败',
|
||||
}
|
||||
: currentPanel,
|
||||
);
|
||||
}
|
||||
}, [characterAnimationPanel, characterAnimationSourceLayer]);
|
||||
|
||||
const hideGeneratedLayerPanelAfterBlur = useCallback(() => {
|
||||
setGenerateDialog((currentDialog) =>
|
||||
hideGeneratedLayerComposerAfterBlur(currentDialog),
|
||||
|
||||
Reference in New Issue
Block a user