01项目概述
日常最有价值的画面往往来不及记:一段报错、一页文档、一张数据表、一个配置项。截图容易,但截图之后散落在相册里就再也找不回来了。月忆解决的是「捕获之后的整理与回忆」——按下快捷键,剩下的全自动。
形态上是 Tauri 2 桌面应用(Rust 外壳 + WebView2 前端),技术选择上坚持 Local First:数据库、截图、附件全部存在本地;只有用户主动触发 AI 时,相关片段才会发给模型,绝不默认上传整个记忆库。
一键捕获闭环
Alt+Shift+S 全屏截图 → 立即入库(不阻塞)→ 后台 OCR → AI 整理 → 系统通知
离线 OCR 不联网
直接调 Windows WinRT OcrEngine,中英混排界面实测识别 353 字,无需第三方 OCR 服务
AI 结构化理解
一次捕获拆成标题 / 摘要 / 标签 / 分类四项,单项失败不影响其余,异常 JSON 容错解析
Ctrl+K 检索问答
命令面板兼全局搜索,问答只带 8 条相关记忆摘要,回答里的 [1] 可点回原始截图
系统级集成
全局快捷键、系统托盘、自定义 memex-img 协议、DPI 感知截图、DWM 圆角小窗
自带设计系统
Token 单一来源、13 种基元组件、应用内 UI 实验室、非标准字号审计、像素级视觉回归
02系统架构全景
用户(Windows 11)
│
┌─────────────▼──────────────────┐
│ 全局快捷键 · 托盘 · 悬浮速记条 │
│ Alt+Shift+S 截屏 · Alt+Shift+M 速记 │
└─────────────┬──────────────────┘
│ Tauri IPC(invoke / event)
┌─────────────▼──────────────────┐
│ Rust 核心(Tauri 2) │
│ capture.rs GDI BitBlt 全屏截图 │
│ ocr.rs WinRT 离线文字识别 │
│ worker.rs 后台 AI 流水线 │
│ ai.rs AiProvider 抽象 │
└──────┬──────────────────┬───────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ SQLite 本地库 │ │ DeepSeek API │
│ 9 张表 · 零上传 │ │ 4 次结构化调用 │
└──────────────┘ └──────────────────┘
▲ ▲
└──────────┬───────┘
│ invoke 返回 / 事件广播
┌─────────────────▼──────────────┐
│ React 18 前端(WebView2) │
│ 时间线 / 合集 / 搜索问答 / 设置 │
│ Zustand · 自研设计系统 · Ctrl+K │
└────────────────────────────────┘
三层职责边界很清晰:前端只管呈现与交互(React + Zustand),Rust 核心管能力(截图、OCR、文件、调度),数据与模型都在进程外(SQLite 文件 + DeepSeek API)。AI 调用被抽象成 AiProvider trait,换供应商只需实现一个 trait。
03技术栈全景
🦀
桌面外壳
Tauri 2(Rust)
单文件 7.3 MB · NSIS 安装包 2.6 MB
内存占用远低于 Electron
⚛️
前端
React 18 + TypeScript
Vite 5 · Tailwind 3 · Zustand
WebView2 渲染
🗄️
本地存储
SQLite(rusqlite bundled)
9 张表 · schema 迁移
数据目录可用环境变量覆盖
👁️
截图与 OCR
Windows GDI BitBlt + CAPTUREBLT
WinRT OcrEngine(离线)
纯 Rust 进程内调用
🤖
AI 层
DeepSeek(OpenAI 兼容)
AiProvider trait 抽象
4 次结构化调用 · 容错解析
🧵
并发模型
后台线程跑 AI 流水线
UI 立即返回「已捕获」
ai_tasks 记录任务状态
🎨
设计系统
双层 Token(Pearl / Night)
13 种基元 · Lucide 统一 1.7 描边
UI 实验室 + 视觉回归
⌨️
系统集成
全局快捷键 / 托盘 / 悬浮速记窗
自定义 memex-img 协议
storage 事件跨窗同步主题
🧪
质量保障
Rust 单测(含真实 API 调用)
CLI 冒烟测试 --capture-once
npm run audit:ui 护栏
04核心闭环:一次捕获发生了什么
① 捕获 — Alt+Shift+S 触发 GDI 全屏位图(含分层窗口),编码 PNG 写入 MemexData/screenshots/,同时插入 memories 记录,状态标为「待整理」,UI 立刻提示「已捕获」
② 识别 — 后台线程调 WinRT OCR 提取文字;语言包缺失或识别失败时原图照常保留,只记录降级原因,不影响后续手动整理
③ 理解 — 把 OCR 文本交给 DeepSeek 做四件事:起标题、写摘要、打标签、归分类。四项分别调用,任一项失败其余继续,不会整体回滚
④ 归档 — 回写 memories / tags / memory_tags / collections,ai_status 置为 done,发系统通知;时间线上出现带「新捕获」标记的卡片
⑤ 回忆 — 任意时刻 Ctrl+K 打开命令面板,走关键词搜索或 AI 问答;问答上下文只给 8 条相关记忆摘要,回答带 [1][2] 引用,点击直接回到原始截图
这条链路上最容易出事的是 ③:模型返回的 JSON 经常带 ```json 包裹、前后废话、字段名漂移。于是写了一个 parse_json_loose——先剥围栏、再截取第一个完整 JSON 片段、最后逐字段兜底,实测未再因格式问题丢数据。
05数据模型
| 表 | 职责 | 设计要点 |
| memories | 记忆主体 | title / content / summary / type / source / ocr_text / ai_status |
| collections | 合集(含颜色) | sort_order 持久化,支持侧栏拖拽排序 |
| tags / memory_tags | 标签多对多 | 标签去重靠唯一约束 + 关联表 |
| attachments | 附件 | 与记忆一对多,文件落在数据目录 |
| ai_tasks | AI 任务记录 | 可观测每次处理的耗时与结果,失败可追溯 |
| search_history | 搜索历史 | 为「最近搜索」与后续排序实验留数据 |
| settings | 键值配置 | 含 API Key;优先级:应用设置 > 环境变量 |
06工程细节:降级与护栏
| 异常场景 | 实际行为 |
| API Key 未配置 / 错误 | AI 标记失败,记忆完整保存,UI 明确提示「AI 离线」 |
| 网络断开 | 本地记录、搜索、截图、合集全部可用 |
| OCR 失败(缺语言包) | 保留原图,ocr_text 留空并写日志,不阻塞入库 |
| 模型返回异常 JSON | parse_json_loose 三段容错:剥围栏 → 截取 JSON → 字段兜底 |
| AI 单项失败(如标签) | 其余项继续写入,不整体回滚 |
| 数据库错误 | 命令返回 Result,前端 toast 提示,进程不崩 |
| 截图失败 | 托盘与快捷键路径 emit capture-error,前端提示原因 |
设计系统同样有护栏:所有颜色/字号/间距只允许来自 src/ui/tokens.css,npm run audit:ui 会扫描页面里的裸色值与非标准字号(--strict 可在 CI 中失败);再加一个 ui-diff.py 做像素级回归,改完 UI 能一眼看出哪里动了。
07实测数据(本机 Windows 11)
应用启动与系统集成
启动无崩溃,托盘图标与全局快捷键注册成功,最小化到任务栏行为符合预期
SQLite 初始化
9 张表 + 5 个默认合集 + 8 项默认设置,首次启动即用
捕获链路
1440×900 全屏 PNG 落盘;WinRT OCR 识别 353 字中英混排文本
AI 真实调用
标题「WorkBuddy 任务助手开发与调试」、标签 5 个、分类「工作记录」全部正确落库
防编造验证
提问记忆库中不存在的内容时,明确回答「我没有在你的记忆中找到相关内容」,不硬编
08界面实拍
以下为应用在 Windows 11 下的真实运行截图(记忆内容为日常使用数据)。
合集视图 · 按主题与颜色组织记忆 · 每张卡显示条目数、最近更新与首条记忆预览,「卡片 / 列表」双视图切换
记忆详情 · 原始链接、AI 摘要、AI 分析与原图对照 09已知限制与后续
区域截图
已实现冻结屏幕 + 鼠标框选,多显示器暂只覆盖主屏
检索方式
相关记忆当前用标签交集计算,related_memories 接口已预留 embedding 扩展位
打包
NSIS 安装包需联网拉工具链;release 产物可直接免安装运行