From 4abf00d00721c0537d0bf88f600fd85d659a9738 Mon Sep 17 00:00:00 2001 From: kdletters Date: Wed, 17 Jun 2026 17:57:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=86=E5=88=86=E7=BC=96=E8=BE=91=E5=99=A8?= =?UTF-8?q?=E7=94=9F=E6=88=90=E6=8F=90=E4=BA=A4=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 抽出图片生成请求与结果快照构建逻辑 补充生成提交模型单测 更新 TRACKING.md 记录第三十五阶段验证 --- TRACKING.md | 1 + ...ageCanvasGenerationSubmissionModel.test.ts | 212 ++++++++++++++++++ .../ImageCanvasGenerationSubmissionModel.ts | 143 ++++++++++++ .../useImageCanvasGenerationWorkflow.ts | 99 ++------ 4 files changed, 374 insertions(+), 81 deletions(-) create mode 100644 src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts create mode 100644 src/components/image-editor/ImageCanvasGenerationSubmissionModel.ts diff --git a/TRACKING.md b/TRACKING.md index aaab65fb..c1846f84 100644 --- a/TRACKING.md +++ b/TRACKING.md @@ -151,3 +151,4 @@ - 2026-06-17 前端拆分第三十二阶段:继续收口 `ImageCanvasGenerationComposerView`,新增 `ImageCanvasBasicGenerationComposerView`、`ImageCanvasCharacterGenerationComposerView` 和 `ImageCanvasEditGenerationModalView`,把普通生图跟随框、角色形象生成面板和修改图片弹窗从 Composer 内联 JSX 中抽出;Composer 降至 312 行,只保留生成模式分流、portal 菜单和各面板装配。新增三组子视图单测覆盖普通生图 prompt / 参考图 / 提交 / 关闭、角色参考图菜单 / 状态恢复 / 提交、修改图片弹窗提示词 / 失败 / 关闭。统一验证命令:`npm run test -- src/components/image-editor/ImageCanvasSidebarView.test.tsx src/components/image-editor/ImageCanvasBasicGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasEditGenerationModalView.test.tsx src/components/image-editor/ImageCanvasEditorShellView.test.tsx src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx src/components/image-editor/ImageCanvasPanelDockView.test.tsx src/components/image-editor/ImageCanvasContextMenusView.test.tsx src/components/image-editor/ImageCanvasSelectedLayerToolbarView.test.tsx src/components/image-editor/ImageCanvasIconSpritesheetComposerView.test.tsx src/components/image-editor/ImageCanvasSpecGenerationPanelView.test.tsx src/components/image-editor/ImageCanvasGenerationImageOptionsView.test.tsx src/components/image-editor/ImageCanvasQuickEditPanelView.test.tsx src/components/image-editor/ImageCanvasCharacterAnimationPanelView.test.tsx src/components/image-editor/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`;浏览器回归:`http://127.0.0.1:10007/editor/canvas` 临时 `XDG_CONFIG_HOME` 下 Chrome headless 打开成功,未登录显示 `账号入口` 且控制台仅有预期 `/api/auth/refresh` 401;关闭登录后 `画布背景设置` 可打开,点击 `暖灰` 后 viewport 背景为 `rgb(243, 240, 234)` 且 `background-image: none`;`打开图层` 切换后侧栏显示 `图层`,`AI画布工具栏` 仍可见;点击 `生成工具` 后 `Image Generator`、`生成图片` 对话框和底部工具栏均可见。 - 2026-06-17 前端拆分第三十三阶段:继续收口 `ImageCanvasAssetLibraryPanelView`,新增 `ImageCanvasAssetFolderSectionView` 和 `ImageCanvasAssetRowView`,把素材库文件夹头 / 文件夹 drop 区域、素材卡片 / 上传进度 / 重命名 / 选择模式从素材库父面板中拆成两个完整 surface;素材库父面板降至 279 行,只保留素材列表容器、新建文件夹表单、批量操作栏和框选遮罩。新增素材行单测覆盖普通点击加入画布、选择模式改为选中、重命名 Enter 提交和上传中禁用 / 进度显示。统一验证命令:`npm run test -- src/components/image-editor/ImageCanvasAssetRowView.test.tsx src/components/image-editor/ImageCanvasSidebarView.test.tsx src/components/image-editor/ImageCanvasBasicGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasEditGenerationModalView.test.tsx src/components/image-editor/ImageCanvasEditorShellView.test.tsx src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx src/components/image-editor/ImageCanvasPanelDockView.test.tsx src/components/image-editor/ImageCanvasContextMenusView.test.tsx src/components/image-editor/ImageCanvasSelectedLayerToolbarView.test.tsx src/components/image-editor/ImageCanvasIconSpritesheetComposerView.test.tsx src/components/image-editor/ImageCanvasSpecGenerationPanelView.test.tsx src/components/image-editor/ImageCanvasGenerationImageOptionsView.test.tsx src/components/image-editor/ImageCanvasQuickEditPanelView.test.tsx src/components/image-editor/ImageCanvasCharacterAnimationPanelView.test.tsx src/components/image-editor/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`;浏览器回归:`http://127.0.0.1:10007/editor/canvas` 临时 `XDG_CONFIG_HOME` 下 Chrome headless 打开成功,未登录显示 `账号入口` 且控制台仅有预期 `/api/auth/refresh` 401;默认素材栏显示 `项目素材` 文件夹、上传入口和底部 `AI画布工具栏`;关闭登录后 `画布背景设置` 可打开,点击 `暖灰` 后 viewport 背景为 `rgb(243, 240, 234)` 且 `background-image: none`;`打开图层` 切换后侧栏显示 `图层`;点击 `生成工具` 后 `Image Generator`、`生成图片` 对话框和底部工具栏均可见。 - 2026-06-17 前端拆分第三十四阶段:继续收口 `ImageCanvasStageView`,新增 `ImageCanvasWorldView`,把画布世界表面、吸附参考线、可见图层排序 / 悬浮 / 选中 / 锁定 / 生成态、元数据角标、框选矩形、生成占位框和浮动生成状态从 StageView 内联 JSX 中抽出;StageView 降至 324 行,继续保留 viewport 宿主、drop overlay、左下 dock、底部工具栏、右键菜单和选中图片工具栏装配。新增 world view 单测覆盖隐藏图层过滤、悬浮尺寸、生成态、元数据按钮、吸附线、框选矩形和生成占位框事件。统一验证命令:`npm run test -- src/components/image-editor/ImageCanvasWorldView.test.tsx`、`npm run test -- src/components/image-editor/ImageCanvasAssetRowView.test.tsx src/components/image-editor/ImageCanvasSidebarView.test.tsx src/components/image-editor/ImageCanvasBasicGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasEditGenerationModalView.test.tsx src/components/image-editor/ImageCanvasEditorShellView.test.tsx src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx src/components/image-editor/ImageCanvasPanelDockView.test.tsx src/components/image-editor/ImageCanvasContextMenusView.test.tsx src/components/image-editor/ImageCanvasSelectedLayerToolbarView.test.tsx src/components/image-editor/ImageCanvasIconSpritesheetComposerView.test.tsx src/components/image-editor/ImageCanvasSpecGenerationPanelView.test.tsx src/components/image-editor/ImageCanvasGenerationImageOptionsView.test.tsx src/components/image-editor/ImageCanvasQuickEditPanelView.test.tsx src/components/image-editor/ImageCanvasCharacterAnimationPanelView.test.tsx src/components/image-editor/ImageCanvasWorldView.test.tsx src/components/image-editor/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`;浏览器回归:`http://127.0.0.1:10007/editor/canvas` 临时 `XDG_CONFIG_HOME` 下 Chrome headless 打开成功,未登录显示 `账号入口`;关闭后 `画布背景设置` 可打开,点击 `暖灰` 后 viewport 背景为 `rgb(243, 240, 234)` 且 `background-image: none`,`AI画布工具栏` 保持可见;`打开图层` 切换后侧栏显示 `图层`;点击 `生成工具` 后 `Image Generator`、`生成图片` 对话框和底部工具栏均可见,控制台仅有预期的未登录 `/api/auth/refresh` 401。 +- 2026-06-17 前端拆分第三十五阶段:继续收口 `useImageCanvasGenerationWorkflow`,新增 `ImageCanvasGenerationSubmissionModel`,把普通生图、修改图片、规范生成和角色形象生成的请求 payload、标准化 prompt、结果图层标题 / assetKind 和 generationInputs 快照构建从 workflow hook 中抽成纯模型;workflow hook 保留对话状态、真实 API 调用、图片引用解析、结果落图、选中和 fit 副作用,避免拆散生成生命周期。新增模型单测覆盖普通生图、修改图、带参考图的规范生成、带规范 / 常规参考图的角色生成和缺失源图异常;`useImageCanvasGenerationWorkflow` 从 1167 行降至 1104 行。统一验证命令:`npm run test -- src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx`、`npm run test -- src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/components/image-editor/ImageCanvasAssetRowView.test.tsx src/components/image-editor/ImageCanvasSidebarView.test.tsx src/components/image-editor/ImageCanvasBasicGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasEditGenerationModalView.test.tsx src/components/image-editor/ImageCanvasEditorShellView.test.tsx src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx src/components/image-editor/ImageCanvasPanelDockView.test.tsx src/components/image-editor/ImageCanvasContextMenusView.test.tsx src/components/image-editor/ImageCanvasSelectedLayerToolbarView.test.tsx src/components/image-editor/ImageCanvasIconSpritesheetComposerView.test.tsx src/components/image-editor/ImageCanvasSpecGenerationPanelView.test.tsx src/components/image-editor/ImageCanvasGenerationImageOptionsView.test.tsx src/components/image-editor/ImageCanvasQuickEditPanelView.test.tsx src/components/image-editor/ImageCanvasCharacterAnimationPanelView.test.tsx src/components/image-editor/ImageCanvasWorldView.test.tsx src/components/image-editor/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`;浏览器回归:`http://127.0.0.1:10007/editor/canvas` 临时 `XDG_CONFIG_HOME` 下 Chrome headless 打开成功,未登录显示 `账号入口`;关闭后 `画布背景设置` 可打开,点击 `暖灰` 后 viewport 背景为 `rgb(243, 240, 234)` 且 `background-image: none`;点击 `生成工具` 后 `Image Generator`、`生成图片` 对话框和 `AI画布工具栏` 均可见,控制台仅有预期的未登录 `/api/auth/refresh` 401。 diff --git a/src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts b/src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts new file mode 100644 index 00000000..01f56579 --- /dev/null +++ b/src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts @@ -0,0 +1,212 @@ +import { describe, expect, it } from 'vitest'; + +import type { CanvasLayer } from './ImageCanvasEditorTypes'; +import { buildImageGenerationSubmissionPlan } from './ImageCanvasGenerationSubmissionModel'; + +function createLayer(overrides: Partial = {}): CanvasLayer { + return { + id: 'layer-source', + resourceId: 'resource-source', + title: '源图', + src: 'data:image/png;base64,source', + x: 120, + y: 140, + width: 320, + height: 240, + originalWidth: 1024, + originalHeight: 768, + zIndex: 2, + sourceType: 'uploaded', + ...overrides, + }; +} + +describe('ImageCanvasGenerationSubmissionModel', () => { + it('builds normal image generation submission plans', () => { + const plan = buildImageGenerationSubmissionPlan({ + dialog: { + mode: 'generate', + prompt: ' 一张发光主视觉 ', + status: 'idle', + }, + layers: [], + nextGeneratedIndex: 3, + }); + + expect(plan).toEqual({ + kind: 'image', + normalizedPrompt: '一张发光主视觉', + input: { + prompt: '一张发光主视觉', + }, + result: { + generationInputs: { + fields: [{ title: '生成提示词', value: '一张发光主视觉' }], + references: [], + }, + }, + }); + }); + + it('builds edit submission plans with the source layer snapshot', () => { + const sourceLayer = createLayer(); + + const plan = buildImageGenerationSubmissionPlan({ + dialog: { + mode: 'edit', + prompt: '', + status: 'idle', + sourceLayerId: sourceLayer.id, + }, + layers: [sourceLayer], + nextGeneratedIndex: 4, + }); + + expect(plan).toMatchObject({ + kind: 'edit', + normalizedPrompt: '修改当前图片', + sourceLayer, + generationInputs: { + fields: [{ title: '修改要求', value: '修改当前图片' }], + references: [ + { + title: '参考图', + label: '源图', + src: 'data:image/png;base64,source', + }, + ], + }, + }); + }); + + it('builds spec generation plans with reference prompt semantics', () => { + const plan = buildImageGenerationSubmissionPlan({ + dialog: { + mode: 'spec', + prompt: '', + status: 'idle', + specType: 'icon', + specValues: { + playSetting: '休闲消除', + artStyle: '清爽卡通', + bodyRatio: '3', + characterView: '', + customPrompt: '', + }, + specReference: { + id: 'spec-ref', + label: '参考.png', + src: 'data:image/png;base64,ref', + }, + }, + layers: [], + nextGeneratedIndex: 8, + }); + + expect(plan).toMatchObject({ + kind: 'image', + normalizedPrompt: 'AI 生成图片', + input: { + size: '2048x1152', + kind: 'spec', + referenceImageSrcs: ['data:image/png;base64,ref'], + prompt: expect.stringContaining('参考图生成规范'), + }, + result: { + assetKind: 'icon-spec', + title: '图标素材规范 8', + }, + }); + expect(plan.kind === 'image' ? plan.result.generationInputs : null).toEqual({ + fields: [ + { title: '玩法设定', value: '休闲消除' }, + { title: '美术风格', value: '清爽卡通' }, + ], + references: [ + { + title: '参考图', + label: '参考.png', + src: 'data:image/png;base64,ref', + }, + ], + }); + }); + + it('builds character plans with references and remembered model', () => { + const plan = buildImageGenerationSubmissionPlan({ + dialog: { + mode: 'character', + prompt: ' 白发骑士 ', + status: 'idle', + imageModel: 'gpt-image-2', + aspectRatio: '2:3', + imageSize: '2K', + characterSpecReference: { + id: 'spec', + label: '角色规范', + src: 'data:image/png;base64,spec', + }, + characterReferences: [ + { + id: 'ref-1', + label: '盔甲参考', + src: 'data:image/png;base64,armor', + }, + ], + }, + layers: [], + nextGeneratedIndex: 9, + }); + + expect(plan).toMatchObject({ + kind: 'image', + normalizedPrompt: '白发骑士', + input: { + prompt: '白发骑士', + kind: 'character', + model: 'gpt-image-2', + aspectRatio: '2:3', + imageSize: '2K', + referenceImageSrcs: [ + 'data:image/png;base64,spec', + 'data:image/png;base64,armor', + ], + }, + result: { + assetKind: 'character', + title: '角色形象 9', + }, + rememberImageModel: 'gpt-image-2', + }); + expect(plan.kind === 'image' ? plan.result.generationInputs : null).toEqual({ + fields: [{ title: '角色设定', value: '白发骑士' }], + references: [ + { + title: '角色形象规范', + label: '角色规范', + src: 'data:image/png;base64,spec', + }, + { + title: '常规参考图 1', + label: '盔甲参考', + src: 'data:image/png;base64,armor', + }, + ], + }); + }); + + it('throws when edit source layer is missing', () => { + expect(() => + buildImageGenerationSubmissionPlan({ + dialog: { + mode: 'edit', + prompt: '修图', + status: 'idle', + sourceLayerId: 'missing-layer', + }, + layers: [], + nextGeneratedIndex: 1, + }), + ).toThrow('未找到要修改的图片'); + }); +}); diff --git a/src/components/image-editor/ImageCanvasGenerationSubmissionModel.ts b/src/components/image-editor/ImageCanvasGenerationSubmissionModel.ts new file mode 100644 index 00000000..e065c977 --- /dev/null +++ b/src/components/image-editor/ImageCanvasGenerationSubmissionModel.ts @@ -0,0 +1,143 @@ +import type { + EditorImageGenerationInput, +} from '../../services/image-editor/editorProjectClient'; +import type { + CanvasGenerationInputs, + CanvasLayer, + GenerateDialogState, +} from './ImageCanvasEditorTypes'; +import { + DEFAULT_IMAGE_MODEL, + DEFAULT_SPEC_FORM_VALUES, + SPEC_GENERATION_SIZE, + SPEC_TYPE_LABEL, + buildCharacterGenerationInputs, + buildEditGenerationInputs, + buildImageGenerationInputs, + buildSpecGenerationInputs, + buildSpecPrompt, +} 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; + }; + +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, + }; + } + + return { + kind: 'image', + normalizedPrompt, + input: { + prompt: normalizedPrompt, + }, + result: { + generationInputs: buildImageGenerationInputs(normalizedPrompt), + }, + }; +} diff --git a/src/components/image-editor/useImageCanvasGenerationWorkflow.ts b/src/components/image-editor/useImageCanvasGenerationWorkflow.ts index f0d78dae..42f35a98 100644 --- a/src/components/image-editor/useImageCanvasGenerationWorkflow.ts +++ b/src/components/image-editor/useImageCanvasGenerationWorkflow.ts @@ -36,22 +36,17 @@ import { ICON_FRAME_ORIGINAL_SIZE, SPEC_FRAME_DISPLAY_SIZE, SPEC_FRAME_ORIGINAL_SIZE, - SPEC_GENERATION_SIZE, - SPEC_TYPE_LABEL, - buildCharacterGenerationInputs, buildEditGenerationInputs, buildIconGenerationInputs, - buildImageGenerationInputs, buildQuickEditModelOptions, buildQuickEditSizeOptions, - buildSpecGenerationInputs, - buildSpecPrompt, calculateCharacterAnimationPrice, createCanvasLayerReference, isCanvasGenerationDialog, resolveCharacterAnimationSourceImageSrc, resolveImageGenerationErrorMessage, } from './ImageCanvasGenerationModel'; +import { buildImageGenerationSubmissionPlan } from './ImageCanvasGenerationSubmissionModel'; import { formatImageSizeValue } from './ImageCanvasEditorModel'; import type { CanvasGenerationDialogState, @@ -801,92 +796,34 @@ export function useImageCanvasGenerationWorkflow({ } try { - if (dialog.mode === 'edit') { - const sourceLayer = layers.find( - (layer) => layer.id === dialog.sourceLayerId, - ); - if (!sourceLayer) { - throw new Error('未找到要修改的图片'); - } + const submissionPlan = buildImageGenerationSubmissionPlan({ + dialog, + layers, + nextGeneratedIndex: layerCounterRef.current + 1, + }); + if (submissionPlan.kind === 'edit') { const referenceImageSrc = await resolveEditorImageReferenceDataUrl( - sourceLayer.src, + submissionPlan.sourceLayer.src, ); const generated = await editEditorImage({ - prompt: normalizedPrompt, + prompt: submissionPlan.normalizedPrompt, sourceImageSrc: referenceImageSrc, }); addGeneratedResultLayer(generated, { - sourceLayer, - generationInputs: buildEditGenerationInputs( - '修改要求', - normalizedPrompt, - sourceLayer, - ), - }); - } else if (dialog.mode === 'spec') { - const specType = dialog.specType ?? 'custom'; - const specValues = - dialog.specValues ?? DEFAULT_SPEC_FORM_VALUES[specType]; - const specPrompt = buildSpecPrompt( - specType, - specValues, - Boolean(dialog.specReference?.src), - ); - const generated = await generateEditorImage({ - prompt: specPrompt, - size: SPEC_GENERATION_SIZE, - model: DEFAULT_IMAGE_MODEL, - kind: 'spec', - ...(dialog.specReference?.src - ? { referenceImageSrcs: [dialog.specReference.src] } - : {}), - }); - addGeneratedResultLayer(generated, { - frame: getGeneratingDialogPlaceholder(dialog), - assetKind: specType === 'icon' ? 'icon-spec' : 'spec', - title: `${SPEC_TYPE_LABEL[specType]} ${layerCounterRef.current + 1}`, - dialogId: canvasDialog?.id, - generationInputs: buildSpecGenerationInputs( - specType, - specValues, - dialog.specReference, - ), - }); - } else if (dialog.mode === 'character') { - const referenceImageSrcs = [ - dialog.characterSpecReference?.src, - ...(dialog.characterReferences ?? []).map( - (reference) => reference.src, - ), - ].filter((src): src is string => Boolean(src)); - const generated = await generateEditorImage({ - prompt: normalizedPrompt, - kind: 'character', - model: dialog.imageModel ?? DEFAULT_IMAGE_MODEL, - aspectRatio: dialog.aspectRatio ?? '1:1', - imageSize: dialog.imageSize ?? '1K', - ...(referenceImageSrcs.length ? { referenceImageSrcs } : {}), - }); - setLastImageModel(dialog.imageModel ?? DEFAULT_IMAGE_MODEL); - addGeneratedResultLayer(generated, { - frame: getGeneratingDialogPlaceholder(dialog), - assetKind: 'character', - title: `角色形象 ${layerCounterRef.current + 1}`, - dialogId: canvasDialog?.id, - generationInputs: buildCharacterGenerationInputs( - normalizedPrompt, - dialog.characterSpecReference, - dialog.characterReferences, - ), + sourceLayer: submissionPlan.sourceLayer, + generationInputs: submissionPlan.generationInputs, }); } else { - const generated = await generateEditorImage({ - prompt: normalizedPrompt, - }); + 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: buildImageGenerationInputs(normalizedPrompt), + generationInputs: submissionPlan.result.generationInputs, }); } } catch (error) {