新增编辑器生成规范、生成角色形象、生成图标素材等功能

新增编辑器生成规范、生成角色形象、生成图标素材等功能
This commit is contained in:
2026-06-16 14:47:13 +08:00
parent 0fd0a06387
commit 7eeff10c67
33 changed files with 8783 additions and 502 deletions

View File

@@ -0,0 +1,106 @@
# 图片画布生成对象独立化修复 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
**Goal:** 修复图片画布底部生成按钮复用单一状态导致后创建对象销毁前一个对象的问题。
**Architecture:** 保留现有图片画布组件结构,先以回归测试锁定“规范占位 + 角色占位可并存”。实现上把生成占位状态从单个 `generateDialog` 扩展为 active dialog + inactive dialog 列表;每次新建生成对象只新增一个 dialog 实例,旧实例保留占位和逻辑状态,只有当前 active 实例渲染编辑面板。
**Tech Stack:** React、TypeScript、Vitest、Testing Library。
---
### Task 1: 补充失败回归测试
**Files:**
- Modify: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.test.tsx`
- [x] **Step 1: Write the failing test**
`keeps the bottom AI toolbar visible while generation panels are open` 附近新增测试:
```tsx
it('keeps existing generation placeholders when another bottom generation object is created', () => {
render(<ImageCanvasEditorView />);
const bottomToolbar = screen.getByRole('toolbar', { name: 'AI画布工具栏' });
fireEvent.click(
within(bottomToolbar).getByRole('button', { name: '生成规范' }),
);
fireEvent.click(screen.getByRole('menuitem', { name: '角色形象规范' }));
expect(screen.getByLabelText('规范生成占位图')).toBeTruthy();
expect(screen.getByRole('dialog', { name: '生成规范' })).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: '生成角色形象' }));
expect(screen.getByLabelText('规范生成占位图')).toBeTruthy();
expect(screen.getByLabelText('角色生成占位图')).toBeTruthy();
expect(screen.getByRole('dialog', { name: '生成角色形象' })).toBeTruthy();
});
```
- [x] **Step 2: Run test to verify it fails**
Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "keeps existing generation placeholders"`
Expected: FAIL because only the latest placeholder remains.
### Task 2: 实现最小独立生成对象状态
**Files:**
- Modify: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.tsx`
- [x] **Step 1: Add stable dialog ids and inactive dialog state**
Add `id: string` to `GenerateDialogState`; add `generationDialogCounterRef`; add `inactiveGenerateDialogs` state. Provide helpers to create ids, archive current active dialog before replacing it, update active/inactive dialogs by id, and list all canvas generation dialogs.
- [x] **Step 2: Update bottom generation openers**
Change `openGenerateDialog``openSpecDialog``openCharacterGenerationDialog``openIconGenerationDialog` so each call archives the current active canvas generation dialog and sets a newly created active dialog. Edit modal remains single active dialog and does not archive.
- [x] **Step 3: Render all placeholders**
Replace the single placeholder render block with a map over inactive dialogs plus active dialog. Only active dialog shows composer; inactive dialogs remain visible and can be clicked to reactivate their own panel.
- [x] **Step 4: Keep actions scoped to active dialog**
Keep submit/update/upload/pick actions operating on active dialog only. Adjust delete, drag, blur, and generated-layer cleanup so they update or remove only the matching active/inactive dialog.
- [x] **Step 5: Keep archived async generation writeback scoped by dialog id**
当一个生成对象已经进入 `generating`,随后用户再创建第二个生成对象并把第一个对象归档为 inactive 时,第一个对象仍可能继续被拖拽或等待异步完成。完成回写必须按 `dialog.id` 从 active + inactive 的最新状态读取占位图,不能使用提交瞬间的旧 `placeholder` 快照。
回归测试:
```bash
npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "keeps archived generation logic"
```
### Task 3: 验证并更新文档
**Files:**
- Modify: `C:/Genarrative/docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`
- Optional Modify: `C:/Genarrative/.hermes/shared-memory/pitfalls.md`
- [x] **Step 1: Run focused tests**
Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "keeps existing generation placeholders|opens character spec generation form|opens icon asset generation panel|removes the active character generation placeholder"`
Expected: PASS.
- [x] **Step 2: Run full image editor test**
Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx`
Expected: PASS.
- [x] **Step 3: Run encoding check**
Run: `npm run check:encoding`
Expected: PASS.
- [x] **Step 4: Document behavior**
Add one sentence to the image canvas editor technical plan: bottom generation buttons create independent canvas generation objects; creating a new one must not destroy previous placeholders or generated-object logic.

View File

@@ -0,0 +1,128 @@
# 画板角色动画生成 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在图片画布编辑器中,仅对角色图片提供角色动画生成入口,并通过后端 seedance2.0 链路生成视频、抽帧、去绿幕并持久化到 OSS。
**Architecture:** 前端在 `ImageCanvasEditorView` 中基于图层 `assetKind === "character"` 控制悬浮按钮和右键菜单,打开锚定到图片右侧的独立动画生成面板。前端 service 调用新增编辑器角色动画 API后端复用 `character_animation_assets.rs` 中现有视频生成、抽帧、绿幕去背、OSS 写入能力,避免新建平行资产系统。
**Tech Stack:** React + TypeScript + VitestRust Axum `api-server`;现有 `shared-contracts` 资产 DTOAliyun OSS 资产持久化VectorEngine/Ark seedance2.0 角色动画链路。
---
### Task 1: 文档补充
**Files:**
- Modify: `C:/Genarrative/docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`
- [ ] **Step 1: 补充角色动画生成章节**
- 明确仅 `assetKind: "character"` 图层展示入口。
- 明确右侧独立面板字段、预设动作、价格、模型、抽帧和 OSS 存储口径。
- 明确非角色图层不展示按钮。
- [ ] **Step 2: 运行编码检查**
- Run: `npm run check:encoding`
- Expected: PASS 或仅与本任务无关的既有问题。
### Task 2: 前端失败测试
**Files:**
- Modify: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.test.tsx`
- Modify: `C:/Genarrative/src/services/image-editor/editorProjectClient.test.ts`
- [ ] **Step 1: 写失败测试**
- 测试角色图层显示悬浮 / 右键 `生成动画`
- 测试非角色图层不显示 `生成动画`
- 测试面板提交请求包含 `sourceLayerId``sourceImageSrc`、prompt、resolution、ratio、frameCount、durationSeconds、priceMudPoints、model。
- [ ] **Step 2: 验证 RED**
- Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx src/services/image-editor/editorProjectClient.test.ts`
- Expected: FAIL失败原因是功能/API 尚未实现。
### Task 3: 前端实现
**Files:**
- Modify: `C:/Genarrative/src/services/image-editor/editorProjectClient.ts`
- Modify: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.tsx`
- Modify: `C:/Genarrative/src/index.css`
- [ ] **Step 1: 新增 service 类型和请求函数**
- `generateEditorCharacterAnimation(input)` 调用 `/api/editor/character-animations/generations`
- 限定 model 固定为 `seedance2.0` 的回包展示字段。
- [ ] **Step 2: 扩展图层 assetKind**
- `CanvasLayer.assetKind` 支持 `'character' | 'spec' | null`
- hydrate / serialize / 图片类型展示跟随扩展。
- [ ] **Step 3: 加入口和面板**
- 角色图层悬浮工具条和右键菜单显示 `生成动画`
- 面板锚定图片右侧,字段按设计实现,文本框 maxLength=4000。
- 价格通过 `resolution * durationSeconds` 计算。
- [ ] **Step 4: 验证 GREEN**
- Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx src/services/image-editor/editorProjectClient.test.ts`
- Expected: PASS。
### Task 4: 后端失败测试
**Files:**
- Modify: `C:/Genarrative/server-rs/crates/shared-contracts/src/assets.rs`
- Modify: `C:/Genarrative/server-rs/crates/api-server/src/character_animation_assets.rs`
- Modify: `C:/Genarrative/server-rs/crates/api-server/src/modules/play_flow.rs` 或现有 editor router 文件(按现有路由事实选择)
- [ ] **Step 1: 写 DTO / prompt / plan 单测**
- 验证请求 480p/720p、32/40/48 帧、比例枚举、模型固定 seedance2.0。
- 验证构造 prompt 包含用户给定固定骨架与动作描述。
- 验证价格计算480p 每秒 10720p 每秒 20。
- [ ] **Step 2: 验证 RED**
- Run: `cargo test -p api-server editor_character_animation --manifest-path server-rs/Cargo.toml`
- Expected: FAIL失败原因是 helper 或 handler 尚未实现。
### Task 5: 后端实现
**Files:**
- Modify: `C:/Genarrative/server-rs/crates/shared-contracts/src/assets.rs`
- Modify: `C:/Genarrative/server-rs/crates/api-server/src/character_animation_assets.rs`
- Modify: `C:/Genarrative/server-rs/crates/api-server/src/modules/play_flow.rs` 或现有 editor router 文件
- [ ] **Step 1: 新增编辑器角色动画 DTO**
- 请求字段sourceLayerId/sourceImageSrc/promptText/resolution/ratio/frameCount/durationSeconds/sourceWidth/sourceHeight。
- 响应字段taskId/model/prompt/previewVideoPath/frames/priceMudPoints。
- [ ] **Step 2: 新增 handler**
- 校验 prompt 1..4000、resolution、ratio、frameCount 与 durationSeconds 组合。
- sourceImageSrc 作为首尾帧参考。
- 调用现有 seedance image-to-video 逻辑生成预览视频。
- 调用现有抽帧 + 绿幕去背 + OSS 持久化逻辑输出帧。
- [ ] **Step 3: 路由接入**
- `POST /api/editor/character-animations/generations`
- 保持走 play_flow 创作/游玩支撑主干或 editor 路由现有聚合,不回到 `app.rs` 平行挂载。
- [ ] **Step 4: 验证 GREEN**
- Run: `cargo test -p api-server editor_character_animation --manifest-path server-rs/Cargo.toml`
- Expected: PASS。
### Task 6: 总验证与收口
**Files:**
- All modified files.
- [ ] **Step 1: 定向前端测试**
- Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx src/services/image-editor/editorProjectClient.test.ts`
- Expected: PASS。
- [ ] **Step 2: 定向后端测试 / check**
- Run: `cargo test -p api-server editor_character_animation --manifest-path server-rs/Cargo.toml`
- Run: `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
- Expected: PASS。
- [ ] **Step 3: 类型与编码**
- Run: `npm run typecheck`
- Run: `npm run check:encoding`
- Expected: PASS。
- [ ] **Step 4: 检查 diff**
- Run: `git diff --check`
- Expected: PASS。