Browse Source
把「代办」页升级为「记忆」页的完整设计:统一 memory_item 模型 承载闹钟/待办/灵感/花销四类且分类可扩展,会议纪要经 AI 抽取自动 落成待办,每日播报、提前 5 分钟提醒、周报月报复盘,以及经 MCP 工具让 EMAI 助手随时问答,形成录入→提醒→复盘→问答的闭环。 盘点后确认在 allhelp 已有的任务体系与异步总结流水线之上演进, 而非另起炉灶;DBTask「仅记录不自动触发」的限制与花销后端空白 是本次要补的主要缺口。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>main
1 changed files with 584 additions and 0 deletions
@ -0,0 +1,584 @@ |
|||||
|
# 记忆中心(Memory Center)设计与开发文档 |
||||
|
|
||||
|
> 版本:v0.1 草案 日期:2026-08-31 |
||||
|
> 范围:`apps/services`(后端)、`apps/client`(Flutter)、`apps/admin`(后台)、百炼 Agent 配置 |
||||
|
> 状态:设计评审中,未开工 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 一、背景与目标 |
||||
|
|
||||
|
App 现有的「代办」页(`TodoTab`)只是一个日历壳子:数据来自 12 条写死的演示数据加上 EMAI 助手落下来的闹钟,全部存在本地 `GetStorage`,服务端没有任何对应存储。 |
||||
|
|
||||
|
本次要把它升级成**「记忆」页**——用户一天里发生的各类事情的统一入口,并围绕它形成「录入 → 提醒 → 复盘 → 问答」的闭环。 |
||||
|
|
||||
|
### 六项目标需求 |
||||
|
|
||||
|
| # | 需求 | 关键约束 | |
||||
|
|---|---|---| |
||||
|
| 1 | 记忆页展示多类事项:闹钟、灵感记忆、花销、待办,**分类可扩展** | 一天多件事,按分类组织 | |
||||
|
| 2 | 每日首次登录,自动弹窗展示当天事项并**语音播报**,播报可随时关闭 | 播报可中断 | |
||||
|
| 3 | 待办/闹钟**提前 5 分钟提醒**,语音播报内容 | App 未打开时也要触发 | |
||||
|
| 4 | 每周一凌晨生成上周总结:未完成事项、花销合计、灵感汇总,播报后**需用户确认** | 服务端定时生成 | |
||||
|
| 5 | 每月 1 号生成上月总结:完成/未完成情况、开销、灵感汇总 | 同上 | |
||||
|
| 6 | 上述数据可在 EMAI 助手里随时提问,助手能答 | 走 MCP 工具 | |
||||
|
|
||||
|
### 设计原则 |
||||
|
|
||||
|
1. **统一模型**:一张表承载所有分类,新增分类不改表结构、不加接口。 |
||||
|
2. **服务端为真相源**:本地只做缓存与离线兜底,换设备数据要在。 |
||||
|
3. **失败隔离**:AI 抽取、总结生成失败不影响主流程,宁可少一条记忆,不可让会议纪要或登录卡住。 |
||||
|
4. **不重复造轮子**:`allhelp` 已有任务体系与 MCP 工具,在其上演进。 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 二、现状盘点(本设计的地基) |
||||
|
|
||||
|
### 2.1 已有且可直接复用 |
||||
|
|
||||
|
| 能力 | 位置 | 说明 | |
||||
|
|---|---|---| |
||||
|
| **用户任务表 `DBTask`** | [allhelp_db.proto](../apps/proto/allhelp/allhelp_db.proto) | `task_name/task_desc/task_type/trigger_time/cron_expr/status/extra`,存 **MySQL 业务库** | |
||||
|
| 任务增删改查接口 | `allhelp` 模块 `api_addtask/gettasks/canceltask/completetask` | 已上线 | |
||||
|
| **MCP 工具(EMAI 可调)** | [tool_get_user_tasks.go](../apps/services/modules/mcp/tool_get_user_tasks.go)、`tool_allhelp_task.go`、`tool_cancel_user_task.go` | EMAI 已能查/建/取消任务 | |
||||
|
| **异步 AI 总结流水线** | [allhelp/summary.go](../apps/services/modules/allhelp/summary.go) | Redis 队列 + N worker + `doubao.Chat`,周报/月报可照搬 | |
||||
|
| 每日聊天总结表 `DBChatSummary` | 同上 | `(uid, summary_date)` 唯一,覆盖重算 | |
||||
|
| **TTS 播报** | [tts_service.dart](../apps/client/lib/data/services/tts_service.dart) | `startspeak/speakOnce/speakStream/flushStream/stop`,**已支持中断** | |
||||
|
| 本地通知插件 | `flutter_local_notifications ^18.0.1` | 目前只用于录音后台提示 | |
||||
|
| 助手端侧指令 | [assistant_directive_service.dart](../apps/client/lib/data/services/assistant_directive_service.dart) | `SET_clock` 已落地成提醒 | |
||||
|
| 会议纪要生成 | [echomeet/tasks.go](../apps/services/modules/echomeet/tasks.go) `AIProcess` | `summary` 已含「四、待办事项」章节 | |
||||
|
|
||||
|
### 2.2 缺口 |
||||
|
|
||||
|
| 缺口 | 影响需求 | |
||||
|
|---|---| |
||||
|
| `DBTask` **「服务端仅做记录,不自动触发」**(proto 注释原文) | 3/4/5 | |
||||
|
| 没有分类概念,无法承载灵感/花销 | 1 | |
||||
|
| **花销功能后端完全空白**,客户端只有 i18n 文案和 mock | 1/4/5 | |
||||
|
| 没有周报/月报表与生成任务 | 4/5 | |
||||
|
| 客户端待办数据不落服务端(`synced` 恒为 false) | 全部 | |
||||
|
| 会议纪要里的待办是自然语言,无结构化字段 | 1 | |
||||
|
| 本地通知从未用于事项提醒 | 3 | |
||||
|
|
||||
|
### 2.3 存储分工(迁移讨论已确认) |
||||
|
|
||||
|
| 数据 | 库 | |
||||
|
|---|---| |
||||
|
| 用户级业务数据(任务、记忆项、报告) | **MySQL 业务库**(per-app 一份) | |
||||
|
| 跨应用共享(会议模板 `echomeet_template`、产品、授权码) | **PostgreSQL 公共库** | |
||||
|
| 缓存与队列 | Redis | |
||||
|
|
||||
|
> 阿龙测试机 8.133.166.29 上 PostgreSQL 是自建 `postgres:16` 容器(库 `starpivot_console`),与第三方无关。 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 三、核心模型:记忆项(MemoryItem) |
||||
|
|
||||
|
### 3.1 为什么不直接扩展 `DBTask` |
||||
|
|
||||
|
`DBTask` 的字段是为「提醒任务」设计的:`task_name` + `trigger_time` + `cron_expr`。要塞进花销金额、灵感正文、会议来源、负责人,只能全丢进 `extra` JSON,那样查询、统计、排序全部失效——月度「一共花了多少钱」将无法用 SQL 聚合。 |
||||
|
|
||||
|
**决策:新建 `memory_item` 表作为唯一真相源**,`DBTask` 作为历史链路保留并迁移(见 §9.1)。 |
||||
|
|
||||
|
### 3.2 表结构 `memory_item`(MySQL 业务库) |
||||
|
|
||||
|
```sql |
||||
|
CREATE TABLE memory_item ( |
||||
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, |
||||
|
uid VARCHAR(50) NOT NULL, |
||||
|
category VARCHAR(16) NOT NULL, -- alarm/todo/idea/expense/…(可扩展) |
||||
|
source VARCHAR(16) NOT NULL, -- meeting/assistant/manual/device |
||||
|
source_id VARCHAR(64) DEFAULT '', -- 会议记录 id、指令 id 等 |
||||
|
|
||||
|
title VARCHAR(200) NOT NULL, -- 主文案(所有分类通用) |
||||
|
detail TEXT, -- 补充说明 / 灵感正文 |
||||
|
|
||||
|
happen_date DATE NOT NULL, -- 归属日期(日历定位用,永不为空) |
||||
|
happen_time VARCHAR(5) DEFAULT '', -- HH:mm,无具体时间则空 |
||||
|
date_certain TINYINT DEFAULT 1, -- 0=日期是推断的(如落在会议当天) |
||||
|
due_raw VARCHAR(64) DEFAULT '', -- 截止时间原文(「下周三前」) |
||||
|
|
||||
|
repeat_rule VARCHAR(16) DEFAULT '', -- once/daily/weekly/weekdays/weekend/monthly |
||||
|
repeat_raw VARCHAR(64) DEFAULT '', -- 重复规则原文 |
||||
|
weekday TINYINT DEFAULT 0, -- weekly 时 1-7 |
||||
|
|
||||
|
amount DECIMAL(12,2) DEFAULT 0, -- 花销金额(category=expense) |
||||
|
currency VARCHAR(8) DEFAULT 'CNY', |
||||
|
owner VARCHAR(64) DEFAULT '', -- 负责人原文(会议待办) |
||||
|
|
||||
|
state TINYINT DEFAULT 0, -- 0待办 1完成 2废弃 |
||||
|
remind_ahead INT DEFAULT 5, -- 提前提醒分钟数,0=不提醒 |
||||
|
remind_at DATETIME NULL, -- 计算好的提醒时刻(服务端算,客户端直接用) |
||||
|
|
||||
|
user_edited TINYINT DEFAULT 0, -- 用户改过 → 重新生成时不覆盖 |
||||
|
gen_round INT DEFAULT 0, -- 第几轮 AI 生成 |
||||
|
extra VARCHAR(2000) DEFAULT '', |
||||
|
|
||||
|
create_time BIGINT, update_time BIGINT, finish_time BIGINT, |
||||
|
|
||||
|
INDEX idx_uid_date (uid, happen_date), |
||||
|
INDEX idx_uid_cat (uid, category, happen_date), |
||||
|
INDEX idx_remind (remind_at, state) |
||||
|
); |
||||
|
``` |
||||
|
|
||||
|
### 3.3 字段设计要点 |
||||
|
|
||||
|
**`happen_date` 永不为空**——这是记忆页能工作的前提。日历按日期排,没有日期的项根本渲染不出来。会议待办抽不出截止日期时,落在**会议当天**并置 `date_certain=0`,卡片上用 `due_raw` 标注「下周三前」,用户可改期。 |
||||
|
|
||||
|
**`category` 是字符串不是枚举**——新增分类(如 `health`、`travel`)只加常量、不改表、不迁移。代价是无法在 DB 层约束取值,靠服务端白名单校验。 |
||||
|
|
||||
|
**`amount` 用 DECIMAL 不用 float**——月度金额聚合,浮点误差不可接受。 |
||||
|
|
||||
|
**`remind_at` 由服务端算好**——重复规则展开、时区换算、提前量扣减都在服务端做一次,客户端拿到就是一个绝对时刻,不重复实现容易出错的日期逻辑。 |
||||
|
|
||||
|
**`user_edited` + `gen_round`**——会议重新总结时,删掉上一轮 `user_edited=0` 的项,保留用户勾过改过的。不做这个,用户点一次「重新生成」就会丢掉自己的修改。 |
||||
|
|
||||
|
### 3.4 分类与字段的对应关系 |
||||
|
|
||||
|
| 分类 | 必填 | 主要用到 | 不用 | |
||||
|
|---|---|---|---| |
||||
|
| `todo` 待办 | title, happen_date | owner, due_raw, state, remind_* | amount, repeat_* | |
||||
|
| `alarm` 闹钟 | title, happen_date, happen_time | repeat_rule, repeat_raw, weekday, remind_* | amount, owner | |
||||
|
| `idea` 灵感 | title, happen_date | detail | amount, remind_*, repeat_* | |
||||
|
| `expense` 花销 | title, happen_date, amount | currency, detail | remind_*, repeat_*, owner | |
||||
|
|
||||
|
统一表的代价是每行有约一半字段为空。以本项目的数据量级(单用户日均个位数条目)完全可以接受,换来的是新增分类零成本。 |
||||
|
|
||||
|
### 3.5 周期报告表 `memory_report` |
||||
|
|
||||
|
```sql |
||||
|
CREATE TABLE memory_report ( |
||||
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, |
||||
|
uid VARCHAR(50) NOT NULL, |
||||
|
period_type VARCHAR(8) NOT NULL, -- week/month |
||||
|
period_key VARCHAR(16) NOT NULL, -- 2026-W35 / 2026-08 |
||||
|
period_start DATE, period_end DATE, |
||||
|
|
||||
|
stat_json TEXT, -- 结构化统计(见下) |
||||
|
summary TEXT, -- AI 生成的播报文案 |
||||
|
state TINYINT DEFAULT 0, -- 0待生成 1生成中 2完成 3失败 |
||||
|
error_msg VARCHAR(500), |
||||
|
|
||||
|
confirmed TINYINT DEFAULT 0, -- 用户是否已确认(需求4) |
||||
|
confirm_time BIGINT, |
||||
|
|
||||
|
create_time BIGINT, update_time BIGINT, |
||||
|
UNIQUE KEY uk_uid_period (uid, period_type, period_key) |
||||
|
); |
||||
|
``` |
||||
|
|
||||
|
`stat_json` 是**服务端用 SQL 算出来的确定性统计**,不经过 LLM: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"todo_total": 12, "todo_done": 8, "todo_undone": 4, |
||||
|
"undone_items": [{"id":123,"title":"跟进 A 客户","happen_date":"2026-08-26"}], |
||||
|
"expense_total": 1280.50, "expense_count": 15, |
||||
|
"expense_by_day": {"2026-08-25": 120.00}, |
||||
|
"idea_count": 3, |
||||
|
"idea_items": [{"id":456,"title":"做一个会议纪要转待办的功能"}], |
||||
|
"alarm_total": 7 |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
**金额与完成数绝不能让 LLM 算**——它只负责把 `stat_json` 组织成一段自然、适合朗读的话。数字算错比话说得干巴严重得多。 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 四、服务端接口 |
||||
|
|
||||
|
新建 `memory` 模块(`apps/services/modules/memory/`),由 `home` 服务装载。按反射注册约定,方法签名满足 `func (c *apiComp) X(session comm.IUserSession, req *pb.XReq) (*pb.XResp, *pb.ErrorData)` 即自动注册为 `memory_x`。 |
||||
|
|
||||
|
| 接口 | 用途 | 关键参数 | |
||||
|
|---|---|---| |
||||
|
| `memory_list` | 拉取记忆项 | `start_date`/`end_date`/`categories[]`/`states[]` | |
||||
|
| `memory_today` | 当天全部事项(需求 2 弹窗直接用) | `date`(客户端本地日期) | |
||||
|
| `memory_add` | 新增 | 整个 item | |
||||
|
| `memory_update` | 修改(自动置 `user_edited=1`) | id + 变更字段 | |
||||
|
| `memory_del` | 删除 | `ids[]` | |
||||
|
| `memory_complete` | 标记完成 | `ids[]` | |
||||
|
| `memory_upcoming` | 拉未来 N 天待提醒项(客户端排本地通知用) | `days`(默认 7) | |
||||
|
| `memory_getreport` | 取周报/月报 | `period_type`/`period_key` | |
||||
|
| `memory_confirmreport` | 用户确认报告(需求 4) | `report_id` | |
||||
|
| `memory_stats` | 即时统计(EMAI 问答与页面头部用) | 时间范围 + 分类 | |
||||
|
|
||||
|
**`memory_today` 单独开一个接口**而不是复用 `memory_list`:登录弹窗是启动路径上的强依赖,要能一次拿全(含当天报告待确认标记),且便于单独做缓存和降级。 |
||||
|
|
||||
|
### 4.1 时区 |
||||
|
|
||||
|
`happen_date` 是**用户本地日期**,不是 UTC 日期。客户端请求时带上 `tz`(如 `Asia/Shanghai`)与本地日期字符串,服务端不做时区推断。跨时区旅行时以客户端当前时区为准——这是刻意选择:用户"今天"的定义应该跟着人走。 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 五、定时与触发 |
||||
|
|
||||
|
这是需求 2/3/4/5 的核心,也是现在完全空白的部分。三类触发分别放在不同的地方,**不要试图用一套机制全包**。 |
||||
|
|
||||
|
### 5.1 提前 5 分钟提醒(需求 3)→ **客户端本地调度** |
||||
|
|
||||
|
**必须在客户端做**,因为 App 未打开、甚至无网时也要响。服务端推送做不到(无 APNs/FCM 通道,且国内厂商推送要逐家接)。 |
||||
|
|
||||
|
``` |
||||
|
登录/前台恢复 → memory_upcoming(days=7) |
||||
|
→ flutter_local_notifications.zonedSchedule() 逐条排期 |
||||
|
→ 到点系统通知(带内容) |
||||
|
→ 用户点击进 App → TtsService 播报 title + detail |
||||
|
``` |
||||
|
|
||||
|
要点: |
||||
|
- 每次拉取**先取消旧排期再重排**,避免改期后旧通知还在。 |
||||
|
- Android 需 `SCHEDULE_EXACT_ALARM` 权限(Android 13+),iOS 需通知授权。**未授权时降级为「打开 App 才提醒」**,不要阻塞主流程。 |
||||
|
- 排期上限按平台限制(iOS 最多 64 条待处理通知),只排最近 7 天、按时间取前 N 条。 |
||||
|
- 通知点击进来才播报,**不在通知里直接播声音**——锁屏状态下强行出声是骚扰。 |
||||
|
|
||||
|
### 5.2 每日登录播报(需求 2)→ **客户端** |
||||
|
|
||||
|
``` |
||||
|
App 启动/切前台 |
||||
|
→ 判断「今天是否已弹过」(本地存 last_daily_popup_date) |
||||
|
→ 否 → memory_today |
||||
|
→ 弹窗(列表 + 播报按钮,默认自动开始播报) |
||||
|
→ TtsService.startspeak → speakStream 逐条 → flushStream |
||||
|
→ 用户点关闭/返回 → TtsService.stop() |
||||
|
``` |
||||
|
|
||||
|
播报文案在客户端本地拼(不调 LLM),格式如: |
||||
|
|
||||
|
> 今天有 3 件事。上午 9 点,项目周会。下午 2 点,跟进 A 客户,负责人王总。另外有一条灵感记录:做一个会议纪要转待办的功能。 |
||||
|
|
||||
|
**`stop()` 必须真的能打断**——`TtsService` 接口已有该方法,且 `speakStream` 是队列式播放,停止时要连带清队列,否则会出现「关了还在念下一句」。 |
||||
|
|
||||
|
### 5.3 周报/月报生成(需求 4/5)→ **服务端 cron + 异步队列** |
||||
|
|
||||
|
照搬 [allhelp/summary.go](../apps/services/modules/allhelp/summary.go) 的成熟模式: |
||||
|
|
||||
|
``` |
||||
|
lego cron(在 memory 模块 Start 中注册) |
||||
|
周一 00:30 → 扫描上周有数据的 uid |
||||
|
1 号 00:30 → 扫描上月有数据的 uid |
||||
|
↓ 逐个 uid 建 memory_report(state=0) 并 LPush Redis 队列 |
||||
|
N 个 worker BRPop |
||||
|
↓ SQL 算出 stat_json(确定性统计,不过 LLM) |
||||
|
↓ LLM 只做一件事:把 stat_json 组织成适合朗读的话 → summary |
||||
|
↓ 回写 state=2 |
||||
|
``` |
||||
|
|
||||
|
要点: |
||||
|
- **cron 只在一个实例上跑**。`home` 是集群服务,多副本会重复生成。用 Redis `SET NX EX` 抢锁,key 带日期(`memory:report:lock:week:2026-W35`)。 |
||||
|
- 时间选 **00:30 而非 00:00**——避开整点其他定时任务,也给跨日数据落库留缓冲。 |
||||
|
- 用户量大时按 uid 分片入队,不要一次性扫全表。 |
||||
|
- **只给「上周期有数据的用户」生成**,空报告没有意义还浪费 LLM 调用。 |
||||
|
- 客户端下次打开时 `memory_getreport` 拉取 → 弹窗播报 → 用户确认 → `memory_confirmreport`。**不做「凌晨推送叫醒用户」**。 |
||||
|
|
||||
|
### 5.4 为什么不用现有 `timer` 服务 |
||||
|
|
||||
|
`timer` 是独立的集群服务,目前只有 `timer_uselog` 一个模块,且与 `memory` 数据不在同一进程,跨服务调用反而增加复杂度。**周报/月报 cron 直接放 `memory` 模块内**(`lego/sys/cron`),与 echomeet 的做法一致。 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 六、客户端设计 |
||||
|
|
||||
|
### 6.1 页面改名与结构 |
||||
|
|
||||
|
`TodoTab` → `MemoryTab`(「记忆」)。i18n key 全量替换,41 个语言文件都要动。 |
||||
|
|
||||
|
``` |
||||
|
记忆页 |
||||
|
├── 日历卡(CalendarCard,复用现有几何/手势逻辑) |
||||
|
├── 分类筛选条(全部 / 待办 / 闹钟 / 灵感 / 花销) ← 新增 |
||||
|
└── 事项列表(MemoryScopeList,由 TodoScopeList 改造) |
||||
|
├── 日/周/月范围切换(已有) |
||||
|
├── 头部汇总:3 件待办 · 2 条灵感 · 花销 ¥248 ← 扩展 |
||||
|
└── 分类着色的卡片 |
||||
|
``` |
||||
|
|
||||
|
### 6.2 数据层 |
||||
|
|
||||
|
新增 `MemoryService`(写法参照 `AssistantDirectiveService`): |
||||
|
|
||||
|
- 服务端为准,本地 `GetStorage` 做缓存与离线兜底 |
||||
|
- `RxList<MemoryItem>`,页面 `Obx` 响应 |
||||
|
- 启动拉取 + 下拉刷新 + 写操作后局部刷新 |
||||
|
- 冲突策略:**服务端返回覆盖本地**(本地不做离线编辑队列,一期不支持离线改) |
||||
|
|
||||
|
`CalendarController._rebuildEvents` 改为**从 `MemoryService` 单一来源**投影成 `CalendarEvent`: |
||||
|
|
||||
|
```dart |
||||
|
title = item.title |
||||
|
description = [item.owner, item.dueRaw].where((e)=>e.isNotEmpty).join(' · ') |
||||
|
date = item.happenDate |
||||
|
startTime = item.happenTime |
||||
|
color = _colorOf(item.category) // 分类配色 |
||||
|
taskStatus = item.state == 1 ? 2 : 0 |
||||
|
amount = item.amount // expense 分类 |
||||
|
``` |
||||
|
|
||||
|
**同时删除 `mockCalendarEvents()`**——真实数据上线后,演示数据混在里面会让人以为系统出错。 |
||||
|
|
||||
|
### 6.3 助手提醒的迁移 |
||||
|
|
||||
|
`AssistantDirectiveService` 保留(它是**指令流水日志**,有独立价值:排查、统计百炼下发了哪些没实现的指令),但: |
||||
|
|
||||
|
- `_handleSetClock` 不再写本地 `reminders`,改为调 `memory_add`(`category=alarm`, `source=assistant`) |
||||
|
- 本地 `reminders` 列表在迁移期保留双写,一个版本后移除 |
||||
|
- 老版本本地已有的提醒,首次启动时**一次性上传**并打标记,避免重复 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 七、会议总结 → 记忆项 |
||||
|
|
||||
|
### 7.1 模板提示词改造(`echomeet_template`,PostgreSQL 公共库) |
||||
|
|
||||
|
`General-Meeting` 的 `template` 第四节现在只有一句「梳理会议中明确的后续执行任务,清晰标注任务核心要求」,**没要求负责人和截止时间**,抽取那一路再强也补不出原文没提炼的信息。 |
||||
|
|
||||
|
改为: |
||||
|
|
||||
|
```markdown |
||||
|
## 四、待办事项 |
||||
|
逐条梳理会议中明确的后续执行任务,每条固定包含「任务内容、负责人、截止时间」三要素,按以下格式输出: |
||||
|
1. 任务内容。负责人:[张三];截止时间:本周五前。 |
||||
|
2. 任务内容。负责人:未明确;截止时间:未明确。 |
||||
|
- 三要素中任何一项会议原文未明确的,写「未明确」,禁止推测、补全或编造; |
||||
|
- 截止时间原样保留原文表述(如「下周三前」「月底」),不要自行换算成具体日期; |
||||
|
- 只收录会议中明确要求执行的任务;仅在讨论中提及、未拍板的设想不计入; |
||||
|
- 同一件事被多次提及只输出一条,取最终确定的版本; |
||||
|
- 若全程没有任何明确的执行任务,本节只输出一句「本次会议无明确待办事项。」 |
||||
|
``` |
||||
|
|
||||
|
`负责人:` / `截止时间:` 是给后续抽取用的**固定锚点**,人读着也自然。「未明确」比留空好——空缺分不清是模型漏了还是原文没有。 |
||||
|
|
||||
|
**只改 `template` 不改 `outline`**:`outline` 产出的 `overview` 是用户在「概览」Tab 直接看的正文([overview_tab.dart:27](../apps/client/lib/modules/meeting/views/tabs/overview_tab.dart) 按 `\n` 拆行渲染),改它会破坏已发布客户端的展示。 |
||||
|
|
||||
|
**铺开策略**:先只改 `zh-CN`(id=227)验证效果,确认后再翻译到其余 40 种语言。29 组模板 × 41 语言 = 1189 条,不要一上来就全铺。 |
||||
|
|
||||
|
### 7.2 抽取那一路(代码内置 prompt,不放模板) |
||||
|
|
||||
|
在 [`AIProcess`](../apps/services/modules/echomeet/tasks.go) 写完 `summary`/`overview`、状态置 `Completed` **之后**,起独立 goroutine: |
||||
|
|
||||
|
``` |
||||
|
输入:<meeting_date>2026-08-31</meeting_date> |
||||
|
<summary>…已生成的纪要…</summary> |
||||
|
prompt:代码内置(不放 echomeet_template) |
||||
|
输出:JSON 数组 → 逐条写 memory_item(category=todo, source=meeting, source_id=记录id) |
||||
|
``` |
||||
|
|
||||
|
**为什么 prompt 放代码不放模板**:① 模板有 1189 条,每条是对应语言的提示词,改不动也改不齐;② 各模板 `outline` 职责本就不同,塞进去会互相干扰;③ 输出要被程序 parse 成 JSON,格式严格性不能交给后台可随手编辑的文本;④ 抽取输入是已提炼的 `summary`(约千字),成本只是前两路(转写全文可达上万字)的零头,省不出什么。 |
||||
|
|
||||
|
**为什么必须注入 `<meeting_date>`**:现在 `AIProcess` 拼给模型的只有 `<remark>` + `<transcript>`,没有日期。模型不知道今天几号,让它把「下周三前」换算成具体日期就是瞎猜。 |
||||
|
|
||||
|
**服务端必须二次校验 `due_date`**:解析不出合法日期、或早于会议当天的,一律丢弃日期只保留 `due_raw`,`happen_date` 落会议当天并置 `date_certain=0`。参考 `SET_clock` 已踩过的坑——实测模型把「下午 3:30」的 time 填成 `03:30`(见 [disambiguateAfternoon](../apps/client/lib/data/models/assistant_directive.dart) 的注释)。 |
||||
|
|
||||
|
**失败隔离**:抽取失败只记日志,**不改 `rec.State`**。用户照样看得到纪要,只是没有自动待办。 |
||||
|
|
||||
|
### 7.3 重新总结的幂等 |
||||
|
|
||||
|
用户点「重新生成」时: |
||||
|
|
||||
|
``` |
||||
|
delete from memory_item |
||||
|
where source='meeting' and source_id=? and user_edited=0 and gen_round < 新轮次 |
||||
|
``` |
||||
|
|
||||
|
`user_edited=1` 的一律保留。 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 八、EMAI / 百炼多模态打通 |
||||
|
|
||||
|
百炼在两个层面参与,**职责必须分清**: |
||||
|
|
||||
|
### 8.1 端侧指令(`tool_calls`)——用户说,助手做 |
||||
|
|
||||
|
百炼在 `RespondingContent` 帧的 `payload.output.extra_info.tool_calls` 下发端侧函数调用,客户端 [`AssistantDirective.parseFromOutput`](../apps/client/lib/data/models/assistant_directive.dart) 解析后分发。 |
||||
|
|
||||
|
现有 `SET_clock`(参数 `time`/`date`/`content`/`repeat`)落地成闹钟。**本次要在百炼后台补三个工具**: |
||||
|
|
||||
|
| 工具 | 参数 | 落地 | |
||||
|
|---|---|---| |
||||
|
| `SET_clock`(已有) | time, date, content, repeat | `category=alarm` | |
||||
|
| `ADD_todo`(新增) | content, date, time, owner | `category=todo` | |
||||
|
| `ADD_idea`(新增) | content | `category=idea` | |
||||
|
| `ADD_expense`(新增) | content, amount, date | `category=expense` | |
||||
|
|
||||
|
已知的百炼侧问题(CLAUDE.md 已记录,本次一并解决): |
||||
|
|
||||
|
- **「取消闹钟」和「记一下待办」目前不下发任何指令**,模型却回「已经帮你取消了」——后台没配对应工具。这是需求 1「灵感/花销」能否语音录入的前提。 |
||||
|
- `SEND_message` / `MAKE_A_PHONE_CALL_phone_call` 本版已无对应能力,**要去百炼后台把这两个工具摘掉**,否则模型会继续下发无法执行的指令。 |
||||
|
|
||||
|
解析侧三个已知坑(照现有实现处理,勿改):`arguments` 是字符串形式 JSON 要二次 parse;参数值是中文自然语言(`repeat="周一"`)不是枚举;`finish_reason=command_calls` 是纯指令帧,text 为空且不会有 `RespondingStarted/Ended`,照 text 建气泡会留下永远填不上的空泡。 |
||||
|
|
||||
|
### 8.2 MCP 工具(服务端)——用户问,助手查 |
||||
|
|
||||
|
服务端 MCP 已经跑在 `/mcp`(streamable HTTP, stateless)与 `/sse`,带 `authFromRequest` 鉴权,工具分 `GLOBAL`/`CHINA`/`OVERSEAS` 三组。 |
||||
|
|
||||
|
**新增工具(`ToolGroup_GLOBAL`)**: |
||||
|
|
||||
|
| 工具 | 描述 | 回答什么问题 | |
||||
|
|---|---|---| |
||||
|
| `get_memory_items` | 按日期范围/分类查记忆项 | 「我明天有什么事」「上周记了哪些灵感」 | |
||||
|
| `get_memory_stats` | 统计(花销合计、完成率) | 「这个月花了多少钱」「上周有几件事没做完」 | |
||||
|
| `get_memory_report` | 取周报/月报 | 「上周总结说了什么」 | |
||||
|
| `add_memory_item` | 新增记忆项 | 「帮我记一下:明天下午three点开会」 | |
||||
|
| `complete_memory_item` | 标记完成 | 「把跟进 A 客户那件事标记完成」 | |
||||
|
|
||||
|
现有 `get_user_tasks` / `allhelp_task` / `cancel_user_task` 三个工具**改为读写 `memory_item`**(见 §9.1),保持工具名不变,避免百炼后台重新配置。 |
||||
|
|
||||
|
### 8.3 两条通路的分工 |
||||
|
|
||||
|
``` |
||||
|
┌──────────────────────────┐ |
||||
|
用户说「记一下…」 → │ 百炼 Agent │ |
||||
|
用户问「我这月花了…」 │ │ |
||||
|
└──┬──────────────────┬────┘ |
||||
|
│ tool_calls │ MCP 调用 |
||||
|
↓ (端侧执行) ↓ (服务端执行) |
||||
|
客户端 Directive 服务端 memory 模块 |
||||
|
│ │ |
||||
|
└────→ memory_item ←┘ |
||||
|
``` |
||||
|
|
||||
|
**记录走端侧、查询走 MCP** 是刻意的:端侧指令随对话即时下发、延迟低、离线也能先落本地;查询需要跨设备的全量数据,必须服务端出。 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 九、迁移与兼容 |
||||
|
|
||||
|
### 9.1 `DBTask` 的处置 |
||||
|
|
||||
|
| 阶段 | 动作 | |
||||
|
|---|---| |
||||
|
| 一期 | `memory_item` 上线;`DBTask` 停止新增写入;MCP 三个旧工具改读 `memory_item` | |
||||
|
| 二期 | 一次性脚本迁移存量 `DBTask` → `memory_item`(`task_type` 映射 `repeat_rule`,`status` 映射 `state`,`category` 一律 `todo`) | |
||||
|
| 三期 | `allhelp` 的 task 接口标记废弃,保留只读 | |
||||
|
|
||||
|
**不要直接删 `DBTask` 表**——EMAI 侧可能有历史会话引用,且迁移脚本需要回滚余地。 |
||||
|
|
||||
|
### 9.2 客户端老版本 |
||||
|
|
||||
|
老版本客户端不知道 `memory_*` 接口,继续用本地 `GetStorage`。**服务端不做任何兼容适配**——新功能只对新版本开放,老版本行为不变(本地闹钟照常)。 |
||||
|
|
||||
|
### 9.3 不向后兼容的点 |
||||
|
|
||||
|
- 记忆页改名后,i18n key 变更需 41 个语言文件同步,**漏一个语言就显示 key 名** |
||||
|
- `mockCalendarEvents()` 删除后,无数据用户看到的是空态页——需要设计空态文案与引导 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 十、闭环全景 |
||||
|
|
||||
|
``` |
||||
|
┌─────────── 录入 ───────────┐ |
||||
|
│ │ |
||||
|
会议录音 → echomeet 转写总结 EMAI 对话 → tool_calls |
||||
|
│ ↓ AIProcess 第三路抽取 │ ↓ AssistantDirective |
||||
|
│ (summary + 会议日期) │ (SET_clock/ADD_todo/…) |
||||
|
└──────────┬─────────────────┘ |
||||
|
↓ |
||||
|
┌──── memory_item ────┐ ← 手动录入(记忆页 +) |
||||
|
│ todo/alarm/idea/ │ |
||||
|
│ expense… │ |
||||
|
└──┬────────┬──────┬───┘ |
||||
|
│ │ │ |
||||
|
┌────┘ │ └────────────┐ |
||||
|
↓ ↓ ↓ |
||||
|
记忆页日历 提醒链路 周期复盘 |
||||
|
(分类展示) ├ 每日登录弹窗+播报 ├ 周一 00:30 cron |
||||
|
├ 提前5分钟本地通知 ├ 1号 00:30 cron |
||||
|
└ TTS 播报(可中断) ├ SQL 算 stat_json |
||||
|
├ LLM 组织播报文案 |
||||
|
└ 播报 → 用户确认 |
||||
|
↑ │ |
||||
|
└────── MCP 工具 ←── EMAI 提问 ─────┘ |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 十一、分期实施 |
||||
|
|
||||
|
### 一期:打通主链路(只读为主) |
||||
|
|
||||
|
| # | 任务 | 端 | |
||||
|
|---|---|---| |
||||
|
| 1 | `memory_item` 表 + `memory` 模块骨架 | 后端 | |
||||
|
| 2 | `memory_list` / `memory_today` / `memory_add` / `memory_update` / `memory_complete` | 后端 | |
||||
|
| 3 | 会议抽取第三路 + `<meeting_date>` 注入 + 日期校验 | 后端 | |
||||
|
| 4 | `General-Meeting` zh-CN 模板第四节改造 | 后台配置 | |
||||
|
| 5 | `MemoryService` + `MemoryItem` 模型 | 客户端 | |
||||
|
| 6 | 记忆页改名、分类筛选、`_rebuildEvents` 换源、删 mock | 客户端 | |
||||
|
| 7 | 助手 `SET_clock` 改写服务端 | 客户端 | |
||||
|
|
||||
|
**验收**:开一次会 → 纪要生成 → 待办自动出现在记忆页日历上,标注来源会议与负责人。 |
||||
|
|
||||
|
### 二期:提醒与播报 |
||||
|
|
||||
|
| # | 任务 | 端 | |
||||
|
|---|---|---| |
||||
|
| 8 | `memory_upcoming` + `remind_at` 计算 | 后端 | |
||||
|
| 9 | 本地通知排期(含权限降级) | 客户端 | |
||||
|
| 10 | 每日登录弹窗 + TTS 播报 + 中断 | 客户端 | |
||||
|
| 11 | 百炼后台补 `ADD_todo`/`ADD_idea`/`ADD_expense`,摘掉电话/短信工具 | 配置 | |
||||
|
| 12 | 花销分类完整支持(录入 + 统计) | 两端 | |
||||
|
|
||||
|
### 三期:复盘与问答 |
||||
|
|
||||
|
| # | 任务 | 端 | |
||||
|
|---|---|---| |
||||
|
| 13 | `memory_report` 表 + cron + 分布式锁 + 异步 worker | 后端 | |
||||
|
| 14 | `stat_json` SQL 统计 + LLM 播报文案生成 | 后端 | |
||||
|
| 15 | 报告弹窗 + 播报 + 确认 | 客户端 | |
||||
|
| 16 | 5 个新 MCP 工具 + 旧工具改读新表 | 后端 | |
||||
|
| 17 | `DBTask` 存量迁移脚本 | 后端 | |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 十二、风险与待确认 |
||||
|
|
||||
|
### 风险 |
||||
|
|
||||
|
| 级别 | 风险 | 应对 | |
||||
|
|---|---|---| |
||||
|
| 高 | **相对时间解析不准**(「下周三前」→ 具体日期) | 注入会议日期 + 服务端二次校验,宁可留空 | |
||||
|
| 高 | **重新总结冲掉用户修改** | `user_edited` + `gen_round` 双字段 | |
||||
|
| 高 | **cron 多副本重复生成报告** | Redis `SET NX EX` 分布式锁 | |
||||
|
| 中 | 本地通知权限被拒 / 平台限制 | 降级为打开 App 才提醒,不阻塞 | |
||||
|
| 中 | LLM 算错金额 | 统计全部走 SQL,LLM 只组织文案 | |
||||
|
| 中 | 多一路 LLM 调用增加成本 | 输入用 `summary` 而非全文;失败不重试 | |
||||
|
| 中 | 41 个语言文件改名遗漏 | 用 `tools/i18n_audit.py` 校验 | |
||||
|
| 低 | 统一表半数字段为空 | 数据量级小,可接受 | |
||||
|
|
||||
|
### 待确认 |
||||
|
|
||||
|
1. **花销录入方式**——纯语音(「打车花了 30」)?手动记账页?是否要拍照识别小票?当前设计只保证数据结构支持,录入 UI 未定。 |
||||
|
2. **灵感与「聊天总结」的关系**——`allhelp` 已有 `DBChatSummary`(每日聊天 AI 总结)。灵感是用户主动说「记一下」,聊天总结是自动提炼,两者要不要合并?建议**先分开**,灵感是显式的、聊天总结是隐式的。 |
||||
|
3. **报告确认的含义**——只是「我看过了」,还是要允许用户在确认时批量处理未完成事项(顺延/放弃)?影响接口设计。 |
||||
|
4. **多设备播报冲突**——同一账号在手机和耳机同时在线,谁播报?建议只在当前活跃前台设备播。 |
||||
|
5. **阿龙测试机 NATS 容器 unhealthy**(已 3 周)——配置变更广播依赖它,且 `stats_global_day` 数据停在 20260731。上线前需修复,否则后台改模板要等 10 分钟 cron 兜底。 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 附录 A:分类配色建议 |
||||
|
|
||||
|
| 分类 | 色值 | 说明 | |
||||
|
|---|---|---| |
||||
|
| `todo` 待办 | `#5B8DEF` 蓝 | | |
||||
|
| `alarm` 闹钟 | `#22C1A6` 青 | 沿用现有助手提醒色 | |
||||
|
| `idea` 灵感 | `#F59E0B` 橙 | | |
||||
|
| `expense` 花销 | `#EC4899` 粉 | | |
||||
|
|
||||
|
深色模式下需各自校验对比度,不要直接沿用。 |
||||
|
|
||||
|
## 附录 B:关键文件索引 |
||||
|
|
||||
|
| 用途 | 路径 | |
||||
|
|---|---| |
||||
|
| 会议纪要生成 | `apps/services/modules/echomeet/tasks.go` (`AIProcess`) | |
||||
|
| 会议模板读写 | `apps/services/modules/console/api_config.go` | |
||||
|
| 已有任务体系 | `apps/services/modules/allhelp/` | |
||||
|
| 异步总结范例 | `apps/services/modules/allhelp/summary.go` | |
||||
|
| MCP 工具 | `apps/services/modules/mcp/tool_*.go` | |
||||
|
| 助手指令解析 | `apps/client/lib/data/models/assistant_directive.dart` | |
||||
|
| 助手指令分发 | `apps/client/lib/data/services/assistant_directive_service.dart` | |
||||
|
| 日历控制器 | `apps/client/lib/modules/home/controllers/calendar_controller.dart` | |
||||
|
| 待办列表 | `apps/client/lib/modules/main_tab/views/widgets/todo_scope_list.dart` | |
||||
|
| TTS 接口 | `apps/client/lib/data/services/tts_service.dart` | |
||||
Loading…
Reference in new issue