机器改和人改会打架
用户手动改对的字,被后台自动精修改了回去,这种 bug 一次就够让人卸载。
转写结果既能自动优化又能手动编辑,冲突了算谁的?
算用户的。畅译给每个段落记了一个状态,标明这段现在是识别原文、是机器精修过的、还是用户手动改过的。自动流程只允许覆盖前两种,碰到用户改过的段落一律跳过。
- 段落状态只有三种,自动流程绝不修改这个状态字段
- 编辑提交不靠确认按钮,焦点离开就存,六条退出路径每条都要存
- 校对的绝大多数动作是确认没问题,不是编辑,界面按这个比例配重
- 有个明显该做的识别优化被砍了,因为下游会把上游的成果覆盖掉
先把权限说清楚
一段转写文字在畅译里可能被三种力量修改:识别器出的原文,后台精修重新识别后的覆盖,还有用户自己动手改。前两种是自动的,随时可能在用户没注意的时候发生。
所以每个段落带一个状态,标明它当前的来源是哪一种。精修流程在写回结果之前先看这个状态,是用户改过的就直接跳过,连碰都不碰。这条规则在精修那段代码里是作为不变式写死的:这个流程绝不修改状态字段本身,它只有读的权限。
听起来很基础,但如果没有这个字段,用户在等待精修完成的那几十秒里做的任何修改都会被静默覆盖。他不会收到任何提示,只会在某次翻回去看的时候发现自己改的字没了。
不要确认按钮,但每条退路都得存
段落编辑没有保存按钮,焦点离开编辑区就提交。手机上多一个按钮就多一次点击,而校对是个高频重复动作。
代价是退出路径变多了,而且每一条都得记得存。自动播放推进到下一段算一条,用户点了屏幕别的地方算一条,点了修正建议算一条,切换段落、退出校对模式、App 切到后台各算一条。六条里漏掉任何一条,用户就会在那个特定操作下丢字,而且是无声地丢。
这类 bug 的麻烦在于复现条件很窄。用户报上来通常只说了一句我改的字没保存,你得把六条路径一条条试过去。所以后来在设计文档里把这六条列成了硬性要求,测试也按这个清单写。
校对的大多数动作不是编辑
做校对功能时最有用的一个判断是:用户在校对时做得最多的动作是确认这段没问题然后往下走,真正动手改的是少数。
这个比例决定了界面怎么排。如果按编辑是主要动作来设计,就会把编辑入口放在最顺手的地方,结果用户每次都要绕过它去点下一段。反过来,把往下走做成自动的、编辑做成打断式的分支,一份全对的稿子用户可以一次都不点屏幕,听完就确认了。
另一个具体到手指的判断是免选中。手机上要修一个词,长按然后拖那两个选择手柄,是整个交互里最烦的动作之一。所以常见错词的修正做成了直接列在文本下方的候选,点一下就替换,全程不需要选中任何文字。真要手动改才落到键盘,那时候双击选词也比长按拖拽快。
一个看起来该做、最后砍掉的优化
做纠错词典时有个很自然的想法:既然用户已经告诉我们哪些是专有名词了,为什么不把它们作为热词注入识别器,让它一开始就别识错?
查下去才发现这条路走不通。热词只对其中一类识别器结构生效,而录音的最终文本并不是那条链路产出的,实时结果会被离线引擎重新识别一遍覆盖掉,导入路径更是从头到尾都不经过那条链路。热词能让屏幕上实时滚动的粗稿变准,但精修一回填就又错回去了。用户看到的最终结果没有任何改善。
顺着这个逻辑,另一个想法也被否了:用识别器给出的逐字置信度来高亮可疑的词。同样的问题,那个概率值只有一类识别器结构会输出,覆盖不到最终稿。
两条路都堵死之后,剩下的结论反而清楚了:能同时作用于所有识别引擎的,只有文本层的后处理。所以词典做成了纯文本的匹配替换,不依赖任何模型的内部输出。这个决定看起来保守,但它是唯一一个在三条引擎上都成立的方案。
做了一个独立界面,然后删了
校对模式一开始是个独立页面,有自己的控制条、自己的播放逻辑、自己的一套交互。设计文档里它是那个版本的核心。
做出来之后的判断是不该有这个页面。用户已经在逐字稿页面里看稿子了,为了校对再跳进另一个界面,等于把一件连续的事切成两段。后来把倍速和逐段停靠搬到了逐字稿页面的播放器上,把修正候选做成了行内的候选块,独立页面整个删掉。
删的时候留下了一条约束:文字上的高亮层只做视觉标记,绝不接收任何点击。已自动纠正的词有淡绿底,待确认的建议有橙点下划线,但它们都不可点。因为单击段落进入编辑、光标落在点击位置,这个行为是整个页面的基础手感,如果高亮层截走了点击,用户会发现有些地方点了能编辑、有些地方点了没反应。