背景
按住说话(PTT)模式下,上一轮还没结束(回复还在流式、语音还在播、或在等追问)时用户又按住说话开始下一轮,会出现三类问题:回复气泡消失后不再出现、音频状态被错误打断、追问把新一轮的状态强行改写。
已确认问题
_startTurn() 开头无条件清空 replyText,同时 fix(assistant): 按住说话点掉回复后,上一轮回复会重现并挡住语音条 #364 加的 request_id 轮次门控没有相应处理:新一轮换了 request_id 后,上一轮剩余的流式增量全部被当成"别人的"丢掉,replyText 永远停在被清空的那一刻——现象是"长回复气泡完全不出现",而语音不受影响照常播,回复越长越容易踩到。
同一类问题也出现在 voice.dialogue.question:没有任何轮次门控,上一轮被打断后晚到的追问会把已经推进到新一轮的 phase 强行掰回 asking。
voice.tts.start/voice.tts.end 同样没有门控(协议本身也不带 request_id,没法照搬同样的判断):上一轮的语音还在播时开始新一轮,两轮音频会同时播放;上一轮迟到的 tts.end 会把已经推进到新一轮的 phase 强行掰回 idle。
目标
再次按住说话应该等同于用户主动点掉气泡(dismissReply):文字立即清空、语音立即停播,然后干净地开始听新一段,不改变现有语音状态机和消息协议。
修复范围
_startTurn() 开始新一轮时清空 replyText 并停播上一轮语音,行为跟点掉气泡一致;voice.dialogue.reply 门控保持"只认当前轮",上一轮迟到的增量正常丢弃,不会把已经清空的气泡重新填回去。
voice.dialogue.question 补上跟回复一样的轮次门控。
新一轮开始(或点掉气泡)时,如果上一轮语音还没播完,主动停止播放并把这条音频的 id 记进一个集合;它迟到的 tts.end 到达时只从集合里摘除、不再碰 phase——用集合是因为连续按多次时,只记一个 id 会被后一次覆盖掉,导致更早放弃的那条迟到收尾消息漏判。
非目标
不改变语音传输协议、request_id 语义,不给 voice.tts.start/voice.tts.end 加 request_id 字段(那需要改后端协议,这次范围只在前端)。
不改动连续通话模式(本身不受影响,它的门控设计不一样)。
已知局限(不在这次修复范围内)
voice.tts.start/voice.tts.end 协议本身不带任何轮次标识,如果新一轮开始的时机恰好卡在服务端"上一轮 tts.start 刚发出、取消还没生效"这个协程调度间隙内(毫秒级窗口,人手操作基本碰不到,网络抖动/服务端负载异常时概率会升高),这条迟到的 tts.start 依然会被当成合法消息接受,可能让 phase 卡在 speaking 直到新一轮自己的消息把它带走。要彻底堵上需要后端配合,本次不在范围。
验收标准
背景
按住说话(PTT)模式下,上一轮还没结束(回复还在流式、语音还在播、或在等追问)时用户又按住说话开始下一轮,会出现三类问题:回复气泡消失后不再出现、音频状态被错误打断、追问把新一轮的状态强行改写。
已确认问题
_startTurn()开头无条件清空replyText,同时 fix(assistant): 按住说话点掉回复后,上一轮回复会重现并挡住语音条 #364 加的 request_id 轮次门控没有相应处理:新一轮换了 request_id 后,上一轮剩余的流式增量全部被当成"别人的"丢掉,replyText永远停在被清空的那一刻——现象是"长回复气泡完全不出现",而语音不受影响照常播,回复越长越容易踩到。voice.dialogue.question:没有任何轮次门控,上一轮被打断后晚到的追问会把已经推进到新一轮的 phase 强行掰回asking。voice.tts.start/voice.tts.end同样没有门控(协议本身也不带 request_id,没法照搬同样的判断):上一轮的语音还在播时开始新一轮,两轮音频会同时播放;上一轮迟到的tts.end会把已经推进到新一轮的 phase 强行掰回idle。目标
再次按住说话应该等同于用户主动点掉气泡(
dismissReply):文字立即清空、语音立即停播,然后干净地开始听新一段,不改变现有语音状态机和消息协议。修复范围
_startTurn()开始新一轮时清空replyText并停播上一轮语音,行为跟点掉气泡一致;voice.dialogue.reply门控保持"只认当前轮",上一轮迟到的增量正常丢弃,不会把已经清空的气泡重新填回去。voice.dialogue.question补上跟回复一样的轮次门控。tts.end到达时只从集合里摘除、不再碰 phase——用集合是因为连续按多次时,只记一个 id 会被后一次覆盖掉,导致更早放弃的那条迟到收尾消息漏判。非目标
voice.tts.start/voice.tts.end加 request_id 字段(那需要改后端协议,这次范围只在前端)。已知局限(不在这次修复范围内)
voice.tts.start/voice.tts.end协议本身不带任何轮次标识,如果新一轮开始的时机恰好卡在服务端"上一轮 tts.start 刚发出、取消还没生效"这个协程调度间隙内(毫秒级窗口,人手操作基本碰不到,网络抖动/服务端负载异常时概率会升高),这条迟到的tts.start依然会被当成合法消息接受,可能让 phase 卡在speaking直到新一轮自己的消息把它带走。要彻底堵上需要后端配合,本次不在范围。验收标准