Auto-open draft result after foundation completes

This commit is contained in:
2026-04-25 10:52:39 +08:00
parent 35c2bce6f1
commit 03acbc5cb1
31 changed files with 36472 additions and 232 deletions

View File

@@ -38,3 +38,15 @@
2. 确认前端不再通过任何路径调用 Node 后端能力。
3. 删除旧脚本、旧 smoke、旧 manifest 与 `server-node/` 目录。
4. 删除冻结基线检查中对历史引用的豁免。
## 6. 已确认迁移项
### 6.1 场景幕背景图提示词
2026-04-25 已把旧 Node 自动资产链路中的场景幕背景图提示词包装迁移到 Rust 主线:
1. 旧来源:`server-node/src/services/customWorldAgentAutoAssetService.ts``buildSceneActPrompt(...)`
2. 新主源:`server-rs/crates/api-server/src/custom_world.rs``build_scene_act_background_image_prompt(...)`
3. 使用位置:`generate_draft_foundation_act_backgrounds(...)` 收集 `sceneChapterBlueprints[].acts[]` 后,先构造幕背景图专用提示词,再调用 `generate_custom_world_scene_image_for_profile(...)`
4. 保留语义:世界名、场景名、幕标题、幕摘要、幕目标、过渡钩子、主角色、辅助角色、世界气质、背景描述,以及“只生成环境背景,不出现角色立绘、站位 UI、对白框、按钮或文字”的约束。
5. 迁移边界:`server-node/` 仅作为历史来源说明,不再参与运行;后续调整统一修改 Rust 主源。

View File

@@ -249,7 +249,19 @@
3. 针对初始同伴流程补一份单独的状态图 / 时序图
4. 对大 chunk 警告做代码分包
## 14. 一句话总结
## 14. SpacetimeDB 绑定桥接层要做同名去重
`server-rs/crates/spacetime-client` 里有一部分内容是围绕 SpacetimeDB 生成绑定补的手写桥接层。
经验:
- 新增 procedure、input type 或 mapper 时,先全局确认 `module_bindings/mod.rs``mapper.rs`、业务封装文件里是否已经存在同名声明
- `module_bindings/mod.rs` 同一个模块只保留一条 `pub mod` 和一条 `pub use`,不要同时放在 reducer 区和 procedure 区
- `mapper.rs` 的字符串枚举解析函数、API 入参结构只保留一个权威定义,业务侧统一复用
- 业务封装文件里同一个 procedure 只暴露一个客户端方法,避免 Rust 在编译期出现 E0428、E0252、E0119、E0592 这类重复定义错误
- 修复重复绑定时优先删除后追加的重复块,不要重写整文件,避免影响中文注释和生成绑定附近的大段内容
## 15. 一句话总结
这个项目真正的开发经验不是“怎么多写一个按钮”,而是:

View File

@@ -24,3 +24,9 @@
- 新草稿中每一幕的 `backgroundPromptText` 应该像自然的画面描述,包含主体、前中远景、站位空间、氛围识别点。
- 不应再出现“第1幕背景玩家会在……”这类明显拼接句。
- 如果 LLM 漏掉 `actBackgroundPromptTexts`,生成幕背景图阶段应失败并提示缺少 `backgroundPromptText`,而不是静默使用拼接文案。
## 2026-04-24 并发限流错误处理补充
- 批量生成幕背景图时,`JoinSet` 子任务的成功值和失败值固定承载 `(chapter_index, act_index, message)`,用于把错误精确标记回对应章节幕。
- `Semaphore::acquire``AcquireError` 不能在子任务中转成裸 `String` 后直接使用 `?`,否则会破坏子任务统一错误类型并导致 `E0277`
- 限流器异常应映射为同一组三元组错误,保持后续 `mark_scene_act_background_generation_error` 和部分成功保留逻辑可复用。

View File

@@ -21,8 +21,11 @@ RPG 草稿生成进入底稿素材阶段后,角色主形象与场景幕背景
- 背景分支使用 `JoinSet``sceneChapterBlueprints[*].acts[*]` 的每一幕背景任务一次性投递,返回后写入 `backgroundImageSrc``backgroundAssetId``generatedScenePrompt``generatedSceneModel`
- `merge_generated_act_backgrounds` 只把背景图字段合并回角色分支副本,再进入后续草稿卡编译和 SpacetimeDB 写入。
- 幕背景 prompt 同时兼容 `backgroundPromptText``scenePromptText``visualPromptText``promptText``imagePromptText``backgroundPrompt``visualPrompt`,避免 LLM 输出字段别名导致整批背景图被误判缺失。
- 每个角色主形象、每一幕背景图都必须独立自动重试,单项最多尝试 3 次。任一单项超过 3 次仍失败时,后台任务必须把 operation 标记为 `failed` 并停止写入草稿卡,避免生成“缺主图 / 缺背景图”的可进入世界档案。\r\n- 图片任务仍然一次性投递,保证角色与幕背景两类任务不回退到串行编排;但真正请求上游生图服务时必须共用并发闸门,当前同一底稿最多同时发起 2 个上游请求,降低 DashScope 瞬时 502 / 限流导致整批失败的概率。\r\n- 幕背景图失败文案必须带第几章、第几幕和幕标题不能只显示“第1幕 / 第2幕 / 第3幕”否则多章节同名幕会被用户误认为同一失败项重复上报
- 中止前必须持久化已经成功生成的部分底稿到会话 `draftProfile`,不能因为某个角色或某一幕失败而丢掉其它已生成的 `imageSrc / generatedVisualAssetId / backgroundImageSrc / backgroundAssetId`
- 每个角色主形象、每一幕背景图都必须独立自动重试,单项最多尝试 3 次。幕背景图允许部分成功:只要至少一幕成功,就必须保留已成功写入的 `backgroundImageSrc` 并继续生成草稿卡;全部幕都失败时才把素材阶段标记为“生成幕背景图失败”
- 图片任务仍然一次性投递,保证角色与幕背景两类任务不回退到串行编排;但真正请求上游生图服务时必须共用并发闸门。并发数由 `GENARRATIVE_DRAFT_ASSET_GENERATION_MAX_CONCURRENT_REQUESTS``DRAFT_ASSET_GENERATION_MAX_CONCURRENT_REQUESTS` 配置,默认 4避免固定为 2 导致多角色、多幕草稿总耗时过长
- 幕背景图失败文案必须带第几章、第几幕和幕标题不能只显示“第1幕 / 第2幕 / 第3幕”否则多章节同名幕会被用户误认为同一失败项重复上报。
- 中止或部分失败前必须持久化已经成功生成的部分底稿到会话 `draftProfile`,不能因为某个角色或某一幕失败而丢掉其它已生成的 `imageSrc / generatedVisualAssetId / backgroundImageSrc / backgroundAssetId`
- 每一幕自动生图必须记录 operation、session、第几章、第几幕、sceneId、sceneName、attempt、elapsedMs 与供应商真实错误,避免再次出现只看到“生成幕背景图失败”但无法定位哪张图、哪次请求、哪个上游原因的问题。
- 前端 `CharacterAnimator` 对带 `generatedVisualAssetId` 但尚无 `animationMap` 的自定义角色,所有状态优先渲染生成主图;只有真正发布了动作集后才按动作帧播放,避免运行或战斗状态回落到模板 sprite。
## 后续注意

View File

@@ -43,3 +43,11 @@
3. SpacetimeDB 写入必须通过 `spacetime-client` 已生成绑定,不在 reducer 中访问网络或文件系统。
4. 所有新增 Rust 代码保留中文注释,且只做局部修改,避免重写包含中文的大文件。
## 6. 失败排查原文日志
1. RPG 草稿生成链路的模型输入与模型输出原文日志统一收口在 `platform-llm` 网关层,避免每个模板调用点重复实现。
2. 只有发生请求失败、上游非 2xx、响应读取失败、JSON/SSE 解析失败或空响应时,才将本次模型输入与已拿到的模型输出原文分别写入文件;正常成功生成不默认落盘原文,避免日志体积不可控。
3. 日志目录默认使用仓库运行目录下的 `logs/llm-raw`,可通过 `LLM_RAW_LOG_DIR` 覆盖;每次失败写成同一 trace 前缀下的 `*.input.json``*.output.txt` 两个 UTF-8 文件。
4. `*.input.json` 记录 provider、model、stream、attempt、maxTokens 与完整 messages`*.output.txt` 记录上游 HTTP 原文、非流式响应原文、SSE 原始事件文本,或请求尚未到达上游时的错误摘要。
5. 文件名只使用时间戳、进程号、递增序号与安全化错误阶段不包含用户输入、sessionId 或 API key输入 JSON 不写入 API key。
6. 文件日志失败只写 warn不影响草稿生成主错误返回该日志仅用于本地开发与排障不作为 SpacetimeDB 真相态。

View File

@@ -0,0 +1,35 @@
# 创作 Agent 发布门槛结果页归一化回写修正
日期:`2026-04-24`
## 1. 问题现象
`custom_world.publish_gate` 诊断日志显示:
1. `has_draft_profile=true`
2. `has_result_preview=true`
3. `has_world_hook=true`
4. `has_core_conflicts=true`
5. 但仍存在 `publish_missing_player_premise / publish_missing_main_chapter / publish_missing_first_act`
这说明接口可正常读取 session问题不在 `GET /api/runtime/custom-world/agent/sessions/:sessionId` 本身,而在结果页 profile 回写到 session 时,发布门槛需要的部分结构字段没有稳定保留下来。
## 2. 根因
前端结果页通过 `normalizeCustomWorldProfileRecord``resultPreview.preview` 转成 `CustomWorldProfile`。该归一化模型原本主要服务作品库与运行时展示,只保留了 `settingText / summary / playerGoal / creatorIntent / anchorContent / sceneChapterBlueprints` 等字段,没有把后端发布门槛直接读取的顶层 `worldHook / playerPremise` 纳入 `CustomWorldProfile` 稳定字段。
当自动保存或发布前执行 `sync_result_profile` 时,前端会把归一化后的 profile 传回 SpacetimeDB。若这份 profile 中缺少顶层 `playerPremise`,且 `creatorIntent / anchorContent` 又未包含可读玩家切入字段,后端最终 publish gate 会继续报 `publish_missing_player_premise`
## 3. 修复口径
1. `CustomWorldProfile` 显式声明 `worldHook / playerPremise` 为 Agent 发布快照兼容字段。
2. `normalizeCustomWorldProfileRecord` 保留顶层 `worldHook / playerPremise`,并在缺失时从 `creatorIntent.worldHook / creatorIntent.playerPremise / summary / playerGoal` 做最小回填。
3. 不在 UI 新增规则说明文案;这两个字段只作为后端发布门槛与 session 回写的稳定数据槽位。
4. 后端 publish gate 继续以 SpacetimeDB 中的 `draft_profile_json` 为最终真相源,前端只负责把结果页当前 profile 完整同步回去。
## 4. 验收标准
1.`resultPreview.preview` 构建结果页 profile 后,`worldHook / playerPremise` 不会被前端归一化丢弃。
2. 自动保存或点击发布前执行 `sync_result_profile` 时,传回后端的 profile 保留发布门槛所需顶层字段。
3. 若当前草稿确实包含玩家切入与 `sceneChapterBlueprints[*].acts`,后端诊断日志不应再出现对应结构 blocker。
4. 若草稿真实缺失章节或第一幕,`publish_missing_main_chapter / publish_missing_first_act` 仍应保留,不做前端假放行。

View File

@@ -52,3 +52,14 @@
- 不再把 `server-node/src/prompts/characterAssetPrompts.ts` 作为主链修改目标。
- 默认描述字段必须由世界草稿生成阶段写入,前端只负责把字段填入输入框并允许用户编辑。
- UI 不默认展示规则解释文案,正式约束只进入后端 prompt。
## 5. 自动草稿素材回写约束
- 世界草稿自动素材生成与草稿页手动生成使用同一套 `server-rs/crates/api-server/src/custom_world_ai.rs` 场景图接口和 OSS/SpacetimeDB 资产持久化链路。
- 自动批量生成幕背景时,后端必须把已成功生成的 `backgroundImageSrc/backgroundAssetId/generatedScenePrompt/generatedSceneModel` 写回 `sceneChapterBlueprints[*].acts[*]`,不能因为同批某一幕失败而丢弃已成功图片。
- 某一幕连续重试仍失败时,只允许在该幕写入 `backgroundGenerationError` 作为诊断字段;只要至少一幕成功,草稿仍应完成并让前端展示成功图片。
- 只有全部幕背景均失败时,才把“生成幕背景图失败”作为草稿素材阶段失败原因保存。
- Rust 服务实际生图模型读取 `DASHSCOPE_SCENE_IMAGE_MODEL` / `DASHSCOPE_COVER_IMAGE_MODEL` / `DASHSCOPE_REFERENCE_IMAGE_MODEL`;兼容旧 `DASHSCOPE_IMAGE_MODEL`,避免 `.env.example` 中配置了模型但服务端仍使用硬编码模型。
- 自动草稿幕背景不能把 `backgroundPromptText` 直接作为最终 `prompt` 传给 DashScope它必须像草稿页手动生成一样把幕级描述作为 `userPrompt`,并用同一个地点对象的 `name/description/dangerLevel` 作为场景上下文,再由 `build_custom_world_scene_image_prompt` 统一拼入世界名、世界摘要、风格、玩家目标、场景名、场景描述和负面词。用户不修改默认描述直接点生成时,手动生成与自动草稿生成的正式生图上下文必须一致。
- 自动草稿幕背景的默认尺寸必须与草稿页手动生成默认尺寸一致,当前统一为 `1280*720`;不能在自动链路中单独改成 `1600*900`,否则同一 prompt 在同一模型下也可能因供应商尺寸支持或耗时不同而表现不一致。
- 批量自动生图失败日志必须保留 `AppError.details.message` 中的供应商真实原因,不能只记录 `AppError.message()` 的 HTTP 泛化文案,否则排查时只能看到“上游服务请求失败”,无法确认是尺寸、模型、限流、超时还是内容审核失败。

View File

@@ -4,6 +4,8 @@
## 文档列表
- [CREATION_AGENT_PUBLISH_GATE_NORMALIZE_WRITEBACK_FIX_2026-04-24.md](./CREATION_AGENT_PUBLISH_GATE_NORMALIZE_WRITEBACK_FIX_2026-04-24.md):记录结果页 profile 归一化回写丢失顶层 `worldHook / playerPremise` 导致 publish gate 继续误报结构 blocker 的根因,并冻结前端归一化保留发布字段的修复口径。
- [CUSTOM_WORLD_RESULT_ENTITY_GENERATION_FIX_2026-04-24.md](./CUSTOM_WORLD_RESULT_ENTITY_GENERATION_FIX_2026-04-24.md):记录世界结果页在 Agent 草稿模式下新增场景、新增 NPC 生成成功但结果页字段不可用的根因,并冻结 `api-server` 生成归一化层补齐 profile 字段的修复口径。
- [ADMIN_CONSOLE_SERVICE_DESIGN_2026-04-23.md](./ADMIN_CONSOLE_SERVICE_DESIGN_2026-04-23.md):冻结 Rust `api-server` 内后台管理服务首版方案,明确管理员用户名密码登录、管理员 JWT 鉴权、数据库概览、受控 API 调试台与同源管理页面的落地边界。
- [SPACETIME_MODULE_LIB_RS_SPLIT_EXECUTION_2026-04-23.md](./SPACETIME_MODULE_LIB_RS_SPLIT_EXECUTION_2026-04-23.md):冻结 `server-rs/crates/spacetime-module/src/lib.rs` 的模块地图、二级落位点与迁移顺序,要求后续 SpacetimeDB 主工程改动按对应模块落位,不再继续堆回单大文件。

View File

@@ -0,0 +1,26 @@
# RPG 生成流程刷新恢复与即时持久化设计2026-04-24
## 背景
- RPG 共创从 Agent 聊天页触发 `draft_foundation` 后进入生成过程页。
- 旧实现只持久化 `activeSessionId``activeOperationId`,刷新时恢复入口会无条件回到 Agent 聊天页。
- operation 失败后继续创作也会因为 operation 指针被清空而缺失生成页上下文。
## 目标
1. 生成中刷新网页后仍停留在生成过程页。
2. 生成完成后结果页内容第一时间落入作品持久化链路。
3. 生成失败后从创作入口继续处理该草稿时,优先回到生成过程页展示失败状态,而不是 Agent 聊天页。
## 落地规则
- 前端只保存恢复指针,不在 UI 持久层复制世界数据。
- `sessionStorage` 与 URL query 中增加生成页来源字段 `customWorldGenerationSource`,当前仅支持 `agent-draft-foundation`
- 初始恢复时:
- 若存在 `activeOperationId` 且来源为 `agent-draft-foundation`,先进入 `custom-world-generating`
- 否则若 session 已经可构建结果预览,进入 `custom-world-result`
- 其他情况进入 `agent-workspace`
- operation 进入 `completed``failed` 后仍保留 `activeOperationId`,直到用户离开、重新发起操作或清理工作区,保证刷新和继续创作能恢复完成/失败状态。
- 生成完成后由 `useRpgCreationResultAutosave` 在结果页立即保存。生成页跳结果页前必须先同步最新 session 并写入 `generatedCustomWorldProfile`,确保自动保存消费的是最新快照。
## 验收点
- 生成中刷新URL/sessionStorage 可恢复 `custom-world-generating`,页面显示“世界草稿生成进度”。
- 生成失败刷新或继续创作:页面仍显示生成过程页和失败信息,不展示 Agent 聊天页。
- 生成完成:跳到结果页后触发 `upsertRpgWorldProfile`,保存请求带 `sourceAgentSessionId`