style(src-tauri): 应用 rustfmt 默认风格统一格式
This commit is contained in:
1 parent
11d9aa4b29
commit
be1118bc2a
17 files changed
+18
-3576
No files matched your search
@@ -1,85 +0,0 @@
|
||||
# Core 2.0 体验梳理与页面改进
|
||||
|
||||
状态:draft;阶段:implementation-backed;更新:2026-09-07。
|
||||
|
||||
## 基线与结论
|
||||
|
||||
实时抓取 `Open-Less/openless` 后确认,#1019 将 Core 2.0 合并到默认主线 `beta`,提交 `fc38edd1a3da3a4f2fa56f433be84278c3456ed1`。稳定 `main` 是 1.3.18 的 `08d70601`。本次按用户授权删除应用仓库旧的未提交修改,用前者替换工作区,再建立 `feat/core-2-ux-linux`。
|
||||
|
||||
项目名为 OpenLess,迁移名为 **Core 2.0**;应用显示版本 `2.0.0-Beta.1` 与后端公开合同 `2.0.0` 是不同概念。范围仍以 [2.0 需求](2.0-requirements.md)为准。
|
||||
|
||||
2.0 的重要收益是业务状态、渠道、迁移与会话取消进入共享 Core。主要体验问题是新架构已就位,界面仍让用户自己推断操作顺序:主页以统计为主,设置混放大量任务,Linux 原有领域界面顺序堆叠。本次优先整理现有功能的入口与显示,不增加另一套业务规则。
|
||||
|
||||
## 迁移领域与页面影响
|
||||
|
||||
本次按 #1019 的变更清单、公开合同、领域出口、平台交接与实际页面梳理;下表是迁移面索引和体验判断,不是逐行安全审计或全部设备验收。
|
||||
|
||||
| 领域 / 桌面验收项 | 2.0 相关实现 | 体验判断与本次处理 |
|
||||
| --- | --- | --- |
|
||||
| 启动、生命周期 D01 | `App.tsx`、IPC `shared.ts`、Core `api.rs` | 保留所有窗口的合同检查;补可读启动状态、重查按钮与折叠错误详情 |
|
||||
| 听写、音频、风格/语言 D02/D03/D07 | Core `dictation_engine.rs`、`audio.rs`、`silence_auto_stop.rs`、`prompt_compose.rs` | 主页解释下一步;录音、外观语言拆分类;保留现有首帧、热键和停止语义 |
|
||||
| 云服务、渠道与凭据 D04/D05 | Core `provider_rules.rs`、`provider_service.rs`、`credentials.rs`;`ChannelList` | 主页按有效 traditional/Omni 状态展示服务,直接进入 AI 设置;渠道规则继续用 descriptor |
|
||||
| 本地模型 D06 | Core `model_store.rs`、`local_asr_service.rs`;React LocalAsr / Linux models | 仍放 AI 服务范围,保留原子激活与下载取消;Linux 管理深度缺口留在 L08 |
|
||||
| 快捷键与插入 D08/D09 | Core `hotkey_interpreter.rs`、`streaming_insert.rs`;各 Host 原生 Adapter | 单独的快捷键与选区分类;权限诊断可直达;不更改原生目标和回退策略 |
|
||||
| QA、选区、Agent D10–D12 | Core `qa_service.rs`、`selection*_service.rs`、`less_computer.rs` | 页头解释划词追问用途;Linux 分页仍保留全局待办、审批和取消 |
|
||||
| 历史、词典与纠错 D13/D14 | Core `history.rs`、`activity.rs`、`vocabulary.rs`、`correction.rs` | 统计置于主页次级;历史在窄窗口使用列表/详情;词汇页面沿用列表与编辑布局 |
|
||||
| 风格包与市场 D15/D16 | Core `style_pack_store.rs`、`style_packs.rs`、`marketplace.rs` | 保留主导航的风格分组;共同页头操作区允许换行,降低多语言窄窗拥挤 |
|
||||
| 手机输入 D17 | Core `remote_input_service.rs`、各 Host TLS/H5 | React 保留录音与输入内入口;Linux 独立手机页、连接状态与陈旧地址处理 |
|
||||
| 设置与维护 D18 | Core `settings.rs`、`preferences.rs`、React settings / Linux settings | 分类、搜索、自动保存提示、直接可见说明、焦点与滚动恢复 |
|
||||
| 旧数据 D19 | Core credentials/model/preferences 等迁移;Host vault/data roots | 本次不改变迁移;真实升级与回滚继续按桌面/Linux 清单验收 |
|
||||
| 合同与其他平台 | `backend-2.0.json`、事件 reducer、JNI 合同、构建入口 | 保留 Core/Host 边界;Android 不展示空的桌面快捷键分类;不改发布工作流 |
|
||||
|
||||
## 已落实的界面设计
|
||||
|
||||
- **主页**:下一步与状态刷新前置;未读取/读取失败/未配置分别处理;本地 ASR、云 ASR 和 Omni 依据已有真实状态呈现;服务卡有配置/管理入口。活动热力图置于近期结果之后,底部卡片自然延展而不被短窗口压扁。
|
||||
- **设置**:录音与输入、快捷键与选区、AI 服务与模型、外观与语言、权限与数据、实验与扩展、关于与更新。稳定的旧 section ID 保留;分类搜索支持当前语言的用途和关键词,不搜索或暴露凭据值。
|
||||
- **AI 分区**:语言模型、语音识别、本地模型、连接与扩展各自呈现,页签随滚动保留;Omni 模式使用对应入口,避免传统模型页变空白。设置实底,不透出背后的页面文字。
|
||||
- **操作细节**:搜索不卸载当前分类草稿;选分类回到页首;Escape 先清空查询、再关闭;打开时移动焦点、关闭时返回入口;已有下拉和快捷键录制优先处理自己的按键。
|
||||
- **页面排布**:设置关键解释直接可读;共享页头不再被内容压缩,操作按钮可换行;历史窗口小于1000像素时用列表/详情切换,缩窗保留正在查看的重润色结果;翻译和划词追问补上已有用途说明。
|
||||
- **入口修复**:空本地模型目录在布局阶段反复写入相同选择值,浏览器可复现最大更新深度错误。增加变化判断后,同一服务入口恢复正常。下拉选项补可访问关联,快捷键小菜单优先消费 Escape。
|
||||
- **渠道与表单**:列表改用统一分隔行,显示名称、模型、当前使用/停用、验证结果与时间;添加放在分区头部,验证/启停/编辑操作有标签。表单按服务连接、模型设置、连接验证组织,名称作为可选信息,删除仍需确认;顶部关闭、Tab循环、子菜单Escape与焦点返回已补齐。
|
||||
- **模型与下载**:管理页减少嵌套卡片,已下载/下载中模型与所选详情各有清楚位置;空态提供下载与重新读取。下载目录有模型大小、引擎、状态、仓库和简介;仅多引擎时显示筛选,窄屏按目录/详情切换,底部操作独立滚动。保留Windows原引擎操作与取消/激活合同。
|
||||
- **Linux**:实际界面行为统一记录在 [04 页面与领域操作](linux-egui-handoff/04-ui-domains.md),剩余事项统一记录在 [02 缺口登记](linux-egui-handoff/02-gap-register.md)。
|
||||
|
||||
## 下一轮建议与优先级
|
||||
|
||||
| 优先级 | 建议 | 依据与验收 |
|
||||
| --- | --- | --- |
|
||||
| P1 | 将单项配置检查连接成首次听写向导 | 渠道列表现已区分当前使用与上次验证;下一步串联麦克风、服务和目标输入检查,让新用户完成一条真实听写 |
|
||||
| P1 | 优先跑首装与升级后的第一条听写闭环 | 2.0 改动大量 Host/Core 边界。分别验证权限拒绝、设备断开、快捷键冲突、取消与最终落字,保留清楚的恢复入口 |
|
||||
| P1 | Linux 先完成输入环境和后台工作可达,再补领域功能 | L01–L03 涉及热键、上下文、录音,直接影响后续历史/纠错。真机覆盖 X11/Wayland 和常用目标应用;不以页面数替代功能闭环 |
|
||||
| P2 | 将主快捷键的编辑统一到一个明确目的地 | 当前录音页和快捷键页都有入口,属于既有行为;下一轮可保留录音页摘要并跳到快捷键编辑,需同步首次引导与迁移提示 |
|
||||
| P2 | 本地/云选择围绕用户需求表达 | 用离线、速度、语言和设备需求组织模型选择;模型实际可用、下载中、加载中、失败分别反馈,不默认“已下载”等于可识别 |
|
||||
| P2 | Linux 跟进模型、历史、词汇/风格/市场完整操作 | 具体范围已在 L04–L09;已有 Core API 可复用,优先交付有完整成功/失败/取消路径的一个领域 |
|
||||
| P2 | 页面能力来自 Host,并给不支持项明确原因 | Linux 能力快照中的 overlay/AppImage flag 不代表 UI/更新器已接好;后续能力合同可携带不可用原因,避免两端界面各自猜测 |
|
||||
| P3 | 按领域拆解持续增长的门面和原生 UI 文件 | `Core api.rs` 与 Linux `main.rs` 较大,增加定位和回归成本;在领域功能实际扩展时拆分,避免无业务目的的全面重写 |
|
||||
|
||||
## 浏览器预览目录
|
||||
|
||||
普通浏览器此前的 `listProviderDescriptors` 返回空数组,2.0 渠道编辑只能停留在加载态。现在浏览器分支懒加载 Core 生成的公开目录;原生分支仍执行同一个受启动合同保护的 IPC。该文件只有公开服务元数据,没有用户凭据或认证状态,Core 继续是规则的唯一来源。
|
||||
|
||||
生成文件:`openless-all/app/src/lib/ipc/provider-descriptors.generated.json`(generated,不手工维护)。在应用目录执行以下命令重新生成:
|
||||
|
||||
```sh
|
||||
cargo run --locked -p openless-core --example export_provider_descriptors
|
||||
```
|
||||
|
||||
生成器只调用 `openless_core::provider_rules::provider_descriptors`,没有启动应用、打开密钥环或请求外部服务。对应浏览器入口测试覆盖 ASR、LLM、Omni 目录可加载及协议字段完整性。
|
||||
|
||||
## 验证结果(2026-09-07 收口)
|
||||
|
||||
| 验证 | 本次结果与范围 |
|
||||
| --- | --- |
|
||||
| `npm test` | 构建与72个前端/合同测试模块通过,包含实际Core快捷键回归;最后运行退出码0 |
|
||||
| 既有渠道/提供商/本地模型/轮询/布局合同 | 通过;渠道重排后恢复保守布局的纵向操作区,没有通过改测试掩盖回归 |
|
||||
| 五语言 | 导航与搜索路径、服务分区、渠道和本地模型文案验证通过;中英切换经浏览器检查 |
|
||||
| Core目录导出 | 直接编译运行生成器通过;生成文件供预览按需加载;浏览器目录测试通过 |
|
||||
| 浏览器桌面 | 主页直达、分类搜索与空结果、服务页签、完整LLM与ASR表单、字段排布、菜单Escape、Tab循环与返回焦点通过 |
|
||||
| 浏览器窄屏 | 390×844下完整LLM表单和模型目录/详情;选择1.7B、返回目录保留选择、关闭返回下载入口通过。900×620历史无横向溢出;1280缩至990时重润色结果保留 |
|
||||
| Linux Host | 50个单元测试与4个Host合同测试通过;本次实际运行在macOS |
|
||||
| egui界面 | 临时macOS验证副本9项通过;追加Omni有效模式2项定向测试覆盖16种配置组合通过,先复现失败再修复 |
|
||||
| 差异与格式 | 最终差异空白检查通过;新增Core导出示例和Linux改动的定向格式检查通过 |
|
||||
|
||||
Linux目标交叉检查缺少对应`core/std`,Docker服务未运行,因此没有Linux原生编译通过证据。真机输入、音频、密钥环、窗口、真实模型下载/取消/推理与安装升级按[Linux验收](linux-egui-handoff/07-acceptance.md)及[桌面验收](2.0-desktop-acceptance.md)继续完成。浏览器使用预览数据,UI通过不代表真实服务、设备或分发验收通过。
|
||||
|
||||
本次确定的页面回归(历史缩窗状态、空目录布局更新、菜单与弹窗焦点、Omni状态)已修复;没有留下已知未处理的本轮问题。完整原生验收属于上述后续清单。代码保留在 `feat/core-2-ux-linux` 工作区,未提交或推送。
|
||||
@@ -1,48 +0,0 @@
|
||||
# Core / Host 平台边界审计
|
||||
|
||||
> 2026-09-06范围更新:[当前2.0需求](./2.0-requirements.md)要求Windows/macOS完整保留各自1.x功能;Linux剩余Host/UI和产品验收移交egui团队,见[拆分交接目录](./linux-egui-handoff/README.md)。本审计保留代码边界证据,不表示Linux产品完整;Linux应用待办不单独阻塞本批桌面2.0。
|
||||
|
||||
审计基线:OpenLess 2.0.0-Beta.1(2026-09-04 工作树)。范围覆盖 `openless-core`、Tauri、Linux egui、React/TypeScript、C++ 插件及构建脚本;vendor、生成物和纯视觉实现排除。
|
||||
|
||||
2026-09-05 更新:下文为边界迁移索引;最新生产行为缺口、修复和证据在
|
||||
[`pr1019-2.0-final-review.md`](./pr1019-2.0-final-review.md)。有接口和通过 fixture
|
||||
不能证明对应 Host 的启用、取消、资源释放或实际输入行为正确。
|
||||
|
||||
## 已迁移项
|
||||
|
||||
- Provider descriptor、Credential channel mutation/active/order、ASR/LLM/Omni、设置事务与事件位于 Core;React/egui 只渲染 descriptor 和本地化标签。
|
||||
- `LessComputerVoiceSession` 在 Core 持有 session lease、ASR snapshot、PCM 校验、TranscriptDelta、静音/fault 决策和 Agent submit。
|
||||
- 主听写与 Selection Voice 的 Hold/Toggle/Auto/Combined、press generation、250ms debounce、450ms terminal cooldown 均由 Core 解释。
|
||||
- 录音 plan、`SilenceAutoStop`、silent retry、correction 顺序/归因、流式 final reconciliation、edit observation generation 与历史重转 mutation 均位于 Core。
|
||||
- `ModelStore` 统一 1.x/custom-root 迁移与 Local ASR 原子激活;Qwen/Whisper 用 target generation lease,Linux Qwen 使用打包 runtime 和进程组 cancel/timeout。
|
||||
|
||||
## 本批修复项
|
||||
|
||||
- Shared realtime ASR 将 Qwen、StepFun、Bailian、Volcengine、讯飞 interim 回调接入 Unicode replace-from `TranscriptDelta`;React/Linux 使用同一 reducer 语义。
|
||||
- Linux production factory 注入 QA、Remote Input、Selection preview/revert、Provider/Model、Less Computer 与打包 Qwen runtime;AppImage 在 hotkey listener 前处理 fcitx5 插件。
|
||||
- Tauri 只保留窗口、原生录音/native ASR、秘密存储、插入目标、进程和协议 transport effect;Credential/QA/Selection/stream/history 的旧 Host policy 已删除并由 source gate 固定。
|
||||
- Linux/Tauri manifest、打包脚本和 AppStream 使用 `AGPL-3.0-only`;2.0.0-Beta.1 为许可证生效边界,1.x 发布物仍为 MIT。
|
||||
|
||||
## 有意保留的 Host 项
|
||||
|
||||
- Tauri 窗口、胶囊、原生录音/native ASR、系统凭据、插入和生命周期。
|
||||
- Linux cpal、fcitx5、Secret Service、资源布局和单实例。
|
||||
- Windows Foundry/Sherpa、macOS Apple Speech/MLX/Whisper 等单平台 runtime。
|
||||
|
||||
## Deferred 候选
|
||||
|
||||
- 不再为当前迁移拆独立 process/transport crate;现有 Core `AgentCommand` + Host `ProcessAdapter` 已满足边界,新增 crate 只会制造中间层。
|
||||
- Ubuntu 实际焦点输入、音频设备、Secret Service、QA/Remote/Selection、Qwen 性能与 deb/rpm/AppImage 安装升级回滚证据。
|
||||
- Windows 原目标恢复/PasteSent/TSF unknown/mute/fault/Foundry/Sherpa,macOS newline/context/edit/login-shell,以及 Android readiness/overlay/IME 的真实设备 smoke。
|
||||
|
||||
## Standards
|
||||
|
||||
依赖方向保持 Host → Core;Core 不引用 Tauri/egui,凭据只经 `CredentialStore`,未注入 runtime 明确返回 `Unsupported`。共享 session 通过一个 mutable lease 和 `BackendEvent.session_id` 关联。
|
||||
|
||||
## Spec
|
||||
|
||||
2.0 目标的 Core voice session、实时增量、三档热键解释和 Linux typed event 已有公共入口;平台原生录音/本地模型仍由 Adapter 提供,不能以 headless fixture 代替设备证据。
|
||||
|
||||
## 剩余风险和证据边界
|
||||
|
||||
本审计证明源码边界和可运行 contract,不证明云服务凭据、真实音频设备、CLI 安装、签名、设备运行或发布产物。Backend contract 版本为 `2.0.0`,不随应用版本升级。
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 616 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 663 KiB |
@@ -1,11 +0,0 @@
|
||||
# Linux 环境引导:官方资料
|
||||
|
||||
状态:canonical;阶段:implementation-backed;来源类型:外部知识。
|
||||
|
||||
|
||||
获取日期:2026-09-06;新鲜度:normal;复核期限:2026-09-16;置信度:高(官方文档/源码),具体桌面可用性仍依设备验证。项目使用egui/eframe 0.31.1;以下Fcitx 5与Secret Service资料是当日上游页面,不声明为本机安装版本。
|
||||
|
||||
- [Fcitx 5设置](https://fcitx-im.org/wiki/Setup_Fcitx_5):桌面启动、输入法框架与会话配置。
|
||||
- [Fcitx 5 on Wayland](https://fcitx-im.org/wiki/Using_Fcitx_5_on_Wayland):配置依桌面与GTK/Qt等工具包而异。
|
||||
- [fcitx5-remote官方源码](https://github.com/fcitx/fcitx5/blob/master/src/tools/remote.cpp):`-r`调用`ReloadConfig`,不是插件安装/进程重启。
|
||||
- [Secret Service官方规范](https://specifications.freedesktop.org/secret-service/latest/):会话、集合锁定及解锁语义;不能从已有渠道配置推断当前vault可访问。
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,67 +0,0 @@
|
||||
# Core 2.0 入口、设置与 Linux 界面整理
|
||||
|
||||
状态:historical;阶段:implementation-backed;更新:2026-09-07;任务状态:complete。
|
||||
|
||||
## 意图基线
|
||||
|
||||
用户要求用云端最新已合并主线替换本地内容,删除未提交修改;梳理 2.0 并提出改进建议,落实更直觉的入口、设置与页面布局,为 Linux 增加引导和设置准备。
|
||||
|
||||
- 应用仓库:`1-app`,远端 `Open-Less/openless`。官网、市场后台、训练与审计项目独立,不在本次修改范围。
|
||||
- 2026-09-06 实时 fetch:默认主线 `beta`,Core 2.0 在 #1019 合并,基线 `fc38edd1a3da3a4f2fa56f433be84278c3456ed1`;稳定 `main` 是 `08d70601`(1.3.18)。代码版本 `2.0.0-Beta.1`。
|
||||
- 已按授权 reset/clean 应用工作区,并切到 `feat/core-2-ux-linux`。
|
||||
|
||||
## 目标、非目标与约束
|
||||
|
||||
主页提供可操作的开始入口;设置按用户任务拆分,保留保存、平台能力和原有功能;Linux 在现有 egui 页面中增加导航和环境准备引导。梳理整个 2.0 迁移面,以界面改动为实施重点。
|
||||
|
||||
不重写共享业务、不扩展公开 IPC、不变更发布版本、不实现完整 Linux 原生适配、不操作其他项目或生产配置。使用现有 React/TypeScript/i18next、Tauri 2、Rust Core 与 egui/eframe 0.31;不增加依赖。Linux 已有功能、待接线功能、设备待验收分别表达。
|
||||
|
||||
## Read Set
|
||||
|
||||
- 本仓库唯一 `AGENTS.md`(本地入口)及 `docs/2.0-requirements.md`、`2.0-desktop-acceptance.md`、`core-platform-boundary-audit.md`。
|
||||
- `docs/linux-egui-handoff/README.md`、`02-gap-register.md`、`04-ui-domains.md`;其他交接小文档按需要读取。
|
||||
- `openless-all/app/src/components/{FloatingShell,SettingsModal,Onboarding}.tsx`、`src/pages/Overview.tsx`、`src/pages/settings/`、平台与 IPC 门面。
|
||||
- `openless-all/app/crates/openless-core/src/{lib,api,ports,events}.rs` 的公开结构;合同与测试;不逐行递归阅读全部旧迁移记录。
|
||||
- `openless-all/app/linux-egui/src/{main,capabilities,settings}.rs`、已有 Host 接线;包清单与测试入口。
|
||||
|
||||
## 方案与影响范围
|
||||
|
||||
1. React 主页将下一步和真实配置状态前置,服务卡片可直接进入设置;短窗口保留自然滚动。
|
||||
2. 设置保留稳定 ID general/services/privacy/advanced/about,增加 shortcuts、appearance,将长通用页按任务拆分。添加说明、可搜索的分类入口、键盘关闭和焦点返回;正确挂载/卸载原有区块。
|
||||
3. Linux 复用已有功能,按开始/听写/AI 服务/模型/工具/手机输入/历史/设置组织页面;环境指导使用现有能力信息,提供实际可执行的准备步骤,保留取消、审批和后台事件消费。
|
||||
4. 补文档入口与 2.0 架构/体验梳理,记录建议、落实项、Linux 剩余工作和验证范围。
|
||||
|
||||
## 里程碑
|
||||
|
||||
- [x] 实时核实并同步云端基线,清理本地改动。
|
||||
- [x] 核对 2.0 范围、平台边界、现有页面与验证方式。
|
||||
- [x] 桌面主页入口和概览布局改进。
|
||||
- [x] 设置分类、查找、布局及可访问性改进。
|
||||
- [x] Linux 原生导航与环境引导。
|
||||
- [x] 补齐中英及现有语言文案,文档与代码同步。
|
||||
- [x] 类型/构建、前端测试、可行 Rust 验证、浏览器桌面/窄屏检查、最终 diff 审阅。
|
||||
|
||||
## 用户追加的视觉改进(同一任务)
|
||||
|
||||
用户在预览后指出 LLM 设置元素不好看、排布不清楚,模型设置和下载也有同样问题。这是本次前端目标的继续细化。
|
||||
|
||||
- [x] 设置改为完全实底,消除透底文字干扰;AI 服务按语言模型/语音识别/本地模型/连接分类,Omni 模式保留真实配置入口。
|
||||
- [x] 渠道列表明确名称、模型、使用状态和验证结果,整理添加/编辑/启停操作;编辑表单分组。
|
||||
- [x] 本地模型管理与下载目录减少嵌套卡片,完善空态、主操作和窄屏排布。
|
||||
- [x] 浏览器检查 LLM、编辑页、模型管理与下载页,并对最终代码重新构建/测试。
|
||||
|
||||
第一轮已通过前端构建及71个测试模块;990像素进入服务页时的空目录布局更新循环已复现并修复;历史重润色结果从宽窗缩至990像素已验证保留。Linux Host50+合同4、macOS临时UI9及追加Omni2项测试通过;原生Linux目标编译仍缺环境,不作通过声明。最终结果以追加视觉改进后的收口记录为准。
|
||||
|
||||
## 验收与回滚
|
||||
|
||||
主页服务入口能打开对应设置;录音/快捷键/外观/服务/隐私/高级/关于均可达;移动端没有丢失录音或语言设置;查找与空结果可恢复;设置支持关闭和焦点返回。Linux 每个旧操作保留真实调用,导航不停止事件消费或丢弃会话。
|
||||
|
||||
使用 `npm test`(含构建)、必要的定向行为测试;Linux 源文件格式检查、Host/Core 测试及能执行的目标检查;浏览器检查常规桌面、短窗口与窄屏。当前主机是 macOS,Linux 原生桌面效果须明确留给 Linux 设备验收,不能以 macOS 上 cfg 排除的构建冒充。
|
||||
|
||||
改动均保持在此分支,按文件撤销可回到 `fc38edd1`;已删除的用户未提交修改不作可恢复承诺。风险集中在设置入口迁移、短窗口溢出、平台 gating 和 Linux 隐藏页面的会话状态。
|
||||
|
||||
## 最终收口
|
||||
|
||||
追加视觉改进已实现并完成浏览器检查。最终 `npm test` 构建及72个测试模块通过;Core公开目录生成器编译运行通过。渠道编辑焦点、供应商下拉可访问性、快捷键菜单Escape、保守布局、历史缩窗结果保留与空模型目录白屏均已关闭。预览目录由Core生成,原生IPC合同不变。
|
||||
|
||||
验证详情、设备边界与下一轮建议统一见[体验梳理](../../2.0-ux-review.md#验证结果2026-09-07-收口)。原生Linux编译/真机与真实服务验收仍在已有交接清单内;本任务没有宣称完成这些产品发布门。
|
||||
@@ -1,90 +0,0 @@
|
||||
# PR #1019:2.0 合并前完整复核与修复记录
|
||||
|
||||
> 更新范围后的新团队复审另见[桌面复审闭环](./pr1019-desktop-review-2026-09-06.md)。其中R29起的问题覆盖此前审核未发现的生产路径;不能用本文旧轮次的零未关闭项代替最新结论。
|
||||
|
||||
> **2026-09-06需求调整**:本文保留此前问题/修复/测试证据;当前完成条件以[2.0需求](./2.0-requirements.md)为准。Windows/macOS完整保留各自1.x功能;Linux本批交付Core和[拆分交接资料](./linux-egui-handoff/README.md)。辅助全局热键等Linux剩余Host/UI缺口已列入[L01–L12](./linux-egui-handoff/02-gap-register.md)交egui团队,不再单独阻塞本批桌面2.0;本记录不能作为“Linux所有功能已完整”的证明。
|
||||
|
||||
本轮从 `2f961caec5d749ed444a0fa3dc24ef136a58f9a5` 开始,比较点为
|
||||
`beta` / `fc9824eeccf7218e3b44c1de6c6e284a4c494c81`;merge-base 等于该 base。
|
||||
平台优先级:Windows、macOS、Linux;同时覆盖 Android 条件编译和共享合同。
|
||||
本文件记录新发现、复现、修复及验证。此前 CI 成功是基线证据,不替代本轮行为复核。
|
||||
|
||||
## 问题登记
|
||||
|
||||
| 编号 | 优先级 / 平台 | 触发与证据 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| R01 | P1 / Windows | TSF Rejected/Failed 被映射为 CopiedFallback,却没有复制,因而跳过新 Adapter 的失败回退。 | 已修复;真实 Failed 与 OutcomeUnknown 分离,Windows IME 7 项通过 |
|
||||
| R02 | P2 / 所有录音 Host | RecordingPlan 错读 history_max_entries,忽略独立 audio_recording_max_entries。 | 已修复;100/7 独立上限用例先红后绿 |
|
||||
| R03 | P2 / 共享凭据事务 | CredentialDirectory 异步 load → mutate → save 无共享锁,并发创建得到相同渠道 ID;Tauri 的 revision CAS 会拒绝冲突保存,避免静默丢数据,但普通并发操作仍会失败,弱存储实现则会覆盖。 | 已修复;克隆共享事务锁,并发创建/重命名/激活回归通过;Tauri 持久层 CAS 保留 |
|
||||
| R04 | P1 / 本地 ASR、跨平台 | activation/目录迁移在 await 前保存整份 preferences,随后提交/补偿覆盖期间编辑;排队 relocation 又使用锁外旧根。独立复核还发现旧渠道补偿会撤销准备期间的新选择。 | 已修复;原子更新所拥有字段、条件补偿、锁内读取根;全部可失败native操作在渠道最终提交之前;Local ASR 22 项通过 |
|
||||
| R05 | P1 / Linux Less Computer | 第二轮 session 更新被 fresh=false 阻止,输出和审批全部丢弃。 | 已修复;每轮 User 接管 session,fresh 仅清历史;WSL 两轮回归通过 |
|
||||
| R06 | P2 / Linux Provider 状态 | status 忽略 multimodal 总开关,和真实 pipeline 不一致。 | 已修复;复用 effective_pipeline_mode,开关组合回归通过 |
|
||||
| R07 | P1 / Windows、macOS 本地 ASR | Generic prepare 和非 Generic preload 固定 Unsupported;Generic preload 读取旧 provider/model。 | 已修复;显式 target/provider type,核验真实 loaded 模型;生产 Tauri Adapter 回归通过 |
|
||||
| R08 | P1 / Native ASR | 丢失动态超时和错误/取消释放;共享代次还可能遗留旧引擎或卸载新模型。 | 已修复;Core deadline、各 runtime 代次及锁内复验、Arc/Weak 所有权;边界/释放测试通过 |
|
||||
| R09 | P2 / Windows 流式 | CR 被吞掉后计数减少,正常 CRLF 被 Core 误判部分写入。 | 已修复;已消费源前缀计数;Unicode 9 项和 insertion 11 项通过 |
|
||||
| R10 | P2 / macOS 普通听写 | 非流式也强制切 ABC,TIS 错误阻止一次性听写。 | 已修复;只准备真实流式,失败回执由 Core降级一次性交付;Core回归通过,原生 TIS 待设备验证 |
|
||||
| R11 | P1 / Linux 热键 | 启动未同步热键;已有 Less Computer UI 开关却被 settings 拒绝。 | 已修复;启动同步/插件重启重载、启用绑定与禁用解绑;Host contract通过 |
|
||||
| R12 | P1 / 所有 Remote Input Host | stop 锁/同步等待阻止取消;Linux 丢 terminal;Tauri 全局 result 广播会发送本机/其它连接的听写。 | 已修复;保留可取消 session、异步 stop、逐连接事件隔离和迟到事件保护;Core 12 项及 Tauri Remote 4 项通过 |
|
||||
| R13 | P2 / Linux QA | delta 覆盖全部状态,历史消失且仅显示最后一个 chunk。 | 已修复;按事件类型合并、终态以全量消息校准;WSL 增量回归通过 |
|
||||
| R14 | P1 / Linux 普通落字 | 插件按其它按键更新全局 savedIc,听写可跨应用误写。 | 已修复;session ticket 冻结原 IC;真实 C++ InputContext 契约通过 |
|
||||
| R15 | P1 / Linux Selection | PRIMARY 残留被当作原选区有效证明,光标移动后仍可能错误插入。 | 已修复;严格核对原 IC、文本、cursor/anchor;Debug/Release C++ 契约通过 |
|
||||
| R16 | P1 / 共享普通听写启动 | AX/凭据捕获期间已有 lease 却无公开 session,Esc 无法取消,慢调用返回后继续录音。 | 已修复;首次 await 前公开 Starting;确定性取消回归及 Core API 73 项通过 |
|
||||
| R17 | P1 / ASR 静默重试 | retry 的 provider start await 后未复验取消,Esc 只清旧 ASR,新返回的重试 ASR 仍接收 PCM/finish且失去取消路由。 | 已修复;资源注册与once标记同锁,迟到资源直接取消且不送PCM;15项Pipeline测试通过 |
|
||||
| R18 | P1 / QA 启动 | Linux安装capture后没有arm/取消复验;两Host文本prepare在上下文await后才注册,取消期间无资源可清,迟到注册可泄漏旧session。 | 已修复;await前登记owner,返回后身份复验,Linux arm;两Host Adapter回归通过 |
|
||||
| R19 | P1 / QA 转写取消 | Host take唯一capture后,Core finish关闭handle并移动transcription,Esc不能取消正在finish的ASR。 | 已修复;共享capture保持Finishing阶段取消句柄,ASR恰取消一次回归通过 |
|
||||
| R20 | P1 / QA 静音停止 | 延迟静音任务调用无session限制的toggle,可能在旧轮取消后误开新录音或停止下一轮。 | 已修复;stop_recording按每轮token原子校验与转换,21项QA contract通过 |
|
||||
| R21 | P2 / 手机默认操作模式 | 桌面Remote设置可选默认hold,但H5无已保存模式时固定toggle,设置没有生产消费者。 | 已修复;两Host渲染白名单默认值,手机显式偏好优先;7种组合及音频队列回归通过 |
|
||||
| R22 | P1 / Linux 原生打包兼容性 | runner的Fcitx版本不支持复制/移动SurroundingText,保存该对象快照导致插件和CTest目标编译失败;WSL较新库未暴露差异。 | 已改为自有普通值快照;5.0.14旧头文件复现后构建通过,5.1.19本机Release CTest通过;完整22.04制品待新CI |
|
||||
| R23 | P1 / Selection Voice 转写取消 | 与QA相同的take/finish(self)所有权断点导致转写阶段Esc无法取消ASR。 | 已修复;复用共享capture控制,原session限定Stop/Cancel及迟到启动/终态保护通过 |
|
||||
| R24 | P1 / Windows TSF | 迁移删去native timeout/cancel HRESULT的OutcomeUnknown分类;原生已进入COM写入时超时仍可能迟到提交,随后SendInput回退会双写。 | 已恢复两种HRESULT分类及session/protocol响应验证;IPC10项通过 |
|
||||
| R25 | P1 / 前台应用快照 | cursor开关错误地同时禁止无需AX的front-app查询,默认关闭丢历史应用/按应用润色/macOS Terminal自动LF。 | 已恢复capture(false)前台查询,禁用时丢弃文档文本;Terminal换行及历史归属回归通过 |
|
||||
| R26 | P1 / CLI取消 | 两Host在启动取消/输出drain前等待stdin写完;不读stdin的大prompt阻断Esc,Tauri PATH查询亦有取消盲区。 | 已统一stdin/drain/wait/PATH/快照取消;Windows suspended+Job,1/4MiB、孤孙、future-drop、自然成功回归通过 |
|
||||
| R27 | P1 / Linux渠道删除 | 删除仅移除metadata,保留SecretService和keys;同厂商ID重建时继承已删除账号的密钥。 | 已按namespace+channel清理;部分删除失败保持可重试,未提交孤立secret不可读;8项凭据回归通过 |
|
||||
| R28 | P1 / Linux 1.x凭据迁移 | 新SecretService命名空间没有读取旧Tauri service/分片/legacy文件;升级后原渠道与密钥丢失。 | 已幂等导入旧JSON/分片/平铺账号,保留来源;新配置优先、失败重试、home_dir=None零迁移访问;旧格式4项及Linux cfg验证通过 |
|
||||
|
||||
## 本轮覆盖范围
|
||||
|
||||
- Windows:启动、IPC/热键入口、录音、TSF/SendInput/Clipboard、目标恢复、Foundry/Sherpa、本地模型、凭据和安装包。
|
||||
- macOS:启动、全局热键、焦点/AX/隐私、输入源/换行/流式、MLX/Whisper/Apple Speech、进程与打包。
|
||||
- Linux:生产 factory、egui UI、Provider/凭据、本地模型、QA/Selection/Remote、fcitx5、事件重放、打包。
|
||||
- Core:所有领域入口、会话/取消/唯一终态、凭据/偏好事务、模型迁移、ASR/LLM、Agent、历史与录音保留。
|
||||
- 合同与仓库:97 个 UserPreferences 字段消费者、2.0 Rust/TS/JNI/Linux DTO、条件编译、锁文件、CI、发布脚本和迁移文档。
|
||||
|
||||
## 验证记录
|
||||
|
||||
修复前建立能失败的针对性行为验证;修复后执行相关测试,收尾执行完整本地检查及最终提交的远端 CI。
|
||||
本次 Windows 本地已完成:前端 build + 66 项;Core 705 passed / 1 ignored,contract 5、Local ASR 21、Marketplace 17、Prompt 3、QA 19、Remote 12、Selection 15、Selection Voice 15 全部通过;Core strict clippy通过。独立复核补修后,Local ASR 包含新增渠道补偿回归的22项再次全部通过。
|
||||
Tauri library 422 项通过,Rust 1.88 check通过,独立 backend contract 1项及其1.88编译通过。
|
||||
Linux Windows侧 38 library + 4 host contract 及strict clippy通过。WSL 45 library + 2 UI + 4 host contract及strict clippy通过,3项真实设备测试ignored;C++真实InputContext Debug/Release契约通过,含目标销毁后两类ticket失效。首轮WSL增量编译因内存不足中止,改用 `CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=1` 后完整通过。
|
||||
Core/Linux依赖、command/event基线(199/30/30)、secret surface、测试隔离、runtime seam、Linux公共面、source contract、workspace fmt与diff门禁均通过。
|
||||
`remote:result` 在历史兼容基线中保留并标为 retired;H5 wire result 保持兼容,实际socket只消费归属于本连接的Core事件。
|
||||
|
||||
子代理顺序:初始专项事实核查 → 修复完成后一名独立复核 → 提交推送 → 两名独立全 PR 审核。
|
||||
Standards(规范/边界/重复策略)与 Spec(缺失/错误行为/兼容性)分别记录结论。
|
||||
最终远端提交、CI和推送后审核结果记录于原 PR #1019 描述,避免把历史run误标为本次证据。
|
||||
|
||||
提交前独立复核已通过:Standards无未关闭项;Spec发现的R04渠道补偿及R12取消后重开两项遗漏均已修复并复读确认。后者以真实Core+永不完成的Host stop复现,Linux Remote 5项及strict clippy通过;终态后迟到cancel保留清理future。
|
||||
|
||||
已排除的候选:设置页同步切换ASR与异步渠道保存的混合并发不会静默覆盖元数据;`apply_credential_metadata` 已在持久层锁内检查 revision CAS,旧快照明确失败。该保护通过继续追踪持久层确认,未为此添加第二套机制。
|
||||
|
||||
### 2026-09-06 最终全 PR 审查的追加修复
|
||||
|
||||
两名全 PR 审核者又发现 R17–R28;其中 R21 来自97项偏好消费者追踪,R22来自真实Ubuntu22.04打包失败。这些问题均已实施并由独立提交前审查复读通过(Standards/Spec无未关闭确定项)。
|
||||
|
||||
追加修复后的本地验证:Core **713 passed / 1 ignored**,8组contract全部通过(5/22/17/3/21/12/15/15);Tauri **429 passed**;Linux Windows-host **48 lib + 4 host**;前端build和66项全部通过。Core/Linux严格clippy、全部改动的rustfmt和架构/秘密/基线门禁通过。Rust1.88 Tauri check通过。
|
||||
|
||||
Windows进程回归使用真实子进程树,验证大stdin阻塞、启动后取消、父先退出、future drop和自然完成的区别;只对本次新建进程作Job控制。Fcitx旧头文件验证准确复现了5.0.14的编译错误,修复后旧头文件构建及本机5.1.19的CTest通过;完整Ubuntu22.04运行仍由最终CI确认。
|
||||
|
||||
Linux迁移明确依赖调用者提供的home_dir;None不读取真实HOME或系统旧vault。旧文件仅来自该目录,旧来源不删除,Secret Service暂不可用不阻断本地ASR但保持迁移未完成标记。测试/自定义工厂不再依靠cfg(test)判断是否可读系统凭据。
|
||||
|
||||
旧head `d4430fcf` 的上游CI和桌面构建成功,但fork Linux制品因R22失败;Android APK在Rust编译后因Maven Central Kotlin POM GET 403失败。它们不能计为追加修复的最终CI,最后提交会重新触发全部验证工作流。
|
||||
|
||||
## 真实环境证据边界
|
||||
|
||||
当前会话可验证源码、Windows/WSL 自动测试和远端各平台构建。实际 GUI 焦点、麦克风断连、
|
||||
TSF/AX/fcitx5 与目标应用交互、系统凭据服务、安装升级回滚及正式签名/公证,需要相应设备证据。
|
||||
未完成的设备验证将逐项保留,不能因 CI 或 fixture 成功而标为已完成。
|
||||
|
||||
当前2.0 macOS普通听写复用了原选区路径的app/pid目标恢复,1.x普通听写未调用该路径;不能倒推为既有普通听写能力。同应用多窗口/多输入控件的精确焦点与光标恢复仍不在已证范围,真实AX/TIS/Secure Input行为需分别验证。
|
||||
|
||||
既有未跟踪 `openless-all/app/src-tauri/openless-ime-payload/` 不属于本轮修改。
|
||||
@@ -1,186 +0,0 @@
|
||||
# PR #1019:更新范围后的桌面复审闭环
|
||||
|
||||
范围依据:[2.0需求](./2.0-requirements.md)与[桌面验收清单](./2.0-desktop-acceptance.md)。Linux剩余Host/UI按[交接目录](./linux-egui-handoff/README.md)移交,不计作本批桌面功能缺陷。
|
||||
第一轮固定比较:`beta@fc9824ee` → `dc350780`;审查整个PR,不仅审查文档提交。本文续记此前R01–R28之后发现的问题。
|
||||
|
||||
## 第一轮:新的独立团队
|
||||
|
||||
| 审查方 | 范围 | 确定问题 |
|
||||
| --- | --- | --- |
|
||||
| Windows专项 | 原生输入/热键/录音/本地ASR/进程Job/窗口与打包;对照1.x生产路径 | R32、R33、R35 |
|
||||
| macOS专项 | AX/TIS/隐私/流式/原生ASR/窗口/CLI/构建;对照1.x生产路径 | R30 |
|
||||
| 共享产品专项 | React IPC/事件、渠道/凭据/模型、QA/Selection/Agent、历史/词典/风格/市场/Remote | R31、R34 |
|
||||
| 主代理复核 | Core主听写/停止/取消/重试/插入、需求和证据边界,逐条确认团队发现 | R29及全部问题的修复复核 |
|
||||
|
||||
本轮Standards未确认独立硬性违规;首轮独立审查发现7项确定问题,综合验证与修复追踪追加R36。内部审核不替代GitHub正式批准,也不证明真实设备功能全部通过。
|
||||
|
||||
## 问题与验证登记
|
||||
|
||||
| ID | 问题、基线与影响 | 修复及验证状态 |
|
||||
| --- | --- | --- |
|
||||
| R29 / P1 | Core停止请求在Starting等待时重新读取当前session;取消A并启动B后,旧stop会完成B并落字,违反D02会话隔离 | 已固定首次目标session;公开Core回归先红(返回B的Inserted)后绿 |
|
||||
| R30 / P1 | Core流式取消未排空native write即恢复TIS,且先释放voice lease;旧CGEvent输入可能与新会话重叠。1.x先等typer结束再恢复,违反D09 | 已修复:排空后恢复、清理后释放占用;一次性落字也等待已提交效果,收尾由Host executor持有,不随stop调用方丢弃。流式/一次性/调用方drop三项先红后绿 |
|
||||
| R31 / P1 | QA dismiss等待旧runtime.cancel后无条件清snapshot/HideQa;期间重开的新对话被清空,违反D10 | 已在首await前逻辑关闭,异步部分仅清旧owner;新对话/show-only/新preview三项先红后绿,全QA合同24项通过 |
|
||||
| R32 / P2 | Windows普通落字恢复原目标失败后直接返回错误,遗漏1.x允许时的copy-only兜底,违反D09 | 已恢复按开关复制降级,不向新焦点粘贴;native源码合同先红后绿,既有结果映射随全Tauri测试通过 |
|
||||
| R33 / P2 | Windows普通落字复用选区恢复逻辑,漏掉1.x的IsIconic→SW_RESTORE,最小化目标不能恢复,违反D09 | 已补还原后激活和原目标指纹复核;native源码合同先红后绿,真窗口证据单列 |
|
||||
| R34 / P1 | 本地模型UI在Core激活前创建/启用/置顶渠道,提前修改active;模型缺失或prepare失败仍破坏原云渠道,违反D06 | 已改为Core准备成功后一次metadata提交;三runtime及缺失/禁用渠道回归先红后绿,含并发编辑共24项合同通过;前端source合同约束新生产路径 |
|
||||
| R35 / P1 | Windows Less Computer窗口虽存在,设置组件、语音快捷键编辑和原生监听仍被macOS条件挡住,违反明确承诺的D12 | 已补内外层UI和原生热键;hook context按安装线程隔离,注册回执参与设置事务,迟到注册核对目标;重绑/禁用只清所属Starting/Recording slot。回归及全Tauri通过,native注入证据见下 |
|
||||
| R36 / P1 | Less Computer取消会释放Capture,但capture_cancelled把不存在/被替换的lease判为未取消;冷启动迟到后可继续录音,现有headless示例亦失败 | 已将失去捕获所有权判为失效,首await前claim、context/native/转写后复验;取消标记先于资源等待,迟到事件不触碰Agent run。两类回归先红后绿,Less Computer24项及headless示例通过 |
|
||||
|
||||
## 本轮已取得的自动证据
|
||||
|
||||
- 文档提交`dc350780`:15个文档文件,链接/空白检查通过,已推送原PR分支。
|
||||
- 第一轮Windows专项:183项限定Rust测试及4项Node合同通过;包含真实Job进程回归,不代表真实输入/设备验收。
|
||||
- 第一轮macOS专项:4项源码/打包合同通过;主机Windows,Speech检查按平台跳过,未执行本地macOS原生测试。
|
||||
- 第一轮共享产品:TypeScript/Vite与66项前端/合同测试通过;后续修复后还须重新完整执行。
|
||||
|
||||
### 第一轮修复后的综合验证
|
||||
|
||||
- Core:718 passed / 1 ignored,领域合同5/24/17/3/24/12/15/15全部通过。
|
||||
- Windows Tauri:434 passed / 1 ignored;Rust1.88 `cargo check`通过。ignored为需显式触发的原生键盘注入smoke,不是普通单元测试失败。
|
||||
- 前端:TypeScript/Vite构建与67个测试入口通过,含新增Windows原目标合同与更新后的Core激活接线合同。
|
||||
- Linux Windows-host:48 lib + 4 host通过;真实Linux条件编译/设备效果由对应runner/设备单列,不把零运行的cfg测试算通过。
|
||||
- Core/Linux严格Clippy与依赖、秘密、隔离、runtime seam、公开面、command/event基线检查通过;headless示例重新可运行。
|
||||
- Windows双monitor真实smoke曾通过独立收键、关闭和重建;末次复跑`SendInput=0`未通过,记录为原生注入环境未稳定复验,不宣称重复设备验收成功。最终主听写/ASR/Agent真实设备闭环仍待对应验收。
|
||||
- 文档head `dc350780` 的CI `33983624572`四平台通过;这只是修复前代码证据,新修复提交必须重新运行CI。
|
||||
|
||||
## 后续轮次记录
|
||||
|
||||
第一轮修复、综合验证并推送后,派全新的团队审查整个PR;若发现确定问题,继续修复、验证、推送,再交新的团队复审。准确head、测试计数和最终结论在完成时补录,不沿用旧head证据冒充新验证。
|
||||
|
||||
### 第二轮:`fc9824ee...98a0ca38`
|
||||
|
||||
两名全新Windows/macOS审查员交叉覆盖整个PR的原生路径、React入口与共享领域,主代理补充复现及修复复核。不是仅审查第一轮补丁;本轮确认以下9项Spec问题,未确认独立Standards违规。
|
||||
|
||||
| ID | 确定问题 | 修复/验证状态 |
|
||||
| --- | --- | --- |
|
||||
| R37 / P1 | Less Toggle/Auto松键后Esc仅清Core,Host Recording slot常驻导致永久无法再开;关闭面板也未清原生捕获 | 已统一Esc、关闭、胶囊及CLI取消的所属capture收尾;Core CLI回归先红后绿,保留QA独立作用域;Host可取消后复录 |
|
||||
| R38 / P2 | Less桥丢弃原生Instant,在ASR冷启动排队后重建now,把Auto短按当成长按 | 已透传modifier/combo真实Instant及generation;450ms启动+50ms短按回归先红后绿 |
|
||||
| R39 / P2 | 录音控制ready-check与pending.push之间可被attach/flush穿过,静音Stop永久丢失 | 已固定pending→slot锁序;Starting保存同一control,静音和胶囊Stop交接前后恰执行一次 |
|
||||
| R40 / P2 | Less录音、电平与转写没有生产事件,Composer只等旧operating胶囊状态,Windows无glow可代替 | 已接Core typed VoiceState、React/胶囊;保留一条带原seq/session的有效投影供截断重放恢复,拒绝旧终态覆盖新录音;语音忙时保留草稿且不提交 |
|
||||
| R41 / P1 | Less成功非debug语音未删除WAV,回退到共享Core后丢失1.x成功归档清理 | 已按成功/非debug条件discard,debug与失败保留;录音保留策略回归先红后绿 |
|
||||
| R42 / P1 | text_inserter.begin尚未登记时取消会释放voice lease;迟到TIS准备/恢复干扰新会话 | 已在begin前登记Shared preparation并持hold,取消与迟到start共同等待一次恢复;原回归和丢弃start回复回归通过 |
|
||||
| R43 / P1 | 会话中关闭cursor_context_enabled后,完成仍按冻结true重启AX编辑观察 | 已与设置事务串行并读当前开关,普通/流式两分支回归先红后绿 |
|
||||
| R44 / P1 | QA/Less先release gate再等待native stop,旧mute恢复可破坏新录音;冷ASR取消后仍可开mic | 已分离逻辑取消与资源hold,初始化/stop/ASR清理完毕前Busy;recorder前检查token,已提交任务持有收尾;也覆盖主听写无插入、初始化/停止/取消回复被丢弃与shutdown清理 |
|
||||
| R45 / P2 | QA Completed/Failed/Cancelled被`phase != Idle`误判仍在使用语音,阻止Less启动 | 已只阻止真实活动阶段,三个QA终态保持面板打开也可启动Less;公开回归通过 |
|
||||
|
||||
第二轮问题全部修复、测试并推送前,不将`98a0ca38`标为最终闭环;下一轮须由新的审查团队复审。
|
||||
|
||||
第二轮定向证据:Windows Host最终8项通过;Core生命周期10项(含最后CLI回归)、typed VoiceState合同及最近投影回归通过;插入准备、隐私、丢弃回复的回归通过。曾挂起的旧stop测试因owned startup首次poll前使用notify_waiters丢通知,已改为保留permit的notify_one,单项再次通过;不是跳过测试。
|
||||
|
||||
第二轮综合验证:Core 722 passed / 1 ignored,生命周期10项、其它合同6/24/17/3/24/12/15/15全部通过;Windows Tauri 440 passed / 1 ignored;前端构建与67个测试入口通过;Linux Windows-host 48+4、headless示例、Core/Linux严格Clippy、依赖和公开合同检查通过。MSRV与最终远端CI按精确修复head记录。`98a0ca38`的CI `34002890692`四平台通过,但不能代替第二轮修复提交的CI。
|
||||
|
||||
第二轮修复提交`c0807d97`已推送;CI `34006442463`的Windows、macOS、Linux、Android全部通过。Linux artifact按PR条件跳过,不算产物验收。
|
||||
|
||||
### 第三轮:`fc9824ee...c0807d97`
|
||||
|
||||
两名新的独立审查员交叉检查桌面原生路径与整个共享Core,主代理复现并处理完成回调竞态。本轮确认以下6项Spec问题,未确认独立Standards违规。
|
||||
|
||||
| ID | 确定问题 | 修复/验证状态 |
|
||||
| --- | --- | --- |
|
||||
| R46 / P1 | Selection Voice通用Esc/故障/shutdown只取消Core状态,Host麦克风与slot仍存活;冷启动也可能留下目标owner | 在首await前绑定既有RecordingControlSink,所有终止入口只调用同一所属capture清理;同步撤销Starting目标,原生stop/ASR清理完成前保留resource hold。Core与真实Tauri Host seam先红后绿 |
|
||||
| R47 / P2 | QA Completed/Failed/Cancelled仍拦截通用取消路由,Selection Voice不能收到Esc | 路由仅匹配Recording/Thinking/AwaitingApproval;三种QA终态与Selection并存的公开回归先红后绿 |
|
||||
| R48 / P2 | Less在Agent启动前遇到recorder.stop、ASR.finish或空转写错误,仅回Idle/日志,没有可见错误 | 复用capture_fault原子终态认领,发布一次安全错误且返回脱敏错误;用户取消不报Error。各错误及取消排除回归先红后绿 |
|
||||
| R49 / P2 | Host翻译标志可跨按钮/CLI/静音停止泄漏到下次会话;Starting捕获上下文期间的翻译请求又会丢失 | 删除Host重复状态,Core保存当轮意图并在所有停止入口统一应用冻结上下文,显式stop override优先;冷启动、三种停止入口与失败补偿回归通过 |
|
||||
| R50 / P2 | Host重建Core胶囊payload丢失warming等字段,native start返回后首PCM未到却显示已就绪 | 透传完整payload;兼容缺省的recordingReady仅在首个实际PCM回调置真,零电平/0ms首帧亦有效;Less遵循同一首帧语义。Core、Host和事件桥回归通过 |
|
||||
| R51 / P1 | A发布Completed后等待设置锁,期间A取消且B启动;A迟到回调会清B状态并为旧文本注册编辑观察 | 观察注册在锁后复核session,迟到原生注册按generation撤销;完成复用按session复位并检查反馈归属。确定性并发回归先红(B被置空)后绿 |
|
||||
|
||||
第三轮综合验证时,新增wire字段暴露canonical fixture未同步、旧热键source合同仍绑定被删除的Host路径;同步实际合同而不跳过断言。完成回调并发测试改用多worker测试runtime,保持Core生产runtime边界检查不变。
|
||||
|
||||
第三轮最终本地验证:Core 725 passed / 1 ignored,生命周期14项、其它合同6/24/17/3/24/12/15/15全部通过;Windows Tauri 444 passed / 1 ignored;前端构建与67个测试入口通过;Linux Windows-host 48+4、headless示例、Core/Linux严格Clippy、Rust1.88 Tauri检查、依赖/秘密/隔离/runtime/公开面/command-event检查均通过。修复提交的远端平台CI与第四轮新团队结论仍单独记录,不把本地验证表述为macOS设备验收。
|
||||
|
||||
### 第四轮:`fc9824ee...90cbd014`
|
||||
|
||||
新的Windows/macOS审查员再次审查整个PR,主代理复核共享合同和数据接线。本轮Standards未确认独立违规;确认以下3项Spec问题。第三轮提交已逐Git对象核对SHA后上传至原PR,未重写历史。
|
||||
|
||||
| ID | 确定问题 | 修复/验证状态 |
|
||||
| --- | --- | --- |
|
||||
| R52 / P2 | 市场服务在构造时独立保存HTTP client,绕过共享net代理缓存;关闭useSystemProxy仍使用系统代理,且运行中切换不生效。1.x市场/OAuth使用共享client | 已删除固定client和重复builder,逐请求URL复用共享net缓存,仍禁止重定向并为loopback直连;独立进程假代理回归先红后绿,覆盖启动关闭、同服务true→false→true及混合配置OAuth回环 |
|
||||
| R53 / P2 | 取消A的原生清理已完成并释放资源,但A调用方尚未恢复时B可以启动;A迟到回复无条件发送HideDictationFeedback。其他失败/shutdown出口也缺归属检查 | 已统一所有Hide出口的session守卫,检查和同步Host enqueue保持同锁;手工延迟cancel回复的公开回归先红(Hide覆盖Show)后绿,R51回归保持通过。Tauri当前该HostAction是no-op,故这是共享Core合同问题,不声称已复现桌面胶囊故障 |
|
||||
| R54 / P1 | Remote鉴权在获取lifecycle锁前读取旧PIN;轮换持锁期间已读取旧值的鉴权等待后可在新服务代次放行,生产WS没有第二次PIN检查 | 已将PIN读取置于与轮换相同的生命周期临界区;公开接口并发回归先红(Ok,预期BadPin)后绿,旧PIN被拒、新PIN仍可认证 |
|
||||
|
||||
第四轮最终本地验证:Core 726 passed / 1 ignored,生命周期14项及其它合同6/24/18/3/24/13/15/15通过;Windows Tauri 444 passed / 1 ignored;TypeScript/Vite与67个前端测试入口、Linux Windows-host 48+4、headless示例、Core/Linux严格Clippy、Rust1.88 Tauri检查、六个架构/安全/兼容基线检查和定向格式检查均通过。独立修复复核确认R53全部10个原有Hide出口共用归属判断,没有复制遗漏。此提交后继续第五轮全新团队审核。
|
||||
|
||||
### 第五轮:`fc9824ee...87e09e1c`
|
||||
|
||||
新的Windows/macOS审查员各自完整审核该PR,未确认独立Standards违规;确认两项Spec问题。`90cbd014`的Windows/Linux/Android CI通过,macOS被新提交自动取消,不计通过。为`87e09e1c`发起的桌面手动打包验证`34009260079`在本轮发现新问题后主动取消,待代码复审收敛再为最终head重建,不将旧产物当作新head证据。
|
||||
|
||||
| ID | 确定问题 | 修复/验证状态 |
|
||||
| --- | --- | --- |
|
||||
| R55 / P2 | Tauri与Linux Host注入仅查当前线程Handle的TokioTaskSpawner;真实麦克风OS线程没有runtime上下文,QA/Less/Selection静音与故障收尾任务被直接丢弃,检测器已fired后不会重试 | Tauri使用框架共享executor,Linux捕获已有Host Handle并用于所有原生线程;默认构造无runtime时在开仓前显式失败,自定义Host可显式注入。两Host真实std::thread回归先红(Disconnected/RecvError)后绿,Core不私建runtime;交接01及main构造已同步 |
|
||||
| R56 / P2 | EventBus在锁外分配seq,backlog更新与broadcast也不处于同一临界区;并发原生/Agent事件乱序,UI最大seq去重丢有效事件,replay水位可能先于入队 | 8线程回归先红(首条seq=6,预期1)后绿;复用backlog锁覆盖分配、投影、入队及发送,replay在同锁内读取水位,事件完整10项通过。不是只对重放结果排序 |
|
||||
|
||||
第五轮最终本地验证:Core 727 passed / 1 ignored,生命周期14项及其它合同6/24/18/3/24/13/15/15通过;Windows Tauri 445 passed / 1 ignored;Linux Windows-host 50+4、headless、TypeScript/Vite与67个前端测试入口、Core/Linux严格Clippy、Rust1.88 Tauri检查和六个架构/安全/兼容基线检查通过。R56线程回归另连续10轮、共8万消息通过;独立有界复核未发现锁序或消费者语义问题。R55两端从真正OS线程调用生产同型spawner,避免只在tokio测试线程内验证。旧大合同的构造示例也已链接当前executor前置说明。修复提交后交第六轮全新团队,不提前宣称闭环。
|
||||
|
||||
### 第六轮:`fc9824ee...588fb35b`
|
||||
|
||||
两名全新审查员再次覆盖整个PR;macOS专项没有新增确定发现,Windows专项确认以下一项同时影响Windows/macOS的共享Tauri录音回归。Standards没有独立硬性违规;同时纠正两处非阻塞过时注释(Windows Less入口、Codex非流式能力/共享分流位置),不将其计作新功能缺失。
|
||||
|
||||
| ID | 确定问题 | 修复/验证状态 |
|
||||
| --- | --- | --- |
|
||||
| R57 / P2 | 可选录音准备被升级为硬依赖:输出设备不存在/静音操作失败,或归档目录路径准备失败时,TauriAudioRecorder直接返回Err而不启动可用麦克风。1.x对静音失败仅告警,归档路径以Option传入Recorder | 已恢复辅助效果告警降级;三种失败组合以旧策略复验均返回Err,新策略回归通过;成功guard持有/释放及禁用不调用平台另有回归。真实Recorder.start错误仍传播,归档为None时不伪造文件。这里验证准备函数与生产接线,不声称真实麦克风已实测 |
|
||||
|
||||
第六轮独立Windows定向验证:Agent 5项、hook 12项、IME 50项通过,交互键盘注入1项未运行;macOS相关源码合同通过,Speech检查在Windows明确跳过。以上并未覆盖R57,不能以绿灯代替新增回归。修复提交后继续第七轮全新团队审核。
|
||||
|
||||
第六轮最终本地验证:Tauri 447 passed / 1 ignored,Rust1.88检查通过;Core 727 passed / 1 ignored、生命周期14项及全部领域合同、Linux Windows-host 50+4、headless、严格Clippy、前端构建67项和六个架构/安全/兼容基线检查通过。`588fb35b` CI `34009963206`四平台已通过,但修复提交须重新独立验证;Linux artifact条件跳过不算产物通过。
|
||||
|
||||
### 第七轮:`fc9824ee...5f668b1d`
|
||||
|
||||
两名全新审查员确认3项Spec问题,主代理再复现QA跨线程关闭竞态,合计4项;Standards无独立硬性违规。macOS目标恢复失败复制候选未计缺陷:被引用的1.x失败分支在macOS不可达,不能仅按共享函数片段推断旧平台语义。
|
||||
|
||||
| ID | 确定问题 | 修复/验证状态 |
|
||||
| --- | --- | --- |
|
||||
| R58 / P2 | Windows默认Agent裸名无法定位npm生成的.cmd入口,产品推荐的安装方式导致检测/运行NotFound | Windows Host按有效PATH解析必要可执行扩展,显式路径不变,复用stdlib转义及原有Job/管道;真实shim先红NotFound后绿,含.exe/显式.cmd/裸名.cmd和空格、引号、元字符参数 |
|
||||
| R59 / P2 | macOS Qwen/Whisper同属Generic但有独立cache;原子激活不释放同runtime旧lease,状态又优先报Qwen,切Whisper后仍显示旧模型并长期驻留 | 已按真实previous target认领/预载/释放代次;cache同锁核对模型和owner,状态按目标家族读取。Core合同先红双缓存后绿,覆盖同ID新代次、普通使用撤销旧权限、渠道切换和失败补偿;普通ASR迟到加载可用未缓存Arc继续本轮,激活所属迟到加载报错且不覆盖新cache |
|
||||
| R60 / P2 | Foundry GPU→CPU切换/首次CPU下载提示被延迟缓存到转写完成,Notification又无Tauri消费者,原1.x实时反馈消失 | Native回调立即发布冻结session通知,released拒绝迟到回调;桥核验当前归属/阶段并显示胶囊,QA离开转写、取消与窗口接管清所属提示。实时/展示/清理回归先红后绿,无真实GPU验收声明 |
|
||||
| R61 / P2 | QA dismiss在两个同步state锁之间允许另一OS线程重开;旧操作清空并隐藏B,发生在首await之前,R31原异步清理回归未覆盖 | 公开事件订阅边界控制线程交错后确定红:B录音返回Cancelled。presentation锁收口录音/文本/仅显示与关闭,所有native await在锁外;状态/回调/模式/答案替换与对应事件在同一state guard内发布,旧阶段先核对session/phase,Host动作不持state锁。三种重开及QA合同25项通过;独立复核又确认并关闭模式切换/撤回旧答案快照窗口 |
|
||||
|
||||
修复提交后继续第八轮全新团队审查;未运行的macOS模型、真实GPU/GUI等仍按设备边界记录,不以源码合同替代。
|
||||
|
||||
第七轮最终本地验证:Core 727 passed / 1 ignored,生命周期14项及合同6/26/18/3/25/13/15/15全部通过;Windows Tauri 452 passed / 1 ignored;Linux Windows-host 50+4、headless、TypeScript/Vite与67项前端入口、Core/Linux严格Clippy、Rust1.88 Tauri和六个架构/安全/兼容基线检查通过。Windows CLI回归还覆盖大小写重复PATH的实际最后覆盖值;R59普通ASR加载与自动清理出口均保留新owner,R61所有当前修改已完成有界复核。实际macOS cache代码仍必须由新head的macOS CI编译,不沿用前一提交结果。
|
||||
|
||||
### 第八轮:`fc9824ee...2d8fd1b7`
|
||||
|
||||
两名全新Windows/macOS审查员覆盖整个PR,主代理沿失败及并发链路补充复现,共确认以下8项Spec问题;没有独立Standards硬性违规。`2d8fd1b7`的CI `34013888722`四平台通过,但仍存在这些行为缺陷,绿灯不能替代产品合同复核。
|
||||
|
||||
| ID | 确定问题 | 修复/验证状态 |
|
||||
| --- | --- | --- |
|
||||
| R62 / P1 | Windows npm OpenCode `.cmd`可检测但无法运行:Core总会加入多行自动化提示,旧Argv传输被Rust Windows批处理参数校验拒绝 | 复用现有stdin管道;官方OpenCode v1.18.29及v1.2.18均读取非TTY stdin。实际Windows `.cmd`进程fixture先检测、再传Core多行提示,先红(换行参数被拒绝)后绿;不是已运行真实模型的OpenCode验收,不引入shell自拼转义 |
|
||||
| R63 / P2 | Selection Voice把Windows已发送的PasteSent判成失败,预览可重复确认且历史丢失;QA确认又丢弃真实回执 | PasteSent贯通原生映射、Core状态、历史与IPC,保持“已发送”不同于“已确认插入”;直接/QA预览、重复确认、失败重试与wire回归通过 |
|
||||
| R64 / P1 | 默认Raw不经过Polishing,却由真实Pipeline无条件发送最终PolishDelta,被Backend判InvalidState;finish异常也没有回收残留engine资源 | Raw仅返回最终EngineResult并走一次性落字,不准备流式/TIS;只在真实LLM阶段发PolishDelta。真实Pipeline连续两次Raw先红后绿;EngineFailure清理回归先红cancel=0后绿cancel=1,复用已有owned清理入口 |
|
||||
| R65 / P1 | QA语音编辑已持QA麦克风lease,却通过SelectionVoice.begin再次领麦克风而Busy;文本预览还会长期占音频,阻止语音续问/主听写 | 纯编辑直接进入Processing,不创建音频占用;Preview只释放匹配session的逻辑lease,保留原生cleanup hold。公共回归原4项Busy失败修至5项全通过,覆盖活动录音互斥及原生清理等待 |
|
||||
| R66 / P1 | QA撤销在会话owner读取、预览撤回、答案写回之间被新turn抢占;确认应用的同根因可拿B预览应用旧文本,或由旧完成回调关闭B | 撤销与签发均收为Core单个按turn校验的状态事务,删除Host三段编排及无身份replace_last_answer;原生完成仅dismiss_session所属回合,stale不撤销真实回执。两类撤销与两类确认窗口均先红后绿,共5项覆盖同conversation、新conversation、仅show空面板、失败重试及重复确认;主按钮/热键dismiss语义不变 |
|
||||
| R67 / P1 | 旧CLI/按钮停止与已排队物理Press重叠,B已返回Started但A清掉其generation,Hold松开变Noop | 公开入口先真红;只改reset锁序仍红,最终将phase读取、Start决定和真实Starting认领置于同hotkey锁,锁外继续原异步启动。普通入口复用同一认领函数,无新增状态;32轮受控交错绿,runtime seam检查通过 |
|
||||
| R68 / P1 | QA/划词语音迁移后被Tauri无条件写WAV,违反1.x不落盘边界;归档删除失败还阻断已有内存转写 | Core冻结RecordingPlan.archive_enabled=false,Host跳过归档目录/文件及prune,保留实际停止/清理错误;两种QA管线、划词语音与主听写/Less对照回归先红Persistence后绿,不以忽略删除失败替代不落盘 |
|
||||
| R69 / P2 | Selection A发Completed后persist重新读全局state;此时B已begin,A历史被写为B/空原文及错误指标 | 在完成原锁内克隆既有SelectionState,并只持该快照持久化;公共事件边界回归先红空原文后绿,核对A原文、结果、LLM与耗时;没有新增并行状态或锁 |
|
||||
|
||||
第八轮最终本地验证:Core 729 passed / 1 ignored,生命周期15项及合同6/26/18/3/5/25/5/13/16/16全部通过;Windows Tauri 455 passed / 1 ignored、Rust1.88检查通过;Linux Windows-host 50+4、headless示例、Core/Linux严格Clippy、TypeScript/Vite与68个前端入口、六个架构/安全/兼容基线检查全部通过。Tauri首轮并行测试在共享全局side-combo监听的既有测试无超时recv处挂起,已终止且不计通过;最终完整测试按单线程执行,两次通过,没有跳过该测试或改动生产监听。Raw/归档独立交叉复核无新增发现,R67已通过完整Core复验,R66完整5项通过后停改。修复提交后继续第九轮全新团队审核;新head平台CI与真实设备验收仍分别记录。
|
||||
|
||||
### 第九轮:`fc9824ee...d16324c3`
|
||||
|
||||
Windows/macOS两名全新审查员均已完成整个PR的独立复核:Standards无新增确定违规,Spec共3项确定问题,主代理逐条核对并复现。`d16324c3`的CI `34016669726`四平台通过,Linux制品任务按PR条件跳过;该结果属于修复前head。
|
||||
|
||||
| ID | 确定问题 | 修复/验证状态 |
|
||||
| --- | --- | --- |
|
||||
| R70 / P1 | Windows主听写在等待上下文/凭据后才捕获落字目标;期间A切到B,最终把B当作原目标,违反1.x首await前冻结及D09 | `TextInserter::capture_target`在共用认领阶段同步冻结本轮目标,仅异步`begin`准备原生输入源;不增加全局槽/Map。直接/CLI/物理入口A→B先红后绿,取消后新B和不插入零准备共3项通过;macOS共享新机制,但不误称其1.x已有早期恢复 |
|
||||
| R71 / P2 | QA普通语音的状态/电平仅发送给QA面板,原Recording/Transcribing/Polishing/终态胶囊反馈丢失,违反D03/D10 | 恢复同一Core事件的Host胶囊投影,区分语音/文本、CPU提示及旧owner;已呈现QA使用实际返回epoch,同锁检查/写入,旧终态及自动隐藏不覆盖后继原生提示。生命周期、迟到Recording和原生CAS出口均先红后绿;首帧快失败等小影响边界按维护者决定暂缓,见收口说明 |
|
||||
| R72 / P2 | 共用context无条件解析ASR/LLM/Omni:Raw被禁用的无关LLM阻断,Omni被禁用的传统ASR阻断;QA文本也被无关ASR阻断 | Raw和Omni分别公开API先红InvalidState后绿。按实际听写/纯ASR/QA文本/QA语音用途解析所需渠道;Raw保留可选LLM解析失败,停止时临时翻译不能切到后来启用的渠道。实需ASR/LLM仍报错,QA文本只要求其响应provider |
|
||||
|
||||
同时订正macOS证据表述:1.x普通听写未调用选区的app/PID恢复路径,当前2.0才复用该机制;不能将新实现倒写成旧基线能力。该项为文档事实更正,不另计运行时缺陷。
|
||||
|
||||
第九轮最终验证:Core 731 passed / 1 ignored、生命周期15项与合同6/3/26/18/3/5/25/5/13/16/16、Linux Windows-host 50+4、Core/Linux严格Clippy、headless示例、六个架构/安全/兼容检查、TypeScript/Vite及68个前端入口均通过。Windows Tauri完整串行测试460 passed / 1 ignored,Rust1.88检查通过;9个改动Rust文件的限定格式及diff检查通过。以上为本地自动证据,不代替最终head CI和真实设备验收。
|
||||
|
||||
### 发布优先收口
|
||||
|
||||
维护者明确要求:若无重大问题,不再修复小影响或不能稳定复现的问题,留下文档后提交推送原PR,准备合并。因此不启动第十轮全面审查,不继续扩展Host显示origin、Core QA开始顺序和旧Selection Voice定时器改造;这些尚未完成的方案未纳入本次代码。
|
||||
|
||||
已知暂缓项仅涉及QA胶囊展示:首条Recording尚未被桥消费时极快失败可能没有胶囊Error;旧Selection Voice自动隐藏及极短跨会话切换可能让未呈现首帧的QA提示被忽略或提前隐藏。QA面板仍接收Core错误/终态事件,当前证据未表明这些显示边界影响回答、录音结果或数据。其依据是源码时序分析,不宣称真实设备稳定复现。后续如有稳定用户复现或影响扩大,再补完整显示origin绑定和统一定时器归属。
|
||||
|
||||
本次两名审查员的整个PR复核及有界核验均等待最终返回。最终提交、验证计数和新head CI状态写入原PR描述;不把上一head成功或未执行的设备/制品检查记作本次通过。验收政策同步见[发布收口决定](./2.0-desktop-acceptance.md#5-2026-09-06-发布收口决定)。
|
||||
|
||||
## 不得混同的完成条件
|
||||
|
||||
- 源码/自动验证:本记录跟踪确定缺陷是否全部关闭。
|
||||
- Windows/macOS设备验收:按D01–D19逐项记录目标应用、录音、原生模型、权限、升级与签名证据;未执行的不填通过。
|
||||
- Linux接入:Core合同与文档可交接;Linux产品侧待办独立交egui团队。
|
||||
- GitHub:CI、正式review与merge gate分别记录;不自动合并、打tag或发布。
|
||||
@@ -1,200 +0,0 @@
|
||||
# 供应商渠道卡片化 实施计划
|
||||
|
||||
> 状态:P0 已完成(2026-08-07,PR #918)
|
||||
> 日期:2026-08-04
|
||||
> 范围:设置 → AI 提供商,LLM 润色 + ASR 语音转写
|
||||
> 参考:[Calcium-Ion/new-api](https://github.com/Calcium-Ion/new-api) 的 Channel 模型与重试策略
|
||||
|
||||
## 1. 要解决的问题
|
||||
|
||||
今天一个供应商只能存一份配置(一把 key、一个 endpoint、一个模型)。实际使用中:
|
||||
|
||||
1. **同一家有多把 key**(主号 / 备号 / 白嫖号),现在只能存一把,换 key 靠手动覆盖粘贴
|
||||
2. **key 之间要频繁切换**,切换过程中旧配置就丢了
|
||||
3. 某把 key 被限流(429)时没有任何自动应对,整条润色链路直接失败
|
||||
|
||||
目标:把配置从"一个供应商一个槽"变成"一张张可命名、可排序、可开关的卡片",并让失败能自动顺延到下一张卡片。
|
||||
|
||||
## 2. 现状核对
|
||||
|
||||
| 事实 | 位置 |
|
||||
| --- | --- |
|
||||
| 存储层已经是 `HashMap<String, Entry>`,key 是 preset id | `credentials.rs:169` |
|
||||
| `CredsLlmEntry` 已有 `displayName` 字段,前端从未使用 | `credentials.rs:257` |
|
||||
| ASR 凭据按 provider 隔离正确(空槽才填默认值) | `ProvidersSection.tsx:370` |
|
||||
| LLM 切 preset 会**强制覆盖** endpoint/model,注释所述的"共用槽"bug 早已不成立 | `ProvidersSection.tsx:305` |
|
||||
| 全局零重试 / 零故障转移(`rg retry\|backoff\|fallback` 无命中) | — |
|
||||
| 凭据读取是**隐式全局** `CredentialsVault::get(...)` 去查 `root.active.*`,调用方无法指定渠道 | coordinator.rs / commands/providers.rs 共数十处 |
|
||||
| ASR provider id 同时承担**协议路由 key**(百炼一个 id 分三协议,stepfun 分两协议) | `coordinator.rs:341` |
|
||||
| 新手引导直接嵌 `<ProvidersSection kind="asr" />` | `Onboarding.tsx:208` |
|
||||
| Windows 默认 ASR 是本地 Foundry(无需 key,开箱即用) | `credentials.rs:155` |
|
||||
| LLM 单次请求超时 30s | `polish.rs:23` |
|
||||
|
||||
**结论**:存储结构不用推倒,改 key 语义即可;真正的成本在"凭据显式化"这次重构。
|
||||
|
||||
## 3. 已定的设计决策
|
||||
|
||||
| 决策 | 结论 |
|
||||
| --- | --- |
|
||||
| 范围 | LLM 与 ASR **都**做卡片 |
|
||||
| 排序 | 列表可拖拽,越靠上越优先;启用列表的**第一个 = 当前使用** |
|
||||
| 开关 | 打开 = 加入重试队列;**关掉自动沉到列表末尾**;重新打开回到启用组末尾 |
|
||||
| 触发切换 | 429 等错误**立即**切下一个渠道 |
|
||||
| 超时 | **不触发**切换(本期先这样) |
|
||||
| 渠道失败 | **只在卡片上标红**(如「上次失败 · 401 · 3 分钟前」),**不自动禁用** |
|
||||
| 全部失败 | 润色链路降级为**直接插入 ASR 原文** + 右上角提示 |
|
||||
| 特殊项 | 本地引擎(qwen3 / sherpa / Apple 语音 / Foundry)与 Codex OAuth **不做预置固定卡片**,它们是「+添加渠道」供应商下拉里的普通选项,选中即长出卡片,表单里没有 key/地址字段 |
|
||||
|
||||
### 3.1 ASR 与 LLM 语义统一
|
||||
|
||||
429 只出现在**建连 / 鉴权阶段**——此时一个字都还没吐出来,音频缓冲尚未被消费,换渠道重连是安全的。因此两边共用同一套心智:
|
||||
|
||||
> 排序 = 优先级;开关 = 在不在重试队列;失败(非超时)顺延下一个。
|
||||
|
||||
ASR 唯一的额外规则:**一旦开始出字就不再切换**,之后连接断了就是断了(流式已吐字,回滚会造成文字重复或跳变)。
|
||||
|
||||
### 3.2 429 冷却(必须有)
|
||||
|
||||
若不加冷却,限流期间**每一次**听写都会白赔一次「打 1 号 → 429 → 打 2 号」的往返(数百毫秒,同步链路里能感知)。
|
||||
|
||||
- 渠道返回 429 → 打 **60 秒冷却**,冷却期内直接跳过
|
||||
- 冷却是**内存态**,不落盘,重启即清
|
||||
- 卡片上显示「限流中 · 47s」小字,到期自动恢复,无需用户干预
|
||||
|
||||
### 3.3 超时值下调(独立改动)
|
||||
|
||||
保留"超时不切换"的规则,但把 `DEFAULT_REQUEST_TIMEOUT_SECS` 从 **30s 压到 8s**。润色是用户盯着屏幕等的同步链路,8 秒未返回的渠道等下去没有意义。
|
||||
|
||||
## 4. 数据模型
|
||||
|
||||
```rust
|
||||
struct Channel {
|
||||
id: String, // 迁移沿用 preset id;同厂商新卡按 -2 / -3 分配独立 id
|
||||
name: String, // 用户取的名字,如「硅基流动-主号」
|
||||
provider_type: String, // deepseek / volcengine / sherpa-onnx-local / codex_oauth ...
|
||||
// 决定协议路由 + 表单形状,必须独立于 id
|
||||
enabled: bool,
|
||||
order: u32, // 拖拽排序;关掉时自动置到末尾
|
||||
last_error: Option<ChannelError>, // { kind, message, at } —— 卡片标红用
|
||||
last_test: Option<ChannelTest>, // { ok, latency_ms, at } —— 连通测试结果
|
||||
// 凭据字段沿用现有 CredsAsrEntry / CredsLlmEntry,按 provider_type 决定渲染哪些
|
||||
}
|
||||
|
||||
// 仅内存,不落盘
|
||||
struct ChannelRuntime {
|
||||
cooldown_until: Option<Instant>, // 429 临时冷却
|
||||
}
|
||||
```
|
||||
|
||||
**`provider_type` 必须独立于 `id`**:否则 `coordinator.rs:341` 那条"按 provider id + 模型名路由到具体协议实现"的链会断——这是漏了就整个 ASR 挂掉的点。
|
||||
|
||||
`active.llm` / `active.asr` 不再是用户直接选择的第二份真相,而是由排序与开关同步计算的
|
||||
**兼容缓存**;旧主链路仍读取它们,"当前使用"始终等于启用列表的第一个。
|
||||
|
||||
## 5. 迁移
|
||||
|
||||
1. 遍历现有 `providers.llm` / `providers.asr` 的每个非空 entry,各补齐渠道元信息
|
||||
- `id` 沿用原 map key,`provider_type` = 原 map key,`name` = `displayName` 或 preset 显示名
|
||||
- 新建同厂商的第二、第三张卡片使用 `<preset>-2`、`<preset>-3`,不改动迁移前的凭据 key
|
||||
2. 原 `active.llm` / `active.asr` 指向的那张排到 **order = 0**,其余按 ASR_PRESETS / LLM_PRESETS 原顺序跟随
|
||||
3. 全部默认 `enabled = true`
|
||||
4. **全新安装**(无任何 entry):按平台预置
|
||||
- Windows → 一张 Foundry 本地 ASR 卡片(保住开箱即用)
|
||||
- mac / Linux → 不预置,走引导
|
||||
5. 迁移必须幂等,且失败时保留原 JSON 不动(参考现有 `load_credentials_for_update` 的写法)
|
||||
|
||||
## 6. 重试策略
|
||||
|
||||
> **状态:未实现,属于 P2。** P0 里一次失败就是一次失败,不会换渠道。
|
||||
> 下表是已定但**尚未落地**的目标行为。
|
||||
|
||||
### 6.0 代码里已经存在的两样东西(别和渠道故障转移混为一谈)
|
||||
|
||||
排查时容易在代码里搜到 `retry` 就以为做了,这两处都是**既有代码**,与渠道无关:
|
||||
|
||||
1. **`net.rs::send_with_retry` —— 连接层重连,不是渠道切换。**
|
||||
只对 `err.is_connect()`(TCP 握手被拒 / 连接重置,请求**尚未送达**服务端)重试,
|
||||
150/300/600/900ms 退避。**拿到任何 HTTP 响应就直接返回**(含 429/401/5xx),
|
||||
超时明确不重试。它重连的始终是同一个 endpoint,永远不会换到另一张卡片。
|
||||
|
||||
2. **润色失败已经会回落 ASR 原文。**
|
||||
`coordinator/polish_flow.rs::polish_or_passthrough` 的失败分支:
|
||||
```rust
|
||||
Err(e) => {
|
||||
log::error!("[coord] polish failed, falling back to raw: {reason}");
|
||||
(raw.text.clone(), Some(reason))
|
||||
}
|
||||
```
|
||||
也就是说,「全部渠道试完仍失败 → 插入 ASR 原文」这条决策**天然满足**,
|
||||
P2 要做的只是在回落之前多试几张卡片,而不是新建一条兜底路径。
|
||||
|
||||
### 6.1 目标行为(P2)
|
||||
|
||||
照搬 New API `shouldRetry()` 的分类,按桌面场景裁剪:
|
||||
|
||||
| 情况 | 行为 |
|
||||
| --- | --- |
|
||||
| 429 | **切下一个** + 当前渠道 60s 冷却 |
|
||||
| 401 / 403 | **切下一个** + 卡片标红(不自动禁用) |
|
||||
| 5xx / 连接失败 | **切下一个** + 卡片标红 |
|
||||
| 超时 | **不切**,直接失败(本期决策) |
|
||||
| 400 参数错误 | **不切**(换渠道多半是同样的错) |
|
||||
| 2xx | 成功 |
|
||||
| 全部启用渠道试完仍失败 | 插入 ASR 原文 + 提示 |
|
||||
|
||||
**不抄** New API 的:`Weight` 加权负载均衡(单用户无负载可均衡,随机选渠道反而让"在用哪个"不可预测)、`Group` / `UsedQuota` / `Balance`(多租户计费概念)、`AutoBan`(桌面软件静默关用户配置会让人一脸懵)。
|
||||
|
||||
**缓一缓**:`ModelMapping` / `ParamOverride`,有用但非第一版必需。
|
||||
|
||||
## 7. UI
|
||||
|
||||
```
|
||||
┌─ LLM 润色 ──────────────────────────────┐
|
||||
│ ⠿ ● 硅基流动-主号 deepseek-v4 28ms ⋮ │ ← 生效中
|
||||
│ ⠿ ○ Ark-备用 deepseek-v3-2 — ⋮ │ ← 备用
|
||||
│ ⠿ ○ 阶跃星辰 (限流中 · 47s) ⋮ │ ← 429 冷却
|
||||
│ ⠿ ⊘ OpenAI (上次失败 · 401) ⋮ │ ← 已关闭,沉底
|
||||
│ + 添加渠道 │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
添加/编辑弹窗:名字 → 选供应商(自动填 baseUrl / 模型占位)→ 按 `provider_type` 渲染凭据字段
|
||||
→ 「测试连通」;字段自动保存,关闭只负责退出弹窗。
|
||||
|
||||
可复用的现成件:
|
||||
- `validateProviderCredentials` / `listProviderModels`(`ProvidersSection.tsx:854` 起)
|
||||
- 按 provider 分支渲染凭据字段的逻辑(火山双鉴权模式、讯飞双字段、百炼词表等)
|
||||
- 本地引擎卡片不显示凭据字段;模型下载与切换继续集中在「高级 → 本地模型」,避免两处管理同一份模型状态
|
||||
|
||||
**新手引导**:列表为空时直接摊开添加表单,跳过空态与加号,省一次点击。
|
||||
|
||||
**平台过滤**:macOS 只显示 Qwen3 Local / Apple Speech;Windows 只显示 Foundry /
|
||||
Sherpa;Linux 与 Android 不显示这些桌面专有本地引擎。云端供应商全平台可选。
|
||||
|
||||
**草稿回收**:保持自动保存。只有打开后从未发生任何用户交互的草稿会在关闭时回收;
|
||||
改过名字、供应商、凭据、模型,或执行过验证/模型拉取后都必须保留,即使内容后来清空
|
||||
或异步保存失败。这样无凭据的本地引擎 / Apple Speech / Codex OAuth 也能正常创建,且
|
||||
关闭弹窗不会与 blur/debounce 保存竞争删除卡片。
|
||||
|
||||
## 8. 分期
|
||||
|
||||
| 期 | 内容 | 可否独立发布 |
|
||||
| --- | --- | --- |
|
||||
| **P0** | 渠道数据模型 + 迁移 + 卡片 UI + 拖拽排序 + 测试连通。**不做重试** | ✅ 独立故事:「我有两把 key,想随手切」 |
|
||||
| | ↑ 已完成(PR #918)。**此时多渠道的价值是"存档 + 手动切换",不是自动容错**:排在第二的卡片永远不会被自动用上,要用得手动拖到第一位。 | |
|
||||
| **P1** | 凭据显式化重构:`CredentialsVault::get(...)` → 上层解析 `ResolvedChannel` 显式下传 | ❌ 纯重构,无用户可见变化,P2 前提 |
|
||||
| **P2** | 重试 + 故障转移 + 429 冷却 + 超时下调 + 全挂兜底 | ✅ |
|
||||
|
||||
P1 是本需求最大的单块工作量,比卡片 UI 大得多。P1 + P2 合成第二个 PR。
|
||||
|
||||
## 9. 待确认
|
||||
|
||||
- [x] 拖拽排序:手写 pointer 事件(已定)。Tauri webview 默认 `dragDropEnabled` 会吞掉
|
||||
HTML5 的 `dragstart`/`drop`,`draggable` 在打包后的 app 里不触发;pointer 方案
|
||||
Windows / Android 行为一致,并配合「拖拽结束吞掉补发 click」避免关掉设置弹窗。
|
||||
- [x] 卡片列表的移动端(Android)形态:与桌面共用同一渠道 UI;Android 走
|
||||
`load_credentials` 的同一条迁移路径,无需单独实现。
|
||||
- [x] Android 加密信封:未改版本号。v2 载荷新增的 `providerType`/`order`/`enabled`/
|
||||
`lastTest` 均为 `Option` 或带默认值字段,老版本 serde 忽略未知字段,可降级读取。
|
||||
- [x] P0 验收判据:已满足(同供应商多卡、重启后顺序与内容不丢、拖拽后第一张生效),
|
||||
由 `persistence::credentials` 迁移/排序测试与作者 macOS 实机验证覆盖。
|
||||
@@ -1,421 +0,0 @@
|
||||
# Windows sherpa-onnx 本地 ASR 实施规划
|
||||
|
||||
> 状态:草案 / 待评审
|
||||
> 日期:2026-05-12
|
||||
> 范围:仅 Windows;不替换 macOS `local-qwen3`;不替换 Windows `foundry-local-whisper`
|
||||
|
||||
按 OpenLess 现有架构(Coordinator 单一拥有者、ASR provider 独立模块、`AudioConsumer`
|
||||
接口)来做,**不重写主链路、不动 macOS、不替换 Foundry**,新增一个 Windows 实验
|
||||
provider。
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- **Windows 新增本地 ASR provider**:`sherpa-onnx-local`
|
||||
- **复用现有听写主链路**:Recorder / Coordinator / polish / insert / history
|
||||
- **支持中文为主,中英混合可用**
|
||||
- **第一阶段 batch,第二阶段流式**
|
||||
- **可与 `foundry-local-whisper` 并存切换**
|
||||
|
||||
### 非目标
|
||||
|
||||
- 不替换 macOS `local-qwen3`
|
||||
- 不替换 Windows `foundry-local-whisper`,仅作为新选项
|
||||
- 不做 Linux 支持(本期)
|
||||
- 不做语者分离、长会议转写、字幕导出
|
||||
- 不做云端模型,不做模型自训
|
||||
|
||||
### 明确边界
|
||||
|
||||
- **不动 Coordinator 的 phase enum / hotkey 流程**
|
||||
- **不动 polish / insertion / history**
|
||||
- **sherpa runtime 只通过 `AudioConsumer` + 转写函数对外暴露**
|
||||
- **任何 sherpa 错误必须降级**:不能让用户的话丢失(与现有 ASR 失败语义一致)
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构定位
|
||||
|
||||
按现有结构对齐 Foundry 路径:
|
||||
|
||||
```
|
||||
asr/local/
|
||||
mod.rs # 增加 sherpa provider id 与 helper
|
||||
foundry_provider.rs # 保留
|
||||
foundry_runtime.rs # 保留
|
||||
sherpa_provider.rs # 新增:AudioConsumer + transcribe()
|
||||
sherpa_runtime.rs # 新增:模型加载 / 推理调用 / 生命周期
|
||||
sherpa_models.rs # 新增:模型 catalog 静态表
|
||||
```
|
||||
|
||||
主要扩展点:
|
||||
|
||||
- `ActiveAsr::SherpaOnnxLocal(Arc<SherpaOnnxAsr>)`
|
||||
- `coordinator/dictation.rs`:`begin_session` / `end_session` 增加
|
||||
`#[cfg(target_os = "windows")]` 分支
|
||||
- `commands.rs`:增加准备 / 释放 / 状态 / 模型管理命令
|
||||
- `types.rs`:增加 `UserPreferences` 字段
|
||||
- 前端 Settings 高级页:在 Windows 下新增第三个本地 ASR toggle
|
||||
|
||||
---
|
||||
|
||||
## 3. 模型策略
|
||||
|
||||
### 第一批模型(重点是中文)
|
||||
|
||||
| 模型 | 用途 | 备注 |
|
||||
|---|---|---|
|
||||
| **SenseVoice small (zh/en/ja/ko/yue, int8)** | 中文 + 多语言默认 | 体验通常优于 Whisper small;包小、速度快 |
|
||||
| **Paraformer (zh, int8)** | 中文专用强力档 | 中文听写更稳;不擅长英文 |
|
||||
| **Whisper small (multilingual, int8)** | 英文/通用 fallback | 与 Foundry Whisper 体验对齐基准 |
|
||||
|
||||
模型形态全部用:
|
||||
|
||||
- **ONNX**
|
||||
- **量化 int8**
|
||||
- **CPU 推理优先**
|
||||
|
||||
后续可选:
|
||||
|
||||
- **streaming Zipformer (zh)**:第二阶段流式使用
|
||||
|
||||
### 模型分发策略
|
||||
|
||||
- **不打进安装包**
|
||||
- **首次启用时下载**
|
||||
- **下载源带镜像**:HuggingFace / 镜像 / 自托管 CDN
|
||||
- **校验 SHA-256**
|
||||
- **存放路径**:
|
||||
```
|
||||
%APPDATA%\OpenLess\models\sherpa-onnx\<alias>\
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 模块设计
|
||||
|
||||
### 4.1 `sherpa_models.rs`
|
||||
|
||||
静态目录 + alias 解析,模仿 `foundry.rs::MODELS`:
|
||||
|
||||
```rust
|
||||
pub const PROVIDER_ID: &str = "sherpa-onnx-local";
|
||||
pub const DEFAULT_MODEL_ALIAS: &str = "sense-voice-small-zh";
|
||||
|
||||
pub struct SherpaModel {
|
||||
pub alias: &'static str,
|
||||
pub display_name: &'static str,
|
||||
pub family: SherpaFamily, // SenseVoice / Paraformer / Whisper / Zipformer
|
||||
pub languages: &'static [&'static str],
|
||||
pub mode: SherpaMode, // Offline / Online
|
||||
pub files: &'static [SherpaModelFile], // name + sha256 + size + url
|
||||
}
|
||||
```
|
||||
|
||||
边界:
|
||||
|
||||
- **不在这里写下载逻辑**
|
||||
- **不依赖 sherpa-onnx 类型**,纯描述
|
||||
|
||||
### 4.2 `sherpa_runtime.rs`
|
||||
|
||||
只这一处依赖 `sherpa-onnx` crate。
|
||||
|
||||
职责:
|
||||
|
||||
- **初始化 OfflineRecognizer / OnlineRecognizer**
|
||||
- **缓存当前已加载的 recognizer**
|
||||
- **暴露**:
|
||||
- `ensure_loaded(alias) -> Result<LoadedHandle>`
|
||||
- `transcribe_pcm(pcm: &[i16]) -> Result<String>`(offline)
|
||||
- `create_stream() -> SherpaStream`(online,第二阶段)
|
||||
- `release_now()`
|
||||
- `status_snapshot()`
|
||||
- **生命周期**:
|
||||
- `lifecycle: AsyncMutex<()>`(与 Foundry 一致,串行化加载/释放)
|
||||
- 闲时延迟释放(参考 `local_asr_keep_loaded_secs` 模式)
|
||||
|
||||
边界:
|
||||
|
||||
- **不知道 Coordinator**
|
||||
- **不知道 Recorder**
|
||||
- **不动 UI**
|
||||
- **不发 Tauri 事件**
|
||||
|
||||
错误统统返回 `anyhow::Error`,由上层翻译为前端文案。
|
||||
|
||||
### 4.3 `sherpa_provider.rs`
|
||||
|
||||
形状与 `foundry_provider.rs` 完全对齐:
|
||||
|
||||
```rust
|
||||
pub struct SherpaOnnxAsr {
|
||||
runtime: Arc<SherpaRuntime>,
|
||||
model_alias: String,
|
||||
language_hint: Option<String>,
|
||||
buffer: Mutex<Vec<u8>>, // PCM s16le 16kHz mono
|
||||
cancel_generation: AtomicU64,
|
||||
}
|
||||
|
||||
impl AudioConsumer for SherpaOnnxAsr {
|
||||
fn consume_pcm_chunk(&self, pcm: &[u8]) { ... }
|
||||
}
|
||||
|
||||
impl SherpaOnnxAsr {
|
||||
pub async fn transcribe(&self, timeout: Duration) -> Result<RawTranscript> { ... }
|
||||
pub fn cancel(&self) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
边界:
|
||||
|
||||
- **batch 阶段不做实时 token 回调**
|
||||
- **流式阶段独立加 `transcribe_stream(on_token)`,不破坏 batch API**
|
||||
|
||||
### 4.4 `coordinator/dictation.rs` 集成
|
||||
|
||||
新增分支,**完全 mirror 现有 foundry 分支**:
|
||||
|
||||
`begin_session`:
|
||||
|
||||
```rust
|
||||
#[cfg(target_os = "windows")]
|
||||
if sherpa::is_sherpa_onnx_local(&active_asr) {
|
||||
let local = Arc::new(SherpaOnnxAsr::new(...));
|
||||
store_asr_for_session(inner, sid, ActiveAsr::SherpaOnnxLocal(Arc::clone(&local)));
|
||||
let consumer: Arc<dyn AudioConsumer> = local;
|
||||
start_recorder_and_enter_listening(inner, sid, &active_asr, consumer).await?;
|
||||
return Ok(());
|
||||
}
|
||||
```
|
||||
|
||||
`end_session`:
|
||||
|
||||
```rust
|
||||
#[cfg(target_os = "windows")]
|
||||
ActiveAsr::SherpaOnnxLocal(local) => {
|
||||
match local.transcribe(sherpa_transcribe_timeout()).await {
|
||||
Ok(r) => { schedule_sherpa_release(...); r }
|
||||
Err(e) => { /* 与 foundry 失败分支同形 */ }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
边界:
|
||||
|
||||
- **不修改 Foundry 分支**
|
||||
- **不修改 macOS Qwen3 分支**
|
||||
- **复用 `RawTranscript` / `polish` / `insertion`**
|
||||
|
||||
### 4.5 `commands.rs`
|
||||
|
||||
新增命令(与 Foundry 同形,方便前端代码复用模式):
|
||||
|
||||
- `sherpa_asr_status`
|
||||
- `sherpa_asr_prepare`
|
||||
- `sherpa_asr_release`
|
||||
- `sherpa_asr_catalog`
|
||||
- `sherpa_asr_set_model`
|
||||
|
||||
只在 `#[cfg(target_os = "windows")]` 下注册。
|
||||
|
||||
### 4.6 `types.rs`
|
||||
|
||||
新增字段(默认值 Windows = SenseVoice 中文,其他平台不可用):
|
||||
|
||||
```rust
|
||||
#[serde(default = "default_sherpa_model_alias")]
|
||||
pub sherpa_onnx_model: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub sherpa_onnx_language_hint: String,
|
||||
|
||||
#[serde(default = "default_local_asr_keep_loaded_secs")]
|
||||
pub sherpa_onnx_keep_loaded_secs: u32,
|
||||
```
|
||||
|
||||
**不改 `default_active_asr_provider()`**:Windows 默认仍是
|
||||
`foundry-local-whisper`,sherpa 通过高级开关启用。
|
||||
|
||||
### 4.7 前端
|
||||
|
||||
在 Windows 高级页加第三个 toggle 行:
|
||||
|
||||
- Foundry Local Whisper
|
||||
- **Sherpa-Onnx Local(新增,实验)**
|
||||
- 模型选择 / 准备 / 删除 / 路径
|
||||
|
||||
复用现有 `LocalAsr` UI 模式。i18n key 用 zh-CN 源 + en 镜像(按 AGENTS.md 规则)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 依赖与打包
|
||||
|
||||
### 5.1 Rust crate
|
||||
|
||||
```toml
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
sherpa-onnx = "..." # 选最新稳定版,feature 关闭非必要后端
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- **关掉 CUDA / DirectML 等 feature**(v1 只用 CPU)
|
||||
- **避免依赖 dynamic ONNX Runtime**:优先静态或随包附带 DLL
|
||||
- **不要引入新的 native build chain**:保证 GH Actions Windows runner 能编
|
||||
|
||||
### 5.2 DLL / native 资源
|
||||
|
||||
如果 sherpa-onnx crate 自带 `onnxruntime.dll` / `sherpa-onnx.dll`:
|
||||
|
||||
- 通过 `build.rs` copy 到 target dir
|
||||
- 由 Tauri bundler 一同打进 NSIS / MSI
|
||||
- WiX 的 `Component` 落到 `INSTALLDIR`
|
||||
- **严格遵守 AGENTS.md 的 Windows CI 红线**:
|
||||
- 两轮 NSIS / MSI
|
||||
- bash shell
|
||||
- `-sice:ICE80`
|
||||
- 不动 Repair 步骤
|
||||
|
||||
如果 crate 不带 DLL:
|
||||
|
||||
- 第一次启用时从镜像下载,与模型同目录
|
||||
- 用 LoadLibrary delay-load
|
||||
|
||||
### 5.3 模型下载
|
||||
|
||||
复用现有 `LocalAsr` 模型管理 UX:
|
||||
|
||||
- 镜像选择
|
||||
- 进度 + 取消
|
||||
- SHA-256 校验
|
||||
- 失败重试
|
||||
|
||||
---
|
||||
|
||||
## 6. 实施里程碑
|
||||
|
||||
### M1 Provider 骨架 (0.5 周)
|
||||
|
||||
- `sherpa_provider.rs` / `sherpa_runtime.rs` / `sherpa_models.rs` 文件结构
|
||||
- `ActiveAsr::SherpaOnnxLocal`
|
||||
- `commands.rs` 桩函数
|
||||
- 前端 toggle + i18n
|
||||
- **不实际推理**,先打通主链路(mock transcribe 返回空串或固定字符串)
|
||||
|
||||
### M2 Batch 推理可用 (1.5 周)
|
||||
|
||||
- 接 `sherpa-onnx` crate
|
||||
- offline recognizer 加载
|
||||
- WAV/PCM → text
|
||||
- 模型:先只接 **SenseVoice small zh**
|
||||
- 错误降级:失败回到 Foundry / Volcengine
|
||||
- Windows 本机 smoke test
|
||||
|
||||
### M3 模型管理 + 多模型 (1 周)
|
||||
|
||||
- 加 Paraformer / Whisper small
|
||||
- 模型下载 / 校验 / 删除
|
||||
- 镜像源切换
|
||||
- 模型切换不需要重启
|
||||
|
||||
### M4 性能与稳定性 (1 周)
|
||||
|
||||
- 启动时延、首次加载时延
|
||||
- 内存占用
|
||||
- 长录音稳定性
|
||||
- 取消(hotkey 再次按下)行为正确
|
||||
- DLL 缺失 / 模型损坏 / 路径含中文 / 路径含空格 全部覆盖
|
||||
|
||||
### M5 流式 ASR(可选,二阶段)
|
||||
|
||||
- 接 OnlineRecognizer
|
||||
- 边录边 partial → `local-asr-token` 事件
|
||||
- 与现有 macOS Qwen3 stream UX 对齐
|
||||
|
||||
### M6 发布
|
||||
|
||||
- 高级页打开为实验
|
||||
- 收集真实用户反馈
|
||||
- 满足质量门槛后再决定是否提升为 Windows 默认
|
||||
|
||||
---
|
||||
|
||||
## 7. 风险与对策
|
||||
|
||||
| 风险 | 对策 |
|
||||
|---|---|
|
||||
| sherpa-onnx Windows 打包带 native DLL,触发 WiX / NSIS 兼容问题 | 严格走 AGENTS.md 的两轮 bundle + `-sice:ICE80`;早期就在 CI 跑 |
|
||||
| ONNX Runtime 版本冲突 | 锁版本;不和其他 crate 共享 ORT |
|
||||
| 模型体积大,下载失败 | 强制镜像 + 断点续传 + SHA-256 + 明确错误文案 |
|
||||
| 安装路径含中文/空格导致模型加载失败 | 用 `\\?\` 长路径前缀 + 单元测试覆盖 |
|
||||
| 首次加载耗时长(用户以为卡死) | 加载阶段发 Tauri 进度事件;胶囊显示"准备模型"态 |
|
||||
| CPU 性能不足机器卡顿 | 默认 SenseVoice small int8;提供更小模型;超时降级 |
|
||||
| 推理 panic 干扰主进程 | 推理放 `spawn_blocking`,错误 → anyhow,绝不 panic 向上 |
|
||||
| 与 Foundry / Qwen3 并存导致状态混乱 | 切换 provider 时强制 release 另一边;测试覆盖 |
|
||||
| 取消语义不一致 | 严格按现有 `cancel_generation` 模式实现 |
|
||||
| macOS / Linux 编译被影响 | 全部 sherpa 代码 `#[cfg(target_os = "windows")]` 包裹 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 验收标准
|
||||
|
||||
### 功能
|
||||
|
||||
- Windows 用户能在高级页启用 `sherpa-onnx-local`
|
||||
- 默认模型 SenseVoice small zh 可下载、加载、转写
|
||||
- 中文短句听写质量明显优于 Foundry Whisper small(盲测)
|
||||
- 失败时不丢用户的话(自动降级或留 raw)
|
||||
- 取消、重复触发、连按热键不崩
|
||||
|
||||
### 工程
|
||||
|
||||
- 不动 macOS 编译产物
|
||||
- 不动 Foundry 路径
|
||||
- 不引入新的 CI 红线
|
||||
- Windows MSI / NSIS 两轮构建仍然通过
|
||||
- 包体增量在可接受范围(建议 < 50MB,不含模型)
|
||||
|
||||
### 测试
|
||||
|
||||
- `cargo test` Windows 通过
|
||||
- 手测脚本:
|
||||
- 中文短句
|
||||
- 中文长句(30s+)
|
||||
- 中英混合
|
||||
- 安静 / 噪音
|
||||
- 取消
|
||||
- 切换模型
|
||||
- 切换 provider
|
||||
- 卸载模型
|
||||
- 无网络再次启动
|
||||
|
||||
---
|
||||
|
||||
## 9. 不做什么(再次明确)
|
||||
|
||||
- **不重构 ASR trait 体系**
|
||||
- **不引入 ASR 中间层抽象**
|
||||
- **不替换 Foundry**
|
||||
- **不动 macOS Qwen3**
|
||||
- **不做 Linux**
|
||||
- **不做云端 fallback 改动**
|
||||
- **不做模型微调**
|
||||
- **不做多 provider 自动选择**
|
||||
|
||||
---
|
||||
|
||||
## 10. 相关参考
|
||||
|
||||
- 现有 Windows 本地 ASR 实现:
|
||||
- `openless-all/app/src-tauri/src/asr/local/foundry.rs`
|
||||
- `openless-all/app/src-tauri/src/asr/local/foundry_provider.rs`
|
||||
- `openless-all/app/src-tauri/src/asr/local/foundry_runtime.rs`
|
||||
- 现有 macOS 本地 ASR 实现:
|
||||
- `openless-all/app/src-tauri/src/asr/local/local_provider.rs`
|
||||
- 主听写链路集成点:
|
||||
- `openless-all/app/src-tauri/src/coordinator/dictation.rs`
|
||||
- Windows CI / 打包红线:见仓库根 `AGENTS.md`「Windows CI 红线」一节
|
||||
@@ -1,186 +0,0 @@
|
||||
# 光标上下文 + 手改学习 —— 装机测试案例
|
||||
|
||||
装的版本:`local/daily`。开关在 **设置 → 隐私 → 数据存储 → 光标上下文(实验)**,默认关。
|
||||
|
||||
盯日志:
|
||||
|
||||
```bash
|
||||
tail -f ~/Library/Logs/OpenLess/openless.log | grep -E "cursor-context|cursor context|vocab"
|
||||
```
|
||||
|
||||
重装后辅助功能授权会短暂失效(ad-hoc 签名每次构建 cdhash 都变),但 app 自己会恢复——实测重试到第 23 次时自己起来,约 70 秒。看到 `hotkey listener installed` 就能用了;万一一直刷 `CGEventTapCreate 失败`,去 系统设置 → 隐私与安全性 → 辅助功能 → OpenLess 关掉再打开。
|
||||
|
||||
---
|
||||
|
||||
## 这个功能干什么
|
||||
|
||||
开关打开后,每次听写会读**你当时正在写的那个文档里、光标附近的几百个字**,跟着请求一起发给 LLM。这样它知道你在写什么,「接口」不会写成「借口」。
|
||||
|
||||
落字之后它还会盯一小会儿:如果你手动改了它插进去的某个词,那个词可能进你的词汇表。
|
||||
|
||||
**只写词汇表,不写纠正规则。** 词条是提示(送给 ASR、进润色 prompt 让 LLM 带上下文判断),错了最多是没帮上忙;纠正规则是字面替换,错了是静默的、全局的。学来的东西配不上后者那份权力。
|
||||
|
||||
---
|
||||
|
||||
## 1. AX 覆盖率 —— 不用开口说话
|
||||
|
||||
**设置 → 高级 → 调试工具 → 光标上下文探针**
|
||||
|
||||
点「探测(5 秒后)」→ 切到目标 app → 在正文里点一下让光标进去 → 切回来看结果。
|
||||
|
||||
```
|
||||
ok · 11ms
|
||||
备忘录 (com.apple.Notes)
|
||||
我们这个模块的接口设计得不太好,⟦光标⟧
|
||||
```
|
||||
|
||||
`⟦光标⟧` 是光标位置,左边上文右边下文。
|
||||
|
||||
| app | 预期 |
|
||||
|---|---|
|
||||
| 备忘录 / 文本编辑 | `ok` |
|
||||
| VS Code / Notion / Claude 桌面版(Electron) | 能读到,但光标位置常常不准 |
|
||||
| 微信 / 飞书 | `ok` |
|
||||
| 浏览器普通输入框 | `ok` |
|
||||
| 浏览器**密码框** | **必须 `blocked` / `secure_text_field`** |
|
||||
| 终端 / iTerm / Warp | **必须 `blocked` / `blocked_app`** |
|
||||
| 1Password | **必须 `blocked` / `blocked_app`** |
|
||||
|
||||
后三行是安全验收,任何一条没拦住立刻停下来说。
|
||||
|
||||
**屏幕上显示出来的原文,就是会发给 LLM 的内容。** 哪个 app 里蹦出了你不希望离开这台机器的东西,那是必须知道的发现。
|
||||
|
||||
---
|
||||
|
||||
## 2. 开关关闭时行为不变
|
||||
|
||||
关掉开关 → 听写几句 → 日志里**不该有任何 `cursor-context` 行**。
|
||||
|
||||
关着的时候一次 AX 都不发,prompt 也和这个功能不存在时逐字节相同(有单测钉死)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 上下文真的进 prompt 了吗
|
||||
|
||||
同一个 app、同一个位置,开/关各听一次同样的话:
|
||||
|
||||
```bash
|
||||
grep "effective_prompt_chars" ~/Library/Logs/OpenLess/openless.log | tail -2
|
||||
```
|
||||
|
||||
开着的那次应该多 **427 + 读到的字数**(427 = 上下文块固定措辞 314 + 注入防御条款 113)。
|
||||
|
||||
必须同一个 app 比——prompt 里带了前台应用名,「备忘录 (com.apple.Notes)」和「Claude (com.anthropic.claudefordesktop)」差 19 个字符,换 app 比会对不上账。
|
||||
|
||||
**更该盯的是 LLM 拿它干了什么。** 最容易翻车的是把上文复述进输出:光标前写着「这个模块的接口设计得不太好,」,你口述「还得再改」,它输出「这个模块的接口设计得不太好,还得再改」——把你已有的字又插了一遍。prompt 里明令禁止了,但那是软约束。
|
||||
|
||||
---
|
||||
|
||||
## 4. 手改 → 卡片 → 词条
|
||||
|
||||
前提:开关开着,**在备忘录这类原生 app 里测**(Electron 的通知不稳)。
|
||||
|
||||
**没有自动入库这条路了。** 任何一处手改都只会变成卡片上的一条建议,你点勾它才进词汇表。
|
||||
|
||||
早期版本让「中文改成英文」这一档静默入库,理由是「没人为了换语气把中文改成英文」。
|
||||
真机跑两天,自动收进去 5 条只有 1 条是对的:
|
||||
|
||||
| 自动收的 | 实际是什么 |
|
||||
|---|---|
|
||||
| `Tailscale`(was telskill) | ✅ 唯一一条真纠错 |
|
||||
| `ype`(was ap) | 逐字敲 `Type` 的中间态 |
|
||||
| `ess`(was ice) | 同上 |
|
||||
| `typeless`(was TypeScript) | 用户本来就要打这个词 |
|
||||
| ` claude`(was cloud) | 带前导空格,永远匹配不上 |
|
||||
|
||||
观察器看到的是**编辑过程中的每一帧**,而中间态和一次真纠错在文本上没有区别。分不出来就别猜。
|
||||
|
||||
### 4a. 卡片长什么样
|
||||
|
||||
1. 口述一句,把其中某个词改掉
|
||||
2. **把光标点到别处**(这是判定「你改完了」的信号)
|
||||
3. 屏幕**右下角**弹出卡片:
|
||||
|
||||
```
|
||||
要记住这个词吗?
|
||||
扣德克斯 → Codex ✓ ✗
|
||||
大禹 → 大鱼 ✓ ✗
|
||||
```
|
||||
|
||||
- 点 **✓** → 进词汇表,落在分割线下面的「自动收集」区
|
||||
- 点 **✗** → 丢掉,什么都不记(没有拒绝名单,下次再改同一个词它还会问)
|
||||
- 等 10 秒不动 → 整张卡片消失,同样什么都不记
|
||||
- 连着改两个词 → 合并到同一张卡片,倒计时重置
|
||||
- 逐条点完最后一条 → 卡片自己收起
|
||||
|
||||
**位置必须是右下角,不是屏幕正下方居中。** 居中那块正是你在写字的地方,卡片停十秒会
|
||||
直接盖住正在编辑的那一行 —— 这是真机上退回来的。
|
||||
|
||||
卡片只挡住它自己那一块的鼠标(窗口会缩到卡片大小,逐条点完还会跟着重算),周围照常能点。
|
||||
|
||||
### 4c. 该拒绝的时候确实拒绝了
|
||||
|
||||
| 操作 | 期待 |
|
||||
|---|---|
|
||||
| 改完切到别的 app 再改 | 无输出(观察器已解除) |
|
||||
| 落字后等 60 秒再改 | 无输出(硬上限) |
|
||||
| 落字后再听写一次,回头改第一段 | 无输出(新会话解除旧观察器) |
|
||||
| 改你自己之前写的内容(不是它插的) | 无输出(只认落在插入文本里的改动) |
|
||||
| 只是补几个字(纯插入) | 不学 |
|
||||
| 把一个词删掉(纯删除) | 检测到但不入库 |
|
||||
| 在聊天框里按回车发送 | 不该产生任何建议 ← 这条曾经翻过车 |
|
||||
| 慢慢逐字打出一个英文词 | **可能**会弹出 `ap → ype` 这种半截建议 —— 点 ✗ 就行 |
|
||||
|
||||
最后一条不是 bug,是这个设计的已知代价:半截和真纠错在文本上没有区别,粗筛拦不住。
|
||||
以前它会**静默入库**,现在最多是让你多点一次叉。
|
||||
|
||||
---
|
||||
|
||||
## 5. 词汇表页
|
||||
|
||||
```
|
||||
词汇表
|
||||
[你自己加的...]
|
||||
───────── 自动收集(N) [全部删除]
|
||||
[自动收的...]
|
||||
```
|
||||
|
||||
试一下「全部删除」,确认**手动加的不会被一起删掉**。
|
||||
|
||||
学来的词条在 `dictionary.json` 里是**追加到最后**的,手动添加的才插到最前。这条顺序是
|
||||
跟 ASR 词表预算的接口约定:预算把最前面的几条当保底席位(理由是「你刚手动加它,多半刚
|
||||
被它坑过」),点一下勾不该享受这个待遇。学来的词在 **LLM 热词块里立刻生效**(那一侧没有
|
||||
名额限制),ASR 侧则靠命中次数自己爬进预算。
|
||||
|
||||
早期版本往**纠正规则**里写过 learned 条目(现在不写了)。如果你的 `correction-rules.json` 里还有,纠正规则区有个「只看自动收集的」筛选可以把它们挑出来删。
|
||||
|
||||
---
|
||||
|
||||
## 6. 不会冻住界面
|
||||
|
||||
对着一个卡死的 app 触发听写。AX 调用 200ms 超时、整次读取 1.2 秒封顶、跑在独立线程,不占 tokio worker。
|
||||
|
||||
---
|
||||
|
||||
## 已知限制
|
||||
|
||||
1. **建议只在内存里**,OpenLess 重启就没了。卡片消失即当没发生——下次改同一个词会再问。
|
||||
2. **Electron 类 app 的光标位置常常不准**,日志里表现为 `before=0 after=N`(上文读成空)。上下文对润色的价值主要在上文,那种情况下收益有限。
|
||||
3. **风格包预览里看不到 `<cursor_context>`**,跟 `front_app` 一样是运行时才有值的东西。
|
||||
|
||||
---
|
||||
|
||||
## 出问题时给我这些
|
||||
|
||||
```bash
|
||||
# 相关日志(诊断细节是 debug 级别,日常不记;要更细的得改 LevelFilter 重编译)
|
||||
grep -E "cursor-context|cursor context|vocab" ~/Library/Logs/OpenLess/openless.log | tail -100
|
||||
|
||||
# 学到的词条
|
||||
python3 -c "import json,os;[print(' ',e['phrase']) for e in json.load(open(os.path.expanduser('~/Library/Application Support/OpenLess/dictionary.json'))) if e.get('note')=='从手改中自动收集']"
|
||||
|
||||
# 开关状态
|
||||
python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/Library/Application Support/OpenLess/preferences.json'))).get('cursorContextEnabled'))"
|
||||
```
|
||||
|
||||
`edit watch disarmed` 那一行带两个数字——收到几次通知、学到几处改动。这两个数字足够判断某个 app 到底发不发通知,也就是逐 app 的覆盖率数据。
|
||||
@@ -42,11 +42,7 @@ pub fn add_correction_rule(
|
||||
|
||||
/// 卡片上点了勾:把这个词收进词汇表,打「自动收集」标记,随时能在词汇表页删掉。
|
||||
#[tauri::command]
|
||||
pub fn accept_pending_correction(
|
||||
core: CoreState<'_>,
|
||||
coord: CoordinatorState<'_>,
|
||||
id: String,
|
||||
) {
|
||||
pub fn accept_pending_correction(core: CoreState<'_>, coord: CoordinatorState<'_>, id: String) {
|
||||
match core.accept_pending_correction(&id) {
|
||||
Ok(Some(suggestion)) => {
|
||||
log::info!(
|
||||
@@ -63,11 +59,7 @@ pub fn accept_pending_correction(
|
||||
|
||||
/// 卡片上点了叉:丢掉这一条,什么都不记(没有拒绝名单)。
|
||||
#[tauri::command]
|
||||
pub fn reject_pending_correction(
|
||||
core: CoreState<'_>,
|
||||
coord: CoordinatorState<'_>,
|
||||
id: String,
|
||||
) {
|
||||
pub fn reject_pending_correction(core: CoreState<'_>, coord: CoordinatorState<'_>, id: String) {
|
||||
if core.reject_pending_correction(&id) {
|
||||
coord.refresh_vocab_suggestion_presentation(!core.pending_corrections().is_empty());
|
||||
}
|
||||
|
||||
@@ -30,8 +30,7 @@ fn command_error(error: openless_core::BackendError) -> String {
|
||||
pub async fn github_device_flow_start(
|
||||
core: CoreState<'_>,
|
||||
) -> Result<GithubDeviceStartResponse, String> {
|
||||
core
|
||||
.services()
|
||||
core.services()
|
||||
.marketplace
|
||||
.start_device_flow()
|
||||
.await
|
||||
@@ -44,8 +43,7 @@ pub async fn github_device_flow_poll(
|
||||
core: CoreState<'_>,
|
||||
flow_id: String,
|
||||
) -> Result<openless_core::OAuthPollResult, String> {
|
||||
core
|
||||
.services()
|
||||
core.services()
|
||||
.marketplace
|
||||
.poll_device_flow(flow_id)
|
||||
.await
|
||||
@@ -57,8 +55,7 @@ pub async fn github_device_flow_cancel(
|
||||
core: CoreState<'_>,
|
||||
flow_id: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
core
|
||||
.services()
|
||||
core.services()
|
||||
.marketplace
|
||||
.cancel_device_flow(flow_id)
|
||||
.await
|
||||
@@ -69,8 +66,7 @@ pub async fn github_device_flow_cancel(
|
||||
pub async fn marketplace_auth_status(
|
||||
core: CoreState<'_>,
|
||||
) -> Result<openless_core::MarketplaceAuthStatus, String> {
|
||||
core
|
||||
.services()
|
||||
core.services()
|
||||
.marketplace
|
||||
.auth_status()
|
||||
.await
|
||||
@@ -79,8 +75,7 @@ pub async fn marketplace_auth_status(
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn marketplace_logout(core: CoreState<'_>) -> Result<(), String> {
|
||||
core
|
||||
.services()
|
||||
core.services()
|
||||
.marketplace
|
||||
.logout()
|
||||
.await
|
||||
|
||||
@@ -12,8 +12,7 @@ pub async fn marketplace_list(
|
||||
sort: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<Vec<openless_core::MarketplaceListItem>, String> {
|
||||
core
|
||||
.services()
|
||||
core.services()
|
||||
.marketplace
|
||||
.list(openless_core::MarketplaceQuery { query, sort, limit })
|
||||
.await
|
||||
@@ -25,8 +24,7 @@ pub async fn marketplace_detail(
|
||||
core: CoreState<'_>,
|
||||
pack_id: String,
|
||||
) -> Result<openless_core::MarketplaceDetail, String> {
|
||||
core
|
||||
.services()
|
||||
core.services()
|
||||
.marketplace
|
||||
.detail(pack_id)
|
||||
.await
|
||||
@@ -38,8 +36,7 @@ pub async fn marketplace_install(
|
||||
core: CoreState<'_>,
|
||||
pack_id: String,
|
||||
) -> Result<StylePack, String> {
|
||||
core
|
||||
.services()
|
||||
core.services()
|
||||
.marketplace
|
||||
.install(pack_id)
|
||||
.await
|
||||
@@ -70,8 +67,7 @@ pub async fn marketplace_upload(
|
||||
pack_id: String,
|
||||
origin_pack_id: Option<String>,
|
||||
) -> Result<openless_core::MarketplaceUploadResult, String> {
|
||||
core
|
||||
.services()
|
||||
core.services()
|
||||
.marketplace
|
||||
.upload(pack_id, origin_pack_id)
|
||||
.await
|
||||
@@ -83,8 +79,7 @@ pub async fn marketplace_like(
|
||||
core: CoreState<'_>,
|
||||
pack_id: String,
|
||||
) -> Result<openless_core::MarketplaceLikeResult, String> {
|
||||
core
|
||||
.services()
|
||||
core.services()
|
||||
.marketplace
|
||||
.toggle_like(pack_id)
|
||||
.await
|
||||
@@ -92,12 +87,8 @@ pub async fn marketplace_like(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn marketplace_delete(
|
||||
core: CoreState<'_>,
|
||||
pack_id: String,
|
||||
) -> Result<(), String> {
|
||||
core
|
||||
.services()
|
||||
pub async fn marketplace_delete(core: CoreState<'_>, pack_id: String) -> Result<(), String> {
|
||||
core.services()
|
||||
.marketplace
|
||||
.delete(pack_id)
|
||||
.await
|
||||
@@ -106,8 +97,7 @@ pub async fn marketplace_delete(
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn marketplace_my_likes(core: CoreState<'_>) -> Result<Vec<String>, String> {
|
||||
core
|
||||
.services()
|
||||
core.services()
|
||||
.marketplace
|
||||
.my_likes()
|
||||
.await
|
||||
@@ -118,8 +108,7 @@ pub async fn marketplace_my_likes(core: CoreState<'_>) -> Result<Vec<String>, St
|
||||
pub async fn marketplace_my_packs(
|
||||
core: CoreState<'_>,
|
||||
) -> Result<Vec<openless_core::MarketplaceMyPackItem>, String> {
|
||||
core
|
||||
.services()
|
||||
core.services()
|
||||
.marketplace
|
||||
.my_packs()
|
||||
.await
|
||||
|
||||
@@ -74,12 +74,7 @@ pub(crate) fn begin_session_state(
|
||||
focus_target: Option<usize>,
|
||||
front_app: Option<String>,
|
||||
) -> Option<SessionId> {
|
||||
begin_session_state_with_id(
|
||||
state,
|
||||
focus_target,
|
||||
front_app,
|
||||
new_session_id(),
|
||||
)
|
||||
begin_session_state_with_id(state, focus_target, front_app, new_session_id())
|
||||
}
|
||||
|
||||
/// 与 [`begin_session_state`] 相同,但允许宿主在进入 Coordinator 状态机前生成
|
||||
|
||||
@@ -55,8 +55,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn null_window_is_not_a_capture_target() {
|
||||
assert!(!hwnd_is_present(
|
||||
windows::Win32::Foundation::HWND::default()
|
||||
));
|
||||
assert!(!hwnd_is_present(windows::Win32::Foundation::HWND::default()));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user