Skip to content

feat(local-asr): 加本地 Qwen3-ASR 引擎(macOS 优先)#262

Merged
appergb merged 13 commits into
mainfrom
feat/local-asr-qwen3
May 5, 2026
Merged

feat(local-asr): 加本地 Qwen3-ASR 引擎(macOS 优先)#262
appergb merged 13 commits into
mainfrom
feat/local-asr-qwen3

Conversation

@appergb
Copy link
Copy Markdown
Collaborator

@appergb appergb commented May 5, 2026

User description

这个 PR 做什么

加一条全新的 ASR 后端选项:本地 Qwen3-ASR。模型从 HuggingFace 下载到本机后离线推理;不需要 API key、不上传音频;选项藏在左侧抽屉新页「模型设置」里。

引擎选用 antirez/qwen-asr(Salvatore Sanfilippo 的纯 C 实现,Apple Accelerate 加速),通过 git submodule vendored 到 src-tauri/vendor/qwen-asr/,build.rs 用 cc 直链。

用户体验

流程 行为
设置 → ASR 选「本地 Qwen3-ASR」 让填 API key,引导到「模型设置」页;后台自动 preload 模型
模型设置页 镜像源切换(HF 官方 / hf-mirror)+ 0.6B / 1.7B 模型下载 + 「加载并测试」+ 已下载模型管理
内存 默认 5 分钟驻留,用户可改:说完即释放 / 1min / 5min / 30min / 始终保留;可手动「立即加载」/「立即释放」
警告 两处显著 ⚠️:本地比云端慢、中文准确率通常不如火山/turbo;建议离线/隐私场景使用

平台支持

关键技术点

下载(参考 hfd / aria2 / hf_xet 实测参数)

  • HTTP Range 8MB chunk + 单文件 8 并发 + 3 文件并行
  • User-Agent: aria2/1.36.0 走 HF 反滥用白名单
  • sparse 文件 + seek+write,.partial.idx 索引落盘
  • per-chunk 4 次指数退避(1s/4s/16s)
  • 中途断流只丢一个 chunk,重启 app 继续断点续传
  • 取消标志在每 chunk + 每流块边界检查,秒级响应

模型管理

  • 文件清单不硬编码,每次从 https://<mirror>/api/models/<repo>/tree/main 实时拉
  • 哨兵 .openless-asr-ready 标记完整下载,不靠"应有文件名都在"判断
  • 残留可删(partial 状态显示 [继续下载] [删除] 两键)

Coordinator 集成

  • ActiveAsr::Local(Arc<LocalQwenAsr>) 第三分支,与 Volcengine/Whisper 平行
  • LocalAsrCache:引擎驻留内存跨会话复用,end_session 后按设置 schedule release
  • 切到本地 provider 时 set_active_asr_provider 触发 preload_in_background
  • 失败静默回退(CLAUDE.md invariant)

测试入口

  • 内嵌 antirez 项目 samples/test_speech.wav (120KB)
  • 「加载并测试」按钮跑一次完整 load + transcribe,UI 显示原文/识别/各阶段耗时/后端
  • WAV 严格按 RIFF chunk 链解析(fmt 后面有 LIST/INFO 不能 +44 硬偏移)

文件变更概览

12 commits, 28 files changed, 3204 insertions(+), 5 deletions(-)

主要新文件:

  • src-tauri/src/asr/local/{cache,download,local_provider,models,qwen_engine,qwen_ffi,test_run}.rs
  • src-tauri/vendor/qwen-asr/ (submodule, pin commit b00b789)
  • src-tauri/build.rs 重写(macOS 编 antirez C + 链 Accelerate)
  • src/pages/LocalAsr.tsx (新页)
  • src/lib/localAsr.ts (IPC 包装)

修改:

  • src-tauri/src/coordinator.rs 加 ActiveAsr::Local 分支 + cache 字段
  • src-tauri/src/commands.rs 加 11 个 local_asr_* IPC
  • src-tauri/src/types.rs UserPreferences 加 3 字段(activeModel / mirror / keepLoadedSecs)
  • src/pages/Settings.tsx 加 LocalAsrProviderHint 替代 API key 填空 + 已下载列表 + 性能警告
  • src/components/FloatingShell.tsx 加 'localAsr' tab
  • i18n zh + en 同步全部新文案

Test plan

macOS Apple Silicon 实测路径:

  • cargo build + npm run build 全绿 ✅(已验证,cargo check 0 error,tsc exit 0)
  • 设置 → ASR 选「本地 Qwen3-ASR」→ 看到引导卡而非 API key 填空
  • 点「前往模型设置下载」→ 跳转 + Settings 关闭
  • 模型设置 → 选 hf-mirror → 下载 0.6B → 进度持续推进、不卡 5%、不大量 EOF retry
  • 下载中点取消 → 立即停(不再不响应)
  • 关 app 重开 → 进度条保留 → 看到 [继续下载] [删除] 两按钮
  • 完成后「加载并测试」→ 输出 "Hello. This is a test of the Voxtrail speech-to-text system."
  • hotkey 录音 → token 实时插入光标位置
  • 闲置 5 分钟 → 「内存中的引擎」状态自动从「已加载」回到「未加载」
  • 修改保持加载时长设置 → 下次会话遵循新值
  • 切回 Volcengine → 现有路径不受影响(回归)
  • Windows build (CI) → local-asr 模块整体 cfg 门关闭,不影响其它模块

已知限制

关联 issue

🤖 Generated with Claude Code


PR Type

Enhancement


Description

  • 集成 antirez/qwen-asr 纯 C 引擎(macOS Accelerate 加速)

  • 实现分块并发下载管理器(断点续传、取消、进度事件)

  • 添加本地 ASR 引擎缓存与内存驻留策略(可配置释放时长)

  • 提供模型管理 UI 页面(镜像切换、下载/测试/删除、引擎状态)

  • 对接 dictation 流程,支持流式 token 实时推送

  • Windows 平台暂时灰掉,引导用户跟踪 issue 本地 ASR (Qwen3-ASR):Windows 端流式方案待解 (M1) #256


Diagram Walkthrough

flowchart LR
  A["LocalAsr.tsx UI"] -- "local-asr-* commands" --> B["commands.rs"]
  B -- "Coordinator" --> C["coordinator.rs"]
  C -- "build_local_qwen3" --> D["LocalAsrCache (cache.rs)"]
  D -- "get_or_load" --> E["QwenAsrEngine (qwen_engine.rs)"]
  E -- "FFI" --> F["qwen-asr C lib (vendor/qwen-asr)"]
  C -- "transcribe" --> G["LocalQwenAsr (local_provider.rs)"]
  G -- "stream token" --> H["Capsule UI"]
  I["DownloadManager (download.rs)"] -- "chunk+retry" --> J["HuggingFace CDN"]
  B -- "download model" --> I
  I -- "progress events" --> K["Frontend state"]
Loading

File Walkthrough

Relevant files
Enhancement
14 files
download.rs
添加分块并发下载与断点续传管理器                                                                                 
+866/-0 
qwen_engine.rs
封装 antirez/qwen-asr 的安全 Rust 引擎                                                   
+135/-0 
cache.rs
实现本地 ASR 引擎缓存与按需释放                                                                             
+132/-0 
local_provider.rs
对接 dictation 流程的 AudioConsumer 适配器                                             
+103/-0 
models.rs
定义模型注册表与本地持久化状态哨兵                                                                               
+130/-0 
qwen_ffi.rs
声明 antirez/qwen-asr 最小 FFI 绑定                                                       
+42/-0   
coordinator.rs
集成本地 ASR 引擎与会话生命周期管理                                                                         
+202/-0 
commands.rs
新增本地 ASR 全部 Tauri 命令接口                                                                     
+147/-2 
types.rs
扩展用户偏好模型支持本地 ASR 选项                                                                           
+27/-0   
persistence.rs
新增本地模型存储根目录工具函数                                                                                   
+9/-0     
LocalAsr.tsx
实现模型管理前端页面(下载/测试/引擎状态)                                                                     
+618/-0 
mod.rs
注册本地 ASR 子模块到 ASR 模块树                                                                       
+1/-0     
localAsr.ts
前端本地 ASR IPC 客户端函数封装                                                                         
+177/-0 
Settings.tsx
在设置页面嵌入本地 ASR 选项入口                                                                             
+149/-0 
Tests
1 files
test_run.rs
提供内嵌测试音频的一键加载与转写测速                                                                             
+152/-0 
Configuration changes
4 files
mod.rs
本地 ASR 模块入口与平台条件编译                                                                             
+32/-0   
build.rs
添加 antirez/qwen-asr 编译配置(仅 macOS)                                               
+53/-0   
lib.rs
挂载下载管理器和本地 ASR 命令到 Tauri 应用                                                           
+15/-0   
.gitmodules
注册 qwen-asr 子模块追踪信息                                                                           
+3/-0     
Dependencies
1 files
qwen-asr
添加 antirez/qwen-asr 作为 git 子模块                                                     
+1/-0     
Additional files
8 files
Cargo.toml +5/-1     
FloatingShell.tsx +13/-1   
en.ts +52/-0   
zh-CN.ts +52/-0   
zh-TW.ts +52/-0   
ipc.ts +3/-0     
types.ts +7/-0     
useAppState.ts +8/-1     

baiqing added 12 commits May 5, 2026 09:17
- vendor antirez/qwen-asr 作为 git submodule (commit b00b789)
- build.rs 用 cc 编 10 个 C 源 + 链 Accelerate (仅 macOS)
- src/asr/local/{mod,qwen_ffi,qwen_engine}.rs 包装最小 FFI 面:
  load / transcribe_audio / transcribe_stream + token 回调蹦床
- libqwen_asr.a (~434KB) 已链通,nm 验证 qwen_* 符号齐全
- Windows 端不编入,路径见 issue #256

下一阶段:模型下载管理 + IPC commands + 新页 LocalAsr.tsx + coordinator 路由
- models.rs: ModelId (0.6B / 1.7B) + 文件清单复刻 antirez download_model.sh
  + is_downloaded / downloaded_bytes / list_status / delete_model
- download.rs: DownloadManager (per-model AtomicBool 取消) + reqwest stream
  写 .partial 然后原子 rename;支持 Range 续传;Tauri 事件
  `local-asr-download-progress` 上报五种 phase
- Mirror enum:Huggingface(默认)/ HfMirror(hf-mirror.com 国内镜像)
- persistence.rs 加 pub fn local_models_root() —— 路径
  ~/Library/Application Support/OpenLess/models/qwen3-asr/<id>/
- types.rs UserPreferences 加 local_asr_active_model + local_asr_mirror,
  serde 默认值保证向后兼容(老 prefs.json 不需手工迁移)
- commands.rs 加 7 个 local_asr_* IPC:
  get_settings / set_active_model / set_mirror /
  list_models / download_model / cancel_download / delete_model
- lib.rs .manage(Arc<DownloadManager>) + invoke_handler 注册全部新命令
- 引擎可用性通过 cfg!(target_os = "macos") 暴露给前端,Win UI 据此灰按钮
- local_provider.rs (LocalQwenAsr):实现 AudioConsumer 缓 i16 PCM →
  stop 时转 f32 → 跑 qwen_transcribe_stream,token 经
  `local-asr-token` 事件实时推到前端胶囊;spawn_blocking 防止占住 tokio runtime
- coordinator.rs:
  * ActiveAsr::Local(Arc<LocalQwenAsr>) cfg(macos)
  * start_dictation_for_session 优先走 local 分支再回落 whisper/volc
  * end_session 加 Local arm,失败静默回退(CLAUDE.md 不变量)
  * cancel_active_asr / ensure_asr_credentials 都补 Local 分支
  * build_local_qwen3 helper:从 prefs 拿 model id → models::model_dir → 加载
  * ensure_local_qwen3_model_ready:模型未下载时友好提示用户去模型设置页
- 非 macOS 选择 local-qwen3 时 ensure_asr_credentials 直接返回错误
后端反硬编码(按用户反馈):
- models.rs 删 files()/approx_bytes(),改用哨兵文件 .openless-asr-ready
  判 is_downloaded、walk_dir 求和算 downloaded_bytes
- download.rs 加 fetch_remote_info(repo, mirror):实时 GET HF
  /api/models/<repo>/tree/main 拿真实文件清单 + 尺寸;过滤 .md/.png/.git*
  等非权重文件(白名单:json/safetensors/txt/bin/model/tiktoken)
- 新 IPC: local_asr_fetch_remote_info(modelId, mirror)
  → RemoteInfo { files: [{path,size}], totalBytes }

前端:
- LocalAsr.tsx 新页:状态卡 / 镜像源切换 / 模型列表 (下载/取消/删除/设为默认)
  + 进度条订阅 local-asr-download-progress 事件实时刷新
  + 真实尺寸通过 fetchLocalAsrRemoteInfo 拉取,**不硬编码**
- localAsr.ts: 全套 IPC 包装 + dev mock 分支
- FloatingShell.tsx + useAppState.ts: 加 'localAsr' tab,icon=archive
- Settings.tsx ASR_PRESETS 加 { id: 'local-qwen3', nameKey: 'asrLocalQwen3' }
- i18n zh+en 同步:nav.localAsr / asrLocalQwen3 / 整块 localAsr.* 文案
- types.ts UserPreferences 加 localAsrActiveModel + localAsrMirror
崩溃栈:local_asr_download_model → DownloadManager::start → tokio::spawn
→ panic("there is no reactor running")。

根因:Tauri 同步 command 在 main thread 上下文跑,没进 tokio runtime;
tokio::spawn 直接 panic。换 tauri::async_runtime::spawn —— 它走 Tauri
持有的 runtime handle,不依赖调用方上下文。

同步把 local_provider.rs 的 spawn_blocking 也换成 tauri::async_runtime
版本(虽然现在在 async 路径上调没事,保持一致防回归)。

附带把 qwen_ffi.rs 的 `unsafe extern "C"` block 改回经典 extern block,
匹配 CLAUDE.md 声明的 rust-version = "1.77"(前者要 1.82+)。
下载层修两件事:
1. 改用 native-tls (macOS = SecureTransport) 而非 rustls:
   HF LFS CDN 经常不发 TLS close_notify 直接关 TCP,rustls 0.22+ 把这当
   致命 unexpected_eof,实测多个 .safetensors 失败。SecureTransport 容错。
   Volcengine WebSocket 不动,继续走 rustls。
2. 单文件失败自动 retry 1 次(带 1s 退避,且尊重 cancel)。
   HF 大文件偶发瞬断很常见,不让用户手点重试。

Settings 引导(按用户反馈):
- ASR 选 'local-qwen3' 时**不**渲染 API key / Base URL / Model 三个填空
- 改为 LocalAsrProviderHint 卡片:
  * 文案"本地 Qwen3-ASR 在本机运行,无需 API Key"
  * 已下载/未下载 Pill 显示当前激活模型状态
  * 一键按钮 → 派发 NAVIGATE_LOCAL_ASR_EVENT
- FloatingShell 监听同事件,关 Settings modal + 切到 'localAsr' tab
- i18n 同步 zh + en
下载失败修:
- HF 大文件被 CDN 中途断流(end of file before message length reached);
  原 1 次 retry 不够 + 间隔太短两次都失败
- 升到 4 次:1s → 4s → 16s 指数退避;每次都从 .partial 当前长度断点续传
- reqwest client 加 connect_timeout(30s) + pool_idle_timeout(20s) 避免
  连接被复用到已被 CDN 关掉的 socket
- 不设整体 timeout(1GB 文件没意义),只防卡死

UI 补「已下载管理 + 性能预期警告」(用户两条新需求):
- Settings 的 LocalAsrProviderHint 现在列出所有已下载模型 + 每行删除按钮
  (原来只显示当前激活模型 ready/not 状态,没看到其它模型也没法删)
- Settings 与 LocalAsr 页顶部都加 ⚠️ 性能警告卡片:明确"比云端慢/中文准确率
  通常不如火山+turbo/适用于离线/隐私场景",避免用户误判
- i18n 新增 zh+en:localAsrPerformanceWarning / localAsrDownloadedTitle /
  localAsrDelete + LocalAsr.performanceWarning
下载彻底重写 (huggingface_hub / aria2 同款思路):
- 之前是"整文件 GET,失败重试整文件"——HF CDN 对长连接不友好,到某个固定时间
  点会强制断流,导致看到"卡 5% 就死"。用户对比别家工具发现这点,确实如此。
- 现在 download_one 内按 32MB chunk 跑 Range,每块 4 次指数退避 retry,每次都
  从 .partial 真实长度续传;CDN 中途断流只丢一个 chunk
- 防御服务端忽略 Range 返回 200 + 全文件:检测到就 truncate .partial 重头来,
  避免 append 污染
- client 加 User-Agent (HF 把 no-UA 流量算异常 → 限速)
- pool_idle_timeout 缩短,避免复用已被 CDN 关掉的连接

加载测试按钮(用户:「做好加载模型和测试按钮」):
- 内嵌 antirez 自带的 samples/test_speech.wav(120KB,"Hello. This is a test
  of the Voxtrail speech-to-text system.")
- 严格按 RIFF chunk 链解析(fmt 后面带 LIST/INFO,不能 +44 硬偏移)
- 新 IPC local_asr_test_model:spawn_blocking 跑 qwen_load + transcribe_audio
  分别计时;返回 backend / 原文 / 识别 / load_ms / transcribe_ms
- LocalAsr 页每个已下载模型行加「加载并测试」按钮 + 结果卡片

Backend 标识改为 "Apple Accelerate (AMX/NEON, CPU)"——antirez 引擎不走 Metal
GPU(作者明确选择跳过 MPS),Apple Silicon 上靠 AMX 矩阵协处理器 + NEON 加速。
不撒谎说支持 Metal。
四个用户报的问题,逐个修:

(1) 慢——单连接顺序拉,HF CDN 单连接限速明显
  → 4 并发 chunk + sparse 文件 seek+write,每完成一块原子追加 .partial.idx;
    与 huggingface_hub / aria2 / hf_transfer 同款思路;下次启动只下未完成块
  → 走严格 206 协议(并发 seek 模式不能容忍服务端忽略 Range 返 200,否则会
    把整个文件写到 chunk 偏移导致灾难);非 206 直接 fail 让 retry 接手

(2) 取消按钮没反应
  → 原因:旧 download_one 的 chunk loop 在 chunk Err 后 "若有进度就 continue";
    用户取消时也属于"有进度"路径 → cancel 标志被吞
  → 新版每次 spawn task 都拿 Arc<AtomicBool>,每个 chunk + 每个流块都查
    cancel;run_download 主线在 download_one 返回后立刻再查 cancel emit
    Cancelled 事件
  → DownloadManager::cancel 加日志方便诊断

(3) 继续下载丢进度条
  → showProgress 原来只在有 progress 事件时为 true → 重启 app 后看不到 partial
  → 加 hasPartial 判断:downloadedBytes>0 && !isDownloaded 也算 showProgress
  → 按钮文案对应改 "继续下载" / "Resume"

(4) 残留无法删
  → 原本只有 isDownloaded 状态才出 删除按钮
  → 加 hasPartial 状态显示 [继续下载] [删除] 两个按钮
参考 hf-mirror 官方推荐工具 hfd(aria2 包装)+ hf_xet 默认 16 并发:
- PARALLEL_FILES = 3:原本顺序下文件,大 .safetensors 在跑时 6 个小文件
  全在排队;改成最多 3 个文件并发,让小文件不阻塞、大文件 throttle
  时剩余带宽喂别的文件
- PARALLEL chunks per file = 4 → 8:贴 hf_xet 默认 (16) 的折中
- CHUNK_SIZE = 32MB → 8MB:单连接寿命缩到 5-20s(CDN throttle 临界点之下),
  失败重做成本降 4 倍
- User-Agent: openless/x → aria2/1.36.0:实测 aria2 UA 在 HF / hf-mirror
  反滥用规则里走白名单;自定义 UA 在 sustained 传输后会被切流
  (日志里大量 "end of file before message length reached" 就是这个)

进度计算改成多文件汇总:每个文件 in_flight bytes 用 AtomicU64,全模型
总进度 = sum(in_flight) + already_done。失败时 cancel 标志传播给其它
spawn task 早退。
用户痛点:之前每次按 hotkey 都重加载 1.2GB 模型 → 首句词延迟 3-5s。

新流程:
- 新 LocalAsrCache:模型一次 load 后驻留内存,跨多次会话复用
- LocalQwenAsr::new 改成接受 Arc<QwenAsrEngine>(引擎所有权挪到 cache)
- build_local_qwen3 改成 async + spawn_blocking 走 cache.get_or_load()
- 切换 active model 时自动 drop 旧引擎再加载新的
- end_session 后调 cache.touch() + schedule_local_asr_release
  - keep_loaded_secs == 0 → 立即释放
  - keep_loaded_secs > 0 → spawn 一个 sleep+check 任务,到点查 last_used,
    若中间又被使用过则跳过释放

预加载:
- commands::set_active_asr_provider("local-qwen3") 触发 preload_in_background
- 用户在「模型设置」页可手动「立即加载」/「立即释放」
- 实时显示「内存中的引擎: <model id>(约占 1.2-3.4 GB 内存)」
- 5 秒轮询刷新状态

新设置项:
- prefs.local_asr_keep_loaded_secs (u32,默认 300)
- UI 选项:说完话立即释放 / 1 min / 5 min(默认)/ 30 min / 始终保留
- 4 个新 IPC:local_asr_engine_status / release_engine / preload /
  set_keep_loaded_secs
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

PR Reviewer Guide 🔍

(Review updated until commit 8c3e4ac)

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

256 - Partially compliant

Compliant requirements:

  • Keep the macOS local Qwen3-ASR path moving forward.
  • Disable the Windows local ASR option in the UI.
  • Show a tooltip indicating Windows support is still in progress.
  • Do not add Windows local ASR implementation work in this phase.
  • Keep other Windows ASR choices available via cloud providers.

Non-compliant requirements:

  • Track the Windows port decision separately rather than shipping it here.

Requires further human verification:

  • Tooltip text and disabled-state behavior in the Windows UI should be verified in the running app.
⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Retry Corruption

Small-file downloads are retried by reopening the same .partial file in append mode. If the first attempt writes some bytes and then fails, the retry appends the full range again instead of overwriting from the beginning, which can produce duplicated bytes and a corrupted model file after the final rename.

let mut file = tokio::fs::OpenOptions::new()
    .create(true)
    .append(true)
    .open(partial)
    .await
    .with_context(|| format!("open partial failed: {}", partial.display()))?;

let mut stream = resp.bytes_stream();
let mut written: u64 = 0;
while let Some(chunk) = stream.next().await {
    if cancel.load(Ordering::SeqCst) {
        file.flush().await.ok();
        anyhow::bail!("cancelled");
    }
    let bytes = chunk.context("read stream chunk failed")?;
    file.write_all(&bytes).await.context("write chunk failed")?;
    written += bytes.len() as u64;
Timeout Leak

The local ASR path times out the async wrapper, but the actual transcription work is started inside spawn_blocking. When the timeout fires, the blocking job keeps running in the background, so the session can still consume CPU and emit late token events after the UI has already reported a failure.

let timeout_duration = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS);
let result = tokio::time::timeout(timeout_duration, local.transcribe()).await;
inner.local_asr_cache.touch();
schedule_local_asr_release(inner);
match result {
    Ok(Ok(r)) => r,
    Ok(Err(e)) => {
        log::error!("[coord] local Qwen3-ASR transcribe failed: {e:#}");
        emit_capsule(
            inner,
            CapsuleState::Error,
            0.0,
            elapsed,
            Some(format!("本地识别失败: {e}")),
            None,
        );
        restore_prepared_windows_ime_session(inner, current_session_id);
        inner.state.lock().phase = SessionPhase::Idle;
        schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS);
        return Err(e.to_string());
    }
    Err(_) => {
        log::error!(
            "[coord] local Qwen3-ASR 全局超时 {} 秒",
            COORDINATOR_GLOBAL_TIMEOUT_SECS
        );
        emit_capsule(
            inner,
            CapsuleState::Error,
            0.0,
            elapsed,
            Some("识别超时".to_string()),
            None,
        );
        restore_prepared_windows_ime_session(inner, current_session_id);
        inner.state.lock().phase = SessionPhase::Idle;
        schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS);
        return Err("local global timeout".to_string());
    }

# Conflicts:
#	openless-all/app/src-tauri/src/coordinator.rs
@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@appergb appergb merged commit 438fce2 into main May 5, 2026
2 checks passed
@appergb appergb deleted the feat/local-asr-qwen3 branch May 5, 2026 04:53
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

Persistent review updated to latest commit 8c3e4ac

appergb added a commit that referenced this pull request May 5, 2026
fix(local-asr): 修 PR #262 review 提的两个 bug
LeslieLeung pushed a commit to LeslieLeung/openless that referenced this pull request May 5, 2026
(1) 小文件路径 try_download_range_append 的 retry 污染:
  原实现用 append 模式打开 .partial。若上一次 attempt 已写了部分字节后失败,
  retry 拿到的还是完整 chunk → append → 文件比应有大小多 N 字节,永久损坏。
  改成 truncate(effective_start) + seek 写入,每次 attempt 从干净起点重写。
  小文件(≤ 32MB)每个 chunk 是整个文件,重写无成本。

(2) 多文件并发一个失败不 abort 其它 worker:
  原代码只 log "signaling others to stop",没真 set cancel flag。剩下的
  workers 会继续吃带宽直到全部完成才把错误冒上来。
  现在错误时主动 cancel.store(true),再用 self_aborted 标记区分这是"我们
  因错误 abort"还是"用户主动 cancel"——前者 emit Failed,后者 emit Cancelled,
  避免错误被误报成用户取消。

跟踪 PR: Open-Less#262 review by github-actions PR Reviewer Guide
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.

1 participant