圆桌草稿持久化:localStorage 迁至后端 MySQL
新增 /api/roundtable-drafts 草稿 CRUD 与会话级推荐历史;前端 useDraftPersistence 改走 api/drafts,删除本地 drafts.ts。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
24e92c437d
commit
838cae0d2d
16 files changed
+1410
-480
No files matched your search
@@ -20,6 +20,7 @@
|
||||
- **leader 共识 = `status: []` 空数组**;澄清 = `status: "clarification"`(字符串);派活 = `status: [[name, task], ...]`。这三者**互斥不混用**。
|
||||
- **后端 picks clamp 2–6,前端用户可手动加到 8**。
|
||||
- **不写死任何具体模型名**:`intent / recommend / multi_agent` 三个 router 的 `model_name = str(payload.get("model") or "")`,空串会被下游 `lead_agent._resolve_model_name()` 自动回退到 `config.yaml` 的 `models[0]`(与 `/api/ai-writing` 行为一致)。内网部署只需配 `config.yaml`,前后端均无需改硬编码。
|
||||
- **草稿/推荐历史持久化(2026-06)**:圆桌页的「历史草稿」从浏览器 `localStorage` 迁到**后端 MySQL**(`/api/roundtable-drafts`,按登录用户隔离);新增**会话级推荐历史**让推荐弹窗回显上次结果。两张表 `roundtable_drafts` / `roundtable_recommend_history`,启动 `create_all` 自动建表,详见 §7A。
|
||||
|
||||
---
|
||||
|
||||
@@ -73,7 +74,12 @@ offline-backend-20260512/backend/
|
||||
│ ├── multi_agent.py # Step 2:init(SSE) + run/stream + 共享的 loopback 工具
|
||||
│ ├── intent.py # Step 1:init + stream(import multi_agent 工具)
|
||||
│ ├── recommend.py # Step 1→2:推荐 stream(import multi_agent 工具)
|
||||
│ └── artifacts.py # Step 3:列出/下载/编辑 thread 产出(通用,非圆桌专用)
|
||||
│ ├── artifacts.py # Step 3:列出/下载/编辑 thread 产出(通用,非圆桌专用)
|
||||
│ └── roundtable_drafts.py # 持久化:草稿 CRUD + 推荐历史(§7A,2026-06 新增)
|
||||
├── packages/harness/deerflow/persistence/roundtable_drafts/
|
||||
│ ├── model.py # ORM:RoundtableDraftRow + RoundtableRecommendHistoryRow
|
||||
│ ├── sql.py # RoundtableDraftRepository(CRUD + 历史)
|
||||
│ └── __init__.py # make_roundtable_draft_store(sf)
|
||||
├── packages/harness/deerflow/tools/builtins/
|
||||
│ └── clarification_utils.py # resolve_allow_multiple + coerce_bool(Step1/Step2 共享)
|
||||
└── .deer-flow/agents/
|
||||
@@ -110,8 +116,15 @@ offline-backend-20260512/backend/
|
||||
| GET | `/api/threads/{thread_id}/artifacts` | 3 | 列出该 thread 的 outputs 文件 |
|
||||
| GET | `/api/threads/{thread_id}/artifacts/{path}` | 3 | 预览/下载单个产出(含 `.skill/` 内文件提取) |
|
||||
| PATCH | `/api/threads/{thread_id}/artifacts/{path}` | 3 | 编辑文本类产物(白名单后缀) |
|
||||
| GET | `/api/roundtable-drafts` | 持久化 | 列出当前用户草稿(轻量元数据) |
|
||||
| POST | `/api/roundtable-drafts` | 持久化 | 新建草稿 |
|
||||
| GET | `/api/roundtable-drafts/{id}` | 持久化 | 取草稿全量(含 step1/step2 快照) |
|
||||
| PUT | `/api/roundtable-drafts/{id}` | 持久化 | 局部更新(title / furthest_step / step1 / step2) |
|
||||
| DELETE | `/api/roundtable-drafts/{id}` | 持久化 | 删除草稿 |
|
||||
| GET | `/api/roundtable-drafts/{id}/recommendations` | 持久化 | 列出该草稿的推荐历史(倒序) |
|
||||
| POST | `/api/roundtable-drafts/{id}/recommendations` | 持久化 | 追加一次推荐记录 |
|
||||
|
||||
字段级请求/响应见各 step 的 API 文档;下文只写**后端实现语义**。
|
||||
字段级请求/响应见各 step 的 API 文档;下文只写**后端实现语义**。圆桌草稿/推荐历史持久化见 §7A。
|
||||
|
||||
---
|
||||
|
||||
@@ -589,6 +602,53 @@ Step 3 **没有** 单独的 `multi_agent` 路由;使用既有 artifacts 能力
|
||||
|
||||
---
|
||||
|
||||
## 7A. 圆桌草稿与推荐历史持久化(`roundtable_drafts.py`,2026-06)
|
||||
|
||||
圆桌规划页的「历史记录」此前只存浏览器 `localStorage`(前端 `utils/drafts.ts`),换设备/清缓存即丢、大对话还会撞 5MB 配额。2026-06 迁到**后端 MySQL**,按登录用户隔离、跨设备保留;并新增**会话级推荐历史**,让推荐弹窗能回显上次推荐结果。
|
||||
|
||||
### 7A.1 数据模型(`deerflow.persistence.roundtable_drafts`)
|
||||
|
||||
仿 `user_prompts`(最简 CRUD)+ `ai_writing_sessions`(大 JSON 用 `PortableLongText` 存)两个现有模式。两张表:
|
||||
|
||||
- **`roundtable_drafts`**(会话主表,一条 = 一次完整圆桌会话)
|
||||
- `id`(PK, str64) / `user_id`(idx) / `title`(512) / `furthest_step`(int, 1|2|3)
|
||||
- `step1` / `step2`:前端 `DraftStep{1,2}Snapshot` 的 JSON,列类型 **`PortableLongText`**(MySQL `LONGTEXT`),避免 Step 2 长对话 + `seatStubMessages` 撞 `TEXT` 64KB 上限
|
||||
- `created_at` / `updated_at`:**`BeijingDateTime`**(项目规范,存北京时间且 tz-aware)
|
||||
- **`roundtable_recommend_history`**(推荐历史,按 `draft_id` 归属)
|
||||
- `id`(PK) / `user_id`(idx) / `draft_id`(idx) / `objective`(512) / `status`(32: done|fallback|asking|error) / `model`(128)
|
||||
- `rationale` / `picks`(JSON) / `candidates`(JSON 候选池快照):均 `PortableLongText`
|
||||
- `created_at`:`BeijingDateTime`
|
||||
|
||||
JSON 列在 repo(`sql.py`)里用 `json.dumps/loads` 手动序列化(与 `ai_writing_sessions.transcript` 同法)。两个 Row 已登记进 `persistence/models/__init__.py`,**启动时 `Base.metadata.create_all` 自动建表**——新部署无需手动建表(需 mysql 账号有建库/表权限)。
|
||||
|
||||
### 7A.2 路由(`app/gateway/routers/roundtable_drafts.py`,前缀 `/api/roundtable-drafts`)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `` | `list_drafts` → 轻量元数据列表(不含 step 大 blob),按 `updated_at` 倒序 |
|
||||
| POST | `` | `create_draft`,可带客户端 `id`(稳定 id 跨 create+update) |
|
||||
| GET | `/{id}` | `get_draft` 全量;非属主 → 404 |
|
||||
| PUT | `/{id}` | `update_draft`,`model_dump(exclude_unset=True)` → 只更新客户端实际发的字段 |
|
||||
| DELETE | `/{id}` | 204;非属主 → 404 |
|
||||
| GET | `/{id}/recommendations` | 推荐历史倒序 |
|
||||
| POST | `/{id}/recommendations` | 追加一次推荐(先校验草稿属主,404 拦截越权) |
|
||||
|
||||
鉴权/属主:用 `_current_user_id(request)`(登录态取 `request.state.user.id`,无登录回退 `get_effective_user_id()` 的 `"default"`),与 `user_prompts` 一致;所有 repo 读写按 `user_id` 过滤,跨用户访问一律 404。store 在 `deps.py` 接为 `app.state.roundtable_draft_store`(`make_roundtable_draft_store(sf)`),`app.py` `include_router`。
|
||||
|
||||
### 7A.3 与前端的契约要点
|
||||
|
||||
- **PUT 不带 `title` 时不改标题**:前端自动保存只发 `furthest_step`/`step1`/`step2`,不发 `title`,因此**不会覆盖用户手动重命名**;标题只在 create(派生)与显式重命名(只发 title)时改。
|
||||
- **推荐时机**:前端在打开推荐弹窗前先 `ensureDraft` 落地草稿拿到 `draft_id`,推荐完成后 POST 一条历史;重开弹窗 GET 历史回显最近一次,可「重新分析」再 POST。
|
||||
- **字段命名**:后端 snake_case(`furthest_step`/`created_at`),前端 `api/drafts.ts` 在边界映射成 camelCase。
|
||||
|
||||
### 7A.4 设计取舍
|
||||
|
||||
- **整存 JSON 而非拆表**:step1/step2 形状宽且常变(前端 hook 里是 loose 类型),整存 JSON 避免每次改前端快照都要迁库;代价是后端不校验嵌套字段(与 `ai_writing_sessions.transcript` 同取舍)。
|
||||
- **推荐历史绑定草稿而非全局**:语义是「这个会话上次推荐了什么」,故按 `draft_id` 归属;这也要求推荐前必有 `draft_id`(前端 `ensureDraft` 保证)。
|
||||
- **本地 dev 走 sqlite**:`config.yaml` `database.backend` 本地默认 sqlite,走同一套 SQLAlchemy 代码,`PortableLongText/JSON` 各自降级;生产切 mysql 用同一份代码。
|
||||
|
||||
---
|
||||
|
||||
## 8. 内置席位 SOUL 设计要点
|
||||
|
||||
每个 `roundtable-*`(除 intent / recommender)遵循统一结构:角色定位、核心职责、输出风格(emoji + 简短结论)、边界(只产出本席位、等总控派活)。
|
||||
@@ -1195,6 +1255,9 @@ client = httpx.AsyncClient(transport=transport, base_url=...)
|
||||
| 推荐路由 | `offline-backend-20260512/backend/app/gateway/routers/recommend.py` |
|
||||
| Step 2 路由 | `offline-backend-20260512/backend/app/gateway/routers/multi_agent.py` |
|
||||
| Step 3 列出/下载/编辑 | `offline-backend-20260512/backend/app/gateway/routers/artifacts.py` |
|
||||
| 草稿/推荐历史路由(§7A) | `offline-backend-20260512/backend/app/gateway/routers/roundtable_drafts.py` |
|
||||
| 草稿/推荐历史持久化 | `offline-backend-20260512/backend/packages/harness/deerflow/persistence/roundtable_drafts/` |
|
||||
| store 接线 | `offline-backend-20260512/backend/app/gateway/deps.py`(`app.state.roundtable_draft_store`) |
|
||||
| 路由注册 | `offline-backend-20260512/backend/app/gateway/app.py` |
|
||||
| 澄清单选/多选 | `offline-backend-20260512/backend/packages/harness/deerflow/tools/builtins/clarification_utils.py` |
|
||||
| 任务理解 Agent | `offline-backend-20260512/backend/.deer-flow/agents/roundtable-intent/` |
|
||||
|
||||
Reference in new issue
Block a user