1 Commits

Author SHA1 Message Date
wangjia 45e40ccd13 desktop: 联调发现的透明窗口修复 + 联调验收报告与文档索引
ci / server (push) Failing after 11s
ci / design-tokens (push) Failing after 10s
- tauri.conf.json 启用 macOSPrivateApi、Cargo.toml 加 macos-private-api feature:
  overlay/tray 为 transparent 窗口,macOS 上 WKWebView 透明必须开启该项,
  否则浮层带不透明底色(tauri dev 启动日志明确告警)
- doc/desktop-integration-acceptance.html:#12/#1C 联调验收报告
  (mac 全链路通过、12B 延迟基线与短句问题、12C 四场景全过、商业链路通过、遗留项清单)
- doc/index.html:项目文档索引(全局文档规范要求)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 12:18:22 +08:00
4 changed files with 299 additions and 1 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = ["tray-icon"] }
tauri = { version = "2", features = ["tray-icon", "macos-private-api"] }
tauri-plugin-global-shortcut = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
+1
View File
@@ -10,6 +10,7 @@
"frontendDist": "../dist"
},
"app": {
"macOSPrivateApi": true,
"windows": [
{
"label": "settings",
+220
View File
@@ -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: 980px; margin: 0 auto; padding: 0 24px 80px; }
header.hero { background: var(--accent); color: #fff; padding: 48px 24px 40px; text-align: center; }
header.hero h1 { margin: 0 0 6px; font-size: 30px; }
header.hero p { margin: 0; opacity: .85; font-size: 15px; }
.chips { margin-top: 16px; }
.chip { display: inline-block; padding: 3px 14px; margin: 3px; background: rgba(255,255,255,.16); border-radius: 999px; font-size: 13px; }
section.card {
background: var(--card); border: 1px solid var(--border-1); border-radius: 14px;
padding: 28px 32px; margin-top: 24px; box-shadow: 0 1px 3px rgba(17,19,26,.05);
}
h2 { font-size: 21px; margin: 0 0 14px; padding-bottom: 8px; border-bottom: 2px solid var(--accent); display: inline-block; }
h3 { font-size: 16px; margin: 22px 0 8px; color: var(--accent-text); }
table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: 13.5px; }
th, td { border: 1px solid var(--border-1); padding: 7px 11px; 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: 16px 18px; border-radius: 10px;
overflow-x: auto; font-family: "SF Mono", Menlo, Consolas, monospace; font-size: 12.5px; line-height: 1.55;
}
code.inline { background: var(--accent-soft); color: var(--accent-text); padding: 1px 6px; border-radius: 4px; font-family: "SF Mono", Menlo, monospace; font-size: 12.5px; }
.callout { border-left: 4px solid var(--accent); background: var(--accent-soft); padding: 11px 16px; border-radius: 0 8px 8px 0; margin: 12px 0; font-size: 13.5px; }
.callout.warn { border-color: var(--warning); background: #FCEFD7; }
.tag { display: inline-block; font-size: 12px; padding: 1px 10px; border-radius: 999px; margin-right: 6px; 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: 22px; }
li { margin: 3px 0; }
footer { text-align: center; color: var(--text-3); font-size: 13px; margin-top: 44px; }
</style>
</head>
<body>
<header class="hero">
<h1>桌面端联调验收报告</h1>
<p>TODO #12(联调与验收)+ #1CmacOS 注入与浮层)· 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 &lt; 300ms</td><td><span class="tag orange">部分达标</span></td><td>P50 261ms ✅ / P95 918ms ❌(受两轮 provider 异常拖累)</td></tr>
<tr><td>12B first_partial P95 &lt; 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_LIMIT3 分钟截断)</td><td><span class="tag green">通过</span></td><td>198s 推流在 180s 准时截断:先定稿下发全部 final,再发 SESSION_LIMIT</td></tr>
<tr><td>12C RATE_LIMITED30 次/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> featurecargo 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+Space3s</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_1006000s 余额)</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 &amp;&amp; npm run tauri dev</code>,注入走 SendInput10F)。</div>
</section>
<section class="card">
<h2>四、12B 延迟基线(gummy 真实 provider10 轮 × 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 &lt; 500ms</td><td><span class="tag red">未达标</span></td></tr>
<tr><td>release_to_commit</td><td>261ms</td><td>918ms</td><td>&lt; 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/3870msrelease 同步劣化到 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 时就 stop30 轮中 16 轮 0 partial),定稿要等 provider 收尾 flush</li>
<li>体验含义:说「好的」这类两三字短语,松开后约 2 秒才上屏,是当前链路最明显的体验短板</li>
</ul>
<h3>建议(已入 backlog</h3>
<ul>
<li>first_partial P95&lt;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>
+77
View File
@@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>dudu — 文档索引</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;
}
* { 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: 980px; margin: 0 auto; padding: 0 24px 80px; }
header.hero { background: var(--accent); color: #fff; padding: 48px 24px 40px; text-align: center; }
header.hero h1 { margin: 0 0 6px; font-size: 30px; }
header.hero p { margin: 0; opacity: .85; font-size: 15px; }
section.card {
background: var(--card); border: 1px solid var(--border-1); border-radius: 14px;
padding: 28px 32px; margin-top: 24px; box-shadow: 0 1px 3px rgba(17,19,26,.05);
}
h2 { font-size: 21px; margin: 0 0 14px; padding-bottom: 8px; border-bottom: 2px solid var(--accent); display: inline-block; }
table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: 13.5px; }
th, td { border: 1px solid var(--border-1); padding: 7px 11px; text-align: left; vertical-align: top; }
th { background: #F0F1F4; font-weight: 600; }
tr:nth-child(even) td { background: #FCFCFD; }
a { color: var(--accent-text); text-decoration: none; }
a:hover { text-decoration: underline; }
.tag { display: inline-block; font-size: 12px; padding: 1px 10px; border-radius: 999px; margin-right: 6px; font-weight: 600; }
.tag.blue { background: var(--accent-soft); color: var(--accent-text); }
.tag.green { background: #DCFCE7; color: var(--positive); }
footer { text-align: center; color: var(--text-3); font-size: 13px; margin-top: 44px; }
</style>
</head>
<body>
<header class="hero">
<h1>dudu 文档索引</h1>
<p>项目全部设计 / 计划 / 验收文档的单一入口</p>
</header>
<div class="container">
<section class="card">
<h2>设计方案</h2>
<table>
<tr><th>文档</th><th>说明</th><th>形式</th></tr>
<tr><td><a href="plan/design.html">MVP 总体方案 v0.3</a></td><td>产品定义、关键技术决策、总体架构、里程碑与风险</td><td><span class="tag blue">HTML</span></td></tr>
<tr><td><a href="frontend-design.html">前端实现设计 v1.1</a></td><td>五端界面实现设计,100% 还原 design/ 设计系统</td><td><span class="tag blue">HTML</span></td></tr>
<tr><td><a href="backend-architecture.html">后端与数据架构 v1.1</a></td><td>API / WS 协议 / 计费账本 / 存储与 Redis Key 设计</td><td><span class="tag blue">HTML</span></td></tr>
</table>
</section>
<section class="card">
<h2>验收报告 / 排障 Runbook</h2>
<table>
<tr><th>文档</th><th>说明</th><th>形式</th></tr>
<tr><td><a href="desktop-integration-acceptance.html">桌面端联调验收报告(#12 / #1C)</a></td><td>mac 全链路联调、12B 延迟实测基线、12C 异常路径验证结论与遗留项</td><td><span class="tag green">HTML</span></td></tr>
</table>
</section>
<footer>dudu · doc/index.html · 新增文档须登记至此索引</footer>
</div>
</body>
</html>