6d964937db
补齐普通生图参考图来源菜单和画布选择流程 接入UI设计图与视频生成面板的提交链路 让生成引用上传目标支持多种生成面板 统一图片信息弹窗断言并补充相关测试 修复图标按钮浮层锚点ref与视频生成类型契约
320 lines
8.5 KiB
TypeScript
320 lines
8.5 KiB
TypeScript
import type {
|
|
EditorCharacterAnimationGenerationInput,
|
|
EditorIconSpritesheetGenerationInput,
|
|
EditorImageGenerationInput,
|
|
EditorVideoGenerationInput,
|
|
} from '../../services/image-editor/editorProjectClient';
|
|
import type {
|
|
CanvasGenerationInputs,
|
|
CanvasLayer,
|
|
CharacterAnimationPanelState,
|
|
GenerateDialogState,
|
|
} from './ImageCanvasEditorTypes';
|
|
import {
|
|
CHARACTER_ANIMATION_MODEL,
|
|
DEFAULT_IMAGE_MODEL,
|
|
DEFAULT_ICON_DESCRIPTIONS,
|
|
DEFAULT_SPEC_FORM_VALUES,
|
|
SPEC_GENERATION_SIZE,
|
|
SPEC_TYPE_LABEL,
|
|
buildIconGenerationInputs,
|
|
buildCharacterGenerationInputs,
|
|
buildEditGenerationInputs,
|
|
buildImageGenerationInputs,
|
|
buildSpecGenerationInputs,
|
|
buildSpecPrompt,
|
|
buildUiDesignGenerationInputs,
|
|
buildVideoGenerationInputs,
|
|
calculateCharacterAnimationPrice,
|
|
calculateEditorVideoPrice,
|
|
resolveCharacterAnimationSourceImageSrc,
|
|
} from './ImageCanvasGenerationModel';
|
|
|
|
type ImageGenerationSubmissionOptions = {
|
|
dialog: GenerateDialogState;
|
|
layers: CanvasLayer[];
|
|
nextGeneratedIndex: number;
|
|
};
|
|
|
|
export type ImageGenerationSubmissionPlan =
|
|
| {
|
|
kind: 'edit';
|
|
normalizedPrompt: string;
|
|
sourceLayer: CanvasLayer;
|
|
generationInputs: CanvasGenerationInputs;
|
|
}
|
|
| {
|
|
kind: 'image';
|
|
normalizedPrompt: string;
|
|
input: EditorImageGenerationInput;
|
|
result: {
|
|
assetKind?: CanvasLayer['assetKind'];
|
|
title?: string;
|
|
generationInputs: CanvasGenerationInputs;
|
|
};
|
|
rememberImageModel?: string;
|
|
}
|
|
| {
|
|
kind: 'video';
|
|
normalizedPrompt: string;
|
|
input: EditorVideoGenerationInput;
|
|
result: {
|
|
title: string;
|
|
generationInputs: CanvasGenerationInputs;
|
|
};
|
|
};
|
|
|
|
export type IconSpritesheetGenerationSubmissionPlan =
|
|
| {
|
|
ok: false;
|
|
errorMessage: string;
|
|
}
|
|
| {
|
|
ok: true;
|
|
iconDescriptions: string[];
|
|
input: EditorIconSpritesheetGenerationInput;
|
|
generationInputs: CanvasGenerationInputs;
|
|
rememberImageModel: string;
|
|
};
|
|
|
|
export type CharacterAnimationSubmissionPlan = {
|
|
promptText: string;
|
|
input: EditorCharacterAnimationGenerationInput;
|
|
};
|
|
|
|
export function buildImageGenerationSubmissionPlan({
|
|
dialog,
|
|
layers,
|
|
nextGeneratedIndex,
|
|
}: ImageGenerationSubmissionOptions): ImageGenerationSubmissionPlan {
|
|
const normalizedPrompt =
|
|
dialog.prompt.trim() ||
|
|
(dialog.mode === 'edit' ? '修改当前图片' : 'AI 生成图片');
|
|
|
|
if (dialog.mode === 'edit') {
|
|
const sourceLayer = layers.find((layer) => layer.id === dialog.sourceLayerId);
|
|
if (!sourceLayer) {
|
|
throw new Error('未找到要修改的图片');
|
|
}
|
|
return {
|
|
kind: 'edit',
|
|
normalizedPrompt,
|
|
sourceLayer,
|
|
generationInputs: buildEditGenerationInputs(
|
|
'修改要求',
|
|
normalizedPrompt,
|
|
sourceLayer,
|
|
),
|
|
};
|
|
}
|
|
|
|
if (dialog.mode === 'spec') {
|
|
const specType = dialog.specType ?? 'custom';
|
|
const specValues = dialog.specValues ?? DEFAULT_SPEC_FORM_VALUES[specType];
|
|
return {
|
|
kind: 'image',
|
|
normalizedPrompt,
|
|
input: {
|
|
prompt: buildSpecPrompt(
|
|
specType,
|
|
specValues,
|
|
Boolean(dialog.specReference?.src),
|
|
),
|
|
size: SPEC_GENERATION_SIZE,
|
|
model: DEFAULT_IMAGE_MODEL,
|
|
kind: 'spec',
|
|
...(dialog.specReference?.src
|
|
? { referenceImageSrcs: [dialog.specReference.src] }
|
|
: {}),
|
|
},
|
|
result: {
|
|
assetKind: specType === 'icon' ? 'icon-spec' : 'spec',
|
|
title: `${SPEC_TYPE_LABEL[specType]} ${nextGeneratedIndex}`,
|
|
generationInputs: buildSpecGenerationInputs(
|
|
specType,
|
|
specValues,
|
|
dialog.specReference,
|
|
),
|
|
},
|
|
};
|
|
}
|
|
|
|
if (dialog.mode === 'character') {
|
|
const referenceImageSrcs = [
|
|
dialog.characterSpecReference?.src,
|
|
...(dialog.characterReferences ?? []).map((reference) => reference.src),
|
|
].filter((src): src is string => Boolean(src));
|
|
const imageModel = dialog.imageModel ?? DEFAULT_IMAGE_MODEL;
|
|
return {
|
|
kind: 'image',
|
|
normalizedPrompt,
|
|
input: {
|
|
prompt: normalizedPrompt,
|
|
kind: 'character',
|
|
model: imageModel,
|
|
aspectRatio: dialog.aspectRatio ?? '1:1',
|
|
imageSize: dialog.imageSize ?? '1K',
|
|
...(referenceImageSrcs.length ? { referenceImageSrcs } : {}),
|
|
},
|
|
result: {
|
|
assetKind: 'character',
|
|
title: `角色形象 ${nextGeneratedIndex}`,
|
|
generationInputs: buildCharacterGenerationInputs(
|
|
normalizedPrompt,
|
|
dialog.characterSpecReference,
|
|
dialog.characterReferences,
|
|
),
|
|
},
|
|
rememberImageModel: imageModel,
|
|
};
|
|
}
|
|
|
|
if (dialog.mode === 'ui-design') {
|
|
const imageModel = dialog.imageModel ?? DEFAULT_IMAGE_MODEL;
|
|
const referenceImageSrcs = [dialog.uiDesignSpecReference?.src].filter(
|
|
(src): src is string => Boolean(src),
|
|
);
|
|
return {
|
|
kind: 'image',
|
|
normalizedPrompt,
|
|
input: {
|
|
prompt: normalizedPrompt,
|
|
kind: 'ui-design',
|
|
model: imageModel,
|
|
aspectRatio: dialog.aspectRatio ?? '16:9',
|
|
imageSize: dialog.imageSize ?? '1K',
|
|
...(referenceImageSrcs.length ? { referenceImageSrcs } : {}),
|
|
},
|
|
result: {
|
|
assetKind: 'ui-design',
|
|
title: `UI设计图 ${nextGeneratedIndex}`,
|
|
generationInputs: buildUiDesignGenerationInputs(
|
|
normalizedPrompt,
|
|
dialog.uiDesignSpecReference,
|
|
),
|
|
},
|
|
rememberImageModel: imageModel,
|
|
};
|
|
}
|
|
|
|
if (dialog.mode === 'video') {
|
|
const resolution = dialog.videoResolution ?? '480p';
|
|
const durationSeconds = dialog.videoDurationSeconds ?? 4;
|
|
const model = dialog.videoModel ?? 'seedance2.0';
|
|
return {
|
|
kind: 'video',
|
|
normalizedPrompt,
|
|
input: {
|
|
prompt: normalizedPrompt,
|
|
model,
|
|
aspectRatio: '16:9',
|
|
durationSeconds,
|
|
resolution,
|
|
mode: 'std',
|
|
sound: 'off',
|
|
priceMudPoints: calculateEditorVideoPrice(
|
|
resolution,
|
|
durationSeconds,
|
|
),
|
|
},
|
|
result: {
|
|
title: `生成视频 ${nextGeneratedIndex}`,
|
|
generationInputs: buildVideoGenerationInputs(
|
|
normalizedPrompt,
|
|
dialog.generationReferences,
|
|
),
|
|
},
|
|
};
|
|
}
|
|
|
|
return {
|
|
kind: 'image',
|
|
normalizedPrompt,
|
|
input: {
|
|
prompt: normalizedPrompt,
|
|
...(dialog.generationReferences?.length
|
|
? {
|
|
referenceImageSrcs: dialog.generationReferences.map(
|
|
(reference) => reference.src,
|
|
),
|
|
}
|
|
: {}),
|
|
},
|
|
result: {
|
|
generationInputs: buildImageGenerationInputs(
|
|
normalizedPrompt,
|
|
dialog.generationReferences,
|
|
),
|
|
},
|
|
};
|
|
}
|
|
|
|
export function buildIconSpritesheetGenerationSubmissionPlan(
|
|
dialog: GenerateDialogState,
|
|
): IconSpritesheetGenerationSubmissionPlan {
|
|
const iconDescriptions = (dialog.iconDescriptions ?? DEFAULT_ICON_DESCRIPTIONS)
|
|
.map((description) => description.trim())
|
|
.filter(Boolean);
|
|
|
|
if (!dialog.iconSpecReference) {
|
|
return {
|
|
ok: false,
|
|
errorMessage: '请选择图标素材规范',
|
|
};
|
|
}
|
|
|
|
if (!iconDescriptions.length) {
|
|
return {
|
|
ok: false,
|
|
errorMessage: '请填写素材描述',
|
|
};
|
|
}
|
|
|
|
const rememberImageModel = dialog.imageModel ?? DEFAULT_IMAGE_MODEL;
|
|
return {
|
|
ok: true,
|
|
iconDescriptions,
|
|
input: {
|
|
referenceImageSrc: dialog.iconSpecReference.src,
|
|
iconDescriptions,
|
|
model: rememberImageModel,
|
|
aspectRatio: dialog.aspectRatio ?? '1:1',
|
|
imageSize: dialog.imageSize ?? '1K',
|
|
},
|
|
generationInputs: buildIconGenerationInputs(
|
|
iconDescriptions,
|
|
dialog.iconSpecReference,
|
|
),
|
|
rememberImageModel,
|
|
};
|
|
}
|
|
|
|
export function buildCharacterAnimationSubmissionPlan({
|
|
panel,
|
|
sourceLayer,
|
|
}: {
|
|
panel: CharacterAnimationPanelState;
|
|
sourceLayer: CanvasLayer;
|
|
}): CharacterAnimationSubmissionPlan {
|
|
const promptText = panel.promptText.trim();
|
|
return {
|
|
promptText,
|
|
input: {
|
|
sourceLayerId: sourceLayer.id,
|
|
sourceImageSrc: resolveCharacterAnimationSourceImageSrc(sourceLayer),
|
|
sourceWidth: sourceLayer.originalWidth,
|
|
sourceHeight: sourceLayer.originalHeight,
|
|
promptText,
|
|
resolution: panel.resolution,
|
|
ratio: panel.ratio,
|
|
frameCount: panel.frameCount,
|
|
durationSeconds: panel.durationSeconds,
|
|
priceMudPoints: calculateCharacterAnimationPrice(
|
|
panel.resolution,
|
|
panel.durationSeconds,
|
|
),
|
|
model: CHARACTER_ANIMATION_MODEL,
|
|
},
|
|
};
|
|
}
|