MetaNotes 是一款 AI 会议助手,它加入 Google Meet、Microsoft Teams 和 Zoom 通话,生成转录稿、行动项和对话摘要。这是一款对外发布的产品,在公司内部它替换掉了 Fireflies 和 tl;dv:这两款工具都把关键功能锁在更高的付费档位里,录音存储成本也在上涨,已经不再适合团队的需求。我是这个项目唯一的前端工程师,在大约 5 个月的开发周期里,从功能范围、部署到产品决策都由我全程负责。

我做了什么
- 转录界面: 构建了带时间戳同步高亮的转录查看器。当前高亮行会跟随视频播放位置移动,点击任意一行都能让播放器跳转到那一刻。
- 会议智能仪表盘: 开发了一键 AI 摘要、行动项跟踪、情绪指标和主题标签的交互视图。
- 前端架构: 从最初构想到上线,我都是这个项目唯一的前端工程师。功能范围、组件架构、状态管理和部署都由我负责。后端管道是异步的,靠 webhook 和定时 cron 任务驱动:转录稿和摘要会在处理完成后一次性取回,而不是实时推送。
为什么它有意义
MetaNotes 作为对外 SaaS 产品发布,同时也被 200 多名 Testlify 员工 用于会议录制和客户通话,替换掉了 Fireflies 和 tl;dv。这为公司省下了此前付给这些供应商的 每年 2.4 万美元运营成本。
主导了这个项目,从头到尾全权负责
我作为前端工程师主导了 MetaNotes,负责的范围也不止界面本身。功能范围、部署和日常产品决策同样由我拍板。从第一次提交到内部上线,大约花了 5 个月。
为什么转录稿是会后才出现,而不是会议中
MetaNotes 不会在机器人参会期间实时推送转录文本。录制过程中,界面只显示一个状态标签(Capturing/Paused),配上暂停、恢复、停止的控制按钮。数据接入走的是 Attendee 的 webhook(这是一个会加入通话并采集媒体的会议机器人 API),再配合一组定时 cron 任务:转录和通过 OpenAI 做的摘要都是在通话结束后异步完成的,前端只需一次服务器操作调用,就能拿到最终的转录稿、摘要和行动项。
考虑到这是一个五个月的开发周期,还要同时兼顾功能范围、部署和产品决策,实时推流并不值得付出这个代价。它意味着要维护一个 WebSocket 连接、处理不完整转录的一致性问题,还要处理断线重连,而这一切换来的只是一个用户其实并不需要的功能:看着转录稿实时逐字生成。会后异步接入能提供完全相同的转录稿、摘要和行动项,活动部件却少得多,所以最终上线的就是这个方案。
时间戳同步的转录高亮
视频播放器和转录列表之间是一套简单的双向绑定:一个 timeupdate 监听器跟踪播放位置,对转录数组做一次扫描找到当前活跃的那一行并高亮它,点击任意一行都会让播放器跳转到那个时间戳。在典型的会议时长下,这完全不需要虚拟化列表,也不需要专门的同步库。用上其中任何一个,都是在解决一个数据量还远远够不上的问题。
数据模型与规模
- 每场对话的转录稿都以单个 JSON 大对象的形式存储,在数据库层面没有分块,也没有分页。
- 对话列表和日历列表按每页 10 到 20 条做分页。
- 调用 OpenAI 做摘要时,每次请求的输出限制在 800 个 token 以内。
按目前的会议量和时长,这些方案都运转良好。如果哪一项的规模涨了一个数量级,我最先会重新考虑的就是单大对象存储的转录字段和线性扫描式的高亮查找。这些是心里有数的取舍,不是没意识到的隐患。
自研还是采购
Testlify 此前一直在为 Fireflies 和 tl;dv 付费,但这两款工具都把关键功能锁在更贵的档位里,而且随着录制会议存储量的增长,费用也越来越高。自己开发去掉了这两个限制;在超过 200 名员工把它用于会议录制和客户通话之后,这为公司省下了此前每年花在这些供应商身上的 2.4 万美元。
技术栈
Next.js 16 / React 19 App Router、TypeScript,客户端状态用 Zustand(带 localStorage 持久化),鉴权用 Clerk,界面用 Radix 加 Tailwind,录制播放器用 Vidstack,Zoom/Meet/Teams 的会议接入则用 Attendee 这个会议机器人 API。