|
|
@ -1,22 +1,40 @@ |
|
|
# 记忆中心(Memory Center)设计与开发文档 |
|
|
# 记忆中心(拾忆)设计与开发文档 |
|
|
|
|
|
|
|
|
> 版本:v0.1 草案 日期:2026-08-31 |
|
|
> 版本:v0.2 日期:2026-09-04(v0.1 为 2026-08-31) |
|
|
> 范围:`apps/services`(后端)、`apps/client`(Flutter)、`apps/admin`(后台)、百炼 Agent 配置 |
|
|
> 范围:`apps/services`(后端)、`apps/client`(Flutter)、`apps/admin`(后台)、百炼 Agent 配置 |
|
|
> 状态:设计评审中,未开工 |
|
|
> 状态:设计评审中,**未开工**(代码侧目前只有 tab 文案改名,见 §2.3) |
|
|
|
|
|
|
|
|
|
|
|
## v0.2 修订说明 |
|
|
|
|
|
|
|
|
|
|
|
v0.1 之后主仓有 4 次提交(翻译延迟优化、设备表合表、收拢积压改动、上传改预签名 URL), |
|
|
|
|
|
本次逐条复核了 v0.1 的「现状盘点」,**有 6 处已经不成立**,另发现 3 个既有缺陷会直接绊到本设计: |
|
|
|
|
|
|
|
|
|
|
|
| # | v0.1 的说法 | 实际 | 影响章节 | |
|
|
|
|
|
|---|---|---|---| |
|
|
|
|
|
| 1 | echomeet 按 `ServiceType` 四套固定服务商,总结用 `GetSummarizer(svcType, lang)` | 已改为**后台编排**:`PickLLM()` / `GetSummarizer(svcId)`,`ResolveServiceType` 已不存在 | §7.2 | |
|
|
|
|
|
| 2 | 会议总结只吃文本 | 已支持**多模态图片**(`imageurls`),小票识别不用另造轮子 | §12 待确认 1 | |
|
|
|
|
|
| 3 | 客户端只有花销 i18n 文案 | `CalendarEventCategory{todo,expense}` + 花销卡片 + 范围内合计**已经写好了** | §6.1 | |
|
|
|
|
|
| 4 | 客户端上传对象存储用腾讯 COS、密钥明文下发 | 已改**服务端签发预签名 URL**,`scene` 是白名单 | §6.4 | |
|
|
|
|
|
| 5 | `flutter_local_notifications` 只用于录音提示 | 属实,但已有**两个各自独立的 plugin 实例**,且**缺 `timezone` 依赖** | §5.1 | |
|
|
|
|
|
| 6 | 「代办」页要改名 | zh-CN/HK/TW 已改「拾忆/拾憶」,但**只有 11/41 个语言文件有 `tabTodo` 这个 key** | §2.3 | |
|
|
|
|
|
|
|
|
|
|
|
新增三条既有缺陷(详见 §2.4):**MCP 完全没有鉴权**、**账号注销漏表**、**i18n 改名没做完**。 |
|
|
|
|
|
|
|
|
--- |
|
|
--- |
|
|
|
|
|
|
|
|
## 一、背景与目标 |
|
|
## 一、背景与目标 |
|
|
|
|
|
|
|
|
App 现有的「代办」页(`TodoTab`)只是一个日历壳子:数据来自 12 条写死的演示数据加上 EMAI 助手落下来的闹钟,全部存在本地 `GetStorage`,服务端没有任何对应存储。 |
|
|
App 的第二个 tab 已由「代办」更名为「**拾忆**」(`tabTodo`),但页面内部还是原样:数据来自 12 条写死的 |
|
|
|
|
|
演示数据(`mockCalendarEvents()`)加上 EMAI 助手落下来的闹钟,全部存在本地 `GetStorage`, |
|
|
|
|
|
服务端没有任何对应存储。 |
|
|
|
|
|
|
|
|
本次要把它升级成**「记忆」页**——用户一天里发生的各类事情的统一入口,并围绕它形成「录入 → 提醒 → 复盘 → 问答」的闭环。 |
|
|
本次要把它做实——用户一天里发生的各类事情的统一入口,并围绕它形成「录入 → 提醒 → 复盘 → 问答」的闭环。 |
|
|
|
|
|
|
|
|
### 六项目标需求 |
|
|
### 六项目标需求 |
|
|
|
|
|
|
|
|
| # | 需求 | 关键约束 | |
|
|
| # | 需求 | 关键约束 | |
|
|
|---|---|---| |
|
|
|---|---|---| |
|
|
| 1 | 记忆页展示多类事项:闹钟、灵感记忆、花销、待办,**分类可扩展** | 一天多件事,按分类组织 | |
|
|
| 1 | 拾忆页展示多类事项:闹钟、灵感记忆、花销、待办,**分类可扩展** | 一天多件事,按分类组织 | |
|
|
| 2 | 每日首次登录,自动弹窗展示当天事项并**语音播报**,播报可随时关闭 | 播报可中断 | |
|
|
| 2 | 每日首次登录,自动弹窗展示当天事项并**语音播报**,播报可随时关闭 | 播报可中断 | |
|
|
| 3 | 待办/闹钟**提前 5 分钟提醒**,语音播报内容 | App 未打开时也要触发 | |
|
|
| 3 | 待办/闹钟**提前 5 分钟提醒**,语音播报内容 | App 未打开时也要触发 | |
|
|
| 4 | 每周一凌晨生成上周总结:未完成事项、花销合计、灵感汇总,播报后**需用户确认** | 服务端定时生成 | |
|
|
| 4 | 每周一凌晨生成上周总结:未完成事项、花销合计、灵感汇总,播报后**需用户确认** | 服务端定时生成 | |
|
|
@ -29,6 +47,8 @@ App 现有的「代办」页(`TodoTab`)只是一个日历壳子:数据来 |
|
|
2. **服务端为真相源**:本地只做缓存与离线兜底,换设备数据要在。 |
|
|
2. **服务端为真相源**:本地只做缓存与离线兜底,换设备数据要在。 |
|
|
3. **失败隔离**:AI 抽取、总结生成失败不影响主流程,宁可少一条记忆,不可让会议纪要或登录卡住。 |
|
|
3. **失败隔离**:AI 抽取、总结生成失败不影响主流程,宁可少一条记忆,不可让会议纪要或登录卡住。 |
|
|
4. **不重复造轮子**:`allhelp` 已有任务体系与 MCP 工具,在其上演进。 |
|
|
4. **不重复造轮子**:`allhelp` 已有任务体系与 MCP 工具,在其上演进。 |
|
|
|
|
|
5. **选路走编排,不写死服务商**(v0.2 新增):本模块所有 LLM 调用一律经 echomeet 的 `providers` 选路, |
|
|
|
|
|
不再出现第二处硬编码 `sys/doubao`。理由见 §7.2。 |
|
|
|
|
|
|
|
|
--- |
|
|
--- |
|
|
|
|
|
|
|
|
@ -40,27 +60,114 @@ App 现有的「代办」页(`TodoTab`)只是一个日历壳子:数据来 |
|
|
|---|---|---| |
|
|
|---|---|---| |
|
|
| **用户任务表 `DBTask`** | [allhelp_db.proto](../apps/proto/allhelp/allhelp_db.proto) | `task_name/task_desc/task_type/trigger_time/cron_expr/status/extra`,存 **MySQL 业务库** | |
|
|
| **用户任务表 `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` | 已上线 | |
|
|
| 任务增删改查接口 | `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 已能查/建/取消任务 | |
|
|
| **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 已能查/建/取消任务。⚠️ **无鉴权**,见 §2.4 | |
|
|
| **异步 AI 总结流水线** | [allhelp/summary.go](../apps/services/modules/allhelp/summary.go) | Redis 队列 + N worker + `doubao.Chat`,周报/月报可照搬 | |
|
|
| **异步 AI 流水线(两套范例)** | [allhelp/summary.go](../apps/services/modules/allhelp/summary.go)(Redis 队列 + N worker)<br>[echomeet/tasks.go](../apps/services/modules/echomeet/tasks.go)(Redis 队列 + ants 池 + 每秒 cron 调度) | 周报/月报照搬。**选路照 echomeet,不要照 allhelp**(后者硬编码 doubao) | |
|
|
| 每日聊天总结表 `DBChatSummary` | 同上 | `(uid, summary_date)` 唯一,覆盖重算 | |
|
|
| 每日聊天总结表 `DBChatSummary` | 同上 | `(uid, summary_date)` 唯一,覆盖重算 | |
|
|
|
|
|
| **LLM 选路(编排)** | [echomeet/providers.go](../apps/services/modules/echomeet/providers.go) `PickLLM` / `GetSummarizer(svcId)` | 后台配置驱动、按应用/区域分层、NATS 热重载。`memory` 与 `echomeet` **同在 home 进程**,可直接复用 | |
|
|
|
|
|
| **多模态图片入 LLM** | `AIProcess` 的 `ChatMessage.Images` → 各 Summarizer | 花销小票 OCR 可直接走这条 | |
|
|
| **TTS 播报** | [tts_service.dart](../apps/client/lib/data/services/tts_service.dart) | `startspeak/speakOnce/speakStream/flushStream/stop`,**已支持中断** | |
|
|
| **TTS 播报** | [tts_service.dart](../apps/client/lib/data/services/tts_service.dart) | `startspeak/speakOnce/speakStream/flushStream/stop`,**已支持中断** | |
|
|
| 本地通知插件 | `flutter_local_notifications ^18.0.1` | 目前只用于录音后台提示 | |
|
|
| 本地通知插件 | `flutter_local_notifications ^18.0.1` | 目前只用于录音后台提示。⚠️ 见 §5.1 的三个坑 | |
|
|
|
|
|
| 通知相关 Android 权限 | `AndroidManifest.xml` | `POST_NOTIFICATIONS` / `SCHEDULE_EXACT_ALARM` / `RECEIVE_BOOT_COMPLETED` **已声明**,不用再加 | |
|
|
| 助手端侧指令 | [assistant_directive_service.dart](../apps/client/lib/data/services/assistant_directive_service.dart) | `SET_clock` 已落地成提醒 | |
|
|
| 助手端侧指令 | [assistant_directive_service.dart](../apps/client/lib/data/services/assistant_directive_service.dart) | `SET_clock` 已落地成提醒 | |
|
|
|
|
|
| **日历分类与花销卡片** | [calendar_event.dart](../apps/client/lib/modules/home/views/widgets/calendar/calendar_event.dart)、[todo_scope_list.dart](../apps/client/lib/modules/main_tab/views/widgets/todo_scope_list.dart) | `CalendarEventCategory{todo,expense}`、`amount` 字段、`_expenseCard`、`expensesForScope` 合计条**都已存在** | |
|
|
| 会议纪要生成 | [echomeet/tasks.go](../apps/services/modules/echomeet/tasks.go) `AIProcess` | `summary` 已含「四、待办事项」章节 | |
|
|
| 会议纪要生成 | [echomeet/tasks.go](../apps/services/modules/echomeet/tasks.go) `AIProcess` | `summary` 已含「四、待办事项」章节 | |
|
|
|
|
|
| **服务端签发上传 URL** | [api_getuploadurl.go](../apps/services/modules/user/api_getuploadurl.go) | 客户端不持密钥;`scene` 白名单,加新场景要改这里 | |
|
|
|
|
|
|
|
|
### 2.2 缺口 |
|
|
### 2.2 缺口 |
|
|
|
|
|
|
|
|
| 缺口 | 影响需求 | |
|
|
| 缺口 | 影响需求 | |
|
|
|---|---| |
|
|
|---|---| |
|
|
| `DBTask` **「服务端仅做记录,不自动触发」**(proto 注释原文) | 3/4/5 | |
|
|
| `DBTask` **「服务端仅做记录,不自动触发」**(proto 注释原文) | 3/4/5 | |
|
|
| 没有分类概念,无法承载灵感/花销 | 1 | |
|
|
| 没有分类概念,无法承载灵感/花销(客户端只有 todo/expense 两类,且只是 mock) | 1 | |
|
|
| **花销功能后端完全空白**,客户端只有 i18n 文案和 mock | 1/4/5 | |
|
|
| **花销后端完全空白**,客户端的花销卡片喂的是 mock 数据 | 1/4/5 | |
|
|
| 没有周报/月报表与生成任务 | 4/5 | |
|
|
| 没有周报/月报表与生成任务 | 4/5 | |
|
|
| 客户端待办数据不落服务端(`synced` 恒为 false) | 全部 | |
|
|
| 客户端待办数据不落服务端(`synced` 恒为 false) | 全部 | |
|
|
| 会议纪要里的待办是自然语言,无结构化字段 | 1 | |
|
|
| 会议纪要里的待办是自然语言,无结构化字段 | 1 | |
|
|
| 本地通知从未用于事项提醒 | 3 | |
|
|
| 本地通知从未用于事项提醒;**`timezone` 依赖未引入** | 3 | |
|
|
|
|
|
| MCP 工具无鉴权,uid 由模型自报 | 6 | |
|
|
|
|
|
|
|
|
|
|
|
### 2.3 「拾忆」改名的真实进度(v0.2 复核) |
|
|
|
|
|
|
|
|
|
|
|
改名只动了一个 i18n key,且**没改全**: |
|
|
|
|
|
|
|
|
|
|
|
| 语言 | `tabTodo` 取值 | |
|
|
|
|
|
|---|---| |
|
|
|
|
|
| zh_cn | `拾忆` | |
|
|
|
|
|
| zh_hk / zh_tw | `拾憶` | |
|
|
|
|
|
| de / en / es / fr / ja / ko / pt / ru | `Remin` | |
|
|
|
|
|
| 其余 **30 个语言文件** | **该 key 不存在** | |
|
|
|
|
|
|
|
|
|
|
|
- ⚠️ **`'Remin'` 不是任何语言里的词**,看着像 `Reminisce` 打了一半。8 个语言的 tab 上现在就顶着它。 |
|
|
|
|
|
- ⚠️ 缺 key 的 30 个语言不会显示 `tabTodo` 字面量——GetX 会回退到 `fallbackLocale`(en_US), |
|
|
|
|
|
于是**这 30 个语言也全都显示 `Remin`**。也就是说 41 个语言里有 38 个是坏的,只是不报错。 |
|
|
|
|
|
- 这条与本设计无关也得修,但既然拾忆页要动,顺手一起做。 |
|
|
|
|
|
- ⚠️ 批量补 key 时**单双引号两种风格都要查**,本仓的语言文件两种都有,只按单引号查会漏, |
|
|
|
|
|
漏了会在 const map 里出现重复键,`flutter build` 阶段才炸。 |
|
|
|
|
|
|
|
|
|
|
|
⚠️ **`tools/i18n_audit.py` 抓不到这个问题,别指望它兜底**(v0.2 实测)。它的循环是 |
|
|
|
|
|
`for k, v in kv.items()` —— 遍历的是**该语言文件已有的 key**,且对 `baseline.get(k) is None` |
|
|
|
|
|
直接 `continue`。**结构上就不报告缺失的 key**。而对那 8 个值为 `Remin` 的语言, |
|
|
|
|
|
`_looks_english_sentence("Remin")` 也返回 False(它靠一张英文常用词表判断),同样不报。 |
|
|
|
|
|
**这个工具对本问题双重失明**——这正是 30 个语言悄悄丢掉 `tabTodo` 而没人发现的原因。 |
|
|
|
|
|
|
|
|
|
|
|
⚠️ **而且这不是孤例,是系统性的**(v0.2 实测):以 zh_cn 的 1748 个 key 为基准, |
|
|
|
|
|
**40 个语言文件里只有 1 个不缺 key**,其余最少缺 233 个、最多缺 388 个(nl_nl), |
|
|
|
|
|
即 13%~22% 的文案在多数语言里是缺的、全部静默回退英文。 |
|
|
|
|
|
审计工具当前还报着 **514 处**「值等于英文基线」的未翻译项——新增信号会直接淹没在里面。 |
|
|
|
|
|
|
|
|
|
|
|
**结论:拾忆要新增的 30~50 个 key 会掉进一条已经在漏的管道。** |
|
|
|
|
|
要么先给 `i18n_audit.py` 加一个「缺失 key」检查并接进 CI,要么接受新 key 同样会漏掉大半。 |
|
|
|
|
|
|
|
|
|
|
|
### 2.4 v0.2 新发现:三个会绊到本设计的既有缺陷 |
|
|
|
|
|
|
|
|
|
|
|
#### (1) MCP 完全没有鉴权,uid 由模型当参数传 —— 高危 |
|
|
|
|
|
|
|
|
|
|
|
[module.go](../apps/services/modules/mcp/module.go) 的 `authFromRequest` 只是把 |
|
|
|
|
|
`Authorization` 头塞进 context: |
|
|
|
|
|
|
|
|
|
|
|
```go |
|
|
|
|
|
func authFromRequest(ctx context.Context, r *http.Request) context.Context { |
|
|
|
|
|
return withAuthKey(ctx, r.Header.Get("Authorization")) |
|
|
|
|
|
} |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
**全仓没有任何地方读回这个 key,也没有任何 JWT 校验**。三个用户工具 |
|
|
|
|
|
(`get_user_tasks` / `allhelp_task` / `cancel_user_task`)的 `uid` 都是 |
|
|
|
|
|
`mcp.WithString("uid", mcp.Required())`——由大模型填进来的普通参数。 |
|
|
|
|
|
|
|
|
|
|
|
现状下的后果已经不小(能读改任意人的提醒),但记忆项装的是**花销金额、灵感原文、 |
|
|
|
|
|
每日行程**,一旦按 §8.2 把 5 个新工具挂上去,等于开放了一个「给 uid 就返回这个人 |
|
|
|
|
|
生活流水」的公开接口。 |
|
|
|
|
|
|
|
|
|
|
|
**决策:§8.2 的新工具落地前,MCP 必须先补 uid 归属校验**——从 `Authorization` 解出会话 uid, |
|
|
|
|
|
**忽略**参数里的 uid(或校验二者一致,不一致直接拒)。这是三期的**前置任务**,不是可选优化。 |
|
|
|
|
|
|
|
|
|
|
|
#### (2) 账号注销 `cancel()` 是逐表硬编码的,新表必须登记 |
|
|
|
|
|
|
|
|
|
|
|
[model_user.go](../apps/services/modules/user/model_user.go) 的 `cancel()` 手写了要清哪几张表: |
|
|
|
|
|
|
|
|
### 2.3 存储分工(迁移讨论已确认) |
|
|
```go |
|
|
|
|
|
mysql.Delete(comm.TableUserdevice, "uid=?", uid) |
|
|
|
|
|
mysql.Delete(comm.TableUserStatistics, "uid=?", uid) |
|
|
|
|
|
mysql.Delete(comm.TableUserUseLog, "uid=?", uid) |
|
|
|
|
|
mysql.Delete(comm.TableAllhelpTask, "uid=?", uid) |
|
|
|
|
|
mysql.Delete(comm.TableChatSummary, "uid=?", uid) |
|
|
|
|
|
mysql.Delete(comm.TableEchomeetRecord, "uid=?", uid) |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
`memory_item` / `memory_report` **必须加进这个列表**,否则用户注销后花销金额、 |
|
|
|
|
|
灵感原文、全部行程仍留在库里。这里没有任何机制会提醒你漏了表——**漏了不报错**。 |
|
|
|
|
|
|
|
|
|
|
|
⚠️ 顺带:`TableUserIdVerify`(实名认证记录,存身份证掩码 + 加盐指纹)**当前也不在清单里**, |
|
|
|
|
|
是既有遗漏,建议本次一并补上。 |
|
|
|
|
|
|
|
|
|
|
|
#### (3) 删 mock 会留下约 40 个死 i18n key |
|
|
|
|
|
|
|
|
|
|
|
`mockCalendarEvents()` 引用了 `demoEvt1..12` / `demoDesc1..12` / `demoLoc1..12` / `demoTag*` |
|
|
|
|
|
共约 40 个 key × 41 个语言文件。§6.2 删 mock 之后这些 key 全部无人引用, |
|
|
|
|
|
要跟着一起删干净,别留下 1600 行无主文案。 |
|
|
|
|
|
|
|
|
|
|
|
### 2.5 存储分工(迁移讨论已确认) |
|
|
|
|
|
|
|
|
| 数据 | 库 | |
|
|
| 数据 | 库 | |
|
|
|---|---| |
|
|
|---|---| |
|
|
@ -82,13 +189,33 @@ App 现有的「代办」页(`TodoTab`)只是一个日历壳子:数据来 |
|
|
|
|
|
|
|
|
### 3.2 表结构 `memory_item`(MySQL 业务库) |
|
|
### 3.2 表结构 `memory_item`(MySQL 业务库) |
|
|
|
|
|
|
|
|
|
|
|
⚠️ **下面的 DDL 只是说明用,本项目不手写建表语句**(v0.2 补)。本仓所有业务表都是 |
|
|
|
|
|
`mysql.CreateTable(表名, &pb.DBXxx{})` → gorm `AutoMigrate`,**schema 完全由 `.proto` 的字段类型 |
|
|
|
|
|
加 `@go_tags(gorm:...)` 决定**(见 `user/model_user.go`、`allhelp/model.go`)。 |
|
|
|
|
|
所以真正要写的是 `apps/proto/memory/memory_db.proto`,而 protobuf 没有 DECIMAL / DATE / |
|
|
|
|
|
DATETIME / 可空 这几种类型,逐个要落地成: |
|
|
|
|
|
|
|
|
|
|
|
| DDL 里想要的 | proto 里实际写法 | |
|
|
|
|
|
|---|---| |
|
|
|
|
|
| `amount DECIMAL(12,2)` | `int64 amount_cents`(**分**)——见下 | |
|
|
|
|
|
| `happen_date DATE` | `string happen_date` + `gorm:"type:date;index"`,值形如 `2026-09-04` | |
|
|
|
|
|
| `next_remind_at DATETIME NULL` | `int64 next_remind_at`(unix 秒,**0 表示无**,proto3 没有 null) | |
|
|
|
|
|
| `UNIQUE KEY (uid, client_key)` | `gorm:"uniqueIndex:idx_mem_uid_ck,priority:N"`,照 `DBChatSummary` 的写法 | |
|
|
|
|
|
|
|
|
|
|
|
⚠️ **金额一律用 `int64` 存「分」,不要 DECIMAL 也不要 double**(v0.2 修正)。 |
|
|
|
|
|
v0.1 写 `DECIMAL(12,2)` 是对的方向(怕浮点误差)但**与全项目口径冲突**: |
|
|
|
|
|
`pay_db.proto` 的 `amount`、`db.proto` 的 `base_amount`/`channel_amount`/`platform_amount` |
|
|
|
|
|
**全是 `int64` 分**。多一种金额表示法,跨模块对账时必然有人换算漏乘 100。 |
|
|
|
|
|
(`db.proto` 里 `DBProduct.price` 是 `double`,那是历史遗留,不要照抄。) |
|
|
|
|
|
|
|
|
```sql |
|
|
```sql |
|
|
CREATE TABLE memory_item ( |
|
|
CREATE TABLE memory_item ( |
|
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, |
|
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, |
|
|
uid VARCHAR(50) NOT NULL, |
|
|
uid VARCHAR(50) NOT NULL, |
|
|
category VARCHAR(16) NOT NULL, -- alarm/todo/idea/expense/…(可扩展) |
|
|
category VARCHAR(16) NOT NULL, -- alarm/todo/idea/expense/…(可扩展) |
|
|
source VARCHAR(16) NOT NULL, -- meeting/assistant/manual/device |
|
|
source VARCHAR(16) NOT NULL, -- meeting/assistant/manual/device |
|
|
source_id VARCHAR(64) DEFAULT '', -- 会议记录 id、指令 id 等 |
|
|
source_id VARCHAR(64) DEFAULT '', -- 会议记录 id、端侧指令 id 等 |
|
|
|
|
|
client_key VARCHAR(64) DEFAULT '', -- 幂等键(见 3.3) |
|
|
|
|
|
|
|
|
title VARCHAR(200) NOT NULL, -- 主文案(所有分类通用) |
|
|
title VARCHAR(200) NOT NULL, -- 主文案(所有分类通用) |
|
|
detail TEXT, -- 补充说明 / 灵感正文 |
|
|
detail TEXT, -- 补充说明 / 灵感正文 |
|
|
@ -97,18 +224,19 @@ CREATE TABLE memory_item ( |
|
|
happen_time VARCHAR(5) DEFAULT '', -- HH:mm,无具体时间则空 |
|
|
happen_time VARCHAR(5) DEFAULT '', -- HH:mm,无具体时间则空 |
|
|
date_certain TINYINT DEFAULT 1, -- 0=日期是推断的(如落在会议当天) |
|
|
date_certain TINYINT DEFAULT 1, -- 0=日期是推断的(如落在会议当天) |
|
|
due_raw VARCHAR(64) DEFAULT '', -- 截止时间原文(「下周三前」) |
|
|
due_raw VARCHAR(64) DEFAULT '', -- 截止时间原文(「下周三前」) |
|
|
|
|
|
tz VARCHAR(48) DEFAULT '', -- 录入时的客户端时区(IANA 名) |
|
|
|
|
|
|
|
|
repeat_rule VARCHAR(16) DEFAULT '', -- once/daily/weekly/weekdays/weekend/monthly |
|
|
repeat_rule VARCHAR(16) DEFAULT '', -- once/daily/weekly/weekdays/weekend/monthly |
|
|
repeat_raw VARCHAR(64) DEFAULT '', -- 重复规则原文 |
|
|
repeat_raw VARCHAR(64) DEFAULT '', -- 重复规则原文 |
|
|
weekday TINYINT DEFAULT 0, -- weekly 时 1-7 |
|
|
weekday TINYINT DEFAULT 0, -- weekly 时 1-7 |
|
|
|
|
|
|
|
|
amount DECIMAL(12,2) DEFAULT 0, -- 花销金额(category=expense) |
|
|
amount_cents BIGINT DEFAULT 0, -- 花销金额(分,category=expense) |
|
|
currency VARCHAR(8) DEFAULT 'CNY', |
|
|
currency VARCHAR(8) DEFAULT 'CNY', -- ⚠️ 多币种问题见 3.3 |
|
|
owner VARCHAR(64) DEFAULT '', -- 负责人原文(会议待办) |
|
|
owner VARCHAR(64) DEFAULT '', -- 负责人原文(会议待办) |
|
|
|
|
|
|
|
|
state TINYINT DEFAULT 0, -- 0待办 1完成 2废弃 |
|
|
state TINYINT DEFAULT 0, -- 0待办 1完成 2废弃 |
|
|
remind_ahead INT DEFAULT 5, -- 提前提醒分钟数,0=不提醒 |
|
|
remind_ahead INT DEFAULT 5, -- 提前提醒分钟数,0=不提醒 |
|
|
remind_at DATETIME NULL, -- 计算好的提醒时刻(服务端算,客户端直接用) |
|
|
next_remind_at DATETIME NULL, -- 下一次提醒时刻(重复项由服务端滚动推进,见 3.3) |
|
|
|
|
|
|
|
|
user_edited TINYINT DEFAULT 0, -- 用户改过 → 重新生成时不覆盖 |
|
|
user_edited TINYINT DEFAULT 0, -- 用户改过 → 重新生成时不覆盖 |
|
|
gen_round INT DEFAULT 0, -- 第几轮 AI 生成 |
|
|
gen_round INT DEFAULT 0, -- 第几轮 AI 生成 |
|
|
@ -118,19 +246,45 @@ CREATE TABLE memory_item ( |
|
|
|
|
|
|
|
|
INDEX idx_uid_date (uid, happen_date), |
|
|
INDEX idx_uid_date (uid, happen_date), |
|
|
INDEX idx_uid_cat (uid, category, happen_date), |
|
|
INDEX idx_uid_cat (uid, category, happen_date), |
|
|
INDEX idx_remind (remind_at, state) |
|
|
INDEX idx_remind (next_remind_at, state), |
|
|
|
|
|
UNIQUE KEY uk_uid_clientkey (uid, client_key) -- client_key 非空时生效,见 3.3 |
|
|
); |
|
|
); |
|
|
``` |
|
|
``` |
|
|
|
|
|
|
|
|
### 3.3 字段设计要点 |
|
|
### 3.3 字段设计要点 |
|
|
|
|
|
|
|
|
**`happen_date` 永不为空**——这是记忆页能工作的前提。日历按日期排,没有日期的项根本渲染不出来。会议待办抽不出截止日期时,落在**会议当天**并置 `date_certain=0`,卡片上用 `due_raw` 标注「下周三前」,用户可改期。 |
|
|
**`happen_date` 永不为空**——这是拾忆页能工作的前提。日历按日期排,没有日期的项根本渲染不出来。会议待办抽不出截止日期时,落在**会议当天**并置 `date_certain=0`,卡片上用 `due_raw` 标注「下周三前」,用户可改期。 |
|
|
|
|
|
|
|
|
**`category` 是字符串不是枚举**——新增分类(如 `health`、`travel`)只加常量、不改表、不迁移。代价是无法在 DB 层约束取值,靠服务端白名单校验。 |
|
|
**`category` 是字符串不是枚举**——新增分类(如 `health`、`travel`)只加常量、不改表、不迁移。代价是无法在 DB 层约束取值,靠服务端白名单校验。 |
|
|
|
|
|
⚠️ 相应地,**客户端必须对不认识的 `category` 有兜底**:按 `todo` 渲染并用中性配色, |
|
|
**`amount` 用 DECIMAL 不用 float**——月度金额聚合,浮点误差不可接受。 |
|
|
绝不能 `switch` 不到就抛。否则服务端一加分类,所有存量客户端的拾忆页整页崩。 |
|
|
|
|
|
|
|
|
**`remind_at` 由服务端算好**——重复规则展开、时区换算、提前量扣减都在服务端做一次,客户端拿到就是一个绝对时刻,不重复实现容易出错的日期逻辑。 |
|
|
**`amount_cents` 用 int64 存分**——月度金额聚合,浮点误差不可接受;用分而不用 DECIMAL 是为了 |
|
|
|
|
|
和 `pay` / 结算模块同口径(§3.2)。客户端展示时除以 100。 |
|
|
|
|
|
|
|
|
|
|
|
**多币种是没解决的问题(v0.2 提出)**——`currency` 字段存了,但 `stat_json` 的 `expense_total` |
|
|
|
|
|
是**单个数字**,把 CNY 和 JPY 直接相加是错的。App 是 41 语言全球发行,用户出国消费很正常。 |
|
|
|
|
|
另外客户端现在的花销卡片是**硬编码 `¥`**([todo_scope_list.dart:291](../apps/client/lib/modules/main_tab/views/widgets/todo_scope_list.dart#L291) |
|
|
|
|
|
的 `'¥${...}'`),`currency` 存了也没地方显示。 |
|
|
|
|
|
一期的可行取舍:**只支持单一货币**(跟随用户所在地区,存在 `currency` 里但不做换算), |
|
|
|
|
|
月报里若出现多种货币就**分币种各列一行**,绝不合并成一个数——不做汇率换算,也别假装能换算。 |
|
|
|
|
|
|
|
|
|
|
|
**`client_key` 是幂等键(v0.2 新增)**——端侧指令会重试,`memory_add` 没有幂等保护就会插重。 |
|
|
|
|
|
客户端用「端侧指令 id」或本地生成的 uuid 填这一列,服务端建 `(uid, client_key)` 唯一索引, |
|
|
|
|
|
重复提交返回已存在那条而不是报错。`AssistantDirectiveService` 现在的按指令 id 去重**只在本进程内存里**, |
|
|
|
|
|
换手机、重装、进程重启后全部失效——不能当服务端幂等用。 |
|
|
|
|
|
|
|
|
|
|
|
**重复项的提醒推进(v0.2 修正)**——v0.1 写的是「`remind_at` 服务端算好、客户端直接用」。 |
|
|
|
|
|
这对一次性项成立,对**重复项不成立**:一条 `repeat_rule=daily` 的闹钟只有一行记录, |
|
|
|
|
|
一个 `remind_at` 字段只能表示一个时刻,谁在它过去之后把它推到明天? |
|
|
|
|
|
本版改为: |
|
|
|
|
|
- 字段改名 `next_remind_at`,语义是「下一次」; |
|
|
|
|
|
- **`memory_upcoming` 在返回时按 `repeat_rule` 就地展开成未来 N 天的具体时刻列表**(不落库), |
|
|
|
|
|
客户端拿到的是一串绝对时刻,直接排通知; |
|
|
|
|
|
- `next_remind_at` 只用于服务端侧的索引与将来可能的推送,不作为客户端唯一依据。 |
|
|
|
|
|
|
|
|
|
|
|
**`tz` 记录录入时区**——跨时区旅行时「今天」跟着人走(§4.1),但一条已经录好的 |
|
|
|
|
|
「明早 8 点」不该因为落地伦敦就变成 8 点伦敦时间。存下录入时区,展开提醒时按它算。 |
|
|
|
|
|
|
|
|
**`user_edited` + `gen_round`**——会议重新总结时,删掉上一轮 `user_edited=0` 的项,保留用户勾过改过的。不做这个,用户点一次「重新生成」就会丢掉自己的修改。 |
|
|
**`user_edited` + `gen_round`**——会议重新总结时,删掉上一轮 `user_edited=0` 的项,保留用户勾过改过的。不做这个,用户点一次「重新生成」就会丢掉自己的修改。 |
|
|
|
|
|
|
|
|
@ -157,6 +311,7 @@ CREATE TABLE memory_report ( |
|
|
|
|
|
|
|
|
stat_json TEXT, -- 结构化统计(见下) |
|
|
stat_json TEXT, -- 结构化统计(见下) |
|
|
summary TEXT, -- AI 生成的播报文案 |
|
|
summary TEXT, -- AI 生成的播报文案 |
|
|
|
|
|
llm_svc_id VARCHAR(64), -- 实际用了哪个总结服务(排障用,同 echomeet 的口径) |
|
|
state TINYINT DEFAULT 0, -- 0待生成 1生成中 2完成 3失败 |
|
|
state TINYINT DEFAULT 0, -- 0待生成 1生成中 2完成 3失败 |
|
|
error_msg VARCHAR(500), |
|
|
error_msg VARCHAR(500), |
|
|
|
|
|
|
|
|
@ -174,32 +329,58 @@ CREATE TABLE memory_report ( |
|
|
{ |
|
|
{ |
|
|
"todo_total": 12, "todo_done": 8, "todo_undone": 4, |
|
|
"todo_total": 12, "todo_done": 8, "todo_undone": 4, |
|
|
"undone_items": [{"id":123,"title":"跟进 A 客户","happen_date":"2026-08-26"}], |
|
|
"undone_items": [{"id":123,"title":"跟进 A 客户","happen_date":"2026-08-26"}], |
|
|
|
|
|
"undone_truncated": false, |
|
|
"expense_total": 1280.50, "expense_count": 15, |
|
|
"expense_total": 1280.50, "expense_count": 15, |
|
|
"expense_by_day": {"2026-08-25": 120.00}, |
|
|
"expense_by_day": {"2026-08-25": 120.00}, |
|
|
"idea_count": 3, |
|
|
"idea_count": 3, |
|
|
"idea_items": [{"id":456,"title":"做一个会议纪要转待办的功能"}], |
|
|
"idea_items": [{"id":456,"title":"做一个会议纪要转待办的功能"}], |
|
|
|
|
|
"idea_truncated": false, |
|
|
"alarm_total": 7 |
|
|
"alarm_total": 7 |
|
|
} |
|
|
} |
|
|
``` |
|
|
``` |
|
|
|
|
|
|
|
|
**金额与完成数绝不能让 LLM 算**——它只负责把 `stat_json` 组织成一段自然、适合朗读的话。数字算错比话说得干巴严重得多。 |
|
|
**金额与完成数绝不能让 LLM 算**——它只负责把 `stat_json` 组织成一段自然、适合朗读的话。数字算错比话说得干巴严重得多。 |
|
|
|
|
|
|
|
|
|
|
|
⚠️ **`undone_items` / `idea_items` 必须截断**(建议各 20 条,超出置 `*_truncated=true`)。 |
|
|
|
|
|
一个重度用户一周攒 200 条未完成,全塞进 prompt 会顶爆上下文、拖慢生成、并且**播报出来 |
|
|
|
|
|
没人听得完**。总数用 `todo_undone` 表达,明细只举前几条。 |
|
|
|
|
|
|
|
|
--- |
|
|
--- |
|
|
|
|
|
|
|
|
## 四、服务端接口 |
|
|
## 四、服务端接口 |
|
|
|
|
|
|
|
|
新建 `memory` 模块(`apps/services/modules/memory/`),由 `home` 服务装载。按反射注册约定,方法签名满足 `func (c *apiComp) X(session comm.IUserSession, req *pb.XReq) (*pb.XResp, *pb.ErrorData)` 即自动注册为 `memory_x`。 |
|
|
新建 `memory` 模块(`apps/services/modules/memory/`),由 `home` 服务装载(`home/main.go` 的 `lego.Run` 里加一行)。 |
|
|
|
|
|
按反射注册约定,方法签名满足 `func (c *apiComp) X(session comm.IUserSession, req *pb.XReq) (*pb.XResp, *pb.ErrorData)` |
|
|
|
|
|
即自动注册为 `memory_x`,客户端路径 `/api/home/memory_x`。接口都要登录,因此不进网关白名单。 |
|
|
|
|
|
|
|
|
|
|
|
⚠️ **模块 `Init` 绝不能返回 error(v0.2 补,这条最容易一次搞挂全站)**: |
|
|
|
|
|
`lego.Run` 里 `service.Init` 一旦返回非 nil 就 `log.Panicf("服务初始化失败")` → home 进程退出; |
|
|
|
|
|
而 gateway/home/api/mcp/timer **五个服务打在同一个镜像里由 `entrypoint.voitrans.sh` 管**, |
|
|
|
|
|
脚本发现任一 pid 退出就 `kill -TERM` 掉其余全部并 `exit 1`("FATAL: pid $pid exited, shutting down container")。 |
|
|
|
|
|
**也就是说 `memory` 模块建表失败会让整站 502。** |
|
|
|
|
|
`CreateTable` 失败必须只 `Errorln` 不外传。 |
|
|
|
|
|
|
|
|
|
|
|
⚠️ 照抄时注意 `allhelp/model.go` 那段是**有 bug 的反例**: |
|
|
|
|
|
|
|
|
|
|
|
```go |
|
|
|
|
|
if err = mysql.CreateTable(A, ...); err != nil { this.module.Errorln(err) } |
|
|
|
|
|
if err = mysql.CreateTable(B, ...); err != nil { this.module.Errorln(err) } |
|
|
|
|
|
return // ← 命名返回值 err 仍然是最后一次的结果 |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
它的本意是「记日志、继续」,实际是:A 失败 B 成功 → 错误被静默吞掉;B 失败 → err 漏出去 → 全站挂。 |
|
|
|
|
|
正确写法是用局部变量接住,`return nil`。 |
|
|
|
|
|
|
|
|
| 接口 | 用途 | 关键参数 | |
|
|
| 接口 | 用途 | 关键参数 | |
|
|
|---|---|---| |
|
|
|---|---|---| |
|
|
| `memory_list` | 拉取记忆项 | `start_date`/`end_date`/`categories[]`/`states[]` | |
|
|
| `memory_list` | 拉取记忆项 | `start_date`/`end_date`/`categories[]`/`states[]` | |
|
|
| `memory_today` | 当天全部事项(需求 2 弹窗直接用) | `date`(客户端本地日期) | |
|
|
| `memory_today` | 当天全部事项(需求 2 弹窗直接用) | `date`(客户端本地日期)+ `tz` | |
|
|
| `memory_add` | 新增 | 整个 item | |
|
|
| `memory_add` | 新增 | 整个 item(含 `client_key` 幂等键) | |
|
|
| `memory_update` | 修改(自动置 `user_edited=1`) | id + 变更字段 | |
|
|
| `memory_update` | 修改(自动置 `user_edited=1`) | id + 变更字段 | |
|
|
| `memory_del` | 删除 | `ids[]` | |
|
|
| `memory_del` | 删除 | `ids[]` | |
|
|
| `memory_complete` | 标记完成 | `ids[]` | |
|
|
| `memory_complete` | 标记完成 | `ids[]` | |
|
|
| `memory_upcoming` | 拉未来 N 天待提醒项(客户端排本地通知用) | `days`(默认 7) | |
|
|
| `memory_upcoming` | 拉未来 N 天待提醒项,**重复项已展开成绝对时刻** | `days`(默认 7)+ `tz` | |
|
|
| `memory_getreport` | 取周报/月报 | `period_type`/`period_key` | |
|
|
| `memory_getreport` | 取周报/月报 | `period_type`/`period_key`;不传则取最近一份未确认的 | |
|
|
| `memory_confirmreport` | 用户确认报告(需求 4) | `report_id` | |
|
|
| `memory_confirmreport` | 用户确认报告(需求 4) | `report_id` | |
|
|
| `memory_stats` | 即时统计(EMAI 问答与页面头部用) | 时间范围 + 分类 | |
|
|
| `memory_stats` | 即时统计(EMAI 问答与页面头部用) | 时间范围 + 分类 | |
|
|
|
|
|
|
|
|
@ -207,7 +388,15 @@ CREATE TABLE memory_report ( |
|
|
|
|
|
|
|
|
### 4.1 时区 |
|
|
### 4.1 时区 |
|
|
|
|
|
|
|
|
`happen_date` 是**用户本地日期**,不是 UTC 日期。客户端请求时带上 `tz`(如 `Asia/Shanghai`)与本地日期字符串,服务端不做时区推断。跨时区旅行时以客户端当前时区为准——这是刻意选择:用户"今天"的定义应该跟着人走。 |
|
|
`happen_date` 是**用户本地日期**,不是 UTC 日期。客户端请求时带上 `tz`(IANA 名如 `Asia/Shanghai`) |
|
|
|
|
|
与本地日期字符串,服务端不做时区推断。跨时区旅行时以客户端当前时区为准——这是刻意选择: |
|
|
|
|
|
用户「今天」的定义应该跟着人走。 |
|
|
|
|
|
|
|
|
|
|
|
⚠️ 但**单条记忆项的提醒时刻按它自己的 `tz` 算**(§3.3),不跟着人走。 |
|
|
|
|
|
两个口径不同不是矛盾:「今天有哪些事」是视图问题,「明早 8 点响」是承诺问题。 |
|
|
|
|
|
|
|
|
|
|
|
⚠️ 服务端容器需能解析 IANA 时区。`home` 的 `main.go` 已 `import _ "time/tzdata"`(为 analyze 统计引入), |
|
|
|
|
|
本模块直接受益,不用另外处理。 |
|
|
|
|
|
|
|
|
--- |
|
|
--- |
|
|
|
|
|
|
|
|
@ -220,7 +409,7 @@ CREATE TABLE memory_report ( |
|
|
**必须在客户端做**,因为 App 未打开、甚至无网时也要响。服务端推送做不到(无 APNs/FCM 通道,且国内厂商推送要逐家接)。 |
|
|
**必须在客户端做**,因为 App 未打开、甚至无网时也要响。服务端推送做不到(无 APNs/FCM 通道,且国内厂商推送要逐家接)。 |
|
|
|
|
|
|
|
|
``` |
|
|
``` |
|
|
登录/前台恢复 → memory_upcoming(days=7) |
|
|
登录/前台恢复 → memory_upcoming(days=7, tz) |
|
|
→ flutter_local_notifications.zonedSchedule() 逐条排期 |
|
|
→ flutter_local_notifications.zonedSchedule() 逐条排期 |
|
|
→ 到点系统通知(带内容) |
|
|
→ 到点系统通知(带内容) |
|
|
→ 用户点击进 App → TtsService 播报 title + detail |
|
|
→ 用户点击进 App → TtsService 播报 title + detail |
|
|
@ -228,10 +417,49 @@ CREATE TABLE memory_report ( |
|
|
|
|
|
|
|
|
要点: |
|
|
要点: |
|
|
- 每次拉取**先取消旧排期再重排**,避免改期后旧通知还在。 |
|
|
- 每次拉取**先取消旧排期再重排**,避免改期后旧通知还在。 |
|
|
- Android 需 `SCHEDULE_EXACT_ALARM` 权限(Android 13+),iOS 需通知授权。**未授权时降级为「打开 App 才提醒」**,不要阻塞主流程。 |
|
|
- Android 的 `SCHEDULE_EXACT_ALARM` / `POST_NOTIFICATIONS` / `RECEIVE_BOOT_COMPLETED` |
|
|
|
|
|
**manifest 里已经声明了**,不用再加;但 Android 13+ 仍需运行时申请 `POST_NOTIFICATIONS`。 |
|
|
|
|
|
iOS 需通知授权。**未授权时降级为「打开 App 才提醒」**,不要阻塞主流程。 |
|
|
- 排期上限按平台限制(iOS 最多 64 条待处理通知),只排最近 7 天、按时间取前 N 条。 |
|
|
- 排期上限按平台限制(iOS 最多 64 条待处理通知),只排最近 7 天、按时间取前 N 条。 |
|
|
|
|
|
⚠️ 一条 `daily` 闹钟展开 7 天就吃掉 7 个名额,**重复项要先按天合并再排**, |
|
|
|
|
|
否则三条日常闹钟就把 iOS 的配额占掉一半。 |
|
|
- 通知点击进来才播报,**不在通知里直接播声音**——锁屏状态下强行出声是骚扰。 |
|
|
- 通知点击进来才播报,**不在通知里直接播声音**——锁屏状态下强行出声是骚扰。 |
|
|
|
|
|
|
|
|
|
|
|
⚠️ **权限拿不到的概率被低估了(v0.2 新增,这条可能让需求 3 大面积失效)**: |
|
|
|
|
|
|
|
|
|
|
|
1. **通知权限有 48 小时冷却,且不允许补救**。[permission_util.dart](../apps/client/lib/core/utils/permission_util.dart) |
|
|
|
|
|
为过审把规则写死了:用户拒绝过一次,**48 小时内一律直接返回 false,不弹系统框、不弹自定义框、 |
|
|
|
|
|
不跳设置页**,拒绝时间存 GetStorage、重启 App 依然生效;调用方拿到 false 只能「把功能置灰或退出」, |
|
|
|
|
|
**注释里明写「不要自己再补一个弹窗」**。 |
|
|
|
|
|
而 `Permission.notification` 是**全 App 共用的一个权限**,录音(`background_session_notifier`)、 |
|
|
|
|
|
会议录音、音乐播放都在申请它。用户在任何一处拒了,提醒功能就有两天完全静默, |
|
|
|
|
|
而且 App **不被允许**告诉他为什么。设计上必须假定「相当一部分用户长期拿不到通知权限」。 |
|
|
|
|
|
唯一的例外是「设置 → 权限管理」页里用户自己点(`userInitiated: true`), |
|
|
|
|
|
所以拾忆页至少要有一条指向那个页面的静态提示(不是弹窗)。 |
|
|
|
|
|
2. **Android 14 的精确闹钟很可能拿不到**。manifest 声明的是 `SCHEDULE_EXACT_ALARM`, |
|
|
|
|
|
Android 14 起它对普通应用**默认不授予**,要用户去系统设置里开。 |
|
|
|
|
|
闹钟/日历类应用应该声明的是 `USE_EXACT_ALARM`(默认授予), |
|
|
|
|
|
但那个权限 **Google Play 要求应用核心功能就是闹钟/日历**并单独提交说明。 |
|
|
|
|
|
拾忆算不算,需要产品先定;不申请就只能用 `inexact` 排期, |
|
|
|
|
|
**「提前 5 分钟」会变成「提前 5 到 20 分钟之间某个时刻」**,与需求 3 的字面要求不符。 |
|
|
|
|
|
3. **国内 ROM 会杀掉后台排期**。华为/小米/OPPO/vivo 的省电策略会清掉未加白名单应用的 |
|
|
|
|
|
AlarmManager 排期,这不是代码能绕过去的。已知的缓解只有引导用户加「自启动/电池优化白名单」, |
|
|
|
|
|
而那又会撞上第 1 条的合规冷却。**需求 3 在国内安卓上做不到 100% 送达,这个预期要提前跟产品对齐。** |
|
|
|
|
|
|
|
|
|
|
|
⚠️ **三个必须先解决的工程前置(v0.2 新增)**: |
|
|
|
|
|
|
|
|
|
|
|
1. **缺 `timezone` 依赖**。`zonedSchedule` 需要 `timezone` 包 + |
|
|
|
|
|
`tz.initializeTimeZones()` + 一个 `tz.Location`;本机时区名还需要 `flutter_timezone`。 |
|
|
|
|
|
`pubspec.yaml` 里两个都没有。不加这两个依赖,这条链路一行都写不了。 |
|
|
|
|
|
2. **通知 ID 要分命名空间**。仓库里已经有**两个各自 new 出来的 `FlutterLocalNotificationsPlugin` 实例** |
|
|
|
|
|
([background_session_notifier.dart](../apps/client/lib/core/utils/background_session_notifier.dart)、 |
|
|
|
|
|
[meeting_record_controller.dart](../apps/client/lib/modules/meeting/controllers/meeting_record_controller.dart)), |
|
|
|
|
|
各自管自己的通知 id。提醒排期再来一套,**id 撞车会互相覆盖**, |
|
|
|
|
|
而「先取消旧排期」若图省事写成 `cancelAll()`,会顺手干掉正在录音的前台通知, |
|
|
|
|
|
表现为录音悄悄失去前台身份、被系统回收。**提醒占一段固定 id 区间,只按 id 逐条 cancel。** |
|
|
|
|
|
3. **通道(channel)单独建一个**。录音提示是常驻低优先级通道,提醒需要能弹能响, |
|
|
|
|
|
共用一个通道会让提醒被降级成静默。 |
|
|
|
|
|
|
|
|
### 5.2 每日登录播报(需求 2)→ **客户端** |
|
|
### 5.2 每日登录播报(需求 2)→ **客户端** |
|
|
|
|
|
|
|
|
``` |
|
|
``` |
|
|
@ -249,9 +477,24 @@ App 启动/切前台 |
|
|
|
|
|
|
|
|
**`stop()` 必须真的能打断**——`TtsService` 接口已有该方法,且 `speakStream` 是队列式播放,停止时要连带清队列,否则会出现「关了还在念下一句」。 |
|
|
**`stop()` 必须真的能打断**——`TtsService` 接口已有该方法,且 `speakStream` 是队列式播放,停止时要连带清队列,否则会出现「关了还在念下一句」。 |
|
|
|
|
|
|
|
|
|
|
|
⚠️ **启动路径上的弹窗已经有两个了(v0.2 新增)**,本弹窗是第三个,必须排在最后: |
|
|
|
|
|
|
|
|
|
|
|
| 顺序 | 弹窗 | 位置 | |
|
|
|
|
|
|---|---|---| |
|
|
|
|
|
| 1 | 华为/荣耀「每次启动重新确认隐私政策」 | `login_controller._initDeviceInfo()` | |
|
|
|
|
|
| 2 | 强制更新弹窗(不可关闭,会把 splash 卡住) | splash `_performVersionCheckAsync` | |
|
|
|
|
|
| 3 | **每日事项弹窗** ← 新增 | 应在 token 校验通过、进入主页之后 | |
|
|
|
|
|
|
|
|
|
|
|
排在强更前面会出现「强更窗盖住播报窗、TTS 在背后念」。 |
|
|
|
|
|
**判据:必须等 `_validateTokenAsync` 通过**——未登录时 `memory_today` 拿不到数据。 |
|
|
|
|
|
|
|
|
|
|
|
⚠️ **TTS 会和通话翻译抢音频通道**。用户在通话翻译进行中切回前台时不要触发播报, |
|
|
|
|
|
先看 `TranslationController` 的活动状态。这条不做,会在通话里突然插一段「今天有 3 件事」。 |
|
|
|
|
|
|
|
|
### 5.3 周报/月报生成(需求 4/5)→ **服务端 cron + 异步队列** |
|
|
### 5.3 周报/月报生成(需求 4/5)→ **服务端 cron + 异步队列** |
|
|
|
|
|
|
|
|
照搬 [allhelp/summary.go](../apps/services/modules/allhelp/summary.go) 的成熟模式: |
|
|
照搬 [echomeet/tasks.go](../apps/services/modules/echomeet/tasks.go) 的成熟模式 |
|
|
|
|
|
(`home` 服务已在 `InitSys` 里 `cron.OnInit`,直接可用): |
|
|
|
|
|
|
|
|
``` |
|
|
``` |
|
|
lego cron(在 memory 模块 Start 中注册) |
|
|
lego cron(在 memory 模块 Start 中注册) |
|
|
@ -260,16 +503,37 @@ lego cron(在 memory 模块 Start 中注册) |
|
|
↓ 逐个 uid 建 memory_report(state=0) 并 LPush Redis 队列 |
|
|
↓ 逐个 uid 建 memory_report(state=0) 并 LPush Redis 队列 |
|
|
N 个 worker BRPop |
|
|
N 个 worker BRPop |
|
|
↓ SQL 算出 stat_json(确定性统计,不过 LLM) |
|
|
↓ SQL 算出 stat_json(确定性统计,不过 LLM) |
|
|
↓ LLM 只做一件事:把 stat_json 组织成适合朗读的话 → summary |
|
|
↓ providers.PickLLM("") → Summarizer.Chat:只做一件事,把 stat_json 组织成适合朗读的话 |
|
|
↓ 回写 state=2 |
|
|
↓ 回写 summary + llm_svc_id + state=2 |
|
|
``` |
|
|
``` |
|
|
|
|
|
|
|
|
要点: |
|
|
要点: |
|
|
- **cron 只在一个实例上跑**。`home` 是集群服务,多副本会重复生成。用 Redis `SET NX EX` 抢锁,key 带日期(`memory:report:lock:week:2026-W35`)。 |
|
|
- **cron 只在一个实例上跑**。`home` 是集群服务,多副本会重复生成。用 Redis `SET NX EX` 抢锁,key 带日期(`memory:report:lock:week:2026-W35`)。 |
|
|
|
|
|
⚠️ **锁 key 必须过 `redissys.RKey()` 加应用前缀**(`REDIS_KEY_PREFIX`),队列 key 同理。 |
|
|
|
|
|
阿龙测试机上多个应用**共用一个 redis:7 实例**,锁 key 不带前缀会让 A 应用抢到的锁挡住 B 应用, |
|
|
|
|
|
表现是「某个应用整周没有报告、且不报任何错」。这比队列串号更隐蔽——队列串号会串出脏数据, |
|
|
|
|
|
锁串号只是安静地什么都不做。 |
|
|
- 时间选 **00:30 而非 00:00**——避开整点其他定时任务,也给跨日数据落库留缓冲。 |
|
|
- 时间选 **00:30 而非 00:00**——避开整点其他定时任务,也给跨日数据落库留缓冲。 |
|
|
|
|
|
- ⚠️ **cron 跑的是容器时区,恒为 `Asia/Shanghai`**(v0.2 查证:`Dockerfile.voitrans` 里 |
|
|
|
|
|
`cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime`,`docker-compose.yml` 又设了 |
|
|
|
|
|
`TZ: Asia/Shanghai`;`lego/sys/cron` 本身**没有任何时区配置项**,`home.yaml` 里也没有 `cron:` 段)。 |
|
|
|
|
|
于是「周一 00:30」对**所有区域、所有用户**都是北京时间—— |
|
|
|
|
|
**纽约用户的周报会在他本地周日 12:30 生成,漏掉整个周日下午和晚上**, |
|
|
|
|
|
而 `happen_date` 又是按用户本地日期存的,两个口径直接对不上。 |
|
|
|
|
|
一期取舍:**接受这个偏差并写进说明**(周报口径 = UTC+8 的周),或者按用户 `tz` 分批触发 |
|
|
|
|
|
(cron 每小时跑一次,只处理「本地时间刚过周一 00:30」的那批用户)。后者更对但复杂度高一档。 |
|
|
|
|
|
- ⚠️ **「上周」的起始日因地区而异**:`period_key` 用 ISO 周(周一起)是欧洲/中国口径, |
|
|
|
|
|
美国、日本部分场景是周日起。41 语言全球发行,这个口径要么明确统一成 ISO 并在 UI 上说明, |
|
|
|
|
|
要么跟 `tz` 一起按用户地区算。**不要留着不定义**。 |
|
|
- 用户量大时按 uid 分片入队,不要一次性扫全表。 |
|
|
- 用户量大时按 uid 分片入队,不要一次性扫全表。 |
|
|
- **只给「上周期有数据的用户」生成**,空报告没有意义还浪费 LLM 调用。 |
|
|
- **只给「上周期有数据的用户」生成**,空报告没有意义还浪费 LLM 调用。 |
|
|
|
|
|
- **LLM 走 `providers.PickLLM` 而不是 `doubao.Chat`**(v0.2 修正)。`allhelp/summary.go` 直接 |
|
|
|
|
|
`import "yunyan/sys/doubao"` 是历史债:绕过了后台编排,海外应用会去调国内豆包, |
|
|
|
|
|
后台换模型也换不掉它。新模块不要再复制这个写法。 |
|
|
- 客户端下次打开时 `memory_getreport` 拉取 → 弹窗播报 → 用户确认 → `memory_confirmreport`。**不做「凌晨推送叫醒用户」**。 |
|
|
- 客户端下次打开时 `memory_getreport` 拉取 → 弹窗播报 → 用户确认 → `memory_confirmreport`。**不做「凌晨推送叫醒用户」**。 |
|
|
|
|
|
- ⚠️ **积压要有上限**:用户一个月不开 App 会攒下 4 份周报 + 1 份月报。 |
|
|
|
|
|
`memory_getreport` 不传 period 时**只返回最近一份未确认的**,其余在拾忆页留个入口自己翻, |
|
|
|
|
|
不要连弹 5 次。 |
|
|
|
|
|
|
|
|
### 5.4 为什么不用现有 `timer` 服务 |
|
|
### 5.4 为什么不用现有 `timer` 服务 |
|
|
|
|
|
|
|
|
@ -279,20 +543,26 @@ lego cron(在 memory 模块 Start 中注册) |
|
|
|
|
|
|
|
|
## 六、客户端设计 |
|
|
## 六、客户端设计 |
|
|
|
|
|
|
|
|
### 6.1 页面改名与结构 |
|
|
### 6.1 页面结构 |
|
|
|
|
|
|
|
|
`TodoTab` → `MemoryTab`(「记忆」)。i18n key 全量替换,41 个语言文件都要动。 |
|
|
tab 文案已是「拾忆」(`tabTodo`,但 41 个语言只有 3 个是对的,见 §2.3)。 |
|
|
|
|
|
底层类名仍是 `TodoTab` / `TodoScopeList` / `CalendarEventCategory`—— |
|
|
|
|
|
**类名可以不动**(改名只带来 diff 不带来价值),但新加的东西一律用 memory 命名。 |
|
|
|
|
|
|
|
|
``` |
|
|
``` |
|
|
记忆页 |
|
|
拾忆页 |
|
|
├── 日历卡(CalendarCard,复用现有几何/手势逻辑) |
|
|
├── 日历卡(CalendarCard,复用现有几何/手势逻辑) |
|
|
├── 分类筛选条(全部 / 待办 / 闹钟 / 灵感 / 花销) ← 新增 |
|
|
├── 分类筛选条(全部 / 待办 / 闹钟 / 灵感 / 花销) ← 新增 |
|
|
└── 事项列表(MemoryScopeList,由 TodoScopeList 改造) |
|
|
└── 事项列表(TodoScopeList,已有) |
|
|
├── 日/周/月范围切换(已有) |
|
|
├── 日/周/月范围切换(已有) |
|
|
├── 头部汇总:3 件待办 · 2 条灵感 · 花销 ¥248 ← 扩展 |
|
|
├── 头部汇总:3 件待办 · 2 条灵感 · 花销 ¥248 ← 由已有的「N 笔花销 / 合计」扩展 |
|
|
└── 分类着色的卡片 |
|
|
└── 分类着色的卡片(已有 todo / expense 两种,补 alarm / idea) |
|
|
``` |
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
⚠️ **`CalendarEventCategory` 现在只有 `{todo, expense}`**,且 `todo_scope_list.dart` 里是 |
|
|
|
|
|
`e.category == expense ? _expenseCard(e) : _todoCard(e)` 的**二选一分支**。 |
|
|
|
|
|
加 `alarm`/`idea` 要把它改成按分类查表,并对**未知分类兜底为 todo 卡片**(§3.3)。 |
|
|
|
|
|
|
|
|
### 6.2 数据层 |
|
|
### 6.2 数据层 |
|
|
|
|
|
|
|
|
新增 `MemoryService`(写法参照 `AssistantDirectiveService`): |
|
|
新增 `MemoryService`(写法参照 `AssistantDirectiveService`): |
|
|
@ -315,14 +585,35 @@ amount = item.amount // expense 分类 |
|
|
``` |
|
|
``` |
|
|
|
|
|
|
|
|
**同时删除 `mockCalendarEvents()`**——真实数据上线后,演示数据混在里面会让人以为系统出错。 |
|
|
**同时删除 `mockCalendarEvents()`**——真实数据上线后,演示数据混在里面会让人以为系统出错。 |
|
|
|
|
|
删的时候把 §2.4(3) 那约 40 个 `demo*` i18n key 一并清掉。 |
|
|
|
|
|
|
|
|
|
|
|
⚠️ 现在的重复闹钟展开逻辑(`_expandReminder`,窗口「今天前 7 天 ~ 后 60 天」) |
|
|
|
|
|
在换源后仍然需要——服务端返回的是记忆项原始行,日历要按天摊开。这段逻辑直接留用, |
|
|
|
|
|
只是输入从 `AssistantReminder` 换成 `MemoryItem`。 |
|
|
|
|
|
|
|
|
### 6.3 助手提醒的迁移 |
|
|
### 6.3 助手提醒的迁移 |
|
|
|
|
|
|
|
|
`AssistantDirectiveService` 保留(它是**指令流水日志**,有独立价值:排查、统计百炼下发了哪些没实现的指令),但: |
|
|
`AssistantDirectiveService` 保留(它是**指令流水日志**,有独立价值:排查、统计百炼下发了哪些没实现的指令),但: |
|
|
|
|
|
|
|
|
- `_handleSetClock` 不再写本地 `reminders`,改为调 `memory_add`(`category=alarm`, `source=assistant`) |
|
|
- `_handleSetClock` 不再只写本地 `reminders`,改为调 `memory_add`(`category=alarm`, `source=assistant`, |
|
|
|
|
|
`client_key=指令 id`) |
|
|
- 本地 `reminders` 列表在迁移期保留双写,一个版本后移除 |
|
|
- 本地 `reminders` 列表在迁移期保留双写,一个版本后移除 |
|
|
- 老版本本地已有的提醒,首次启动时**一次性上传**并打标记,避免重复 |
|
|
- 老版本本地已有的提醒,首次启动时**一次性上传**并打标记,避免重复。 |
|
|
|
|
|
上传走同一个 `client_key` 幂等键(用本地 reminder 的 id), |
|
|
|
|
|
这样即使标记丢了、重装了,重复上传也不会插重 |
|
|
|
|
|
|
|
|
|
|
|
### 6.4 花销录入与小票(v0.2 补) |
|
|
|
|
|
|
|
|
|
|
|
上传链路已经就绪,不需要新建:客户端 `user_getuploadurl` 拿预签名 PUT URL → dio 直传 OSS。 |
|
|
|
|
|
要做的只有两件: |
|
|
|
|
|
|
|
|
|
|
|
1. **`uploadScenes` 白名单加一项**(如 `MemoryImages`),在 |
|
|
|
|
|
[api_getuploadurl.go](../apps/services/modules/user/api_getuploadurl.go)。 |
|
|
|
|
|
⚠️ 少加这一项的表现是「那条上传链路整条报『不支持的上传用途』」,且只在真机跑到那个场景才发现。 |
|
|
|
|
|
2. **小票识别复用 echomeet 的多模态通道**——`ChatMessage.Images` 已经打通, |
|
|
|
|
|
各 Summarizer(豆包/通义/Azure 用 OpenAI 风格 `image_url` 直传,Gemini 内部转 base64)都支持。 |
|
|
|
|
|
一张小票 → LLM 出 `{title, amount, date}` → 落 `category=expense`。 |
|
|
|
|
|
⚠️ 金额必须**回显给用户确认再落库**,不要静默入账:LLM 认错一位小数,月报就废了。 |
|
|
|
|
|
|
|
|
--- |
|
|
--- |
|
|
|
|
|
|
|
|
@ -360,15 +651,38 @@ amount = item.amount // expense 分类 |
|
|
输入:<meeting_date>2026-08-31</meeting_date> |
|
|
输入:<meeting_date>2026-08-31</meeting_date> |
|
|
<summary>…已生成的纪要…</summary> |
|
|
<summary>…已生成的纪要…</summary> |
|
|
prompt:代码内置(不放 echomeet_template) |
|
|
prompt:代码内置(不放 echomeet_template) |
|
|
|
|
|
选路:复用本条记录已定的 rec.LlmSvcId → providers.GetSummarizer(rec.LlmSvcId) |
|
|
输出:JSON 数组 → 逐条写 memory_item(category=todo, source=meeting, source_id=记录id) |
|
|
输出:JSON 数组 → 逐条写 memory_item(category=todo, source=meeting, source_id=记录id) |
|
|
``` |
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
⚠️ **选路口径已变(v0.2 修正)**:v0.1 写的 `GetSummarizer(svcType, language)` 和四套固定服务商 |
|
|
|
|
|
(ByteDance/Ali/Google/Microsoft)**已经不存在了**——`ResolveServiceType` 在仓库里一处都搜不到。 |
|
|
|
|
|
现在是后台编排:记录上存 `asr_svc_id` / `mt_svc_id` / `llm_svc_id` 三个选路结果, |
|
|
|
|
|
总结走 `providers.PickLLM(clientSvcId)` 拿 svcId、`GetSummarizer(svcId)` 拿实现。 |
|
|
|
|
|
抽取这一路**直接复用 `rec.LlmSvcId`**,与本条记录的总结同模型,不要另选—— |
|
|
|
|
|
同一条会议两路用不同模型,出问题时无法归因。 |
|
|
|
|
|
|
|
|
**为什么 prompt 放代码不放模板**:① 模板有 1189 条,每条是对应语言的提示词,改不动也改不齐;② 各模板 `outline` 职责本就不同,塞进去会互相干扰;③ 输出要被程序 parse 成 JSON,格式严格性不能交给后台可随手编辑的文本;④ 抽取输入是已提炼的 `summary`(约千字),成本只是前两路(转写全文可达上万字)的零头,省不出什么。 |
|
|
**为什么 prompt 放代码不放模板**:① 模板有 1189 条,每条是对应语言的提示词,改不动也改不齐;② 各模板 `outline` 职责本就不同,塞进去会互相干扰;③ 输出要被程序 parse 成 JSON,格式严格性不能交给后台可随手编辑的文本;④ 抽取输入是已提炼的 `summary`(约千字),成本只是前两路(转写全文可达上万字)的零头,省不出什么。 |
|
|
|
|
|
|
|
|
**为什么必须注入 `<meeting_date>`**:现在 `AIProcess` 拼给模型的只有 `<remark>` + `<transcript>`,没有日期。模型不知道今天几号,让它把「下周三前」换算成具体日期就是瞎猜。 |
|
|
**为什么必须注入 `<meeting_date>`**:现在 `AIProcess` 拼给模型的只有 `<remark>` + `<transcript>`,没有日期。模型不知道今天几号,让它把「下周三前」换算成具体日期就是瞎猜。 |
|
|
|
|
|
|
|
|
|
|
|
⚠️ **`<meeting_date>` 取哪个字段(v0.2 补)**:`DBEchoMeetRecord` 上**没有「会议实际发生日期」这个字段**。 |
|
|
|
|
|
可用的是 `creationtime`(记录创建时间)和 `starttime`(处理开始时间,不是会议时间)。 |
|
|
|
|
|
只能用 `creationtime`,并接受它的语义是「上传当天」——用户补录三天前的录音时会算错。 |
|
|
|
|
|
记录上也没有客户端时区,服务端只能按固定时区(同 analyze,`Asia/Shanghai`)折算成日期。 |
|
|
|
|
|
**这两点都是已知偏差,不要假装它精确**;真要做准得给 `DBEchoMeetRecord` 加 |
|
|
|
|
|
`meet_date` + `tz` 两列由客户端上报,那是另一个改动。 |
|
|
|
|
|
|
|
|
**服务端必须二次校验 `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) 的注释)。 |
|
|
**服务端必须二次校验 `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) 的注释)。 |
|
|
|
|
|
|
|
|
|
|
|
⚠️ **`date_certain=0` 会把会议当天堆爆(v0.2 补)**:一次会抽出 8 条待办、全部没写截止时间, |
|
|
|
|
|
就全落在会议当天,日历那一格直接不可读。拾忆页对 `date_certain=0` 的项要 |
|
|
|
|
|
**单独归到「待定日期」分组**、不与当天确定事项混排。 |
|
|
|
|
|
|
|
|
|
|
|
⚠️ **抽取输出的语言**:`summary` 是按用户所选模板语言生成的(41 语言), |
|
|
|
|
|
抽取 prompt 内置在代码里是中文,但**要显式要求「title/owner 保持与输入相同的语言」**, |
|
|
|
|
|
否则日语会议的待办会变成中文,混在日语纪要里。 |
|
|
|
|
|
|
|
|
**失败隔离**:抽取失败只记日志,**不改 `rec.State`**。用户照样看得到纪要,只是没有自动待办。 |
|
|
**失败隔离**:抽取失败只记日志,**不改 `rec.State`**。用户照样看得到纪要,只是没有自动待办。 |
|
|
|
|
|
|
|
|
### 7.3 重新总结的幂等 |
|
|
### 7.3 重新总结的幂等 |
|
|
@ -405,12 +719,24 @@ delete from memory_item |
|
|
|
|
|
|
|
|
- **「取消闹钟」和「记一下待办」目前不下发任何指令**,模型却回「已经帮你取消了」——后台没配对应工具。这是需求 1「灵感/花销」能否语音录入的前提。 |
|
|
- **「取消闹钟」和「记一下待办」目前不下发任何指令**,模型却回「已经帮你取消了」——后台没配对应工具。这是需求 1「灵感/花销」能否语音录入的前提。 |
|
|
- `SEND_message` / `MAKE_A_PHONE_CALL_phone_call` 本版已无对应能力,**要去百炼后台把这两个工具摘掉**,否则模型会继续下发无法执行的指令。 |
|
|
- `SEND_message` / `MAKE_A_PHONE_CALL_phone_call` 本版已无对应能力,**要去百炼后台把这两个工具摘掉**,否则模型会继续下发无法执行的指令。 |
|
|
|
|
|
- ⚠️ 还需要一个 **`CANCEL_clock`**:「取消闹钟」现在是纯幻觉。记忆项上了服务端之后, |
|
|
|
|
|
取消若只删本地、服务端还留着,下次同步又会长回来——比现在更糟。 |
|
|
|
|
|
|
|
|
解析侧三个已知坑(照现有实现处理,勿改):`arguments` 是字符串形式 JSON 要二次 parse;参数值是中文自然语言(`repeat="周一"`)不是枚举;`finish_reason=command_calls` 是纯指令帧,text 为空且不会有 `RespondingStarted/Ended`,照 text 建气泡会留下永远填不上的空泡。 |
|
|
解析侧三个已知坑(照现有实现处理,勿改):`arguments` 是字符串形式 JSON 要二次 parse;参数值是中文自然语言(`repeat="周一"`)不是枚举;`finish_reason=command_calls` 是纯指令帧,text 为空且不会有 `RespondingStarted/Ended`,照 text 建气泡会留下永远填不上的空泡。 |
|
|
|
|
|
|
|
|
### 8.2 MCP 工具(服务端)——用户问,助手查 |
|
|
### 8.2 MCP 工具(服务端)——用户问,助手查 |
|
|
|
|
|
|
|
|
服务端 MCP 已经跑在 `/mcp`(streamable HTTP, stateless)与 `/sse`,带 `authFromRequest` 鉴权,工具分 `GLOBAL`/`CHINA`/`OVERSEAS` 三组。 |
|
|
⚠️⚠️ **前置:MCP 目前没有任何鉴权,本节的工具在补上鉴权之前不得上线。** 见 §2.4(1)。 |
|
|
|
|
|
|
|
|
|
|
|
服务端 MCP 跑在 `/mcp`(streamable HTTP, stateless)与 `/sse`,工具分 `GLOBAL`/`CHINA`/`OVERSEAS` 三组。 |
|
|
|
|
|
`authFromRequest` 把 `Authorization` 头放进 context 但**无人读取、无校验**, |
|
|
|
|
|
所有用户工具的 `uid` 都是模型填的参数。 |
|
|
|
|
|
|
|
|
|
|
|
**必须先做的改造**(三期 #16 的前置): |
|
|
|
|
|
|
|
|
|
|
|
1. 从 context 里的 `Authorization` 解出会话 uid(复用 gateway 那套 JWT 校验); |
|
|
|
|
|
2. 用户类工具**忽略参数里的 uid**,一律用会话 uid;解不出会话 uid 直接返回错误,不降级; |
|
|
|
|
|
3. 存量三个工具(`get_user_tasks`/`allhelp_task`/`cancel_user_task`)同步改。 |
|
|
|
|
|
|
|
|
**新增工具(`ToolGroup_GLOBAL`)**: |
|
|
**新增工具(`ToolGroup_GLOBAL`)**: |
|
|
|
|
|
|
|
|
@ -419,7 +745,7 @@ delete from memory_item |
|
|
| `get_memory_items` | 按日期范围/分类查记忆项 | 「我明天有什么事」「上周记了哪些灵感」 | |
|
|
| `get_memory_items` | 按日期范围/分类查记忆项 | 「我明天有什么事」「上周记了哪些灵感」 | |
|
|
| `get_memory_stats` | 统计(花销合计、完成率) | 「这个月花了多少钱」「上周有几件事没做完」 | |
|
|
| `get_memory_stats` | 统计(花销合计、完成率) | 「这个月花了多少钱」「上周有几件事没做完」 | |
|
|
| `get_memory_report` | 取周报/月报 | 「上周总结说了什么」 | |
|
|
| `get_memory_report` | 取周报/月报 | 「上周总结说了什么」 | |
|
|
| `add_memory_item` | 新增记忆项 | 「帮我记一下:明天下午three点开会」 | |
|
|
| `add_memory_item` | 新增记忆项 | 「帮我记一下:明天下午三点开会」 | |
|
|
| `complete_memory_item` | 标记完成 | 「把跟进 A 客户那件事标记完成」 | |
|
|
| `complete_memory_item` | 标记完成 | 「把跟进 A 客户那件事标记完成」 | |
|
|
|
|
|
|
|
|
现有 `get_user_tasks` / `allhelp_task` / `cancel_user_task` 三个工具**改为读写 `memory_item`**(见 §9.1),保持工具名不变,避免百炼后台重新配置。 |
|
|
现有 `get_user_tasks` / `allhelp_task` / `cancel_user_task` 三个工具**改为读写 `memory_item`**(见 §9.1),保持工具名不变,避免百炼后台重新配置。 |
|
|
@ -454,14 +780,30 @@ delete from memory_item |
|
|
|
|
|
|
|
|
**不要直接删 `DBTask` 表**——EMAI 侧可能有历史会话引用,且迁移脚本需要回滚余地。 |
|
|
**不要直接删 `DBTask` 表**——EMAI 侧可能有历史会话引用,且迁移脚本需要回滚余地。 |
|
|
|
|
|
|
|
|
### 9.2 客户端老版本 |
|
|
### 9.2 账号注销必须登记新表(v0.2 新增,**别漏**) |
|
|
|
|
|
|
|
|
|
|
|
[model_user.go](../apps/services/modules/user/model_user.go) 的 `cancel()` 是硬编码表清单, |
|
|
|
|
|
加两行: |
|
|
|
|
|
|
|
|
|
|
|
```go |
|
|
|
|
|
mysql.Delete(comm.TableMemoryItem, "uid=?", uid) |
|
|
|
|
|
mysql.Delete(comm.TableMemoryReport, "uid=?", uid) |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
同时在 `comm/const.go` 登记 `TableMemoryItem` / `TableMemoryReport` 两个常量。 |
|
|
|
|
|
**漏了不报错**,只是用户注销后花销与灵感留在库里——发现时已经是合规问题。 |
|
|
|
|
|
|
|
|
|
|
|
### 9.3 客户端老版本 |
|
|
|
|
|
|
|
|
老版本客户端不知道 `memory_*` 接口,继续用本地 `GetStorage`。**服务端不做任何兼容适配**——新功能只对新版本开放,老版本行为不变(本地闹钟照常)。 |
|
|
老版本客户端不知道 `memory_*` 接口,继续用本地 `GetStorage`。**服务端不做任何兼容适配**——新功能只对新版本开放,老版本行为不变(本地闹钟照常)。 |
|
|
|
|
|
|
|
|
### 9.3 不向后兼容的点 |
|
|
### 9.4 不向后兼容的点 |
|
|
|
|
|
|
|
|
- 记忆页改名后,i18n key 变更需 41 个语言文件同步,**漏一个语言就显示 key 名** |
|
|
- 拾忆页新增分类后,老客户端拿到 `alarm`/`idea` 会落进未知分支——**这就是 §3.3 要求兜底的原因**, |
|
|
|
|
|
一期就要把兜底发出去,否则二期加分类会打崩存量包 |
|
|
- `mockCalendarEvents()` 删除后,无数据用户看到的是空态页——需要设计空态文案与引导 |
|
|
- `mockCalendarEvents()` 删除后,无数据用户看到的是空态页——需要设计空态文案与引导 |
|
|
|
|
|
- i18n:`tabTodo` 现状是 38/41 个语言显示 `Remin`(§2.3),本次要补齐; |
|
|
|
|
|
新增的分类名、空态文案同样要 41 份 |
|
|
|
|
|
|
|
|
--- |
|
|
--- |
|
|
|
|
|
|
|
|
@ -475,14 +817,14 @@ delete from memory_item |
|
|
│ (summary + 会议日期) │ (SET_clock/ADD_todo/…) |
|
|
│ (summary + 会议日期) │ (SET_clock/ADD_todo/…) |
|
|
└──────────┬─────────────────┘ |
|
|
└──────────┬─────────────────┘ |
|
|
↓ |
|
|
↓ |
|
|
┌──── memory_item ────┐ ← 手动录入(记忆页 +) |
|
|
┌──── memory_item ────┐ ← 手动录入(拾忆页 +)/小票拍照 |
|
|
│ todo/alarm/idea/ │ |
|
|
│ todo/alarm/idea/ │ |
|
|
│ expense… │ |
|
|
│ expense… │ |
|
|
└──┬────────┬──────┬───┘ |
|
|
└──┬────────┬──────┬───┘ |
|
|
│ │ │ |
|
|
│ │ │ |
|
|
┌────┘ │ └────────────┐ |
|
|
┌────┘ │ └────────────┐ |
|
|
↓ ↓ ↓ |
|
|
↓ ↓ ↓ |
|
|
记忆页日历 提醒链路 周期复盘 |
|
|
拾忆页日历 提醒链路 周期复盘 |
|
|
(分类展示) ├ 每日登录弹窗+播报 ├ 周一 00:30 cron |
|
|
(分类展示) ├ 每日登录弹窗+播报 ├ 周一 00:30 cron |
|
|
├ 提前5分钟本地通知 ├ 1号 00:30 cron |
|
|
├ 提前5分钟本地通知 ├ 1号 00:30 cron |
|
|
└ TTS 播报(可中断) ├ SQL 算 stat_json |
|
|
└ TTS 播报(可中断) ├ SQL 算 stat_json |
|
|
@ -490,45 +832,60 @@ delete from memory_item |
|
|
└ 播报 → 用户确认 |
|
|
└ 播报 → 用户确认 |
|
|
↑ │ |
|
|
↑ │ |
|
|
└────── MCP 工具 ←── EMAI 提问 ─────┘ |
|
|
└────── MCP 工具 ←── EMAI 提问 ─────┘ |
|
|
|
|
|
(需先补鉴权) |
|
|
``` |
|
|
``` |
|
|
|
|
|
|
|
|
--- |
|
|
--- |
|
|
|
|
|
|
|
|
## 十一、分期实施 |
|
|
## 十一、分期实施 |
|
|
|
|
|
|
|
|
|
|
|
### 零期:前置修缮(不做完,后面几期会返工) |
|
|
|
|
|
|
|
|
|
|
|
| # | 任务 | 端 | |
|
|
|
|
|
|---|---|---| |
|
|
|
|
|
| 0.1 | `tabTodo` 补齐 41 语言、改掉 `Remin`(单双引号都要查) | 客户端 | |
|
|
|
|
|
| 0.2 | `pubspec` 引入 `timezone` + `flutter_timezone` | 客户端 | |
|
|
|
|
|
| 0.3 | 通知 id 区间与专用 channel 规划(不新建第三个 plugin 实例) | 客户端 | |
|
|
|
|
|
| 0.4 | `comm/const.go` 登记两张表常量 + `cancel()` 加两行(连同补上 `TableUserIdVerify`) | 后端 | |
|
|
|
|
|
| 0.5 | `i18n_audit.py` 加「缺失 key」检查并接进 CI(当前 40 个语言里 39 个在漏) | 工具 | |
|
|
|
|
|
| 0.6 | 定 `USE_EXACT_ALARM` 申不申请 + 与产品对齐「提醒送达率做不到 100%」 | 产品 | |
|
|
|
|
|
| 0.7 | 定多币种口径(建议一期单币种)与「上周」的起始日 | 产品 | |
|
|
|
|
|
|
|
|
### 一期:打通主链路(只读为主) |
|
|
### 一期:打通主链路(只读为主) |
|
|
|
|
|
|
|
|
| # | 任务 | 端 | |
|
|
| # | 任务 | 端 | |
|
|
|---|---|---| |
|
|
|---|---|---| |
|
|
| 1 | `memory_item` 表 + `memory` 模块骨架 | 后端 | |
|
|
| 1 | `memory_item` 表 + `memory` 模块骨架(挂进 `home/main.go`) | 后端 | |
|
|
| 2 | `memory_list` / `memory_today` / `memory_add` / `memory_update` / `memory_complete` | 后端 | |
|
|
| 2 | `memory_list` / `memory_today` / `memory_add` / `memory_update` / `memory_complete` | 后端 | |
|
|
| 3 | 会议抽取第三路 + `<meeting_date>` 注入 + 日期校验 | 后端 | |
|
|
| 3 | 会议抽取第三路 + `<meeting_date>` 注入 + 日期校验 + 复用 `rec.LlmSvcId` | 后端 | |
|
|
| 4 | `General-Meeting` zh-CN 模板第四节改造 | 后台配置 | |
|
|
| 4 | `General-Meeting` zh-CN 模板第四节改造 | 后台配置 | |
|
|
| 5 | `MemoryService` + `MemoryItem` 模型 | 客户端 | |
|
|
| 5 | `MemoryService` + `MemoryItem` 模型(含 `client_key` 幂等) | 客户端 | |
|
|
| 6 | 记忆页改名、分类筛选、`_rebuildEvents` 换源、删 mock | 客户端 | |
|
|
| 6 | `CalendarEventCategory` 扩到 4 类 + **未知分类兜底** + 分类筛选 + `_rebuildEvents` 换源 + 删 mock 与 40 个 demo key | 客户端 | |
|
|
| 7 | 助手 `SET_clock` 改写服务端 | 客户端 | |
|
|
| 7 | 助手 `SET_clock` 改写服务端(带 `client_key`) | 客户端 | |
|
|
|
|
|
|
|
|
**验收**:开一次会 → 纪要生成 → 待办自动出现在记忆页日历上,标注来源会议与负责人。 |
|
|
**验收**:开一次会 → 纪要生成 → 待办自动出现在拾忆页日历上,标注来源会议与负责人; |
|
|
|
|
|
无截止时间的落在「待定日期」分组而不是堆在会议当天。 |
|
|
|
|
|
|
|
|
### 二期:提醒与播报 |
|
|
### 二期:提醒与播报 |
|
|
|
|
|
|
|
|
| # | 任务 | 端 | |
|
|
| # | 任务 | 端 | |
|
|
|---|---|---| |
|
|
|---|---|---| |
|
|
| 8 | `memory_upcoming` + `remind_at` 计算 | 后端 | |
|
|
| 8 | `memory_upcoming` + 重复项按 `tz` 展开成绝对时刻 | 后端 | |
|
|
| 9 | 本地通知排期(含权限降级) | 客户端 | |
|
|
| 9 | 本地通知排期(含权限降级、id 命名空间、重复项按天合并) | 客户端 | |
|
|
| 10 | 每日登录弹窗 + TTS 播报 + 中断 | 客户端 | |
|
|
| 10 | 每日登录弹窗 + TTS 播报 + 中断(排在强更/隐私弹窗之后;通话中不播) | 客户端 | |
|
|
| 11 | 百炼后台补 `ADD_todo`/`ADD_idea`/`ADD_expense`,摘掉电话/短信工具 | 配置 | |
|
|
| 11 | 百炼后台补 `ADD_todo`/`ADD_idea`/`ADD_expense`/`CANCEL_clock`,摘掉电话/短信工具 | 配置 | |
|
|
| 12 | 花销分类完整支持(录入 + 统计) | 两端 | |
|
|
| 12 | 花销分类完整支持(录入 + 统计 + `MemoryImages` scene + 小票识别回显确认) | 两端 | |
|
|
|
|
|
|
|
|
### 三期:复盘与问答 |
|
|
### 三期:复盘与问答 |
|
|
|
|
|
|
|
|
| # | 任务 | 端 | |
|
|
| # | 任务 | 端 | |
|
|
|---|---|---| |
|
|
|---|---|---| |
|
|
| 13 | `memory_report` 表 + cron + 分布式锁 + 异步 worker | 后端 | |
|
|
| 13 | `memory_report` 表 + cron + 分布式锁 + 异步 worker(选路走 `PickLLM`) | 后端 | |
|
|
| 14 | `stat_json` SQL 统计 + LLM 播报文案生成 | 后端 | |
|
|
| 14 | `stat_json` SQL 统计(明细截断)+ LLM 播报文案生成 | 后端 | |
|
|
| 15 | 报告弹窗 + 播报 + 确认 | 客户端 | |
|
|
| 15 | 报告弹窗 + 播报 + 确认(只弹最近一份未确认的) | 客户端 | |
|
|
| 16 | 5 个新 MCP 工具 + 旧工具改读新表 | 后端 | |
|
|
| 16 | **MCP 鉴权改造**(前置)→ 5 个新工具 + 旧工具改读新表 | 后端 | |
|
|
| 17 | `DBTask` 存量迁移脚本 | 后端 | |
|
|
| 17 | `DBTask` 存量迁移脚本 | 后端 | |
|
|
|
|
|
| 18 | admin 后台排障页:按 uid 查记忆项、看报告生成状态与失败原因 | 后台 | |
|
|
|
|
|
|
|
|
--- |
|
|
--- |
|
|
|
|
|
|
|
|
@ -538,22 +895,59 @@ delete from memory_item |
|
|
|
|
|
|
|
|
| 级别 | 风险 | 应对 | |
|
|
| 级别 | 风险 | 应对 | |
|
|
|---|---|---| |
|
|
|---|---|---| |
|
|
|
|
|
| 高 | **MCP 无鉴权,uid 由模型自报**——挂上记忆工具等于开放他人生活流水。且 MCP 走独立进程独立端口(7300),**不经过网关那套 token 校验**,又必须对百炼公网可达 | 三期 #16 前置改造,解出会话 uid 并忽略参数 uid | |
|
|
|
|
|
| 高 | **`memory` 模块 Init 返回 error 会打挂整站**——五服务同容器,entrypoint 见任一 pid 退出就全杀 | 建表失败只记日志;别照抄 `allhelp/model.go` 的命名返回值写法(§4) | |
|
|
|
|
|
| 高 | **需求 3 在国内安卓上无法保证送达**:通知权限 48 小时合规冷却 + Android 14 精确闹钟默认不授予 + 国内 ROM 杀后台排期 | 三条都不是代码能绕的,需与产品对齐预期(§5.1) | |
|
|
|
|
|
| 高 | **i18n 管道本身在漏**:40 个语言只有 1 个不缺 key,其余缺 233~388 个;审计工具结构上不检查缺失 key | 先给 `i18n_audit.py` 加缺失检查并接 CI,否则新增 30~50 个 key 会同样漏掉大半(§2.3) | |
|
|
| 高 | **相对时间解析不准**(「下周三前」→ 具体日期) | 注入会议日期 + 服务端二次校验,宁可留空 | |
|
|
| 高 | **相对时间解析不准**(「下周三前」→ 具体日期) | 注入会议日期 + 服务端二次校验,宁可留空 | |
|
|
| 高 | **重新总结冲掉用户修改** | `user_edited` + `gen_round` 双字段 | |
|
|
| 高 | **重新总结冲掉用户修改** | `user_edited` + `gen_round` 双字段 | |
|
|
| 高 | **cron 多副本重复生成报告** | Redis `SET NX EX` 分布式锁 | |
|
|
| 高 | **cron 多副本重复生成报告** | Redis `SET NX EX` 分布式锁 | |
|
|
| 中 | 本地通知权限被拒 / 平台限制 | 降级为打开 App 才提醒,不阻塞 | |
|
|
| 高 | **注销漏删新表** | §9.2,与建表同一个 PR 提交 | |
|
|
| 中 | LLM 算错金额 | 统计全部走 SQL,LLM 只组织文案 | |
|
|
| 中 | 端侧指令重试导致重复插入 | `client_key` 幂等键 + 唯一索引 | |
|
|
|
|
|
| 中 | 老客户端遇到新分类崩页 | 一期就发未知分类兜底 | |
|
|
|
|
|
| 中 | 本地通知权限被拒 / 平台限制 / iOS 64 条上限 | 降级为打开 App 才提醒;重复项按天合并 | |
|
|
|
|
|
| 中 | `cancelAll()` 误杀录音前台通知 | 提醒占固定 id 区间,逐条 cancel | |
|
|
|
|
|
| 中 | LLM 算错金额 | 统计全部走 SQL,LLM 只组织文案;小票识别结果必须回显确认 | |
|
|
| 中 | 多一路 LLM 调用增加成本 | 输入用 `summary` 而非全文;失败不重试 | |
|
|
| 中 | 多一路 LLM 调用增加成本 | 输入用 `summary` 而非全文;失败不重试 | |
|
|
| 中 | 41 个语言文件改名遗漏 | 用 `tools/i18n_audit.py` 校验 | |
|
|
| 中 | 启动弹窗打架(隐私 / 强更 / 每日播报) | 固定顺序,每日弹窗排最后且在登录态之后 | |
|
|
|
|
|
| 中 | **周报的「周一凌晨」是容器固定的北京时间**,纽约用户的周报在他本地周日中午就生成,漏掉周日下半天 | 接受并写进说明,或按 `tz` 分批触发(§5.3) | |
|
|
|
|
|
| 中 | **「上周」起始日因地区而异**(ISO 周一起 vs 美国周日起) | 明确统一成 ISO 并在 UI 说明,不要留着不定义 | |
|
|
|
|
|
| 中 | **多币种花销无法合计**,且客户端硬编码 `¥` | 一期只支持单一货币;月报多币种分行列出,不做汇率换算(§3.3) | |
|
|
|
|
|
| 中 | **Redis 锁 key 漏加应用前缀**——测试机多应用共用一个 Redis,会安静地让某应用整周没报告 | 锁与队列 key 都过 `redissys.RKey()` | |
|
|
|
|
|
| 中 | proto 表达不了 DECIMAL/DATE/DATETIME/null,照 DDL 直译会写不出来或写歪 | 按 §3.2 的映射表落地;金额一律 `int64` 分 | |
|
|
|
|
|
| 中 | 41 个语言文件改名遗漏(现状已有 38 个是坏的) | 单双引号都查;**不要依赖 `i18n_audit.py`**,它抓不到(§2.3) | |
|
|
|
|
|
| 中 | **GetStorage 全量缓存会越撑越慢**——它是把整个 box 读进内存的 JSON 文件,记忆项一年 3000+ 条 | `MemoryService` 只缓存最近 N 天(建议 90 天),不做全量镜像(§6.2) | |
|
|
|
|
|
| 中 | **`memory_list` 没有分页参数**——月视图尚可,但「数据导出拉全量」会撑爆 | 接口定义时就把 `page`/`size` 留出来,不要等要用了再加字段(§4) | |
|
|
|
|
|
| 中 | **一个埋点都没有**,而项目里每个功能都有;`analyze.writeEvent` 是固定 switch,加维度要同时改 console 看板列 | 先定要不要埋(待确认 10);要埋则注意 `ANALYZE_APP_NAME` 必须等于 `app_registry.name`,否则快照被静默拒收、看板恒为 0 且不报错 | |
|
|
| 低 | 统一表半数字段为空 | 数据量级小,可接受 | |
|
|
| 低 | 统一表半数字段为空 | 数据量级小,可接受 | |
|
|
|
|
|
|
|
|
### 待确认 |
|
|
### 待确认 |
|
|
|
|
|
|
|
|
1. **花销录入方式**——纯语音(「打车花了 30」)?手动记账页?是否要拍照识别小票?当前设计只保证数据结构支持,录入 UI 未定。 |
|
|
1. **周报/月报的成本与额度**——每人每周一次 LLM 调用,用户量上来就是纯支出,而项目里 |
|
|
2. **灵感与「聊天总结」的关系**——`allhelp` 已有 `DBChatSummary`(每日聊天 AI 总结)。灵感是用户主动说「记一下」,聊天总结是自动提炼,两者要不要合并?建议**先分开**,灵感是显式的、聊天总结是隐式的。 |
|
|
翻译/会议/AI 对话都各有额度桶(`Tradeintegral`/`Meetintegral`/`Aichatintegral`)。 |
|
|
3. **报告确认的含义**——只是「我看过了」,还是要允许用户在确认时批量处理未完成事项(顺延/放弃)?影响接口设计。 |
|
|
报告生成要不要挂额度或 VIP 门槛?目前设计是**所有人免费生成**,需要确认这是有意的。 |
|
|
4. **多设备播报冲突**——同一账号在手机和耳机同时在线,谁播报?建议只在当前活跃前台设备播。 |
|
|
2. **拾忆页要不要走实名闸门**——现在所有功能卡片都按 `IdVerifyGuard.blocked()` 拦。 |
|
|
5. **阿龙测试机 NATS 容器 unhealthy**(已 3 周)——配置变更广播依赖它,且 `stats_global_day` 数据停在 20260731。上线前需修复,否则后台改模板要等 10 分钟 cron 兜底。 |
|
|
拾忆是底部 tab,整页拦不合适;但语音录入走 EMAI,EMAI 已被拦,等于游客只能手动录。 |
|
|
|
|
|
要确认这个不对称是可接受的。 |
|
|
|
|
|
3. **数据导出**——花销与灵感是用户自己积累的资产,没有导出入口等于数据绑架, |
|
|
|
|
|
合规角度也该有。一期不做,但接口设计上 `memory_list` 要能不分页拉全量。 |
|
|
|
|
|
4. **花销录入方式**——纯语音(「打车花了 30」)?手动记账页?拍小票? |
|
|
|
|
|
(小票通道 §6.4 已确认技术上现成,剩下是产品决策。) |
|
|
|
|
|
5. **灵感与「聊天总结」的关系**——`allhelp` 已有 `DBChatSummary`(每日聊天 AI 总结)。灵感是用户主动说「记一下」,聊天总结是自动提炼,两者要不要合并?建议**先分开**,灵感是显式的、聊天总结是隐式的。 |
|
|
|
|
|
6. **报告确认的含义**——只是「我看过了」,还是要允许用户在确认时批量处理未完成事项(顺延/放弃)?影响接口设计。 |
|
|
|
|
|
7. **多设备播报冲突**——同一账号在手机和耳机同时在线,谁播报?建议只在当前活跃前台设备播。 |
|
|
|
|
|
8. **`allhelp/summary.go` 的 doubao 硬编码要不要一并改**——本模块不复制这个写法(§5.3), |
|
|
|
|
|
但存量那处仍然绕过编排。是否借这次一起收拾? |
|
|
|
|
|
9. **`USE_EXACT_ALARM` 要不要申请**——拾忆算不算「核心功能是闹钟/日历」, |
|
|
|
|
|
影响 Google Play 提交材料,也决定「提前 5 分钟」是精确还是「5~20 分钟之间」。产品先定。 |
|
|
|
|
|
10. **记忆中心要不要埋点**——`analyze` 模块的 `writeEvent` 是一张固定的 `switch` |
|
|
|
|
|
(登录/注册/设备激活/绑定/订单/翻译/会议/AI对话/资源发放),加统计维度要改这个 switch |
|
|
|
|
|
**并且**改 console 看板的列。目前设计里一个埋点都没有,而项目里每个功能都有。 |
|
|
|
|
|
如果要加,注意 `ANALYZE_APP_NAME` 必须与 `app_registry.name` 相等,否则快照被 console |
|
|
|
|
|
静默拒收、看板恒为 0 且不报错。**这一条同时列在风险表里**——「不埋」是可以的决策, |
|
|
|
|
|
但必须是被决策过的,而不是忘了。 |
|
|
|
|
|
11. **阿龙测试机 NATS 容器 unhealthy**(v0.1 记为已 3 周)——配置变更广播依赖它。 |
|
|
|
|
|
**本次未复核,上线前需重新确认**;不通的话后台改模板要等 10 分钟 cron 兜底。 |
|
|
|
|
|
|
|
|
--- |
|
|
--- |
|
|
|
|
|
|
|
|
@ -561,10 +955,11 @@ delete from memory_item |
|
|
|
|
|
|
|
|
| 分类 | 色值 | 说明 | |
|
|
| 分类 | 色值 | 说明 | |
|
|
|---|---|---| |
|
|
|---|---|---| |
|
|
| `todo` 待办 | `#5B8DEF` 蓝 | | |
|
|
| `todo` 待办 | `#5B8DEF` 蓝 | 与现有 mock 的 todo 卡片同色 | |
|
|
| `alarm` 闹钟 | `#22C1A6` 青 | 沿用现有助手提醒色 | |
|
|
| `alarm` 闹钟 | `#22C1A6` 青 | 沿用现有助手提醒色(`_expandReminder` 里已是这个值) | |
|
|
| `idea` 灵感 | `#F59E0B` 橙 | | |
|
|
| `idea` 灵感 | `#F59E0B` 橙 | | |
|
|
| `expense` 花销 | `#EC4899` 粉 | | |
|
|
| `expense` 花销 | `#EC4899` 粉 | | |
|
|
|
|
|
| 未知分类 | 中性灰 | 兜底,见 §3.3 | |
|
|
|
|
|
|
|
|
深色模式下需各自校验对比度,不要直接沿用。 |
|
|
深色模式下需各自校验对比度,不要直接沿用。 |
|
|
|
|
|
|
|
|
@ -573,12 +968,18 @@ delete from memory_item |
|
|
| 用途 | 路径 | |
|
|
| 用途 | 路径 | |
|
|
|---|---| |
|
|
|---|---| |
|
|
| 会议纪要生成 | `apps/services/modules/echomeet/tasks.go` (`AIProcess`) | |
|
|
| 会议纪要生成 | `apps/services/modules/echomeet/tasks.go` (`AIProcess`) | |
|
|
|
|
|
| **LLM 选路(编排)** | `apps/services/modules/echomeet/providers.go` (`PickLLM` / `GetSummarizer`) | |
|
|
| 会议模板读写 | `apps/services/modules/console/api_config.go` | |
|
|
| 会议模板读写 | `apps/services/modules/console/api_config.go` | |
|
|
| 已有任务体系 | `apps/services/modules/allhelp/` | |
|
|
| 已有任务体系 | `apps/services/modules/allhelp/` | |
|
|
| 异步总结范例 | `apps/services/modules/allhelp/summary.go` | |
|
|
| 异步队列范例 | `apps/services/modules/echomeet/tasks.go`(推荐)、`apps/services/modules/allhelp/summary.go`(选路是反例) | |
|
|
| MCP 工具 | `apps/services/modules/mcp/tool_*.go` | |
|
|
| **账号注销清表** | `apps/services/modules/user/model_user.go` (`cancel`) | |
|
|
|
|
|
| 上传预签名 URL / scene 白名单 | `apps/services/modules/user/api_getuploadurl.go` | |
|
|
|
|
|
| MCP 工具与(缺失的)鉴权 | `apps/services/modules/mcp/module.go`、`tool_*.go` | |
|
|
|
|
|
| 表名常量 | `apps/services/comm/const.go` | |
|
|
| 助手指令解析 | `apps/client/lib/data/models/assistant_directive.dart` | |
|
|
| 助手指令解析 | `apps/client/lib/data/models/assistant_directive.dart` | |
|
|
| 助手指令分发 | `apps/client/lib/data/services/assistant_directive_service.dart` | |
|
|
| 助手指令分发 | `apps/client/lib/data/services/assistant_directive_service.dart` | |
|
|
| 日历控制器 | `apps/client/lib/modules/home/controllers/calendar_controller.dart` | |
|
|
| 日历控制器 | `apps/client/lib/modules/home/controllers/calendar_controller.dart` | |
|
|
| 待办列表 | `apps/client/lib/modules/main_tab/views/widgets/todo_scope_list.dart` | |
|
|
| **日历事件模型 / mock** | `apps/client/lib/modules/home/views/widgets/calendar/calendar_event.dart` | |
|
|
|
|
|
| 拾忆列表与花销卡片 | `apps/client/lib/modules/main_tab/views/widgets/todo_scope_list.dart` | |
|
|
|
|
|
| 本地通知(已有两处实例) | `apps/client/lib/core/utils/background_session_notifier.dart`、`apps/client/lib/modules/meeting/controllers/meeting_record_controller.dart` | |
|
|
| TTS 接口 | `apps/client/lib/data/services/tts_service.dart` | |
|
|
| TTS 接口 | `apps/client/lib/data/services/tts_service.dart` | |
|
|
|