Skip to content

feat(windows): add sherpa-onnx local ASR#504

Merged
H-Chris233 merged 4 commits into
Open-Less:betafrom
weikeyi:feat/windows-sherpa-onnx-local-asr
May 22, 2026
Merged

feat(windows): add sherpa-onnx local ASR#504
H-Chris233 merged 4 commits into
Open-Less:betafrom
weikeyi:feat/windows-sherpa-onnx-local-asr

Conversation

@weikeyi
Copy link
Copy Markdown
Contributor

@weikeyi weikeyi commented May 20, 2026

User description

背景

本 PR 为 Windows 端接入 sherpa-onnx 本地离线 ASR 能力,补齐 OpenLess 在 Windows 上不依赖云端服务的语音识别路径。

目标是让用户可以在本地下载、管理 Sherpa 模型,并通过现有 Tauri/React 设置页启用本地识别,同时保持现有 Qwen、Foundry、云端 ASR 路径不受影响。

主要变更

1. 新增 Windows Sherpa 本地 ASR 后端

  • 新增 Sherpa 模型目录、catalog、状态描述和模型别名管理。
  • 新增 SherpaOnnxRuntime,封装 Windows 下的模型加载、转写、释放和取消逻辑。
  • 新增 SherpaOnnxAsr provider,接入现有 coordinator 的 ASR provider 抽象。
  • 支持以下模型别名:
    • sense-voice-small-zh
    • paraformer-zh
    • whisper-small-multi
  • 在 Cargo.toml 中为 Windows 引入 sherpa-onnx = 1.13.2 静态链接依赖。

2. 新增 Sherpa 模型下载与管理

  • 新增 sherpa_download.rs,实现 Sherpa 模型下载管理器。
  • 支持远程模型信息获取、分片下载、取消下载、大小校验和 SHA-256 校验。
  • 下载完成后只保留运行时必要文件,减少模型目录冗余。
  • 复用现有 Local ASR 下载辅助逻辑。
  • 新增 Sherpa 下载进度事件:
    • sherpa-onnx-asr-download-progress

3. 扩展 Tauri IPC 命令

新增 Sherpa 相关命令,用于前端管理本地模型和运行时状态,包括:

  • 查询状态和模型目录
  • 查询静态模型 catalog
  • 获取远程模型信息
  • 下载 / 取消下载模型
  • 设置当前模型和语言提示
  • 准备 / 取消准备运行时
  • 释放运行时
  • 删除模型
  • 打开模型目录

同时在 lib.rs 中注册 SherpaDownloadManager 和相关 command handler。

4. 接入 coordinator 运行链路

  • 在 coordinator 中新增 Sherpa local ASR runtime/provider 管理。
  • 支持从偏好设置切换到 sherpa-onnx-local provider。
  • 会话开始、取消、结束和资源释放逻辑接入现有状态机。
  • 在 provider 切换时优先取消 Sherpa prepare,避免运行时状态残留。

5. 前端 Local ASR / Settings 页面集成

  • 在 Local ASR 页面中加入 Sherpa 模型选择、下载、准备、启用、删除和打开目录等操作。
  • 支持模型下载进度展示、取消下载、运行时准备状态展示。
  • 扩展前端 IPC 类型、Local ASR 类型和 i18n 文案。
  • Settings 相关 UI 保持和现有本地 ASR 体验一致。

6. Windows Common Controls v6 manifest 修复

Sherpa 测试二进制在 Windows 上曾因为缺少 Common Controls v6 manifest,启动时报 STATUS_ENTRYPOINT_NOT_FOUND

本 PR 在 build.rs 中加入更稳妥的 manifest dependency 注入方式:

  • 不使用 /MANIFESTINPUT,避免和 Tauri app 自带 manifest 产生 duplicate resource type:MANIFEST
  • 不直接通过 cargo:rustc-link-arg=/MANIFESTDEPENDENCY:... 传参,避免 MSVC/Cargo 因空格拆参导致 LNK1181
  • 改为生成一个小 C object,通过 #pragma comment(linker, "/manifestdependency:...") 注入 Common Controls v6 dependency,并用 /INCLUDE 强制 linker 拉入该 object。

这样测试二进制和主应用二进制都能获得 Common Controls v6 dependency,同时不会造成 manifest 重复资源冲突。

7. 测试修正

  • 更新 Sherpa runtime/provider 单元测试,使其匹配真实运行时行为。
  • 修复 Windows 下 cmd modifier 归一化为 Ctrl 的快捷键测试断言。
  • 修复 Foundry Windows App Runtime 检测脚本测试断言,匹配当前转义正则字符串。

验证结果

已在 Windows 本地完成以下验证:

  • git diff --check
  • git diff --cached --check
  • 冲突标记扫描 ✅
  • cargo check --manifest-path openless-all/app/src-tauri/Cargo.toml
  • cargo build --manifest-path openless-all/app/src-tauri/Cargo.toml
  • cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml sherpa --lib -- --nocapture
    • 19 passed
  • cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml --lib
    • 323 passed
  • npm run build

已知非阻塞项

  • Rust 仍有若干既有 dead code / unused warning,不影响构建和测试。
  • Vite build 仍有动态/静态 import 混用和 chunk size warning,不影响产物生成。
  • 建议合并前在真实 Sherpa 模型下载完成后的 Windows 环境中再做一次人工端到端 smoke test,覆盖“下载模型 → prepare → 启用 → 录音识别 → release/delete”的完整路径。

PR Type

Enhancement, Tests, Documentation


Description

  • Add Sherpa runtime skeleton

  • Expose download and prepare commands

  • Route Windows sessions to Sherpa

  • Update settings, tests, and docs


Diagram Walkthrough

flowchart LR
  UI["Local ASR settings UI"] -- "invoke IPC" --> CMD["Tauri commands"]
  CMD -- "persist prefs / control model" --> COORD["Coordinator"]
  CMD -- "catalog / download / prepare" --> DL["Sherpa download manager"]
  COORD -- "route sessions" --> PROV["Sherpa provider"]
  PROV -- "load / transcribe" --> RT["Sherpa runtime"]
  RT -- "check files / release" --> DL
Loading

File Walkthrough

Relevant files
Enhancement
13 files
commands.rs
Add Sherpa ASR command endpoints                                                 
+453/-24
sherpa_download.rs
Implement model manifest and download flow                             
+788/-0 
sherpa_runtime.rs
Add Windows Sherpa runtime skeleton                                           
+510/-0 
sherpa.rs
Define Sherpa catalog and metadata                                             
+351/-0 
coordinator.rs
Integrate Sherpa runtime into coordinator                               
+89/-3   
dictation.rs
Route dictation sessions to Sherpa                                             
+74/-0   
sherpa_provider.rs
Wrap Sherpa runtime as audio consumer                                       
+182/-0 
lib.rs
Register Sherpa state and commands                                             
+33/-1   
download.rs
Expose reusable download helpers                                                 
+5/-2     
LocalAsr.tsx
Add Sherpa local ASR settings view                                             
+2896/-1136
localAsr.ts
Add frontend helpers for Sherpa ASR                                           
+491/-224
ipc.ts
Extend IPC bindings for Sherpa commands                                   
+1019/-783
AdvancedSection.tsx
Surface Sherpa controls in settings                                           
+399/-219
Configuration changes
3 files
types.rs
Persist Sherpa model preferences                                                 
+30/-0   
build.rs
Link Windows manifest dependency                                                 
+22/-0   
persistence.rs
Serialize new Sherpa preference fields                                     
+7/-0     
Miscellaneous
1 files
mod.rs
Export Sherpa local ASR modules                                                   
+12/-2   
Tests
1 files
backend_rust.rs
Include Sherpa stub in backend tests                                         
+9/-0     
Dependencies
1 files
Cargo.toml
Add Windows Sherpa dependency and config                                 
+4/-0     
Documentation
1 files
windows-sherpa-onnx-asr-plan.md
Document Sherpa ASR rollout plan                                                 
+421/-0 
Additional files
13 files
foundry_native.rs +2/-2     
combo_hotkey.rs +3/-0     
resources.rs +2/-0     
qa_hotkey.rs +3/-0     
en.ts +24/-0   
ja.ts +24/-0   
ko.ts +24/-0   
zh-CN.ts +24/-0   
zh-TW.ts +24/-0   
stylePrefs.test.ts +3/-0     
types.ts +6/-0     
shared.tsx +164/-56
HotkeySettingsContext.tsx +212/-161

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 20, 2026

PR Reviewer Guide 🔍

(Review updated until commit 83221df)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Stale hotkey

The new change detection skips hotkey.trigger, so editing only the dictation trigger will not refresh the active bindings. Because refresh_combo_hotkey() now only runs when dictation_hotkey changes, a trigger-only edit can leave the old dictation/combo registration active until restart.

let dictation_shortcut_changed = previous.dictation_hotkey != prefs.dictation_hotkey;
let dictation_mode_changed = previous.hotkey.mode != prefs.hotkey.mode;
let qa_changed = previous.qa_hotkey != prefs.qa_hotkey;
let translation_changed = previous.translation_hotkey != prefs.translation_hotkey;
let switch_style_changed = previous.switch_style_hotkey != prefs.switch_style_hotkey;
let open_app_changed = previous.open_app_hotkey != prefs.open_app_hotkey;
coord.write_settings(prefs)?;
if dictation_shortcut_changed || dictation_mode_changed {
    coord.refresh_dictation_hotkey();
}
if dictation_shortcut_changed {
    coord.refresh_combo_hotkey();
}

@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 691068f

@weikeyi weikeyi force-pushed the feat/windows-sherpa-onnx-local-asr branch from 691068f to 55c4531 Compare May 20, 2026 14:30
@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 55c4531

@H-Chris233
Copy link
Copy Markdown
Collaborator

那啥,你搞完了踢我一下
我就准备合

@weikeyi weikeyi force-pushed the feat/windows-sherpa-onnx-local-asr branch from 55c4531 to 7072c30 Compare May 21, 2026 01:49
@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 7072c30

@weikeyi
Copy link
Copy Markdown
Contributor Author

weikeyi commented May 21, 2026

@H-Chris233 冲突解决完了

@H-Chris233
Copy link
Copy Markdown
Collaborator

对于 release-archive 模型,此函数只检查归档文件本身。下载成功后,归档会被解压并删除,因此尽管模型文件已经存在,downloaded_bytes 仍会回落为 0。这会导致 UI 和任何状态逻辑将已完整下载的模型报告为缓存为空。

weikeyi added 2 commits May 21, 2026 18:37
…-onnx-local-asr

# Conflicts:
#	openless-all/app/src/i18n/en.ts
#	openless-all/app/src/i18n/ja.ts
#	openless-all/app/src/i18n/ko.ts
#	openless-all/app/src/i18n/zh-CN.ts
#	openless-all/app/src/i18n/zh-TW.ts
@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 83221df

@weikeyi
Copy link
Copy Markdown
Contributor Author

weikeyi commented May 21, 2026

@H-Chris233 已经修复,麻烦再看一下

@H-Chris233
Copy link
Copy Markdown
Collaborator

PR Reviewer Guide 🔍

(Review updated until commit 83221df)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Stale hotkey

The new change detection skips hotkey.trigger, so editing only the dictation trigger will not refresh the active bindings. Because refresh_combo_hotkey() now only runs when dictation_hotkey changes, a trigger-only edit can leave the old dictation/combo registration active until restart.

let dictation_shortcut_changed = previous.dictation_hotkey != prefs.dictation_hotkey;
let dictation_mode_changed = previous.hotkey.mode != prefs.hotkey.mode;
let qa_changed = previous.qa_hotkey != prefs.qa_hotkey;
let translation_changed = previous.translation_hotkey != prefs.translation_hotkey;
let switch_style_changed = previous.switch_style_hotkey != prefs.switch_style_hotkey;
let open_app_changed = previous.open_app_hotkey != prefs.open_app_hotkey;
coord.write_settings(prefs)?;
if dictation_shortcut_changed || dictation_mode_changed {
    coord.refresh_dictation_hotkey();
}
if dictation_shortcut_changed {
    coord.refresh_combo_hotkey();
}

@claude
这对吗?这个问题真的存在吗?检查一遍。

@claude
Copy link
Copy Markdown

claude Bot commented May 21, 2026

Claude finished @H-Chris233's task in 1m 47s —— View job


分析 Stale Hotkey 问题

  • 读取相关代码,验证问题是否真实存在
  • 分析 hotkey.trigger vs dictation_hotkey 的数据结构
  • 得出结论并报告

结论:这是误报,问题并不存在

Bot 的顾虑是:如果用户"只修改了 hotkey.trigger",则 refresh_combo_hotkey() 不会被调用,导致旧快捷键残留。

这个假设本身是错的。 关键在于 sync_dictation_hotkey_legacy_fields

// commands.rs:160-162
let mut previous = coord.read_settings();
sync_dictation_hotkey_legacy_fields(&mut previous);  // ← 对旧值同步
sync_dictation_hotkey_legacy_fields(&mut prefs);      // ← 对新值同步

这个 sync 函数的逻辑(commands.rs:2012-2028)是单向的:hotkey.trigger 始终由 dictation_hotkey 推导出来,而不是反过来:

fn sync_dictation_hotkey_legacy_fields(prefs: &mut UserPreferences) {
    if let Some(trigger) = legacy_modifier_trigger(&prefs.dictation_hotkey) {
        prefs.hotkey.trigger = trigger;   // trigger 由 dictation_hotkey 决定
        prefs.custom_combo_hotkey = None;
        return;
    }
    prefs.hotkey.trigger = HotkeyTrigger::Custom;  // 同上
    prefs.custom_combo_hotkey = ...;
}

sync 之后,hotkey.trigger 完全是 dictation_hotkey 的函数。用户无法在保持 dictation_hotkey 不变的情况下单独改变 sync 后的 hotkey.trigger——即便 JSON payload 里改了 hotkey.trigger,sync 也会把它覆盖回来。

因此:

  • dictation_hotkey 未变 → sync 后 hotkey.trigger 也未变 → dictation_shortcut_changed = false → 不调用 refresh_combo_hotkey() ✅ 正确
  • dictation_hotkey 有变 → dictation_shortcut_changed = truerefresh_combo_hotkey() 会被调用 ✅ 正确

另外,hotkey.mode(toggle/hold)是控制"按键触发方式"的,与 combo hotkey 的注册(监听哪个组合键)无关。update_combo_hotkey_bindingcoordinator.rs:532-581)根本不读 hotkey.mode,所以 mode 变化时不刷新 combo hotkey 也是正确行为

这段代码逻辑是正确的,无需修改。 Bot 没有追踪到 sync_dictation_hotkey_legacy_fields 的覆盖效果,导致了误判。

@weikeyi
Copy link
Copy Markdown
Contributor Author

weikeyi commented May 22, 2026

@H-Chris233

@H-Chris233 H-Chris233 merged commit 2fcf45b into Open-Less:beta May 22, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants