This commit is contained in:
2026-04-24 22:25:13 +08:00
parent 75681751c2
commit 67062a8af3
43 changed files with 1857 additions and 268 deletions

View File

@@ -0,0 +1,45 @@
# AI 生成过程草稿持久化设计2026-04-24
## 1. 背景
当前创作类模板已经具备 session / message / operation 级别的最终态落库能力,但部分流式生成只把模型增量推给前端。若 HTTP/SSE 连接、浏览器页面或 LLM 请求在最终解析前中断,用户只能看到短暂流式文本,服务端缺少可恢复的生成中间态。
本设计补齐“生成过程中已经生成的内容必须持续持久化”的机制,并要求该机制对所有创作模板统一生效。
## 2. 目标
1. 每次模板生成开始前创建或绑定一个 `ai_task`
2. 模型每次产出可见文本增量时,写入 `ai_text_chunk`,并同步更新 `ai_task.latest_text_output` 与对应 stage 的 `text_output`
3. 生成失败或连接中断时,不丢弃已经落库的 chunk后续可用 `ai_task.latest_text_output` 作为续写上下文。
4. 成功解析并 finalize 后,将最终结构化结果继续写回各模板原有 session 表,保持现有业务快照不变。
## 3. 统一落库边界
### 3.1 真相表
- `ai_task`:记录一次模板生成任务的业务来源、状态、最新聚合文本、结构化结果。
- `ai_task_stage`:记录模板生成阶段状态;当前创作对话统一使用 `DraftGeneration`
- `ai_text_chunk`:按 `sequence` 追加保存模型增量文本,是断点恢复的最小粒度。
### 3.2 适用模板
- 自定义世界创作 Agent。
- 解谜游戏创作 Agent。
- 大鱼吃小鱼创作 Agent。
- 后续新增模板必须复用同一生成草稿持久化工具,不允许只在 UI 内存保存流式文本。
## 4. 续写策略
1. 发起生成时,后端根据 `template_key + session_id + operation_id` 创建稳定 `task_id`
2. LLM 流式回调收到 `replyText` 的最新可见文本后,计算相对上一次文本的增量;只有非空增量写入 `ai_text_chunk`
3. 写入失败不应阻断当前生成主流程,但必须记录 warn 日志,避免因持久化瞬时失败导致用户生成直接失败。
4. 若最终解析失败,`ai_task` 保持 `Running` 或显式 `Failed`,已写入的 `latest_text_output` 仍可作为下一轮 prompt 的“已生成草稿”。
5. 下一轮续写 prompt 应优先带上最近未完成任务的 `latest_text_output`;本次先落地服务端 chunk 持久化能力,后续模板 prompt 可逐步消费该草稿。
## 5. 编码要求
1. 持久化逻辑放在 `server-rs/crates/api-server` 的通用工具中,由各模板路由接入。
2. 不引入 `server-node` 兼容分支。
3. SpacetimeDB 写入必须通过 `spacetime-client` 已生成绑定,不在 reducer 中访问网络或文件系统。
4. 所有新增 Rust 代码保留中文注释,且只做局部修改,避免重写包含中文的大文件。

View File

@@ -0,0 +1,41 @@
# 世界草稿图片生成与预览补齐说明
更新时间:`2026-04-24`
## 1. 检查结论
当前 server-rs 的世界底稿生成链路已经在 `draft_foundation` 后台任务中补齐两类图片:
1. `playableNpcs``storyNpcs` 中的每个角色都会调用角色主形象生成链路,并把 `imageSrc``generatedVisualAssetId` 写回底稿。
2. `sceneChapterBlueprints[].acts[]` 中的每一幕都会调用场景图生成链路,并把 `backgroundImageSrc``backgroundAssetId`、生成提示词与模型信息写回底稿。
图片生成后不落本地真值,而是通过 OSS `put_object -> head_object -> confirm_asset_object -> bind_asset_object_to_entity` 确认对象,并用兼容的 `/generated-*` 路径供前端读取。
## 2. 前端缺口
结果页的场景列表此前只把每个场景的第一张幕背景图作为场景卡封面。这样虽然后端已经生成了每一幕图片,但用户只能看到第一幕,无法在结果页确认同一场景下其他幕的图片是否存在。
## 3. 本次落地
1.`CustomWorldEntityCatalog` 中增加每幕图片缩略条,来源为当前场景匹配到的 `sceneChapterBlueprints[].acts[].backgroundImageSrc`
2. 保留原来的场景卡封面策略:第一幕背景图仍作为主封面,旧的场景图字段继续作为兜底。
3. 缩略条只展示已生成图片的幕,不额外暴露章节结构文本,避免结果页变成规则说明面板。
4. 增加结果页测试,覆盖同一场景下两幕背景图都能在前端以图片形式预览。
## 4. 验收点
1. 生成世界草稿完成后,角色页签中所有可扮演角色和场景角色能展示 `imageSrc`
2. 场景页签中,每个场景卡片仍展示主封面。
3. 场景卡片下方能横向预览该场景所有已生成幕背景图。
4. OSS 未配置或上传失败时,后端任务应失败并把错误写入 operation而不是生成伪本地路径。
## 5. 上游图片服务失败降级
`draft_foundation` 的底稿文本结构是进入结果页和继续编辑的主产物,角色主图、幕背景图属于可后补资产。若 DashScope 或 OSS 上游临时不可用,后台任务不应把整份底稿标记为失败。
本次补充后:
1. 角色主图分支失败时operation 记录错误信息并继续使用未带角色图的底稿。
2. 幕背景图分支失败时operation 记录错误信息并继续使用未带幕图的底稿。
3. 已成功的并行资产分支仍会合并回底稿,不会被失败分支覆盖。
4. 后续可通过资产工坊或单项生成动作补齐缺失图片。

View File

@@ -37,8 +37,9 @@ npm run dev:rust
4. 等待 `spacetime --root-dir=server-rs/.spacetimedb/local server ping http://127.0.0.1:3101` 可用。
5. 执行 `spacetime --root-dir=server-rs/.spacetimedb/local publish <本地数据库名> --server http://127.0.0.1:3101 --module-path server-rs/crates/spacetime-module -c=on-conflict --yes`,确保 publish 的签名身份与 standalone 的本地控制库一致,并在当前开发阶段允许新版模块表结构变化且发生 schema 冲突时清除旧模块数据。
6. 注入 `GENARRATIVE_API_*``GENARRATIVE_SPACETIME_*` 后启动 `cargo run -p api-server`;直接运行 `api-server` 时,如未显式设置 `GENARRATIVE_SPACETIME_DATABASE`,服务端也会向上查找 `spacetime.local.json` 作为本地默认库名。
7. 注入 `GENARRATIVE_BACKEND_STACK=rust``RUST_SERVER_TARGET``GENARRATIVE_RUNTIME_SERVER_TARGET` 后启动 Vite
8. 任一子进程退出时,脚本回收其余子进程
7. 等待 `http://127.0.0.1:<api-port>/healthz` 返回 HTTP 响应后再启动 Vite避免前端初始化请求早于 Rust `api-server` 监听完成并在终端刷出 `ECONNREFUSED 127.0.0.1:<api-port>`
8. 注入 `GENARRATIVE_BACKEND_STACK=rust``RUST_SERVER_TARGET``GENARRATIVE_RUNTIME_SERVER_TARGET` 后启动 Vite
9. 任一子进程退出时,脚本回收其余子进程。
Vite 代理覆盖范围:
@@ -84,6 +85,13 @@ npm run dev:rust:logs -- --follow
1. 如果首页公开广场出现 `上游服务请求失败`,优先检查 `api-server` 错误详情里的 `ws://.../v1/database/<database>/subscribe` 是否指向了未发布的库。
2. `spacetime --root-dir=server-rs/.spacetimedb/local list --server http://127.0.0.1:3101` 应能看到 `spacetime.local.json` 中的库名;若没有,执行 `spacetime --root-dir=server-rs/.spacetimedb/local publish <本地数据库名> --server http://127.0.0.1:3101 --module-path server-rs/crates/spacetime-module -c=on-conflict --yes`
3. 发布库名与 `GENARRATIVE_SPACETIME_DATABASE` 不一致时,`/api/runtime/custom-world-gallery` 会从 Rust `api-server` 返回 `502`,前端首页只能展示空态或错误提示,无法自行修复。
4. 如果 Vite 输出 `/api/auth/refresh``/api/auth/login-options``/api/runtime/custom-world-gallery``ECONNREFUSED`,先确认当前脚本是否已经打印 `等待 api-server 就绪` 并通过;正常情况下 Vite 只会在 `/healthz` 可访问后启动,不应再因为 Rust 监听未完成而代理失败。
编译警告治理:
1. Rust 本地栈启动日志应保持可行动,运行态未使用函数不应长期保留为普通编译警告。
2. 仅供测试断言使用的辅助函数使用 `#[cfg(test)]` 限定,避免进入 `cargo run -p api-server` 的普通二进制编译。
3. 已无调用入口且无迁移价值的映射函数直接删除;如果后续新增同类 SpacetimeDB 记录映射,再按实际调用路径补回,避免提前保留死代码。
## 3. Ubuntu 发布包脚本