Skip to content

release(v1.2.0): i18n + ASR 数据不丢失 + 状态机修复 + 单实例 + UI 动画补齐#78

Merged
appergb merged 12 commits into
mainfrom
develop
Apr 30, 2026
Merged

release(v1.2.0): i18n + ASR 数据不丢失 + 状态机修复 + 单实例 + UI 动画补齐#78
appergb merged 12 commits into
mainfrom
develop

Conversation

@appergb
Copy link
Copy Markdown
Collaborator

@appergb appergb commented Apr 30, 2026

本轮 develop 累积工作汇总(v1.1.4 → v1.2.0)

🌍 i18n(closes #40

  • `src/i18n/{zh-CN,en}.ts` 完整双语;`en.ts` 通过 `typeof zhCN` 类型绑定,缺 key 编译报错
  • 设置 → 语言分区:跟随系统 / 简体中文 / English;首启按 `navigator.language` 自动检测
  • 持久化 `localStorage 'ol.locale'`,热切换无需重启

🔒 ASR 数据不丢失三件套(closes #54 #55 #56

  • partial transcript 缓存:服务端 close / 网络中断时用最新 partial 兜底
  • 末帧排队:pending_sends + Notify 让 send_last_frame 等所有 chunk 发完
  • Whisper 失败保留 buffer:clone 而非 take,成功才 clear

🎯 会话状态机(closes #51 #52

  • Starting 阶段补偿热键边沿(toggle 快速双击 / hold 快速松开不再卡死)
  • Esc 取消支持 Processing 阶段(critical):cancelled flag + polish/insert 检查点

🛡️ 基础设施(closes #50 #58

  • tauri-plugin-single-instance:防双进程双插入
  • 移除 SiliconFlow ASR 选项(后端未实现)

💄 UI 动画补齐(closes #69 部分)

📝 polish 提示词重写(closes #47

  • 拆段落式(# 角色 / # 任务 / # 通用规则 / # 示例 / # 输出)
  • raw / light / formal 各加 1-shot 示例;Structured 改两层结构

🧹 清理

  • Swift 时代 Resources/ + 18 份过时 docs 删除
  • CLAUDE.md 去 Swift 化
  • 设置弹窗去掉所有空壳,每个可见控件都接到真实后端

同时合入 main

Test plan

baiqing and others added 11 commits April 30, 2026 12:34
用户反馈:括号中间层("1)" "2)")多余,希望保留主题编号 + 字母要点的清爽结构。

变更:
- polish.rs Structured prompt:层级规则从 3 层改为 2 层;明确禁用带括号中间层;
  示例改为 "1. 主题 / a. 要点 / b. 要点 / 2. 主题 / ..."
- i18n style.modes.structured.sample(zh-CN / en):与新 prompt 输出格式同步

代价:极复杂的多级嵌套口述(如"登录页里的密码错与图形验证码")在 2 层结构下会
被压扁;复杂场景建议改用「原文」或「轻度润色」mode。
用户明确:这些 docs/* 是已废弃 Swift 实现的产物,全平台 Tauri 后不再需要。
- 删除 18 份过时 architecture / requirements / release-notes / review 文档
- 不影响 Tauri 当前文档(CLAUDE.md / README / USAGE 仍在)
修三件事:

1. **波形不动** — 后端电平回调 ~185 Hz emit,CSS 0.12s transition 导致 22 个并行
   transition 互相覆盖、视觉上被平均成静止。
   - coordinator.rs: 节流 emit 到 30 Hz(33ms 最少间隔)
   - Capsule.tsx: bars transition 改 0.06s linear,每次 emit 完整跳到目标高度

2. **玻璃静止** — 加电平驱动的微缩放 + 投影增强(≤2%),录音态整个胶囊在呼吸。
   transition: transform 0.06s linear, box-shadow 0.06s linear。

3. **词汇本不实时** — record_hits 改完 dictionary.json 但前端不知道,需切 tab 才更新。
   - coordinator.rs: total_hits > 0 时 emit('vocab:updated', total_hits)
   - Vocab.tsx: useEffect 订阅 vocab:updated 后立即 refresh()

不改产品语义;不动 ASR / polish 路径。
之前设置弹窗里有一组占位组件(SegSimple/SwitchLite/SelectLite),看起来可点
但只切换内部 useState,不影响任何实际行为。这次按 issue #69 的清单全清。

移除:
- AccountSection 整个 tab —— 没有账号系统,"登录/同步" 按钮无 onClick
- PersonalizeSection 的「外观(跟随系统/浅色/深色)」—— 没有 dark mode CSS
- PersonalizeSection 的「启动时打开(概览/上次位置)」—— 无对应 preference
- PersonalizeSection 的「开机自启」—— 无 autostart 集成
- 不再 import SegSimple / SwitchLite / SelectLite

接通:
- modal sub-nav 的「帮助中心」「版本说明」点击实际打开 GitHub README / Releases
- AboutMini 的「检查」「文档 ↗」「GitHub Issues ↗」三个按钮接到 openExternal
- modal nav 按钮加入 transition 动画(background/color 0.12s ease-out)
- 关闭按钮加 hover 反馈

保留可用:
- PersonalizeSection 的「界面语言」+「毛玻璃强度」—— 都已正常工作,保留

后续 issue:#69 跟踪未来要补回来的功能(账号 / 主题 / 启动项 / autostart)。
…t 弹窗动画

按 Codex agent A 的前端审计 fix-now 列表批量补齐:

**hover/active/focus 反馈(一行 transition 即可)**
- WindowChrome winBtnStyle: bg + color
- _atoms Btn: bg + color + border + shadow + transform
- FloatingShell sidebar nav / FooterIcon / 检查更新 / ProviderPrompt 两按钮
- History filter chip / 列表行
- Vocab input / chip
- Style 卡片 / 单卡 toggle / 整体 toggle
- Settings 分区 nav / 录音方式 segmented / Toggle / inputStyle / iconBtnStyle
- Capsule cancel/confirm(opacity + bg + transform)
- Onboarding 授权按钮
- Overview WeekChart bars(height + opacity)

**新增入场动画**
- 页面 tab 切换 ol-page-fade 0.18s(每次 setCurrentTab 重 mount key 触发淡入 + 上滑 4px)
- ProviderSetupPrompt ol-prompt-fade(背景)+ ol-prompt-pop(卡片)
- Vocab chip ol-chip-in(添加新词条时缩放淡入)

**保持不变**
- Capsule 自身的 audio bars 动画(上一轮已优化)
- SettingsModal 的 ol-modal-fade / ol-modal-pop(已有)

零功能变化、零 bug 风险。仅 CSS / inline style 添加。
closes #58

后端 coordinator.rs 只把 active_asr=='whisper' 路由到 WhisperBatchASR,其他全部
走 VolcengineStreamingASR + 火山凭证。设置页之前暴露 SiliconFlow 选项,用户选后
ASR 必然失败。

修复:从 ASR_PRESETS 删除 siliconflow 条目 + 删除 asrProvider==='siliconflow' 的
凭证字段分支。重新启用方案:在后端实现真正的 SiliconFlow ASR provider 后再加回。

Co-authored-by: baiqing <lbx12309@icloud.com>
…71)

closes #50

现象:/Applications/OpenLess.app 与 dev build 同时存在时,两个进程各自注册
macOS CGEventTap 监听全局热键。用户按 Right Option 一次:
- A 实例捕获 → ASR session 1 → polish → Cmd+V
- B 实例同时捕获 → ASR session 2 → polish → Cmd+V
结果:用户口述被插入两份。

修复:
- Cargo.toml: 加入 tauri-plugin-single-instance v2
- lib.rs: builder 第一个 plugin 即注册它;第二个进程启动时回调聚焦已有主窗口

激活信号通过 plugin 内部 socket 传递,无需额外 IPC 设计。

Co-authored-by: baiqing <lbx12309@icloud.com>
closes #54, #55, #56

三件事,同一目标:用户的口述内容不能因为网络抖动 / 服务端关连接 / 凭证错误
而消失。

## #54 partial cache (volcengine.rs)
- SyncState 加 last_partial_text;每次非 final result 缓存当前累积文本
- 服务端 close(无 final 帧)和 receive loop Err(网络中断)路径不再直接 NoFinalResult
- 新增 fallback_to_partial_or_error:有 partial 就 signal_success(RawTranscript),
  没有才 signal_error;外层 await_final_result 拿到的是已识别那段文字而非空
- 实测命中:网络抖动断线,用户至少拿到断线前那段已识别的话

## #55 audio finalize race (volcengine.rs)
- 新增 pending_sends: AtomicUsize + send_done: Notify
- consume_pcm_chunk 每个 spawn fetch_add(1),spawn 内发送完毕 fetch_sub(1) + notify
- send_last_frame 进函数先 await pending_sends == 0(800ms 上限避网络极端场景)
  再发 leftover + NegativeSequence 末帧
- 防止:fire-and-forget 的 chunk-send 与末帧并发,末帧先到服务端 → 后续 chunk
  被当成"流已结束"后多余帧丢弃 → 尾句吞掉

## #56 Whisper buffer 保留 (whisper.rs)
- 之前 transcribe 一进函数就 mem::take 把 buffer 清空,凭证错 / 网络挂 / 解析失败
  都让 PCM 直接消失,无法重试也无法 fallback
- 改为先 clone (~960 KB / 30s 音频) → transcribe_inner → 成功才 clear buffer
- 失败时 buffer 保持,给上层"再试一次"或"留失败历史记录"留余地

Co-authored-by: baiqing <lbx12309@icloud.com>
)

closes #51, #52

## #51 — Starting 阶段忽略热键边沿
握手 ASR 期间(phase=Starting)的"停止"边沿(toggle 第二次按 / hold 松开)
之前被直接 _ => {} 掉,会话锁死直到用户再按一次或重启 app。

修:
- SessionState 加 pending_stop: bool
- handle_pressed Toggle/Starting → pending_stop = true
- handle_released Hold/Starting → pending_stop = true
- begin_session 转 phase=Listening 时 mem::replace 取出 pending_stop,
  若为 true 立即 await end_session

## #52 — Esc 取消在 Processing 阶段无效
用户按 Esc 时若 end_session 已进入 Processing(润色 / 插入进行中),cancel
信号被忽略,文本仍插入到光标位置 → 数据无意写入用户当前 app。

修:
- SessionState 加 cancelled: bool
- cancel_session 设 cancelled=true(不再仅 Idle 之外 = 转 Idle)
  Processing 阶段保持 phase=Processing 让 end_session 自己走完检查 + 收尾
- end_session 在 ASR 完成后、polish 之前 + polish 完成后、insert 之前
  各检查一次 cancelled,命中即跳过 insert/history.append

新会话 begin_session 入口清两个 flag,避免遗留触发奇怪行为。

Co-authored-by: baiqing <lbx12309@icloud.com>
之前只有 Done 路径有 700ms 自动隐藏,cancel / 各种 error 都让胶囊一直挂着。
用户反馈:不论点 ✕ / ✓ / 中途出错 / 按 Esc,2 秒后底部胶囊都应自动消失。

修改:
- 新增 CAPSULE_AUTO_HIDE_DELAY_MS = 2000 + schedule_capsule_idle(inner, ms) helper
  延迟后再次检查 phase==Idle 才 emit Idle,避免覆盖期间用户重新触发的 Recording
- Done 路径:原 700ms inline schedule 替换为 helper 调用 (2s)
- Cancelled 路径:cancel_session 末尾追加 schedule_capsule_idle
- Error 路径 5 处:mic 权限失败 / ASR 连接失败 / 录音启动失败 /
  await_final 失败 / Whisper transcribe 失败,全部追加 schedule_capsule_idle

Co-authored-by: baiqing <lbx12309@icloud.com>
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @appergb, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

#79)

closes Codex audit blockers on PR #78

## HIGH #1 — begin_session 内 await 期间 cancel 被覆盖
背景:volcengine open_session().await + Recorder::start 都是异步,期间用户按
Esc 调用 cancel_session 把 phase 改回 Idle 并 cancelled=true。但原代码后续无
条件 `state.phase = SessionPhase::Listening`,把 Idle 又翻回 Listening →
用户的 cancel 被吞掉,session 仍然继续。

修:
- 新增 `cancel_raced_during_starting(inner)` helper:持锁查 cancelled or phase
  != Starting
- ASR open_session().await 后调用:如已 race,asr.cancel() + 回 Idle
- Recorder::start Ok 分支用 BeginOutcome { Started / PendingStop / CancelRaced }
  原子在同一 lock 内决定,CancelRaced 时清理 recorder + asr 资源不进 Listening

## HIGH #2 — cancelled check 与 inserter.insert 之间的窗口
背景:end_session 检查 cancelled 后到调用 inserter.insert 之间释放了 lock,
此时 Esc 触发 cancel_session 设 cancelled=true 已经晚了 — Cmd+V 即将发出,
撤销不掉。但 cancel_session 仍然 emit "已取消" → UI 与实际行为相反(已插入但
显示已取消)。

修:
- 新增 SessionPhase::Inserting:表示「已过最后一次 cancel 检查、即将/正在
  调用 inserter.insert」的窗口
- end_session:把 polish 后的 cancel check + phase 转换 atomic 在同一 lock
  内:cancelled → Idle + return;否则 → phase=Inserting 后 release lock 走 insert
- cancel_session:phase==Inserting → 直接 return(不设 cancelled、不 emit 取
  消)。理由:物理上无法撤销 Cmd+V,硬装"已取消"只会让 UI 撒谎

cargo check 通过,13 个 warnings 全部是 pre-existing。

Co-authored-by: baiqing <lbx12309@icloud.com>
@appergb appergb merged commit 94c8507 into main Apr 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant