早上醒来,群里多了38条消息。你一条条往上翻,跳过"哈哈""""好的",翻了半天,只为找到有人问你的那一句。Sloth Recap 想解决的,就是这个很懒的时刻。
![]()
用法很直接:把聊天记录导出丢进去(WhatsApp 的"导出聊天"生成的 .txt 或 .zip,也可以直接粘贴文本),输入一次自己的名字,按下" Catch me up",你会拿到三样东西:
- ☕ 一段2行的摘要,讲你上次发言之后发生了什么;
- Needs you:只列出提到你、或者向你提问的消息;
- Topics:其余消息按所属话题归到对应的那一行下面,想看细节再展开。
作者给自己定了一条设计规则:Sloth Recap 只引用真实消息,只做挑选,从不生成新文本,所以它编不出一个没人提过的计划。摘要里写"Technician comes Wednesday between 9 and 12",就意味着真的有人这么说过。
在内置的"Flat 3B"合租群样本里,38条新消息变成了2行摘要,外加3条真正需要本人回复的消息:给技术员开门、带提拉米苏、确认周三。按每分钟200词的阅读速度算,读完38条大约要1分27秒,还没算上滑动屏幕的时间;读摘要加"Needs you"列表大约18秒。
整个应用是纯 HTML、CSS 和 JavaScript,没有构建步骤,没有后端,免费托管在 GitHub Pages 上。处理流程是一条流水线:解析聊天导出 → 取出"我最后一条消息之后"的内容 → 丢掉"ok/lol/"这类填充词 → 用 EmbeddingGemma 生成向量(跑在 Web Worker 里)→ 挑出关键消息 → 把其余消息分组 → 输出2行摘要、"Needs you"和话题。
模型用的是开放权重的 EmbeddingGemma,通过 transformers.js 加载 onnx-community/embeddinggemma-300m-ONNX。浏览器支持 WebGPU 就跑在 WebGPU 上,否则退回 CPU(WASM)。每条新消息都会变成一个向量,用的是 EmbeddingGemma 的句子相似度任务提示。
作者在调优里提到几个具体做法。第一是中心化向量:小嵌入模型眼里所有聊天消息都"差不多像",在测试聊天里,不相关消息对的余弦相似度平均约0.58,同话题消息对约0.63,差距几乎为零;减去整个聊天的平均向量后,两组分别变成约-0.07和+0.06,差距扩大了一倍多,话题更容易区分。第二是借用邻居上下文:"Work for me"或"Ma dai "单独看没有意义,所以每条消息会带上前后消息向量的一部分。第三是挑关键消息:一条消息如果被很多其他消息谈论、包含具体事实(时间、日期、价格)、且比较新,得分就高;挑选时逐条进行,并对与已选消息过于相似的消息做惩罚(经典的 MMR 技巧),保证2行摘要讲的是两件不同的事。剩下的消息归到离它最近的关键消息下面,有人向本人提问的话题排在最前。
"Needs you"列表刻意不用 AI,而是简单规则:把名字作为完整单词或 @名字 匹配,加上英语和意大利语的疑问句式。作者的理由是,"有没有人问我什么"这件事需要100%可预测。
如果模型加载失败,或者用户手动关掉,同一条流水线会跑在 TF-IDF 关键词向量上。作者在无头 Chrome 里屏蔽 Hugging Face 做了测试:应用显示"Local AI: failed to load",仍然用简单模式产出了摘要。
在普通笔记本 CPU 上跑快,是作者自认收获最大的部分。在只有 CPU 的测试机(无头 Chrome,38条消息)上:4位"no_gather"模型单线程约26秒;8位模型单线程约8秒;8位模型4线程约3秒。4位导出版本下载体积更小,但在 WASM 上比8位文件慢约3倍。作者说不清具体是哪个算子慢,但测量结果很明确,所以应用在 CPU 上用8位,只在 WebGPU 上用4位。多线程需要"跨源隔离"的页面,GitHub Pages 设不了这些响应头,于是作者加了一个约20行的 service worker(sw.js),给站点自己的文件补上这些头。首次加载之后,模型就能用上多个 CPU 线程(测试机上是4个)。
测试方面:npm test 用 node:test 跑了11个单元测试,覆盖 WhatsApp 安卓/iPhone 格式、美式/欧式日期、多行消息、填充词、"needs you"、降级摘要、读取 .zip。一个 Puppeteer 脚本在无头 Chrome 里打开线上 GitHub Pages 站点,下载模型、用 AI 跑两个样本、检查"needs you"数量和复制按钮、加载一个 .zip 导出、再跑无 AI 模式。文章里的截图和 GIF 都来自线上站点。eval/eval-model.mjs 在 Node 里对5个小聊天跑真实模型,其中2个是演示聊天,3个是留出的,有一个是中英混合。
作者也坦白了局限:这是自己写的小数据集,只能算 sanity check,不是基准测试。两种模式在这些聊天上都能挑出不错的句子;模型的优势体现在分组上,尤其是短回复和切换语言的消息("The heating in my room is not working" / "Anche da me il termosifone è freddo")。
为什么开放权重对这个项目是核心?作者给了几条理由。群聊是私密的,里面全是别人的消息、地址、计划和玩笑,未经同意把朋友的消息贴进云端 API 对他们不公平;EmbeddingGemma 跑在浏览器标签页里,聊天记录不离开设备,没有服务器能收到它。运行成本为零:没有 API key、没有按条计费、没有账号,模型跑在自己的 CPU/GPU 上,站点是 GitHub Pages 上的静态页。没有可依赖的 AI 服务:首次访问后模型来自浏览器缓存,作者验证过,把 Hugging Face 完全屏蔽,第二次访问仍然能跑 AI,没人能限流、改价或关停它。权重、ONNX 导出和运行时都是开放的,作者才能在自己的机器上对比4位和8位文件、测出其中一个慢3倍然后换掉。模型名只是 src/model-worker.js 里的一行,下个月出了更好的小型开放嵌入模型,一个下午就能试,而 TF-IDF 降级意味着应用从不依赖任何单一模型。
作者在文末做了 AI 披露:他是来自意大利的初学者,这个项目在 AI 编程助手的大量帮助下完成,包括代码、测试、调试和测量;想法、"绝不生成新文本"的规则、哪些用 AI 哪些用规则的取舍、以及测试,都是他自己的,并且确认自己理解每一个文件。
演示站上有两个样本聊天( 英国合租、⚽ 意大利足球群),点进去再按 Catch me up 即可。首次 AI 运行会下载一次模型(WebGPU 约175 MB,CPU 约310 MB),之后浏览器会缓存。不想下载就取消勾选"Use local AI",立刻得到简单的关键词模式。模型是多语言的,意大利语和混合语言聊天也能用。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.