拆分编辑器高级生成提交模型
抽出图标素材生成校验和请求参数组装 抽出角色动画生成请求参数组装 补充高级生成提交模型单测 更新 TRACKING.md 记录第三十八阶段验证
This commit is contained in:
@@ -154,3 +154,4 @@
|
|||||||
- 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。
|
- 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。
|
||||||
- 2026-06-17 前端拆分第三十六阶段:继续收口 `useImageCanvasUploadWorkflow`,新增 `ImageCanvasUploadModel`,把上传目标文件夹解析、上传中素材占位卡、上传到画布的临时图层、无效 drop 坐标兜底和图片真实尺寸回填坐标计算从 hook 中抽成纯模型;upload workflow hook 保留登录恢复、文件读取、真实素材创建 API、上传进度状态和生成面板参考图写入副作用。新增模型单测覆盖文件夹兜底、占位素材、画布落点、非法坐标兜底和真实尺寸修正;`useImageCanvasUploadWorkflow` 从 546 行降至 510 行。统一验证命令:`npm run test -- src/components/image-editor/ImageCanvasUploadModel.test.ts src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx`、`npm run test -- src/components/image-editor/ImageCanvasUploadModel.test.ts 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/useImageCanvasUploadWorkflow.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。
|
- 2026-06-17 前端拆分第三十六阶段:继续收口 `useImageCanvasUploadWorkflow`,新增 `ImageCanvasUploadModel`,把上传目标文件夹解析、上传中素材占位卡、上传到画布的临时图层、无效 drop 坐标兜底和图片真实尺寸回填坐标计算从 hook 中抽成纯模型;upload workflow hook 保留登录恢复、文件读取、真实素材创建 API、上传进度状态和生成面板参考图写入副作用。新增模型单测覆盖文件夹兜底、占位素材、画布落点、非法坐标兜底和真实尺寸修正;`useImageCanvasUploadWorkflow` 从 546 行降至 510 行。统一验证命令:`npm run test -- src/components/image-editor/ImageCanvasUploadModel.test.ts src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx`、`npm run test -- src/components/image-editor/ImageCanvasUploadModel.test.ts 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/useImageCanvasUploadWorkflow.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。
|
||||||
- 2026-06-17 前端拆分第三十七阶段:继续收口 `useImageCanvasAssetLibrary`,新增 `ImageCanvasAssetLibraryModel`,把素材分组、可选择素材筛选、全选状态、素材 / 文件夹重命名、文件夹折叠、本地新建文件夹占位、持久化文件夹替换、本地删除素材、删除文件夹回默认文件夹、选择集合切换、批量删除和本地移动素材到文件夹从 hook 中抽成纯模型;asset library hook 继续保留加载账号素材库、后端 CRUD 调用、登录弹窗、DOM 框选和素材拖拽命中生命周期。新增模型单测覆盖分组 / 选择、重命名 / 折叠 / 本地文件夹、本地文件夹持久化替换、删除文件夹回默认文件夹、全选 / 批量删除和本地移动;`useImageCanvasAssetLibrary` 从 609 行降至 573 行。统一验证命令:`npm run test -- src/components/image-editor/ImageCanvasAssetLibraryModel.test.ts src/components/image-editor/useImageCanvasAssetLibrary.test.tsx`、`npm run test -- src/components/image-editor/ImageCanvasAssetLibraryModel.test.ts src/components/image-editor/ImageCanvasUploadModel.test.ts 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/useImageCanvasAssetLibrary.test.tsx src/components/image-editor/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/useImageCanvasUploadWorkflow.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。
|
- 2026-06-17 前端拆分第三十七阶段:继续收口 `useImageCanvasAssetLibrary`,新增 `ImageCanvasAssetLibraryModel`,把素材分组、可选择素材筛选、全选状态、素材 / 文件夹重命名、文件夹折叠、本地新建文件夹占位、持久化文件夹替换、本地删除素材、删除文件夹回默认文件夹、选择集合切换、批量删除和本地移动素材到文件夹从 hook 中抽成纯模型;asset library hook 继续保留加载账号素材库、后端 CRUD 调用、登录弹窗、DOM 框选和素材拖拽命中生命周期。新增模型单测覆盖分组 / 选择、重命名 / 折叠 / 本地文件夹、本地文件夹持久化替换、删除文件夹回默认文件夹、全选 / 批量删除和本地移动;`useImageCanvasAssetLibrary` 从 609 行降至 573 行。统一验证命令:`npm run test -- src/components/image-editor/ImageCanvasAssetLibraryModel.test.ts src/components/image-editor/useImageCanvasAssetLibrary.test.tsx`、`npm run test -- src/components/image-editor/ImageCanvasAssetLibraryModel.test.ts src/components/image-editor/ImageCanvasUploadModel.test.ts 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/useImageCanvasAssetLibrary.test.tsx src/components/image-editor/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/useImageCanvasUploadWorkflow.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。
|
||||||
|
- 2026-06-17 前端拆分第三十八阶段:继续收口 `useImageCanvasGenerationWorkflow`,扩展 `ImageCanvasGenerationSubmissionModel`,把图标素材批量生成的规范校验 / 描述清洗 / 请求 payload / generationInputs,以及角色动画生成的 prompt 清洗 / objectKey 优先源图 / 尺寸 / 价格 / 模型参数从 workflow hook 中抽成纯模型;workflow hook 继续保留对话状态、真实 API 调用、生成结果落图、失败恢复和角色动画面板生命周期。新增模型单测覆盖图标缺少规范、图标空描述、图标描述 trim / 参考快照,以及角色动画 trim、objectKey 源图和价格计算;`useImageCanvasGenerationWorkflow` 从 1104 行降至 1075 行。统一验证命令:`npm run test -- src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx`、`npm run test -- src/components/image-editor/ImageCanvasAssetLibraryModel.test.ts src/components/image-editor/ImageCanvasUploadModel.test.ts 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/useImageCanvasAssetLibrary.test.tsx src/components/image-editor/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/useImageCanvasUploadWorkflow.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画布工具栏` 均可见;点击 `生成图标素材` 后 `Icon Generator` 占位和 `生成图标素材` 面板可见,控制台仅有预期的未登录 `/api/auth/refresh` 401。
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import type { CanvasLayer } from './ImageCanvasEditorTypes';
|
import type { CanvasLayer } from './ImageCanvasEditorTypes';
|
||||||
import { buildImageGenerationSubmissionPlan } from './ImageCanvasGenerationSubmissionModel';
|
import {
|
||||||
|
buildCharacterAnimationSubmissionPlan,
|
||||||
|
buildIconSpritesheetGenerationSubmissionPlan,
|
||||||
|
buildImageGenerationSubmissionPlan,
|
||||||
|
} from './ImageCanvasGenerationSubmissionModel';
|
||||||
|
|
||||||
function createLayer(overrides: Partial<CanvasLayer> = {}): CanvasLayer {
|
function createLayer(overrides: Partial<CanvasLayer> = {}): CanvasLayer {
|
||||||
return {
|
return {
|
||||||
@@ -209,4 +213,121 @@ describe('ImageCanvasGenerationSubmissionModel', () => {
|
|||||||
}),
|
}),
|
||||||
).toThrow('未找到要修改的图片');
|
).toThrow('未找到要修改的图片');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns an icon spritesheet error when the spec reference is missing', () => {
|
||||||
|
const plan = buildIconSpritesheetGenerationSubmissionPlan({
|
||||||
|
mode: 'icon',
|
||||||
|
prompt: '',
|
||||||
|
status: 'idle',
|
||||||
|
iconDescriptions: ['返回按钮'],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(plan).toEqual({
|
||||||
|
ok: false,
|
||||||
|
errorMessage: '请选择图标素材规范',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an icon spritesheet error when descriptions are empty', () => {
|
||||||
|
const plan = buildIconSpritesheetGenerationSubmissionPlan({
|
||||||
|
mode: 'icon',
|
||||||
|
prompt: '',
|
||||||
|
status: 'idle',
|
||||||
|
iconSpecReference: {
|
||||||
|
id: 'icon-spec',
|
||||||
|
label: '图标规范',
|
||||||
|
src: 'data:image/png;base64,spec',
|
||||||
|
},
|
||||||
|
iconDescriptions: [' ', '\n'],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(plan).toEqual({
|
||||||
|
ok: false,
|
||||||
|
errorMessage: '请填写素材描述',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds icon spritesheet plans with trimmed descriptions and references', () => {
|
||||||
|
const plan = buildIconSpritesheetGenerationSubmissionPlan({
|
||||||
|
mode: 'icon',
|
||||||
|
prompt: '',
|
||||||
|
status: 'idle',
|
||||||
|
imageModel: 'gpt-image-2',
|
||||||
|
aspectRatio: '3:2',
|
||||||
|
imageSize: '2K',
|
||||||
|
iconSpecReference: {
|
||||||
|
id: 'icon-spec',
|
||||||
|
label: '图标规范',
|
||||||
|
src: 'data:image/png;base64,spec',
|
||||||
|
},
|
||||||
|
iconDescriptions: [' 返回按钮 ', '', '设置按钮'],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(plan).toEqual({
|
||||||
|
ok: true,
|
||||||
|
iconDescriptions: ['返回按钮', '设置按钮'],
|
||||||
|
input: {
|
||||||
|
referenceImageSrc: 'data:image/png;base64,spec',
|
||||||
|
iconDescriptions: ['返回按钮', '设置按钮'],
|
||||||
|
model: 'gpt-image-2',
|
||||||
|
aspectRatio: '3:2',
|
||||||
|
imageSize: '2K',
|
||||||
|
},
|
||||||
|
generationInputs: {
|
||||||
|
fields: [
|
||||||
|
{ title: '素材描述 1', value: '返回按钮' },
|
||||||
|
{ title: '素材描述 2', value: '设置按钮' },
|
||||||
|
],
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
title: '图标素材规范',
|
||||||
|
label: '图标规范',
|
||||||
|
src: 'data:image/png;base64,spec',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
rememberImageModel: 'gpt-image-2',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds character animation plans with trimmed prompt and object key source', () => {
|
||||||
|
const sourceLayer = createLayer({
|
||||||
|
id: 'character-layer',
|
||||||
|
title: '角色图',
|
||||||
|
objectKey: 'generated/character.png',
|
||||||
|
assetKind: 'character',
|
||||||
|
originalWidth: 960,
|
||||||
|
originalHeight: 1280,
|
||||||
|
});
|
||||||
|
|
||||||
|
const plan = buildCharacterAnimationSubmissionPlan({
|
||||||
|
panel: {
|
||||||
|
sourceLayerId: 'character-layer',
|
||||||
|
promptText: ' 循环奔跑动作 ',
|
||||||
|
resolution: '720p',
|
||||||
|
ratio: 'same',
|
||||||
|
frameCount: 48,
|
||||||
|
durationSeconds: 6,
|
||||||
|
status: 'idle',
|
||||||
|
},
|
||||||
|
sourceLayer,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(plan).toEqual({
|
||||||
|
promptText: '循环奔跑动作',
|
||||||
|
input: {
|
||||||
|
sourceLayerId: 'character-layer',
|
||||||
|
sourceImageSrc: 'generated/character.png',
|
||||||
|
sourceWidth: 960,
|
||||||
|
sourceHeight: 1280,
|
||||||
|
promptText: '循环奔跑动作',
|
||||||
|
resolution: '720p',
|
||||||
|
ratio: 'same',
|
||||||
|
frameCount: 48,
|
||||||
|
durationSeconds: 6,
|
||||||
|
priceMudPoints: 120,
|
||||||
|
model: 'seedance2.0',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +1,29 @@
|
|||||||
import type {
|
import type {
|
||||||
|
EditorCharacterAnimationGenerationInput,
|
||||||
|
EditorIconSpritesheetGenerationInput,
|
||||||
EditorImageGenerationInput,
|
EditorImageGenerationInput,
|
||||||
} from '../../services/image-editor/editorProjectClient';
|
} from '../../services/image-editor/editorProjectClient';
|
||||||
import type {
|
import type {
|
||||||
CanvasGenerationInputs,
|
CanvasGenerationInputs,
|
||||||
CanvasLayer,
|
CanvasLayer,
|
||||||
|
CharacterAnimationPanelState,
|
||||||
GenerateDialogState,
|
GenerateDialogState,
|
||||||
} from './ImageCanvasEditorTypes';
|
} from './ImageCanvasEditorTypes';
|
||||||
import {
|
import {
|
||||||
|
CHARACTER_ANIMATION_MODEL,
|
||||||
DEFAULT_IMAGE_MODEL,
|
DEFAULT_IMAGE_MODEL,
|
||||||
|
DEFAULT_ICON_DESCRIPTIONS,
|
||||||
DEFAULT_SPEC_FORM_VALUES,
|
DEFAULT_SPEC_FORM_VALUES,
|
||||||
SPEC_GENERATION_SIZE,
|
SPEC_GENERATION_SIZE,
|
||||||
SPEC_TYPE_LABEL,
|
SPEC_TYPE_LABEL,
|
||||||
|
buildIconGenerationInputs,
|
||||||
buildCharacterGenerationInputs,
|
buildCharacterGenerationInputs,
|
||||||
buildEditGenerationInputs,
|
buildEditGenerationInputs,
|
||||||
buildImageGenerationInputs,
|
buildImageGenerationInputs,
|
||||||
buildSpecGenerationInputs,
|
buildSpecGenerationInputs,
|
||||||
buildSpecPrompt,
|
buildSpecPrompt,
|
||||||
|
calculateCharacterAnimationPrice,
|
||||||
|
resolveCharacterAnimationSourceImageSrc,
|
||||||
} from './ImageCanvasGenerationModel';
|
} from './ImageCanvasGenerationModel';
|
||||||
|
|
||||||
type ImageGenerationSubmissionOptions = {
|
type ImageGenerationSubmissionOptions = {
|
||||||
@@ -43,6 +51,24 @@ export type ImageGenerationSubmissionPlan =
|
|||||||
rememberImageModel?: string;
|
rememberImageModel?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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({
|
export function buildImageGenerationSubmissionPlan({
|
||||||
dialog,
|
dialog,
|
||||||
layers,
|
layers,
|
||||||
@@ -141,3 +167,72 @@ export function buildImageGenerationSubmissionPlan({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import {
|
|||||||
} from './ImageCanvasGenerationLayerModel';
|
} from './ImageCanvasGenerationLayerModel';
|
||||||
import {
|
import {
|
||||||
CHARACTER_ANIMATION_DURATION_OPTIONS,
|
CHARACTER_ANIMATION_DURATION_OPTIONS,
|
||||||
CHARACTER_ANIMATION_MODEL,
|
|
||||||
CHARACTER_FRAME_DISPLAY_SIZE,
|
CHARACTER_FRAME_DISPLAY_SIZE,
|
||||||
CHARACTER_FRAME_ORIGINAL_SIZE,
|
CHARACTER_FRAME_ORIGINAL_SIZE,
|
||||||
DEFAULT_ICON_DESCRIPTIONS,
|
DEFAULT_ICON_DESCRIPTIONS,
|
||||||
@@ -37,16 +36,18 @@ import {
|
|||||||
SPEC_FRAME_DISPLAY_SIZE,
|
SPEC_FRAME_DISPLAY_SIZE,
|
||||||
SPEC_FRAME_ORIGINAL_SIZE,
|
SPEC_FRAME_ORIGINAL_SIZE,
|
||||||
buildEditGenerationInputs,
|
buildEditGenerationInputs,
|
||||||
buildIconGenerationInputs,
|
|
||||||
buildQuickEditModelOptions,
|
buildQuickEditModelOptions,
|
||||||
buildQuickEditSizeOptions,
|
buildQuickEditSizeOptions,
|
||||||
calculateCharacterAnimationPrice,
|
calculateCharacterAnimationPrice,
|
||||||
createCanvasLayerReference,
|
createCanvasLayerReference,
|
||||||
isCanvasGenerationDialog,
|
isCanvasGenerationDialog,
|
||||||
resolveCharacterAnimationSourceImageSrc,
|
|
||||||
resolveImageGenerationErrorMessage,
|
resolveImageGenerationErrorMessage,
|
||||||
} from './ImageCanvasGenerationModel';
|
} from './ImageCanvasGenerationModel';
|
||||||
import { buildImageGenerationSubmissionPlan } from './ImageCanvasGenerationSubmissionModel';
|
import {
|
||||||
|
buildCharacterAnimationSubmissionPlan,
|
||||||
|
buildIconSpritesheetGenerationSubmissionPlan,
|
||||||
|
buildImageGenerationSubmissionPlan,
|
||||||
|
} from './ImageCanvasGenerationSubmissionModel';
|
||||||
import { formatImageSizeValue } from './ImageCanvasEditorModel';
|
import { formatImageSizeValue } from './ImageCanvasEditorModel';
|
||||||
import type {
|
import type {
|
||||||
CanvasGenerationDialogState,
|
CanvasGenerationDialogState,
|
||||||
@@ -657,29 +658,15 @@ export function useImageCanvasGenerationWorkflow({
|
|||||||
) => {
|
) => {
|
||||||
updateCanvasGenerationDialogById(nextDialog.id, () => nextDialog);
|
updateCanvasGenerationDialogById(nextDialog.id, () => nextDialog);
|
||||||
};
|
};
|
||||||
const iconDescriptions = (
|
const submissionPlan =
|
||||||
dialog.iconDescriptions ?? DEFAULT_ICON_DESCRIPTIONS
|
buildIconSpritesheetGenerationSubmissionPlan(dialog);
|
||||||
)
|
if (!submissionPlan.ok) {
|
||||||
.map((description) => description.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
if (!dialog.iconSpecReference) {
|
|
||||||
if (canvasDialog) {
|
if (canvasDialog) {
|
||||||
setSubmittingIconDialog({
|
setSubmittingIconDialog({
|
||||||
...canvasDialog,
|
...canvasDialog,
|
||||||
status: 'failed',
|
status: 'failed',
|
||||||
composerOpen: true,
|
composerOpen: true,
|
||||||
errorMessage: '请选择图标素材规范',
|
errorMessage: submissionPlan.errorMessage,
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!iconDescriptions.length) {
|
|
||||||
if (canvasDialog) {
|
|
||||||
setSubmittingIconDialog({
|
|
||||||
...canvasDialog,
|
|
||||||
status: 'failed',
|
|
||||||
composerOpen: true,
|
|
||||||
errorMessage: '请填写素材描述',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -691,32 +678,28 @@ export function useImageCanvasGenerationWorkflow({
|
|||||||
|
|
||||||
setSubmittingIconDialog({
|
setSubmittingIconDialog({
|
||||||
...canvasDialog,
|
...canvasDialog,
|
||||||
iconDescriptions,
|
iconDescriptions: submissionPlan.iconDescriptions,
|
||||||
status: 'generating',
|
status: 'generating',
|
||||||
composerOpen: false,
|
composerOpen: false,
|
||||||
errorMessage: undefined,
|
errorMessage: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const generated = await generateEditorIconSpritesheet({
|
const generated = await generateEditorIconSpritesheet(
|
||||||
referenceImageSrc: dialog.iconSpecReference.src,
|
submissionPlan.input,
|
||||||
iconDescriptions,
|
);
|
||||||
model: dialog.imageModel ?? DEFAULT_IMAGE_MODEL,
|
setLastImageModel(submissionPlan.rememberImageModel);
|
||||||
aspectRatio: dialog.aspectRatio ?? '1:1',
|
|
||||||
imageSize: dialog.imageSize ?? '1K',
|
|
||||||
});
|
|
||||||
setLastImageModel(dialog.imageModel ?? DEFAULT_IMAGE_MODEL);
|
|
||||||
addIconSpritesheetResultLayers(
|
addIconSpritesheetResultLayers(
|
||||||
generated,
|
generated,
|
||||||
generated.iconImageSrcs,
|
generated.iconImageSrcs,
|
||||||
buildIconGenerationInputs(iconDescriptions, dialog.iconSpecReference),
|
submissionPlan.generationInputs,
|
||||||
getGeneratingDialogPlaceholder(dialog),
|
getGeneratingDialogPlaceholder(dialog),
|
||||||
canvasDialog.id,
|
canvasDialog.id,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setSubmittingIconDialog({
|
setSubmittingIconDialog({
|
||||||
...canvasDialog,
|
...canvasDialog,
|
||||||
iconDescriptions,
|
iconDescriptions: submissionPlan.iconDescriptions,
|
||||||
status: 'failed',
|
status: 'failed',
|
||||||
composerOpen: true,
|
composerOpen: true,
|
||||||
errorMessage: resolveImageGenerationErrorMessage(error),
|
errorMessage: resolveImageGenerationErrorMessage(error),
|
||||||
@@ -913,10 +896,13 @@ export function useImageCanvasGenerationWorkflow({
|
|||||||
if (!characterAnimationPanel || !characterAnimationSourceLayer) {
|
if (!characterAnimationPanel || !characterAnimationSourceLayer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const promptText = characterAnimationPanel.promptText.trim();
|
const submissionPlan = buildCharacterAnimationSubmissionPlan({
|
||||||
|
panel: characterAnimationPanel,
|
||||||
|
sourceLayer: characterAnimationSourceLayer,
|
||||||
|
});
|
||||||
const nextPanel = {
|
const nextPanel = {
|
||||||
...characterAnimationPanel,
|
...characterAnimationPanel,
|
||||||
promptText,
|
promptText: submissionPlan.promptText,
|
||||||
status: 'generating' as const,
|
status: 'generating' as const,
|
||||||
errorMessage: undefined,
|
errorMessage: undefined,
|
||||||
result: undefined,
|
result: undefined,
|
||||||
@@ -924,24 +910,9 @@ export function useImageCanvasGenerationWorkflow({
|
|||||||
setCharacterAnimationPanel(nextPanel);
|
setCharacterAnimationPanel(nextPanel);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await generateEditorCharacterAnimation({
|
const result = await generateEditorCharacterAnimation(
|
||||||
sourceLayerId: characterAnimationSourceLayer.id,
|
submissionPlan.input,
|
||||||
sourceImageSrc: resolveCharacterAnimationSourceImageSrc(
|
);
|
||||||
characterAnimationSourceLayer,
|
|
||||||
),
|
|
||||||
sourceWidth: characterAnimationSourceLayer.originalWidth,
|
|
||||||
sourceHeight: characterAnimationSourceLayer.originalHeight,
|
|
||||||
promptText,
|
|
||||||
resolution: nextPanel.resolution,
|
|
||||||
ratio: nextPanel.ratio,
|
|
||||||
frameCount: nextPanel.frameCount,
|
|
||||||
durationSeconds: nextPanel.durationSeconds,
|
|
||||||
priceMudPoints: calculateCharacterAnimationPrice(
|
|
||||||
nextPanel.resolution,
|
|
||||||
nextPanel.durationSeconds,
|
|
||||||
),
|
|
||||||
model: CHARACTER_ANIMATION_MODEL,
|
|
||||||
});
|
|
||||||
setCharacterAnimationPanel((currentPanel) =>
|
setCharacterAnimationPanel((currentPanel) =>
|
||||||
currentPanel
|
currentPanel
|
||||||
? {
|
? {
|
||||||
|
|||||||
Reference in New Issue
Block a user