dudu 前端实现设计

基于 design/ 设计系统的 100% 还原实现方案 · 桌面(Tauri 2)+ 移动(原生键盘扩展与主 App)

v1.1 2026-06-11 依据:design/ 设计系统 + doc/plan/design.html v0.2 v1.1 新增:反馈问题 + 客户端打点

目录

一、设计系统消费策略

设计系统位于 design/(tokens / React 组件 / UI kits / 品牌资产),是所有界面的唯一真相源。两类终端用不同方式消费:

消费方式保真度保证
桌面端
(Tauri 2 webview)
直接复用:React 18 + Vite,把 design/ 作为 monorepo 内共享包(@dudu/design)引入,链入 styles.css 全套令牌,直接 import components/ 下的 Button / Switch / SettingRow / MicBar / RecognitionOverlay 等组件 零转译,与 ui_kits 演示像素级一致
移动端
(Swift / Kotlin 原生)
令牌导出管线 + 组件规格对照:构建脚本解析 design/tokens/*.css,生成 iOS DuduTheme.swift(light/dark 双值 Color/CGFloat)与 Android DuduTheme.kt + values/values-night 资源;组件按本文档第三章的规格表原生实现 令牌单一来源自动同步;组件规格逐条对照 JSX 源码(尺寸/圆角/字号/色值/文案均给出精确值)
令牌导出脚本放在 design-pipeline/(Node 或 Go 实现均可),CI 中校验生成产物与 tokens/*.css 一致,防止两端漂移。

设计系统硬规则(全端强制)

二、设计令牌总表

来源 design/tokens/,此处摘录实现必需的关键值(完整以源文件为准)。

2.1 颜色(语义别名)

令牌LightDark用途
--bg-app#F6F7FA#0F1116页面底色
--surface-card#FFFFFF#171A22卡片 / 键帽
--accent#4F6EF7#5C77E8主操作 / 品牌(hover #3D58DB·#6E89F9,press #2F44B3·#4A63D6)
--positive#16A34A#34C46A录音中 / 成功
--warning#E8890C#F2A33C超额 / 警告
--danger#DC2626#F05B5B错误 / 权限异常
--text-1/2/3#1B1E26 / #5A6072 / #A6ABB8#ECEEF4 / #9AA1B2 / #5F6678正文 / 次要 / 弱化
--overlay-bgrgba(24,26,34,.95)(dark: rgba(30,33,43,.96))+ blur(12px)识别浮层(双模式深色玻璃)
--overlay-text/-2/-3#F2F4FA / #9AA3BD / #646E8C浮层 final / partial / hint
--focus-ring0 0 0 3px #C4CFFB0 0 0 3px rgba(92,119,232,.4)仅键盘聚焦显示

2.2 字体 / 间距 / 圆角 / 控件 / 动效

类别关键值
字体族UI 走系统中文字体(PingFang SC / MiSans / HarmonyOS Sans / 雅黑);Outfit 仅用于 "dudu" 字标、数字与价格;mono 用于计时 / 用量数字
字号12(角标/键帽)· 13(菜单/次要)· 14(桌面正文)· 15(浮层文本/移动正文)· 17(区块标题)· 20(窗口标题)· 24(页面大标题)· 32(价格大数字)
字重400 / 500 / 600(700 仅价格等大数字)
间距4px 网格(4/8/12/16/20/24/32/40/48/64)
圆角4(kbd)· 6(按钮/输入框/键帽)· 10(菜单)· 14(卡片/浮层)· 20(大容器)· 999(麦克风条/胶囊)
控件高度28 / 36(桌面紧凑/默认)· 44 / 56(移动触控/主操作与麦克风条)
阴影card(极轻 0 1px 3px)/ menu / window / overlay(0 12px 36px)四档 + key(键帽底边 0 1px 0);dark 模式阴影加深、靠边框提层级
动效ease-out cubic-bezier(.2,.8,.2,1);120ms(hover/按压)· 200ms(浮层进出/开关)· 320ms(屏幕切换);浮层入场淡入 + 上移 4px;尊重 prefers-reduced-motion

三、组件库与原生映射

React 组件(桌面端直接用)→ 移动端原生等价规格。文案全部内置于组件,移动端照抄。

3.1 核心组件速查

组件关键 props / 状态原生实现要点(iOS SwiftUI / Android Compose)
Buttonvariant: primary/secondary/ghost/danger · size: sm 28 / md 36 / lg 44 · block · disabled(opacity .45)圆角 6,字重 500;primary 填充 accent,hover/press 用 accent-hover/press;ghost 文字 accent-text、hover 底 accent-soft
Badgetone: green/blue/orange/red/neutral胶囊 999,soft 底 + 功能色文字,12px
Kbd / HotkeyCombokeys={['⌘','⇧','Space']}仅桌面端使用
ProgressBarvalue/max · warnAt=0.9(超阈值自动转 warning 色)试用进度(1/3 分钟);轨道 surface-3、填充 accent,≥90% 变 warning
Input / Switch / SettingRowSettingRow: label + description(12px text-3) + 右侧控件;行 padding 12 0、底边 border-1、末行无边设置页与主 App 列表行复用此范式

3.2 Waveform(波形)

3.3 MicBar(麦克风条,移动端核心)

state视觉文案(内置)
idleaccent 填充、白字;按压 accent-press按住说话
recordingpositive 填充、白字、左侧白色 Waveform(9,18)松开完成 · 上滑取消
disabledsurface-3 底、text-3 字、cursor 默认打开 dudu App 登录
quotawarning 填充、白字今日试用已用完,去购买时长

尺寸:高 56(--control-xl)、全宽、胶囊 999、15px/600 字重、图标线条 mic 20px;touch-action:none;移动端按压可缩 0.97(桌面无缩放)。

3.4 RecognitionOverlay(识别浮层)

四、桌面端实现(Tauri 2)

4.1 技术栈与工程结构

desktop/
  src-tauri/            Rust:快捷键 / 音频采集 / WS / 注入 / 托盘 / 窗口管理
  src/                  React + Vite + TypeScript
    windows/
      overlay/          识别浮层窗口
      settings/         设置窗口
      login/            登录 / 购买窗口
      tray/             自绘托盘菜单窗口
    shared/             事件桥(tauri event ↔ React state)、API client
  package.json          依赖 @dudu/design(指向 ../design)

4.2 窗口清单与规格

窗口视觉规格(来自 ui_kits/desktop)Tauri 实现要点
识别浮层 RecognitionOverlay 340px;深色玻璃双模式不变;跟随光标 / 目标窗口下方居中 无边框 + 透明 + 置顶 + focusable:false(不抢焦点是功能正确性前提)+ skip-taskbar;常驻隐藏、按键时 show 避免创建开销;blur 用 CSS backdrop-filter
托盘菜单 248px 自绘菜单:surface-card 96% 不透明 + blur(20px)、圆角 10、shadow-menu;hover 整行 accent 反白(系统菜单惯例);条目:标题行(logo + "dudu 语音输入" + 已就绪 Badge)/ 启用快捷键✓ / 按住说话 ⌘⇧Space / 设置… / 时长余额 472 分钟 / 账号 wang*** 已登录 / 检查更新 / 退出 dudu 原生 tray 菜单做不出此样式 → tray 图标点击弹自绘无边框小窗,贴托盘坐标定位,失焦自动关闭;tray 图标用 logo-mark(template image 随系统明暗)
设置窗口 440px;四分组(12px 灰色组标题):输入=说话快捷键(HotkeyCombo + 修改)/ 麦克风下拉 / 提示音 Switch;通用=开机自启 Switch / 外观三段选(跟随系统 · 浅色 · 深色);账号与时长=登录状态(wang*** + 余额充足 Badge)/ 时长余额(472 分钟 + 购买时长 secondary 按钮)/ 今日免费试用 ProgressBar 1/3 分钟;帮助(v1.1 新增)=反馈问题行(描述"遇到问题或建议,告诉我们"+ ghost 按钮"去反馈") SettingRow 控件 28px(--control-sm)紧凑行高;外观选择驱动根节点 data-theme 并持久化;"跟随系统"监听系统主题事件
反馈窗口
(v1.1 新增)
420px;标题"反馈问题";多行输入框(placeholder"说说遇到的问题或建议",上限 1000 字,右下角字数提示);图片区=虚线"添加图片"框(Lucide image-plus)+ 64px 缩略图(右上角 × 删除),最多 3 张;SettingRow"附带诊断信息"(描述"版本、系统、最近错误摘要")Switch 默认开;底部主按钮"提交"(block primary);成功态=56px 绿对勾圆 +"已收到,谢谢你" 图片支持粘贴截图 / 拖拽 / 文件选择;提交走 POST /v1/feedback multipart;详见第八章
登录 / 购买 400px 四步流:① 扫码登录(logo 40 + 二维码 + "打开微信扫一扫,确认后自动登录")② 选时长包(三档卡片:100 分钟 ¥9 / 500 分钟 ¥39 省 13% / 2000 分钟 ¥129 省 28%;选中 1.5px accent 边 + accent-soft 底;底注"时长不过期 · 每天另有 3 分钟免费试用")③ 扫码支付(金额 32px 大数字 + 二维码 + "支付完成后自动开通")④ 完成(56px 绿色对勾圆 + "已到账 xx 分钟" + 余额提示 + 开始使用按钮) 二维码内容来自后端:登录 /v1/auth/qr、支付订单 code_url;两处均轮询状态自动推进步骤;窗口标题随步骤切换(dudu — 登录 / dudu — 购买时长)

4.3 首次启动流程

欢迎页 → 权限引导(macOS 辅助功能 + 麦克风,带系统设置深链)→ 微信扫码登录 → 试一试(示例输入框按住 ⌘⇧Space 说一句话)。Windows 无辅助功能步骤。

五、桌面端状态机与 Rust↔Web 事件

5.1 识别状态机(对照 OverlayDemo.jsx)

idle ──按下快捷键──▶ listening ──首个识别结果──▶ streaming ──松开──▶ idle
                      │ (浮层立即弹出"聆听中",               │ final 注入焦点输入框
                      │  不等首个结果,420ms 内无结果保持)     │ 浮层淡出
                      └──松开(未出字)──▶ idle(不注入)

5.2 Rust → Web 事件

事件载荷 / 行为
hotkey:down / hotkey:up浮层 show + listening 态 / 触发 stop 流程
asr:partial{final, partial} 累积文本,浮层转 text 态
asr:final{text} 句子定稿
asr:error{code, message};QUOTA_EXCEEDED → 浮层显示警告文案"今日试用已用完,去购买时长"(warning 色)并可点击打开购买窗口;SESSION_LIMIT(单次 3 分钟截断)→ 已识别内容正常上屏 + 提示"单次最长 3 分钟,松开后可继续";RATE_LIMITED → "操作过于频繁,稍后再试";网络错误 → "连接中断,松开重试"。移动端键盘对应在 partial 文本条位置展示同文案
account:update{balance_seconds, trial_used_today, trial_daily_limit};托盘 / 设置实时刷新(识别中由 WS usage 帧驱动)
audio:level音量包络(30ms 间隔),驱动浮层波形高度(MVP 可先用固定动画)

5.3 Web → Rust 命令(invoke)

set_hotkey · set_mic_device / list_mic_devices · set_autostart · set_theme · open_url(更新下载页)· logout。设置持久化于本地(tauri-plugin-store),登录态 JWT 存系统钥匙串。

六、移动端键盘扩展(iOS / Android)

6.1 面板结构(自上而下,对照 KeyboardScreen.jsx)

区域规格
工具条左:13px logo + "dudu 语音输入"(12px text-2);右:状态文案(见 6.2)
功能键行
(非录音时)
五键:切换键盘(flex1.3) / ,/ 。/ 退格(1.3) / 发送(1.3);键帽高 40、圆角 6、surface-card 底 + shadow-key 底边、15px;图标 Lucide(globe/backspace/enter 17px);不做 26 键,语音为主
partial 文本条
(录音中替换功能键行)
min-height 40、padding 9 12、圆角 10、surface-card + border-1;final 亮 text-1 / partial 灰 text-3 拼接;15px 行高 1.5
大麦克风条MicBar 56px 胶囊(四态见 3.3)
取消提示
(仅录音中)
居中 12px text-3:"↑ 上滑取消"(Lucide arrow-up 11px)

面板底色 --surface-2 + 顶部 1px border-1;跟随系统 light/dark。

6.2 账户四态(工具条右侧文案 × MicBar 状态联动)

账户态工具条右侧MicBar
ok 有余额余额 472 分钟 ✓(text-3,对勾 Lucide check 11px)idle 可用
trial 试用中试用 · 今日剩 2 分钟(text-3)idle 可用
guest 未登录未登录(text-3)disabled:"打开 dudu App 登录",点击 deep link 跳主 App
quota 超额今日试用已用完(warning 色)quota:"今日试用已用完,去购买时长",点击跳主 App 购买页
录音中(任意可用态)→ 工具条右侧变 mono 计时 00:04(positive 色)recording

6.3 交互流程

  1. 按住麦克风条 → 进 recording:开始采集 + WS 推流;宿主输入框边框变 1.5px accent(视觉锚定目标)
  2. 识别中:partial 文本条流式刷新(final 亮 + partial 灰);partial 不进宿主输入框
  3. 松开 → committed(final + 未定稿 partial)经 iOS textDocumentProxy.insertText / Android currentInputConnection.commitText 上屏;状态复位
  4. 上滑取消:手指上移超过阈值(48pt)松开 → 丢弃本次结果,不上屏
  5. 功能键:,。 直接 insertText;退格 deleteBackward;发送 = 宿主 return key

6.4 平台实现要点

平台要点
iOS / iPadOSUIInputViewController + SwiftUI 子视图;需"完全访问"联网;token 经 App Group 共享;键盘内录音 1A spike 已实证不可行(见下),走 deep link 降级路径;iPad 同一 target,size class 自适应(横屏麦克风条加宽)+ 悬浮键盘紧凑布局
AndroidInputMethodService + Compose(ComposeView in onCreateInputView);RECORD_AUDIO 权限在主 App 申请;token 经同应用内部存储共享;键盘高度按系统建议值

6.5 iOS 录音主路径结论(1A spike 真机实证,14D 落地)

键盘扩展进程内无法录音:即使"允许完全访问"开启,AVAudioSession setActive/start 在扩展进程内直接报 CoreAudio 错误 2003329396 —— iOS 平台硬限制,与权限无关。因此 iOS 语音主路径采用预留的 deep link 降级方案:

  1. 键盘 MicBar 改为"按一下 · 去 dudu 说话",经 extensionContext.open 打开 dudu://dictate(主 App 全屏听写页,深色玻璃 overlay 风格)
  2. 主 App 进程录音推流:AVAudioEngine 采集 → AVAudioConverter 重采样 16kHz/16bit/mono → 100ms/3200B 分帧 → WSS /v1/asr/stream,partial/final 流式显示(final 亮 / partial 灰)
  3. 松开定稿:committed 文本复制到剪贴板 + 写 App Group pending_text(120s 有效)双保险,提示"已复制 · 回到原 App 粘贴"
  4. 用户手动切回原 App(iOS 无"自动跳回上一 App"的公开 API,键盘扩展也无法携带可跳回的 return 上下文)→ dudu 键盘 viewWillAppear 检出未过期 pending_text,textDocumentProxy.insertText 自动上屏并在工具条短暂显示"已上屏";过期则用户手动粘贴剪贴板兜底

七、移动端主 App 四屏(对照 AppScreens.jsx)

规格与交互
① 启用引导 顶部 logo 44 + "三步开启 dudu"(20px/600)+ 副题"按住说话,松开上屏";三步卡片(添加键盘 / 开启完全访问 / 试一试),三态:done(绿对勾 + positive-soft 图标底)/ active(1.5px accent 边)/ todo(opacity .62);底部主按钮文案随步骤:"去设置" → "去设置" → "我说完了" → "完成",点击带系统设置深链推进;Android 版第二步替换为"启用输入法"(系统输入法设置深链)
② 登录 居中 logo 64 + "dudu"(24px Outfit, tracking-wide)+ "大模型语音输入 · 说话即上屏";底部微信绿 #07C160 胶囊按钮"微信一键登录"(56px,唯一例外色),拉起微信 OpenSDK;下方 12px text-3"登录即同意《用户协议》与《隐私政策》"
③ 购买时长 余额卡(左"时长余额 472 分钟"24px/700 大数字,右注"时长不过期 / 每天另有 3 分钟免费试用")+ 三档时长包卡片(同桌面规格,选中 accent 边 + soft 底,"省 13%/28%" warning-soft 角标)+ 底部主按钮"微信支付 ¥39"(lg block,金额随选中联动)+ 注"支付后立即到账 · 时长不过期";点击拉起微信 APP 支付,回调成功后余额卡即时刷新
④ 我的 账号卡(头像圈 accent-soft + wang*** + "时长余额 472 分钟 · 不过期" + 余额充足 Badge);今日免费试用卡("1 / 3 分钟" mono + ProgressBar);权限自检列表(麦克风权限 / 键盘已添加 / 完全访问,正常绿对勾,异常红色"去开启 ›"跳系统设置);权限卡下方加"反馈问题 ›"列表行(v1.1 新增,进反馈页,见第八章);低余额时 Badge 转 orange"余额不足"并显示购买入口

八、反馈问题(v1.1 新增)

用户在客户端内直接反馈问题或建议,支持文字 + 图片,可选附带诊断信息。入口:桌面设置窗口"帮助"分组、移动端"我的"页列表行;键盘扩展内不放入口(保持键盘极简)。

8.1 表单规格(桌面 / 移动一致)

字段规格
文字内容多行输入框,placeholder"说说遇到的问题或建议",1–1000 字,右下角实时字数(text-3 12px);为空时提交按钮 disabled
图片最多 3 张,单张 ≤5MB,jpg/png/webp;虚线添加框(border-2 虚线 + Lucide image-plus,hover 转 accent);已选图片 64px 圆角 6 缩略图,右上角 20px × 删除钮;桌面支持粘贴截图 / 拖拽 / 文件选择,移动端从相册选或拍照
附带诊断信息SettingRow + Switch,默认开;描述"版本、系统、最近错误摘要";内容=app_version、os_version、platform、device_id、最近 10 条错误日志摘要(不含识别文本)
提交主按钮"提交"(block primary,一屏唯一蓝主按钮);提交中转 loading 态 disabled;失败 toast"提交失败,请稍后再试"保留已填内容
成功态替换表单内容:56px 绿对勾圆(positive-soft 底 + positive 对勾,同购买完成步样式)+"已收到,谢谢你"(17px/600)+ ghost 按钮"继续反馈"/ 关闭

8.2 交互与实现

九、客户端打点(v1.1 新增)

目的:量化响应时间(对照延迟目标)与关键链路质量,驱动体验改进。隐私底线:任何事件不携带识别文本与音频。

9.1 指标清单

事件测量目标
asr.first_partial_ms按键(桌面快捷键 / 移动 MicBar 按下)→ 收到首个 partialP95 < 500ms
asr.release_to_commit_ms松开 → final 注入 / 上屏完成P95 < 300ms
audio.start_ms按键 → 音频采集首帧P95 < 100ms
ws.connect_ms / ws.reconnectWS 建连耗时 / 重连次数 + 原因监控
asr.sessionaudio_seconds、partial_count、是否 cancel、error_code质量监控
inject.fail(仅桌面)注入失败 + 前台应用标识(bundle id / exe 名)兼容性改进
漏斗事件(低频)onboarding.steplogin.successpurchase.successkb.enabled转化分析

公共维度(每条自动附带):device_id(客户端首次启动生成的匿名 UUID,与账号无关)、platform、app_version、os_version、client_ts。

9.2 上报机制

9.3 各端实现位置

实现
桌面Rust 侧统一 metrics 模块(计时起点都在 Rust:快捷键、音频、WS),webview 漏斗事件经 invoke 汇入同一队列;队列持久化于本地数据目录
iOS 键盘扩展事件写 App Group 共享队列,由扩展自身(活跃时)或主 App 启动时 flush
AndroidIME Service 与主 App 共用应用内队列(同进程组),WorkManager 定期 flush

十、UI 状态 ↔ 后端接口对照

前端每个动态数据点都有明确来源(接口详见 backend-architecture.html):

UI 数据点 / 交互接口与字段
桌面扫码登录二维码 + 自动登录POST /v1/auth/qr → qr_url;轮询 GET /v1/auth/qr/:state → JWT
移动端微信一键登录微信 OpenSDK code → POST /v1/auth/wechat → JWT
余额 472 分钟 / 试用 1/3 分钟 / wang*** / Badge 态GET /v1/me:balance_seconds、trial_used_today、trial_daily_limit、nickname_masked(分钟数 = 秒数向下取整显示)
时长包三档(价格 / 单价文案 / 省 x% 角标)GET /v1/packs(服务端可配,前端不写死)
支付二维码 / 拉起收银台 / "支付完成后自动开通"POST /v1/orders → code_url 或 APP 支付参数;轮询 GET /v1/orders/:id → paid 后跳完成步
识别全流程(partial/final/计时/余额实时扣减)WSS /v1/asr/stream:下行 partial / final / usage / error 帧
键盘 guest/quota 态判定无 token → guest;WS error QUOTA_EXCEEDED 或 /v1/me 余额+试用均为 0 → quota
托盘"检查更新"GET /v1/app/latest → 版本号 + 下载地址
反馈提交(文字 + 图片 + 诊断)POST /v1/feedback(multipart)→ feedback_id;超限错误 FEEDBACK_RATE_LIMITED
打点批量上报POST /v1/metrics/batch(gzip JSON 数组,可匿名)→ 204

十一、100% 还原核查清单(验收标准)

  1. 所有颜色 / 字号 / 间距 / 圆角 / 阴影 / 动效值只来自令牌,无硬编码(移动端来自生成的 Theme 文件)
  2. 一屏一个蓝色主按钮;secondary/ghost 用于其余操作
  3. 全局无渐变;唯一例外色为微信绿 #07C160(仅登录按钮)
  4. 识别浮层在 light/dark 下均为深色玻璃(--overlay-bg + blur12)且 focusable:false
  5. 图标全部 Lucide 2px 线条 currentColor;UI 无 emoji;键帽字符 ⌘⇧⌫↵ 仅在 Kbd/HotkeyCombo
  6. 文案抽样核查:用"你"、微文案无句号、"·"分隔提示、半角数字 + 空格(472 / 600 分钟、00:04)、无 partial/final 字样
  7. MicBar 四态视觉与文案逐字对照 MicBar.jsx;浮层两态对照 RecognitionOverlay.jsx
  8. 波形是唯一循环动画且仅录音中出现;prefers-reduced-motion 下停止
  9. 焦点环仅键盘聚焦显示(:focus-visible)
  10. light/dark 切换全界面无遗漏(含键盘面板、设置、主 App);桌面"跟随系统"实时响应
  11. 对照 ui_kits 演示页(design/ui_kits/desktop/index.html、mobile/index.html)逐屏截图比对
自动化辅助:design 包内附 _adherence.oxlintrc.json,桌面端 lint 接入可静态检查 token 使用规范。