合并编辑器素材库分支

合并编辑器素材库与项目持久化前后端改动

补齐画布素材面板、导入导出、生成流程与相关测试

同步外部 API Key、OpenAPI、运维守门与项目记忆文档
This commit is contained in:
2026-06-22 17:59:03 +08:00
380 changed files with 91410 additions and 701 deletions
+3
View File
@@ -38,6 +38,9 @@ temp*build*/
/target/
/logs
/.codegraph/
/.playwright-cli/
**/.playwright-cli/
/output/playwright/
/server-rs/crates/*/logs/
.worktrees/
.rag/
+16
View File
@@ -12,6 +12,22 @@ _Avoid_: 默认对话式 Agent 工作台、默认轻输入 Agent 工作台、复
角色形象、UI 背景、容器、封面、分享图等单张图资产的统一输入与重生成方式,统一通过 `CreativeImageInputPanel` 表达上传、AI 重绘、参考图、历史图和删除确认。
_Avoid_: 在玩法页面内手写上传、参考图、重绘、预览、删除确认
**图片画布工程**:
独立 `/editor` 中可保存、恢复和继续编辑的图片画布工作状态,包含画布视图、图层布局和资源引用;用于多图对比、生成结果衍生和画布级编辑,不替代玩法页面内的单图资产编辑。
_Avoid_: 玩法结果页单图槽位、发布态作品、只存在前端内存里的临时画布
**画布资源**:
图片画布工程中可被一个或多个图层引用的图片资源记录,保存 OSS 对象引用、上传 / 生成来源、提示词、模型、任务和尺寸等资源元数据;同一资源可以在工程布局中出现多次。
_Avoid_: 图层位置、前端 hover / selected 状态、直接内嵌图片二进制
**图层布局**:
图片画布工程中描述资源实例如何摆放的画布结构,包含 resourceId、位置、尺寸、缩放、层级和选中所需的稳定图层 ID;布局属于工程快照,不属于画布资源本身。
_Avoid_: 把同一资源的全局元数据和某一次摆放坐标混在同一条资源记录里
**生成资源**:
由图片生成或图片修改流程产生的画布资源,必须记录来源资源、提示词、实际提示词、模型、provider、任务 ID 和生成时间;本期 `/editor` 的生成修改先允许 mock 生成资源,但仍按生成资源元数据形状保存。
_Avoid_: 无来源的静态素材、只显示在 UI 但不落工程资源记录的生成结果
**系列素材图集生成**:
一组同类素材的统一批量生成方式,采用批量规划、sheet 生图、后端切图、透明化、OSS 持久化和局部重生成的通用流水线。
_Avoid_: 为每个玩法单独发明素材流水线、把系列素材建模成任一玩法专属 DTO
+176
View File
File diff suppressed because one or more lines are too long
+6
View File
@@ -21,6 +21,12 @@
微信小程序虚拟支付接入、`wechat_mp_virtual` 渠道、`wx.requestVirtualPayment` 承接页和后端签名配置见 [【技术方案】微信虚拟支付接入-2026-05-26.md](./%E3%80%90%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88%E3%80%91%E5%BE%AE%E4%BF%A1%E8%99%9A%E6%8B%9F%E6%94%AF%E4%BB%98%E6%8E%A5%E5%85%A5-2026-05-26.md)。
`/editor/agent` 浏览器内 AI Web 工程编辑器的静态 SPA 沙箱预览 MVP,采用“平台编辑器壳 + api-server 控制面 + 独立 runner worker + 独立预览域”四层结构;技术方案、威胁模型和验收清单见 [【技术方案】浏览器内AIWeb工程沙箱预览方案-2026-06-13.md](./technical/【技术方案】浏览器内AIWeb工程沙箱预览方案-2026-06-13.md)、[【安全模型】AIWeb工程Runner与预览隔离威胁模型-2026-06-13.md](./technical/【安全模型】AIWeb工程Runner与预览隔离威胁模型-2026-06-13.md) 和 [【测试用例】AIWeb工程静态预览MVP验收清单-2026-06-13.md](./technical/【测试用例】AIWeb工程静态预览MVP验收清单-2026-06-13.md)。P1 先用确定性 mock Agent 生成结构化 patch、真实打通项目 / 快照 / 构建 / artifact / 预览闭环,落地拆分见 [【技术方案】EditorAgentMockAgentP1落地计划-2026-06-15.md](./technical/【技术方案】EditorAgentMockAgentP1落地计划-2026-06-15.md)。
`/editor/canvas` 图片画布编辑器的画布素材 ZIP 导出能力,入口放在右上角标题栏下载图标内,第一版采用前端 JSZip 打包画布中有效图层引用的上传图、生成图和修改结果,方案见 [【前端架构】图片画布素材导出方案-2026-06-15.md](./technical/【前端架构】图片画布素材导出方案-2026-06-15.md)。
桌面端 `/creation` 创作工具主页、顶级“草稿”入口替换为“项目”、最近项目、新建项目和陶泥儿精选素材瀑布流的落地计划见 [【玩法创作】创作主页与项目入口改版计划-2026-06-18.md](./%E3%80%90%E7%8E%A9%E6%B3%95%E5%88%9B%E4%BD%9C%E3%80%91%E5%88%9B%E4%BD%9C%E4%B8%BB%E9%A1%B5%E4%B8%8E%E9%A1%B9%E7%9B%AE%E5%85%A5%E5%8F%A3%E6%94%B9%E7%89%88%E8%AE%A1%E5%88%92-2026-06-18.md)。
本地通过 SSH alias 管理多台服务器、查看硬件 / systemd / HTTP 健康状态并执行受控服务启停的 egui 桌面工具见 [【开发运维】本地SSH服务器管理面板技术方案-2026-06-11.md](./technical/【开发运维】本地SSH服务器管理面板技术方案-2026-06-11.md)。
生产部署切换到 systemd + Nginx + SpacetimeDB 自托管的总方案见 [PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md](./technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md),该文档也是当前生产 Jenkinsfile 的唯一入口。Pingora 只作为独立二进制影子网关试点时,边界、路由口径与替换前验收见 [【开发运维】Pingora独立网关试点-2026-06-11.md](./technical/【开发运维】Pingora独立网关试点-2026-06-11.md)。SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md);private 表迁移 JSON 导入导出、HTTP 413 分片导入和旧数据库迁移流水线经验见 [SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md](./technical/SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md) 与 [JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md](./technical/JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md);后台管理独立前端工程技术方案见 [ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md](./technical/ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md)。
File diff suppressed because it is too large Load Diff
@@ -151,15 +151,15 @@ WF-*
1. 调用 VectorEngine `/v1/images/edits`,模型固定为 `gpt-image-2`;
2. multipart 参考图固定包含默认木鱼图 `/wooden-fish/default-hit-object.png`,作为基础结构和画风参考;
3. 若用户上传参考图,该图只作为新主题参考追加到同一次 image2 edits 请求,不直接进入运行态;
4. 尺寸固定 `1:1`,必须输出绿色背景主体图(纯绿色绿幕),背景为单一纯绿色 `#00FF00`,并显式禁止黑底、白底、棋盘格、纸板底或任何其它实底背景;
4. 尺寸固定 `1:1`,必须输出单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景主体图,并显式禁止黑底、白底、棋盘格、纸板底或任何其它实底背景;
5. 提示词严格使用:
```text
生成敲木鱼新样式,要求结构,画风与参考图保持高度一致,新样式颜色搭配使用新主题对应的颜色。尺寸1:1,先输出绿色背景主体图(纯绿色绿幕),背景必须是单一纯绿色 #00FF00 且平整无纹理、无渐变、无阴影、无道具,主体完整居中,主体边缘必须干净,不要直接输出透明底。随后由服务端对绿色背景主体图做抠图去除绿色背景。最终结果只保留单个敲击物图案,禁止黑底、白底、棋盘格、纸板底或任何实底背景;主体本身不要使用与绿幕接近的纯绿色,若新主题天然包含绿色,请改用偏深、偏黄或偏蓝的绿色并与绿幕清晰区分。
生成敲木鱼新样式,要求结构,画风与参考图保持高度一致,新样式颜色搭配使用新主题对应的颜色。尺寸1:1,先输出单一纯绿色 #00FF00 / RGB(0,255,0) 绿幕背景主体图,背景必须严格使用 #00FF00 / RGB(0,255,0) 且平整无纹理、无渐变、无阴影、无道具,主体完整居中,主体边缘必须干净,不要直接输出透明底。随后由服务端对绿色背景主体图做抠图去除绿色背景。最终结果只保留单个敲击物图案,禁止黑底、白底、棋盘格、纸板底或任何实底背景;主体本身不要使用与绿幕接近的纯绿色,若新主题天然包含绿色,请改用偏深、偏黄或偏蓝的绿色并与绿幕清晰区分。
新主题为:(用户提供参考图或用户输入关键词)
```
敲击物图案落盘前,`api-server` 必须只对第一步生成的纯绿色绿幕背景执行去绿处理,把绿色背景转成真实透明 alpha PNG;不得对黑底、白底或其它未知实底执行泛抠图,避免误伤玉米等主体像素。去绿处理必须保留主体内部深色结构和主题细节。
敲击物图案落盘前,`api-server` 必须只对第一步生成的单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景执行去绿处理,把绿色背景转成真实透明 alpha PNG;不得对黑底、白底或其它未知实底执行泛抠图,避免误伤玉米等主体像素。去绿处理必须保留主体内部深色结构和主题细节。
背景环境图生成流程固定为:
@@ -178,12 +178,12 @@ WF-*
1. 调用 VectorEngine `/v1/images/edits`,模型固定为 `gpt-image-2`;
2. multipart 参考图固定包含第一步去除绿色背景后的敲击物主体图,以及第二步生成的背景环境图;
3. 尺寸固定 `1:1`,必须输出绿色背景主体图(纯绿色绿幕),后端落库前执行同一套去绿背景处理;
3. 尺寸固定 `1:1`,必须输出单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景主体图,后端落库前执行同一套去绿背景处理;
4. 按主题、画风、材质和配色生成左上角返回按钮图,但参考图只用于约束圆形底色和中央左箭头的颜色搭配,不得借鉴复杂造型、花纹、浮雕边、异形外框或装饰图案;按钮必须始终是标准圆形,主体视觉尺寸比当前模板再放大约 50%,圆形外沿必须有与主题色搭配的干净外描边,中央只保留单个清晰左箭头或返回箭头,不得包含文字、数字、水印、额外 UI 面板、木槌或敲击道具;
5. 提示词严格使用:
```text
生成敲木鱼左上角返回按钮图。要求以参考图-去除绿色背景后的敲击物主体和背景环境图为主题、画风、材质和配色参考,但参考图只用来约束圆形底色和中央左箭头的颜色搭配,不要继承复杂造型、花纹、浮雕边、异形外框或装饰图案。按钮必须始终是标准圆形,整体像单个圆形图标,按钮主体在画布中的视觉尺寸比当前模板再放大约 50%,圆心居中,圆形外沿加一圈和主题色搭配的干净外描边,让它更像一个按钮,但仍然只保留一个清晰、简洁、居中的向左返回箭头,不要出现文字、数字、水印、按钮外标签、额外 UI 面板、木槌或敲击道具。尺寸1:1,输出绿色背景主体图(纯绿色绿幕),背景必须是单一纯绿色 #00FF00 且平整无纹理、无渐变、无阴影。按钮主体边缘干净,后续由服务端扣除绿色背景;按钮底色不要使用与绿幕接近的纯绿色,若主题天然包含绿色,请仅在圆形底色上使用偏深、偏黄或偏蓝的主题绿色,并用更高对比的箭头颜色区分。
生成敲木鱼左上角返回按钮图。要求以参考图-去除绿色背景后的敲击物主体和背景环境图为主题、画风、材质和配色参考,但参考图只用来约束圆形底色和中央左箭头的颜色搭配,不要继承复杂造型、花纹、浮雕边、异形外框或装饰图案。按钮必须始终是标准圆形,整体像单个圆形图标,按钮主体在画布中的视觉尺寸比当前模板再放大约 50%,圆心居中,圆形外沿加一圈和主题色搭配的干净外描边,让它更像一个按钮,但仍然只保留一个清晰、简洁、居中的向左返回箭头,不要出现文字、数字、水印、按钮外标签、额外 UI 面板、木槌或敲击道具。尺寸1:1,输出单一纯绿色 #00FF00 / RGB(0,255,0) 绿幕背景主体图,背景必须严格使用 #00FF00 / RGB(0,255,0) 且平整无纹理、无渐变、无阴影。按钮主体边缘干净,后续由服务端扣除绿色背景;按钮底色不要使用与绿幕接近的纯绿色,若主题天然包含绿色,请仅在圆形底色上使用偏深、偏黄或偏蓝的主题绿色,并用更高对比的箭头颜色区分。
主题为:(用户提供参考图或用户输入关键词)
```
+225 -13
View File
@@ -16,6 +16,94 @@
---
## 2026-06-21 图片画布生成完成态由后端写入画布布局
- 背景:图片画布角色形象等长耗时生成在服务端完成后,如果浏览器已刷新或原 HTTP 回调丢失,前端无法再把生成结果图层和 `generation-dialog` 完成态写回 `editor_canvas.layers_json`,用户会继续看到“生成中”卡片。
- 决策:图片生成请求在有项目上下文时携带 `canvasCompletion`(生成器 `dialogId`、结果标题和占位框);`api-server` 在生成成功并创建 `editor_project_resource` / `editor_asset` 后,直接读取当前项目布局,只有当前布局仍存在对应生成器时才插入轻量结果图层,把生成器标记为 `idle` 并写入 `generatedLayerId`,沿用后端当前 viewport 保存 layout 后返回最新项目快照。前端只应用后端快照刷新显示,不再把生成完成态作为正式业务真相,也不在项目加载时根据资源行推断完成态。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、图片画布生成提交工作流、项目快照 hydrate / persistence、图片画布技术方案和排障记录。
- 验证方式:`cargo test -p api-server editor_canvas_generation_completion --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run test -- src/components/image-editor/ImageCanvasEditorModel.test.ts src/components/image-editor/useImageCanvasProjectPersistence.test.tsx src/services/image-editor/editorProjectClient.test.ts src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx -- --runInBand`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-21 图片画布参考图元数据只保存项目内行引用
- 背景:参考图如果把 Data URL、signed URL 或 `objectKey` 写入 `generationInputs` 或生成器布局快照,会撑大资源 / 素材 / 画布 JSON,也无法稳定索引到项目内用户可见行数据。
- 决策:`generationInputs.references` 只保存 `{ title, label, refType, refId }`,其中 `refType="project-resource"` 指向 `editor_project_resource.resourceId`,`refType="asset"` 指向 `editor_asset.assetId`。生成器 `itemType="generation-dialog"` 布局快照中的参考图也只保存 `resourceId/sourceAssetId` 和展示 label,不保存图片 Data URL、signed URL 或 `objectKey`;提交生成请求前的内存态可以临时持有 `src/objectKey`,刷新恢复时从 `editor_project_resource` / `editor_asset` 行补回请求所需图片源。不兼容旧 `src` 型参考图元数据。
- 影响范围:图片画布生成输入快照、生成器布局保存 / 恢复、参考图上传工作流、元数据弹窗和图片画布技术文档。
- 验证方式:运行图片画布生成模型、生成提交、上传工作流、项目持久化、元数据弹窗相关前端测试,执行 `npm run typecheck`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-19 外部 OpenAPI 与 API Key 管理走 server-rs 正式链路
- 背景:外部调用方需要稳定调用图片画布项目创建、画布布局保存和编辑器美术生图能力,同时需要可撤销的开发者凭据,不能依赖前端临时状态或人工分发密钥。
- 决策:外部 API 固定放在 `/api/external/v1` 命名空间,v1 暴露素材直传凭证 / asset object 确认 / 签名读取、项目列表 / 最近 / 创建 / 读取 / 重命名 / 删除、默认画布保存、账号级素材库、项目资源记录、编辑器图片 / 视频 / 音频生成和 `/api/external/v1/openapi.json`。API Key 管理走登录态 `/api/profile/api-keys`,外部调用使用 `Authorization: Bearer tnr_sk_xxx`;后端只保存 `key_hash` 和 `key_prefix`,明文只在创建响应返回一次。外部 API 鉴权、项目 / 画布 / 素材写回全部经 `api-server -> spacetime-client -> spacetime-module`,生成素材成功后按请求写入账号级 `editor_asset`,带 `projectId` 时写入 `editor_project_resource`;外部确认 asset object 时 owner 固定为 API Key 所属账号。API Key 管理接口不进入外部 OpenAPI JSON。
- 影响范围:`server-rs/crates/api-server/src/external_*`、`server-rs/crates/api-server/src/modules/external_api.rs`、`server-rs/crates/spacetime-module/src/external_api_key_storage.rs`、`server-rs/crates/spacetime-client/src/external_api_key.rs`、`docs/openapi/genarrative-external-v1.openapi.json` 和后端数据契约文档。
- 验证方式:`cargo test -p api-server external_api --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server external_editor_api --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:spacetime-schema`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md`。
## 2026-06-19 图片画布素材生成元数据上移到资源和素材
- 背景:角色、图标、UI 设计图、视频和音频等生成结果会在图片信息页展示用户可见输入快照;此前这些 `assetKind/generationInputs` 主要保存在画布 layer JSON 中,素材进入账号级素材库后跨项目复用和刷新恢复都依赖画布布局,不符合素材库作为账号级事实源的边界。
- 决策:普通图层的新保存不再把 `assetKind/generationInputs` 写入 `editor_canvas.layers_json`。`editor_project_resource` 保存项目画布资源快照的 `asset_kind/generation_inputs_json`,`editor_asset` 保存账号级素材的同名元数据;图片 / 图标 / UI 提取等生成 BFF 在请求携带 `projectId` / `assetFolderId` 时由后端创建新 resource / asset 并把快照回传前端,前端只用回包更新画布图层和素材栏,不再把同一生成结果二次调用保存接口。前端加载时优先从 resource / asset 恢复素材类别和生成输入快照,旧 layout 中的同名字段只作为历史兼容兜底。生成器对象本身仍作为 `itemType="generation-dialog"` 保存在画布布局中。
- 影响范围:`server-rs/crates/spacetime-module/src/editor_project_storage.rs`、`server-rs/crates/spacetime-client/src/mapper/editor_project.rs`、`server-rs/crates/api-server/src/editor_project.rs`、`src/services/image-editor/editorProjectClient.ts`、图片画布 hydrate / serialize / project persistence / asset library 代码和后端数据契约文档。
- 验证方式:运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`、`npm run test -- src/components/image-editor/ImageCanvasEditorModel.test.ts src/components/image-editor/useImageCanvasProjectPersistence.test.tsx src/services/image-editor/editorProjectClient.test.ts`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`,并按需补充 `cargo check -p spacetime-client -p api-server --manifest-path server-rs/Cargo.toml`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-19 图片画布生成按钮价格统一绑定模型定价配置
- 背景:图片画布的生成图片、生成视频、生成规范、生成角色、生成素材、生成 UI、宣发素材、快速编辑、重绘和音频生成入口都在按钮内显示泥点;如果按钮文案、前端提交和后端校验各自写固定数值,后续调整模型价格会出现展示价、提交价和扣费价不一致。
- 决策:所有画板生成按钮价格必须从 `src/components/image-editor/ImageCanvasGenerationModel.ts` 的模型定价配置函数计算;需要提交 `priceMudPoints` 的视频、角色动画、图标素材、音效和背景音乐也使用同一函数。后端用 `server-rs/crates/api-server/src/editor_generation_config.rs` 的同名语义配置重新计算并校验 / 扣费。当前正式模型均已覆盖定价:图片类 `nanobanana2`(真实模型 `gemini-3.1-flash-image-preview`)为 12,`gpt-image-2` 为 20;生成规范固定 `gpt-image-2` 为 5;视频按模型和清晰度分档,`seedance2.0-fast` 为 480p 每秒 10 / 720p 每秒 20,`seedance2.0` 为 12 / 24,`kling3.0` 为 15 / 30,`kling3.0-omni` 为 20 / 40,Veo 旧布局兼容价仍为 10 / 20。画板 UI 统一显示 `nanobanana2`,历史输入或旧布局中的 `nano-banana` 必须归一到真实模型 ID 后再提交和计费。
- 影响范围:图片画布生成类面板、生成提交模型、编辑器图片 / 视频 / 音频 BFF、`editor_generation_config` 和 Lovart 生成类面板文档。
- 验证方式:运行 `npm run test -- src/components/image-editor/ImageCanvasGenerationModel.test.ts src/components/image-editor/ImageCanvasQuickEditPanelView.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx`,并执行 `npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`。
## 2026-06-18 图片画布 UI 设计图提取素材保留图集
- 背景:UI 设计图需要从成图中继续抽取可复用独立素材;原图标素材生成只把拆分后的图标放入画布,spritesheet 原图没有保留,后续追溯和二次切图不方便。
- 决策:`assetKind="ui-design"` 图层浮动工具栏新增 `提取素材`,固定调用 `/api/editor/ui-designs/assets/extractions`,后端固定 `gpt-image-2` 和提示词 `提取画面中的所有独立并整理成spritesheet`,返回结构复用图标 spritesheet 响应。图标生成与 UI 提取都必须把 spritesheet 图集作为 `assetKind="icon-spritesheet"` 图层放到画布,再放拆分后的 `assetKind="icon"` 素材。
- 影响范围:图片画布浮动工具栏、编辑器图片生成 BFF、`platform-image` 图集连通域拆分、画布图层类型和编辑器文档。
- 验证方式:运行图片画布工具栏 / 图集落层 / 生成提交相关前端测试,`cargo test -p platform-image generated_asset_sheets --manifest-path server-rs/Cargo.toml`,以及 `cargo test -p api-server editor_ui_design_asset_extraction_prompt_is_fixed --manifest-path server-rs/Cargo.toml`。
- 关联文档:`docs/【编辑器】画板UI设计图生成入口设计-2026-06-17.md`、`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`。
## 2026-06-18 `/creation` 独立为陶泥儿创作工具主页
- 背景:图片画布项目已经成为独立项目资产,旧“创作”站内 Tab 和一级“草稿”入口不能清晰表达桌面端创作工具主页与项目管理入口。
- 决策:桌面端新增独立 `/creation` 创作工具主页,桌面端直接打开站点首页 `/` 时也默认展示新版创作主页;顶级“创作”入口跳转 `/creation`,原“草稿”入口替换为“项目”并跳转 `/project`。移动端首页 `/` 继续保持原推荐首页,移动端隐藏“创作”和“项目”入口,但不拦截直接 URL。页面文案统一使用“陶泥儿”,外部品牌只作为设计参考,不进入用户可见 UI、测试名、产品文案或验收口径。`陶泥儿精选` 是用户素材瀑布流,不展示玩法入口列表;创作入口事实源继续来自 `/api/creation-entry/config`,项目入口通过 `createEditorProject` 和 `/editor/canvas?projectid=xxx` 链路进入画布。
- 影响范围:平台入口导航、`SelectionStage` 路由、`/creation` 首页、`/project` 项目入口、`/editor/canvas` 项目打开链路、“我的”页快捷入口和创作入口相关测试。
- 验证方式:桌面 `/` 初始阶段和 `/creation` 解析为创作主页,移动端 `/` 仍是推荐首页,`/creation/<play>` 仍进入对应玩法工作台;桌面导航显示“创作 / 项目”且不显示“草稿”;移动端底部不显示“创作 / 项目”;页面不出现外站品牌或 `Discord` 字样;新建项目进入 `/editor/canvas?projectid=xxx`。
- 关联文档:`docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-18 图片画布 Seedance 2.0 参考媒体提交边界
- 背景:`/editor/canvas` 生成视频需要严格对齐火山 Seedance 2.0 多模态参考输入;参考视频若继续走 Base64 / `data:video` 会超过请求体并被上游拒绝,参考音频单独输入和非 Seedance 模型携带参考字段也会违反文档契约。
- 决策:仅 `seedance2.0-fast` / `seedance2.0` 可提交参考图片、参考视频、参考音频;图片 0~9、视频 0~3、音频 0~3,音频必须搭配图片或视频。参考视频只能提交公网 URL、`asset://` 或画板资源 `objectKey`,禁止 `data:video/*`;视频 / 音频上传先走 OSS 直传和 asset_object confirm,前端保存 signed URL 预览但提交优先 `objectKey`,后端统一重新签名给 Ark。Ark body 按 `image_url` / `video_url` / `audio_url` + `reference_*` role 构造,并显式发送 `generate_audio:false`。
- 影响范围:图片画布生成视频面板、参考媒体上传工作流、`editorReferenceUploadClient`、`ImageCanvasGenerationSubmissionModel`、`shared-contracts`、`api-server` 编辑器视频 BFF、Lovart 生成类面板文档。
- 验证方式:运行 `npx vitest run src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/services/image-editor/editorReferenceUploadClient.test.ts --reporter verbose`、`cargo test -p api-server editor_video --manifest-path server-rs/Cargo.toml`、`cargo test -p shared-contracts editor_video_request_supports_seedance_multimodal_references --manifest-path server-rs/Cargo.toml`,并执行 `npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`、火山 Seedance 2.0 任务创建文档。
## 2026-06-21 图片画布生成视频参数扩展
- 背景:编辑器画布生成视频需要开放更多 Lovart 式参数,同时保留模型能力边界;`seedance2.0-fast` 不支持 `1080p`,联网搜索暂没有可确认的 Ark 视频生成 body 字段。
- 决策:生成视频参数面板支持比例 `16:9 / 9:16 / 1:1 / 4:3 / 3:4 / 21:9`,时长为 4 到 15 秒整数 slider,清晰度支持 `480p / 720p / 1080p`;`seedance2.0-fast` 不展示可用 `1080p`,从其它模型的 `1080p` 切回 Fast 时自动降到 `720p`,后端也拒绝 `seedance2.0-fast + 1080p`。静音只作为一个 toggle 展示,默认有声并映射 `sound=on` / Ark `generate_audio=true`;关闭静音时传 `sound=off` / `generate_audio=false`。`webSearchEnabled` 默认随请求提交为 `true`,但前端不展示联网搜索开关,后端当前只接收契约字段,不向 Ark 透传未知参数。
- 影响范围:图片画布生成视频面板、生成提交模型、画布项目快照恢复、`editorProjectClient`、`shared-contracts`、`api-server` 编辑器视频 BFF、编辑器技术文档。
- 验证方式:运行 `npm run test -- src/components/image-editor/ImageCanvasGenerationModel.test.ts src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/components/image-editor/ImageCanvasGenerationComposerView.test.tsx src/services/image-editor/editorProjectClient.test.ts src/components/image-editor/ImageCanvasGenerationDialogModel.test.ts`、`cargo test -p shared-contracts editor_video --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server editor_video --manifest-path server-rs/Cargo.toml`,并执行 `npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`。
## 2026-06-18 图片画布生成音乐入口作为音频图层接入
- 背景:图片画布底部生成工具需要补齐游戏音效和游戏背景音乐生成,既要复用现有 Lovart 式画布生成器快照、占位避让和持久化,又不能把音频能力并入图片素材库或视觉小说专用音频开关。
- 决策:`/editor/canvas` 新增底部 `生成音乐` 入口,点击后先弹出“生成游戏音效 / 生成游戏背景音乐”选项框,再分别创建 `audio-sound-effect` 或 `audio-background-music` 生成器;生成结果作为 `mediaType="audio"` 的画布音频卡保存,`assetKind` 分别为 `sound-effect` / `background-music`。音效请求字段固定映射 Vidu `prompt/model/duration`,模型固定 `audio1.0`、时长严格 `2-10` 秒;背景音乐请求字段固定映射 `gpt_description_prompt` 且 `make_instrumental=true`。
- 影响范围:图片画布生成工作流、前端 editorProjectClient、`shared-contracts`、`platform-audio`、`api-server` 编辑器音频 BFF、图片画布技术方案和音乐生成入口设计文档。
- 验证方式:运行编辑器生成入口 / 提交 / 音频图层相关前端测试,`platform-audio` 请求体测试,`shared-contracts` editor audio 序列化测试,`api-server` editor audio 归一化测试,并执行 `npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】画板音乐生成入口设计-2026-06-18.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`。
## 2026-06-17 图片画布生成占位统一避让落点
- 背景:图片画布的普通图片、规范、角色、图标、视频和 UI 设计图生成入口都会先在画布中新建“即将生成”的占位图;若各入口直接使用当前视口中心,容易压住已有图片或已有生成占位,Lovart 式连续创作体验不稳定。
- 决策:所有会新建画布生成占位的入口统一经过 `ImageCanvasGenerationPlacementModel` 计算落点。模型以当前视口世界中心为目标,避让所有未隐藏画布图层和 active / inactive generation dialog placeholder,按 32px 画布世界坐标间距外扩阻挡矩形,选择距离当前屏幕中心对应画板位置最近且不重叠的位置。选定后立即调用 `centerViewportOnPlacement(...)`,保持当前缩放比例不变,只平移画布 viewport,让屏幕中心移动到新占位中心。
- 影响范围:`/editor/canvas` 图片画布生成入口、`useImageCanvasGenerationWorkflow`、`ImageCanvasGenerationPlacementModel`、图片画布技术方案和 Lovart 生成类面板文档。
- 验证方式:运行 `npm run test -- src/components/image-editor/ImageCanvasGenerationPlacementModel.test.ts src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx`,并执行 `npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`。
## 2026-06-13 Pingora 低端口直连只通过显式 systemd drop-in 启用
- 背景:`genarrative-pingora-gateway.service` 默认以 `genarrative` 非 root 用户运行,shadow 阶段只监听本机高端口;如果正式评估让 Pingora 直接绑定公网 `80/443`,需要低端口绑定能力,但不能让 Server-Provision 或默认 service 自动改变接流边界。
@@ -104,17 +192,9 @@
- 背景:本仓库的 SpacetimeDB 接入已固定为 `server-rs + Axum + SpacetimeDB`,本地 skill 需要从上游 SpacetimeDB `skills/` 更新到 2.5 口径,同时避免继续维护当前项目不使用的 TypeScript server/client、C# 和 Unity 专用 skill。
- 决策:`.codex/skills/` 下只保留 `spacetimedb-cli`、`spacetimedb-concepts`、`spacetimedb-rust` 三个本地 SpacetimeDB skill;删除 `spacetimedb-typescript`、`spacetimedb-csharp`、`spacetimedb-unity`。前端 / Node 侧如需处理 SpacetimeDB 订阅或绑定,按当前生成绑定、项目代码和官方文档核对,不再依赖仓库内单独 TypeScript skill。
- 影响范围:`AGENTS.md` 的 SpacetimeDB skill 清单、`.codex/skills/` 本地 skill 维护范围、后续 SpacetimeDB 设计 / CLI / Rust module 开发协作口径。
- 验证方式:用上游 `clockworklabs/SpacetimeDB@master` 的 `skills/` 目录对照,运行本地 skill 校验、删除引用扫描、`git diff --check -- .codex/skills AGENTS.md docs/project-memory/shared-memory/decision-log.md` 和 `npm run check:encoding`。
- 验证方式:用上游 `clockworklabs/SpacetimeDB@master` 的 `skills/` 目录对照,运行本地 skill 校验、删除引用扫描、`git diff --check -- .codex/skills AGENTS.md .hermes/shared-memory/decision-log.md` 和 `npm run check:encoding`。
- 关联文档:`AGENTS.md`、`.codex/skills/spacetimedb-cli/SKILL.md`、`.codex/skills/spacetimedb-concepts/SKILL.md`、`.codex/skills/spacetimedb-rust/SKILL.md`。
## 2026-06-18 长期项目记忆固定读取 `docs/project-memory/`
- 背景:项目记忆已从仓库 `.hermes/` 迁移到 `docs/project-memory/`,但部分协作说明仍把 `.hermes/` 写成共享记忆或计划容器,容易让 Agent 和开发者回到旧路径。
- 决策:长期项目记忆、计划和 TODO 只从 `docs/project-memory/` 读取和维护;`.hermes/` 仅保存 Hermes 专用的仓库级 skills、plugins 和启用说明。`AGENTS.md` 的复杂任务阅读清单不再把 `.hermes/README.md` 放在项目记忆入口中,只有使用 Hermes 工具资源时才读取它。
- 影响范围:`AGENTS.md`、`.hermes/README.md`、`docs/project-memory/README.md`、`docs/project-memory/shared-memory/`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
- 验证方式:扫描 `AGENTS.md`、当前 `docs/` 与 `.hermes/README.md`,确认当前口径不再把 `.hermes/` 描述为长期项目记忆路径。
- 关联文档:`AGENTS.md`、`docs/project-memory/README.md`、`.hermes/README.md`。
## 2026-06-13 图片大图预览统一为黑底全屏查看器
- 背景:`CreativeImageInputPanel` 的参考图 / 主图预览曾使用白底 `UnifiedModal` 工具弹窗,移动端会透出原页面背景,且不能全屏查看、缩放或拖拽细节。
@@ -131,6 +211,14 @@
- 验证方式:生成页不出现“生成队列”区域;登录用户进入“我的”页且队列有 pending/running 或当前 job 为 queued/running/failed 时显示队列卡;退出登录或切换账号时不保留旧账号队列概览。前端验证运行 `npm run test -- src/components/platform-entry/PlatformEntryFlowShellImpl.test.ts src/components/unified-creation/UnifiedGenerationPage.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`。
## 2026-06-13 `/editor/agent` AI Web 工程编辑器采用静态沙箱预览 MVP
- 背景:`/editor/agent` 需要承载浏览器内类似 IDE 的 AI Web 工程编辑和实时预览能力,但 AI 生成工程的构建和运行不能进入 Genarrative 主站 JS 上下文、当前仓库源码目录或 api-server 进程。
- 决策:第一版采用“平台编辑器壳 `/editor/agent` + api-server 控制面 + 独立 `web-project-runner` worker + 独立 preview origin”的四层结构。MVP 只支持固定 React / Vite / TypeScript 静态模板、虚拟文件系统、结构化 AI patch、平台固定构建命令、独立 runner 静态构建和独立域 iframe 预览;明确不做 HMR、终端 shell、后端服务、任意端口代理、任意 npm 安装、AI 自定义 shell script 或主站同源预览。
- 影响范围:`/editor/agent` 前端入口、api-server Web project 控制面、Web project runtime job、runner 部署、preview gateway、artifact store、安全验收和后续作品化发布链路。
- 验证方式:Phase 0 必须先完成技术方案、威胁模型和验收清单;Phase 1 只能在路径校验、runner 资源限制、网络隔离、preview token、iframe/CSP、失败保留上一版预览和刷新恢复验收口径明确后进入编码。
- 关联文档:`docs/technical/【技术方案】浏览器内AIWeb工程沙箱预览方案-2026-06-13.md`、`docs/technical/【安全模型】AIWeb工程Runner与预览隔离威胁模型-2026-06-13.md`、`docs/technical/【测试用例】AIWeb工程静态预览MVP验收清单-2026-06-13.md`。
## 2026-06-12 外部生成 worker 扩展到跳一跳、拼消消和敲木鱼
- 背景:外部图片生成已从 HTTP 长请求迁到 `external_generation_job` 队列;跳一跳、拼消消和敲木鱼继续扩展时需要统一 job 粒度、前端等待展示和本地 / 生产验证口径。
@@ -1122,7 +1210,7 @@
## 2026-05-22 敲木鱼图片创作采用三图 image2 链路
- 背景:敲木鱼自定义题材只生成中央敲击物时,运行态缺少与新主题匹配的竖屏背景和主题化返回按钮;若直接让背景 prompt 自由发挥,又容易把敲击物或木槌画进背景里。
- 决策:敲木鱼 `compile-draft` / `regenerate-hit-object` 图片链路固定为三步 image2 edits。第一步调用 VectorEngine `/v1/images/edits` + `gpt-image-2`,以默认木鱼图作为结构和画风参考,用户上传参考图只作为同次请求的新主题参考,结合用户题材关键词或参考图主题生成 `1:1` 绿色背景主体图;`api-server` 先对这张绿幕图执行去绿背景处理并写回 `hitObjectAsset`。第二步必须以第一步抠图完成后的透明敲击物图作为参考,结合用户原始题材生成 `9:16` 背景环境图并写回 `backgroundAsset`,避免背景图继承绿幕或纯绿色画布。第三步必须以去绿后的敲击物主体图和背景环境图为参考,生成 `1:1` 绿色背景返回按钮图,服务端去绿后写回 `backButtonAsset`。三步 prompt 使用 PRD 中固定隐藏关键词,不追加额外 negative prompt;返回按钮只允许参考图约束圆形底色和箭头配色,不允许继承复杂造型、花纹、浮雕边、异形外框或装饰图案,主体视觉尺寸比当前模板再放大约 50%,并带主题色外描边;背景图不得包含敲击物本体或木槌互动物品,返回按钮图不得包含文字、数字、水印或额外 UI 面板。
- 决策:敲木鱼 `compile-draft` / `regenerate-hit-object` 图片链路固定为三步 image2 edits。第一步调用 VectorEngine `/v1/images/edits` + `gpt-image-2`,以默认木鱼图作为结构和画风参考,用户上传参考图只作为同次请求的新主题参考,结合用户题材关键词或参考图主题生成 `1:1` 单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景主体图;`api-server` 先对这张绿幕图执行去绿背景处理并写回 `hitObjectAsset`。第二步必须以第一步抠图完成后的透明敲击物图作为参考,结合用户原始题材生成 `9:16` 背景环境图并写回 `backgroundAsset`,避免背景图继承绿幕或纯绿色画布。第三步必须以去绿后的敲击物主体图和背景环境图为参考,生成 `1:1` 单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景返回按钮图,服务端去绿后写回 `backButtonAsset`。三步 prompt 使用 PRD 中固定隐藏关键词,不追加额外 negative prompt;返回按钮只允许参考图约束圆形底色和箭头配色,不允许继承复杂造型、花纹、浮雕边、异形外框或装饰图案,主体视觉尺寸比当前模板再放大约 50%,并带主题色外描边;背景图不得包含敲击物本体或木槌互动物品,返回按钮图不得包含文字、数字、水印或额外 UI 面板。
- 影响范围:`api-server` 木鱼图片生成编排、`wooden_fish_work_profile.background_asset_json`、`wooden_fish_work_profile.back_button_asset_json`、shared contracts、前端结果页 / 运行态背景与返回按钮展示、敲木鱼 PRD 和平台链路文档。
- 验证方式:执行 `cargo test -p api-server wooden_fish --manifest-path server-rs/Cargo.toml`、`cargo test -p spacetime-client wooden_fish --manifest-path server-rs/Cargo.toml`、`npm run spacetime:generate`、`npm run check:spacetime-schema`、`npm run typecheck`。
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
@@ -1212,7 +1300,7 @@
## 2026-05-22 抓大鹅素材生成改为关卡整图派生三图
- 背景:旧抓大鹅素材链路按物品 5x5 sheet、纯背景和独立容器图分开生产,难以保证背景、UI、容器和物品风格一致,也让结果页继续暴露背景 / 容器重生成入口。
- 决策:抓大鹅草稿生成先用 `gpt-image-2` 无参考图生成竖屏 `9:16` 完整关卡画面;关卡画面完成后,以它作为参考并发生成三张可运行资产:`1K 1:1` UI spritesheet、`1K 9:16` 关卡背景图、`2K 1:1` 物品 spritesheet。UI 与物品 spritesheet 都固定要求纯绿色绿幕背景,后端上传 OSS 前扣成真实透明 PNG。物品 spritesheet 固定 `10*10`,每行两种物品、每种五个形态。运行态和编辑器都按 alpha 连通域矩形检测解析 UI 和物品图集,不按固定像素坐标切图。
- 决策:抓大鹅草稿生成先用 `gpt-image-2` 无参考图生成竖屏 `9:16` 完整关卡画面;关卡画面完成后,以它作为参考并发生成三张可运行资产:`1K 1:1` UI spritesheet、`1K 9:16` 关卡背景图、`2K 1:1` 物品 spritesheet。UI 与物品 spritesheet 都固定要求单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景,后端上传 OSS 前扣成真实透明 PNG。物品 spritesheet 固定 `10*10`,每行两种物品、每种五个形态。运行态和编辑器都按 alpha 连通域矩形检测解析 UI 和物品图集,不按固定像素坐标切图。
- 兼容:新增字段继续存入现有 `generatedItemAssets[].backgroundAsset` / `generatedBackgroundAsset` JSON,不新增 SpacetimeDB schema 字段。历史 `containerImage*` 字段只作兼容;如果它与 `uiSpritesheetImage*` 同源,不得再作为运行态中心容器图。
- 影响范围:`server-rs/crates/api-server/src/match3d/*`、`server-rs/crates/shared-contracts/src/match3d_*`、`packages/shared/src/contracts/match3dWorks.ts`、`src/components/match3d-result/Match3DResultView.tsx`、`src/components/match3d-runtime/Match3DRuntimeShell.tsx`、`src/services/match3dSpritesheetParser.ts`。
- 验证方式:执行 `cargo test -p api-server match3d --manifest-path server-rs\Cargo.toml`、`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx src/components/match3d-runtime/Match3DRuntimeShell.test.tsx src/services/match3dSpritesheetParser.test.ts src/services/match3dGeneratedModelCache.test.ts`、`npm run typecheck`、`npm run check:encoding`。
@@ -1884,10 +1972,10 @@
- 验证方式:VN 定向前端测试、`npm run typecheck`、`npm run check:encoding`、`cargo test -p api-server visual_novel`、`cargo test -p api-server creation_agent_document_input`。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`。
## 2026-05-04 历史:曾在仓库 `.hermes/` 中建立团队共享记忆
## 2026-05-04 在仓库 `.hermes/` 中建立团队共享记忆
- 背景:团队有 3 名开发人员,均在各自本地安装 Hermes,并需要独立拉取仓库、修改代码、本地测试;团队希望形成共享的长期项目记忆。
- 决策:不共享个人 `~/.hermes`,当时先在 Genarrative 仓库内使用 `.hermes/` 保存可 Git 同步的团队共享记忆、计划和未来 skills;该路径已被 2026-06-18 的 `docs/project-memory/` 口径取代,当前不再作为长期项目记忆入口。
- 决策:不共享个人 `~/.hermes`,先在 Genarrative 仓库内使用 `.hermes/` 保存可 Git 同步的团队共享记忆、计划和未来 skills。
- 影响范围:`AGENTS.md`、`.hermes/README.md`、`docs/project-memory/shared-memory/`。
- 验证方式:任一开发者拉取仓库后,在项目根目录启动 Hermes,均可读取同一套 `docs/project-memory/shared-memory/` 文件。
- 关联文档:`.hermes/README.md`、`docs/project-memory/shared-memory/team-conventions.md`。
@@ -2379,3 +2467,127 @@
- 影响范围:`api-server` 资产计费包裹、钱包退款补偿、拼图首图后台生成、`spacetime-module` 拼图 task 表、`spacetime-client` bindings/facade、前端 API request id 复用和后端架构文档。
- 验证方式:`npm run spacetime:generate`、`npm run check:spacetime-schema`、`npm run check:spacetime-runtime-access`、`node scripts/check-server-rs-ddd-boundaries.mjs`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml wallet_refund_outbox`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml asset_operation`、`npm run test -- src/services/apiClient.test.ts`、`npm run check:encoding`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-11 图片画布编辑器作为独立画布工程接入
- 背景:网站需要新增 Lovart 风格图片画布编辑器能力,既要支持素材栏、平移缩放、工具模式、吸附线、元数据窗口和修改结果并排展示,也要能保存当前用户的画布视图、图层布局和资源元数据。
- 决策:主站新增 `/editor` 对应 `image-editor` 阶段,编辑器作为独立图片画布工程挂在平台壳下,并在创作 Tab 提供入口;工程与资源通过 `editor_project` / `editor_project_resource` 落到 SpacetimeDB,经 `spacetime-client` facade 和 `/api/editor/projects*` BFF 读写。图片生成 / 修改 provider、计费和真实任务进度暂不接入,本期修改结果允许使用 mock 生成资源,但必须按生成资源元数据形状保存。
- 影响范围:主站路由、平台创作入口、图片画布编辑器组件、editor project API client、`api-server` BFF、`spacetime-client` facade、`spacetime-module` 表 / procedure、后端数据契约文档和前端架构文档。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx`、`npm run test -- src/services/image-editor/editorProjectClient.test.ts`、`npm run typecheck`、`cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:spacetime-schema`、`npm run check:encoding`、`git diff --check`、headless Playwright smoke。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-14 图片画布素材库按账号级持久化
- 背景:图片画布需要 Lovart 式素材管理,素材不应只挂在单个 project 临时状态里;用户在任意项目上传的图片素材,都应作为账号素材库在其它项目中可见,同时画布自身的图层、视图和分组仍属于项目下的画布数据。
- 决策:新增 `editor_asset_folder` / `editor_asset` 作为账号级素材库表,以 `owner_user_id` 为归属;`editor_project` 继续承载工程元数据,`editor_canvas` 继续承载 project 下的画布视图和图层布局,`editor_project_resource` 继续承载具体画布资源引用。素材库 CRUD 统一经 `spacetime-client` facade 和 `/api/editor/assets*` BFF,前端只保留选择模式、框选、拖拽上传、图层打组和小地图拖拽等交互状态,不直接绕过后端持久化。
- 影响范围:`server-rs/crates/spacetime-module/src/editor_project_storage.rs`、`server-rs/crates/spacetime-client/src/editor_project.rs`、`server-rs/crates/api-server/src/editor_project.rs`、`src/services/image-editor/editorProjectClient.ts`、`src/components/image-editor/ImageCanvasEditorView.tsx`、后端数据契约文档和图片画布前端技术方案。
- 验证方式:`npm run spacetime:generate -- --rust-only`、`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx src/services/image-editor/editorProjectClient.test.ts`、`npm run typecheck`、`npm run check:spacetime-schema`、`npm run check:encoding`、`cargo check -p spacetime-client -p api-server --manifest-path server-rs/Cargo.toml`、`git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-15 图片画布角色图层新增动画生成入口
- 背景:图片画布已有角色形象图层标记 `assetKind="character"`,需要只对角色图片开放动画生成,不让普通素材误触发角色动画链路。
- 决策:角色动画入口只由画布图层 `assetKind="character"` 控制,在图片上方浮动工具条和右键菜单显示 `生成动画`;非角色图层不展示入口。点击后打开独立 `角色动画生成面板`,桌面端锚定到图片右侧,移动端按底部面板承接。前端固定提交 `seedance2.0-fast`、分辨率 / 比例 / 帧数 / 时长 / 价格字段;后端经 `/api/editor/character-animations/generations` 使用角色图作为首帧和尾帧生成视频,并立即抽取 32 / 40 / 48 帧、绿幕去背后写入 OSS。
- 影响范围:`src/components/image-editor/ImageCanvasEditorView.tsx`、`src/services/image-editor/editorProjectClient.ts`、`server-rs/crates/api-server/src/character_animation_assets.rs`、`server-rs/crates/shared-contracts/src/assets.rs`、图片画布技术方案。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx src/services/image-editor/editorProjectClient.test.ts`、`cargo test -p api-server editor_character_animation --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-16 图片画布图标素材面板采用 Lovart 式参考卡与横向增宽布局
- 背景:图标素材生成面板里,规范入口与素材描述项过于平铺,且子面板内部采用滑动列表,和 Lovart 风格画布的参考卡 / 物料卡不一致。
- 决策:`生成图标素材` 面板不使用内部纵向滚动列表;每新增一个素材描述项就让面板整体增宽,保持描述项横向卡片一眼可扫。图标规范入口改为 Lovart 式参考卡:缩略图、名称、绑定状态和轻量动作分区分开呈现,独立菜单只负责来源切换,不再承载说明文案。
- 影响范围:`src/components/image-editor/ImageCanvasEditorView.tsx`、`src/index.css`、图标素材生成专项设计文档。
- 验证方式:新增或增删素材描述项时,面板宽度应随项数变化;图标规范入口应呈现参考卡视觉而非纯文本按钮;移动端下仍应固定在底部锚定,不出现内部滚动条。
- 关联文档:`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-16 图片画布图标素材与角色生成支持双图片模型
- 背景:图片画布需要一次生成多枚 UI 图标素材,并保证生成后能按用户输入顺序命名、拆成独立透明素材铺回画布;角色形象生成也需要和图标素材共用同一套图片模型选择、比例和大小口径。
- 决策:底部 `生成图标素材` 入口创建一叠空白图标占位和独立面板;图标规范参考图只允许绑定 `assetKind="icon-spec"`。`生成角色形象` 与 `生成图标素材` 均支持 VectorEngine `gemini-3.1-flash-image-preview`(UI 显示 `nanobanana2`)和 `gpt-image-2`,默认 `nanobanana2`,用户在两类面板中切换过模型后下一次打开继续沿用上次模型。前端提交 `model`、`aspectRatio`、`imageSize`;后端不再按图标数量分 `512x512/1024x1024`,而是按模型归一尺寸:`nanobanana2` 走 `/v1beta/models/{model}:generateContent`,把参考图写成 `inline_data`,并在 `generationConfig.imageConfig` 写入比例和大小,`0.5K` 传 `"512"`;`gpt-image-2` 无参考图走 generations,有参考图走 edits,按文档支持的 `size` 字符串映射。图标素材仍先生成绿幕 spritesheet,再由 `platform-image` 绿幕去背并按 8 邻域连通域从上到下、从左到右拆分。成品图标图层写入 `assetKind="icon"`,角色图层写入 `assetKind="character"`。
- 影响范围:`src/components/image-editor/ImageCanvasEditorView.tsx`、`src/components/image-editor/useImageCanvasGenerationWorkflow.ts`、`src/services/image-editor/editorProjectClient.ts`、`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/platform-image/src/vector_engine/*`、`server-rs/crates/platform-image/src/generated_asset_sheets/sheet.rs`、图片画布技术方案。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/ImageCanvasGenerationPlacementModel.test.ts src/services/image-editor/editorProjectClient.test.ts`、`cargo test -p api-server editor_generation_dimensions_follow_model_options --manifest-path server-rs/Cargo.toml`、`cargo test -p platform-image nanobanana_generate_content --manifest-path server-rs/Cargo.toml`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-16 图片画布生成面板与浮层层级收口
- 背景:图片画布底部工具栏和角色参考图行都存在局部滚动 / 裁切容器,生成规范菜单和角色规范来源菜单如果仍内嵌在触发按钮附近,会被边界遮挡;同时生成类面板打开后隐藏底部工具栏会破坏连续创作节奏。
- 决策:`生成规范`、`角色规范来源` 和 `图标规范来源` 菜单统一通过页面级 fixed portal 渲染到 `document.body`,触发按钮只提供定位锚点;点击 `生成工具`、`生成角色形象` 或 `生成图标素材` 后底部 AI 工具栏保持可见。点击画布空白区域只关闭当前生成面板并清除图片选中样式,不删除新建的占位图。角色面板中的 `角色规范` 与 `上传常规参考图` 入口统一改为 Lovart 式参考图卡片。
- 影响范围:`src/components/image-editor/ImageCanvasEditorView.tsx`、`src/index.css`、`src/components/image-editor/ImageCanvasEditorView.test.tsx`、图片画布前端技术方案和角色形象生成设计文档。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-16 图片画布图片信息页不展示生图 Prompt
- 背景:图片画布中每张生成图片的信息页原来展示 `Prompt` 和复制 Prompt,但该字段可能是后端组装后的生图提示词,不适合作为用户可见的图片输入信息。
- 决策:图片信息页删除生图 Prompt 展示和复制入口,改为展示生成时的用户面板输入快照,包括普通生成提示词、规范表单字段、角色设定、图标素材描述、修改要求,以及角色规范、常规参考图、图标规范和修改参考图等参考图卡片。旧数据或上传图片没有输入快照时显示 `-`,不得回退展示内部 Prompt。
- 影响范围:`src/components/image-editor/ImageCanvasEditorView.tsx`、图片画布 layout snapshot、图片画布技术方案。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx` 应覆盖图片信息页无 `Prompt`、无 `复制Prompt`,并展示普通生成、角色生成、图标素材和修改结果的输入快照。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-16 图片画布按 Resolution 原分辨率显示
- 背景:图片画布图层曾同时维护展示 `Size` 与资源 `Resolution`,旧布局快照里的 `width/height` 可能把大图缩成小图,导致画布视觉和图片信息里的原始分辨率不一致。
- 决策:图片图层不再把独立 `Size` 作为用户可见字段或展示真相;画布图层渲染宽高、悬浮尺寸胶囊和图片信息页统一以 `originalWidth/originalHeight`(即 `Resolution`)为准。旧 layout 中的 `width/height` 只作为缺少 Resolution 时的兼容兜底,不再优先决定展示大小。
- 影响范围:`src/components/image-editor/ImageCanvasEditorView.tsx`、图片画布 layout hydrate、新建 / 上传 / 生成 / 快速编辑 / 图标素材生成结果铺回画布逻辑,以及图片画布技术方案。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "hydrates canvas images from Resolution instead of saved Size|opens generated image info from the corner button and creates a real right-side edit result|shows image resolution on hover"`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-19 图片画布上传入口按来源区分入画布语义
- 背景:底部工具栏上传入口选择文件后只进入素材库,未创建画布图层;同时上传图层和素材记录先使用固定兜底尺寸,浏览器异步读到图片原始尺寸前已经把错误尺寸持久化。
- 决策:底部工具栏上传是“上传到画布”入口,选中文件后写入默认素材文件夹并立即在当前画布视口中心创建图层;素材栏文件夹内上传仍只写入目标素材文件夹。上传图片必须在创建占位素材、画布图层和账号级素材记录前解析原图 Resolution,PNG/JPEG/WebP/GIF/BMP 先读文件头,无法解析时才回退浏览器解码或上传兜底尺寸。
- 影响范围:`src/components/image-editor/ImageCanvasEditorView.tsx`、`src/components/image-editor/useImageCanvasUploadWorkflow.ts`、`src/components/image-editor/ImageCanvasFileModel.ts`、`src/components/image-editor/ImageCanvasUploadModel.ts` 和图片画布技术方案。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasFileModel.test.ts src/components/image-editor/ImageCanvasUploadModel.test.ts src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx`、`npm run test -- src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx -t "bottom toolbar uploads|multiple files as account-level assets"`、`npm run typecheck`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-17 图片画布底部生成视频接入 Lovart 面板
- 背景:编辑器画板底部工具栏需要新增 `生成视频`,并和现有 Lovart 式生成类面板、泥点展示、占位图和画布结果图层保持一致。
- 决策:`生成视频` 点击后创建独立视频生成占位和极简面板,提交 `POST /api/editor/videos/generations`;首期前端仅开放 `seedance2.0-fast`、`seedance2.0`、`kling3.0`、`kling3.0-omni`,不展示 Veo 模型入口,默认 `seedance2.0-fast`。后端必须严格区分 Seedance 2.0 Fast 与标准版:`seedance2.0-fast` 映射 `doubao-seedance-2-0-fast-260128`,`seedance2.0` 映射 `doubao-seedance-2-0-260128`,不得混用;固定文字转视频、`16:9`、标准模式和静音。后端复用 Ark / VectorEngine content generation task 轮询链路,下载视频后持久化到 OSS。生成结果在画布中写入 `mediaType="video"` 与 `assetKind="video"`,图片信息弹窗按视频显示为 `视频信息` / `视频类型`。生成类泥点价格统一走 `editor_generation_config`:角色动画固定 `seedance2.0-fast` 仍为 480p 每秒 10 / 720p 每秒 20;生成视频按模型分档,`seedance2.0-fast` 为 10 / 20,`seedance2.0` 为 12 / 24,`kling3.0` 为 15 / 30,`kling3.0-omni` 为 20 / 40,Veo 旧布局兼容价为 10 / 20。
- 影响范围:图片画布生成工作流、前端 editorProjectClient、`shared-contracts`、`api-server` 视频生成 BFF、编辑器技术方案和生成类面板方案。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "opens the bottom generate video panel"`、`npm run test -- src/components/image-editor/ImageCanvasMetadataModalView.test.tsx`、`npm run test -- src/services/image-editor/editorProjectClient.test.ts`、`cargo test -p shared-contracts editor_video --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server editor_video --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-17 图片画布生成器快照纳入画布布局
- 背景:生成占位图和生成器对话框里包含用户输入、参数、参考图、占位框位置和生成结果绑定,刷新后丢失会让已生成图片无法回到 Lovart 式跟随编辑状态。
- 决策:生成器对象统一作为 `editor_canvas` 布局 JSON 的 `itemType: "generation-dialog"` 项保存,不新增表;成功生成后仍保留生成器快照和最后占位框位置,并通过 `generatedLayerId` 锚定到成品图层,渲染时不重复显示灰色占位框。图片类生成结果同步写入账号级素材库;视频结果当前只作为画布视频资源保存。
- 影响范围:图片画布 layout 序列化 / hydrate、生成工作流、生成器渲染、项目自动保存、素材库回填和编辑器技术方案。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorModel.test.ts src/components/image-editor/useCanvasGenerationDialogs.test.tsx src/components/image-editor/useImageCanvasProjectPersistence.test.tsx src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx src/components/image-editor/ImageCanvasWorldView.test.tsx src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`、浏览器刷新 smoke。
- 关联文档:`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-18 编辑器画板音效待生成占位类型化
- 背景:`/editor/canvas` 新建视频、角色形象、音效和背景音乐待生成对象时沿用图片占位 icon,音效面板仍把 `type` 与 `tempo` 分成两个旧字符串选项,不符合 Lovart 式简洁参数按钮和 BPM 输入需求。
- 决策:画布待生成占位按生成器模式渲染专属空白样式、icon 与右上角标签:视频、角色、音效、背景音乐不再统一使用图片 icon;角标继续按 viewport 反向缩放。原计划中的 `type + tempo/BPM` 音效参数已在 2026-06-19 被 Vidu `prompt + duration` 契约替代,后续不要再恢复 `单次·120BPM` 入口。
- 影响范围:`src/components/image-editor/ImageCanvasWorldView.tsx`、`ImageCanvasGenerationComposerView.tsx`、`ImageCanvasEditorTypes.ts`、`ImageCanvasGenerationSubmissionModel.ts`、`src/services/image-editor/editorProjectClient.ts`、`server-rs/crates/shared-contracts/src/assets.rs`、`server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs`、`server-rs/crates/platform-audio/src/request.rs`。
- 验证方式:`npx vitest run src/components/image-editor/ImageCanvasWorldView.test.tsx src/components/image-editor/ImageCanvasGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasGenerationDialogModel.test.ts src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/components/image-editor/ImageCanvasEditorModel.test.ts src/components/image-editor/ImageCanvasGenerationModel.test.ts src/services/image-editor/editorProjectClient.test.ts --reporter verbose`、`cargo test -p shared-contracts --manifest-path server-rs/Cargo.toml`、`cargo test -p platform-audio --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server editor_sound_effect --manifest-path server-rs/Cargo.toml`。
## 2026-06-18 图片画布角色动画改为角色动作生成占位
- 背景:旧角色动画入口点击后打开窄侧边面板,和 Lovart 式新建图片 / 视频占位不一致;点击生成好的角色图时也容易被误解为会自动进入重绘或生成面板。
- 决策:点击已生成角色图只选中图层并显示浮动工具栏,不自动弹出重绘、快速编辑或角色动画面板。点击工具栏或右键菜单的 `生成动画` 后,创建 `mode="character-animation"` 的画布 generation dialog,占位走统一避让落点与视口居中;占位使用角色动作 icon、橙色动作配色和右上角 `动作` 标签。角色动画参数面板复用原内容,但作为统一 generation composer 跟随占位底部,宽度对齐图片生成面板。提交成功后以首帧创建 `assetKind="character-animation"` 的角色动作图片图层,右上角标签显示 `动作`。
- 影响范围:`src/components/image-editor/useImageCanvasGenerationWorkflow.ts`、`useImageCanvasGenerationSurface.tsx`、`ImageCanvasCharacterAnimationPanelView.tsx`、`ImageCanvasWorldView.tsx`、`ImageCanvasGenerationLayerModel.ts`、`useImageCanvasGenerationSubmissionWorkflow.ts`、`src/index.css`。
- 验证方式:`npx vitest run src/components/image-editor/ImageCanvasWorldView.test.tsx src/components/image-editor/ImageCanvasGenerationDialogModel.test.ts src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx --reporter=dot`、`npx vitest run src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx -t "character animation" --reporter=dot`。
## 2026-06-19 编辑器游戏音效默认改用 Vidu 文生音频
- 背景:VectorEngine Apifox `创建文生音频任务` 文档明确 Vidu `/ent/v2/text2audio` 请求体使用 `model: "audio1.0"`、`prompt`、`duration` 和可选 `seed`;编辑器此前把游戏音效提交到 Suno `task: "sound"`,与当前游戏音效默认模型要求不一致。
- 决策:`/editor/canvas` 的 `生成游戏音效` 入口继续保留,但默认且暂时唯一可用模型为 Vidu `audio1.0`,前端请求固定发送 `prompt`、`model: "audio1.0"`、`duration` 和 `priceMudPoints`,面板只显示 `Vidu` 与 `2-10` 秒时长选项,默认 `5` 秒;不再展示 `type`、`tempo`、BPM 或 Suno 文生音效入口。后端 `/api/editor/audios/sound-effects/generations` 只接受空模型或 `audio1.0`,拒绝 Suno / `chirp-*`;提交到 VectorEngine 时对内 `prompt` 同步映射为上游 body 的 `prompt` 与 `sound`,兼容 Apifox 文档和线上网关实际 `missing field sound` 校验;提交和轮询改走 Vidu `/ent/v2/text2audio` 与 `/ent/v2/tasks/{taskId}/creations`。背景音乐仍保留 Suno `/suno/submit/music`、`/suno/fetch/{taskId}` 和 wav clip 兜底逻辑。
- 影响范围:`server-rs/crates/platform-audio`、`server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs`、`server-rs/crates/shared-contracts/src/assets.rs`、`src/services/image-editor/editorProjectClient.ts`、`src/components/image-editor/ImageCanvasGeneration*`。
- 验证方式:`cargo test -p platform-audio --manifest-path server-rs/Cargo.toml --test vector_engine_audio`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml editor_sound_effect`、`cargo test -p shared-contracts --manifest-path server-rs/Cargo.toml editor_audio_requests_and_response_use_canvas_audio_shape`、`npx vitest run src/services/image-editor/editorProjectClient.test.ts src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/components/image-editor/ImageCanvasGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasGenerationModel.test.ts --reporter=dot`。
## 2026-06-19 角色主图抠图补充内部镂空检测
- 背景:编辑器角色形象和 Big Fish 正式图复用 `character_visual_assets::try_apply_background_alpha_to_png` 的角色主图透明背景后处理;原算法主要从画布四边扩散清理绿幕 / 近白背景,对主体包围的内部绿幕或近白镂空不稳定。
- 决策:角色主图抠图继续保留在 `api-server` 的 `character_visual_assets` 口径内,不切换到 `platform-image::generated_asset_sheets`。在边缘连通背景 BFS 之后、软边扩展和边缘去污染之前,新增内部背景连通域检测:只清理不触画布边界、像素数达到阈值、且为高置信绿幕 / 近白背景的内部连通域,避免误伤普通角色纹理。
- 影响范围:`server-rs/crates/api-server/src/character_visual_assets.rs`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`。
- 验证方式:`cargo test -p api-server --manifest-path server-rs/Cargo.toml character_background_alpha_removes_internal_green_holes`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml editor_character_image_postprocess`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 编辑器角色形象回填改用通用抠图
- 背景:画板编辑器里的 `生成角色形象` 属于编辑器图片生成链路,用户要求把“人物抠图”改成通用抠图方法,不再与 RPG / 资产工坊角色主图专用后处理绑定。
- 决策:仅 `/api/editor/images/generations` 中 `kind = "character"` 的编辑器角色形象回填改用 `platform-image::generated_asset_sheets` 通用绿幕 / 近白去背能力,并开启内部镂空检测;输出仍归一为透明 PNG。`character_visual_assets::try_apply_background_alpha_to_png` 继续服务 RPG 角色主图与 Big Fish 等“角色主图口径”调用者,本轮不改变这些链路。
- 2026-06-22 补充:所有明确设置绿幕用于后续抠图的 prompt 都必须固定写明 `#00FF00 / RGB(0,255,0)`,不能只写“纯绿色绿幕”或“接近 #00FF00”;编辑器角色图通用抠图额外开启暗绿 / 灰绿绿幕背景识别,只作为生成模型偏离标准亮绿时的兜底。该宽松识别只参与从画布边缘连通扩散出的背景清理,不参与全图断开绿色区域删除,避免误伤角色衣物或纹理。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`。
- 验证方式:`cargo test -p api-server --manifest-path server-rs/Cargo.toml editor_character_image_general_cutout`、`cargo test -p platform-image --manifest-path server-rs/Cargo.toml generated_asset_sheet_muted_green_alpha_requires_explicit_option`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`、`git diff --check`。
+151 -7
View File
@@ -23,6 +23,142 @@
- 验证:运行 `npm run check:production-ops`、`npm run check:production-api-release` 和 `npm run check:production-api-deploy`,确认构建包、Jenkins 归档链路和 deploy fail-fast 检查口径一致。
- 关联:`jenkins/Jenkinsfile.production-api-build`、`jenkins/Jenkinsfile.production-api-deploy`、`scripts/deploy/production-api-deploy.sh`、`scripts/check-production-ops-guardrails.mjs`。
## 图片画布角色动作结果不能把首帧当主媒体
- 现象:画板里 `生成角色动作` 返回后显示成一张不可播放图片,点击下载或素材 ZIP 导出时拿到的也是 PNG,而不是动作视频。
- 原因:后端已经生成 `previewVideoPath`,并继续执行抽帧、绿幕去背和帧素材 OSS 落盘;前端落层时却把 `frames[0].imageSrc` 当作图层主 `src`,且没有设置 `mediaType: "video"`,导致预览和导出都按图片处理。
- 处理:角色动作结果图层主 `src` 必须使用 `previewVideoPath`,`mediaType` 固定为 `video`,`assetKind` 固定为 `character-animation`;首帧透明 PNG 只写入 `thumbnailSrc`,用于 `<video poster>` 和后续动作素材二次生成源。不得移除后端原有视频生成、抽帧、绿幕去背和帧落盘流程。
- 验证:`ImageCanvasGenerationLayerModel` 应断言动作结果 `src` 为预览视频且 `thumbnailSrc` 为首帧;画布集成测试应出现 `<video controls poster=...>`;导出模型应按 `mediaType="video"` 输出 mp4/webm/mov 等真实视频扩展名。
- 关联:`src/components/image-editor/ImageCanvasGenerationLayerModel.ts`、`src/components/image-editor/ImageCanvasWorldView.tsx`、`src/components/image-editor/ImageCanvasExportModel.ts`、`server-rs/crates/api-server/src/character_animation_assets.rs`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`。
## Vidu 文生音频线上网关可能要求 sound 字段
- 现象:画板点击 `生成游戏音效` 后,请求返回 `Failed to deserialize the JSON body into the target type: missing field sound`。
- 原因:VectorEngine Apifox `创建文生音频任务` 文档仍写 `/ent/v2/text2audio` 使用 `model + prompt + duration`,但线上 Vidu 网关曾按 `sound` 字段反序列化;只发送 `prompt` 会被上游拦截在 JSON 解析阶段。
- 处理:前端和 BFF 对内继续使用用户语义更清晰的 `prompt`;`platform-audio` 转发到 VectorEngine Vidu 时同时发送 `prompt` 与 `sound`,两者值保持一致。不要把 UI 改回 Suno `task: "sound"`、`type`、`tempo` 或 BPM。
- 验证:`cargo test -p platform-audio --manifest-path server-rs/Cargo.toml --test vector_engine_audio` 中音效请求体测试必须同时断言 `prompt` 与 `sound`;必要时用线上生成音效 smoke 确认不再出现 `missing field sound`。
- 关联:`server-rs/crates/platform-audio/src/request.rs`、`server-rs/crates/platform-audio/tests/vector_engine_audio.rs`、`docs/【编辑器】画板音乐生成入口设计-2026-06-18.md`。
## Suno 任务完成不代表已经拿到 wav 下载地址
- 现象:画板生成背景音乐时,前端报 `音频生成尚未返回可下载地址(requestId:...)`;画板生成音效时,前端可能报 `获取 Suno 音效 wav 失败(requestId:...)`。上游任务可能已经完成,但 wav 下载地址还没就绪。
- 原因:VectorEngine Suno `/suno/fetch/{task_id}` 可能先在 `data` 中返回歌曲 / 音效 clip id,而不是直接返回 `.wav` / `.mp3` URL;需要再调用 `/suno/act/wav/{clipId}` 获取 `wav_file_url`。如果只兼容 `data` 是字符串,会漏掉 `data` 对象 / 数组里的 `id`、`clip_id`、`audioId` 或 `songId`。
- 处理:`platform-audio` 查询 Suno 结果时先提取直接音频 URL;没有 URL 时,从 `data` 字符串、对象或数组提取 clip id,逐个调用 `/suno/act/wav/{clipId}`。已拿到 clip id 但 wav 地址仍未就绪,或 wav 子请求暂时返回上游错误时,都保持 `processing` 让上层继续轮询,不能直接判定为缺少可下载地址或 wav 获取失败。
- 验证:`cargo test -p platform-audio --manifest-path server-rs/Cargo.toml`;`cargo test -p api-server vector_engine_audio_generation --manifest-path server-rs/Cargo.toml`。
- 关联:`server-rs/crates/platform-audio/src/client.rs`、`server-rs/crates/platform-audio/src/response.rs`、`docs/【编辑器】画板音乐生成入口设计-2026-06-18.md`。
## 图片画布音频卡播放条 0:00 要优先查签名 URL 和嵌套交互
- 现象:画板音效或背景音乐已经生成成功,但卡片里的播放条显示 `0:00`,点击无法预览。
- 原因:generated 音频资源通常是私有 OSS 路径,直接把 `/generated-*` 或 generated OSS 地址交给 `<audio>` 会无鉴权读取失败;如果音频控件嵌在 `<button>` 图层里,浏览器还可能因嵌套交互元素阻断 controls 行为。
- 处理:音频图层使用非嵌套交互容器承接画布选择语义,内部 `<audio controls preload="metadata">` 单独阻止 pointer / click 冒泡;generated 音频播放前统一通过 `useResolvedAssetReadUrl` / `/api/assets/read-url` 换签。卡片和角标展示 `时长`,后端没返回时长时可用 `loadedmetadata.duration` 兜底。
- 验证:`npx vitest run src/components/image-editor/ImageCanvasWorldView.test.tsx src/components/image-editor/ImageCanvasMetadataModalView.test.tsx src/components/image-editor/ImageCanvasGenerationLayerModel.test.ts --reporter verbose`,并在浏览器确认 generated 音频控件可播放。
- 关联:`src/components/image-editor/ImageCanvasWorldView.tsx`、`src/components/image-editor/ImageCanvasMediaModel.ts`、`docs/【编辑器】画板音乐生成入口设计-2026-06-18.md`。
## 图片编辑器底部生成按钮不要复用单一画布生成状态
- 现象:图片画布里先新建一个“生成规范”占位,再点击“生成角色形象”或其它底部生成入口,前一个规范占位和面板状态被销毁。
- 原因:底部普通生成、规范、角色和图标素材曾共用单个 `generateDialog` 状态;后一次点击直接覆盖该状态,等同把前一个画布生成对象卸载。
- 处理:底部生成类入口每次点击都创建独立 generation dialog id;当前 active 对象只负责显示编辑面板,旧对象归档为 inactive 后仍保留占位和生成逻辑状态。生成完成 / 失败回写、生成中拖拽和删除都必须按 dialog id 读取 active + inactive 中的最新对象,不能回退到提交瞬间的旧占位快照。
- 验证:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "keeps existing generation placeholders"` 应断言规范占位和角色占位可同时存在;`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "keeps archived generation logic"` 应断言旧对象归档后拖动,占位完成回写仍落在最新位置。
- 关联:`src/components/image-editor/ImageCanvasEditorView.tsx`、`src/components/image-editor/ImageCanvasEditorView.test.tsx`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 图片编辑器生成中设定面板不要和预览框绑成同一可见性
- 现象:图片编辑器里点击生成后,有时设定面板没收起,有时连画布上的占位预览一起消失,看起来像“生成中界面掉了”。
- 原因:生成中状态只收了 composer 可见性,或把占位框和设定面板共用了同一段条件渲染;面板隐藏后把 placeholder 也一起卸掉,就会丢掉 Lovart 式生成中预览。
- 处理:进入 `generating` 后只隐藏设定面板,保留占位框和生成中状态胶囊;面板外观、预览框和结果图层分开控制,不共用同一个 `composerOpen` 条件。
- 验证:对应测试应断言生成按钮点击后 `dialog` 消失但 `image-canvas-editor__generation-frame--generating` 仍然存在。
- 关联:`src/components/image-editor/ImageCanvasEditorView.tsx`、`src/components/image-editor/ImageCanvasEditorView.test.tsx`。
## 图片编辑器宣发素材生成器刷新后不要丢快照
- 现象:图片画布刷新后,宣发素材生成卡片消失,或卡片仍在但游戏名、分类、描述和参考图丢失。
- 原因:画布布局把生成器保存为 `itemType: "generation-dialog"`,但恢复白名单漏掉 `publication` 模式和 `publicationWorkflowId` / `publicationGameInfo` / `publicationReferences` 字段,导致整条生成器快照被当成无效布局项丢弃。
- 处理:`hydrateCanvasGenerationDialog` 必须把 `publication` 视为正式画布生成器模式,并显式恢复宣发素材专属字段;组件层应断言刷新回读项目快照后仍显示卡片类型、字段和参考图。
- 验证:`npm run test -- src/components/image-editor/ImageCanvasEditorModel.test.ts src/components/image-editor/ImageCanvasGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx --reporter verbose`。
- 关联:`src/components/image-editor/ImageCanvasEditorModel.ts`、`src/components/image-editor/ImageCanvasPublicationMaterialsDemoPanelView.tsx`、`src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx`。
## 图片画布快速编辑不要直接提交普通图片 URL
- 现象:图片画布快速编辑站内示例图、历史 generated 图或 OSS generated 图时,后端返回 `修改图片参考图必须是图片 Data URL。`。
- 原因:快速编辑直接把图层 `src` 塞进 `/api/editor/images/generations` 的 `referenceImageSrcs`;默认示例图和部分持久化图层的 `src` 是 `/creation-type-references/*.webp`、`/generated-*` 或 OSS URL,而 `api-server` 的编辑参考图解析只接收 `data:image/*;base64,...`。
- 处理:前端统一通过 `resolveEditorImageReferenceDataUrl(...)` 在提交前读取图片字节并转成图片 Data URL;Data URL 原样透传,`/generated-*` 和 generated OSS URL 走 `/api/assets/read-bytes` 避免 CORS,普通 public 路径直接 fetch。
- 验证:`npm run test -- src/services/image-editor/editorImageReference.test.ts src/components/image-editor/ImageCanvasEditorView.test.tsx -t "editorImageReference|converts non-data-url quick edit source images before submitting references"`。
- 关联:`src/services/image-editor/editorImageReference.ts`、`src/components/image-editor/ImageCanvasEditorView.tsx`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 图片编辑器角色动画不要默认提交大图 Data URL
- 现象:图片编辑器里对角色图点击 `生成动画` 后,后端返回 `Failed to buffer the request body: length limit exceeded`,请求还没进入角色动画 handler。
- 原因:角色动画生成请求曾把角色图片 `src` 原样作为 `sourceImageSrc` 放进 JSON;角色图如果是较大的 Data URL,会超过 Axum 默认 `2MB` body limit,在 `Json` 提取器阶段被拦截。
- 处理:前端在角色图已持久化时优先提交 `objectKey`,只把 Data URL 作为未持久化本地临时图兜底;后端 `/api/editor/character-animations/generations` 单独配置 `12MB` body limit 兼容旧请求,但新链路不应依赖传大图 JSON。
- 验证:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "only exposes character animation"`;`cargo test -p api-server editor_character_animation_accepts_character_image_body_above_default_limit --manifest-path server-rs/Cargo.toml`。
- 关联:`src/components/image-editor/ImageCanvasEditorView.tsx`、`server-rs/crates/api-server/src/modules/play_flow.rs`、`server-rs/crates/api-server/src/app.rs`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`。
## 图片编辑器角色动画抽帧不要采到视频尾点
- 现象:画板角色图点击 `生成动画` 后,Ark 视频已生成并上传 OSS,但后端返回 `ffmpeg 已执行但未产出动作帧文件(requestId:...)`。
- 原因:FFmpeg 在 `-ss` 采样时间落到视频尾点附近时可能退出码仍为 `0`,但实际输出 `0` 帧;如果后端按 `duration - 0.001` 抽最后一帧,低帧率或短视频很容易踩到不可解码尾点。
- 处理:角色动画抽帧按目标帧数预留一个采样步长,例如 `32帧·4秒` 最后一帧采 `3.875s`,不要采 `3.999s`;`ffmpeg` 返回成功但无输出文件时,错误 details 保留 `targetSeconds`、`stdout`、`stderr` 和输出路径,用户主文案保持简短。
- 验证:`cargo test -p api-server editor_character_animation --manifest-path server-rs/Cargo.toml`,其中 `editor_character_animation_extracts_final_sample_from_short_video` 应覆盖本机 FFmpeg 8 的 0 帧回归。
- 关联:`server-rs/crates/api-server/src/character_animation_assets.rs`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`。
## 图片编辑器生成长请求完成态必须由后端写入画布
- 现象:画板角色形象等生成请求已经在服务端返回 `200`,OSS 中也已有 `generated-character-drafts/.../image.png`,但用户刷新或页面重载后仍看到旧生成卡片停在“生成中”。
- 原因:生成是一次长 HTTP 请求,浏览器在请求完成前刷新或重新挂载时会丢失原页面的成功回调;如果完成态只靠前端回调把结果图层写回 `editor_canvas.layers_json`,服务端虽然已经创建 `editor_project_resource` / `editor_asset`,但布局里的 `generation-dialog` 仍可能停在 `status="generating"` 且没有 `generatedLayerId`。如果之后从素材库把同一私有素材加回画布,前端再次创建项目资源时若提交 signed URL / Data URL,还会触发 `413`,进一步阻断资源行绑定。
- 处理:图片生成提交必须在有项目上下文时携带 `canvasCompletion`(生成器 `dialogId`、标题和占位框);`api-server` 生成成功并创建资源后,直接读取当前项目布局,只有当前布局仍存在对应生成器时才插入轻量结果图层、把生成器改回 `idle` 并写入 `generatedLayerId`,再沿用后端当前 viewport 保存 layout 并返回最新项目快照。前端只应用该快照刷新显示,不在加载时根据资源行推断完成态;有项目上下文但后端没有返回快照时也不得本地补结果图层。为已有 `objectKey` 的图层创建项目资源时,`imageSrc` 只提交 `/<objectKey>`,不要提交 signed URL / Data URL。
- 验证:`cargo test -p api-server editor_canvas_generation_completion --manifest-path server-rs/Cargo.toml` 覆盖后端完成态写 layout;`npm run test -- src/components/image-editor/ImageCanvasEditorModel.test.ts src/components/image-editor/useImageCanvasProjectPersistence.test.tsx src/services/image-editor/editorProjectClient.test.ts src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx -- --runInBand` 覆盖前端提交 `canvasCompletion`、应用后端快照、项目加载不推断完成态和 `objectKey` 资源创建不提交大 URL。
- 关联:`server-rs/crates/api-server/src/editor_project.rs`、`src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.ts`、`src/components/image-editor/useImageCanvasProjectPersistence.ts`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 图片编辑器 Seedance 2.0 参考媒体不要提交视频 Data URL
- 现象:画板生成视频选择 Seedance 2.0 并上传参考视频后,请求体暴涨、可能返回 `413` 或上游拒绝 `video_url.url`;文档示例或测试如果写 `data:video/mp4;base64,...`,后续实现很容易照抄。
- 原因:火山 Seedance 2.0 参考视频只支持公网 URL 或 `asset://` 素材 ID,项目内画板资源应以 `objectKey` 由后端换签;视频不支持 Base64 / `data:video`,且 50MB 视频转 Base64 后会逼近或超过 64MB 请求体上限。参考音频虽然支持 Base64,但也不能单独输入,且大文件同样不应塞进 JSON。
- 处理:参考视频 / 音频上传先走 `/api/assets/direct-upload-tickets` 直传 OSS,再 `/api/assets/objects/confirm` 确认;前端保留 signed URL 做预览,提交生成时优先使用 `objectKey`。后端归一化必须拒绝 `data:video/*`,非 Seedance 模型携带参考字段也必须拒绝;Ark body 显式带 `generate_audio:false`。
- 验证:`npx vitest run src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/services/image-editor/editorReferenceUploadClient.test.ts --reporter verbose`;`cargo test -p api-server editor_video --manifest-path server-rs/Cargo.toml`;`cargo test -p shared-contracts editor_video_request_supports_seedance_multimodal_references --manifest-path server-rs/Cargo.toml`。
- 关联:`src/services/image-editor/editorReferenceUploadClient.ts`、`src/components/image-editor/useImageCanvasUploadWorkflow.ts`、`src/components/image-editor/ImageCanvasGenerationSubmissionModel.ts`、`server-rs/crates/api-server/src/character_animation_assets.rs`、`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`。
## 图片编辑器生成类菜单要挂到页面级 portal
- 现象:底部 `生成规范` 菜单、角色面板里的 `角色规范` 来源菜单点击后像没有弹出来,实际被按钮所在的局部滚动容器挡住了。
- 原因:菜单仍然渲染在底部工具栏或参考图横向滚动行内部,父容器带 `overflow`,弹层无法越出边界;即便挂到 portal,如果菜单根节点的 `pointerdown` 继续冒泡到画布视口,也会先触发画布失焦并卸载面板,导致菜单项 `click` 前消失。
- 处理:这类轻量菜单统一用页面级 fixed portal 挂到 `document.body`,位置根据触发按钮的 `getBoundingClientRect()` 计算;`PlatformFloatingMenu` 根节点必须阻止 `pointerdown` 冒泡,避免画布清空当前生成面板;底部 AI 工具栏在生成面板打开时仍保持可见,不要整栏隐藏。
- 验证:测试断言菜单不包含在底部工具栏 / 参考图行里,并且生成面板打开时底部 `AI画布工具栏` 仍存在;规范参考图来源菜单应能通过 portal 点击“从画布中选择 / 上传图片”并写回规范参考图。
- 关联:`src/components/common/PlatformFloatingMenu.tsx`、`src/components/image-editor/ImageCanvasEditorView.tsx`、`src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx`。
## 图片编辑器规范图片面板不要脱离统一生成 shell
- 现象:生成 UI 设计图或新建图标规范时,面板参考图、输入区和底部生成按钮相对生成图片 / 生成角色 / 生成视频错位;图标规范甚至可能缺少首行参考图入口。
- 原因:规范、UI 设计图等面板虽然都属于生成类入口,但 JSX 和 CSS 曾各自维护 `spec-footer`、局部 field wrapper 或缺省参考区,导致后续改造只覆盖普通图片 / 角色 / 视频,规范图片类面板结构漂移。
- 处理:生成规范下的角色规范、图标规范、自定义规范,以及生成 UI 设计图,都必须复用 `image-canvas-editor__generation-composer image-canvas-editor__generation-composer--image` 外层 shell;首行统一 `image-canvas-editor__generation-ref`,底部统一 `image-canvas-editor__generation-composer-footer` + `image-canvas-editor__generation-submit`。多字段内容只在中央字段区保持紧凑,不单独发明 footer 或省略参考区。
- 验证:`npm test -- src/components/image-editor/ImageCanvasGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx -t "生成UI设计图|生成规范|visible titles|图标规范|character spec"`。
- 关联:`src/components/image-editor/ImageCanvasGenerationComposerView.tsx`、`src/index.css`、`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`。
## 图片编辑器生成占位图在生成中也要使用最新拖拽位置
- 现象:用户在图片编辑器里提交生成后继续拖动画布占位图,预览框可以移动,但生成完成后的真实图片仍落回提交瞬间的旧位置。
- 原因:生成提交函数闭包里保存了旧的 `dialog.placeholder` 快照;如果完成回包仍用这个快照创建图层,就会丢失生成中期间的拖拽坐标。若 `handleGenerationFramePointerDown` 又按 `status === 'generating'` 拦截,则生成中占位图完全不能拖动。
- 处理:生成占位图的 pointer down 不因 `generating` 禁止;普通图片、规范图、角色图和图标素材回包创建图层时,都从当前 `generateDialogRef.current.placeholder` 读取最新占位位置,失败后保留的占位图也继续走同一拖拽链路。
- 验证:`npm test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "keeps the generation placeholder draggable while the image is generating"`。
- 关联:`src/components/image-editor/ImageCanvasEditorView.tsx`、`src/components/image-editor/ImageCanvasEditorView.test.tsx`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 图片画布 Lovart 新生成占位必须避让已有图层和占位
- 现象:用户在画布中心已有图片时继续点击“生成图片 / 生成视频 / 生成规范”等入口,新建的待生成占位压在已有图片或其它待生成占位上;生成完成后看起来像图片被覆盖或丢失。
- 原因:入口直接把 placeholder 放在当前视口中心,没有把已有图层、隐藏状态和 inactive generation dialog 的占位统一纳入避让计算,也没有在落点确定后把 viewport 平移到新占位中心。
- 处理:所有会创建 generation dialog 的入口都必须走 `ImageCanvasGenerationPlacementModel`,避让所有 `hidden !== true` 的图层和 active / inactive placeholder;按 32px 世界坐标间距外扩阻挡矩形,在候选点中选择距离当前屏幕中心对应画板位置最近且不重叠的位置,再调用 `centerViewportOnPlacement(...)` 保持缩放只平移。
- 验证:`npm run test -- src/components/image-editor/ImageCanvasGenerationPlacementModel.test.ts src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx`。
- 关联:`src/components/image-editor/ImageCanvasGenerationPlacementModel.ts`、`src/components/image-editor/useImageCanvasGenerationWorkflow.ts`、`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`。
## Windows 本地 dev 不要把 RUSTC_WRAPPER 绕过写成 rustc
- 现象:Windows 上执行 `npm run dev:api-server` 时,api-server 在 Cargo 启动阶段失败,日志出现 `error: multiple input filenames provided (first two filenames are ... rustc.exe and -)`,`/healthz` 无法访问。
- 原因:`server-rs/.cargo/config.toml` 默认配置 `rustc-wrapper = "sccache"`;本地 dev 脚本为了绕过损坏的 sccache 需要覆盖 wrapper。Windows 下如果把 `RUSTC_WRAPPER` 设置为 `rustc`,Cargo 会按 wrapper 协议调用 `rustc <真实rustc路径> - ...`,真实 rustc 把 wrapper 传入的 rustc 路径和 stdin `-` 都当输入文件。
- 处理:Windows 本地 dev 脚本应把 `RUSTC_WRAPPER` 和 `CARGO_BUILD_RUSTC_WRAPPER` 显式设为空字符串,让 Cargo 覆盖项目配置并直连真实 rustc;Linux 保持 `/usr/bin/env` 绕过 sccache。
- 验证:`npm run test -- scripts/dev.test.ts -t "Windows 下本地 dev Rust env 用空 wrapper 覆盖项目 sccache"`,并用 `npm run dev:api-server` 拉起后访问实际 api 端口的 `/healthz` 返回 200。
- 关联:`scripts/dev.mjs`、`scripts/dev.test.ts`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## Pingora 直连 80/443 不能只改 env
- 现象:`/etc/genarrative/pingora-gateway.env` 已把 `GENARRATIVE_PINGORA_GATEWAY_TLS_LISTEN` / `HTTP_REDIRECT_LISTEN` 改到 `0.0.0.0:443` / `0.0.0.0:80`,但 `genarrative-pingora-gateway.service` 启动失败,日志出现低端口绑定权限错误。
@@ -491,7 +627,7 @@
- 现象:拼图或抓大鹅运行态解析 UI spritesheet 时,把整张背景图、棋盘格、叶子或装饰图也当作 UI 素材区域,按钮映射错乱;截图里常表现为底部按钮区只剩透明棋盘格或素材碎片。
- 原因:前端解析依赖 alpha 连通域检测,透明背景是前提;但生图模型收到“透明背景 spritesheet”提示后仍可能输出带实景背景或伪透明棋盘格的普通不透明 PNG,OSS 中保存的图没有真实 alpha。
- 处理:UI spritesheet 提示词应要求统一纯绿色绿幕背景,而不是让模型直接产透明背景;后端在上传 OSS 前复用 `generated_asset_sheets::apply_generated_asset_sheet_green_screen_alpha(...)` 把绿幕扣成真实透明 PNG,再把透明图写入 `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`。
- 处理:UI spritesheet 提示词应要求统一单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景,而不是让模型直接产透明背景;后端在上传 OSS 前复用 `generated_asset_sheets::apply_generated_asset_sheet_green_screen_alpha(...)` 把绿幕扣成真实透明 PNG,再把透明图写入 `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`。
- 验证:`cargo test -p api-server puzzle_ui_spritesheet_postprocess_turns_green_screen_transparent --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server puzzle_level_scene_spritesheet_and_background_requests_use_references --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server match3d_derived_asset_prompts_match_three_sheet_pipeline --manifest-path server-rs\Cargo.toml`。
- 关联:`server-rs/crates/api-server/src/puzzle/generation.rs`、`server-rs/crates/api-server/src/match3d/works.rs`、`server-rs/crates/api-server/src/generated_asset_sheets.rs`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
@@ -499,7 +635,7 @@
- 现象:苹果等主题试玩时,中央敲击物图带明显黑底;背景图中央还可能出现苹果主体,或背景环境图偶发变成纯绿色底,和“中央只叠加 hitObjectAsset”的运行态设定冲突。
- 原因:gpt-image-2 对“透明底”和“背景只做外围氛围”的遵循不稳定。若 hit object 直接入库,黑底会被当成真实像素展示;若背景 prompt 只有软描述,模型会把主题主体画进中央。第一步为了去背刻意要求绿幕图时,如果第二步参考图或 prompt 没有切断绿幕语义,背景图也可能继承纯绿色画布。
- 处理:敲木鱼 hit object prompt 固定要求先输出 `1:1` 绿色背景主体图(纯绿色绿幕、单一 `#00FF00` 背景),再由 `api-server` 只对绿幕背景做去绿透明化;不要回到黑底 / 白底 / 透明底 prompt 后再做泛抠图。背景生成必须使用第一步抠图完成后的透明图作为参考图,并在 prompt 中显式禁止继承绿色底色、绿幕底色或纯绿色画布;背景 prompt 还要固定要求中央 40% 主体预留区干净,禁止主题主体、局部特写、轮廓影子、重复元素和主题碎片,只允许外围氛围。不要在背景 prompt 写“木鱼预设在屏幕中央位置”或类似中心主体正向描述,运行态敲击物只能由前端叠放。
- 处理:敲木鱼 hit object prompt 固定要求先输出 `1:1` 单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景主体图,再由 `api-server` 只对绿幕背景做去绿透明化;不要回到黑底 / 白底 / 透明底 prompt 后再做泛抠图。背景生成必须使用第一步抠图完成后的透明图作为参考图,并在 prompt 中显式禁止继承绿色底色、绿幕底色或纯绿色画布;背景 prompt 还要固定要求中央 40% 主体预留区干净,禁止主题主体、局部特写、轮廓影子、重复元素和主题碎片,只允许外围氛围。不要在背景 prompt 写“木鱼预设在屏幕中央位置”或类似中心主体正向描述,运行态敲击物只能由前端叠放。
- 验证:`cargo test -p api-server wooden_fish --manifest-path server-rs\Cargo.toml`,并用花朵 / 苹果 / 玉米主题跑试玩图确认绿幕被去除、主体未被抠除、背景中央不出现主题主体,背景环境图不再出现纯绿色底。
- 关联:`server-rs/crates/api-server/src/wooden_fish.rs`、`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
@@ -888,11 +1024,11 @@
- 现象:点击生成抓大鹅草稿后,页面只提示“服务暂不可用”,或者本地 `npm run dev:api-server` 看似启动但生成接口不可用。
- 原因:配置缺失类错误通常在后端 `error.details.reason` 中给出具体缺项,前端如果只读 `details.message` 会吞掉原因;本地只配置 `ALIYUN_OSS_BUCKET` / `ALIYUN_OSS_ENDPOINT` 时,旧逻辑还会在启动期构造空 AccessKey 的 OSS 客户端并失败。抓大鹅新链路仍是 2D 生图切割,不需要也不应回退 Rodin/GLB。
- 处理:前端 API 错误展示优先读取 `details.reason`,再读取 `details.message`,避免底层 `error sending request` 覆盖真正可操作的配置或网络原因;`api-server` 只有在 OSS 四件套齐全时初始化 OSS 客户端,部分缺失只记 warning 并让具体 generated 上传/换签接口返回 `OSS 未完成环境变量配置`。抓大鹅素材、封面和背景生成在调用 VectorEngine 前先预检 OSS,并通过 `details.missingEnv` 列出缺项;真实生成需补齐 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和完整 `ALIYUN_OSS_*` 四件套。抓大鹅 UI spritesheet 和物品 spritesheet 的提示词必须要求纯绿色绿幕背景,后端上传 OSS 前统一扣成透明 PNG,避免运行态 alpha 连通域解析失败。
- 处理:前端 API 错误展示优先读取 `details.reason`,再读取 `details.message`,避免底层 `error sending request` 覆盖真正可操作的配置或网络原因;`api-server` 只有在 OSS 四件套齐全时初始化 OSS 客户端,部分缺失只记 warning 并让具体 generated 上传/换签接口返回 `OSS 未完成环境变量配置`。抓大鹅素材、封面和背景生成在调用 VectorEngine 前先预检 OSS,并通过 `details.missingEnv` 列出缺项;真实生成需补齐 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和完整 `ALIYUN_OSS_*` 四件套。抓大鹅 UI spritesheet 和物品 spritesheet 的提示词必须要求单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景,后端上传 OSS 前统一扣成透明 PNG,避免运行态 alpha 连通域解析失败。
- 验证:`npm run test -- src/services/apiClient.test.ts` 覆盖 `details.reason`;`cargo test -p api-server state --manifest-path server-rs/Cargo.toml` 覆盖半配置 OSS 不阻断启动;`npm run dev:api-server` 后按实际 `GENARRATIVE_API_PORT` 请求 `/healthz`,不要默认打 `3100`。
- 关联:`packages/shared/src/http.ts`、`server-rs/crates/api-server/src/state.rs`、`docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`、`docs/technical/AUTH_SNAPSHOT_AND_MATCH3D_LOCAL_DEV_FIX_2026-05-01.md`。
2026-05-22 补充:抓大鹅“物品 spritesheet”不再按旧 Gemini `generateContent` / `5*5` sheet 路径排查;当前链路先用 `gpt-image-2` 无参考图生成 `9:16` 关卡整图,再以该关卡整图作为 multipart `image` 参考并发编辑生成 `1K 1:1` UI spritesheet、`1K 9:16` 背景图和 `2K 1:1` 物品 spritesheet。UI 与物品 spritesheet 都要求纯绿色绿幕背景,上传 OSS 前通过后端透明化处理写入真实 alpha PNG。
2026-05-22 补充:抓大鹅“物品 spritesheet”不再按旧 Gemini `generateContent` / `5*5` sheet 路径排查;当前链路先用 `gpt-image-2` 无参考图生成 `9:16` 关卡整图,再以该关卡整图作为 multipart `image` 参考并发编辑生成 `1K 1:1` UI spritesheet、`1K 9:16` 背景图和 `2K 1:1` 物品 spritesheet。UI 与物品 spritesheet 都要求单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景,上传 OSS 前通过后端透明化处理写入真实 alpha PNG。
## 抓大鹅发布按钮要先开发布面板,封面编辑收口到发布面板内
@@ -902,11 +1038,11 @@
- 验证:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`;`npm run typecheck`。
- 关联:`src/components/match3d-result/Match3DResultView.tsx`、`src/components/match3d-result/Match3DResultView.test.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
## `.hermes` 只放 Hermes 工具资源,不放项目记忆或个人配置
## `.hermes` 只放共享内容,不放个人 Hermes 配置
- 现象:团队成员误把个人 Hermes 配置、会话或密钥复制进仓库。
- 原因:仓库 `.hermes/` 与个人 `~/.hermes/` 名称相似。
- 处理:仓库 `.hermes/` 只放 Hermes 专用 skills、plugins 和启用说明;团队共享记忆、计划和 TODO 统一放在 `docs/project-memory/`;不提交 `.env`、`config.yaml`、`sessions/`、`auth.json`。
- 处理:仓库 `.hermes/` 只放 Markdown 共享记忆、计划和可公开 skills;不提交 `.env`、`config.yaml`、`sessions/`、`auth.json`。
- 验证:提交前检查 `git diff -- .hermes`,确认没有密钥、会话记录或个人路径敏感信息。
- 关联:`.hermes/README.md`。
@@ -1570,7 +1706,7 @@
- 现象:修改抓大鹅素材时容易沿用旧 Rodin/GLB 方案,导致新草稿生成耗时变长、进度停在模型阶段,或运行态等待不存在的 GLB。
- 原因:仓库里保留了 Hyper3D 通用代理和历史模型字段,旧文档也曾要求草稿阶段同步生成 GLB。当前产品口径已经改为 2D 多视角素材。
- 处理:新 `match3d_compile_draft` 与批量新增只生成 2D 图片:每个物品 5 个形态,单张 `2K 1:1` 物品 spritesheet 固定 `10*10`,每行承载两种物品、每种五个形态,单张最多承载 20 种物品。素材图 prompt 固定要求纯绿色绿幕背景,上传 OSS 前先把整张 spritesheet 绿幕处理为透明 alpha,再由运行态和编辑器按 alpha 连通域解析;`generatedItemAssets[].status` 使用 `image_ready`,发布校验看 `imageViews[]`、首图引用或可解析的物品 spritesheet。`generated-models` 仅用于历史外部模型链接转存,不能作为新生产链路。
- 处理:新 `match3d_compile_draft` 与批量新增只生成 2D 图片:每个物品 5 个形态,单张 `2K 1:1` 物品 spritesheet 固定 `10*10`,每行承载两种物品、每种五个形态,单张最多承载 20 种物品。素材图 prompt 固定要求单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景,上传 OSS 前先把整张 spritesheet 绿幕处理为透明 alpha,再由运行态和编辑器按 alpha 连通域解析;`generatedItemAssets[].status` 使用 `image_ready`,发布校验看 `imageViews[]`、首图引用或可解析的物品 spritesheet。`generated-models` 仅用于历史外部模型链接转存,不能作为新生产链路。
- 验证:`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`npm run test -- src\services\miniGameDraftGenerationProgress.test.ts src\components\match3d-result\Match3DResultView.test.tsx src\components\match3d-runtime\Match3DRuntimeShell.test.tsx`。
- 关联:`server-rs/crates/api-server/src/match3d.rs`、`src/components/match3d-runtime/Match3DRuntimeShell.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
@@ -2339,6 +2475,14 @@
- 验证:`npm run test -- src/components/common/CreativeImageInputPanel.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`。
- 关联:`src/components/common/CreativeImageInputPanel.tsx`、`src/components/puzzle-result/PuzzleResultView.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 项目画布跳转不要先写无参画布路由
- 现象:从 `/creation` 最近项目或 `/project` 项目卡进入画布时,浏览器先进入 `/editor/canvas`,随后再进入 `/editor/canvas?projectid=xxx`,导致返回来源页需要点两次。
- 原因:`App` 传给平台壳的 `setSelectionStage` 会按 stage 自动 `pushAppHistoryPath(resolvePathForSelectionStage(stage))`;如果项目入口先 `setSelectionStage('image-editor')` 再写项目 URL,就会把无参数画布路由塞入 history。
- 处理:项目入口必须先写入最终 `/editor/canvas?projectid=xxx`,再切 `image-editor` 阶段;`App` 的 stage setter 在当前位置已经解析为 `image-editor` 时不要再补写基础画布路由。
- 验证:`npm run test -- src/App.test.tsx`;浏览器中从最近项目或项目页打开项目后,后退一次应直接回到 `/creation` 或 `/project`。
- 关联:`src/App.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md`。
## 统一创作页短表单软键盘打开不要露出黑底
- 现象:小程序 / H5 移动端点击拼图或敲木鱼创作输入框后,输入框和键盘之间出现一大片黑色区域;H5 还会明显弹一下。跳一跳因为按钮区用 `mt-auto` 撑开页面,看起来没有同样问题。
@@ -24,7 +24,7 @@ Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台,把 A
- 小程序 WebView 外壳:`miniprogram/`。
- 法律文本:`media/files/user_agreement.md`、`media/files/privacy_policy.md`、`media/files/disclaimer.md`。
移动端一级 Tab:`推荐 / 发现 / 创作 / 草稿 / 我的`。
移动端一级 Tab:`推荐 / 发现 / 我的`。桌面端导航保留 `创作` 并新增 `项目`,其中 `/creation` 是独立创作工具主页,`/project` 是画布项目入口。
## 当前后端路线
@@ -0,0 +1,146 @@
# Editor Image Model Options 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:** 让图片画布的“生成角色形象”和“生成图标素材”支持 `nanobanana2` 与 `gpt-image-2`,并按模型提供合法的尺寸比例与大小尺寸选项。
**Architecture:** 前端抽出编辑器图片模型配置,生成面板只保存模型、比例、大小三个轻量状态;后端集中归一模型和尺寸组合,角色图与图标 spritesheet 继续走现有 VectorEngine、去背、OSS 和拆分链路。用户模型偏好用 localStorage 记住,默认 `nanobanana2`。
**Tech Stack:** React + TypeScript + Vitest;Rust Axum api-server;platform-image VectorEngine provider;Markdown 项目文档。
---
## File Structure
- Modify `C:\Genarrative\src\services\image-editor\editorProjectClient.ts`
- 扩展图片生成和图标 spritesheet 请求类型,加入 `aspectRatio` 与 `imageSize`。
- 默认图标模型改为 `gemini-3.1-flash-image-preview` 对应的 `nanobanana2`。
- Modify `C:\Genarrative\src\services\image-editor\editorProjectClient.test.ts`
- 先补失败测试:角色 / 图标请求会带模型、比例、大小。
- Modify `C:\Genarrative\src\components\image-editor\ImageCanvasEditorView.tsx`
- 增加模型配置、选项归一、localStorage 偏好、角色 / 图标面板字段和提交 payload。
- Modify `C:\Genarrative\src\components\image-editor\ImageCanvasEditorView.test.tsx`
- 先补失败测试:默认显示 nanobanana2,切换模型后比例 / 大小选项变更,并在请求中传递。
- Modify `C:\Genarrative\server-rs\crates\platform-image\src\vector_engine\request.rs`
- 让 `512` 不被 normalize 成非法尺寸,并保留 gpt-image-2 尺寸。
- Modify `C:\Genarrative\server-rs\crates\platform-image\tests\vector_engine.rs`
- 先补失败测试:nanobanana2 0.5K 请求 body 保留 `512`。
- Modify `C:\Genarrative\server-rs\crates\api-server\src\editor_project.rs`
- 扩展请求 DTO,集中校验 `nanobanana2 / gpt-image-2` 与尺寸组合。
- 角色生成按模型走 with_model 调用;图标生成按模型和组合选择尺寸。
- Modify docs:
- `C:\Genarrative\docs\【编辑器】画板角色形象生成入口设计-2026-06-15.md`
- `C:\Genarrative\docs\【编辑器】画板图标素材生成入口设计-2026-06-15.md`
- `C:\Genarrative\docs\technical\【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`
---
### Task 1: Client request contract
**Files:**
- Modify: `C:\Genarrative\src\services\image-editor\editorProjectClient.ts`
- Test: `C:\Genarrative\src\services\image-editor\editorProjectClient.test.ts`
- [ ] **Step 1: Write failing tests**
Add tests asserting `generateEditorImage` and `generateEditorIconSpritesheet` serialize `model`, `aspectRatio`, and `imageSize` when supplied.
- [ ] **Step 2: Run test to verify failure**
Run: `npm run test -- src/services/image-editor/editorProjectClient.test.ts -t "image model options"`
Expected: FAIL because payloads do not include `aspectRatio` / `imageSize`.
- [ ] **Step 3: Minimal implementation**
Extend input types and JSON body builders to include optional `aspectRatio` and `imageSize`; change icon default model constant to `gemini-3.1-flash-image-preview` if not already.
- [ ] **Step 4: Verify green**
Run same test command. Expected: PASS.
### Task 2: Frontend panel state and local preference
**Files:**
- Modify: `C:\Genarrative\src\components\image-editor\ImageCanvasEditorView.tsx`
- Test: `C:\Genarrative\src\components\image-editor\ImageCanvasEditorView.test.tsx`
- [ ] **Step 1: Write failing tests**
Add tests for:
1. opening `生成角色形象` defaults to model `nanobanana2` and shows `尺寸比例` / `大小尺寸`;
2. switching to `gpt-image-2` limits visible combinations and submits model + mapped size metadata;
3. icon spritesheet defaults to `nanobanana2` and submits the chosen model.
- [ ] **Step 2: Run test to verify failure**
Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "模型|尺寸比例|大小尺寸"`
Expected: FAIL because current UI has placeholder model button only.
- [ ] **Step 3: Minimal implementation**
Add model option config, dialog fields `imageModel/aspectRatio/imageSize`, localStorage helpers, option buttons/selects, and submit payload wiring.
- [ ] **Step 4: Verify green**
Run same test command. Expected: PASS.
### Task 3: Backend model and dimension normalization
**Files:**
- Modify: `C:\Genarrative\server-rs\crates\platform-image\src\vector_engine\request.rs`
- Modify: `C:\Genarrative\server-rs\crates\api-server\src\editor_project.rs`
- Test: `C:\Genarrative\server-rs\crates\platform-image\tests\vector_engine.rs`
- Test: existing unit tests inside `editor_project.rs`
- [ ] **Step 1: Write failing tests**
Add tests covering:
1. `build_vector_engine_image_request_body_with_model("gemini-3.1-flash-image-preview", ..., "512", ...)` keeps `size = "512"`.
2. editor character model normalization defaults to nanobanana2 and maps `gpt-image-2 + 2:3 + 1K` to `1024x1536`.
3. icon spritesheet model normalization accepts both models.
- [ ] **Step 2: Run backend tests to verify failure**
Run:
`cargo test -p platform-image --manifest-path server-rs/Cargo.toml vector_engine_request_body_can_use_nanobanana2_half_k -- --nocapture`
`cargo test -p api-server --manifest-path server-rs/Cargo.toml editor_project -- --nocapture`
Expected: FAIL until normalization functions exist.
- [ ] **Step 3: Minimal implementation**
Add constants and helpers:
- `EDITOR_IMAGE_MODEL_NANOBANANA2 = "gemini-3.1-flash-image-preview"`
- `EDITOR_IMAGE_MODEL_GPT_IMAGE_2 = "gpt-image-2"`
- `normalize_editor_image_model`
- `normalize_editor_generation_dimensions`
Use with_model calls for character and icon generation responses.
- [ ] **Step 4: Verify green**
Run the same backend tests. Expected: PASS.
### Task 4: Documentation and final checks
**Files:**
- Modify docs listed above.
- [ ] **Step 1: Update docs**
Document defaults, user preference, model-specific options, and Apifox source URLs.
- [ ] **Step 2: Run focused verification**
Run:
`npm run test -- src/services/image-editor/editorProjectClient.test.ts src/components/image-editor/ImageCanvasEditorView.test.tsx`
`cargo test -p platform-image --manifest-path server-rs/Cargo.toml vector_engine_request_body_can_use_nanobanana2_half_k -- --nocapture`
`cargo test -p api-server --manifest-path server-rs/Cargo.toml editor_project -- --nocapture`
`npm run typecheck -- --pretty false`
`npm run check:encoding`
---
## Self-Review
- Spec coverage: covers role generation, icon spritesheet generation, default model, user preference, model-specific dimensions, docs.
- Placeholder scan: no unresolved placeholders.
- Type consistency: frontend uses `model/aspectRatio/imageSize`; backend DTO mirrors camelCase fields.
@@ -0,0 +1,84 @@
# 图片信息生成输入快照 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 图片画布编辑器的图片信息页删除生图 Prompt 字段,改为展示图片生成时的面板输入快照,包括文本字段和参考图。
**Architecture:** 在 `CanvasLayer` 上新增轻量 `generationInputs` 前端快照,只保存用户可见输入和参考图摘要;生成成功时从对应面板状态构建快照,随画布 layout JSON 持久化。图片信息弹窗只读取该快照渲染,不回退显示后端组装 Prompt。
**Tech Stack:** React + TypeScript + Vitest + Testing Library;现有 `UnifiedModal`、平台按钮和图片画布 layout snapshot。
---
### Task 1: 信息弹窗行为测试
**Files:**
- Test: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.test.tsx`
- [ ] **Step 1: Write the failing tests**
- 修改已有图片信息测试,断言弹窗不出现 `Prompt` 和 `复制Prompt`。
- 新增普通生成图片测试:生成后打开信息页,应显示 `生成输入`、`生成提示词` 和用户输入值。
- 新增角色生成图片测试:绑定角色规范参考图后生成,信息页应显示 `角色设定`、`角色规范` 与参考图名称。
- 新增图标素材生成测试:绑定图标规范后生成,信息页应显示 `素材描述`、具体描述和 `图标规范` 参考图。
- [ ] **Step 2: Run test to verify it fails**
- Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx`
- Expected: FAIL because `generationInputs` is not yet implemented and old `Prompt` field still exists.
### Task 2: 生成输入快照模型与持久化
**Files:**
- Modify: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.tsx`
- [ ] **Step 1: Add minimal types**
- Add `CanvasGenerationInputField`, `CanvasGenerationInputReference`, `CanvasGenerationInputs`.
- Add optional `generationInputs` to `CanvasLayer`.
- [ ] **Step 2: Serialize and hydrate**
- Include `generationInputs` in `serializeLayer`.
- Hydrate only trusted shapes: string `title`, string `value`, string `label`, string `src`.
### Task 3: Build snapshots when creating generated layers
**Files:**
- Modify: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.tsx`
- [ ] **Step 1: Add builders**
- `buildGenerationInputsForImagePrompt(prompt)`.
- `buildGenerationInputsForSpec(specType, specValues)`.
- `buildGenerationInputsForCharacter(prompt, specRef, references)`.
- `buildGenerationInputsForIcon(iconDescriptions, iconSpecRef)`.
- `buildGenerationInputsForEdit(prompt, sourceLayer)`.
- [ ] **Step 2: Attach snapshots**
- Pass `generationInputs` into `addGeneratedResultLayer`, `addQuickEditResultLayer`, and `addIconSpritesheetResultLayers`.
- Keep old `prompt/actualPrompt` for backend metadata and backward compatibility, but do not render them in UI.
### Task 4: Render image info without生图 Prompt
**Files:**
- Modify: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.tsx`
- Modify: `C:/Genarrative/src/index.css` if existing metadata styles need a reference grid helper.
- [ ] **Step 1: Replace Prompt row**
- Remove `Prompt` dt/dd and `复制Prompt` button.
- Render `生成输入` row.
- [ ] **Step 2: Render fields and references**
- If `generationInputs` has fields, render each field title/value.
- If it has references, render thumbnail cards with title and image.
- If both empty or absent, render `-`.
### Task 5: Documentation and verification
**Files:**
- Modify: `C:/Genarrative/docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`
- [ ] **Step 1: Update documentation**
- Add note: image info displays panel input snapshot and references, never assembled generation Prompt.
- [ ] **Step 2: Run verification**
- Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx`
- Run: `npm run typecheck`
- Run: `npm run check:encoding`
- Run: `git diff --check`
@@ -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.
@@ -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-fast 链路生成视频、抽帧、去绿幕并持久化到 OSS。
**Architecture:** 前端在 `ImageCanvasEditorView` 中基于图层 `assetKind === "character"` 控制悬浮按钮和右键菜单,打开锚定到图片右侧的独立动画生成面板。前端 service 调用新增编辑器角色动画 API,后端复用 `character_animation_assets.rs` 中现有视频生成、抽帧、绿幕去背、OSS 写入能力,避免新建平行资产系统。
**Tech Stack:** React + TypeScript + Vitest;Rust Axum `api-server`;现有 `shared-contracts` 资产 DTO;Aliyun OSS 资产持久化;VectorEngine/Ark seedance2.0-fast 角色动画链路。
---
### 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-fast` 的回包展示字段。
- [ ] **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-fast。
- 验证构造 prompt 包含用户给定固定骨架与动作描述。
- 验证价格计算:480p 每秒 10,720p 每秒 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。
@@ -0,0 +1,158 @@
# 图片画布素材导出方案
日期:2026-06-15
## 背景
`/editor/canvas` 已承载项目画布、账号级素材库、生成图片、图层、分组、隐藏、锁定、翻转、缩放和右键菜单等编辑能力。用户需要一次性下载当前画布中使用到的全部素材,用于本地归档、外部编辑或跨工具交接。
素材导出应作为画布级能力,而不是单个图层的“导出为”。单图导出仍保留在目标右键菜单;全部素材导出放在画布标题栏,入口稳定、可发现且不打断画布操作。
## 入口设计
- 在画布右上角标题栏内增加下载图标按钮。
- 图标使用 `lucide-react` 的 `Download`。
- 按钮 `aria-label` 使用 `下载画布素材`。
- 入口与返回项目页、项目名称同属标题栏,但视觉上靠右,不进入左下角画布工具组,也不进入底部 AI 工具栏。
- 标题栏空间不足时,下载图标保持固定尺寸,项目名称使用省略号收缩。
## 第一版范围
第一版实现“导出画布素材 ZIP”:
- 导出当前画布中有效图层引用的图片。
- 包含上传图、生成图、修改生成结果。
- 默认包含隐藏图层。
- 跳过已被素材库删除且已判定无效的上传图层。
- 锁定、分组、翻转等状态不影响图片文件导出,但写入元数据。
- 空画布时按钮置灰,或点击后显示轻提示。
暂不实现:
- 画布整体截图 PNG。
- PSD / Figma / 工程包导出。
- 后端异步打包任务。
- 导入导出包恢复画布。
## ZIP 结构
导出文件名:
```text
项目名-画布素材-YYYYMMDD.zip
```
包内结构:
```text
项目名-画布素材/
├─ images/
│ ├─ 001-拼图素材.png
│ ├─ 002-生成图片.png
│ └─ 003-修改结果.png
├─ metadata.json
└─ manifest.txt
```
## 元数据结构
`metadata.json` 记录项目、导出时间、图层和图片文件映射:
```json
{
"projectId": "editor-project-default",
"projectTitle": "默认项目",
"exportedAt": "2026-06-15T00:00:00.000Z",
"layers": [
{
"layerId": "layer-generated-1",
"title": "生成图片 1",
"file": "images/002-生成图片.png",
"width": 1024,
"height": 1024,
"canvas": {
"x": 120,
"y": 80,
"width": 420,
"height": 420,
"zIndex": 12,
"hidden": false,
"locked": false,
"flipX": false,
"flipY": false,
"groupId": null
},
"sourceType": "generated",
"prompt": "一张明亮的拼图主视觉",
"actualPrompt": "一张明亮的拼图主视觉",
"model": "gpt-image-2",
"provider": "VectorEngine",
"taskId": "editor-real-task-1"
}
]
}
```
`manifest.txt` 用于人工快速查看:
```text
项目:默认项目
导出时间:2026-06-15T00:00:00.000Z
素材数量:3
图层数量:5
```
## 去重规则
同一张图片被多个图层引用时,只导出一份图片,所有图层在 `metadata.json` 中指向同一个 `file`。
图片去重 key 优先级:
```text
assetObjectId > objectKey > sourceAssetId > src
```
文件命名按图层 zIndex 从低到高排序,遇到重复名称追加序号。
## 前端实现
第一版优先使用客户端 ZIP:
1. 从当前 `layers` 取目标图层。
2. 过滤无效图层,保留隐藏图层。
3. 按去重 key 合并图片源。
4. 对每个图片源读取 Blob:
- `data:image/...` 直接转换为 Blob。
- 同源或可访问 URL 使用 `fetch` 拉取 Blob。
- 拉取失败时记录失败项,不中断整个导出。
5. 使用 `JSZip` 写入 `images/`、`metadata.json` 和 `manifest.txt`。
6. `zip.generateAsync({ type: 'blob' })` 生成文件。
7. 用临时 `<a download>` 触发下载。
若当前仓库未安装 `jszip`,新增依赖时应只引入 `jszip`,不引入额外下载库。
## 错误处理
- 空画布:按钮置灰或显示短提示。
- 部分图片下载失败:ZIP 仍生成,`metadata.json` 记录失败图片,UI 显示“部分素材未能导出”。
- 全部图片失败:不下载 ZIP,显示失败提示。
- 浏览器不支持 Blob 下载:显示失败提示。
## 测试计划
- 标题栏右上角显示 `下载画布素材` 图标入口。
- 空画布时入口不可执行或提示为空。
- 上传图和生成图都写入 ZIP。
- 多图层引用同一素材时,图片文件只写一份。
- 隐藏图层默认写入 `metadata.json`。
- 图层的锁定、翻转、分组状态写入元数据。
- `data:image` 图片不经过网络请求即可导出。
- URL 图片 fetch 失败时不中断其他素材导出。
## 后续扩展
- 导出当前选中素材。
- 导出画布快照 PNG。
- 导出可恢复工程包。
- 导入工程包恢复画布。
- 后端异步打包大项目,前端只轮询下载结果。
File diff suppressed because one or more lines are too long
@@ -0,0 +1,251 @@
# 图片画布编辑器前端拆分计划
日期:2026-06-17
## 背景
`src/components/image-editor/ImageCanvasEditorView.tsx` 已承载图片画布的素材库、图层、生成对象、导出、右键菜单、拖拽、缩放、吸附、小地图和登录恢复等能力。文件体量超过八千行,阅读一个局部交互时需要先穿过大量无关类型、常量和纯函数,后续继续补齐 Lovart 式画布能力会持续放大维护成本。
本轮拆分目标不是把界面拆成很多浅模块,而是先把稳定模型和纯逻辑放到有深度的模块中。主视图继续保留 React 状态编排和 JSX 工作面,避免第一轮把复杂交互拆成大量 props 透传。
## 拆分原则
- 类型、常量和纯函数优先拆出,React 状态闭环暂不拆散。
- 模块接口要承载真实规则,不建立只转发一两个属性的浅模块。
- 画布坐标、素材快照、生成输入快照和导出元数据分别放在相近规则的模块内,提升局部修改能力。
- `ImageCanvasEditorView.tsx` 不再反向作为模型模块的依赖;依赖方向固定为 `View -> model/types/services`。
- 任何拆分必须保持现有交互、持久化和测试断言不变。
## 第一阶段模块
- `ImageCanvasEditorTypes.ts`
- 承载编辑器前端共享类型:素材、图层、视口、工具、生成对象、历史快照、剪贴板、右键菜单、拖拽状态等。
- 只暴露类型,不承载运行时逻辑。
- `ImageCanvasEditorModel.ts`
- 承载画布基础模型:尺寸、缩放、背景色、素材默认文件夹、快照序列化 / 水合、素材库快照映射、吸附、右键菜单定位、DataTransfer 工具和通用数值格式化。
- 保留“图片显示尺寸跟随 Resolution”“只保留一个默认素材文件夹”“右键菜单不滚动而是限制到视口内”等规则。
- `ImageCanvasGenerationModel.ts`
- 承载生成相关模型:生成占位尺寸、默认模型、规范表单默认值、角色动画选项、生成输入快照、规范 prompt 构建、生成对象识别和错误文案。
- 保留角色动画优先用 `objectKey` 的体积保护规则。
- `ImageCanvasExportModel.ts`
- 承载画布素材导出的底层规则:文件名清理、日期格式、图片去重 key、Data URL 转 Blob、Blob 读取和图层导出元数据。
- ZIP 组包和下载触发仍留在主视图,作为 UI 状态编排的一部分。
## 第二阶段模块
- `ImageCanvasSidebarView.tsx`
- 承载素材 / 图层共用左侧整合面板的 JSX,包括素材文件夹、新建 / 折叠 / 重命名 / 删除、上传入口、素材选择模式、框选层、批量删除、素材拖到文件夹和图层列表。
- 继续通过 props 调用主视图状态机,不接管上传、登录弹窗、持久化、拖到画布的坐标换算和图层历史记录。
- 保持“素材”和“图层”同一侧栏切换的 Lovart 式布局,不恢复右侧独立图层栏或左侧竖向工具栏。
## 第三阶段模块
- `ImageCanvasStageView.tsx`
- 承载中央画布工作区的视觉树:viewport / world DOM、图层渲染、生成占位框、选中图片浮动工具栏、空白和图片右键菜单、左下 dock、缩放菜单、背景设置面板、小地图和底部 AI 工具栏。
- 继续通过 props 调用主视图状态机,不接管拖拽 / 平移 / 缩放、画布坐标换算、历史 undo / redo、上传、登录、生成提交、素材持久化和右键命令实现。
- 保持 `canvasViewportRef` 由主视图传入,确保 pointer capture、drop 坐标、滚轮缩放和小地图拖拽仍使用同一套坐标源。
- 图片图层渲染必须和音频、视频一样走统一资源读取解析;图层存在 `objectKey` 时优先用它换签,避免重进项目后私有上传 / 生成资源只剩旧路径而无法显示。
第三阶段以后,主视图仍是画布编排入口。继续拆分前应优先选择能形成稳定边界的深模块,避免把上传链路、DataTransfer、画布坐标和历史快照拆成互相回调的小碎片。
## 第四阶段模块
- `ImageCanvasGenerationComposerView.tsx`
- 承载生成图片、生成规范、生成角色形象、生成图标素材、快速编辑图片、角色动画和修改图片弹窗的视觉表单。
- 保留生成对象状态机、提交 API、上传文件 input、引用选择、生成结果回写、图层历史和坐标锚定在主视图内,避免把 Lovart 式生成对象拆成不可追踪的远程状态。
- 该组件可以管理局部字段输入和菜单展示,但所有会影响画布事实的动作都通过主视图回调落回原有状态机。
## 第五阶段模块
- `ImageCanvasLayerCommandModel.ts`
- 承载图层命令的纯数据规则:右键目标解析、复制快照、粘贴 / 创建副本定位、层级移动、分组 / 解组、显示 / 隐藏、锁定 / 解锁、水平 / 垂直翻转和删除。
- 主视图继续负责命令触发时机、历史快照、选中态、菜单关闭、元数据清理和导出下载等 UI / 浏览器副作用。
- 该模块有独立单测锁定当前右键菜单语义,避免后续调整 UI 时顺手改变图层数据规则。
## 第六阶段模块
- `ImageCanvasInteractionModel.ts`
- 承载画布交互纯计算:适合视图、中心缩放、普通滚轮纵向滚动、Ctrl / Cmd 滚轮缩放、画布坐标换算、框选命中、平移、生成占位框拖拽、图层拖拽吸附、小地图投影、小地图点击定位和小地图拖拽视图移动。
- 主视图继续负责 React 事件对象、pointer capture、history 快照、生成对象回写、选中态和 `setState`。
- 该模块用独立单测覆盖小地图灵敏度、吸附、多选拖拽和滚轮缩放等之前容易回退的交互规则。
## 第七阶段模块
- `useCanvasHistory.ts`
- 承载画布历史栈:快照创建、快照恢复、撤销、重做、历史栈长度限制和 `canUndo` / `canRedo` 派生状态。
- 主视图继续负责在具体用户动作前调用 `captureCanvasHistory`,并通过 hook 注入恢复快照后需要清理的 hover、元数据、框选、吸附、右键菜单和平移拖拽状态。
- 该 hook 有独立测试覆盖图层、视口、active / archived 生成对话框和选中态的撤销 / 重做恢复,避免后续把 history 逻辑继续埋回主视图。
## 第八阶段模块
- `useImageCanvasProjectPersistence.ts`
- 承载图片画布工程持久化协调:项目加载、`projectId` 维护、未就绪资源队列、工程资源创建、资源创建后即时 layout 保存、450ms 自动保存和鉴权失败登录弹窗。
- 该 hook 以“项目持久化协调器”整体抽出,避免把加载、保存和资源创建拆成多个小 hook 后打散 `projectIdRef`、`pendingProjectResourceLayersRef`、`isProjectReady` 和 `saveTimerRef` 的时序约束。
- 主视图继续负责项目重命名 UI、素材库管理、上传流程和用户动作触发;新增图层仍通过 `appendCanvasLayersWithResources` 先写本地图层快照,再创建 project resource 并保存带真实 `resourceId` 的 layout。
- 项目加载 hydrate 时必须从 project resource 回填 `objectKey`、`assetObjectId`、`sourceResourceId` 等资源元数据;layout 快照保持轻量,但重进项目后的画布图层仍要保留换签和后续编辑所需线索。
## 第九阶段模块
- `useCanvasGenerationDialogs.ts`
- 承载画布生成 dialog 注册表:active / inactive dialog 状态、id 分配、归档、激活、按 id 更新 / 删除、按关联图层清理,以及生成中异步回写时获取最新占位框。
- 该 hook 只处理 `generate/spec/character/icon` 这类画布生成对象,继续保留 `GenerateDialogState.mode === 'edit'` 的直接 `setGenerateDialog` 能力用于低风险迁移;真实生成请求、quick edit、角色动画、结果落图和资源持久化仍留在主视图。
- 主视图通过 `onActivate` 处理激活生成对象后的清空图层选择和关闭图片菜单等跨状态副作用,避免 dialog hook 反向依赖画布图层状态。
## 第十阶段模块
- `useImageCanvasAssetLibrary.ts`
- 承载账号级素材库状态模型:素材文件夹、素材列表、文件夹折叠 / 新建 / 重命名 / 删除、素材重命名 / 删除、素材选择模式、框选、多选删除、素材拖到文件夹和鉴权失败登录弹窗。
- 主视图继续保留上传文件读取、上传占位卡片进度、拖到画布坐标、创建画布图层、工程资源持久化和画布图层清理;素材删除通过 `onDeleteAssets` 回调通知主视图清理关联图层。
- 该 hook 有独立单测覆盖素材库加载归一化、401 登录、新建文件夹临时 id 替换、素材移动、删除回调和多选删除,避免后续整理侧栏 JSX 时丢失素材库能力。
## 第十一阶段模块
- `ImageCanvasFileModel.ts`
- 承载图片文件判定和 `FileReader` Data URL 读取工具,供素材上传、生成参考图上传和后续导入能力复用。
- 该模块不依赖素材库状态,避免把通用文件读取继续挂在素材库 hook 上。
- `useImageCanvasUploadWorkflow.ts`
- 承载图片画布上传工作流:隐藏文件 input、上传目标分发、未登录拦截和登录后续传、上传占位卡片、文件读取、素材落库、拖到画布建层、选中新图层、打开图层侧栏,以及角色 / 图标生成参考图上传。
- 主视图继续负责画布 drop 外层事件判断、素材库已有素材加入画布、项目资源持久化 hook 注入和画布历史捕获,避免上传 hook 反向成为画布全局状态真相。
- 该 hook 用独立单测覆盖登录续传、上传占位 / 成功回写、上传到画布建层、鉴权失败和生成参考图分发,主视图保留 DOM 级 smoke 覆盖侧栏上传、画布 drop 上传和文件夹定向上传。
- 普通素材上传入口必须在打开系统文件选择器前检查登录态;未登录时先弹 `账号入口`,登录后由用户再次点击上传入口打开选择器,避免浏览器拦截异步触发的系统文件选择器。角色 / 图标生成参考图只作为本地引用进入生成表单,不强制登录。
## 第十二阶段模块
- `ImageCanvasGenerationLayerModel.ts`
- 承载生成结果落画布的纯数据规则:普通生图、修改图片、快速编辑和图标素材批量结果如何生成图层 id、临时 resourceId、标题、位置、原始分辨率尺寸、zIndex、source metadata、`assetKind`、源图关联和 `generationInputs`。
- 主视图继续负责生成提交 API、生成对象 active / archived 状态、资源持久化、图层选择、侧栏切换、对话框收起和适合视图等副作用,避免把多个画布生成对象的生命周期拆成浅 wrapper。
- 该模块用独立单测锁定“图片显示尺寸跟随原始 Resolution”“生成占位框只作为定位参考”“图标素材沿用当前行宽换行规则”和“快速编辑保留源图分组 / 类型”的规则。
## 第十三阶段模块
- `useImageCanvasAssetExportWorkflow.ts`
- 承载画布素材导出工作流:导出状态、空画布提示、单图右键导出、整包 ZIP 组包、图片去重、读取失败记录、`metadata.json`、`manifest.txt`、下载链接创建和浏览器不支持时的错误提示。
- 主视图继续负责右键菜单目标解析、下载按钮渲染和状态提示展示;导出 hook 不接管图层选择、右键菜单生命周期或画布状态。
- 该 hook 用独立单测覆盖 ZIP 内容、重复图层复用同一图片文件、失败图片 metadata、manifest 失败数量、空画布提示和单图文件名清理。
## 第十四阶段模块
- `useImageCanvasLayerCommands.ts`
- 承载图层命令工作流:画布剪贴板、右键目标解析、复制 / 剪切 / 粘贴、创建副本、层级移动、分组 / 解组、显示 / 隐藏、锁定 / 解锁、翻转、删除选中图层、按 id 删除图层和单图导出委托。
- 主视图继续负责右键菜单定位、画布事件、生成提交、素材上传、项目持久化和实际下载实现;hook 只接收必要 setter 与副作用回调,不反向读取 DOM 或路由。
- 该 hook 用独立单测覆盖菜单关闭、历史捕获、选中态更新、删除副作用、剪贴板和导出委托,避免主视图后续继续堆积右键菜单 orchestration。
## 第十五阶段模块
- `useImageCanvasGenerationWorkflow.ts`
- 承载图片画布生成工作流:打开普通生图 / 规范 / 角色 / 图标生成对象、打开修改图片 / 快速编辑 / 角色动画面板、选择画布规范参考、提交真实生成 API、失败恢复、生成结果落图、侧栏切换、适合视图和删除图层后的生成态清理。
- 主视图继续负责画布事件、生成对话框定位样式、占位框拖拽、DOM 渲染、上传入口、工程资源持久化和历史捕获;生成 hook 只接收状态 setter 与落图 / 选中 / 适合视图回调。
- 该 hook 用独立单测覆盖打开生成占位、普通生图落图、快速编辑落图并适配源图、删除源图时清理 quick edit / edit dialog、角色动画入口过滤和隐藏生成面板不删除占位框,避免后续拆分再次导致生成工具或底部工具栏状态回退。
## 第十六阶段模块
- `useImageCanvasEditorChrome.ts`
- 承载编辑器 chrome 状态:项目标题 / 重命名、左侧栏开关、当前工具、缩放菜单、背景设置面板、小地图开关、画布背景色和 HEX 输入。
- 主视图继续负责真正跨工作流的动作编排,例如上传工具触发上传工作流、生成工具触发生成工作流、项目加载后注入标题、键盘 Escape 同时关闭生成 / 快速编辑 / 图片菜单等非 chrome 面板。
- 该 hook 用独立单测覆盖项目重命名、鉴权失败登录、背景色合法 / 非法 HEX、侧栏切换、缩放 / 背景面板关闭、小地图和工具状态,避免后续改顶部栏或左下 dock 时把这些状态重新散回主视图。
## 第十七阶段模块
- `useImageCanvasViewportControls.ts`
- 承载画布视口控制:`viewport`、`canvasSize`、小地图投影、适合视图、中心缩放、普通滚轮纵向滚动、Ctrl / Cmd 滚轮缩放、屏幕点到画布 / 世界坐标换算和小地图点击 / 拖拽移动视图。
- 主视图继续负责图层拖拽、生成占位框拖拽、框选、多选、历史触发时机、上传 drop 分流和小地图 pointer down 事件;该 hook 只作为视口控制协调器,不接管画布完整 pointer 状态机。
- 该 hook 用独立单测覆盖尺寸同步、适合视图、中心缩放、坐标换算、滚轮语义和小地图移动,为后续抽 `useImageCanvasStageInteractions` 预留更清晰的视口接口。
## 第十八阶段模块
- `useImageCanvasStageInteractions.ts`
- 承载画布舞台 pointer 状态机:选择 / 框选、多选图层拖拽、生成占位框拖拽、抓手 / Space 临时抓手 / 中键平移、小地图 click / drag 分流和吸附线状态。
- 主视图继续保留原生文件 / 素材 drop、右键菜单定位、上传工作流、生成提交、项目持久化和工具栏动作分流;舞台 hook 只接收这些能力需要的回调,不反向读取路由、API 或素材库状态。
- 该 hook 用独立单测覆盖多选拖拽、框选、临时抓手、生成占位拖拽和小地图 click / drag 分流;主视图 DOM 测试继续覆盖真实组件路径和历史上容易回退的浏览器级交互。
## 第十九阶段模块
- `useImageCanvasCanvasDropWorkflow.ts`
- 承载画布区域 drag over / leave / drop 分流:识别素材库拖拽 MIME、本地文件拖拽、画布遮罩状态、默认文件夹选择、素材入画布和文件上传到画布参数组装。
- 主视图继续提供已有能力注入:账号级素材列表、默认素材文件夹、屏幕点转画布 drop 点、素材建层、文件上传、素材移动高亮清理;drop hook 不直接创建资源、不访问 API,也不读取项目持久化状态。
- 该 hook 用独立单测覆盖素材库图片拖入画布、文件拖入画布、无关拖拽不拦截和 drag leave 清理遮罩;主视图集成测试继续覆盖真实 DOM 中的“素材库拖到画布”和“文件拖到画布”路径。
## 第二十阶段模块
- `ImageCanvasMetadataModalView.tsx`
- 承载图片信息弹窗渲染:图片类型、生成输入字段、参考图、模型、分辨率、Provider、Task 和 Object 信息。
- 主视图只保留 `metadataLayer` 状态和打开 / 关闭动作;图片信息的视觉结构、fallback 文案和 `UnifiedModal` 接入不再散在主视图底部。
- 该组件用独立单测覆盖生成图 metadata、上传图 fallback、参考图渲染和关闭回调;主视图集成测试继续覆盖从画布图层点击右上角信息按钮打开弹窗。
- 本阶段同步把项目 / 素材初始加载挂到 `AuthGate` 的受保护数据可访问状态之后;未登录进入编辑器只拉起 `账号入口`,不再抢跑 `/api/editor/*` 造成素材或工程读取 401 噪声。
- `重置画布视图` 按钮必须显式调用 `onFitLayers()`,不能把 React click event 作为目标图层数组透传给适合视图逻辑。
## 第二十一阶段模块
- `useImageCanvasKeyboardShortcuts.ts`
- 承载图片画布全局键盘快捷键:Ctrl / Cmd + Z 撤销、Ctrl / Cmd + Shift + Z 重做、Shift 按下态、Backspace / Delete 删除选中图层、删除可编辑生成占位、Escape 关闭临时面板,以及 Space 临时抓手。
- 主视图继续保留各工作流状态和具体副作用,例如图层删除、生成对话框、规格菜单、快速编辑面板和 chrome 面板状态;快捷键 hook 只接收 ref、setter 与回调,不直接读写素材库、路由或 API。
- 该 hook 用独立单测覆盖输入框忽略快捷键、撤销重做、选中图层删除、生成占位删除、Escape 保留生成中面板、Space 临时抓手和 Shift 状态;主视图 DOM 测试继续覆盖真实编辑器里的 Backspace、Escape、Space 和 undo / redo 集成路径。
## 第二十二阶段模块
- `useImageCanvasAssetPointerDragBridge.ts`
- 承载素材库卡片 pointer 拖拽桥接:全局 `pointermove` / `pointerup` / `pointercancel` 监听、拖拽激活阈值、画布 drop 提示、文件夹移动高亮、拖到文件夹移动素材、拖到画布创建图层,以及拖拽结束后的点击抑制。
- 主视图继续保留素材库事实、画布建层、历史捕获、工程资源持久化和素材移动 API 编排;该 hook 只作为“侧栏素材拖拽到画布或文件夹”的事件桥,不直接读写路由、API 或图层持久化。
- 该 hook 用独立单测覆盖拖拽激活、文件夹 drop、画布 drop、非激活拖拽清理和 pointer cancel 完成路径;主视图 DOM 测试继续覆盖真实素材库拖到画布和拖到文件夹的集成链路。
- 本阶段同步恢复认证弹窗使用 portal 渲染,避免 `/editor/canvas` 这类全屏画布内容把 `账号入口` 遮住;同时把画布背景设置面板调整为独立白色浮层,包含当前色、色域、色相、自定义颜色、预设网格、HEX 输入和恢复默认。
## 第二十三阶段模块
- `ImageCanvasOverlayModel.ts`
- 承载画布浮层定位纯规则:生成输入框锚定、图标素材生成面板横向扩宽、选中图片工具栏边界限制、快速编辑面板定位和角色动画面板定位。
- 主视图继续负责生成对象、quick edit、角色动画、视口和图层状态编排,只把可纯函数验证的屏幕坐标计算移出,避免后续调整 Lovart 式浮层时继续在主视图里散落坐标公式。
- 该模块用独立单测覆盖生成结果优先锚定、生成中 / 手动关闭隐藏输入框、icon 描述项宽度、图片工具栏 clamp、quick edit 和角色动画面板边界,以及画布生成 dialog 模式识别。
- 本阶段主视图从 1182 行降至 1133 行;下一步若继续拆分,可在该模型基础上抽更大的 `useImageCanvasStageController`,承接舞台派生状态、右键菜单和工具切换胶水。
## 第二十四阶段模块
- `useImageCanvasAssetCanvasBridge.ts`
- 承载素材库与画布之间的桥接工作流:删除素材时清理关联画布图层、素材加入画布创建图层、素材 pointer 拖入画布 / 文件夹、画布区域 drag over / leave / drop 分流,以及拖拽文件上传到画布的参数组装。
- 主视图继续掌握素材库事实、上传文件读取、工程资源持久化、历史捕获触发时机和实际 API 副作用;该 hook 只负责把已有素材 / 文件 drop 转成画布动作,不反向读取路由、登录态或项目数据。
- 该 hook 用独立单测覆盖素材建层、pointer drop 入画布和删除素材清理关联图层 / 选中态;原有 pointer drag 和 canvas drop hook 单测继续保留,主视图 DOM 测试继续覆盖真实素材库拖入画布路径。
- 本阶段主视图从 1133 行降至 1086 行;下一步可继续抽顶栏视图或更高内聚的舞台控制层,但应优先选择能收敛真实状态规则的深边界。
## 第二十五阶段模块
- `ImageCanvasStageControllerModel.ts`
- 承载舞台派生状态和右键菜单模型:选中图层、选中浮动工具栏位置、图片菜单图层、右键菜单目标图层,以及显示 / 解锁菜单文案判断。
- 该模型复用既有图层命令模型与浮层定位模型,不重新实现右键目标和选中工具栏坐标规则;生成 Composer 锚点不属于舞台控制器,后续由生成表面编排统一负责。
- 新增单测覆盖选中工具栏位置、右键目标集合、显示 / 解锁判断和菜单位置限制。
- `useImageCanvasStageController.ts`
- 承载舞台控制胶水:清空画布焦点、空白画布右键菜单和图层右键菜单处理。
- 主视图继续保留工具切换、上传 / 生成入口、图层命令、项目持久化和舞台 pointer 状态机,避免把跨工作流动作塞进单个 hook。
- 本阶段主视图从 1086 行降至 993 行;后续若继续拆分,应优先考虑顶栏 / 项目标题区域或把侧栏图层右键入口并入同一舞台菜单控制层。
## 第二十六阶段模块
- `ImageCanvasTopbarView.tsx`
- 承载编辑器顶部栏视觉结构:返回项目入口、项目标题展示、项目标题重命名表单、下载画布素材按钮和导出状态提示。
- 主视图继续保留项目标题 / 重命名状态所属的 chrome hook、项目持久化、导出工作流和实际导出副作用;顶栏只负责表单事件和显示,不接管业务状态。
- 新增组件单测覆盖返回入口、标题编辑入口、重命名提交 / 取消、导出按钮禁用 / 启用和导出状态提示。
- 本阶段主视图从 993 行降至 905 行;后续可继续评估隐藏上传 input / 侧栏拖拽预览这类根级浮层是否值得抽为编辑器 shell 视图。
## 第二十七阶段模块
- `useImageCanvasGenerationSurface.tsx`
- 承载 Lovart 式生成表面编排:组合 `useImageCanvasGenerationWorkflow`,统一计算普通生图、图标生成、快速编辑和角色动画的浮层位置,并构建 `ImageCanvasGenerationComposerView` 节点。
- `useImageCanvasGenerationWorkflow` 继续作为生成状态机和真实 API 提交入口;生成表面 hook 只收口 Composer JSX、工具切换分流和浮层定位,避免主视图继续维护大段生成 props 胶水。
- 本阶段同步把生成 Composer 锚点从 `ImageCanvasStageControllerModel` 移出,避免舞台控制器和生成表面各自计算一套生成浮层状态。
- 新增 hook 单测覆盖生成工具切换、Composer 位置、关闭生成输入框和规范菜单;主视图从 905 行降至 793 行。
## 后续阶段
- 后续可继续选择更高内聚的交互 workflow 或持久化边界,不再把生成链路继续拆成浅层 wrapper。
- 工程资源持久化、工具切换和历史捕获仍在主视图编排,拆分前需要先确认不会破坏多生成对象同时存在、完成时读取最新占位框、素材拖拽上传位置和角色动画优先传 `objectKey` 的历史保护规则。
## 验证计划
- `npm run test -- src/components/image-editor/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/ImageCanvasStageControllerModel.test.ts src/components/image-editor/useImageCanvasStageController.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`
- 浏览器回归 `/editor/canvas`:确认登录弹窗、素材上传、背景设置面板、底部工具栏、Space 临时抓手、撤销 / 重做和画布基础渲染仍正常。
@@ -0,0 +1,241 @@
# AI Web 工程 Runner 与预览隔离威胁模型
更新时间:`2026-06-13`
## 范围
本文约束 `/editor/agent` 浏览器内 AI Web 工程编辑器的 MVP:固定 React / Vite / TypeScript 静态模板、虚拟文件系统、独立 runner 构建、独立 origin iframe 预览。
不覆盖 HMR、终端 shell、后端服务、任意依赖安装、任意端口代理和正式作品发布。这些能力进入后续阶段前必须单独补威胁模型。
## 信任边界
```text
用户浏览器主站 origin
| /editor/agent 编辑器壳
api-server 控制面
| 最小任务能力
web-project-runner 执行面
| immutable artifact
preview gateway / 独立 preview origin
```
关键原则:
- 主站不执行 AI 工程代码。
- api-server 不执行 AI 工程代码。
- runner 无平台密钥、无宿主源码挂载、无 Docker socket。
- 预览域和主站不同 origin。
- 预览页拿不到平台 cookie、access token、SpacetimeDB、OSS 写权限或 LLM provider 密钥。
## 资产与威胁
| 资产 | 主要威胁 | MVP 缓解 |
| --- | --- | --- |
| 主站 access token / cookie | 预览代码同源读取、XSS 窃取 | 独立 preview origin;iframe 不带主站 cookie;预览页不能访问主站 storage |
| 用户 Web 工程源码 | 跨租户读取、snapshot 枚举 | project / owner 校验;snapshotId 不可枚举;preview token 绑定 owner / project / snapshot |
| preview artifact | 路径穿越、MIME 错误、旧 token 访问 | preview gateway 校验 token;禁止 `..`;按白名单 MIME 服务;短期 token 可撤销 |
| runner 临时工作区 | 逃逸到宿主源码、读取密钥 | 独立临时目录或容器;非 root;无宿主源码挂载;任务结束销毁 |
| 依赖缓存 | 缓存污染、恶意 postinstall | MVP 固定依赖;禁用 scripts;缓存 key 包含模板、Node 版本和 lock digest |
| api-server / SpacetimeDB | runner 横向访问内部服务 | runner 默认无内网访问;阻断 api-server 管理端口、SpacetimeDB 和生产数据库 |
| OSS / artifact store | 越权读写、签名 URL 泄露 | runner 只拿短期只读资产签名或受控写 artifact 能力;日志脱敏 |
| 构建日志 | 泄露环境变量、宿主路径、签名 URL | 日志限长、脱敏、错误摘要化;不回显平台密钥 |
| 用户浏览器 | 弹窗逃逸、下载、剪贴板、摄像头、Service Worker 常驻 | iframe sandbox;CSP;禁用 Service Worker;不授权敏感能力 |
## Runner 限制
runner 必须 fail-closed。每个 job 至少限制:
- CPU。
- 内存。
- 磁盘。
- 进程数。
- 打开文件数。
- 任务时长。
- 日志大小。
- artifact 大小。
- 单文件大小。
执行环境要求:
- 非 root 用户。
- 只读基础镜像。
- 无 Docker socket。
- 无宿主源码目录挂载。
- 无平台 `.env`。
- 无平台 token。
- 临时工作区任务结束销毁。
构建超时或资源超限时直接 kill,job 进入 `failed` 或 `expired`,保留可展示错误摘要,不推进 active preview。
## 网络限制
MVP 构建期默认无外网。若模板构建确实需要网络,只允许:
- 平台受控 npm registry mirror。
- 平台资产只读签名域。
必须阻断:
- RFC1918 内网网段。
- 云 metadata 地址。
- api-server 管理端口。
- SpacetimeDB。
- 生产数据库。
- Docker daemon。
- 任意用户配置 registry。
需要覆盖 DNS rebinding 和 HTTP redirect 到内网的情况;不能只按初始 URL 字符串判断。
## 预览 token
preview token 必须满足:
- 绑定 `ownerUserId`。
- 绑定 `projectId`。
- 绑定 `snapshotId`。
- 绑定 `artifactId`。
- 短期有效。
- 可撤销。
- 不可枚举。
- 不能跨租户复用。
访问无权限 artifact 应返回 `403` 或不可区分的 `404`,不得泄露其它项目是否存在。
## Preview Gateway
gateway 只读服务 immutable artifact。
必须校验:
- token 有效且未撤销。
- artifact 属于 token 绑定 snapshot。
- 请求路径规范化后仍在 artifact 根目录。
- `index.html` fallback 只在 SPA 路由范围内生效。
- MIME 类型来自白名单映射,不信任上传文件名。
必须测试:
- path traversal。
- 错误 MIME。
- HTML 注入。
- 缓存串租户。
- 旧 token 访问。
- artifact 删除后访问。
- token 过期后访问。
## 浏览器隔离
iframe sandbox MVP 建议最小化:
```text
sandbox="allow-scripts"
```
如果后续需要表单或同源能力,必须单独评审。MVP 不允许:
- `allow-same-origin`
- `allow-top-navigation`
- `allow-downloads`
- `allow-popups`
- `allow-modals`
- 摄像头。
- 麦克风。
- 剪贴板。
- 地理位置。
CSP 默认收紧:
```text
default-src 'none';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob: https://assets.genarrative.world;
font-src 'self' data:;
connect-src 'none';
frame-ancestors https://www.genarrative.world;
worker-src 'none';
```
MVP 禁用 Service Worker 和持久缓存,避免恶意预览代码在用户浏览器里长期驻留。
## Patch 校验
AI 只能提交结构化 patch:
- create file
- update file
- delete file
- rename file
- package manifest request
api-server 必须拒绝:
- 绝对路径。
- `..`。
- 符号链接。
- `.env`、`.npmrc`、`.git`、`.ssh` 等隐藏敏感文件。
- 超深目录。
- 超大文件。
- 超大总 snapshot。
- 二进制膨胀。
- 大 Data URL。
- 可执行权限。
前端编辑器可以显示校验错误,但正式准入以 api-server 为准。
## 依赖供应链
MVP 固定模板依赖,不开放任意 npm。即便用户或 AI 修改 `package.json`,也必须拒绝或忽略:
- `preinstall`。
- `install`。
- `postinstall`。
- `prepare`。
- `git:` 依赖。
- `file:` 依赖。
- `http:` / `https:` tarball 依赖。
- 私有 registry。
- lockfile 篡改。
- native build。
- 二进制下载型包。
后续开放白名单依赖时,必须加入依赖 resolver、registry mirror、封禁列表、许可证 / 安全扫描和缓存污染防护。
## 审计与熔断
必须记录:
- `ownerUserId`
- `projectId`
- `snapshotId`
- `jobId`
- `artifactId`
- `templateKey`
- 依赖 lock digest
- 构建耗时
- 资源用量
- 失败原因摘要
- runner id
- preview token 签发和撤销事件
必须提供运维开关:
- kill job。
- 禁用项目。
- 禁用 owner。
- 禁用模板。
- 禁用依赖。
- 清理 artifact。
- 暂停 runner。
- 全局关闭 `/editor/agent` preview build。
## 发布门禁
临时 preview artifact 不能直接升为线上作品。作品化发布必须:
- 重新构建。
- 重新校验 snapshot。
- 重新签发发布 artifact。
- 使用 immutable artifact。
- 走平台作品身份、公开详情、统计和权限链路。
@@ -0,0 +1,488 @@
# Editor Agent Mock Agent P1 落地计划
更新时间:`2026-06-15`
## 背景
`/editor/agent` 的长期目标是浏览器内 AI Web 工程编辑器:用户通过自然语言和代码编辑生成一个完整 Web 工程,并在独立沙箱中预览。当前基础方案已确定为“平台编辑器壳 + api-server 控制面 + 独立 runner + 独立 preview origin”的四层结构。
P1 阶段先不接真实 AI Agent,不调用 LLM、不接 `platform-agent`、不做 Agent 记忆、工具调用或多轮规划。P1 使用确定性的 mock Agent 生成结构化 patch,把风险集中在真正需要先验证的工程链路:项目、快照、patch 校验、静态构建、artifact、preview gateway、iframe 预览和刷新恢复。
## P1 目标
P1 完成一个可端到端验收的最小纵切:
```text
/editor/agent 输入 mock 指令
-> api-server mock Agent 生成结构化 patch
-> patch 校验并保存新 snapshot
-> 创建 preview build
-> runner 静态构建固定模板
-> 产出 immutable artifact
-> preview gateway 签发独立预览 URL
-> 前端 iframe 切换预览
```
P1 结束时,真实 Agent 仍可后置;后续只替换“需求文本 -> 结构化 patch”的来源,不推翻快照、构建和预览主链路。
## 非目标
P1 明确不做:
- 真实 LLM / Agent 调用。
- LangChain-Rust / `platform-agent` 接入。
- 任意 npm 依赖安装。
- AI 自定义 `package.json` scripts。
- HMR、长驻 dev server、WebSocket 代理。
- 终端 shell、后端服务、任意端口代理。
- Web project 作品化发布。
- 完整 lease / controller / worker 持久任务队列。
- 与主站同源预览。
- 把 AI 生成工程写入当前仓库源码目录。
## 阶段边界
P1 使用 mock Agent,但后续链路必须按生产架构实现:
| 能力 | P1 做法 | 后续替换点 |
| --- | --- | --- |
| 需求理解 | api-server 内确定性 mock 规则 | Phase 2/3 接真实 Agent |
| patch 生成 | mock Agent 返回结构化 patch | 保留同一 patch DTO |
| patch 校验 | api-server 真实校验 | 继续复用 |
| 快照保存 | SpacetimeDB 真实持久化 | 可拆对象存储优化 |
| 静态构建 | runner 真实构建固定模板 | 可换成持久队列领取 |
| artifact | 本地 / 受控 artifact store | 可换 OSS 或专用 artifact store |
| 预览 | 独立 origin / 独立端口 iframe | 生产接独立域名 |
## 与原始设计一致性检查
P1 与 2026-06-13 的技术方案、威胁模型和验收清单总体一致:mock Agent 只替换“需求文本 -> 结构化 patch”的来源,不改变“编辑器壳 -> api-server 控制面 -> runner 执行面 -> preview gateway”的信任边界。
为避免实现时放宽安全边界,P1 额外明确以下硬约束:
| 主题 | 原始设计要求 | P1 执行口径 |
| --- | --- | --- |
| Agent | AI 只能提交结构化 patch | mock Agent 也只能返回结构化 patch,并必须经过同一后端校验 |
| api-server | 控制面不执行 AI 工程代码 | api-server 可以创建 job 和触发 runner,但不得直接执行 `npm` / `vite` / 用户工程代码 |
| 允许命令 | 平台固定构建命令,禁止 AI 自定义脚本 | runner 只允许固定 argv 命令清单,不经 shell,不执行 `npm run`、`npx` 或用户传入命令 |
| 依赖 | 固定模板依赖,不开放任意 npm | P1 默认离线使用 runner 管理的模板依赖缓存;如需 registry,只能访问平台受控 mirror |
| 工作区 | 独立临时目录或容器,无宿主源码挂载 | 每个 job 使用 runner 创建的任务级临时目录;禁止挂载当前仓库源码、`.env`、Docker socket 和宿主 `node_modules` |
| 网络 | 构建期默认无外网 | 默认 deny all;只允许受控 npm mirror 和只读资产签名域,必须阻断内网、metadata、api-server、SpacetimeDB、Docker daemon |
| artifact | immutable artifact,不复用临时目录 | artifact root 由 runner 配置,不来自请求;preview 只服务 artifact,不服务工作区 |
| 预览 | 独立 preview origin | 开发态也必须使用独立端口 / origin;不得把预览挂在主站同源路径下 |
| 状态机 | 失败不覆盖上一版预览 | 只有当前 active snapshot 的 `succeeded` build 能推进 active preview |
## 契约设计
P1 新增 Web Project 契约,Rust `shared-contracts` 与前端 `packages/shared` 保持同名字段。
核心 DTO:
```text
WebProject
WebProjectSnapshot
WebProjectFile
WebProjectPatch
WebProjectPatchOperation
WebProjectPreviewBuild
WebProjectPreviewBuildEvent
MockAgentTurnRequest
MockAgentTurnResponse
```
P1 字段建议:
- `projectId`:不可枚举字符串 ID。
- `ownerUserId`:项目归属用户。
- `templateKey`:固定为 `react-vite-ts-static`。
- `activeSnapshotId`:当前编辑快照。
- `activePreviewBuildId`:当前成功预览构建。
- `files`:P1 可先存为 `Vec<WebProjectFile>` / JSON;只允许小型文本源码。
- `patchSummary`:mock Agent 或用户编辑摘要。
- `buildStatus`:`queued | running | succeeded | failed | cancelled | expired | stale`。
- `previewUrl`:由 api-server 返回给前端,不由前端拼接。
路径和内容限制必须写入契约注释和后端校验:
- 只允许相对路径。
- 拒绝绝对路径和 `..`。
- 拒绝 `.env`、`.npmrc`、`.git`、`.ssh`。
- 拒绝符号链接语义。
- 限制目录深度、单文件大小、snapshot 总大小。
- P1 只允许文本源码和少量静态资源。
- `package.json` 不是事实源;新增依赖和 scripts 在 P1 拒绝或忽略。
## 后端存储
P1 新增 SpacetimeDB 表:
```text
web_project
web_project_snapshot
web_project_preview_build
```
`web_project`:
- `project_id`
- `owner_user_id`
- `title`
- `template_key`
- `active_snapshot_id`
- `active_preview_build_id`
- `created_at`
- `updated_at`
`web_project_snapshot`:
- `snapshot_id`
- `project_id`
- `owner_user_id`
- `parent_snapshot_id`
- `template_key`
- `files_json`
- `patch_summary`
- `created_by`
- `created_at`
`web_project_preview_build`:
- `job_id`
- `project_id`
- `snapshot_id`
- `owner_user_id`
- `status`
- `logs_json`
- `artifact_id`
- `preview_token_id`
- `preview_url`
- `error_summary`
- `created_at`
- `started_at`
- `finished_at`
- `updated_at`
落点:
- `server-rs/crates/spacetime-module/src/web_project.rs`
- `server-rs/crates/spacetime-module/src/lib.rs`
- `server-rs/crates/spacetime-module/src/migration.rs`
- `server-rs/crates/spacetime-client/src/mapper/web_project.rs`
- `server-rs/crates/spacetime-client/src/web_project.rs`
如果已有表新增字段,字段必须放在结构体最后并设置明确默认值;修改字段名或字段类型前必须确认迁移计划。schema 修改后必须执行:
```bash
npm run spacetime:generate
npm run check:spacetime-schema
```
## api-server 控制面
新增模块:
```text
server-rs/crates/api-server/src/web_project.rs
server-rs/crates/api-server/src/web_project_mock_agent.rs
server-rs/crates/api-server/src/modules/web_project.rs
```
P1 API:
```text
POST /api/creation/web-project/projects
GET /api/creation/web-project/projects/{projectId}
GET /api/creation/web-project/projects/{projectId}/snapshot
PATCH /api/creation/web-project/projects/{projectId}/files
POST /api/creation/web-project/projects/{projectId}/mock-agent-turns
POST /api/runtime/web-project/projects/{projectId}/preview-builds
GET /api/runtime/web-project/preview-builds/{jobId}
GET /api/runtime/web-project/preview-builds/{jobId}/events
```
控制面职责:
- 鉴权并校验项目 owner。
- 创建固定模板项目。
- 读取 active snapshot。
- 校验用户编辑和 mock patch。
- 保存新 snapshot。
- 创建 preview build。
- 触发 P1 runner 构建。
- 记录构建日志和状态。
- 构建成功后签发 preview URL。
- 构建失败时保留上一版 active preview。
`/events` 复用 `src/services/sseStream.ts` 的事件口径。P1 可以先用 api-server 内存广播或短轮询兼容,但外部契约必须保持 SSE:
```text
queued
running
log
succeeded
failed
cancelled
expired
stale
```
## Mock Agent 规则
mock Agent 必须确定性、可测试、不可绕过 patch 校验。
输入:
```json
{
"prompt": "做一个蓝色计数按钮页面",
"baseSnapshotId": "snapshot_xxx"
}
```
输出:
```json
{
"snapshot": "...",
"patch": {
"operations": [
{
"type": "updateFile",
"path": "src/App.tsx",
"content": "..."
}
]
},
"summary": "更新首页计数按钮示例"
}
```
P1 至少支持以下 mock 指令族:
- `计数` / `按钮`:生成带计数按钮的 React 页面。
- `卡片` / `列表`:生成卡片列表页面。
- `蓝色` / `绿色` / `粉色`:调整 CSS 主题色。
- `破坏构建`:生成一个 TypeScript 编译错误,用于验收失败保留上一版预览。
- 其它输入:只更新标题和说明文案,避免 mock 分支过多。
mock Agent 输出仍必须经过统一 `validate_web_project_patch(...)`,不得直接写表。
## Runner 与构建
P1 runner 可以先采用“api-server 创建 job 后触发一次性 runner 进程”的方式,不做持久 worker 队列;但执行面必须独立于 api-server、主站源码目录和预览域。api-server 只传递 job 身份和最小任务能力,不得在自身进程内执行构建命令。
建议新增:
```text
server-rs/crates/web-project-runner
```
P1 runner 输入:
- `jobId`
- `projectId`
- `snapshotId`
- snapshot files 包。
- artifact root 配置键或受控 artifact 写入能力。
请求体、snapshot 或 mock Agent 输出中不得携带 runner 命令、构建参数、工作区路径或 artifact 输出路径。
P1 runner 步骤:
1. 创建任务级临时目录。
2. 用 runner 内置模板或 runner 管理的只读模板缓存写入固定 React / Vite / TypeScript 模板。
3. 规范化 snapshot 文件路径,写入前确认最终路径仍在任务临时目录内。
4. 忽略或重写用户 `package.json` 的危险字段。
5. 使用环境变量白名单启动子进程,清空平台密钥、`.env`、token、代理和私有 registry 配置。
6. 执行平台固定命令清单。
7. 限制 CPU、内存、磁盘、进程数、打开文件数、构建时间、日志长度、单文件大小和 artifact 大小。
8. 成功后把 `dist/` 复制到 immutable artifact 目录;artifact 目录由 runner 配置计算,不接受请求指定。
9. 失败时返回错误摘要和日志片段,日志需脱敏并避免完整宿主路径。
10. 清理临时目录。
P1 允许的构建命令只能来自平台常量,使用 argv 方式执行,不经 shell、不拼接字符串、不执行用户传入命令:
```text
npm ci --ignore-scripts --offline --no-audit --fund=false
npm exec --offline -- vite build
```
如果离线缓存不足,P1 只能改为访问平台受控 npm registry mirror,并由 runner 写入受控 `.npmrc`;用户 snapshot 中的 `.npmrc`、registry、proxy、`package-lock.json` 篡改和新增依赖必须拒绝或重写。禁止为了开发速度复用当前仓库的 `node_modules`、源码目录或本机全局 npm cache 作为 runner 工作区输入。
P1 默认网络策略为 deny all。仅当使用平台受控 registry mirror 或资产只读签名域时开放精确白名单;必须阻断 RFC1918 内网、云 metadata 地址、api-server 管理端口、SpacetimeDB、生产数据库、Docker daemon,并覆盖 HTTP redirect 和 DNS rebinding 到内网的情况。
## Preview Gateway
P1 开发态 preview 可以使用独立端口:
```text
http://127.0.0.1:<previewPort>/p/<previewToken>/
```
生产形态继续对齐独立域名:
```text
https://sandbox.genarrative.world/p/<previewToken>/
```
gateway 必须:
- 校验 token。
- 绑定 owner / project / snapshot / artifact。
- 禁止 path traversal。
- MIME 类型按白名单输出。
- `index.html` fallback 只在 artifact 根内生效。
- token 过期或 artifact 删除后返回确定错误。
- 输出 CSP,默认 `connect-src 'none'`,并禁用 Service Worker 和持久缓存。
- 不向预览页注入平台 access token、用户 cookie、SpacetimeDB、OSS 写权限或 LLM provider 密钥。
preview gateway 可以与 api-server 共享代码仓库或部署单元,但服务静态 artifact 的入口必须是独立 listener / 端口 / vhost / origin;不得把 preview artifact 挂到主站同源路径下。
前端 iframe:
```text
sandbox="allow-scripts"
```
P1 不增加 `allow-same-origin`、`allow-downloads`、`allow-popups`、`allow-top-navigation`。
## 前端落点
新增:
```text
src/components/editor/agent/WebProjectAgentEditorPage.tsx
src/components/editor/agent/WebProjectAgentEditorPage.test.tsx
src/components/editor/agent/webProjectAgentViewModel.ts
src/services/web-project/webProjectClient.ts
src/services/web-project/webProjectSse.ts
src/services/web-project/webProjectClient.test.ts
```
入口路由:
```text
/editor/agent
```
P1 桌面布局:
- 左侧文件树。
- 中间代码编辑区,P1 可先用 `<textarea>`。
- 下方或右侧 mock Agent 输入区。
- 构建日志区。
- 右侧 iframe 预览。
P1 移动端布局:
- 使用 tabs:文件、代码、预览、日志。
- 不默认展示大段功能说明文案。
- 预览 iframe 保持可见尺寸和明确加载 / 失败状态。
前端状态原则:
- active preview 来自后端返回,不由前端自行推断。
- 构建失败不清空已有 iframe。
- 刷新后先读取项目和 active snapshot,再恢复未终态 job 订阅。
- 用户连续编辑触发新 snapshot 时,旧 build 只能显示为旧日志,不得覆盖新 preview。
## 任务拆分
| 编号 | 任务 | 主要文件 | 完成标准 |
| --- | --- | --- | --- |
| P1-01 | 契约与 DTO | `shared-contracts`、`packages/shared` | 前后端类型通过,字段和状态枚举一致 |
| P1-02 | SpacetimeDB 表与 procedure/facade | `spacetime-module`、`spacetime-client` | 能创建项目、保存 snapshot、记录 build |
| P1-03 | api-server Web Project 模块 | `api-server/src/web_project.rs` | API 鉴权、owner 校验、读写 snapshot |
| P1-04 | mock Agent | `api-server/src/web_project_mock_agent.rs` | 指令生成 patch 且走统一校验 |
| P1-05 | P1 runner | `server-rs/crates/web-project-runner` | 固定模板可构建,失败有日志摘要 |
| P1-06 | preview gateway | `api-server` 或独立 runner/gateway | 独立 URL 可服务 artifact,token 校验生效 |
| P1-07 | `/editor/agent` 页面 | `src/components/editor/agent` | 端到端可创建、编辑、构建、预览 |
| P1-08 | 自动化与 smoke | 测试文件、验收脚本 | happy path 和失败保留旧预览通过 |
## 验收场景
P1 必须通过:
1. 打开 `/editor/agent`,创建固定模板项目。
2. 输入“做一个蓝色计数按钮页面”。
3. mock Agent 返回结构化 patch。
4. api-server 保存新 snapshot。
5. 前端创建 preview build。
6. runner 构建成功并产出 immutable artifact。
7. SSE 返回 `queued -> running -> succeeded`。
8. iframe 切换到新 preview URL。
9. 输入“破坏构建”。
10. 新 build 进入 failed,页面展示错误摘要。
11. 上一版 active preview 仍保留。
12. 刷新 `/editor/agent` 后,项目、active snapshot、active preview 和未终态 job 状态可恢复。
安全验收必须覆盖:
- 绝对路径被拒绝。
- `..` 被拒绝。
- `.env` / `.npmrc` / `.git` / `.ssh` 被拒绝。
- 修改 `package.json` 新增依赖被拒绝或忽略。
- 用户传入构建命令、scripts、registry、proxy 被拒绝或忽略。
- runner 构建命令不经 shell,且只来自平台 allowlist。
- runner 无平台密钥环境变量、无宿主源码挂载、无 Docker socket、无宿主 `node_modules` 输入。
- runner 只能在任务级临时目录写入 snapshot,路径解析后不能逃逸工作区。
- 构建期默认无外网;允许网络时只能访问平台白名单域名。
- artifact root 不来自请求,preview 不服务 runner 临时工作区。
- preview iframe 与主站不同 origin。
- preview gateway 输出 CSP,禁止 Service Worker 和未白名单 `connect-src`。
- failed / cancelled / expired / stale 不覆盖 active preview。
## 验证命令
后端与 schema:
```bash
npm run spacetime:generate
npm run check:spacetime-schema
cargo test -p spacetime-module web_project --manifest-path server-rs/Cargo.toml
cargo test -p spacetime-client web_project --manifest-path server-rs/Cargo.toml
cargo test -p api-server web_project --manifest-path server-rs/Cargo.toml
```
前端:
```bash
npm run test -- src/services/web-project
npm run test -- src/components/editor/agent
npm run typecheck
npm run check:encoding
git diff --check
```
浏览器 smoke:
```text
打开 /editor/agent
创建模板项目
提交一次 mock Agent 指令
等待静态构建成功
确认 iframe 展示新预览
提交一次破坏构建的 mock 指令
确认错误出现且上一版预览仍保留
刷新页面确认项目、日志和 active preview 可恢复
```
## 风险与处理
- Runner 与 api-server 边界被做薄:P1 可以由 api-server 触发 runner,但 runner 仍必须独立工作区执行,不能在主站源码目录构建。
- mock Agent 绕过校验:mock 输出必须走同一 patch DTO 和后端校验函数。
- P1 为赶进度直接同源预览:禁止;开发态也要独立端口或独立 origin。
- snapshot 存大 JSON 后续膨胀:P1 限制总大小;P2 再拆 digest/object store。
- 失败覆盖成功预览:状态机必须以 active snapshot 和 succeeded build 双条件推进 active preview。
## 后续衔接
P1 完成后进入 P2 时,优先补:
- `web_project_runtime_job` 持久任务表。
- lease / controller / worker 模式。
- 手动取消、stale、expired 和 runner crash 恢复。
- 构建日志分页与可重连 SSE。
- 真实 Agent 接入,但继续产出同一结构化 patch。
真实 Agent 接入前不得扩大 P1 的执行权限;任意依赖安装、HMR、dev server 和作品化发布必须进入后续独立评审。
@@ -0,0 +1,358 @@
# 浏览器内 AI Web 工程沙箱预览方案
更新时间:`2026-06-13`
## 背景
`/editor/agent` 需要承载一个类似 IDE 的 AI Web 工程编辑器:用户在浏览器里看到文件树、代码编辑、AI 指令输入、构建日志和实时预览,AI 生成的是一个完整 Web 工程。这个工程的构建、依赖安装和运行必须与 Genarrative 主站、当前仓库源码目录、平台密钥和正式业务数据隔离。
第一版不追求“浏览器里完整云 IDE + 任意 npm + HMR + 后端服务”。目标是先跑通一个安全、可恢复、可验收的静态 SPA 预览闭环。
## 结论
MVP 采用四层结构:
```text
平台编辑器壳 /editor/agent
-> api-server 控制面
-> 独立 web-project-runner worker
-> 独立 preview origin / preview gateway
```
第一版只支持固定 React / Vite / TypeScript 静态模板、虚拟文件系统、结构化 AI patch、平台固定构建命令、独立 runner 静态构建和独立域 iframe 预览。
MVP 明确不做:
- 终端 shell。
- 任意后端服务。
- 任意端口代理。
- HMR / 长驻 dev server。
- 任意 npm 依赖安装。
- AI 自定义 build shell script。
- diff 视图。
- 与主站同源的预览 iframe。
- 将 AI 生成工程写入当前 Genarrative 仓库源码目录。
## 分层职责
### 平台编辑器壳
入口路由固定为 `/editor/agent`。这一层只负责前端体验:
- 左侧聊天、中间预览、右侧 IDE 的水平三分布局。
- AI 指令输入、构建日志、当前项目版本和预览 iframe。
- 右侧 IDE 默认只显示文件树;展开后显示当前文件内容,并隐藏左侧聊天栏。
- 保存用户编辑和 AI patch。
- 订阅构建 job SSE 状态。
- 在构建成功后切换 iframe 的 `previewUrl`。
- 构建失败时保留上一版可用预览。
平台编辑器壳不得直接执行用户工程代码,不持有 runner 临时工作区路径,不把平台 access token、用户 cookie、SpacetimeDB 连接信息或 OSS 写权限暴露给预览页。
如果后续接入现有创作链路,入口仍应走 `play_flow` 主干和 `shared-contracts` DTO,不在前端壳或 `app.rs` 中新建平行业务流程。
### 智能体编排器
`/editor/agent` 的智能体不是普通聊天页,也不是直接生成整包代码的黑盒。它是“工程改动编排器”,负责把用户意图转成可审计、可校验、可回滚的 Web 工程 patch。
智能体职责:
- 理解用户自然语言目标,并结合当前 snapshot、文件树、选中文件和最近构建结果形成任务上下文。
- 输出结构化 patch plan,而不是直接写真实目录或执行 shell。
- 将 patch plan 提交给 api-server 校验,校验通过后生成新 snapshot。
- 创建 preview build job,并订阅 job SSE。
- 读取构建日志、错误摘要和 preview 状态,失败时继续生成修复 patch,成功时推动预览切换。
- 保留每轮 agent turn 的用户输入、上下文摘要、patch 摘要、校验结果、jobId 和最终状态,便于刷新恢复和审计。
智能体最小闭环:
```text
用户在左侧聊天输入需求
-> 前端提交 agent turn
-> api-server 读取当前 snapshot 和允许暴露的上下文
-> LLM 返回结构化 patch plan
-> api-server 校验 patch plan
-> 校验通过后生成新 snapshot
-> 创建 preview build job
-> runner 构建
-> SSE 回传 queued / running / log / succeeded / failed
-> 成功时中间预览 iframe reload
-> 失败时智能体基于错误摘要进入下一轮修复
```
智能体输出只允许以下 patch 操作:
- `create_file`
- `update_file`
- `delete_file`
- `rename_file`
- `package_manifest_request`
`package_manifest_request` 只是依赖请求,不是事实源。MVP 中 api-server 必须拒绝或忽略任意新增依赖、生命周期脚本、私有 registry、`git:` / `file:` / `http:` 依赖和 AI 自定义构建命令。
智能体不得:
- 直接写入当前 Genarrative 仓库源码目录。
- 直接访问 runner 临时工作区。
- 直接执行 shell、npm script 或任意构建命令。
- 直接拿平台 access token、用户 cookie、SpacetimeDB 连接、OSS 写权限或 LLM provider 密钥。
- 绕过 api-server 的路径、依赖、大小、敏感文件和 snapshot 校验。
### 页面布局
`/editor/agent` MVP 使用水平三分布局,不做 diff 视图:
```text
┌───────────────┬──────────────────────────────┬────────────────┐
│ 左侧聊天 │ 中间预览 │ 右侧 IDE │
│ Agent 对话 │ sandbox iframe + 状态日志 │ 默认文件树 │
└───────────────┴──────────────────────────────┴────────────────┘
```
布局规则:
- 左侧聊天承载用户输入、智能体回复、构建状态摘要和失败修复建议。
- 中间预览始终是主工作区,展示当前 active preview iframe;构建失败时保留上一版 succeeded preview,并在聊天或日志区域展示失败摘要。
- 右侧 IDE 默认只显示文件树,突出当前工程结构,不默认展示代码内容。
- 用户展开右侧 IDE 后,右侧显示文件树和当前文件内容,左侧聊天栏隐藏,中间预览与右侧 IDE 形成双栏工作模式。
- 用户收起右侧文件内容后,恢复左侧聊天 + 中间预览 + 右侧文件树三栏。
- 文件内容查看用于理解和轻量编辑当前文件,不提供 diff 视图;版本变化通过 agent turn 摘要、snapshot 时间线和构建状态表达。
- 移动端优先保持预览和聊天可用,右侧 IDE 通过抽屉或分段入口进入;展开文件内容时同样隐藏聊天。
### api-server 控制面
控制面只管理权限、快照、任务和状态,不执行 AI 工程代码:
- 鉴权和项目归属校验。
- 校验 AI patch 和用户编辑。
- 保存虚拟文件系统 snapshot。
- 创建 preview build job。
- 给 runner 发放最小任务能力。
- 输出构建日志和状态 SSE。
- 签发短期 preview URL。
- 将业务状态通过受控 procedure 或 BFF 更新到正式数据层。
api-server 不把 SpacetimeDB、OSS 写权限、LLM provider、支付、用户 cookie 或平台内部 token 传给 runner。runner 需要读取资产时,只拿只读短期签名 URL 或受控 fixture。
### web-project-runner worker
新增独立进程角色,例如 `web-project-runner`。它可以复用外部生成 worker 的“任务表 + lease + controller + worker”模式,但不应把 Web 工程执行塞进 `external_generation_job`,避免语义污染。更合适的落点是新增 `web_project_runtime_job` 或抽象出通用 job 模块。
runner 只做执行面:
- 拉取指定 snapshot。
- 展开到独立临时工作区或容器。
- 使用平台白名单模板依赖。
- 执行平台固定构建命令。
- 采集构建日志、资源用量和结果。
- 上传静态 artifact。
runner 不能反向写平台业务表。业务状态回写必须经过 api-server / SpacetimeDB 的受控入口。
### preview gateway 与独立预览域
预览页使用独立 origin,例如:
```text
https://preview-<token>.sandbox.genarrative.world
```
或者统一域名加不可枚举 token path:
```text
https://sandbox.genarrative.world/p/<previewToken>/
```
预览域不得与主站同源,不带平台 cookie,不共享主站 `localStorage` / `sessionStorage`。主站只通过 sandbox iframe 嵌入预览。
MVP 只服务静态构建产物。HMR、WebSocket 代理和长驻 dev server 后置到单独阶段评审。
## MVP 流程
```text
用户在 /editor/agent 输入需求或编辑文件
-> AI 返回结构化 patch
-> api-server 校验 patch
-> 生成 workspace snapshot
-> 前端 debounce 后创建 preview build job
-> runner claim job 并构建静态 dist
-> artifact 上传 artifact store
-> preview gateway 只读服务 artifact
-> 前端 SSE 收到 succeeded 后 iframe reload
```
失败链路:
```text
构建失败 / 超时 / runner 崩溃
-> job failed / expired
-> 前端展示错误日志摘要
-> active preview 保持上一版 succeeded artifact
```
## 接口草案
接口归属分成项目控制面和运行时控制面。
```text
POST /api/creation/web-project/projects
GET /api/creation/web-project/projects/{projectId}/snapshot
PATCH /api/creation/web-project/projects/{projectId}/files
POST /api/creation/web-project/projects/{projectId}/agent-turns
POST /api/runtime/web-project/projects/{projectId}/preview-builds
GET /api/runtime/web-project/preview-builds/{jobId}
GET /api/runtime/web-project/preview-builds/{jobId}/events
```
`/events` 复用现有 `src/services/sseStream.ts` 的 SSE 传输层口径。事件类型建议包括:
- `queued`
- `running`
- `log`
- `succeeded`
- `failed`
- `cancelled`
- `expired`
- `stale`
前端刷新恢复时,先读取项目 active snapshot 和 active preview,再按未终态 job 继续订阅 SSE。
`agent-turns` 的请求建议包含:
- `projectId`
- `baseSnapshotId`
- `message`
- `selectedFilePath`
- `visibleFilePaths`
- `recentBuildJobId`
`agent-turns` 的响应建议包含:
- `turnId`
- `status`
- `assistantMessage`
- `patchSummary`
- `validationErrors[]`
- `snapshotId`
- `buildJobId`
当 patch 校验失败时,不生成 snapshot,不创建 build job,只返回可展示的校验错误和智能体修正建议。
## 虚拟文件系统
平台内的“文件系统”是虚拟工作区,不是真实目录长期挂载。
snapshot manifest 建议包含:
- `projectId`
- `snapshotId`
- `parentSnapshotId`
- `ownerUserId`
- `templateKey`
- `files[]`
- `createdAt`
- `createdBy`
- `patchSummary`
文件项建议包含:
- `path`
- `contentDigest`
- `sizeBytes`
- `mediaType`
- `encoding`
- `executable=false`
路径必须 fail-closed:
- 只允许相对路径。
- 拒绝绝对路径。
- 拒绝 `..`。
- 拒绝符号链接。
- 拒绝超深目录。
- 拒绝超大文件。
- 拒绝隐藏敏感文件名,例如 `.env`、`.npmrc`、`.ssh`、`.git`。
- MVP 只支持文本源码和少量静态资源。
文件内容小文本可以按 digest 存对象;大一点的 snapshot 可打包成 tar / zip artifact 放受控 artifact store。图片、音频等大资产继续走现有资产链路,不让 AI 工程随意内嵌大 Data URL。
## 依赖和构建
MVP 只支持平台预置模板依赖:
- React
- Vite
- TypeScript
- 平台已审核的 UI / 工具依赖子集
`package.json` 在 MVP 中不是事实源。AI 可以提出 manifest patch,但 api-server 会重写或忽略危险字段。构建命令由平台固定,例如:
```text
npm ci --ignore-scripts
npm exec vite build
```
第一版应禁止:
- `preinstall` / `install` / `postinstall`
- `git:` / `file:` / `http:` 依赖。
- 私有 registry。
- native build。
- 二进制下载型包。
- AI 自定义 shell script。
后续若要开放依赖,必须进入“受控依赖白名单”和“隔离依赖解析服务”阶段,不混入 MVP。
## 预览状态机
```text
draft snapshot
-> build queued
-> build running
-> build succeeded
-> build failed
-> build cancelled
-> build expired
-> build stale
```
状态规则:
- 新 snapshot 触发新 build 时,旧 running build 应取消或标记 stale。
- 只有当前 active snapshot 的 `succeeded` job 可以成为 active preview。
- failed / cancelled / expired / stale 不能覆盖 active preview。
- 回滚是切回历史 snapshot 并复用对应 immutable artifact,或重新构建该 snapshot。
- 不允许复用 runner 临时目录作为回滚来源。
## 后续阶段
### Phase 0:文档和威胁模型
补齐技术方案、威胁模型和验收清单,明确 MVP 不可做能力、安全门禁、状态机和 QA 口径。
### Phase 1:静态模板预览纵切
完成 `/editor/agent` 编辑器壳、固定模板、虚拟文件系统、AI patch 保存、runner 静态构建、artifact 服务和 iframe reload。
### Phase 2:持久任务和恢复
引入 `web_project_runtime_job`,按 lease / controller / worker 模式实现任务队列、取消、stale、刷新恢复和日志 SSE。
### Phase 3:受控依赖安装
支持白名单依赖、lockfile 固化、依赖层缓存、安装失败可读错误、依赖封禁和审计。
### Phase 4:实时体验增强
在安全评审后再做长驻 dev server、HMR、WebSocket 代理、端口租约和空闲回收。
### Phase 5:作品化发布
将通过安全门禁的 Web project 接入平台作品类型。发布必须重新构建 immutable artifact,不直接提升临时预览 artifact。
## 与现有系统的关系
- 前端 SSE 读取复用 `src/services/sseStream.ts`。
- 后端 job 模式参考外部生成 worker 的 lease / controller 经验,但使用新的 Web 工程 runtime 语义。
- `/editor/agent` 是第一版用户入口,不新增重复 IDE 页面。
- 业务真相仍通过 server-rs + SpacetimeDB 受控写入,前端和 runner 都不能绕过控制面。
@@ -0,0 +1,226 @@
# AI Web 工程静态预览 MVP 验收清单
更新时间:`2026-06-13`
## 范围
本清单用于验收 `/editor/agent` 浏览器内 AI Web 工程编辑器 MVP。
MVP 必须只支持:
- 固定 React / Vite / TypeScript 静态模板。
- 虚拟文件系统。
- 结构化 AI patch。
- 独立 runner 静态构建。
- 独立 preview origin iframe 预览。
- 失败保留上一版可用预览。
- 左侧聊天、中间预览、右侧 IDE 的 `/editor/agent` 三分布局;右侧 IDE 展开文件内容后隐藏左侧聊天。
MVP 不支持:
- 终端 shell。
- 后端服务。
- HMR。
- 任意端口代理。
- 任意 npm 安装。
- AI 自定义 package scripts。
- diff 视图。
- Service Worker。
- 主站同源预览。
## Happy Path
- [ ] 用户打开 `/editor/agent` 能看到左侧聊天、中间预览和右侧 IDE 文件树。
- [ ] 右侧 IDE 默认只显示文件树,不默认显示文件内容。
- [ ] 用户展开右侧 IDE 后能看到当前文件内容,左侧聊天栏隐藏。
- [ ] 用户收起右侧文件内容后恢复左侧聊天、中间预览和右侧文件树三栏。
- [ ] 页面不展示 diff 视图。
- [ ] 创建新 Web project 后得到固定模板文件树。
- [ ] 用户在左侧聊天提交需求后创建 agent turn。
- [ ] 智能体结合当前 snapshot、选中文件和最近构建状态生成结构化 patch plan。
- [ ] AI patch plan 新增或修改一个 React 组件后,api-server 校验通过并生成新 snapshot。
- [ ] patch 校验失败时不生成 snapshot,不创建 build job,并在聊天中展示可读校验错误。
- [ ] 前端 debounce 后创建 preview build job。
- [ ] runner 构建成功并产出 immutable artifact。
- [ ] SSE 返回 `queued -> running -> succeeded`。
- [ ] iframe 切换到新 preview URL。
- [ ] 刷新 `/editor/agent` 后能恢复当前 project、active snapshot、active preview、agent turn 历史和未完成 job 状态。
## 智能体编排
- [ ] 智能体只通过 `agent-turns` 或等价受控接口提交用户需求,不直接写真实目录。
- [ ] 智能体输出只包含 `create_file/update_file/delete_file/rename_file/package_manifest_request` 这类结构化 patch 操作。
- [ ] 智能体不输出或执行 shell 命令。
- [ ] 智能体不直接触发任意 npm script。
- [ ] 智能体不直接访问 runner 临时工作区。
- [ ] 智能体不接收平台 access token、用户 cookie、SpacetimeDB 连接、OSS 写权限或 LLM provider 密钥。
- [ ] 每轮 agent turn 记录用户输入、上下文摘要、patch 摘要、校验结果、snapshotId、jobId 和最终状态。
- [ ] 构建失败后,智能体基于受脱敏的错误摘要生成下一轮修复 patch。
- [ ] 构建成功后,智能体不会覆盖非当前 active snapshot 的 preview。
## Patch 与路径校验
- [ ] 相对路径普通源码修改通过。
- [ ] 绝对路径被拒绝。
- [ ] `..` 路径被拒绝。
- [ ] 符号链接被拒绝。
- [ ] `.env` 被拒绝。
- [ ] `.npmrc` 被拒绝。
- [ ] `.git/` 被拒绝。
- [ ] `.ssh/` 被拒绝。
- [ ] 超深目录被拒绝。
- [ ] 超大单文件被拒绝。
- [ ] 超大 snapshot 被拒绝。
- [ ] 大 Data URL 被拒绝或转资产流程。
- [ ] 二进制膨胀被拒绝。
- [ ] rename 后目标路径仍需重新校验。
## 构建与状态机
- [ ] job 状态覆盖 `queued/running/succeeded/failed/cancelled/expired/stale`。
- [ ] 新 snapshot 创建后,旧 running job 被取消或标记 stale。
- [ ] 只有当前 active snapshot 的 `succeeded` job 能推进 active preview。
- [ ] failed job 不覆盖上一版 active preview。
- [ ] cancelled job 不覆盖上一版 active preview。
- [ ] expired job 不覆盖上一版 active preview。
- [ ] stale job 不覆盖上一版 active preview。
- [ ] 构建失败时展示可读错误摘要和日志片段。
- [ ] 构建超时后 runner 被 kill,job 进入 failed 或 expired。
- [ ] runner 崩溃后 job 能恢复为可重领或 expired。
- [ ] 页面刷新后能通过 projectId / jobId 恢复日志和状态。
- [ ] snapshot 回滚会复用对应 immutable artifact 或重新构建,不复用临时目录。
## Runner 隔离
- [ ] runner 进程无平台密钥环境变量。
- [ ] runner 无宿主源码目录挂载。
- [ ] runner 无 Docker socket。
- [ ] runner 使用非 root 用户。
- [ ] runner 工作区为任务级临时目录。
- [ ] 任务结束后临时目录被销毁。
- [ ] CPU 限制生效。
- [ ] 内存限制生效。
- [ ] 磁盘限制生效。
- [ ] 进程数限制生效。
- [ ] 打开文件数限制生效。
- [ ] 日志大小限制生效。
- [ ] artifact 大小限制生效。
- [ ] 任务超时限制生效。
## 网络隔离
- [ ] 构建期默认不能访问公网。
- [ ] 若允许 registry mirror,只能访问白名单域名。
- [ ] 不能访问 RFC1918 内网地址。
- [ ] 不能访问云 metadata 地址。
- [ ] 不能访问 api-server 管理端口。
- [ ] 不能访问 SpacetimeDB。
- [ ] 不能访问生产数据库。
- [ ] HTTP redirect 到内网时被阻断。
- [ ] DNS rebinding 到内网时被阻断。
## 依赖供应链
- [ ] AI 修改 `package.json` 新增普通依赖时,MVP 拒绝或忽略。
- [ ] `preinstall` 被拒绝或忽略。
- [ ] `install` 被拒绝或忽略。
- [ ] `postinstall` 被拒绝或忽略。
- [ ] `prepare` 被拒绝或忽略。
- [ ] `git:` 依赖被拒绝。
- [ ] `file:` 依赖被拒绝。
- [ ] `http:` / `https:` tarball 依赖被拒绝。
- [ ] 私有 registry 被拒绝。
- [ ] lockfile 篡改被拒绝或重写。
- [ ] 构建命令由平台固定,不执行 AI 写入的 shell script。
## Preview Token 与 Gateway
- [ ] preview token 绑定 owner。
- [ ] preview token 绑定 project。
- [ ] preview token 绑定 snapshot。
- [ ] preview token 绑定 artifact。
- [ ] preview token 短期有效。
- [ ] preview token 可撤销。
- [ ] preview token 不可枚举。
- [ ] 跨租户访问返回 403 或 404。
- [ ] path traversal 被拒绝。
- [ ] 错误 MIME 不会按可执行脚本服务。
- [ ] SPA fallback 只在 artifact 根内生效。
- [ ] artifact 删除后旧 URL 不能继续读取。
- [ ] token 过期后旧 URL 不能继续读取。
- [ ] preview cache 不串租户。
## 浏览器隔离
- [ ] 预览 origin 与主站 origin 不同。
- [ ] 预览 iframe 不带主站 cookie。
- [ ] 预览代码不能读取主站 `localStorage`。
- [ ] 预览代码不能读取主站 `sessionStorage`。
- [ ] 预览代码不能调用主站认证 API。
- [ ] iframe sandbox 不允许 top navigation。
- [ ] iframe sandbox 不允许 downloads。
- [ ] iframe sandbox 不允许 popups。
- [ ] iframe sandbox 不允许 clipboard。
- [ ] iframe sandbox 不允许 camera / mic。
- [ ] CSP 禁止未白名单 `connect-src`。
- [ ] Service Worker 被禁用。
## 日志与错误
- [ ] 构建日志按 jobId 分段展示。
- [ ] 日志限长。
- [ ] 错误摘要可读。
- [ ] 日志不包含平台 token。
- [ ] 日志不包含 OSS 写签名。
- [ ] 日志不包含完整宿主路径。
- [ ] 日志不包含环境变量 dump。
- [ ] SSE 断开后前端可重新拉取 job 状态。
## 取消、回滚与并发
- [ ] 用户连续编辑触发多个 snapshot 时,只保留最新 snapshot 的 active build 候选。
- [ ] 用户手动取消当前 build 后,runner 停止或 job 被标记 cancelled。
- [ ] 两个标签页同时编辑同一项目时,有明确版本冲突或 last-write 策略提示。
- [ ] 回滚到历史 snapshot 后,active preview 对应历史 snapshot。
- [ ] 历史 artifact 缺失时可重新构建。
- [ ] 构建队列积压时 `/editor/agent` 显示确定状态,不假装实时完成。
## Artifact GC
- [ ] 未引用的 failed artifact 会清理。
- [ ] 过期 preview artifact 会清理。
- [ ] active preview artifact 不会被误删。
- [ ] 历史 snapshot 的可回滚 artifact 按保留策略保留或可重建。
- [ ] GC 后 preview gateway 对已删除 artifact 返回确定错误。
## 最小自动化验证建议
后端 / runner:
```bash
cargo test -p api-server web_project --manifest-path server-rs/Cargo.toml
cargo test -p spacetime-module web_project --manifest-path server-rs/Cargo.toml
```
前端:
```bash
npm run test -- src/services/sseStream.test.ts
npm run test -- src/components/editor/agent
npm run typecheck
npm run check:encoding
git diff --check
```
浏览器 smoke:
```text
打开 /editor/agent
创建模板项目
提交一次 AI patch
等待静态构建成功
确认 iframe 展示新预览
提交一次故意破坏构建的 patch
确认错误出现且上一版预览仍保留
刷新页面确认项目、日志和 active preview 可恢复
```
@@ -59,7 +59,8 @@ npm run check:server-rs-ddd
- 个人中心:`/api/profile/*`,包括钱包流水、任务、领奖、充值、反馈、邀请和兑换等账号侧能力。
- 平台基础能力:`/api/llm/*`、`/api/speech/volcengine/*`,只保留通用 LLM 和语音代理。
- 资产基础能力:`/api/assets/direct-upload-tickets`、`/api/assets/sts-upload-credentials`、`/api/assets/objects/*`、`/api/assets/read-*`,负责直传、确认、绑定和读取。
- 创作 / 游玩支撑能力:`/api/creation-entry/config`、`/api/ai/tasks*`、`/api/runtime/chat/*`、`/api/runtime/settings`、`/api/runtime/save/snapshot`、`/api/profile/browse-history`、`/api/profile/save-archives*`、`/api/profile/play-stats`、`/api/assets/history`、`/api/assets/character-visual/*`、`/api/assets/character-animation/*`、`/api/assets/character-workflow-cache*`、`/api/assets/hyper3d/*`、`/api/runtime/custom-world/asset-studio/*`。
- 外部 OpenAPI:`/api/external/v1/openapi.json`、`/api/external/v1/assets/direct-upload-tickets`、`/api/external/v1/assets/objects/confirm`、`/api/external/v1/assets/read-url`、`/api/external/v1/editor/*`,使用 Bearer API Key 鉴权;API Key 管理仍在登录态 `/api/profile/api-keys`,不进入外部 OpenAPI JSON。
- 创作 / 游玩支撑能力:`/api/creation-entry/config`、`/api/ai/tasks*`、`/api/runtime/chat/*`、`/api/runtime/settings`、`/api/runtime/save/snapshot`、`/api/profile/browse-history`、`/api/profile/save-archives*`、`/api/profile/play-stats`、`/api/assets/history`、`/api/assets/character-visual/*`、`/api/assets/character-animation/*`、`/api/assets/character-workflow-cache*`、`/api/assets/hyper3d/*`、`/api/runtime/custom-world/asset-studio/*`、`/api/editor/projects*`。
- 后台入口配置:`/admin/api/creation-entry/config`、`/admin/api/creation-entry/config/banners` 和 `/admin/api/creation-entry/config/interactions`。
- 自定义世界 / RPG:`/api/runtime/custom-world*`、`/api/story/*`、`/api/runtime/chat/*`。
- 拼图:`/api/runtime/puzzle/*`。
@@ -199,10 +200,10 @@ npm run check:server-rs-ddd
- LLM:通用 LLM 门面继续使用 `GENARRATIVE_LLM_*`;创意 Agent `gpt-5` Responses / Chat Completions 文本链路已于 2026-06 从 APIMart 迁移到 VectorEngine,使用 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` 构造 OpenAI-compatible client,`api-server` 会把未带 `/v1` 的 VectorEngine base URL 规范化到 `/v1` 后请求 `/responses`。`APIMART_BASE_URL` / `APIMART_API_KEY` 只作为历史残留,不再作为创意 Agent gpt-5 客户端来源;后续排障时优先确认 VectorEngine `/v1/models`、`/v1/chat/completions` 和 `/v1/responses` 可用性。
- 图片生成:VectorEngine `gpt-image-2` 图片 provider 归属 `platform-image`,密钥只在后端环境变量中;`api-server` 内的 `openai_image_generation.rs` 只是兼容调用面和外部失败审计桥接,不再承载 provider 协议实现。实际外部生成运行记录统一落 `tracking_event`,`event_key = external_generation_run`,metadata 记录开始 / 结束时间、耗时、状态、成功标记、失败原因、provider task id 和结果摘要,不再写回过时的 `ai_task`。DashScope 只按仍在使用的历史能力单独处理,不作为 GPT-image-2 兜底。VectorEngine `/v1/images/generations` 和 `/v1/images/edits` 上游 POST 使用 `libcurl` 发送;`reqwest` 只保留给参考图 URL 下载和响应中图片 URL 下载。`/v1/images/edits` 的 multipart 参考图必须作为 libcurl 文件上传 part 发送,字段名为 `image`,实现上使用 `Form::buffer(file_name, bytes)` 并设置 `Content-Type`;不能只用 `contents(...).filename(...)`,否则上游会把请求转码为缺少图片并返回 `image is required`。`request_send` 阶段的 curl timeout / connect error 按可重试传输错误处理,最多尝试 5 次,并使用指数退避加短抖动;排障时优先看 `attempt`、`max_attempts`、`retry_delay_ms`、`reference_image_bytes_total` 和 `request_params`,不要把 `SendRequest` 当成上游业务错误。
- Match3D 物品 sheet:关卡整图完成后走 VectorEngine `/v1/images/edits` multipart `image`,模型为 `gpt-image-2`,`2K 1:1` 输出 `10*10` spritesheet;物品 sheet prompt 固定要求纯绿色绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG,并把透明整图写入 `itemSpritesheetImageSrc/itemSpritesheetImageObjectKey`。后端优先按透明 alpha 连通域从该 sheet 识别真实素材矩形并持久化 20 个物品、每个 5 个形态;识别数量不足时才回退 `10*10` 固定网格。通用系列素材图集的行列索引按每行 2 个物品计算,必须落在 `1..=10`,难度只决定运行态加载 3 / 9 / 15 / 20 种。
- Match3D UI spritesheet 和背景派生图:关卡整图作为参考图并发生成 `1K 1:1` UI spritesheet 与 `1K 9:16` 背景图,模型均为 `gpt-image-2`。UI spritesheet prompt 固定要求纯绿色绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG;背景图必须合成为全画幅不透明 PNG。
- Match3D 物品 sheet:关卡整图完成后走 VectorEngine `/v1/images/edits` multipart `image`,模型为 `gpt-image-2`,`2K 1:1` 输出 `10*10` spritesheet;物品 sheet prompt 固定要求单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG,并把透明整图写入 `itemSpritesheetImageSrc/itemSpritesheetImageObjectKey`。后端优先按透明 alpha 连通域从该 sheet 识别真实素材矩形并持久化 20 个物品、每个 5 个形态;识别数量不足时才回退 `10*10` 固定网格。通用系列素材图集的行列索引按每行 2 个物品计算,必须落在 `1..=10`,难度只决定运行态加载 3 / 9 / 15 / 20 种。
- Match3D UI spritesheet 和背景派生图:关卡整图作为参考图并发生成 `1K 1:1` UI spritesheet 与 `1K 9:16` 背景图,模型均为 `gpt-image-2`。UI spritesheet prompt 固定要求单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG;背景图必须合成为全画幅不透明 PNG。
- Match3D 1:1 容器 UI:VectorEngine `/v1/images/edits` multipart 参考图。该容器参考图是后端生图协议输入,必须通过 `include_bytes!` 随 `api-server` 编译进二进制,避免 API 单独发布或运行目录缺少 `public/` 时生成失败。
- 敲木鱼敲击物和背景环境图:VectorEngine `/v1/images/edits`,模型固定 `gpt-image-2`。敲击物支持 multipart 多参考图,第一张固定为后端内嵌默认木鱼图,用户上传图只作为新主题参考;prompt 必须要求 `1:1` 真实透明 alpha PNG 并禁止黑底、白底、棋盘格和任何实底背景。当前敲击物上传 OSS 前不做服务端抠图后处理,避免误伤玉米等主体像素。背景环境图只使用第一步抠图完成后的透明敲击物图作为参考,prompt 必须要求中央主体预留区保持干净,中央 40% 区域禁止出现主题主体、主体局部特写、轮廓影子或重复元素,主题元素只能作为外围氛围,且必须显式声明不继承任何绿色底色、绿幕底色或纯绿色画布。
- 敲木鱼敲击物和背景环境图:VectorEngine `/v1/images/edits`,模型固定 `gpt-image-2`。敲击物支持 multipart 多参考图,第一张固定为后端内嵌默认木鱼图,用户上传图只作为新主题参考;prompt 必须要求 `1:1` 单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景主体图,并禁止黑底、白底、棋盘格和任何实底背景。当前敲击物和返回按钮上传 OSS 前只做服务端绿幕去背后处理,避免泛抠图误伤玉米等主体像素。背景环境图只使用第一步抠图完成后的透明敲击物图作为参考,prompt 必须要求中央主体预留区保持干净,中央 40% 区域禁止出现主题主体、主体局部特写、轮廓影子或重复元素,主题元素只能作为外围氛围,且必须显式声明不继承任何绿色底色、绿幕底色或纯绿色画布。
- Hyper3D / Rodin:只保留后端安全代理和旧数据兼容;Rodin 提交、状态、下载和响应解析归属 `platform-hyper3d`,`api-server/src/hyper3d_generation.rs` 只做路由、配置和错误 envelope 映射;新 Match3D 草稿和批量新增不再生成 GLB。
- 音频:视觉小说专用音频路由保留;VectorEngine Suno/Vidu provider 协议、任务提交/查询、音频 URL 提取、下载、MIME/extension 归一和 OSS put 请求准备归属 `platform-audio`。`api-server/src/vector_engine_audio_generation.rs` 只做路由、配置、计费、asset object confirm、entity binding 和错误 envelope 映射;拼图、抓大鹅和敲木鱼提示词生成音效入口暂时关闭,通用 `/api/creation/audio/*` 对这些目标返回 `410 Gone`。敲木鱼创作只接收上传 / 录音音频资产;前端选择或录音阶段只在浏览器本地处理待提交音频,统一限制裁切后最长 1 秒、裁掉前后声音过小片段,并用浏览器端近似响度算法平衡到 `-15 LKFS` 后做峰值保护。点击生成时才直传 OSS 并确认 `asset_object`,创作 JSON 只提交轻量 `WoodenFishAudioAsset`,不得继续上传 Data URL 音频;未提供时由 `api-server` 写回内置默认木鱼音 `/wooden-fish/default-hit-sound.mp3`。
- OSS:私有 generated legacy path 进入浏览器前必须通过 `/api/assets/read-url` 换签;不要裸请求 `/generated-*`。前端如果收到同一 OSS bucket 的完整 `https://*.oss-*.aliyuncs.com/generated-*` 地址,也必须先归一为 legacy path 后走同一换签链路,避免裸连私有 bucket 403 或绕过签名缓存。OSS 签名、读签名、HEAD 和 PUT 的结构化日志由 `platform-oss` 输出,排查资产写入 / 确认失败时优先按 `operation`、`object_key` / `key_prefix`、`status_class`、`error_kind` 和 `elapsed_ms` 下钻。新上传 generated 私有对象默认写入 `Cache-Control: public, max-age=31536000, immutable`;旧对象若缺该头,只能依赖 `ETag` / `Last-Modified` 协商缓存,应通过 OSS 元数据刷新或 CDN 配置补齐,不要恢复 api-server 静态代理。
@@ -429,6 +430,48 @@ npm run check:server-rs-ddd
- Rust 结构体:`DatabaseMigrationOperator`
- 源码:`server-rs/crates/spacetime-module/src/migration.rs`
### `external_api_key`
- Rust 结构体:`ExternalApiKey`
- 源码:`server-rs/crates/spacetime-module/src/external_api_key_storage.rs`
- 说明:外部 OpenAPI 调用使用的账号级 API Key 凭据表,只保存 key prefix、SHA-256 hash、作用域、撤销状态和使用时间;明文 Key 只在 `/api/profile/api-keys` 创建接口返回一次,不进入 SpacetimeDB,且 API Key 管理接口不写入外部 OpenAPI JSON。v1 默认作用域为 `editor:project`、`editor:canvas`、`editor:image-generate`、`editor:asset`;其中 `editor:project` 覆盖项目列表、最近项目、创建、读取、重命名和删除,`editor:canvas` 覆盖默认画布布局保存,`editor:image-generate` 覆盖编辑器现有图片生成、重绘 / 调整、规范图、宣发素材、图标 spritesheet 生成 / 拆分、UI 设计图素材拆分、角色动画、视频、音效和背景音乐生成,`editor:asset` 覆盖素材直传凭证、素材对象确认、签名读取、账号级素材库和项目画布资源记录操作。
- 索引:`by_external_api_key_owner_user_id` 用于登录态 API Key 列表;`key_hash` 唯一索引用于外部 API 鉴权。
### `editor_project`
- Rust 结构体:`EditorProject`
- 源码:`server-rs/crates/spacetime-module/src/editor_project_storage.rs`
- 说明:图片画布工程真相表,保存 owner、标题和工程时间戳;viewport 与图层布局已拆到 `editor_canvas`,旧 layout columns 暂作为兼容列保留,不再作为权威数据源。只通过 `/api/editor/projects*` BFF 和 `spacetime-client` facade 读写;项目页列表、重命名和删除也使用该能力,删除工程时级联清理默认画布和资源元数据。
- 索引:`by_editor_project_owner_user_id` 用于读取当前用户最近编辑工程和项目页工程列表。
### `editor_canvas`
- Rust 结构体:`EditorCanvas`
- 源码:`server-rs/crates/spacetime-module/src/editor_project_storage.rs`
- 说明:图片画布数据表,归属于 `editor_project`,保存默认画布的 viewport typed columns、图层布局 JSON、owner 和时间戳;当前编辑器读取 / 保存 project 的默认 canvas,后续支持一个工程多个 canvas。
- 索引:`by_editor_canvas_project_id`、`by_editor_canvas_owner_user_id`。
### `editor_project_resource`
- Rust 结构体:`EditorProjectResource`
- 源码:`server-rs/crates/spacetime-module/src/editor_project_storage.rs`
- 说明:图片画布工程资源元数据表,保存已经放入某个 project 画布的上传 / 生成图片资源快照、OSS 引用、尺寸、来源类型、prompt、provider、task、源资源关系、`asset_kind` 和 `generation_inputs_json`。`asset_kind` 标记角色、图标、UI 设计图、视频、音频等素材类别;`generation_inputs_json` 保存用户可见生成输入快照,供图片信息页刷新后恢复。图片 / 图标 / UI 提取等生成 BFF 在请求携带 `project_id` 时负责创建该表记录并把 resource 快照返回前端;前端只保存布局引用,不能把同一生成结果再次作为正式业务真相写入。账号级素材删除不级联删除该表,避免历史画布丢图。`editor_canvas.layers_json` 只保存图层几何、层级、分组、资源引用和生成器对象;新写入不再把素材生成输入快照作为图层布局真相保存,旧布局字段只作为兼容兜底读取。
- 索引:`by_editor_project_resource_project_id`、`by_editor_project_resource_owner_user_id`。
### `editor_asset_folder`
- Rust 结构体:`EditorAssetFolder`
- 源码:`server-rs/crates/spacetime-module/src/editor_project_storage.rs`
- 说明:图片画布账号级素材文件夹表,归属于用户账号而不是 project;首次读取素材库时自动创建系统默认“项目素材”文件夹。文件夹支持重命名、折叠和删除,系统默认文件夹不能删除。
- 索引:`by_editor_asset_folder_owner_user_id`。
### `editor_asset`
- Rust 结构体:`EditorAsset`
- 源码:`server-rs/crates/spacetime-module/src/editor_project_storage.rs`
- 说明:图片画布账号级素材表,保存用户上传 / 生成素材的名称、文件夹、图片读取地址、OSS 引用、尺寸、来源类型、prompt、provider、task、`asset_kind` 和 `generation_inputs_json`。素材在同一账号的所有项目中可见;图片 / 图标 / UI 提取等生成 BFF 在请求携带 `asset_folder_id` 时负责创建账号级生成素材并返回 asset 快照,前端只用该快照更新素材栏。素材放入画布时复制为 `editor_project_resource` 并由图层引用 resourceId,画布从 resource / asset 级元数据恢复素材类别和用户可见生成输入快照。
- 索引:`by_editor_asset_owner_user_id`、`by_editor_asset_folder_id`。
### `inventory_slot`
- Rust 结构体:`InventorySlot`
@@ -0,0 +1,146 @@
# 外部 OpenAPI 与 API Key 接入方案
## 背景
外部调用方需要通过稳定 HTTP 契约使用图片画布编辑器内的素材生成、编辑和管理能力,并能创建项目、保存画板布局和管理账号级素材库。该能力必须走 `server-rs + Axum + SpacetimeDB` 正式链路,不能把 API Key、画板状态、素材状态或生成结果放到前端临时状态中。
## v1 范围
本期新增外部 API 命名空间:
```text
/api/external/v1
```
v1 只开放以下能力:
- `POST /api/external/v1/assets/direct-upload-tickets`:创建素材直传 OSS 凭证。
- `POST /api/external/v1/assets/objects/confirm`:确认已上传素材对象,`ownerUserId` 固定为 API Key 所属账号。
- `GET /api/external/v1/assets/read-url`:获取私有素材读取签名 URL。
- `GET /api/external/v1/editor/projects`:列出当前 API Key 所属账号的图片画布项目。
- `POST /api/external/v1/editor/projects`:创建图片画布项目。
- `GET /api/external/v1/editor/projects/recent`:读取当前账号最近图片画布项目。
- `GET /api/external/v1/editor/projects/{projectId}`:读取项目与默认画布。
- `PATCH /api/external/v1/editor/projects/{projectId}/metadata`:更新图片画布项目标题。
- `DELETE /api/external/v1/editor/projects/{projectId}`:删除图片画布项目,并级联清理默认画布和项目资源元数据。
- `PATCH /api/external/v1/editor/projects/{projectId}/canvas`:保存默认画布的 viewport 和 layers。
- `POST /api/external/v1/editor/projects/{projectId}/resources`:创建项目画布资源记录。
- `GET /api/external/v1/editor/assets/library`:读取账号级编辑器素材库。
- `POST /api/external/v1/editor/assets/folders`:创建素材文件夹。
- `PATCH /api/external/v1/editor/assets/folders/{folderId}`:更新素材文件夹名称或折叠状态。
- `DELETE /api/external/v1/editor/assets/folders/{folderId}`:删除素材文件夹并返回最新素材库。
- `POST /api/external/v1/editor/assets`:创建素材记录。
- `PATCH /api/external/v1/editor/assets/{assetId}`:更新素材名称或所在文件夹。
- `DELETE /api/external/v1/editor/assets/{assetId}`:删除素材记录。
- `POST /api/external/v1/editor/images/generations`:调用编辑器图片素材生成能力;通过 `kind` 支持普通图、规范图 `spec`、角色图 `character`、快速编辑参考图 `quick-edit`、UI 设计图 `ui-design` 和宣发素材 `publication-material`。可选传入 `projectId` 和 `assetFolderId`,生成后按站内编辑器规则写入 `editor_project_resource` 和账号级 `editor_asset`。
- `POST /api/external/v1/editor/images/edits`:重绘 / 调整已有图片,结果可写入项目资源和素材库。
- `POST /api/external/v1/editor/icon-spritesheets/generations`:按规范图生成图标 spritesheet,并拆分为独立图标素材。
- `POST /api/external/v1/editor/ui-designs/assets/extractions`:从 UI 设计图中提取 / 拆分素材。
- `POST /api/external/v1/editor/character-animations/generations`:基于角色图片生成角色动画预览和帧序列。
- `POST /api/external/v1/editor/videos/generations`:生成编辑器视频素材,支持现有 Seedance / Kling / Veo 模型参数和参考媒体限制。
- `POST /api/external/v1/editor/audios/sound-effects/generations`:生成编辑器音效素材。
- `POST /api/external/v1/editor/audios/background-music/generations`:生成编辑器背景音乐素材。
- `GET /api/external/v1/openapi.json`:导出本版本 OpenAPI 3.1 JSON。
管理 API Key 的登录态接口保留在站内个人中心链路,但不写入外部 OpenAPI JSON:
```text
GET /api/profile/api-keys
POST /api/profile/api-keys
DELETE /api/profile/api-keys/{keyId}
```
前端入口位于登录后个人中心的 `我的 → 开发者 API Key`,用于查看当前 Key、创建新 Key、复制一次性明文和撤销已创建 Key。外部 OpenAPI 只描述 `/api/external/v1` 下可由 API Key 调用的接口,不混入登录态 API Key 管理接口。
## 鉴权
外部调用使用 Bearer API Key:
```http
Authorization: Bearer tnr_sk_xxx
```
规则:
- API Key 归属于 `owner_user_id`,外部接口只能访问该账号自己的项目、画布和生成素材。
- 明文 Key 只在创建接口返回一次,后端只保存 `key_hash` 与 `key_prefix`。
- API Key 被撤销后立即不可再用于外部接口。
- 外部 API 鉴权不复用登录态 JWT,不检查 refresh session;它是独立开发者凭据。
- OpenAPI JSON 公共可读,不需要鉴权。
## 数据模型
新增 SpacetimeDB private 表:
```text
external_api_key
```
字段:
- `key_id`:主键。
- `owner_user_id`:所属账号。
- `name`:用户可识别名称。
- `key_prefix`:前缀片段,用于列表展示和排障。
- `key_hash`:完整 Key 的 SHA-256 十六进制摘要,唯一。
- `scopes_json`:作用域 JSON,v1 固定包含 `editor:project`、`editor:canvas`、`editor:image-generate`、`editor:asset`。其中 `editor:image-generate` 覆盖图片生成、重绘、规范图、宣发图、图标拆分、UI 素材拆分、角色动画、视频、音效和音乐生成。
- `created_at` / `last_used_at` / `revoked_at` / `updated_at`。
SpacetimeDB procedure:
- `create_external_api_key_and_return`
- `list_external_api_keys_and_return`
- `revoke_external_api_key_and_return`
- `authenticate_external_api_key_and_return`
## 素材生成与落库
外部生成接口复用站内编辑器已有 handler 和 DTO,不维护第二套生成语义:
- 图片生成 / 重绘 / 规范图 / 宣发图 / UI 设计图复用 `/api/editor/images/generations` 与 `/api/editor/images/edits` 的校验、模型归一、计费和持久化规则。
- 图标 spritesheet 和 UI 设计图素材提取复用站内拆分逻辑,生成图集后按连通域切片,并把图集与切片都按请求写入项目资源和素材库。
- 角色动画、视频、音效和背景音乐复用站内编辑器生成链路;音频类外部调用使用 API Key 所属账号作为 asset owner。
- API Key 管理接口仍只属于登录态个人中心,不进入外部 OpenAPI JSON。
图片类外部生成成功后,后端拿到素材后:
1. 通过 OSS / asset object adapter 持久化图片。
2. 写入 `editor_asset`,让生成图进入账号级素材库。
3. 如果请求带 `projectId`,写入 `editor_project_resource`。
4. 返回图片读取地址、素材 ID、资源 ID、尺寸、prompt、model、provider 和 taskId。
如果请求未带 `projectId`,只生成并写入素材库;调用方可随后创建项目或自行保存画板布局。
## 素材与项目资源操作
外部素材操作复用站内图片画布素材库和项目资源的后端事实源:
- 外部上传素材时先调用 `POST /api/external/v1/assets/direct-upload-tickets` 获取 OSS 表单直传参数,上传完成后调用 `POST /api/external/v1/assets/objects/confirm` 写入 `asset_object`,再用 `assetObjectId` / `objectKey` 创建账号级素材或项目资源记录。
- 外部读取私有 generated / uploaded 素材预览时调用 `GET /api/external/v1/assets/read-url` 获取短期签名 URL;不开放浏览器 STS 写权限。
- 素材库读取、文件夹创建 / 更新 / 删除、素材创建 / 更新 / 删除、项目画布资源创建全部通过 `api-server -> spacetime-client -> spacetime-module`。
- 所有外部素材接口使用 API Key 所属的 `owner_user_id`,不能由请求体传入 owner。
- 素材库仍是账号级事实源,不归属于单个项目;项目画布内资源继续使用 `editor_project_resource`。
- 素材创建和项目资源创建接口只保存记录和元数据;图片二进制上传、签名读取和生成图持久化继续复用已有资产 / 生成链路。
## OpenAPI 导出
OpenAPI 3.1 JSON 固定落在:
```text
docs/openapi/genarrative-external-v1.openapi.json
```
服务端 `GET /api/external/v1/openapi.json` 使用同一份 JSON,通过 `include_str!` 导出,避免运行时生成结果与仓库文档漂移。
## 验收
- API Key 创建只返回一次明文,列表不返回明文。
- 撤销后的 API Key 调用外部接口返回 `401`。
- 外部图片生成、重绘、图标拆分和 UI 素材拆分成功后,生成结果按请求同时出现在画布资源和账号级素材库。
- 外部视频、角色动画、音效和音乐接口使用站内编辑器相同的请求校验、模型限制和价格校验。
- OpenAPI JSON 能被 `serde_json` 解析,且 security scheme 为 Bearer API Key。
- OpenAPI JSON 不包含 `/api/profile/api-keys`、`UserAccessToken` 或 API Key 管理 schema。
- 外部素材库接口覆盖当前已有素材操作:直传凭证、素材对象确认、签名读取、读取素材库、创建 / 更新 / 删除文件夹、创建 / 更新 / 删除素材、创建项目画布资源。
- 外部项目接口覆盖当前已有项目管理操作:项目列表、最近项目、创建、读取、重命名、删除和默认画布保存。
- 外部素材生成接口覆盖当前已有编辑器素材操作:生图、重绘 / 调整、规范图生成、宣发素材生成、图标素材生成与拆分、UI 设计图生成与拆分、角色动画、视频、音效和背景音乐。
- 修改 SpacetimeDB schema 后运行 `npm run spacetime:generate` 与 `npm run check:spacetime-schema`。
@@ -49,6 +49,8 @@ Linux 本机多用户并发开发时,`npm run dev` 和 `npm run dev:*` 单模
后端日志默认写入 `logs/api-server/`。后端 API smoke 使用 `npm run dev:api-server` 并检查 `/healthz`;需要确认实例可接生产流量时检查 `/readyz`。不要使用旧 `api-server:maincloud` 或任何 `GENARRATIVE_SPACETIME_MAINCLOUD_*` 口径。
Windows 本地 `npm run dev` / `npm run dev:api-server` 会用空的 `RUSTC_WRAPPER` / `CARGO_BUILD_RUSTC_WRAPPER` 覆盖 `server-rs/.cargo/config.toml` 里的 `sccache`,从而直连真实 `rustc`。不要把 wrapper 绕过值写成 `rustc`;Cargo 会按 wrapper 协议调用 `rustc <真实rustc路径> - ...`,最终报 `multiple input filenames provided` 并导致 api-server 无法启动。排查本地启动失败时,先看 dev 日志是否出现该错误,再确认脚本注入的 wrapper 为空。
开发态 `npm run dev` 与 `npm run dev:api-server` 会默认注入 `GENARRATIVE_DEV_PASSWORD_ENTRY_AUTO_REGISTER_ENABLED=true`,因此密码登录在本地开发环境可直接注册未知手机号账号;生产环境仍按 `api-server` 配置默认关闭该开关。
本地 `npm run dev` 和 `npm run dev:api-server` 默认保留 inline 开发体验:未显式设置 `GENARRATIVE_EXTERNAL_GENERATION_MODE=queue` 时,外部生成 handler 会同步复用 worker executor,完成后返回 `completed`,便于快速确认 provider、OSS 和 SpacetimeDB 写回链路。inline 不创建 `external_generation_job`,也不能验证 worker lease、队列等待展示或动态扩缩容。
@@ -0,0 +1,208 @@
# 创作主页与项目入口改版计划
日期:2026-06-18
## 背景
当前平台首页中的“创作”仍偏向站内 Tab 和创作入口列表,“草稿”也仍作为一级入口存在。图片画布项目已经成为独立项目资产,需要在桌面端给用户一个更明确的创作工具主页,并把项目管理提升为一级入口。
本轮改版参考外部创作工具首页的信息组织,但所有用户可见文案、测试命名和产品命名都使用“陶泥儿”。外部品牌只作为设计参考,不进入产品 UI、测试名或文档验收口径。
## 目标
- 新增桌面端独立 `/creation` 创作工具主页。
- 桌面端直接打开站点首页 `/` 时默认展示新版创作主页内容;移动端继续保持原推荐首页。
- 顶级导航中“创作”进入 `/creation`,“草稿”改为“项目”并进入 `/project`。
- 移动端隐藏“创作”和“项目”入口,只保留浏览和个人相关入口。
- 登录后在创作主页展示最近项目,并通过新建项目进入 `/editor/canvas?projectid=xxx`。
- `陶泥儿精选` 展示真实账号素材瀑布流,不使用 mock 素材。
- 现有 `/creation/<play>` 玩法工作台、草稿、作品架、生成恢复和发布链路保持不变。
## 页面结构
### 顶部品牌区
- 标题:`陶泥儿 - 开启全民精品游戏创作`
- 主按钮:`开始创作`
- 社群入口:原地弹出“玩家社区”二维码弹窗,复用现有社区弹层能力,不跳转或切换到“我的 / 用户中心”。
- 营销点:`登录即送100泥点,可以免费制作50个素材`
`开始创作` 与“新建项目”使用同一项目创建链路。用户已登录时调用现有 `createEditorProject`,成功后进入 `/editor/canvas?projectid=xxx`;未登录或登录过期时打开登录弹窗,并在登录后重试创建。
该区是页面第一屏的品牌与主操作区域,不使用外站品牌名。桌面端 `/creation` 继续保留现有平台左侧导航栏和上方栏,创作主页只替换中间内容区。
### 九大创作工具能力
创作主页展示九项能力,用于说明陶泥儿创作工具覆盖范围:
1. 游戏视觉规范:轻松约束多类素材视觉一致性。
2. 游戏角色:整套高完成度 2D / 3D 角色素材及动画。
3. 游戏 UI:轻松生成高一致性的 UI 素材。
4. 游戏音乐:轻松制作游戏背景音乐、音效。
5. 游戏特效:低成本生成精品游戏特效。
6. 游戏场景:轻松生成各类游戏场景素材。
7. 游戏美宣:轻松生成游戏上架所需图片和视频。
8. 游戏功能编码:自然语言驱动游戏生成。
9. 一键导出:支持一键导出为微抖小游戏、Godot、Unity、UE 工程。
### 最近项目
仅登录后展示。模块位置在“陶泥儿精选”上方。
- 标题:`最近项目`
- 标题右侧:`查看全部`,跳转 `/project`
- 左侧第一项:`新建项目`
- 右侧项目卡:按 `updatedAt` 倒序展示最近 4 个项目。
- 项目卡点击进入 `/editor/canvas?projectid=xxx`。
- 项目卡点击必须直接写入最终 `/editor/canvas?projectid=xxx` 历史项,不允许先进入无参数 `/editor/canvas` 再二次跳转,否则浏览器返回需要点两次。
- 项目缩略图复用 `/project` 项目卡的画布中心封面逻辑,避免两套封面算法。
未登录状态不展示最近项目区,避免空态文案占据首屏。
### 陶泥儿精选
`陶泥儿精选` 是页面底部的用户素材瀑布流,不承载玩法入口列表。瀑布流卡片按真实素材宽高设置预览比例,同一行允许出现不同高度卡片,不使用固定等高网格。创作入口配置仍继续来自 `/api/creation-entry/config`,供旧创作入口和具体 `/creation/<play>` 工作台使用,但不作为本页精选区内容。
精选内容优先使用真实账号级素材和生成结果数据;未登录或账号素材为空时,可使用已公开作品中真实用户生成的图片作为瀑布流素材来源。两种来源都不使用 mock。若现有数据缺少提示词、作者名或成本字段,v1 显示保守占位,不伪造内容。
Tab:
- 素材包
- 角色
- UI
- 音乐
- 特效
- 场景
- 美宣
- 游戏
展示口径:
- 素材包:每个列表项展示同一规范图生成的规范图和相关素材组合。
- 角色:每个列表项展示游戏角色图和该角色生成的角色动画素材组合。
- UI:每个列表项展示游戏 UI 图和该 UI 图拆出的子素材组合。
- 音乐:展示背景音乐和音效。
- 美宣:展示美宣类图片和视频素材。
- 特效、场景、游戏:v1 暂不实装生成链路,只在有真实数据时展示,无数据时不造假卡片,也不在 UI 中写“暂不实装”说明。
交互口径:
- 点击任一精选列表项后打开接近全屏的素材预览弹窗。
- 弹窗中部展示当前素材,图片使用 contain 完整展示,视频和音频展示可播放控件。
- 弹窗底部固定显示当前组合的缩略图导航,点击缩略图切换当前素材。
- 弹窗底部同时展示该列表项的提示词、作者名和生成总成本。
每个列表项需要标注:
- 提示词
- 作者名
- 生成总成本,单位为泥点
数据来源:
- 图片类素材优先读取账号级素材库和已落库生成结果。
- 未登录或账号素材为空时,读取已公开作品中的真实用户生成图片作为精选素材补充,但卡片仍按素材瀑布流呈现,不展示玩法入口卡。
- 音乐、音效、视频类素材只展示已存在的真实记录。
- 不为了填满展示区创建假素材、假作者、假泥点成本或假组合关系。
- 暂无真实数据的 Tab 保留 Tab 入口,但内容区显示简洁空态。
## 数据与状态边界
- `/creation` 是创作工具主页,不承接正式玩法业务真相。
- 创作入口配置继续从 `/api/creation-entry/config` 读取。
- 最近项目继续使用编辑器项目接口:`listEditorProjects`、`createEditorProject` 和 `/editor/canvas?projectid=xxx`。
- 最近项目和项目页打开画布时,浏览器 history 只保留带 `projectid` 的最终画布路由。
- 项目封面逻辑复用 `/project` 项目卡已有的画布中心缩略图算法。
- `陶泥儿精选` 读取账号级素材库;若字段缺失,前端只显示空值或隐藏对应元信息,不在前端推导业务真相。
- `/creation/<play>` 的玩法工作台、草稿、生成页、结果页、发布、运行态和作品架链路保持原状。
## 路由与导航
- 新增 `SelectionStage = 'creation-home'`。
- `/creation` 映射到 `creation-home`。
- 桌面端初始打开 `/` 时也进入 `creation-home`;移动端初始打开 `/` 仍进入 `platform` 推荐首页。
- `/creation/<play>` 现有玩法工作台路由保持原有 stage 映射。
- 桌面端点击“创作”时调用 `pushAppHistoryPath('/creation')`。
- 桌面端原“草稿”入口改为“项目”,点击 `pushAppHistoryPath('/project')`。
- 桌面端平台一级页统一复用 `/creation` 的平台桌面 chrome,保留左侧完整导航栏和右侧顶部栏;推荐、发现、我的、`/creation` 与 `/project` 只替换右侧主内容区,不能作为脱离导航的独立全屏页渲染,避免用户进入任一页后无法返回或切换页签。
- 桌面端从 `/creation` 切换到 `/project` 后,“项目”页签进入选中态,“创作”页签必须恢复为普通默认态,不保留创作主页的主按钮强调表现。
- `/project` 项目页头部必须提供 `返回主站` 按钮,点击后切回平台首页 `/`,行为与桌面侧栏切回推荐页保持一致。
- 移动端导航列表不展示“创作”和“项目”。
- 移动端不拦截用户直接访问 `/creation` 或 `/project`。
## 实施拆分
### 路由与导航
- 增加 `/creation` 路由映射和测试。
- 增加桌面 `/` 初始阶段映射测试,确认不会影响移动端首页。
- 调整桌面导航文案与点击行为。
- 移动端导航移除“创作”和“项目”入口。
- 保持现有 `/creation/<play>` 工作台、生成页、结果页和运行态路由不回归。
### 创作主页
- 新增 `CreationLandingView`。
- 实现首屏主视觉、九大功能区、最近项目区和陶泥儿精选素材瀑布流。
- 桌面端复用现有平台壳层,保留上方栏和左侧导航栏。
- 主页整体采用浅色布局,避免外站品牌文案。
- 视觉排版参考陶土质感创作工具工作台:首屏标题居中、主按钮与社区按钮使用大号圆角胶囊尺寸,九大创作工具以 3 列大卡片展示,卡片左侧保留大号陶土感图标区域,右侧展示原有标题与描述;桌面平台一级页的顶部栏和左侧导航统一压平成单层工作台 chrome,避免外框套内框;桌面端页面结构必须先分为“左侧完整栏 / 右侧内容区”,左侧完整栏同时包含顶部品牌 Logo 和下方导航页签,右侧内容区再拆为顶部搜索、钱包、账号栏与下方主屏,不能用顶部栏的第一列假装左侧品牌区;左侧栏采用紧凑但保留图标和文字的导航胶囊,顶部搜索栏、泥点入口和账号入口都保持精简宽度,提升推荐、发现、我的、创作主页和项目页的工具感与精致度;只调整样式与布局,不改文案、搜索、登录、导航、项目创建、社区弹层、最近项目、精选素材 Tab 或数据读取逻辑。
- 背景、顶部品牌小陶偶、左侧导航、按钮和九大创作工具图标使用 `gpt-image-2` / image2 生成资源,统一保存在 `public/creation-home/`;资源只作为装饰图标或背景纹理接入,不包含图内文字,不替换或隐藏现有 UI 文案和交互。
- 社群入口复用现有玩家社区弹层,点击后必须停留在 `/creation`。
- 不在 UI 中放规则说明、实现说明或“暂不实装”提示。
- 未登录时不渲染最近项目区;需要创建项目时走登录弹窗和登录后重试。
### 项目卡与封面复用
- 从 `ProjectGalleryView` 抽出项目封面模型或组件。
- `/creation` 最近项目与 `/project` 共用同一封面逻辑。
- 新建项目复用现有 `createEditorProject`、`listEditorProjects` 和 `/editor/canvas?projectid=xxx` 链路。
### 我的页
- 删除“项目 / 画布项目”快捷入口。
- 常用功能区稳定为 4 项:泥点充值、兑换码、玩家社区、反馈与建议。
## 测试计划
单元和组件测试:
- `appPageRoutes.test.ts`:覆盖 `/creation -> creation-home`,并确认现有 `/creation/<play>` 不回归。
- `RpgEntryHomeView.recharge.test.tsx`:桌面显示“创作”“项目”,不显示“草稿”;移动端不显示“创作”和“项目”;我的页不再出现项目快捷入口。
- `CreationLandingView.test.tsx`:覆盖首屏主视觉、九大功能、最近项目、新建入口、查看全部、未登录隐藏最近项目、素材 Tab。
- `PlatformEntryFlowShellImpl` 相关测试:点击“创作”进入 `/creation`,点击“项目”进入 `/project`,项目页头部“返回主站”回到 `/`,新建项目进入 `/editor/canvas?projectid=xxx`。
- `ProjectGalleryView.test.tsx`:封面抽取后项目封面、头部“返回主站”、重命名、删除和选择模式保持通过。
验证命令:
```bash
npm run test -- src/routing/appPageRoutes.test.ts src/components/project/ProjectGalleryView.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx
npm run typecheck
npm run check:encoding
git diff --check
```
浏览器回归:
- 桌面打开站点首页 `/`,首屏显示新版创作主页。
- 桌面打开首页,点击“创作”进入 `/creation`。
- `/creation` 显示 `陶泥儿 - 开启全民精品游戏创作` 和 `陶泥儿精选`。
- 页面不出现外站品牌或 `Discord` 字样。
- 未登录时最近项目区不展示,点击“开始创作”弹登录。
- 登录后显示最近项目、新建项目、最多 4 个项目和“查看全部”。
- 点击新建项目进入 `/editor/canvas?projectid=xxx`。
- 桌面导航显示“项目”,不显示“草稿”。
- `/project` 头部点击“返回主站”回到 `/`,并退出项目页主内容。
- 移动视口底部不显示“创作”和“项目”。
- “我的”页没有项目快捷入口。
- 陶泥儿精选素材瀑布流不出现 mock 素材、假作者或假泥点成本。
- 陶泥儿精选素材卡按真实素材宽高呈现瀑布流,不退化为固定等高网格。
- 陶泥儿精选不出现玩法入口卡;即使使用公开作品图片补充展示,也必须呈现为用户素材瀑布流。
## 非目标
- 不新增后端接口。
- 不删除现有玩法草稿、作品架、生成恢复、发布或运行态能力。
- 不恢复前端硬编码创作入口配置。
- 不用 mock 素材填充用户素材展示。
- 不在移动端禁止直接 URL 访问 `/creation` 或 `/project`。
@@ -139,7 +139,7 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列
- 前端创作、结果页、生成页和错误提示不展示 GPT / Gemini 等具体模型名称;如需在内部保留模型路由,UI 只使用“标准模式”“创意模式”等产品化名称。
- 若浏览器锁屏、息屏或网络切换导致 compile 请求失败,前端在标记失败前必须先复读 `getPuzzleAgentSession(sessionId)`;只有最新 session 仍缺 `draft.coverImageSrc`、首关 `coverImageSrc` 或候选图时才展示失败,复读到已生成草稿时按成功收尾、刷新作品架并继续自动试玩/结果页链路。
- 拼图参考图 AI 重绘走 VectorEngine `/v1/images/edits`;无参考图时走 `/v1/images/generations`。两者模型都使用 `gpt-image-2`,参考图由后端作为 multipart `image` part 传入编辑接口。
- 每次新建关卡生成或重新生成关卡图都必须由 `api-server` 串起当前关卡资产包:AI 重绘开启时第一段沿用草稿生成第一关的拼图主图提示词配置和模型 / 尺寸 / 参考图规则生成 `coverImageSrc/coverAssetId` 作为关卡拼图画面和结果页预览图,提示词来源同样按显式画面描述、关卡画面描述、草稿摘要顺序回退,且固定要求输出画面比例为 `1:1`;上传图且关闭 AI 重绘时跳过这一段,把上传图或历史图持久化为 `sourceType=uploaded` 的正式候选。随后用正式候选图作为参考,`9:16` 生成完整拼图游戏关卡画面并写入 `levelSceneImageSrc/levelSceneImageObjectKey`,提示词必须要求道具按钮上不要显示次数标注,且返回按钮和设置按钮旁禁止标注文字;UI spritesheet 与关卡纯背景在关卡画面完成后并发生成,spritesheet 用 `1:1`、`1k` 先生成纯绿色绿幕背景图,后端上传 OSS 前必须把绿幕扣成透明 PNG,再写入 `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`,按钮顺序固定为返回、设置、下一关、提示、原图、冻结,按钮素材自身保留对应中文文字,返回和设置按钮不得额外生成白色外圈、白底圆环或浮雕外框;纯背景用 `9:16`、`1k` 写入 `levelBackgroundImageSrc/levelBackgroundImageObjectKey`,提示词必须包含“禁止在背景中出现人像或和拼图画面中主体一致的内容”。运行态不直接使用第二段完整关卡画面,但必须持久化它用于追踪和后续再生成。结果页局部关卡生成进度按 AI 重绘开启约 270 秒、关闭 AI 重绘约 180 秒展示。
- 每次新建关卡生成或重新生成关卡图都必须由 `api-server` 串起当前关卡资产包:AI 重绘开启时第一段沿用草稿生成第一关的拼图主图提示词配置和模型 / 尺寸 / 参考图规则生成 `coverImageSrc/coverAssetId` 作为关卡拼图画面和结果页预览图,提示词来源同样按显式画面描述、关卡画面描述、草稿摘要顺序回退,且固定要求输出画面比例为 `1:1`;上传图且关闭 AI 重绘时跳过这一段,把上传图或历史图持久化为 `sourceType=uploaded` 的正式候选。随后用正式候选图作为参考,`9:16` 生成完整拼图游戏关卡画面并写入 `levelSceneImageSrc/levelSceneImageObjectKey`,提示词必须要求道具按钮上不要显示次数标注,且返回按钮和设置按钮旁禁止标注文字;UI spritesheet 与关卡纯背景在关卡画面完成后并发生成,spritesheet 用 `1:1`、`1k` 先生成单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景图,后端上传 OSS 前必须把绿幕扣成透明 PNG,再写入 `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`,按钮顺序固定为返回、设置、下一关、提示、原图、冻结,按钮素材自身保留对应中文文字,返回和设置按钮不得额外生成白色外圈、白底圆环或浮雕外框;纯背景用 `9:16`、`1k` 写入 `levelBackgroundImageSrc/levelBackgroundImageObjectKey`,提示词必须包含“禁止在背景中出现人像或和拼图画面中主体一致的内容”。运行态不直接使用第二段完整关卡画面,但必须持久化它用于追踪和后续再生成。结果页局部关卡生成进度按 AI 重绘开启约 270 秒、关闭 AI 重绘约 180 秒展示。
- 结果页允许多关卡并行编辑和生成;某一关卡图片生成完成回包只静默更新该关卡素材与生成态,不得自动打开或切换关卡详情面板,避免打断用户正在编辑的其它关卡。
- 结果页关卡图片生成只标记对应关卡的局部生成进度,不禁用“新增关卡”、其它关卡详情编辑和结果页导航。
- 结果页单关测试只能把完整草稿持久化,并通过 `levelId` 指定运行态起始关卡;不得把单关快照作为整份草稿调用 `updatePuzzleWork`,否则 source session 和作品 profile 的 `levels` 会被覆盖成单关,退出重进后其它关卡会丢失。
@@ -219,7 +219,7 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列
3. `功德有什么`:最多 8 条飘字,创作态首屏只保留一个默认词条 `幸运`,其下提供加号格继续追加词条;创作态只保存词条名,运行态飘字展示时再追加 `+1`。运行态顶部总数卡采用品牌化徽标样式,子项计数器预置展示在可展开面板中,未出现词条初始值为 0。
4. `作品标题 / 作品简介 / 主题标签`:不再放在创作工作台首屏,改为生成草稿后的结果页补录区,提交试玩或发布前必须先写回当前作品信息。主题标签编辑样式对齐拼图结果页的胶囊标签编辑器。
图片生成链路固定为三图 image2 流程:第一步用默认木鱼图作为结构和画风参考,按用户题材关键词或参考图主题生成 `1:1` 绿色背景主体图(纯绿色绿幕),prompt 必须显式要求背景为单一纯绿色 `#00FF00` 且平整无纹理、无渐变、无阴影、无道具,主体完整居中,且禁止黑底、白底、棋盘格和任何实底背景;后端在落库前只对这张绿幕主体图执行去绿背景处理,不做泛抠图,避免误伤玉米等主体像素。第二步必须使用第一步抠图完成后的透明图作为参考图,再用新敲击物作为主题和画风参考生成 `9:16` 背景环境图,背景图只适配主题和画风,不能包含新敲击物本体,也不能增加木槌互动物品;画面中央主体预留区必须干净,中央 40% 区域禁止出现主题主体、主体局部特写、轮廓影子或重复元素,主题元素只能作为外围氛围。第三步必须使用去绿后的敲击物主体图和背景环境图作为参考图生成 `1:1` 返回按钮图,返回按钮必须始终是标准圆形,主体视觉尺寸比当前模板再放大约 50%,圆形外沿必须有与主题色搭配的干净外描边,中央只保留单个左箭头,参考图只约束圆形底色和箭头配色,不得延伸到复杂造型和花纹;按钮不得出现文字、数字、水印、额外 UI 面板或木槌物品。三个资产分别写回 `hitObjectAsset`、`backgroundAsset` 与 `backButtonAsset`,并绑定到 `wooden_fish_work` 的 `hit_object` / `background` / `back_button` 槽位。运行态和结果页消费 `backgroundAsset` 做竖屏背景,中央再叠加 `hitObjectAsset`,左上角返回按钮消费 `backButtonAsset`。
图片生成链路固定为三图 image2 流程:第一步用默认木鱼图作为结构和画风参考,按用户题材关键词或参考图主题生成 `1:1` 单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景主体图,prompt 必须显式要求背景严格使用该固定色且平整无纹理、无渐变、无阴影、无道具,主体完整居中,且禁止黑底、白底、棋盘格和任何实底背景;后端在落库前只对这张绿幕主体图执行去绿背景处理,不做泛抠图,避免误伤玉米等主体像素。第二步必须使用第一步抠图完成后的透明图作为参考图,再用新敲击物作为主题和画风参考生成 `9:16` 背景环境图,背景图只适配主题和画风,不能包含新敲击物本体,也不能增加木槌互动物品;画面中央主体预留区必须干净,中央 40% 区域禁止出现主题主体、主体局部特写、轮廓影子或重复元素,主题元素只能作为外围氛围。第三步必须使用去绿后的敲击物主体图和背景环境图作为参考图生成 `1:1` 返回按钮图,返回按钮必须始终是标准圆形,主体视觉尺寸比当前模板再放大约 50%,圆形外沿必须有与主题色搭配的干净外描边,中央只保留单个左箭头,参考图只约束圆形底色和箭头配色,不得延伸到复杂造型和花纹;按钮不得出现文字、数字、水印、额外 UI 面板或木槌物品。三个资产分别写回 `hitObjectAsset`、`backgroundAsset` 与 `backButtonAsset`,并绑定到 `wooden_fish_work` 的 `hit_object` / `background` / `back_button` 槽位。运行态和结果页消费 `backgroundAsset` 做竖屏背景,中央再叠加 `hitObjectAsset`,左上角返回按钮消费 `backButtonAsset`。
木鱼初始 `compile-draft` 是长耗时同步 action,生成页必须按上述三图 image2 链路展示进度:整理草稿、生成敲击物、生成背景环境图、生成返回按钮图、写入正式草稿。本地或供应商慢时一次 action 可能持续数分钟;前端不得把已关闭的提示词生成音效当成进度阶段,也不得在未收到 action 回包前宣称生成完成。
@@ -281,8 +281,8 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列
2. 先写入可恢复草稿 profile,再执行文本计划、关卡整图生成、三张派生图生成、OSS 上传和素材解析;作品摘要在背景、UI spritesheet 或物品 spritesheet 未完整时下发 `generationStatus=generating`,完整后下发 `ready`,草稿完成条件不包含 `backgroundMusic`。
3. 首次调用 VectorEngine `gpt-image-2`,无参考图,竖屏 `9:16`,生成完整抓大鹅关卡画面并持久化到 `generatedBackgroundAsset.levelSceneImageSrc/levelSceneImageObjectKey`。提示词必须包含用户主题描述、顶部返回 / 标题倒计时 / 设置按钮、中间与主题匹配且贴横向边缘的容器,以及底部“移出 / 凑齐 / 打乱”三个道具按钮。
4. 关卡整图完成后并发发起三次 `gpt-image-2` 编辑请求,三者都以关卡整图作为参考图:`1K`、`1:1` 的 UI spritesheet 写入 `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`;`1K`、`9:16` 的背景图写入 `imageSrc/imageObjectKey`;`2K`、`1:1` 的物品 spritesheet 写入 `itemSpritesheetImageSrc/itemSpritesheetImageObjectKey`。
5. UI spritesheet 提示词固定要求按从上到下、从左到右整理纯绿色绿幕背景素材:返回按钮、设置按钮、方格素材(不含边框,仅保留一个)、移出按钮、凑齐按钮、打乱按钮;后端上传 OSS 前必须把绿幕扣成透明 PNG。背景图提示词固定要求移除全部 UI 组件和容器内含物,完整保留容器和背景,并补全被 UI 覆盖的背景内容。
6. 物品 spritesheet 固定 `10行*10列`、统一纯绿色绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG;素材间距严格均匀分布,每一行包含两种物品,每种物品五个不同形态,物品来自参考图中心容器中的 2D 素材,严禁高相似度物品。新流程每次解析并持久化 `20` 种物品,物品信息列表全部展示这 `20` 种;后端切 `generatedItemAssets[].imageViews[]` 时优先按透明 alpha 连通域识别真实素材矩形,再按原图从上到下、从左到右排序,每 `5` 个区域组成一个物品的五个形态;只有识别出的区域数量不足时才回退 `10*10` 固定网格。持久化单格映射元数据仍按 `row = itemIndex / 2 + 1`、`col = itemIndex % 2 * 5 + viewIndex + 1` 写入通用系列素材图集,不能再用 `row = itemIndex + 1`。`generatedItemAssets[].imageViews[]` 仍兼容已切好的五视角图,缺失时运行态和编辑器按 spritesheet 自动解析结果回退。
5. UI spritesheet 提示词固定要求按从上到下、从左到右整理单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景素材:返回按钮、设置按钮、方格素材(不含边框,仅保留一个)、移出按钮、凑齐按钮、打乱按钮;后端上传 OSS 前必须把绿幕扣成透明 PNG。背景图提示词固定要求移除全部 UI 组件和容器内含物,完整保留容器和背景,并补全被 UI 覆盖的背景内容。
6. 物品 spritesheet 固定 `10行*10列`、统一单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG;素材间距严格均匀分布,每一行包含两种物品,每种物品五个不同形态,物品来自参考图中心容器中的 2D 素材,严禁高相似度物品。新流程每次解析并持久化 `20` 种物品,物品信息列表全部展示这 `20` 种;后端切 `generatedItemAssets[].imageViews[]` 时优先按透明 alpha 连通域识别真实素材矩形,再按原图从上到下、从左到右排序,每 `5` 个区域组成一个物品的五个形态;只有识别出的区域数量不足时才回退 `10*10` 固定网格。持久化单格映射元数据仍按 `row = itemIndex / 2 + 1`、`col = itemIndex % 2 * 5 + viewIndex + 1` 写入通用系列素材图集,不能再用 `row = itemIndex + 1`。`generatedItemAssets[].imageViews[]` 仍兼容已切好的五视角图,缺失时运行态和编辑器按 spritesheet 自动解析结果回退。
7. 前端和运行态统一使用 alpha 连通域矩形检测解析 spritesheet:UI 图先把识别出的透明素材矩形按行聚类,再在每一行内按横向 `x` 坐标排序,最后按返回、设置、方格、移出、凑齐、打乱顺序映射回原 UI 位置;不能只按全局 `y` 坐标排序,否则同一行素材上下略有错位时会把方格和底部道具按钮顺序打乱。物品图按检测顺序每 `5` 个区域组成一个物品的五个形态,最多 `20` 个物品。透明背景是解析前提,不能在前端按固定像素坐标写死切片。
8. 文本生成物品名称时必须同时生成 `itemSize`,只允许 `大`、`中`、`小`。该字段随 `generatedItemAssets[].itemSize` 持久化并下发;历史缺失字段的素材按 `大` 兼容,模型缺失或非法值按物品名本地推断。
9. 当前抓大鹅音频生成关闭:入口无 `生成音效`,草稿不生成背景音乐或点击音效,结果页不展示背景音乐 Tab 或点击音效生成入口。历史 `backgroundMusic` / `clickSound` 字段继续兼容传递。

Some files were not shown because too many files have changed in this diff Show More