Commit Graph
446 Commits
Author SHA1 Message Date
baiqing 81fa80da69 fix(windows): NSIS x64 dll 用 Sysnative 绕过 WOW64 重定向
NSIS installer 是 32-bit 进程,进程内的 $SYSDIR / $WINDIR\System32 都因
WOW64 文件系统重定向变成 $WINDIR\SysWOW64,跑的是 32-bit regsvr32 → x64 dll
被注册到 HKLM\Software\Wow6432Node\Classes\CLSID(KEY_WOW64_32KEY 视图)。
但 windows_ime_profile.rs 用 KEY_WOW64_64KEY 校验时只看 HKLM\Software\Classes
\CLSID,永远查不到 → 仍判 RegistrationBroken("OpenLess COM registration is
missing")。

修复:x64 改用 $WINDIR\Sysnative\regsvr32.exe — 这是 32-bit 进程访问真
64-bit System32 的专用 alias,写入会落在 KEY_WOW64_64KEY 视图。x86 保持
SysWOW64 不变(确实需要 32-bit regsvr32 写 Wow6432Node)。
2026-05-06 10:52:12 +08:00
baiqing c52658b08b fix(windows): NSIS 也注册 32-bit TSF DLL — 修 P1 review
Codex review 发现:windows_ime_profile.rs::inspect_windows_ime_registration() 同时
用 KEY_WOW64_64KEY 和 KEY_WOW64_32KEY 两次检查 HKLM\Software\Classes\CLSID\{...}\
InprocServer32,少了任一边就返回 RegistrationBroken("OpenLess 32-bit COM
registration is missing")。MSI 流程通过 wxs 同时跑了 64-bit 和 SysWOW64\regsvr32
注册两份;之前的 NSIS hook 只注册了 x64,所以 NSIS / updater 用户依然在 Settings
页看到「不可用」。

修复:
- bundle.resources 拆出 x64 / x86 两份 dll target;x64 → tsf-ime\x64,x86 → tsf-ime\x86
- installer.nsh PostInstall/PreUninstall 增加 x86 分支,
  x86 用 $WINDIR\SysWOW64\regsvr32.exe(写 KEY_WOW64_32KEY 必需)
- CI Build Windows IME native DLLs 步骤把复制 dll 移到循环里,x64 + x86 都覆盖
  仓库占位
- 仓库新增 0 字节 x86 占位 dll,让本地 mac/linux 构建也能 resolve resources
2026-05-06 10:47:03 +08:00
baiqing 5aec74653b fix(windows): NSIS 安装包注册 OpenLess TSF DLL — 修"输入法后端:不可用"
## 现象
1.2.15 起在 Settings 页"Windows 输入法后端"出现 Pill「不可用」。`get_windows_ime_status()`
查 HKLM 注册表四件套(COM CLSID + TSF LanguageProfile + 三个 Category),缺一即报 NotInstalled。

## 根因
- MSI 通过 wix/openless-ime.wxs 的 RegisterOpenLessIme* CustomAction 跑 regsvr32 → 注册齐全。
- NSIS 包既没把 OpenLessIme.dll 打进去(wxs.componentRefs 是 MSI-only),也没 hook 跑
  regsvr32 → 用 setup.exe 安装的用户、走 updater 自动升级的存量用户全部命中"不可用"。
- 由于 updater latest-windows-x86_64.json 是 NSIS pass 写的,老 MSI 用户每次更新后也
  会被替换为没 TSF 的 NSIS 版本。

## 修复
- 加 src-tauri/installer.nsh:NSIS_HOOK_POSTINSTALL/PREUNINSTALL 跑 regsvr32 注册/反注册。
- bundle.resources map 把 OpenLessIme.dll 拷贝到 $INSTDIR\tsf-ime\OpenLessIme.dll
  (target 路径避开 wxs File 已声明的 windows-ime\x64\,避免 MSI component 路径冲突)。
- bundle.windows.nsis.installMode = "perMachine",让 NSIS 以管理员身份运行(写 HKLM 必需)。
- CI 在 build native dll 后把真 dll 复制到 src-tauri/openless-ime-payload/x64/OpenLessIme.dll;
  仓库 commit 0 字节占位文件,让本地 mac 端 tauri build 也能 resolve resources 路径。
- build-mac.sh 与 Linux build 步骤加 --config '{"bundle":{"resources":{}}}' 跳过 Windows-only
  的 dll 占位,避免污染 mac .app / Linux .deb/.rpm/.AppImage。

## Trade-off
- NSIS perMachine 升级需要 UAC,老 currentUser 用户首次升级时多一次 UAC 提示。
- MSI 包里 dll 会有两份(wxs File + bundle.resources),磁盘冗余 ~几百 KB,无功能影响。
2026-05-06 10:41:07 +08:00
baiqing 463a6d9716 fix(local-asr): 切走/删模型/手动释放时真正归还 RAM
三个问题合修:

1. 用户点"释放"按钮后活动监视器看 RSS 不下降。原因:encoder f32 weights
   是 malloc 出来的几百 MB,free 后 macOS libmalloc 不会立即把页归还内核。
   修:drop QwenAsrEngine 后调 malloc_zone_pressure_relief(NULL, 0),主动
   归还。decoder bf16 走 mmap 不依赖这个。

2. 切回云端 ASR 后本地引擎仍驻留。set_active_asr_provider 切到非 local
   时未释放,1.2GB+ RAM 一直留到进程退出。

3. 删除已下载的模型时未释放内存里加载的同 id 引擎,留下 mmap 指向已
   unlink 文件 + RAM 不归还的双重问题。

Windows 端本期不上线(cache.rs macOS cfg 块包住改动)。
2026-05-05 20:20:46 +08:00
TRIP c85b1bd4c6 Merge pull request #263 from appergb/fix/local-asr-pr-review-fixes
fix(local-asr): 修 PR #262 review 提的两个 bug
2026-05-05 13:36:55 +08:00
baiqing cd00ebc584 fix(local-asr): 修 PR #262 review 提的两个 bug
(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: #262 review by github-actions PR Reviewer Guide
2026-05-05 12:59:01 +08:00
TRIP 438fce26ab Merge pull request #262 from appergb/feat/local-asr-qwen3
feat(local-asr): 加本地 Qwen3-ASR 引擎(macOS 优先)
2026-05-05 12:53:35 +08:00
baiqing 8c3e4aca0d Merge remote-tracking branch 'origin/main' into feat/local-asr-qwen3
# Conflicts:
#	openless-all/app/src-tauri/src/coordinator.rs
2026-05-05 12:48:13 +08:00
Chris233 a0cc88d2ac Merge pull request #260 from weikeyi/capsule-confirm-overflow
fix(capsule): keep confirm icon inside pill on done/error states
2026-05-05 12:46:39 +08:00
Chris233 f984dec84b Merge pull request #261 from sora520/fix/windows-hotkey-trigger-leak
fix(windows): suppress consumed hotkey trigger events
2026-05-05 12:42:27 +08:00
Chris233 3e1ba69bfc Merge pull request #252 from Cooper-X-Oak/fix/recorder-timeout-238
fix(recorder): 添加 liveness watchdog 和全局超时保护以修复 #238
2026-05-05 12:32:37 +08:00
kaku.ki 9c0ae1495a fix(windows): suppress consumed hotkey trigger events 2026-05-05 13:17:50 +09:00
baiqing 520787b0f7 feat(local-asr): 引擎内存常驻 + 切到本地时预加载 + 释放时机可配
用户痛点:之前每次按 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
2026-05-05 12:09:55 +08:00
baiqing 8ed674ecaf fix(local-asr): 多文件并发 + 8MB 小块 + aria2 UA — 对齐 hfd/hf_xet 实测
参考 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 早退。
2026-05-05 11:33:46 +08:00
weikeyi dab04fc345 fix(capsule): keep confirm icon inside pill on done/error states 2026-05-05 11:17:49 +08:00
baiqing 209dc9fece fix(local-asr): 4 并发分块下载 + 取消正确传播 + 残留进度可见可删
四个用户报的问题,逐个修:

(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 状态显示 [继续下载] [删除] 两个按钮
2026-05-05 11:13:41 +08:00
Chris233 c5aa3fef4a Merge pull request #259 from H-Chris233/main
feat(i18n): add full Traditional Chinese (zh-TW) UI support
2026-05-05 11:07:37 +08:00
Chris233 2a472edc59 Merge pull request #253 from sora520/fix/windows-capsule-visuals
fix(windows): stabilize capsule visuals and audio meter
2026-05-05 11:06:09 +08:00
kaku.ki 23d7f44a96 fix(windows): stabilize capsule visuals and audio meter 2026-05-05 11:51:57 +09:00
H-Chris233 e50bc17a15 Add complete Traditional Chinese UI locale support
Add zh-TW locale resources and wire it through language selection,
persistence, and system-locale detection. Also add static shape typing
for zh-TW to prevent future key drift from zh-CN.

Constraint: Keep changes minimal and localized to i18n + language pickers
Rejected: Partial zh-TW translation only for language section | fails full locale support goal
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep zh-TW key shape locked to zh-CN when adding new translation keys
Tested: npm run -s build (openless-all/app)
Not-tested: Manual runtime walkthrough on all platform shells
2026-05-05 10:51:46 +08:00
baiqing 4a3531083b feat(local-asr): HTTP Range 分块下载 + 加载测试按钮
下载彻底重写 (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。
2026-05-05 10:27:48 +08:00
baiqing 25d9e9f3da fix(local-asr): 下载 retry 升级 4 次指数退避 + 已下载列表/删除/性能警告
下载失败修:
- 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
2026-05-05 10:03:26 +08:00
baiqing 5a58f74c4a fix(local-asr): HF 下载 TLS unexpected_eof + Settings 引导改下载入口
下载层修两件事:
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
2026-05-05 09:56:54 +08:00
baiqing 839ad76c5b fix(local-asr): 用 tauri::async_runtime 取代 tokio::spawn — 修点击下载即崩
崩溃栈: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+)。
2026-05-05 09:49:33 +08:00
baiqing dd43a7801d feat(local-asr): 前端「模型设置」页 + Settings 预设 + 反硬编码重构 — P5
后端反硬编码(按用户反馈):
- 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
2026-05-05 09:39:26 +08:00
baiqing 4656ecc7c7 feat(local-asr): coordinator 接 ActiveAsr::Local 第三分支 — P4
- 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 直接返回错误
2026-05-05 09:29:12 +08:00
baiqing 51b638a52e feat(local-asr): IPC commands + 偏好持久化 — P3
- 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 据此灰按钮
2026-05-05 09:26:46 +08:00
baiqing 7b38e26473 feat(local-asr): 加模型注册表 + HF/HF-Mirror 下载管理 — P2
- 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>/
2026-05-05 09:24:27 +08:00
baiqing bd3bac3ca6 feat(local-asr): 接入 antirez/qwen-asr 编译链路 — 阶段 1 脚手架
- 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 路由
2026-05-05 09:17:32 +08:00
baiqing 003b713d52 docs(agents): 写入 Windows CI 红线,避免下一个 agent 重复踩四颗雷
修复迭代里翻过四个不可合并的雷:
1. light.exe 调用要 -sice:ICE80(IME wxs 的 32-bit Component 落 64-bit Directory)
2. tauri build 必须拆 NSIS+MSI 两轮(Tauri 签名是 post-bundle 钩子)
3. Build (Windows) shell 必须 bash 不是 pwsh(pwsh 吃 JSON 引号)
4. Repair 假设 candle 已跑出 wixobj

把每条的 ✗ 反例写出来,附上 'CI 失败先看历史日志' 的固定动作和
'保持 bump+tag 流程' 的偏好声明,下一个 agent 就不需要从头复盘。
2026-05-05 07:25:03 +08:00
cooperandClaude Sonnet 4.6 27451298da audit(system): 完成系统级多尺度审计 - 发现 3 大架构风险和 13 项技术债务
建立多尺度审计框架(4 个尺度):
- 尺度 1: 系统级(架构、安全、扩展性)
- 尺度 2: 模块级(设计、接口、依赖)
- 尺度 3: 功能级(实现、体验、边界)
- 尺度 4: 代码级(bug、风格、性能)

**系统级审计关键发现**:

架构风险(3 项):
- 🔴 Coordinator 过于庞大(3462 行)
- 🔴 缺少统一的 ASR Provider trait
- 🔴 测试基础设施缺失(覆盖率接近 0%)

技术债务(13 项):
- P0: 2 项(测试相关)
- P1: 5 项(架构 + 测试 + 代码)
- P2: 4 项(架构 + 文档 + 代码)
- P3: 2 项(文档)
- 预计偿还成本: 14 周(3.5 个月)

**关键决策**:
1. ✅ 需要架构重构
2. ⏸️  暂停低尺度审计(先解决高尺度问题)

**下一步行动**:
1. 编写测试策略文档
2. 编写 Coordinator 拆分设计文档
3. 编写 ASR Provider trait 设计文档

**审计产出**:
- 多尺度审计框架文档
- 架构风险地图
- 技术债务矩阵
- 系统级审计总结
- 系统级审计脚本

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 23:16:12 +08:00
baiqing 149a6b8eca chore(release): 1.2.19 — Windows updater 修 NSIS+MSI 拆两轮跑 2026-05-04 23:12:55 +08:00
baiqing 0bd338a94c ci(release): split Windows tauri build into NSIS + MSI passes
Tauri 的签名 / updater artifact 是 post-bundle 钩子,单次 tauri build 内
任意 bundler 失败都会跳过 .sig 生成。MSI 必踩 ICE80,所以单 pass 永远
拿不到 NSIS 的 .exe.sig(v1.2.18 build 验证)。

拆两轮:
- Pass 1: --bundles nsis 单独跑,必须成功,产出 .exe + .exe.sig
- Pass 2: --bundles msi 单独跑,允许 ICE80 失败,Repair 用 light 重链兜底

不 bump 版本号——下一次 release 之前用 workflow_dispatch 验证 CI 全绿。
2026-05-04 23:11:41 +08:00
cooperandClaude Sonnet 4.6 7e7b200198 docs(epic): 建立 Cooper 贡献体系 - 两大 EPIC 母体与 Finding 流程
建立完整的贡献规划体系:

**两大 EPIC 母体**:
- EPIC-001: 测试基础设施建设 (41 tasks, 6 weeks)
  - 目标:测试覆盖率 0% → 60%+
  - Phase 1: 核心模块单元测试
  - Phase 2: 集成测试
  - Phase 3: CI 自动化

- EPIC-002: ASR 功能扩展与优化 (71 tasks, 6 weeks)
  - Phase 1: 混淆词纠错层 (对应 #89)
  - Phase 2: 本地 ASR 支持 (对应 #211)
  - Phase 3: ASR 架构优化

**Finding 基础设施**:
- finding-helper.sh: 自动化分析脚本
- 生成 4 份 finding 报告:
  - 测试覆盖率分析
  - ASR 模块分析
  - 依赖关系分析
  - Finding 总结

**工作流程文档**:
- COOPER_README.md: 快速开始指南
- COOPER_WORKFLOW.md: 详细工作流程
- COOPER_CONTRIBUTION_STRATEGY.md: 贡献策略分析

**关键指标**:
- 包含测试的文件数: 15
- 测试函数数: 76
- ASR 模块代码量: 1164 行

下一步:开始实现混淆词纠错层(EPIC-002 Phase 1)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 23:00:25 +08:00
baiqing 429780b5e4 chore(release): 1.2.18 — Windows bundle 顺序锁 nsis,msi 让 updater 资产先落盘
历史链:
  1.2.13 ✓  无 IME wxs,MSI+NSIS 全过
  1.2.15 ✗  加 IME wxs;CI pwsh 把 JSON 引号吃掉,tauri 连 candle 都没跑
  1.2.16 ✗  shell 切 bash 后到 light 阶段,但 ICE80 触发,Repair 没 -sice
  1.2.17 ✗  Repair 加了 -sice:ICE80 修了 MSI;但 Tauri 字母序 msi→nsis 让
            MSI 先挂掉,NSIS 永远没机会跑,updater .exe(.sig) 缺失
  1.2.18    显式 --bundles nsis,msi:NSIS 先写盘产 updater 资产,MSI 再失败
            由 Repair 兜底 light.exe 重链

唯一改动:Build (Windows) 步骤两个分支都加 --bundles nsis,msi。
预留:NSIS 不读 wix.fragmentPaths,所以 .exe 暂不带 IME DLL;MSI 仍带,
后续单独 issue 跟进 NSIS IME 集成,不挂当前发版。
2026-05-04 22:56:33 +08:00
cooper 84e1649681 docs: 添加 Watchdog 线程影响分析与风险评估
- 分析了 5 个潜在风险点
- 验证了 ASR 资源清理逻辑
- 结论:当前实现完全安全,可以合并
2026-05-04 22:31:08 +08:00
baiqing a5f1a8f16d chore(release): 1.2.17 — Windows MSI 修 light.exe ICE80 报错
x86/x64 IME DLL 都装进 INSTALLDIR\windows-ime\,32-bit 组件落在 64-bit
Directory 是 ICE80 的硬性报错。本场景路径绝对指向、不依赖 SysWOW64
重定向,按 Microsoft 文档是合法用法,给手动 light.exe 重链加 -sice:ICE80
即可放行。改动同步覆盖 CI workflow 和本地打包脚本。
2026-05-04 22:21:50 +08:00
cooper d1c68f0f30 test: 添加实机测试报告(仅保留在 fork)
- BUILD_TEST_REPORT.md: 构建和启动测试
- P1_TEST_REPORT.md: 麦克风异常恢复测试
- TEST_VERIFICATION.md: 代码审查验证

这些文档仅用于 fork 仓库的测试记录,不提交到上游 PR
2026-05-04 22:21:14 +08:00
cooper 4e66c91e07 docs: 添加 Issue #238 修复的代码审查验证报告 2026-05-04 21:54:35 +08:00
baiqing 49210363d6 chore(release): 1.2.16 — Windows MSI 修 PowerShell 引号吞双引号 bug
1.2.15 第 4 轮 Windows job 在 wix 阶段之前就 exit 2,根因是上一版我把
Build (Windows) 整步切成 shell:pwsh 后,& npm run tauri --- build --config
'{"bundle":{"createUpdaterArtifacts":true}}' 这条命令 PowerShell 调外部
进程时把 JSON 内的双引号剥光,tauri 收到 {bundle:{createUpdaterArtifacts:true}}
直接拒绝。log 第 586 行能看到剥过引号的 commandline。

修法:
- Build (Windows) 切回 shell:bash(单引号能保留内部双引号)。set +e 抓
  exit code 写到 GITHUB_ENV,让本步不直接失败,把判断权交给下一步。
- 新增独立 step "Repair Windows MSI if Tauri failed at WiX link"(pwsh),
  仅在 tauri 非零退出且 MSI 不存在时手动从 appRoot 跑 light.exe 重链。
  本地 windows-package-msvc.ps1::Repair-TauriMsiBundle 同款手术。
2026-05-04 21:53:23 +08:00
cooper a8741dd568 fix(recorder): watchdog 从播放开始时计时,避免慢启动误报
- 移除 StreamState::stream_start_time 字段
- 在 watchdog 线程内部记录启动时间
- 确保首次回调截止时间从 stream.play() 成功后开始计时
- 修复慢速后端可能导致的过早超时问题
2026-05-04 21:46:07 +08:00
Chris233 88452cb2ca Merge pull request #251 from H-Chris233/feat/asr-e2e-validation
feat(settings): add end-to-end ASR provider validation
2026-05-04 21:45:45 +08:00
cooperandClaude Sonnet 4.6 87838617f8 fix(recorder): 添加首次回调截止时间检测
根据 code review 反馈,修复 watchdog 的行为缺口:

## 问题
修复 Finding 3 后,虽然避免了误报慢启动设备,但也失去了对
"第一个回调永远不到达"这种失效模式的检测能力。

如果 stream.play() 成功但 CPAL 从未发送任何回调,watchdog
的 `if let Some(last_time)` 分支永远不会执行,session 仍然
可能无限期挂起。

## 修复方案
添加"首次回调截止时间"机制,watchdog 现在检查两种情况:

1. **首次回调未到达**(last_callback_time = None)
   - 检查是否超过 FIRST_CALLBACK_DEADLINE_SECS (5秒)
   - 如果超过,报错:"录音启动后 X 秒内未收到回调"

2. **回调中途停止**(last_callback_time = Some)
   - 检查是否超过 CALLBACK_TIMEOUT_SECS (3秒)
   - 如果超过,报错:"录音回调静默停止 X 秒"

## 技术细节
- 在 StreamState 中添加 stream_start_time 字段
- 在 watchdog 中使用 match 语句区分两种情况
- 5 秒首次回调截止时间足够慢启动设备,但能捕获永不回调的失败

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 21:39:52 +08:00
H-Chris233 cb2eb8e9ce Preserve query parameters when building ASR transcription validation URL
ASR validation URL normalization now edits the parsed URL path instead of string-appending to the raw endpoint, so endpoints with query parameters (e.g. api-version) remain valid after adding /audio/transcriptions.

Constraint: Keep endpoint normalization minimal and avoid changing query/fragment semantics

Rejected: Drop query parameters during validation | would break Azure-style OpenAI-compatible deployments

Confidence: high

Scope-risk: narrow

Tested: cargo test -q
2026-05-04 21:35:14 +08:00
H-Chris233 df2d51e014 Normalize ASR transcription endpoint and enforce streaming response cap
Addresses review feedback: ASR validation now recognizes OpenAI-style /chat/completions endpoints when building the /audio/transcriptions URL, and response-size protection is enforced while streaming the body instead of after buffering it all.

Constraint: Keep ASR validation behavior provider-agnostic and minimal in scope

Rejected: Heuristic URL rewrites beyond known suffixes | risk of breaking custom reverse proxies

Confidence: high

Scope-risk: narrow

Directive: If changing reqwest features, keep Cargo.lock in sync and re-run tests

Tested: cargo test -q
2026-05-04 21:16:46 +08:00
H-Chris233 fd60f815c0 Accept /audio base URLs when validating ASR transcription endpoint
Some OpenAI-compatible deployments expose ASR under a base URL that already ends with /audio. ASR validation now treats that as a valid prefix and appends only /transcriptions to avoid generating /audio/audio/transcriptions.

Constraint: Keep ASR validation URL normalization conservative and backwards-compatible

Rejected: Full URL path rewrite heuristics | risk of breaking custom reverse-proxy layouts

Confidence: high

Scope-risk: narrow

Tested: cargo test -q
2026-05-04 20:51:40 +08:00
baiqing dcfeb405bf chore(release): 1.2.15 — Windows MSI 修复 IME bundle 集成
1.2.14 三轮 release pipeline Windows job 全挂在 WiX 集成上:
  1. 没构建 IME DLL → light.exe 找不到文件
  2. 加了 build 步骤后用相对路径 src-tauri\target\... → Tauri wix bundler
     cwd 和本地脚本不同,相对路径 resolve 错位
  3. 改 $(env.OPENLESS_IME_DLL_X64) 想用绝对路径 → candle 处理 env var
     substitution 时 abort(具体原因被 Tauri 吞掉)

1.2.15 切换到本地 windows-package-msvc.ps1 已经验证可行的 Repair 思路:
  - wxs 维持字面相对路径 src-tauri\target\... (让 candle 正常吃过)
  - workflow 接住 tauri build 失败:从 appRoot (openless-all/app/) 手动跑
    light.exe 重链 main.wixobj + openless-ime.wixobj。这是 light 唯一能
    resolve 该相对路径的 cwd。
2026-05-04 20:39:22 +08:00
Chris233 9c3ab9b100 Merge pull request #250 from H-Chris233/chore/readme-update
docs: add final acknowledgements for sponsors, developers, and LinuxDo community
2026-05-04 20:28:10 +08:00
H-Chris233 73dbd4cef7 Harden ASR validation path against endpoint and response regressions
Follow-up fixes from dual review keep the new end-to-end ASR check while removing compatibility and robustness regressions: normalize transcription endpoint URL, enforce HTTPS except localhost, cap response size, and switch to stable backend error codes mapped in i18n.

Constraint: Preserve issue #242 full-chain validation behavior with minimal surface change

Rejected: Revert to model-list-only validation | no longer verifies real transcription route

Confidence: high

Scope-risk: narrow

Directive: Keep providerHttpStatus:* and stable error-code contract synchronized with Settings error mapping

Tested: npm run build; cargo check -q

Not-tested: Manual validation against all third-party ASR endpoints
2026-05-04 20:27:08 +08:00
H-Chris233 2ae3bdf68a Use real transcription call for ASR connection validation
ASR validation now runs one end-to-end /audio/transcriptions request with the currently selected model and a tiny bundled silence WAV payload. This verifies endpoint, auth, model routing, and response schema in one pass instead of model-list reachability only.

Constraint: Keep UI/API contract unchanged and avoid touching recording pipeline

Rejected: Reuse live microphone capture in settings validation | invasive and non-deterministic for quick checks

Confidence: high

Scope-risk: narrow

Directive: Preserve providerHttpStatus:* error marker so frontend can keep status-specific guidance

Tested: npm run build; cargo check -q

Not-tested: Live validation against every third-party ASR provider implementation
2026-05-04 20:27:08 +08:00