我把 Telegram 和 Web 的账号打通了
跨平台身份统一的概念插画
两边的账号其实是断开的
v0.1.4 做了跨平台消息同步,Telegram 上聊的内容,Web 端也能看到。但有个更根本的问题一直没解决:两边的账号是断开的。
用户在 Telegram 是一个 ID,在 Web 是另一个 Supabase 账号。消息能跨平台读,但在身份这一层,系统眼里这就是两个人,关系进化的评分、记忆关联、用户偏好,全都没法真正打通。所以 v0.1.5 最主要的一件事,就是把 Telegram 和 Web 的身份合成一个。
绑定就是一个 6 位令牌的事
方案我想做到足够简单,用户不该为了绑个账号去学任何新概念。具体流程是这样的:在 Telegram 里输 /link,bot 生成一个 6 位令牌,复制到 Web 端输进去,就完事了,整个过程大概 30 秒。
一个 6 位令牌把断开的 Telegram 账号和 Web 账号焊成同一个人
实现上有几个细节值得讲一下:
- 字符集把容易认错的全去掉了。令牌只用
ABCDEFGHJKLMNPQRSTUVWXYZ23456789,I、O、0、1 都不要。用户在屏幕上分不清I和1,这种事不该让用户去猜。 - 15 分钟过期。令牌存在
account_link_tokens表里,带expires_at字段,过期了重新/link生成一个就行。 - 关联本身就是一条 SQL。令牌验证通过之后,把 Web 端的
supabase_user_id写进 users 表里对应的 Telegram 记录。不用迁移数据,也不用合并账号,因为 Telegram 的 user 记录本来就是主记录,Web 端相当于挂上去。 - 双向都锁死。一个 Telegram 账号不能绑两个 Web 账号,反过来也不行,数据库层面直接加唯一约束。
- 幂等。同一个令牌用两次不报错,返回一个「已关联」的提示;同一对账号重复绑定,也是同样的处理。
反正原则就一条:绑定是个基础操作,用户不该在这里花任何脑子,上面这些细节都是冲着这一条去的。
关系模式跟着角色走,不跟 bot 走
v0.1.4 在 Web 管理后台加了关系进化的开关,static 或者 evolving,但 Telegram 端的 onboarding 完全不知道这回事。v0.1.5 把这个选择放进了 /start 流程:第一次进 bot,除了选语言和昵称,现在还会让你选关系模式,「固定关系」还是「自然发展」。
这里改动最大的一点是模式挂在哪一层。之前 BOT_RELATIONSHIP_MODES 是 bot 级别的环境变量,一个 bot 下所有用户看到的是同一个模式;现在每个 agent 记录有自己的 relationship_mode,同一个 bot 下,不同用户可以选不同的模式。向下兼容也留了:agent 记录里没有这个字段,就回退到环境变量,老用户不受影响。
一个角色只让绑一次
Mio 目前跑着好几个 Telegram bot,一个角色一个。这里有个坑:用户可能跑到不同的 bot 上,跟同一个角色重复绑定。这不只是浪费的问题,重复绑定意味着对话历史、关系评分、记忆这些东西全都各存了一份,互相不通,用户以为在跟同一个 Mio 聊,实际上是在跟两个互相不知道对方的实例聊。
解法:/start 和 /reonboard 现在会查这个用户在所有 bot 上的已有绑定,某个角色已经绑过,就直接标「已绑定」,不能再选。哪些角色在用,一眼就能看到,不会误操作。
为什么要做原生 App
这个版本还把原生 App 做出来了。网页端功能上确实什么都能做,这个没什么好争的,但对陪伴类产品,光有功能不够,用户得能感觉到 TA 一直在。
推送通知是最直接的例子。Mio 发一条主动消息,用户得现在就看到,在锁屏上,像朋友发微信一样,而不是「下次打开浏览器的时候」才看到。推送这个事网页端做不到,得靠原生 App。反正没有推送的话,主动消息发了用户也看不到,基本等于没发。
然后是打开的成本。App 点开直接进对话,没有地址栏,不用等加载,也不用重新登录;本地缓存让聊天记录秒开,网络不好也能先看到之前的对话。一天要打开好几次的东西,这些体验很大程度上决定用户会不会把 Mio 当成一个一直在的人。
技术栈这边:Expo SDK 55 + React Native,深色主题底色 #0D0D0B,强调色 #8B7BF4。五个页面:登录、聊天、发现、个人中心、onboarding。聊天支持视频预览、全屏图片、语音录制(脉冲红点加计时器动画)、typing indicator。认证走 Supabase,token 存在 expo-secure-store。i18n 从第一天就是中英双语。
主动消息的两个改进
主动消息是 Mio 的核心体验之一,TA 会在合适的时间主动来找你聊。v0.1.5 改了两个地方。
角色也有自己的时区
之前主动消息只看用户的时区。但角色自己也有时区,TA 的设定要是住在东京,TA 就应该知道自己那边几点。现在提示词里两边的时间都给:「你那边现在是早上 9 点,对方那边是晚上 11 点」,角色的时区从 persona-config.json 里读。效果就是 Mio 能说出「你那边应该挺晚了吧,早点休息」这种话,而不是在用户深夜的时候发一条「早上好」。
语气跟着关系阶段走
主动消息的语气现在跟关系阶段挂钩,四个阶段各有一套策略:
- 刚认识:礼貌、克制,撒娇和亲密称呼直接禁掉
- 好朋友:轻松、可以开玩笑,但有分寸
- 暧昧:多一些试探和暗示,语气柔和
- 情侣:亲密撒娇,自然地用昵称
这样主动消息就不会再是千篇一律的问候,同一个时间点,刚认识的用户和情侣阶段的用户收到的是完全不同的两条消息,各自对得上当前的关系状态。
主动消息的语气跟着四个关系阶段逐级升温,同一时间不同阶段收到完全不同的话
数据库改了三处
三个 schema 改动:
account_link_tokens表:存 6 位令牌、Telegram user ID、过期时间、使用状态users.supabase_user_id:新字段,存关联的 Web 端 Supabase 用户 ID,带唯一约束agents.user_nickname:新字段,存用户在这个 agent 上的昵称,之前这个昵称只在 onboarding 里临时传一下,没有落库
接下来
账号关联把身份这一层理顺了。下一个版本想做的是让 Mio 能理解用户分享的链接和媒体,不只是处理纯文本。
前面几个版本:v0.1.0 做语音,v0.1.2 做多人设,v0.1.3 做防注入,v0.1.4 做关系进化,这个版本做身份统一。Mio 到底想做成什么,见 Mio 宣言。

