Merge branch 'master' into fix/artagent-sub-modal-style
This commit is contained in:
@@ -61,7 +61,7 @@
|
||||
- 吸附阈值以屏幕像素为准,换算到世界坐标后参与拖拽计算;边缘 / 中心线和等距吸附共用同一阈值。拖拽结束后只保存最终图层或生成占位布局,不保存临时参考线。
|
||||
- 项目页封面和画布图片图层必须先渲染项目卡、图层外框、标题、尺寸和操作 chrome;图片换签或解码未完成时,只在图片区域显示轻量加载态,不阻塞外框和文字等低成本信息先出现。
|
||||
- 素材量增大时,拖拽吸附热路径不得对所有素材做全量两两配对。边缘 / 中心线吸附保持线性扫描;等距吸附只在跨轴相交且轴向邻近的候选图层之间计算,避免大量远处素材拖慢 pointermove。
|
||||
- 画布自动保存使用防抖 + 串行队列:图层拖拽、缩放、资源新增和修改结果创建后延迟保存工程快照;如果上一次 `PATCH /api/editor/projects/{projectId}` 尚未完成,只保留最新待保存快照,待当前请求结束后再发送下一次保存,避免慢保存请求并发堆积触发发布入口连接限流。手型平移和小地图拖动属于临时 viewport 交互,拖动中只更新画布显示,不触发 `serializeCanvasLayout`、sessionStorage 项目缓存写入或封面快照上传,`pointerup` / `pointercancel` 后再保存最终 viewport。每次 `PATCH /api/editor/projects/{projectId}` 都必须携带最近一次服务端权威快照或保存 ack 给出的 `expectedRevision`;缺少版本号的请求在 HTTP 写入口直接拒绝,不允许回退到无版本覆盖。接口只返回 `{ projectId, canvasId, revision, updatedAt }` 轻量 ack,不返回完整 project;前端用 ack 更新后续保存版本,仍必须以后续显式读取或生成完成返回的后端快照作为项目真相。
|
||||
- 画布自动保存使用防抖 + 串行队列:图层拖拽、缩放、资源新增和修改结果创建后延迟保存工程快照;如果上一次 `PATCH /api/editor/projects/{projectId}` 尚未完成,只保留最新待保存快照,待当前请求结束后再发送下一次保存,避免慢保存请求并发堆积触发发布入口连接限流。手型平移和小地图拖动属于临时 viewport 交互,拖动中只更新画布显示,不触发 `serializeCanvasLayout`、sessionStorage 项目缓存写入或封面快照上传,`pointerup` / `pointercancel` 后再保存最终 viewport。每次 `PATCH /api/editor/projects/{projectId}` 都必须携带最近一次服务端权威快照或保存 ack 给出的 `expectedRevision`;缺少版本号的请求在 HTTP 写入口直接拒绝,不允许回退到无版本覆盖。接口只返回 `{ projectId, canvasId, revision, updatedAt }` 轻量 ack,不返回完整 project;前端用 ack 更新后续保存版本。生成完成或显式读取返回较新权威快照时,若同项目仍有防抖待保存或在途保存的本地布局,前端必须以新快照的资源和生成终态为权威,只重放本地几何、层序、分组、隐藏、锁定、翻转、viewport、背景色和生成面板编辑,并立即基于新 revision 入队保存;后端新增项必须合入,后端已删除的旧项不得被本地旧快照复活,本地在请求期间删除的旧项也不得复活。
|
||||
- 移动端保留同一套状态模型,底部工具栏可横向滚动,侧边栏默认可收起。
|
||||
- 项目页卡片默认点击打开工程;hover 项目卡片右下角显示 `...` 菜单,菜单承载重命名和删除。选择模式下项目卡片只切换选中态,不进入画布;底部批量工具栏提供全选 / 取消全选、已选数量、批量删除和退出选择模式。
|
||||
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
|
||||
> 2026-07-21 已实施、待生产压测专题:BgFilter 作为受限内部资源,仍遵守“单用户动作一个外部生成 job”;用户可见层与调度层都只有父 `external_generation_job`。父 future 保持原 lease 和 attempt,在当前调用栈内同步请求唯一 `bgfilter-worker` 的内部 HTTP,成功图片字节直接返回父流程。首版不新增 SpacetimeDB 子任务表、父 checkpoint / continuation 或 raw 中间结果 OSS。完整边界见 [`BgFilter 受限资源调度方案(同步内部 HTTP 原地等待版)`](./【后端架构】BgFilter受限资源调度方案-2026-07-21.md)。
|
||||
|
||||
更新时间:`2026-07-21`
|
||||
更新时间:`2026-07-31`
|
||||
|
||||
## 背景
|
||||
|
||||
当前 VectorEngine `gpt-image-2`、音频、LLM 等外部生成链路多数由 `api-server` 的 HTTP handler 直接等待上游、OSS 持久化和 SpacetimeDB 回写完成。前端虽然有生成页和会话轮询,但 HTTP 进程仍承担长耗时副作用,导致接入更多玩法或大图生成时只能放大 API 进程,而不能单独扩展外部生成吞吐。
|
||||
VectorEngine `gpt-image-2`、音频、LLM 等外部生成不能由面向外部调用方的 HTTP 请求长期等待上游、OSS 持久化和 SpacetimeDB 回写。站内保留受控 `inline` 排障模式;External v1 的八类生成则固定使用持久队列和统一查询接口,避免调用方超时后重复提交、重复扣费或丢失已完成结果。
|
||||
|
||||
## 目标
|
||||
|
||||
- 默认 `queue` 模式下,`api-server` 的 HTTP 角色只负责鉴权、入参校验、扣费前置/状态初始化、任务入队和返回 `queued` 操作结果。
|
||||
- 外部生成副作用由独立 `external-generation-worker` 角色执行。
|
||||
- 多个 worker 进程通过 SpacetimeDB 任务表抢占任务,依赖 lease 超时恢复,支持按进程数和单进程并发动态缩扩容。
|
||||
- 本地或小流量同步排查可显式启用 `inline` 模式,由 HTTP handler 复用同一 worker executor 同步执行并返回 `completed`;该模式不创建队列任务,也不具备 worker 横向扩容能力。
|
||||
- 本地或小流量站内同步排查可显式启用 `inline` 模式,由站内 HTTP handler 复用同一 worker executor 同步执行并返回 `completed`;该模式不创建队列任务,也不具备 worker 横向扩容能力。External v1 不继承此例外,始终异步入队。
|
||||
- SpacetimeDB reducer / procedure 只做任务状态流转,不做网络、文件系统或外部 provider I/O。
|
||||
- 已接入拼图 `compile_puzzle_draft`、结果页 `generate_puzzle_images` 与结果页 `generate_puzzle_ui_background`,跳一跳、拼消消和敲木鱼的外部图片生成动作,以及图片画布编辑器的图片、改图、手动去背景、图标 spritesheet、UI 素材提取、角色动作、视频、音效和背景音乐生成。后续玩法和编辑器生成入口继续复用同一队列 Module,不再为每个入口发明独立队列。
|
||||
- 第一版外部生成队列粒度固定为“单个用户动作对应单个 job”。例如草稿编译、结果页单槽重生、图集重生都各自入一个 job;job 内部可以串行或并行调用 provider、OSS、SpacetimeDB 写回,但不再拆成“提示词 / 生图 / 切图 / 去背景 / 持久化 / 回写”等阶段 job。用户可见执行阶段通过现有任务行及摘要投影的轻量 `phase` 保存,不作为队列调度单位,也不写回大 payload。
|
||||
@@ -37,6 +37,8 @@
|
||||
- `get_external_generation_job_summary_and_return`:按 `job_id` 从轻量摘要投影读取单个任务状态,给 BFF 和生成页展示使用;必须只返回调用者有权读取的任务,不能暴露其它用户的 payload、错误详情或 worker 内部字段。
|
||||
- `get_external_generation_job_result_and_return`:仅供后端内部回填异步编辑器 Agent 工具调用;按 `job_id + owner_user_id` 返回 `status`、`last_error_message` 和已持久化的 `result_payload_json`,不返回请求 payload、lease 或其它 worker 字段。该 procedure 不替代摘要状态读取接口,也不经 BFF 暴露给前端。
|
||||
|
||||
External API job 复用同一个 `result_payload_json` 列,但只额外保存 `result` compact 引用:允许 objectKey、resource/asset ID、assetObjectId、尺寸、媒体类型、taskId 和告警;禁止完整 project/canvas、大布局、Data URL、Blob URL、临时 signed URL、provider 原始响应和 lease/fencing 控制字段。普通站内 job 继续保持原 payload 语义,不能为了 External 查询把所有队列结果扩成第二套资产 read model。
|
||||
|
||||
不带 `summary / summaries` 的旧 `get / list / acknowledge_external_generation_job*` procedure 只保留给受控内部兼容,不是 BFF 正式读取入口。
|
||||
|
||||
这个 Module 的 **Seam** 在 SpacetimeDB procedure + `spacetime-client` facade;`api-server` HTTP role 和 worker role 都只依赖这个 Interface。外部 provider、OSS、计费补偿、玩法草稿回写仍留在 `api-server` worker implementation 内,不进入 SpacetimeDB reducer。
|
||||
@@ -112,6 +114,8 @@ pending/running -> cancelled (预留)
|
||||
- `queue`:默认值,HTTP handler 入队 `external_generation_job`,由 `external-generation-worker` 角色 claim lease 后执行;生产、预发和压测默认使用该模式。
|
||||
- `inline`:HTTP handler 直接调用同一个 worker executor,同步等待 provider、OSS 和 SpacetimeDB 写回完成后返回 `operation.status = completed`;只用于本地或低并发排查,不提供队列持久化、lease 重领和 worker 横向扩容。
|
||||
|
||||
External v1 八类生成不读取上述模式分支:即使进程配置为 `inline`,External handler 仍只做校验、幂等入队并返回 HTTP `202`。调用方按 `/api/external/v1/generations/{operationId}` 查询;这条外部契约不能因部署环境不同而从异步退化为同步响应。
|
||||
|
||||
同一个 Rust binary 通过 `GENARRATIVE_PROCESS_ROLE` 切换:
|
||||
|
||||
- `api`:只启动 HTTP server。
|
||||
@@ -207,7 +211,19 @@ controller 配置:
|
||||
|
||||
透明背景处理正常成功时,角色形象、图标 spritesheet 和 UI 素材提取的画布都同时放透明主结果与 provider 原图:透明主结果保持生成器 `generatedLayerId` 主锚点,provider 原图作为第二个图层放在其右侧;图标和 UI 实际拆分出的业务素材从 provider 原图右侧继续排列。
|
||||
|
||||
inline 与 external v1 成功响应继续使用结构化 `warning.code/reason`;图标 / UI 的透明图已经成功、只有自动拆分失败时,继续返回结构化 `sliceWarning.code/reason`,其中 `sliceWarning.reason` 保留原始诊断。queue worker 把两类告警归一为有界的 `result_payload_json.warning`:只有一条时原样保留完整 `reason`;两条并存时按“通用在前、拆分在后”拼接,`code` 收敛为 `multiple-generation-warnings`(两条 `code` 相同则沿用原 `code`),任何一条都不得被丢弃。`sliceWarning.reason` 无论是否与通用告警并存都由 worker 添加“图集已生成,但自动拆分未完成:”前缀,拼接结果最后统一做长度上界收敛。任务摘要将该展示就绪的 `reason` 原样提取到 `warning_message`,单 job 状态和刷新后的任务列表 BFF 再以 `warning: string` 返回;Web 必须直接展示,不再补前缀或按 code 推断类型。历史任务保留写入时的 `reason` 快照,摘要 backfill 不按当前格式重新解释或补写前缀。该字符串语义是 worker / BFF / Web 的内部同版本契约,三者必须协调发布,不承诺滚动混部或旧 Web 缓存下的跨版本字符串兼容。
|
||||
inline 完成结果与 External v1 completed compact result 继续使用结构化 `warning.code/reason`;图标 / UI 的透明图已经成功、只有自动拆分失败时,继续返回结构化 `sliceWarning.code/reason`,其中 `sliceWarning.reason` 保留原始诊断。queue worker 把两类告警归一为有界的 `result_payload_json.warning`:只有一条时原样保留完整 `reason`;两条并存时按“通用在前、拆分在后”拼接,`code` 收敛为 `multiple-generation-warnings`(两条 `code` 相同则沿用原 `code`),任何一条都不得被丢弃。`sliceWarning.reason` 无论是否与通用告警并存都由 worker 添加“图集已生成,但自动拆分未完成:”前缀,拼接结果最后统一做长度上界收敛。任务摘要将该展示就绪的 `reason` 原样提取到 `warning_message`,单 job 状态和刷新后的任务列表 BFF 再以 `warning: string` 返回;Web 必须直接展示,不再补前缀或按 code 推断类型。历史任务保留写入时的 `reason` 快照,摘要 backfill 不按当前格式重新解释或补写前缀。该字符串语义是 worker / BFF / Web 的内部同版本契约,三者必须协调发布,不承诺滚动混部或旧 Web 缓存下的跨版本字符串兼容。
|
||||
|
||||
### External v1 异步提交与查询
|
||||
|
||||
External v1 复用上述九类 editor job kind 中除手动去背景外的八类生成 kind。外部 POST handler 只负责 API Key scope、owner、请求校验和入队,不调用 `*_for_owner` 同步执行函数:
|
||||
|
||||
1. 每个生成 POST 必须携带 `Idempotency-Key`。服务端把 owner、job kind、稳定键和规范请求纳入 dedupe;未知结果重试必须复用原键。
|
||||
2. 成功入队返回 HTTP `202`、`operationId`、`statusUrl`、`pollAfterMs`,并设置 `Location` / `Retry-After`;不返回 project、asset 或媒体结果。
|
||||
3. `GET /api/external/v1/generations/{operationId}` 通过 owner-safe facade 读取摘要。`queued/running` 返回 phase/progress;`failed` 返回脱敏错误;`completed` 再读取同一 owner 的生成 artifacts 并返回 `result_payload_json.result`。
|
||||
4. 跨 owner operationId 按不存在处理。查询路径不开放 claim、renew、complete、fail、retry、acknowledge 或 controller 控制面。
|
||||
5. completed 查询返回 compact artifact 引用。调用方需要完整画布时重新读取项目,需要媒体临时 URL 时再对稳定 objectKey 换签。
|
||||
|
||||
托管 MCP 的生成 tools 也走同一 External REST router:MCP 参数中的 `idempotencyKey` 映射到 HTTP `Idempotency-Key`,`get_external_editor_generation_job` 映射统一查询。MCP 不直接调用 SpacetimeDB procedure,不形成平行队列或结果账本。
|
||||
|
||||
## 验收
|
||||
|
||||
@@ -226,6 +242,8 @@ cargo check -p api-server --manifest-path server-rs/Cargo.toml
|
||||
cargo test -p spacetime-module external_generation --manifest-path server-rs/Cargo.toml
|
||||
cargo test -p spacetime-module level_generation_failure --manifest-path server-rs/Cargo.toml
|
||||
cargo test -p api-server external_generation_worker --manifest-path server-rs/Cargo.toml
|
||||
cargo test -p api-server external_editor_generation --manifest-path server-rs/Cargo.toml
|
||||
cargo test -p api-server external_mcp --manifest-path server-rs/Cargo.toml
|
||||
npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx -t "keeps generation progress visible"
|
||||
npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "compile_puzzle_draft"
|
||||
```
|
||||
@@ -239,7 +257,7 @@ curl -f http://127.0.0.1:<api-port>/healthz
|
||||
|
||||
本地 `npm run dev` 与 `npm run dev:api-server` 默认注入 `GENARRATIVE_PROCESS_ROLE=all`,同一 Rust 进程同时监听 HTTP 并消费外部生成队列;显式设置 `GENARRATIVE_PROCESS_ROLE` 时保留显式值。需要验证生产式拆分角色、lease 重领或扩缩容时,再分别启动 `api`、`external-generation-worker` 和 `external-generation-controller`,也可以使用隔离容器 smoke。
|
||||
|
||||
生产 smoke 需要保持 `GENARRATIVE_EXTERNAL_GENERATION_MODE=queue`,并至少启动一个 `api` 角色、一个 `external-generation-worker` 角色和一个 `external-generation-controller` 角色;发布脚本会在默认 worker pattern 下自动启用并启动 `genarrative-external-generation-worker@1.service`,重启并验活 `genarrative-external-generation-controller.service`。`genarrative-api.service` 还通过 systemd `Wants=genarrative-external-generation-controller.service` 弱依赖覆盖只启动 API 的现场兜底;controller 仍是独立进程,不由 HTTP 进程内执行 `systemctl`。若 worker 数量归零,生成任务会保持 `queued/running`,不会由 HTTP 进程偷偷执行。部署验证除 `/healthz` / `/readyz` 外,还要确认任务列表 BFF 可读、未确认终态任务会弹出提示、提示展示后后台 acknowledge 且刷新后不再弹出,单 job 状态能从 `queued/running` 收敛到业务 session/detail 的 ready 或 failed。
|
||||
生产 smoke 需要保持 `GENARRATIVE_EXTERNAL_GENERATION_MODE=queue`,并至少启动一个 `api` 角色、一个 `external-generation-worker` 角色和一个 `external-generation-controller` 角色;发布脚本会在默认 worker pattern 下自动启用并启动 `genarrative-external-generation-worker@1.service`,重启并验活 `genarrative-external-generation-controller.service`。`genarrative-api.service` 还通过 systemd `Wants=genarrative-external-generation-controller.service` 弱依赖覆盖只启动 API 的现场兜底;controller 仍是独立进程,不由 HTTP 进程内执行 `systemctl`。若 worker 数量归零,生成任务会保持 `queued/running`,不会由 HTTP 进程偷偷执行。部署验证除 `/healthz` / `/readyz` 外,还要确认任务列表 BFF 可读、未确认终态任务会弹出提示、提示展示后后台 acknowledge 且刷新后不再弹出,单 job 状态能从 `queued/running` 收敛到业务 session/detail 的 ready 或 failed。External smoke 还必须证明生成 POST 返回 `202`、同幂等键不重复创建任务、统一查询能读到 compact completed result、跨 owner 返回 `404`,并通过托管 MCP 调用同一提交/查询工具链。
|
||||
|
||||
systemd 生产 controller 与手动兜底示例:
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -26,7 +26,7 @@
|
||||
- 为历史审计、迁移、资产归属核对所必需的最小只读表定义;不得借兼容读取重新暴露旧创作、发布、公开详情或运行接口。
|
||||
- 编辑器、项目、账号、钱包、资产、HostBridge、运维和安全等平台公共能力。
|
||||
- 通用 `feature_gate_config`、`GET/PUT /admin/api/feature-gates` 与后台 `#gray-release` 控制页。灰度页只读取通用 gate,不再请求旧 `/admin/api/creation-entry/config`,固定目标只登记现役功能;不得恢复 `creation-entry:*` 动态目标。
|
||||
- 新版 `/creation` 创作工具主页、`/project` 项目入口、稳定的 `/profile` 个人页路由、`creation-home` 展示组件与现役静态资产。桌面端保留“创作 / 项目 / 我的”公共侧边栏,移动端保留同样三项的底部 dock;“我的”保留头像 / 昵称编辑、陶泥号复制、钱包与账单、统计、充值、兑换码、玩家社区、反馈、通用设置、开发者 API Key 和法律信息,不恢复旧模板入口、旧作品架或生成队列。
|
||||
- 新版 `/creation` 创作工具主页、`/project` 项目入口、稳定的 `/profile` 个人页路由、`creation-home` 展示组件与现役静态资产。桌面端保留“创作 / 项目 / 我的”公共侧边栏;移动端底部 dock 只保留“我的”,不得因退役旧模板而扩大移动端创作范围。“我的”保留头像 / 昵称编辑、陶泥号复制、钱包与账单、统计、充值、兑换码、玩家社区、反馈、通用设置、开发者 API Key 和法律信息,不恢复旧模板入口、旧作品架或生成队列。
|
||||
- `runtime_setting` 是账号级公共设置事实,不属于旧模板运行态。原表结构和数据不变,继续由鉴权后的 `GET/PUT /api/runtime/settings`、`get_runtime_setting_or_default` 与 `upsert_runtime_setting_and_return` procedure 支撑音乐音量和平台主题读写。
|
||||
- 旧页面、测试、素材、handler、service、worker、生成 bindings 和纯业务 crate 的源码目录;它们仅用于历史追溯,不属于任何正式入口或编译目标。
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
## 验收
|
||||
|
||||
- 旧 URL 不再命中旧页面或后端路由。
|
||||
- `/creation`、`/project` 与 `/profile` 在桌面端显示“创作 / 项目 / 我的”公共侧边栏,在 `390x844` 等移动视口显示同样三项的底部 dock;点击、刷新及浏览器前进 / 后退均保持路由与选中态一致。新创作主页只调用编辑器项目和公开编辑器素材接口;顶栏保持现役搜索、公共泥点入口与账号胶囊,不重新拼装平行账号按钮组。
|
||||
- `/creation`、`/project` 与 `/profile` 在桌面端显示“创作 / 项目 / 我的”公共侧边栏;在 `390x844` 等移动视口,底部 dock 只显示“我的”。移动端直达 `/creation`、`/project`、`/editor/canvas` 时显示桌面端创作提示,且不得挂载创作主页、项目列表或图片画布;移动端首页触发项目或画布动作时使用同一门禁。桌面端新创作主页只调用编辑器项目和公开编辑器素材接口;顶栏保持现役搜索、公共泥点入口与账号胶囊,不重新拼装平行账号按钮组。
|
||||
- “我的”桌面布局按原平台公共资料页全宽展示四个常用入口、两行设置和法律栏;头像、昵称、复制、充值、兑换码、社区、反馈、API Key 等入口可用,但不发起旧模板、旧公开作品或旧运行态请求。
|
||||
- 鉴权访问 `GET/PUT /api/runtime/settings` 不得返回 404,读写必须经 `spacetime-client` 调用现役 settings procedure;未鉴权请求返回 401,不恢复任何旧运行态设置路由。
|
||||
- `tsc --listFilesOnly` 与 Vite 干净加载均不得出现旧业务目录、上述顶层退役 module 或小程序旧订阅授权实现。
|
||||
|
||||
Reference in New Issue
Block a user