diff --git a/AGENTS.md b/AGENTS.md index 5699de822..65504fb28 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,93 +1,65 @@ # AGENTS.md -## 项目共享记忆 -- 本仓库的团队级项目记忆位于 [`docs/project-memory/`](docs/project-memory/),用于在 3 名开发人员和各自本地 Agent 之间同步长期项目知识。 -- [`.hermes/`](.hermes/) 只保存 Hermes 专用的仓库级工具资源,例如 skills、plugins 和启用说明,不作为项目知识库。 -- 开始复杂开发任务前,除阅读本文件外,还应优先读取: - - [`docs/project-memory/README.md`](docs/project-memory/README.md) - - [`docs/project-memory/shared-memory/project-overview.md`](docs/project-memory/shared-memory/project-overview.md) - - [`docs/project-memory/shared-memory/team-conventions.md`](docs/project-memory/shared-memory/team-conventions.md) - - [`docs/project-memory/shared-memory/development-workflow.md`](docs/project-memory/shared-memory/development-workflow.md) - - 与任务相关的 [`docs/project-memory/shared-memory/decision-log.md`](docs/project-memory/shared-memory/decision-log.md) 和 [`docs/project-memory/shared-memory/pitfalls.md`](docs/project-memory/shared-memory/pitfalls.md) -- 仅在需要使用仓库级 Hermes skills/plugins 时,再读取 [`.hermes/README.md`](.hermes/README.md);长期项目记忆不要从 `.hermes/` 读取。 -- 如果本次任务产生长期有效的架构约定、接口变化、排障经验、开发流程或协作规则,应同步更新 `docs/project-memory/shared-memory/` 中对应文件。 -- 禁止提交个人 `~/.hermes` 配置、`.env`、API Key、Token、会话记录、认证文件和本地私密路径。 -- 若 `docs/project-memory/shared-memory/` 与当前代码或 `docs/` 最新文档冲突,以代码和最新 `docs/` 为准,并同步修正过期共享记忆。 +## 入口定位 -## Agent 本地 RAG -- 本仓库提供面向 Agent 的本地文档 RAG,入口位于 [`scripts/rag/`](scripts/rag/);RAG 主要用于 Agent 检索项目上下文,不替代人工阅读 `AGENTS.md`、`docs/README.md` 和 `docs/project-memory/`。 -- 开始复杂任务、跨模块任务或不确定文档入口时,Agent 可先用 `npm run rag:search -- --query "问题或关键词" --limit 8 --max-chars 12000` 取候选上下文;需要刷新索引时运行 `npm run rag:index`。 -- RAG 输出只作为候选上下文。涉及精确代码或文档修改时,仍需打开对应源文件核对;来源冲突时,以当前代码和最新 `docs/` 为准。 -- 默认不安装 RAG 运行时依赖,也不把 LanceDB、Transformers.js 或本地 embedding 模型写入根 `package.json`。需要启用时,Agent 必须先询问用户是否安装,并在确认后只安装到 gitignored 的 `.rag/runtime/`;详细命令见 [`scripts/rag/README.md`](scripts/rag/README.md)。 +- 本文件只保留 Agent 进入仓库后必须立即遵守的最高优先级规则;完整执行细则见 [`docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`](docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md)。 +- 团队级长期项目记忆位于 [`docs/project-memory/`](docs/project-memory/),供 3 名开发人员和各自本地 Agent 通过 Git 同步。 +- [`.hermes/`](.hermes/) 只保存 Hermes 专用仓库级工具资源,例如 skills、plugins 和启用说明;长期项目知识不要写入 `.hermes/`。 +- 若 `docs/project-memory/shared-memory/` 与当前代码或最新 `docs/` 冲突,以代码和最新 `docs/` 为准,并同步修正过期共享记忆。 -## Agent skills +## 开始任务前 -### Issue tracker +- 简单自包含任务可以直接执行;复杂开发、跨模块修改、后端 / UI / 文档体系调整前,按顺序读取: + 1. 本文件。 + 2. [`docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`](docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md)。 + 3. [`docs/project-memory/README.md`](docs/project-memory/README.md)、[`project-overview.md`](docs/project-memory/shared-memory/project-overview.md)、[`team-conventions.md`](docs/project-memory/shared-memory/team-conventions.md)、[`development-workflow.md`](docs/project-memory/shared-memory/development-workflow.md)。 + 4. 与任务相关的 [`decision-log.md`](docs/project-memory/shared-memory/decision-log.md)、[`pitfalls.md`](docs/project-memory/shared-memory/pitfalls.md)、[`docs/README.md`](docs/README.md) 和当前专题文档。 +- 落地工程修改前,先确认是否已有足够具体的 PRD、技术方案或当前融合文档;文档仍存在编码级歧义时,先补文档再编码。 +- 本仓库的本地 RAG 位于 [`scripts/rag/`](scripts/rag/);RAG 只作为候选上下文,不替代打开源文件核对。默认不安装 RAG 运行时依赖,需要启用时必须先询问用户,并只安装到 gitignored 的 `.rag/runtime/`。 -Issues are tracked in the self-hosted Gitea remote for this repo. Use Gitea Issues via the configured Gitea UI/API or `tea` CLI when available; do not use GitHub `gh` or GitLab `glab` unless the repo is migrated. Current issue workflow is summarized in `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`. +## 绝对约束 -### Triage labels +- 禁止提交个人 `~/.hermes` 配置、`.env`、API Key、Token、Cookie、会话记录、认证文件、本地私密路径、构建产物、日志、缓存和数据库 dump。 +- 不要在 `.gitignore` 中新增 `.env.local`。 +- 不要擅自把现有中文文案、注释、剧情或文档改写成英文;看到中文乱码时先确认真实编码,不要沿用乱码或用英文替换。 +- 修改包含中文的文件时优先局部补丁,避免整文件重写;修改后优先运行仓库编码检查。 +- 后续新增 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`;历史文档不要求批量重命名,除非本次任务明确涉及。 +- 工程修改要同步更新对应 `docs/` 文档;产生长期有效的架构约定、接口变化、排障经验、开发流程或协作规则时,同步更新 `docs/project-memory/shared-memory/`。 +- 默认保持系统简洁:优先复用、修改、扩展现有系统、页面和公共组件,不新建平行系统或平行页面。 +- UI 面板中不要默认写功能说明、规则描述或开发解释文案;移动端优先,同时保证网页端可正常显示和操作。 +- 点击按钮弹出独立面板的设计,不要实现成在当前面板下面追加内容。 -Use the default canonical triage labels: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`. +## 任务路由 -### Domain docs +- Issue 使用自托管 Gitea;优先用 Gitea UI/API 或 `tea` CLI,不使用 GitHub `gh` 或 GitLab `glab`,除非仓库已迁移。默认 triage 标签:`needs-triage`、`needs-info`、`ready-for-agent`、`ready-for-human`、`wontfix`。 +- 需要仓库级 Hermes skills/plugins 时,再读取 [`.hermes/README.md`](.hermes/README.md)。 +- 新增、补齐、迁移或重构玩法入口、玩法类型、创作工作台、生成页、结果页、发布、运行态、作品架、广场或公开 read model 前,必须读取并按 [`genarrative-play-type-integration`](.codex/skills/genarrative-play-type-integration/SKILL.md) 执行。 +- 涉及 `npm run dev` / `npm run dev:spacetime` / `npm run dev:api-server` / `npm run dev:web` / `npm run dev:admin-web` 的端口探测、端口漂移、SpacetimeDB publish server、api-server 环境变量、Vite 代理目标或后台 dev 端口时,按 [`.hermes/skills/genarrative-dev-stack-port-routing/SKILL.md`](.hermes/skills/genarrative-dev-stack-port-routing/SKILL.md) 执行。 +- 涉及 SpacetimeDB 的设计、实现、脚本、调试、发布、绑定生成、schema、reducer、procedure、view 或 Rust API 时,必须读取并按 [`spacetimedb-cli`](.codex/skills/spacetimedb-cli/SKILL.md)、[`spacetimedb-rust`](.codex/skills/spacetimedb-rust/SKILL.md)、[`spacetimedb-concepts`](.codex/skills/spacetimedb-concepts/SKILL.md) 中相关 skill 执行。 -Single-context layout: read root `CONTEXT.md` when present. Current architecture and product constraints are consolidated under `docs/`. +## 后端红线 -### 新增玩法接入 - -- 凡是新增、补齐、迁移或重构任何玩法入口、玩法类型、创作工作台、生成页、结果页、发布、运行态、作品架、广场或公开 read model 的任务,开始前必须显式读取并按 [$genarrative-play-type-integration](.codex\skills\genarrative-play-type-integration\SKILL.md) 执行;未先使用该 skill 的,不允许进入编码。 - -## 项目约束 -- 代码需要有完善的中文注释 -- 在落地工程修改前检查是否有详细指导本次落地的文档,若没有文档或文档的完善程度仍有落地过程中编码级别的歧义优先优化文档后落地工程迭代。 -- 对工程的修改不仅要落地到代码更面,还要更改对应文档,若没有生成新的文档,文档统一存在doc目录中 -- 后续新增的 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`;例如 `【后端架构】api-server能力模块化与图片资产Adapter收口计划-2026-05-14.md`。不要求批量重命名历史文档,除非本次任务明确涉及该文档。 -- 不要擅自把现有中文文案、注释、剧情、文档改写成英文,除非用户明确要求翻译。 -- 看到中文乱码时,不要直接沿用乱码文本,也不要用英文替换;先确认文件真实编码,再决定是否修改。 -- 在 PowerShell 5.1 中读取或写入文本时,必须显式使用 UTF-8;如果终端输出疑似乱码,要用 `Get-Content -Encoding UTF8`、Python 或 Node 再次核对原文。 -- 非必要不要整文件重写,尤其是包含中文的文件;优先做局部补丁,避免把未改动的中文内容重新编码。 -- 修改包含中文的文件后,优先运行仓库里的编码检查,确保没有把文本写坏。 -- UI面板中不要默认写一些规则描述文案,清爽一些,按照游戏UI设计规范设计即可。 -- UI设计需要兼顾网页端、移动端双端的使用体验,确保在不同设备上都能正常显示和操作,移动端优先考虑。 -- 不要在gitignore中添加.env.local文件。 -- 提交代码时,提交标题必须使用中文;标题后必须逐行写明本次提交修改了什么,每条变更单独一行。 -- 严格遵循简洁的代码风格 -- 请默认保持系统的简洁性,能复用、修改、扩展现有系统、页面就不新建新系统新页面。 -- 禁止将功能说明描述类的文本默认写入UI界面中。 -- prd文档中每个模块的描述要落地设计到可以精准编码到位,不能出现需求落地漂移。 -- 点击按钮弹出独立的面板的设计不要实现成在当前面板下面显示内容。 -- 每个阶段任务完成后自动压缩上下文,确保后续阶段在清晰、低噪音的上下文基础上继续推进。 - -## 后端技术约束 - 后端最新技术约束以 [`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`](docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md) 为准。 -- 契约、路由、DTO 去留和 breaking change 以当前后端架构文档、`server-rs/crates/api-server/src/app.rs`、`shared-contracts` 和 `packages/shared` 为准;不得在前端、`api-server` 或临时兼容层中重新发明旧接口。 -- SpacetimeDB 表结构、自动迁移限制和冲突处理以当前后端架构文档的 schema 变更规则和表目录为准;涉及 table、reducer、procedure、row shape 或绑定变化时,必须同步 `migration.rs`、表目录和生成绑定。 -- SpacetimeDB 已有表新增字段时,字段必须放在 Rust 表结构体最后,并设置明确默认值(例如 `#[default(...)]`);需要修改字段名时,必须先询问用户并确认迁移计划,再改代码,同时更新 `server-rs/crates/spacetime-module/src/migration.rs`、表目录和生成绑定。 -- 修改 SpacetimeDB schema 后必须运行 `npm run check:spacetime-schema`;该检查会拦截新增字段缺 default、字段不在末尾、字段删除/改名/重排/改类型,以及漏改 `migration.rs`、表目录或生成绑定。 -- 后端路线固定为 `server-rs + Axum + SpacetimeDB`。旧 `server-node`、Express、PostgreSQL 不再作为兼容目标;历史实现只能作为迁移参考,若旧文档与 DDD 约束冲突,先修正文档和方案再编码。 +- 后端路线固定为 `server-rs + Axum + SpacetimeDB`;旧 `server-node`、Express、PostgreSQL、Go 服务端、`maincloud` / `Maincloud` / `MAINCLOUD` 只作为历史残留,不作为兼容目标。 - DDD 分层边界按总纲执行:领域规则沉到 `module-*`,SpacetimeDB 表和事务编排留在 `spacetime-module`,后端访问 SpacetimeDB 统一经 `spacetime-client` facade,HTTP/SSE/BFF 留在 `api-server`,外部副作用留在 `platform-*`,前后端 DTO 留在 `shared-contracts`。 - 前端只做表现、交互和临时 UI 状态,不承接正式业务真相,不绕过后端投影或后端 API 直接实现业务规则。 -- 修改后端代码后,按对应 DDD 文档中的验收命令执行测试;涉及 API smoke 时使用 `npm run api-server` 重新拉起后端并执行相应自动测试,同时确认 `/healthz`。 -- `maincloud` / `Maincloud` / `MAINCLOUD` 相关脚本、环境变量、测试、文档要求和命名全部视为历史残留,禁止新增、运行或引用;若旧材料仍要求 `api-server:maincloud` 或 `GENARRATIVE_SPACETIME_MAINCLOUD_*`,以当前后端架构文档和本文件为准。 -- 除 CI/CD 脚本内部受控用法外,人工命令、本地联调、排障步骤和文档示例禁止继续使用 `spacetime --root-dir`。本地数据隔离使用项目脚本或 `--data-dir`,发布目标必须显式传 `--server` / `--server-url`,身份问题通过同一 CLI 登录态、专用运行用户或显式 token 处理;若旧文档仍推荐 `--root-dir`,先修正文档口径再执行。 -- 凡是涉及 SpacetimeDB 的设计、实现、脚本、调试、前端绑定接入,统一显式使用以下 skill 作为执行依据: - - [$spacetimedb-cli](.codex\\skills\\spacetimedb-cli\\SKILL.md) - - [$spacetimedb-rust](.codex\\skills\\spacetimedb-rust\\SKILL.md) - - [$spacetimedb-concepts](.codex\\skills\\spacetimedb-concepts\\SKILL.md) -- 涉及 `spacetime` CLI、发布、绑定生成、本地联调时,按 `spacetimedb-cli` 执行。 -- 涉及 `npm run dev` / `npm run dev:rust` / `npm run dev:web` 的端口探测、端口漂移、SpacetimeDB publish server、api-server 环境变量、Vite 代理目标或后台 dev 端口时,按 [`.hermes/skills/genarrative-dev-stack-port-routing/SKILL.md`](.hermes/skills/genarrative-dev-stack-port-routing/SKILL.md) 执行。 -- 涉及 `crates/spacetime-module` 的表、reducer、view、Rust API 使用时,按 `spacetimedb-rust` 与 `spacetimedb-concepts` 执行。 -- 涉及前端或 Node 侧的 SpacetimeDB 订阅、绑定使用时,按当前生成绑定、项目代码和官方文档核对;本仓库不再维护单独 TypeScript / C# / Unity SpacetimeDB skill。 -- 若仓库内旧实现或旧文档与这些 skill 冲突,先修正文档和方案,再继续编码。 -- 修改后端代码后,必须使用 `npm run api-server` 自动重新运行后端,并执行相应自动测试;不要再使用旧的后端重启命令。 -- 数据库表结构更改后,需要对齐migration.rs +- 契约、路由、DTO 去留和 breaking change 以当前后端架构文档、`server-rs/crates/api-server/src/app.rs`、`shared-contracts` 和 `packages/shared` 为准;不得在前端、`api-server` 或临时兼容层中重新发明旧接口。 +- SpacetimeDB 已有表新增字段时,字段必须放在 Rust 表结构体最后,并设置明确默认值;需要删除、改名、重排或改类型时,必须先询问用户并确认迁移计划。 +- 修改 SpacetimeDB schema 后必须同步 `migration.rs`、表目录和生成绑定,并运行 `npm run check:spacetime-schema`。 +- 除 CI/CD 脚本内部受控用法外,人工命令、本地联调、排障步骤和文档示例禁止继续使用 `spacetime --root-dir`。 + +## 验证与提交 + +- 修改后按范围运行定向测试、类型检查、`npm run check:encoding` 和 `git diff --check`;后端 API smoke 使用 `npm run dev:api-server` 拉起后端并检查 `/healthz`。 +- 修改 SpacetimeDB schema 后追加 `npm run check:spacetime-schema`;涉及发布或生产运维时按当前开发运维文档和脚本门禁执行。 +- 提交代码时,提交标题必须使用中文;标题后逐行写明本次提交修改了什么,每条变更单独一行。 ## 文档图谱 ```text docs/ ├─ README.md +├─ 【协作规范】Agent工作入口与执行准则-2026-06-22.md ├─ 【项目基线】当前产品与工程约束-2026-05-15.md ├─ 【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md ├─ 【玩法创作】平台入口与玩法链路-2026-05-15.md diff --git a/docs/README.md b/docs/README.md index 2f04921f6..c2c64c992 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,9 +1,10 @@ # 文档总览 -`docs/` 现在按主题拆成了 6 类;旧后端路线文档开始聚合和删除,后续实现以 Rust / SpacetimeDB 当前基线为准。 +`docs/` 现在按主题维护项目当前口径;旧后端路线文档开始聚合和删除,后续实现以 Rust / SpacetimeDB 当前基线为准。 ## 快速入口 +- [Agent 工作入口与执行准则](./%E3%80%90%E5%8D%8F%E4%BD%9C%E8%A7%84%E8%8C%83%E3%80%91Agent%E5%B7%A5%E4%BD%9C%E5%85%A5%E5%8F%A3%E4%B8%8E%E6%89%A7%E8%A1%8C%E5%87%86%E5%88%99-2026-06-22.md):复杂任务前的 Agent 阅读顺序、执行边界、技能路由、文档规则和验证口径。 - [经验沉淀](./experience/README.md):项目开发经验、UI 交接、历史实现经验。 - [审计与复盘](./audits/README.md):工程审查、文本/乱码审计、专项落地审计。 - [系统设计](./design/README.md):玩法、关系、物品与对话设计。 diff --git a/docs/project-memory/README.md b/docs/project-memory/README.md index f95953f80..8245289d0 100644 --- a/docs/project-memory/README.md +++ b/docs/project-memory/README.md @@ -21,7 +21,7 @@ docs/project-memory/ ## 使用原则 -- 开发前先读 `AGENTS.md`,再按任务读取 `docs/project-memory/shared-memory/` 和当前 `docs/` 文档。 +- 开发前先读 `AGENTS.md`;复杂任务继续读取 `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`,再按任务读取 `docs/project-memory/shared-memory/` 和当前 `docs/` 文档。 - 长期有效的架构约定、接口变化、排障经验、开发流程和协作规则写入 `shared-memory/`。 - 阶段性计划写入 `plans/`,已确定但暂未实施的共享 TODO 写入 `todos/`。 - 如果本目录内容与代码或最新 `docs/` 冲突,以代码和最新 `docs/` 为准,并同步修正过期记忆。 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index b1516663c..8b183e01f 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -24,6 +24,14 @@ - 验证方式:运行 `editor_generation_config`、公开定价路由、图标素材价格校验、图片画布定价模型和后台定价页相关测试。 - 关联文档:`docs/【编辑器】模型定价配置管理方案-2026-06-22.md`、`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`。 +## 2026-06-22 AGENTS.md 收敛为入口导航 + +- 背景:`AGENTS.md` 同时承载项目记忆、RAG、Issue、UI、Git、后端、SpacetimeDB 和文档图谱等细则,入口过重,复杂任务启动成本高。 +- 决策:`AGENTS.md` 只保留最高优先级规则、任务路由、后端红线、验证提交要求和文档图谱;新增 `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md` 承接完整执行细则。复杂任务阅读顺序固定为 `AGENTS.md` -> Agent 执行准则 -> `docs/project-memory/` -> `docs/README.md` 和专题文档。 +- 影响范围:`AGENTS.md`、`docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`、`docs/README.md`、`docs/project-memory/README.md` 和共享记忆索引。 +- 验证方式:执行 `npm run check:encoding`、`git diff --check`,并检查入口文档不再重复承载专题细则。 +- 关联文档:`AGENTS.md`、`docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`。 + ## 2026-06-22 图片画布角色动作主媒体改为透明序列帧 - 背景:角色动作生成后端已经在视频生成后抽取透明 PNG 帧并完成绿幕去背;画板继续把 `previewVideoPath` 当主媒体会让用户看到未扣绿幕视频,下载也拿不到可直接用于游戏素材的帧序列。 diff --git a/docs/project-memory/shared-memory/development-workflow.md b/docs/project-memory/shared-memory/development-workflow.md index af677416b..5a76fb868 100644 --- a/docs/project-memory/shared-memory/development-workflow.md +++ b/docs/project-memory/shared-memory/development-workflow.md @@ -5,7 +5,7 @@ ## 标准任务流程 ```text -同步代码 → 读取 AGENTS.md → 读取 docs/project-memory/shared-memory → 查找/完善 docs → 制定计划 → 小步实现 → 本地验证 → 更新文档/记忆 → 提交 +同步代码 → 读取 AGENTS.md → 复杂任务读取 Agent 执行准则 → 读取 docs/project-memory/shared-memory → 查找/完善 docs → 制定计划 → 小步实现 → 本地验证 → 更新文档/记忆 → 提交 ``` ## 建议启动方式 @@ -30,6 +30,7 @@ hermes - [ ] 当前分支是否正确 - [ ] 是否已拉取最新代码 - [ ] 是否阅读 `AGENTS.md` +- [ ] 复杂任务是否阅读 `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md` - [ ] 是否阅读 `docs/project-memory/shared-memory/` 相关文件 - [ ] 是否阅读 `README.md` 中的运行和检查命令 - [ ] 是否阅读 `docs/README.md` 及任务相关分类 README @@ -294,7 +295,7 @@ npm run check:server-rs-ddd ```text 请检查当前 git diff,指出: -1. 是否违反 AGENTS.md 或 docs/project-memory/shared-memory 约定; +1. 是否违反 AGENTS.md、Agent 执行准则或 docs/project-memory/shared-memory 约定; 2. 是否需要补充 docs; 3. 是否有长期知识需要写入 docs/project-memory/shared-memory; 4. 建议的测试命令和提交信息。 diff --git a/docs/project-memory/shared-memory/document-map.md b/docs/project-memory/shared-memory/document-map.md index 24a66370d..3952997e7 100644 --- a/docs/project-memory/shared-memory/document-map.md +++ b/docs/project-memory/shared-memory/document-map.md @@ -1,12 +1,13 @@ # 文档地图与阅读索引 -更新时间:`2026-05-15` +更新时间:`2026-06-22` ## 当前文档入口 | 场景 | 优先阅读 | | --- | --- | | 建立项目背景 | `README.md`、`AGENTS.md`、`docs/project-memory/shared-memory/project-overview.md` | +| Agent 复杂任务执行规则 | `AGENTS.md`、`docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md` | | 找当前文档 | `docs/README.md` | | 产品、命名、UI、协作和废弃路线 | `docs/【项目基线】当前产品与工程约束-2026-05-15.md` | | 后端、DDD、API、SpacetimeDB schema 和表目录 | `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` | @@ -22,9 +23,10 @@ 通用复杂任务: 1. `AGENTS.md` -2. `docs/project-memory/shared-memory/` -3. `docs/README.md` -4. 与任务匹配的当前融合文档 +2. `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md` +3. `docs/project-memory/shared-memory/` +4. `docs/README.md` +5. 与任务匹配的当前融合文档 后端 / 数据真相 / SpacetimeDB: @@ -48,6 +50,7 @@ ## 维护规则 - 当前 `docs/` 只保留少量融合文档。 +- `AGENTS.md` 只保留最高优先级入口;Agent 执行细则优先沉到 `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md` 或对应专题文档。 - 新增工程实现时,如果已有对应当前文档,必须同步更新。 - 如果没有合适位置,新文档文件名必须使用 `【标签名】中文标题-YYYY-MM-DD.md`。 - 阶段性流水账、一次性修复记录和已关闭实验不要再新增为长期文档。 diff --git a/docs/project-memory/shared-memory/project-overview.md b/docs/project-memory/shared-memory/project-overview.md index 10efa6a22..81920e365 100644 --- a/docs/project-memory/shared-memory/project-overview.md +++ b/docs/project-memory/shared-memory/project-overview.md @@ -51,6 +51,7 @@ server-rs + Axum + SpacetimeDB ## 当前文档入口 - `docs/README.md` +- `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md` - `docs/【项目基线】当前产品与工程约束-2026-05-15.md` - `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` - `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` diff --git a/docs/project-memory/shared-memory/team-conventions.md b/docs/project-memory/shared-memory/team-conventions.md index 81f3a5ba0..ef7364769 100644 --- a/docs/project-memory/shared-memory/team-conventions.md +++ b/docs/project-memory/shared-memory/team-conventions.md @@ -19,6 +19,7 @@ - `.hermes/skills/` Hermes 专用仓库级 skills - `docs/` 中 PRD、设计、技术、经验、审计、查询手册 - `AGENTS.md` 项目级 Agent 约束 +- `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md` Agent 执行细则 禁止提交: @@ -33,10 +34,11 @@ 1. 拉取最新代码。 2. 阅读 `AGENTS.md`。 -3. 阅读 `docs/project-memory/shared-memory/` 中与任务相关的文件。 -4. 阅读 `docs/README.md` 和任务相关分类 README。 -5. 阅读对应 PRD、设计、技术、经验或审计文档。 -6. 如果文档不足以指导编码,先补充或修正文档。 +3. 复杂任务阅读 `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`。 +4. 阅读 `docs/project-memory/shared-memory/` 中与任务相关的文件。 +5. 阅读 `docs/README.md` 和任务相关分类 README。 +6. 阅读对应 PRD、设计、技术、经验或审计文档。 +7. 如果文档不足以指导编码,先补充或修正文档。 ## 开发中 @@ -64,21 +66,16 @@ 1. `README.md` 2. `AGENTS.md` -3. `docs/project-memory/shared-memory/` -4. `docs/README.md` -5. `docs/experience/README.md` -6. `docs/audits/README.md` -7. 任务所属分类:`docs/design/`、`docs/technical/`、`docs/planning/`、`docs/prd/`、`docs/reference/`、`docs/tracking/`、`docs/operations/` +3. `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md` +4. `docs/project-memory/shared-memory/` +5. `docs/README.md` +6. 任务所属分类:`docs/design/`、`docs/technical/`、`docs/planning/`、`docs/prd/`、`docs/reference/`、`docs/tracking/`、`docs/operations/` 后端任务建议: -1. `docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md` -2. `docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md` -3. `docs/technical/SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md` -4. `docs/technical/SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md` -5. `docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md` -6. `docs/technical/SPACETIMEDB_TABLE_CATALOG.md` -7. `docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md` +1. `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` +2. `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md` +3. 任务相关 crate README、源码和当前专题文档 ## 共享记忆更新准则 diff --git a/docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md b/docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md index 5074673be..347df3aeb 100644 --- a/docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md +++ b/docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md @@ -23,7 +23,7 @@ - 图片生成 / 修改统一经 api-server BFF 接入 VectorEngine。普通生成、生成规范和重绘保留既有 `gpt-image-2` 路径;图片快速编辑默认从原图模型和分辨率初始化,但提交使用面板当前选择的 `model/aspectRatio/imageSize`;其中生成规范类图片固定 `16:9`、`2K`、`gpt-image-2`,面板底部用与可编辑面板一致的比例 / 尺寸 / 模型胶囊按钮展示固定参数,但按钮为禁用态,不允许在该面板改比例、尺寸或模型。`生成角色形象` 与 `生成图标素材` 支持 `nanobanana2`(`gemini-3.1-flash-image-preview`)和 `gpt-image-2`,默认 `nanobanana2`,并在两类面板之间沿用用户上次选择的模型。`nanobanana2` 走 `/v1beta/models/{model}:generateContent`,请求体写入 `generationConfig.imageConfig.aspectRatio/imageSize`;`gpt-image-2` 走 `/v1/images/generations` 或 `/v1/images/edits`,请求体按 VectorEngine 文档映射 `size`。宣发素材必须按 workflow 同时提交 `outputSize`、`aspectRatio` 和 `imageSize`,其中游戏首图为 `720x540 / 4:3`、详情单图为 `720x1280 / 9:16`、运营海报为 `1280x720 / 16:9`;生成回填图层优先使用生成占位的 `originalWidth/originalHeight`,即使上游回包尺寸漂移也不得把宣发素材卡片变成随机 `1:1` 或 `4:3`。纯文本生成走 `/api/editor/images/generations`,重绘在前端读入当前图层图片 Data URL 后走同一图片生成 BFF,并在原图右侧生成一张新图;普通图层重绘作为 `quick-edit` 参考图提交,角色图层重绘必须按 `kind: "character"` 提交,继续套用角色生成器提示词限定、透明 PNG 后处理和角色资产持久化。`生成视频` 走 `/api/editor/videos/generations`,前端模型入口仅展示 Seedance 2.0 Fast / Seedance 2.0 / Kling 3.0 / Kling 3.0 Omni,不展示 Veo 入口,默认 Seedance 2.0 Fast;视频参数按当前正式面板支持的比例、时长、清晰度和声音开关提交,且 Seedance Fast 与 Seedance 标准版必须按各自真实模型 ID 独立映射,不得混用。生成结果以视频图层加入画布。纯文本生成入口采用 Lovart 式画布内占位图 + 锚定生成输入框:点击生成图片后以当前视口世界中心为目标,经统一 placement 避让后创建选中的灰色占位框,输入框跟随占位框显示;待生成、生成中和失败后保留的占位图都必须继续支持拖动,生成完成时真实生成图或视频落在最新占位框位置,输入框继续跟随新生成图层;占位图失焦时隐藏高亮边框、左上角生成器名称和右上角原始尺寸,重新聚焦时再显示,且名称 / 尺寸在画布缩小时按 viewport 反向缩放保持屏幕尺寸稳定;点击所有图片 / 视频生成入口并确认请求开始后,必须隐藏对应设置面板,只保留画布内占位图或原图预览,并在预览上显示 Lovart 式生成中遮罩,避免“面板仍占屏”或“预览一起消失”。图片快速编辑和重绘在调用图片 BFF 前必须把当前图层图片源读取为图片 Data URL;视频素材快速编辑走视频生成 BFF,不允许走图片模型;角色动作快速编辑固定使用 `seedance2.0-fast` 动作 / 视频模型。前端不持有 provider 密钥;上游失败或配置缺失时恢复当前生成设置面板展示失败,不创建 mock 成功图。 - 快速编辑面板对齐其它生成类面板:首行支持额外参考图,最多 8 张;原图 / 原素材自动作为最后一张隐式参考提交,但不在参考图条里固定展示 `图x`。打开快速编辑时画布必须自动平移缩放,让原素材完整落在可视区上半部分,底部面板固定出现在素材下方且不遮挡内容,竖屏 UI 素材也必须完整展示。快速编辑右侧显示矩形、椭圆、画笔框选工具,但进入时不默认启用;点击工具后显示选中态,再点同一工具取消启用。尺寸和模型默认从原素材配置 / 分辨率推断,底部参数胶囊可点击修改;图片快速编辑左下角统一显示 `x:y·xK`,右下角模型胶囊紧贴生成按钮。提交前将提示词里对原素材的 `原图`、`当前图片`、`当前图` 或 `图1` 引用改写为最后一张源素材编号,例如额外 1 张参考图时写成 `图2`。点击快速编辑生成后立即创建独立 `Quick Edit Generator` 画布占位播放生成中动画,不再在原图图层上播放生成中遮罩;该生成中占位可通过键盘 `Delete` / `Backspace` 删除。 - 底部生成类按钮每次点击都必须创建独立的画布生成对象;新建规范、角色形象或图标素材时,只切换当前编辑面板,不得销毁此前尚未生成或已生成后的其它生成对象状态。归档为非当前编辑对象的生成占位仍可拖动、删除和等待异步完成,完成 / 失败回写必须按生成对象 ID 读取最新占位状态,不能使用提交瞬间的旧快照。 -- 所有会新建画布生成占位的入口必须先创建 draft,再统一经过 `ImageCanvasGenerationPlacementModel` 计算落点,禁止各入口自行使用当前视口中心裸坐标。当前覆盖入口包括 `生成图片`、`生成规范`、`生成角色形象`、`生成图标素材`、`生成视频`、`生成UI设计图` 和 `生成角色动作`。placement 模型的避让对象为所有未隐藏画布图层,以及当前 active / inactive generation dialogs 中仍存在的 placeholder;每个避让矩形按 32px 画布世界坐标间距外扩。候选落点以当前视口世界中心为距离目标,优先选择离视口中心最近且不重叠的占位位置;若中心被占用,会按上下左右和环形候选继续寻找。打开生成面板时必须把避让后的 placeholder 写入 `openCanvasGenerationDialog(...)`,并立即调用 `centerViewportOnPlacement(...)` 居中到新占位中心,保持原 viewport scale 不变。 +- 所有会新建画布生成占位的入口必须先创建 draft,再统一经过 `ImageCanvasGenerationPlacementModel` 计算落点,禁止各入口自行使用当前视口中心裸坐标或原图右侧固定偏移。当前覆盖入口包括 `生成图片`、`生成规范`、`生成角色形象`、`生成图标素材`、`生成视频`、`生成UI设计图`、`生成角色动作` 和快速编辑提交后创建的 `Quick Edit Generator`。placement 模型的避让对象为所有未隐藏画布图层,以及当前 active / inactive generation dialogs 中仍存在的 placeholder;每个避让矩形按 32px 画布世界坐标间距外扩。候选落点以当前视口世界中心为距离目标,优先选择离视口中心最近且不重叠的占位位置;若中心被占用,会按上下左右和环形候选继续寻找。打开生成面板时必须把避让后的 placeholder 写入 `openCanvasGenerationDialog(...)`,并立即调用 `centerViewportOnPlacement(...)` 居中到新占位中心,保持原 viewport scale 不变;快速编辑提交时同样必须把避让后的 placeholder 写入独立生成占位,生成结果落在该占位位置。 ## 交互规则 diff --git a/docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md b/docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md new file mode 100644 index 000000000..a2d523728 --- /dev/null +++ b/docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md @@ -0,0 +1,116 @@ +# Agent 工作入口与执行准则 + +更新时间:`2026-06-22` + +## 文档定位 + +本文件承接 `AGENTS.md` 中不适合塞在入口页里的执行细则。`AGENTS.md` 是最高优先级入口,本文件是复杂任务的默认操作手册;如果本文件与代码、最新 `docs/` 或项目共享记忆冲突,以当前代码和最新 `docs/` 为准,并同步修正文档。 + +## 阅读顺序 + +简单自包含任务可以直接执行。复杂开发、跨模块修改、后端 / UI / 文档体系调整前,按这个顺序读: + +1. `AGENTS.md` +2. 本文件 +3. `docs/project-memory/README.md` +4. `docs/project-memory/shared-memory/project-overview.md` +5. `docs/project-memory/shared-memory/team-conventions.md` +6. `docs/project-memory/shared-memory/development-workflow.md` +7. 与任务相关的 `decision-log.md`、`pitfalls.md`、`docs/README.md` 和专题文档 + +如果已有文档不能精确指导字段、契约、页面状态、资产链路、迁移或验收命令,先补文档再编码。 + +## 信息来源边界 + +- `docs/`:当前 PRD、架构、开发运维、设计和测试口径。 +- `docs/project-memory/shared-memory/`:长期团队记忆、决策、流程和踩坑摘要。 +- `.hermes/`:Hermes 工具资源,不作为项目知识库。 +- `.codex/skills/`:Codex 可复用技能;只在任务命中时读取。 +- `scripts/rag/`:Agent 本地检索入口,只提供候选上下文。 + +RAG 默认不安装运行时依赖,也不把 LanceDB、Transformers.js 或本地 embedding 模型写入根 `package.json`。需要启用时,先询问用户;用户确认后只安装到 gitignored 的 `.rag/runtime/`,模型缓存和向量库留在 `.rag/`。 + +## 执行风格 + +- 修改范围保持聚焦,不做无关重构。 +- 优先复用现有系统、页面、组件、脚本、DTO 和文档位置。 +- 不新增平行入口、平行作品架、平行公开列表、平行业务真相或临时兼容层。 +- 不把前端临时状态当正式业务事实;正式状态以后端投影、后端 API 或当前架构文档为准。 +- 涉及中文内容时保持中文,不擅自翻译成英文。 +- 发现中文乱码时先确认真实编码,不直接沿用乱码,也不用英文替换。 +- 含中文文件优先局部补丁;非必要不要整文件重写。 +- 阶段性大任务完成后,整理当前上下文、剩余风险和下一步入口,降低后续接手噪音。 + +## 文档规则 + +- 工程修改必须同步更新对应 `docs/` 文档。 +- 没有合适文档时,新文档放入 `docs/` 下合适位置,文件名使用 `【标签名】中文标题-日期.md`。 +- PRD 或技术方案要具体到能指导编码,不写会导致落地漂移的泛泛描述。 +- 长期有效的架构约定、接口变化、排障经验、开发流程或协作规则写入 `docs/project-memory/shared-memory/`。 +- 阶段性计划放入 `docs/project-memory/plans/`;确定但未实施的共享 TODO 放入 `docs/project-memory/todos/`。 +- 不提交个人配置、密钥、Token、Cookie、会话记录、认证文件、本地私密路径、构建产物、日志、缓存和数据库 dump。 + +## UI 与前端规则 + +- UI 面板保持清爽,不默认写功能说明、规则说明、键盘快捷键说明或开发解释文本。 +- 移动端优先,同时保证桌面端体验完整。 +- 弹出独立面板的交互使用弹窗、抽屉、popover 或页面级 portal,不在当前面板下面追加内容。 +- 页面展示以后端返回状态为准,不在前端自行计算结论型业务状态。 +- 创作入口事实源来自 SpacetimeDB,经 `/api/creation-entry/config` 下发;前端只做展示派生。 +- 优先扩展现有公共组件,例如平台弹窗、图片输入、媒体预览、状态提示和动作按钮,不在业务页复制通用逻辑。 + +## 后端与数据真相 + +- 后端路线固定为 `server-rs + Axum + SpacetimeDB`。 +- 旧 `server-node`、Express、PostgreSQL、Go 服务端、`maincloud` 相关脚本、环境变量、测试和文档要求均为历史残留。 +- 领域规则沉到 `module-*`;SpacetimeDB 表、reducer、procedure、事务 adapter 和 row mapper 留在 `spacetime-module`。 +- 后端访问 SpacetimeDB 统一经 `spacetime-client` facade。 +- HTTP / SSE / BFF 和外部副作用编排留在 `api-server`;OSS、LLM、认证、语音等外部平台能力留在 `platform-*`。 +- 前后端 DTO 和公开契约留在 `shared-contracts` / `packages/shared`。 +- 契约、路由、DTO 去留和 breaking change 以当前后端架构文档、`api-server/src/app.rs`、`shared-contracts` 和 `packages/shared` 为准。 + +后端修改后按当前 DDD 文档执行验收。涉及 API smoke 时,使用 `npm run dev:api-server` 重新拉起后端并检查 `/healthz`;不要使用旧 `maincloud` 启动口径。 + +## SpacetimeDB 规则 + +涉及 SpacetimeDB 设计、实现、脚本、调试、发布、绑定生成、schema、reducer、procedure、view 或 Rust API 时,先读取对应 skill: + +- `.codex/skills/spacetimedb-cli/SKILL.md` +- `.codex/skills/spacetimedb-rust/SKILL.md` +- `.codex/skills/spacetimedb-concepts/SKILL.md` + +已有表新增字段时,字段必须放在 Rust 表结构体最后,并设置明确默认值。删除、改名、重排或改类型前必须先询问用户并确认迁移计划。 + +修改 schema 后必须同步: + +- `server-rs/crates/spacetime-module/src/migration.rs` +- 表目录 / 数据契约文档 +- 生成绑定 +- `npm run check:spacetime-schema` + +人工命令、本地联调、排障步骤和文档示例禁止继续使用 `spacetime --root-dir`;本地数据隔离使用项目脚本或 `--data-dir`,发布目标显式传 `--server` / `--server-url`。 + +## 技能路由 + +- 新增、补齐、迁移或重构玩法入口、玩法类型、创作工作台、生成页、结果页、发布、运行态、作品架、广场或公开 read model:读取 `.codex/skills/genarrative-play-type-integration/SKILL.md`。 +- 本地 dev 端口、代理目标、端口漂移、SpacetimeDB publish server、api-server 环境变量、Vite 代理和后台 dev 串联:读取 `.hermes/skills/genarrative-dev-stack-port-routing/SKILL.md`。 +- 仓库级 Hermes skills/plugins:先读 `.hermes/README.md`,只把 `.hermes/` 当工具目录。 + +## Issue 与提交 + +- Issue 使用自托管 Gitea;优先使用 Gitea UI/API 或 `tea` CLI。 +- 默认 triage 标签:`needs-triage`、`needs-info`、`ready-for-agent`、`ready-for-human`、`wontfix`。 +- 提交标题必须使用中文;标题后逐行写明本次提交修改了什么,每条变更单独一行。 +- 提交前检查 staged diff,避免把无关文件、密钥、本地配置或用户未要求的改动带进去。 + +## 默认验证 + +按修改范围选择验证,不追求无意义全量扫: + +- 文档 / 中文文本:`npm run check:encoding`、`git diff --check` +- 前端:定向测试、`npm run typecheck`、必要的页面交互 smoke 和移动端视口检查 +- 后端:对应 crate 的 `cargo test` / `cargo check`、API smoke、`/healthz` +- SpacetimeDB schema:`npm run check:spacetime-schema` +- 发布 / 运维:当前开发运维文档中的脚本门禁、host 侧进程和公开端点验证 + +如果无法运行某项验证,最终说明要写清原因、风险和已经完成的替代检查。 diff --git a/docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md b/docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md index ff3c9cd61..3918f7049 100644 --- a/docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md +++ b/docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md @@ -97,7 +97,7 @@ - 图片类待生成占位尺寸必须与面板当前比例和尺寸同步:普通图片、角色形象、图标素材、UI 设计图按当前 `aspectRatio + imageSize` 计算像素尺寸;生成规范固定为 `16:9·2K`,占位为 `2048 x 1152`;宣发素材按 workflow 输出尺寸创建占位。 - 视频待生成占位必须与面板当前比例和清晰度同步:默认 `16:9 · 480p` 为 `854 x 480`,切换比例、`720p` 或 `1080p` 后按比例和清晰度重算偶数宽度;调整参数时保持占位中心点不变。 - 面板中用户修改比例、尺寸或清晰度后,已有空白待生成占位立即同步更新 `width / height / originalWidth / originalHeight`,且保持中心点不跳动。 -- 快速编辑点击生成后不在原图上播放生成中遮罩,而是立即创建独立 `Quick Edit Generator` 画布生成占位并播放生成中动画;生成成功后结果落在该占位框位置,失败时占位标记失败并恢复快速编辑面板。 +- 快速编辑点击生成后不在原图上播放生成中遮罩,而是立即创建独立 `Quick Edit Generator` 画布生成占位并播放生成中动画;该占位必须复用新建图片的 placement 避让逻辑,和已有素材 / 生成占位至少保留 32px 画布间距,不允许固定放到原图右侧后压住其它素材;生成成功后结果落在该占位框位置,失败时占位标记失败并恢复快速编辑面板。 ## 画布悬浮信息 diff --git a/src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx b/src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx index c79084641..920ff5e70 100644 --- a/src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx +++ b/src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx @@ -120,6 +120,35 @@ function createIconResult(name: string, imageSrc: string) { }; } +function parseLayerRect(text: string, layerId: string) { + const entry = text + .split('|') + .find((currentEntry) => currentEntry.startsWith(`${layerId}:`)); + if (!entry) { + throw new Error(`missing layer rect for ${layerId}`); + } + const [, x, y, width, height] = entry.split(':'); + return { + x: Number(x), + y: Number(y), + width: Number(width), + height: Number(height), + }; +} + +function rectOverlapsExpanded( + rect: { x: number; y: number; width: number; height: number }, + blocker: { x: number; y: number; width: number; height: number }, + gap: number, +) { + return ( + rect.x < blocker.x + blocker.width + gap && + rect.x + rect.width > blocker.x - gap && + rect.y < blocker.y + blocker.height + gap && + rect.y + rect.height > blocker.y - gap + ); +} + function SubmissionWorkflowHarness({ initialDialog = null, initialQuickEditPanel = null, @@ -179,6 +208,7 @@ function SubmissionWorkflowHarness({ quickEditPanel, quickEditSourceLayer, quickEditSelectionState: initialQuickEditSelectionState, + canvasGenerationDialogs: dialogs.canvasGenerationDialogs, setQuickEditPanel, characterAnimationPanel, characterAnimationDialog: activeCanvasDialog, @@ -216,6 +246,14 @@ function SubmissionWorkflowHarness({ ) .join('|')} + + {layers + .map( + (layer) => + `${layer.id}:${layer.x}:${layer.y}:${layer.width}:${layer.height}`, + ) + .join('|')} + {activeDialog ? `${activeDialog.mode}:${activeDialog.status}:${activeDialog.composerOpen !== false ? 'open' : 'closed'}:${activeDialog.generatedLayerId ?? '-'}:${activeDialog.placeholder ? 'placeholder' : '-'}:${activeDialog.errorMessage ?? '-'}` @@ -410,6 +448,45 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => { expect(screen.getByTestId('fit-count').textContent).toBe('1'); }); + it('places quick edit result with the new image placement logic away from existing assets', async () => { + const blockingLayer = createLayer({ + id: 'layer-blocker', + resourceId: 'resource-blocker', + title: '右侧已有素材', + x: 472, + y: 140, + width: 1024, + height: 768, + originalWidth: 1024, + originalHeight: 768, + zIndex: 3, + }); + generateEditorImageMock.mockResolvedValueOnce( + createGenerated({ prompt: '快速修图', width: 1024, height: 768 }), + ); + render( + , + ); + + fireEvent.click(screen.getByRole('button', { name: '打开快速编辑' })); + fireEvent.click(screen.getByRole('button', { name: '填写快速编辑' })); + fireEvent.click(screen.getByRole('button', { name: '提交快速编辑' })); + + await waitFor(() => { + expect(screen.getByTestId('layers').textContent).toContain( + 'layer-quick-edit-1:源图 快速编辑:resource-source:-', + ); + }); + const layerRects = screen.getByTestId('layer-rects').textContent ?? ''; + const resultRect = parseLayerRect(layerRects, 'layer-quick-edit-1'); + + expect( + rectOverlapsExpanded(resultRect, blockingLayer, 32), + ).toBe(false); + }); + it('submits quick edits with the numbered red selection image as the final source reference', async () => { generateEditorImageMock.mockResolvedValueOnce( createGenerated({ prompt: '对1号红色圈选框里的内容做以下修改:改成蓝色' }), diff --git a/src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.ts b/src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.ts index e8c9afa18..2edf895b5 100644 --- a/src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.ts +++ b/src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.ts @@ -44,6 +44,7 @@ import { createQuickEditResultLayer, createVideoResultLayer, } from './ImageCanvasGenerationLayerModel'; +import { chooseGenerationPlacement } from './ImageCanvasGenerationPlacementModel'; import { buildCharacterAnimationGenerationInputs, buildEditGenerationInputs, @@ -98,6 +99,7 @@ type GenerationSubmissionWorkflowOptions = { quickEditPanel: QuickEditPanelState | null; quickEditSourceLayer: CanvasLayer | null; quickEditSelectionState: UiAssetExtractionState | null; + canvasGenerationDialogs: CanvasGenerationDialogState[]; setQuickEditPanel: Dispatch>; characterAnimationPanel: CharacterAnimationPanelState | null; characterAnimationDialog: CanvasGenerationDialogState | null; @@ -165,6 +167,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({ quickEditPanel, quickEditSourceLayer, quickEditSelectionState, + canvasGenerationDialogs, setQuickEditPanel, characterAnimationPanel, characterAnimationDialog, @@ -741,21 +744,33 @@ export function useImageCanvasGenerationSubmissionWorkflow({ ); let quickEditDialogId: string | undefined; if (panelMode === 'quick-edit') { - quickEditDialogId = openCanvasGenerationDialog( - createQuickEditGenerationDialogDraft({ - sourceLayer: quickEditSourceLayer, - prompt: normalizedPrompt, - status: 'generating', - references: quickEditReferences, - model: quickEditPanel.model, - aspectRatio: quickEditAspectRatio, - imageSize: quickEditImageSize, - frame: { - width: quickEditOutputSize.width, - height: quickEditOutputSize.height, - }, - }), - ); + const quickEditDraft = createQuickEditGenerationDialogDraft({ + sourceLayer: quickEditSourceLayer, + prompt: normalizedPrompt, + status: 'generating', + references: quickEditReferences, + model: quickEditPanel.model, + aspectRatio: quickEditAspectRatio, + imageSize: quickEditImageSize, + frame: { + width: quickEditOutputSize.width, + height: quickEditOutputSize.height, + }, + }); + const quickEditPlacement = quickEditDraft.placeholder + ? chooseGenerationPlacement({ + canvasSize, + viewport, + frame: quickEditDraft.placeholder, + layers, + generationDialogs: canvasGenerationDialogs, + }) + : quickEditDraft.placeholder; + quickEditDialogId = openCanvasGenerationDialog({ + ...quickEditDraft, + // 中文注释:快速编辑生成结果复用新建图片的避让落点,避免覆盖已有素材。 + placeholder: quickEditPlacement, + }); setQuickEditPanel(null); selectSingleLayer(null); } else { @@ -956,7 +971,10 @@ export function useImageCanvasGenerationSubmissionWorkflow({ addCharacterAnimationResultLayer, addQuickEditResultLayer, addVideoResultLayer, + canvasGenerationDialogs, + canvasSize, getGeneratingDialogPlaceholder, + layers, openCanvasGenerationDialog, projectId, assetFolderId, @@ -966,6 +984,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({ selectSingleLayer, setQuickEditPanel, updateCanvasGenerationDialogById, + viewport, ]); const submitImageGeneration = useCallback( diff --git a/src/components/image-editor/useImageCanvasGenerationWorkflow.ts b/src/components/image-editor/useImageCanvasGenerationWorkflow.ts index 3564757ef..96cefcc9f 100644 --- a/src/components/image-editor/useImageCanvasGenerationWorkflow.ts +++ b/src/components/image-editor/useImageCanvasGenerationWorkflow.ts @@ -1210,6 +1210,7 @@ export function useImageCanvasGenerationWorkflow({ quickEditPanel, quickEditSourceLayer, quickEditSelectionState, + canvasGenerationDialogs, setQuickEditPanel, characterAnimationPanel: effectiveCharacterAnimationPanel, characterAnimationDialog,