2026-05-17 16:15:52 +08:00
|
|
|
|
# 页面 AI 块编辑 — model 主路径操作缺少 content(direct path 架构定位错误)
|
|
|
|
|
|
|
|
|
|
|
|
> 发现时间:2026-05-16
|
|
|
|
|
|
>
|
2026-05-18 17:01:35 +08:00
|
|
|
|
> 更新时间:2026-05-18(运行时收口验证)
|
2026-05-17 16:15:52 +08:00
|
|
|
|
>
|
2026-05-18 17:01:35 +08:00
|
|
|
|
> 状态:`[done]`
|
2026-05-17 16:15:52 +08:00
|
|
|
|
>
|
|
|
|
|
|
> 关联主线:`07-ai` / `05-editor-mainline`
|
|
|
|
|
|
>
|
|
|
|
|
|
> 关联设计稿:`design/07-ai/done/7-13-page-block-editor-runtime-actor-v1.md`
|
|
|
|
|
|
>
|
|
|
|
|
|
> 关联代码:
|
|
|
|
|
|
> - `rust/crates/mnote-web/src/routes/page_ai_workflow.rs` — block_edit_workflow, direct_block_edit_operations(应退役), call_block_edit_model(唯一正确路径)
|
|
|
|
|
|
> - `rust/crates/mnote-web/src/hermes_tools/block.rs:436` — doc_apply_block_ops, replace 操作缺少 content
|
|
|
|
|
|
|
|
|
|
|
|
## 症状
|
|
|
|
|
|
|
|
|
|
|
|
用户用自然语言在页面 AI 面板输入块编辑指令时,`mnote.page_ai.block_edit_workflow` 返回 400:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
replace 操作缺少 content
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 复现步骤
|
|
|
|
|
|
|
|
|
|
|
|
1. 打开任意文档
|
|
|
|
|
|
2. 点击右下角「AI 助手」
|
|
|
|
|
|
3. 输入自然语言指令,如 `把第一段文字改成:AI成功修改了这一段。`
|
|
|
|
|
|
4. → `POST /api/page-ai/block-edit-workflow` 返回 400
|
|
|
|
|
|
5. 页面上显示 `mnote.page_ai.block_edit_workflow 失败`
|
|
|
|
|
|
|
|
|
|
|
|
## 架构问题:两条路径的设计是错误的
|
|
|
|
|
|
|
|
|
|
|
|
当前 `block_edit_workflow` 有两条路径,但**正确的路径只有一条**:
|
|
|
|
|
|
|
|
|
|
|
|
### 唯一正确路径:Model path(`call_block_edit_model`)
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
用户自然语言 → 模型理解语义 → 产出 operations JSON → doc_apply_block_ops → 写入
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这是 AI 面板应有的行为:用户说人话,模型理解意图,产出操作。这是**唯一主路径**。
|
|
|
|
|
|
|
|
|
|
|
|
### 应退役路径:Direct path(`direct_block_edit_operations`)
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
用户特定格式 → 正则抠「」内文本 → 直接拼 operations → 写入
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这不是 AI,这是**命令行**。它要求用户按固定格式输入(「」引号),本质是把 AI 面板当成 shell 在用。当前它"能用"只是因为绕过了模型调用,看起来"快",但:
|
|
|
|
|
|
- 不能处理自然语言("把这段话改简洁一些")
|
|
|
|
|
|
- 不能批量推理("把所有 TODO 改成已完成")
|
|
|
|
|
|
- 不能跨块理解("把第一段和第二段合并")
|
|
|
|
|
|
- 和 AI 对话的本意完全背离
|
|
|
|
|
|
|
|
|
|
|
|
**结论**:direct path 应该退役,model path 是唯一主路径。
|
|
|
|
|
|
|
|
|
|
|
|
## 当前 model 主路径的具体缺陷
|
|
|
|
|
|
|
|
|
|
|
|
系统 prompt(`page_ai_workflow.rs:274`)只说了 `op` 四选一,**没有告诉模型每种 operation 需要的字段**:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
当前 prompt:
|
|
|
|
|
|
"operations 的 op 只能是 replace、insert_after、delete、move_after。优先使用 page_xml 中的 block id;禁止输出解释文字。"
|
|
|
|
|
|
|
|
|
|
|
|
缺少的信息:
|
|
|
|
|
|
- replace 需要 blockId(或 matchText)+ content
|
|
|
|
|
|
- insert_after 需要 blockId(或 matchText)+ content
|
|
|
|
|
|
- delete 只需要 blockId(或 matchText)
|
|
|
|
|
|
- move_after 需要 blockId + targetBlockId
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
模型不知道 schema,自然会漏掉 `content` 字段。DeepSeek v4 Flash 的 `response_format: json_object` 只保证输出是合法 JSON,不保证字段齐全。
|
|
|
|
|
|
|
|
|
|
|
|
## 更深层问题:块操作粒度是否合理
|
|
|
|
|
|
|
|
|
|
|
|
当前 AI 编辑通过 `operations: [{ op: "replace", blockId: "...", content: "..." }]` 这种逐个块操作的方式执行。但:
|
|
|
|
|
|
|
|
|
|
|
|
1. **mnote 的在线文档本质上是一个 markdown 文件**,Tiptap 只是其块级 UI 表现层
|
|
|
|
|
|
2. **本地 md 文件模式**下,Tiptap 退化为纯显示层,编辑直接在 markdown 文本上进行
|
|
|
|
|
|
3. 在线文档应该**向本地文档靠拢**——对 AI 而言,最自然的编辑方式是"给我一段 markdown,我返回修改后的 markdown",而不是"给我 blocks 数组,我逐个块产出 op"
|
|
|
|
|
|
|
|
|
|
|
|
如果在线文档和本地文档走两套 AI 编辑口径(一套块操作、一套文本操作),长期维护成本翻倍。
|
|
|
|
|
|
|
|
|
|
|
|
## 建议修复方向
|
|
|
|
|
|
|
|
|
|
|
|
### 立即修复(让 model 主路径可用)
|
|
|
|
|
|
1. **补全 system prompt 的 operation JSON schema**:明确 replace/insert_after 需要 `content` 字段,给出完整示例
|
|
|
|
|
|
|
|
|
|
|
|
### 架构收口(应该做的)
|
|
|
|
|
|
2. **退役 direct path**:`direct_block_edit_operations` + `quoted_segments` 整条路径标记 deprecated
|
|
|
|
|
|
3. **考虑降低块操作粒度**:AI 编辑是否可以走 markdown diff 而非逐个块 ops?在线文档和本地 md 能否共用同一条 AI 写入路径?
|
|
|
|
|
|
4. **在线/本地口径收敛**:在 `design/07-ai/` 中明确在线文档 AI 编辑应向本地 md 的简洁模型靠拢
|
|
|
|
|
|
|
|
|
|
|
|
## 证据
|
|
|
|
|
|
|
|
|
|
|
|
- Browser smoke (2026-05-16):
|
|
|
|
|
|
- Direct path(「」quote,本质是命令行): ✅ 177ms — 这说明不了 AI 能力,只是正则匹配
|
|
|
|
|
|
- Model path(自然语言,真 AI): ❌ 400,replace 缺少 content
|
|
|
|
|
|
- 代码:`page_ai_workflow.rs` system prompt 缺 operation schema
|
|
|
|
|
|
- 用户反馈:direct path 不是 AI 面板应有的行为,应退役
|
2026-05-18 17:01:35 +08:00
|
|
|
|
|
|
|
|
|
|
## 运行时修复
|
|
|
|
|
|
|
|
|
|
|
|
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:67) 当前运行时已退役 direct path,所有块编辑请求统一走模型输出 `search/replace` 对,再进入 `mnote.doc.markdown_edit`。
|
|
|
|
|
|
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:307) system prompt 已改为明确要求 `operations[].search` 与 `operations[].replace`,不再要求模型输出旧的 block op `content` 字段。
|
|
|
|
|
|
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:109) fast-path 写入已改走统一 mnote tool executor,避免绕过 tool toggle / audit / idempotency。
|
|
|
|
|
|
|
|
|
|
|
|
## 验证
|
|
|
|
|
|
|
|
|
|
|
|
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_workflow -- --nocapture`
|
|
|
|
|
|
- 结果:3 passed
|
|
|
|
|
|
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web markdown_edit -- --nocapture`
|
|
|
|
|
|
- 结果:8 passed
|
|
|
|
|
|
|
|
|
|
|
|
## 剩余说明
|
|
|
|
|
|
|
|
|
|
|
|
旧的 `direct_block_edit_operations` / `quoted_segments` helper 当前仅作为 dead code 与历史单测保留,不再是 `block_edit_workflow` 的运行时入口;后续可单独清理测试语义,但不影响本缺陷的运行时修复。
|