合并图标规范与图集生成优化

合入 PR 137 的图标规范生成、稳定引用与图集提示词链路
保留主分支现行音效提交、Prompt 模块与结果压缩逻辑
解决 External Worker、生成提交和客户端测试冲突
This commit is contained in:
2026-08-08 21:44:15 +08:00
76 changed files with 7690 additions and 3063 deletions
@@ -25,6 +25,7 @@ import {
extractEditorUiDesignAssets,
generateEditorBackgroundMusic,
generateEditorCharacterAnimation,
generateEditorIconSpec,
generateEditorIconSpritesheet,
generateEditorImage,
generateEditorScene,
@@ -83,7 +84,6 @@ import {
buildIconSpritesheetGenerationSubmissionPlan,
buildImageGenerationSubmissionPlan,
resolveGenerationAssetLabel,
resolveImageGenerationDialogPrompt,
} from './ImageCanvasGenerationSubmissionModel';
import { validateSoundEffectPrompt } from './ImageCanvasSoundEffectPromptModel';
import type {
@@ -362,7 +362,7 @@ type GenerationSubmissionWorkflowOptions = {
onGenerationWarning?: (message: string) => void;
};
async function normalizeImageGenerationReferenceImages<
async function normalizeGenerationReferenceImages<
T extends { referenceImageSrcs?: string[] },
>(
input: T,
@@ -743,10 +743,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({
);
const isBackgroundMusicSubmissionProjectCurrent = useCallback(
(scope: {
currentUserId: string | null;
projectId: string | null;
}) => {
(scope: { currentUserId: string | null; projectId: string | null }) => {
const currentScope = currentBackgroundMusicSubmissionScopeRef.current;
return (
currentScope.mounted &&
@@ -1365,11 +1362,6 @@ export function useImageCanvasGenerationSubmissionWorkflow({
if (!canvasDialog) {
return;
}
const iconSpecReference = dialog.iconSpecReference;
if (!iconSpecReference) {
return;
}
setSubmittingIconDialog({
...canvasDialog,
iconDescriptions: submissionPlan.iconDescriptions,
@@ -1381,11 +1373,6 @@ export function useImageCanvasGenerationSubmissionWorkflow({
try {
const canvasCompletionPlaceholder =
getGeneratingDialogPlaceholder(dialog);
const referenceImageSrc = await resolveEditorGenerationMediaReference(
iconSpecReference,
'image',
projectId,
);
const referenceImageSrcs = await Promise.all(
(dialog.generationReferences ?? []).map((reference) =>
resolveEditorGenerationMediaReference(
@@ -1398,7 +1385,6 @@ export function useImageCanvasGenerationSubmissionWorkflow({
const generated = await runEditorGenerationWithWalletRefresh(
generateEditorIconSpritesheet({
...submissionPlan.input,
referenceImageSrc,
...(referenceImageSrcs.length ? { referenceImageSrcs } : {}),
projectId,
generationInputs: submissionPlan.generationInputs,
@@ -1884,6 +1870,26 @@ export function useImageCanvasGenerationSubmissionWorkflow({
onWalletBalanceMayHaveChanged,
]);
const resetProjectBackedGenerationDialog = useCallback(
(canvasDialog: CanvasGenerationDialogState | null) => {
if (!canvasDialog || !projectId) {
return false;
}
updateCanvasGenerationDialogById(canvasDialog.id, (currentDialog) =>
currentDialog.generatedLayerId
? currentDialog
: {
...currentDialog,
status: 'idle',
composerOpen: true,
errorMessage: undefined,
},
);
return true;
},
[projectId, updateCanvasGenerationDialogById],
);
const submitImageGeneration = useCallback(
async (dialog: GenerateDialogState) => {
const canvasDialog = isCanvasGenerationDialog(dialog) ? dialog : null;
@@ -2128,6 +2134,64 @@ export function useImageCanvasGenerationSubmissionWorkflow({
canvasDialog?.id,
submissionPlan.result.title,
);
} else if (submissionPlan.kind === 'icon-spec') {
const iconSpecInput = submissionPlan.input;
const canvasCompletionPlaceholder =
getGeneratingDialogPlaceholder(dialog);
const generated = await runEditorGenerationWithWalletRefresh(
generateEditorIconSpec({
...iconSpecInput,
projectId,
assetFolderId,
assetLabel: submissionPlan.result.title,
...(projectId && canvasCompletionPlaceholder
? {
canvasCompletion: {
dialogId: canvasDialog?.id,
title: submissionPlan.result.title,
placeholder: canvasCompletionPlaceholder,
},
}
: {}),
}),
onWalletBalanceMayHaveChanged,
);
notifyEditorGenerationWarning(
generated.warning?.reason,
onGenerationWarning,
);
if (
await applyQueuedEditorGenerationProject(
generated,
projectId,
applyProjectSnapshot,
onQueuedGenerationTask,
onWalletBalanceMayHaveChanged,
onGenerationWarning,
canvasDialog?.id,
)
) {
return;
}
if (generated.project && applyProjectSnapshot) {
applyProjectSnapshot(generated.project);
if (generated.asset) {
upsertGeneratedAsset?.(generated.asset);
}
return;
}
if (resetProjectBackedGenerationDialog(canvasDialog)) {
return;
}
addGeneratedResultLayer(generated, {
frame: canvasCompletionPlaceholder,
assetKind: submissionPlan.result.assetKind,
title: submissionPlan.result.title,
dialogId: canvasDialog?.id,
generationInputs:
generated.asset?.generationInputs ??
submissionPlan.result.generationInputs,
});
} else if (submissionPlan.kind === 'video') {
const canvasCompletionPlaceholder =
getGeneratingDialogPlaceholder(dialog);
@@ -2174,6 +2238,9 @@ export function useImageCanvasGenerationSubmissionWorkflow({
}
return;
}
if (resetProjectBackedGenerationDialog(canvasDialog)) {
return;
}
addVideoResultLayer(
generated,
submissionPlan.result.title,
@@ -2430,6 +2497,9 @@ export function useImageCanvasGenerationSubmissionWorkflow({
}
return;
}
if (resetProjectBackedGenerationDialog(canvasDialog)) {
return;
}
addAudioResultLayer(
generated,
submissionPlan.result.title,
@@ -2438,12 +2508,11 @@ export function useImageCanvasGenerationSubmissionWorkflow({
canvasDialog?.id,
);
} else if (submissionPlan.kind === 'scene') {
const sceneGenerationInput =
await normalizeImageGenerationReferenceImages(
submissionPlan.input,
resolveImageGenerationDialogReferences(dialog),
projectId,
);
const sceneGenerationInput = await normalizeGenerationReferenceImages(
submissionPlan.input,
resolveImageGenerationDialogReferences(dialog),
projectId,
);
const canvasCompletionPlaceholder =
getGeneratingDialogPlaceholder(dialog);
const generated = await runEditorGenerationWithWalletRefresh(
@@ -2487,26 +2556,10 @@ export function useImageCanvasGenerationSubmissionWorkflow({
}
if (generated.project && applyProjectSnapshot) {
applyProjectSnapshot(generated.project);
if (generated.asset) {
upsertGeneratedAsset?.(generated.asset);
}
return;
}
if (canvasDialog && projectId) {
updateCanvasGenerationDialogById(
canvasDialog.id,
(currentDialog) =>
currentDialog.generatedLayerId
? currentDialog
: {
...currentDialog,
status: 'idle',
composerOpen: true,
errorMessage: undefined,
},
);
if (generated.asset) upsertGeneratedAsset?.(generated.asset);
return;
}
if (resetProjectBackedGenerationDialog(canvasDialog)) return;
addGeneratedResultLayer(generated, {
frame: canvasCompletionPlaceholder,
assetKind: submissionPlan.result.assetKind,
@@ -2515,12 +2568,11 @@ export function useImageCanvasGenerationSubmissionWorkflow({
generationInputs: submissionPlan.result.generationInputs,
});
} else {
const imageGenerationInput =
await normalizeImageGenerationReferenceImages(
submissionPlan.input,
resolveImageGenerationDialogReferences(dialog),
projectId,
);
const imageGenerationInput = await normalizeGenerationReferenceImages(
submissionPlan.input,
resolveImageGenerationDialogReferences(dialog),
projectId,
);
const resultTitle =
submissionPlan.result.title ??
`生成图片 ${layerCounterRef.current + 1}`;
@@ -2572,19 +2624,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({
}
return;
}
if (canvasDialog && projectId) {
updateCanvasGenerationDialogById(
canvasDialog.id,
(currentDialog) =>
currentDialog.generatedLayerId
? currentDialog
: {
...currentDialog,
status: 'idle',
composerOpen: true,
errorMessage: undefined,
},
);
if (resetProjectBackedGenerationDialog(canvasDialog)) {
return;
}
addGeneratedResultLayer(generated, {
@@ -2721,6 +2761,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({
layers,
quickEditSelectionState,
rememberImageModel,
resetProjectBackedGenerationDialog,
projectId,
assetFolderId,
applyProjectSnapshot,