@@ -0,0 +1,220 @@
<!DOCTYPE html>
< html lang = "zh-CN" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > dudu — 桌面端联调验收报告(#12 / #1C)< / title >
< style >
: root {
--accent : #4F6EF7 ;
--accent-text : #3D58DB ;
--accent-soft : #EEF1FE ;
--bg : #F6F7FA ;
--card : #FFFFFF ;
--text-1 : #1B1E26 ;
--text-2 : #5A6072 ;
--text-3 : #A6ABB8 ;
--border-1 : #E4E6EB ;
--positive : #16A34A ;
--warning : #E8890C ;
--danger : #DC2626 ;
--code-bg : #1B1E26 ;
--code-text : #D6DCEA ;
}
* { box-sizing : border-box ; }
body {
margin : 0 ;
font-family : - apple-system , BlinkMacSystemFont , "PingFang SC" , "Microsoft YaHei" , "Segoe UI" , sans-serif ;
background : var ( - - bg ) ; color : var ( - - text -1 ) ; line-height : 1.75 ;
}
. container { max-width : 980 px ; margin : 0 auto ; padding : 0 24 px 80 px ; }
header . hero { background : var ( - - accent ) ; color : #fff ; padding : 48 px 24 px 40 px ; text-align : center ; }
header . hero h1 { margin : 0 0 6 px ; font-size : 30 px ; }
header . hero p { margin : 0 ; opacity : .85 ; font-size : 15 px ; }
. chips { margin-top : 16 px ; }
. chip { display : inline-block ; padding : 3 px 14 px ; margin : 3 px ; background : rgba ( 255 , 255 , 255 , .16 ) ; border-radius : 999 px ; font-size : 13 px ; }
section . card {
background : var ( - - card ) ; border : 1 px solid var ( - - border -1 ) ; border-radius : 14 px ;
padding : 28 px 32 px ; margin-top : 24 px ; box-shadow : 0 1 px 3 px rgba ( 17 , 19 , 26 , .05 ) ;
}
h2 { font-size : 21 px ; margin : 0 0 14 px ; padding-bottom : 8 px ; border-bottom : 2 px solid var ( - - accent ) ; display : inline-block ; }
h3 { font-size : 16 px ; margin : 22 px 0 8 px ; color : var ( - - accent - text ) ; }
table { width : 100 % ; border-collapse : collapse ; margin : 10 px 0 ; font-size : 13.5 px ; }
th , td { border : 1 px solid var ( - - border -1 ) ; padding : 7 px 11 px ; text-align : left ; vertical-align : top ; }
th { background : #F0F1F4 ; font-weight : 600 ; }
tr : nth-child ( even ) td { background : #FCFCFD ; }
pre . code {
background : var ( - - code - bg ) ; color : var ( - - code - text ) ; padding : 16 px 18 px ; border-radius : 10 px ;
overflow-x : auto ; font-family : "SF Mono" , Menlo , Consolas , monospace ; font-size : 12.5 px ; line-height : 1.55 ;
}
code . inline { background : var ( - - accent - soft ) ; color : var ( - - accent - text ) ; padding : 1 px 6 px ; border-radius : 4 px ; font-family : "SF Mono" , Menlo , monospace ; font-size : 12.5 px ; }
. callout { border-left : 4 px solid var ( - - accent ) ; background : var ( - - accent - soft ) ; padding : 11 px 16 px ; border-radius : 0 8 px 8 px 0 ; margin : 12 px 0 ; font-size : 13.5 px ; }
. callout . warn { border-color : var ( - - warning ) ; background : #FCEFD7 ; }
. tag { display : inline-block ; font-size : 12 px ; padding : 1 px 10 px ; border-radius : 999 px ; margin-right : 6 px ; font-weight : 600 ; }
. tag . green { background : #DCFCE7 ; color : var ( - - positive ) ; }
. tag . orange { background : #FCEFD7 ; color : var ( - - warning ) ; }
. tag . red { background : #FEE2E2 ; color : var ( - - danger ) ; }
. tag . blue { background : var ( - - accent - soft ) ; color : var ( - - accent - text ) ; }
ul , ol { padding-left : 22 px ; }
li { margin : 3 px 0 ; }
footer { text-align : center ; color : var ( - - text -3 ) ; font-size : 13 px ; margin-top : 44 px ; }
< / style >
< / head >
< body >
< header class = "hero" >
< h1 > 桌面端联调验收报告< / h1 >
< p > TODO #12(联调与验收)+ #1C( macOS 注入与浮层)· 2026-07-10< / p >
< div class = "chips" >
< span class = "chip" > macOS 实测< / span >
< span class = "chip" > gummy 真实 provider< / span >
< span class = "chip" > 无头网关压测< / span >
< / div >
< / header >
< div class = "container" >
< section class = "card" >
< h2 > 一、结论速览< / h2 >
< table >
< tr > < th > 验收项< / th > < th > 结果< / th > < th > 说明< / th > < / tr >
< tr > < td > 12A mac 全链路(快捷键→采集→流式→浮层→注入)< / td > < td > < span class = "tag green" > 通过< / span > < / td > < td > 真实 App + 模拟快捷键,mock 文本成功注入 TextEdit,计费准确< / td > < / tr >
< tr > < td > 12A Windows 全链路< / td > < td > < span class = "tag red" > 未执行< / span > < / td > < td > Windows 机(ssh windows)缺 Rust/Node 工具链,需先装环境< / td > < / tr >
< tr > < td > 12B release_to_commit < 300ms< / td > < td > < span class = "tag orange" > 部分达标< / span > < / td > < td > P50 261ms ✅ / P95 918ms ❌(受两轮 provider 异常拖累)< / td > < / tr >
< tr > < td > 12B first_partial P95 < 500ms< / td > < td > < span class = "tag red" > 未达标< / span > < / td > < td > P50 845ms / P95 3870ms——gummy partial 下发节奏本身 ~750-850ms,目标需重新校准< / td > < / tr >
< tr > < td > 12C 断连重建< / td > < td > < span class = "tag green" > 通过< / span > < / td > < td > 服务端重启后桌面端数秒内自动重连(18E 生效)< / td > < / tr >
< tr > < td > 12C SESSION_LIMIT( 3 分钟截断)< / td > < td > < span class = "tag green" > 通过< / span > < / td > < td > 198s 推流在 180s 准时截断:先定稿下发全部 final,再发 SESSION_LIMIT< / td > < / tr >
< tr > < td > 12C RATE_LIMITED( 30 次/30 分钟)< / td > < td > < span class = "tag green" > 通过< / span > < / td > < td > 第 31 个会话被精确拒绝,前 30 个正常< / td > < / tr >
< tr > < td > 12C QUOTA_EXCEEDED(试用/余额用尽)< / td > < td > < span class = "tag green" > 通过< / span > < / td > < td > 扣穿当句不掐断、final 不丢;再次 start 预检拒绝(帧序备注见 §五)< / td > < / tr >
< tr > < td > 1C 浮层不抢焦点 + 注入< / td > < td > < span class = "tag green" > TextEdit 通过< / span > < / td > < td > 会话期间焦点保持在目标 App,注入成功;微信/浏览器/IDE 三目标待人工点验< / td > < / tr >
< tr > < td > 商业链路(登录/购买/入账)< / td > < td > < span class = "tag green" > 通过< / span > < / td > < td > mock 扫码登录、下单、回调入账、回调重放幂等、订单状态全部正确< / td > < / tr >
< / table >
< div class = "callout warn" >
< b > 联调中发现并已修复:< / b > overlay/tray 为 < code class = "inline" > transparent: true< / code > 窗口,但未启用
< code class = "inline" > macOSPrivateApi< / code > ——macOS 上 WKWebView 透明必须开启该项,否则浮层带不透明底色。
本次已在 < code class = "inline" > tauri.conf.json< / code > 加 < code class = "inline" > "macOSPrivateApi": true< / code > 、
< code class = "inline" > Cargo.toml< / code > 加 < code class = "inline" > macos-private-api< / code > feature( cargo check 通过),
< b > 视觉效果需真机目测确认< / b > 。
< / div >
< / section >
< section class = "card" >
< h2 > 二、测试环境与方法< / h2 >
< table >
< tr > < th > 项< / th > < th > 值< / th > < / tr >
< tr > < td > 服务端< / td > < td > 本机 < code class = "inline" > go run ./cmd/server< / code > ( :8080) + docker compose pg/redis;先 mock 后切 < b > gummy 真实 provider< / b > (key 经 rbw 管道注入环境,未落盘)< / td > < / tr >
< tr > < td > 桌面端< / td > < td > < code class = "inline" > npm run tauri dev< / code > 调试构建,settings.json 预置 mock 登录 token 指向 localhost< / td > < / tr >
< tr > < td > 快捷键模拟< / td > < td > osascript System Events 按住/松开 Cmd+Shift+Space( 3s) < / td > < / tr >
< tr > < td > 延迟/异常压测< / td > < td > 自制无头 WS 客户端 < code class = "inline" > wslat< / code > ( Go + gorilla),按协议 100ms/3200B 实时推流真实中文语音(macOS say 合成 8.6s / 1s / 198s 三种)走 < code class = "inline" > /v1/asr/stream< / code > 真实网关路径< / td > < / tr >
< tr > < td > 测试账号< / td > < td > mock 微信登录建两个 trial 用户;用户一经 mock 支付购入 pack_100( 6000s 余额)< / td > < / tr >
< / table >
< h3 > 延迟指标口径< / h3 >
< ul >
< li > < b > first_partial< / b > :首帧音频发出 → 收到首个 partial(客户端视角,含网关中继与 provider 全链路)< / li >
< li > < b > release_to_commit< / b > :发出 stop → 收到定稿 final(对应桌面端「松开→注入」的服务端部分)< / li >
< / ul >
< / section >
< section class = "card" >
< h2 > 三、12A 全链路(mac) < / h2 >
< ol >
< li > mock 扫码登录:< code class = "inline" > POST /v1/auth/qr< / code > → 手动打回调(模拟扫码)→ 轮询 confirmed 拿 JWT ✅< / li >
< li > App 启动即与服务端建立 WS 预连接 + REST 轮询(lsof 确认 4 条连接)✅< / li >
< li > TextEdit 聚焦 → 模拟按住快捷键 3s → 松开 → mock provider 脚本文本「帮我把这份周报整理一下,重点突出本周的进展。」完整注入 TextEdit ✅< / li >
< li > 会话期间前台应用保持为目标 App(浮层 < code class = "inline" > focusable:false< / code > 生效,即 1C 验证点)✅< / li >
< li > 计费:3s 会话后 < code class = "inline" > /v1/me< / code > 的 trial_used_today 精确 +3 ✅< / li >
< / ol >
< div class = "callout" > Windows 侧同方法可复用:装好 rustup + Node + VS Build Tools 后 clone 仓库 < code class = "inline" > npm i & & npm run tauri dev< / code > ,注入走 SendInput( 10F)。< / div >
< / section >
< section class = "card" >
< h2 > 四、12B 延迟基线(gummy 真实 provider, 10 轮 × 8.6s) < / h2 >
< table >
< tr > < th > 指标< / th > < th > P50< / th > < th > P95< / th > < th > 目标< / th > < th > 判定< / th > < / tr >
< tr > < td > first_partial< / td > < td > 845ms< / td > < td > 3870ms< / td > < td > P95 < 500ms< / td > < td > < span class = "tag red" > 未达标< / span > < / td > < / tr >
< tr > < td > release_to_commit< / td > < td > 261ms< / td > < td > 918ms< / td > < td > < 300ms< / td > < td > < span class = "tag orange" > P50 达标 / P95 超< / span > < / td > < / tr >
< / table >
< ul >
< li > 10 轮 final 文本完全一致且正确(含标点/数字 ITN:「今天下午3点,我们开个短会,…」),识别质量稳定< / li >
< li > 8/10 轮 first_partial 集中在 840-944ms——与 gummy partial 下发节奏(约 0.75-0.85s/个)一致,< b > 首包时延已贴近 provider 下限,非网关开销< / b > < / li >
< li > 2/10 轮异常(3302/3870ms, release 同步劣化到 592/918ms),服务端无任何告警日志——疑似 DashScope 会话建立耗时波动< / li >
< / ul >
< h3 > 超短句补充数据(RATE 测试顺带产出,1s 语音 × 30 轮)< / h3 >
< ul >
< li > release_to_commit < b > P50 = 2192ms / P95 = 3732ms< / b > ——远高于长句的 261ms< / li >
< li > 原因:短句常在 provider 尚未出 partial 时就 stop( 30 轮中 16 轮 0 partial),定稿要等 provider 收尾 flush< / li >
< li > 体验含义:说「好的」这类两三字短语,松开后约 2 秒才上屏,是当前链路最明显的体验短板< / li >
< / ul >
< h3 > 建议(已入 backlog) < / h3 >
< ul >
< li > first_partial P95< 500ms 目标与 gummy 实际下发节奏矛盾,建议重校准为「P50≤900ms / P95≤1500ms」或换更快首包的 provider 档位< / li >
< li > 短句定稿慢(P50 2.2s):浮层在 stop 后无 final 期间应有「定稿中」状态;调研 gummy 快速定稿参数或 stop 时本地提交最后 partial 的策略< / li >
< li > 网关为 provider 会话建立耗时与 partial 间隔加观测日志/metrics,否则线上无法定位此类波动< / li >
< li > 排查慢会话(3 轮 3.2-3.9s 首包):确认是否 DashScope 冷启动,可考虑 provider 侧连接预热< / li >
< / ul >
< / section >
< section class = "card" >
< h2 > 五、12C 异常路径< / h2 >
< table >
< tr > < th > 场景< / th > < th > 做法< / th > < th > 观察< / th > < th > 判定< / th > < / tr >
< tr >
< td > 断连重建< / td >
< td > App 保持连接时重启服务端进程< / td >
< td > 桌面端数秒内自动重连(lsof 确认新连接),无需用户操作< / td >
< td > < span class = "tag green" > 通过< / span > < / td >
< / tr >
< tr >
< td > SESSION_LIMIT< br > 3 分钟截断< / td >
< td > 余额账号推 198s 长音频< / td >
< td > 180s 准时截断:219 个 partial、全部 final 先定稿下发,随后 < code class = "inline" > SESSION_LIMIT:单次最长 3 分钟,松开后可继续< / code > ,会话关闭< / td >
< td > < span class = "tag green" > 通过< / span > < / td >
< / tr >
< tr >
< td > RATE_LIMITED< br > 30 次/30 分钟< / td >
< td > 同设备连发 31 个 1s 短会话< / td >
< td > 第 1-30 个会话全部正常识别并计费;第 31 个 start 被拒:< code class = "inline" > RATE_LIMITED:操作过于频繁,稍后再试< / code > ——滑动窗口精确在第 31 次生效< / td >
< td > < span class = "tag green" > 通过< / span > < / td >
< / tr >
< tr >
< td > QUOTA_EXCEEDED< br > 试用+余额用尽< / td >
< td > Redis 置试用已用 177/180s 后推 8.6s 音频;再次 start< / td >
< td > 剩 3s 时 start 预检放行 → 推流中扣穿 → 下发 < code class = "inline" > QUOTA_EXCEEDED:今日试用已用完,去购买时长< / code > → < b > 随后仍 flush 当句 final< / b > (已识别内容不丢,实测收到截至扣穿点的定稿文本);再次 start 在预检被直接拒绝< / td >
< td > < span class = "tag green" > 通过< / span > < / td >
< / tr >
< / table >
< div class = "callout" > 以上为协议/服务端行为验证;桌面端 overlay 对各错误码的< b > 文案呈现< / b > ( 19C 实现)需人工目测一遍。< / div >
< h3 > QUOTA 帧序备注(跨端确认项)< / h3 >
< ul >
< li > 实现顺序为「先发 QUOTA_EXCEEDED 错误帧 → 再 Close provider flush 当句 final」,与 16E 文案(「本句 final 后下发」)顺序相反,但内容不丢< / li >
< li > 桌面端已核对无碍:< code class = "inline" > on_server_msg< / code > 对 error 帧不丢弃会话,错误帧之后的 final 照常进 CommitBuffer,松键时注入< / li >
< li > < b > Android / iOS 键盘需各自确认< / b > :收到 error 帧后不立即关闭会话/连接,仍处理后续 final 并 commitText 上屏,否则移动端会「扣了费但字没上屏」< / li >
< / ul >
< / section >
< section class = "card" >
< h2 > 六、商业链路(登录 → 购买 → 入账)< / h2 >
< ol >
< li > < code class = "inline" > GET /v1/packs< / code > :三档时长包(100/500/2000 分钟)正常返回 ✅< / li >
< li > < code class = "inline" > POST /v1/orders< / code > :下单返回 native < code class = "inline" > code_url< / code > ✅< / li >
< li > < code class = "inline" > POST /v1/pay/notify< / code > ( mock 明文回调):SUCCESS,余额 +6000s,订单转 paid,账号 trial→ok ✅< / li >
< li > < b > 回调重放幂等< / b > :同 transaction_id 二次回调返回 SUCCESS 但余额不重复入账 ✅< / li >
< / ol >
< div class = "callout warn" > 再次强调(17I 已标注):MockPay 不验签,< code class = "inline" > /v1/pay/notify< / code > 现等于公开充值接口,< b > 上线前必须换 wechatpay-go 真实验签< / b > 。< / div >
< / section >
< section class = "card" >
< h2 > 七、遗留项(需人工 / 后续)< / h2 >
< table >
< tr > < th > 项< / th > < th > 负责< / th > < th > 说明< / th > < / tr >
< tr > < td > 真人麦克风实说验证< / td > < td > 人工 · 2 分钟< / td > < td > App 正在运行且已登录:任意输入框按住 Cmd+Shift+Space 说话即可(服务端 gummy) < / td > < / tr >
< tr > < td > 1C 三目标注入点验< / td > < td > 人工 · 3 分钟< / td > < td > 微信 / 浏览器 / IDE 各注入一次;同时目测浮层深色玻璃是否透明(本次修复效果)< / td > < / tr >
< tr > < td > 辅助功能/麦克风权限引导< / td > < td > 人工< / td > < td > 正式打包后首启流程(11F)在无权限干净环境走一遍< / td > < / tr >
< tr > < td > Windows 全链路< / td > < td > 装环境后复测< / td > < td > ssh windows 可达但缺 rustup/Node/VS Build Tools< / td > < / tr >
< tr > < td > overlay 错误态文案目测< / td > < td > 人工< / td > < td > 断网/超限场景下浮层文案(19C) < / td > < / tr >
< tr > < td > first_partial 目标重校准 + provider 观测日志< / td > < td > 开发< / td > < td > 见 §四建议,建议并入 #22 backlog< / td > < / tr >
< / table >
< / section >
< footer > dudu · 桌面端联调验收报告 · 2026-07-10 · 详细原始数据见本次联调分支提交说明< / footer >
< / div >
< / body >
< / html >