docs: 以源码为准重写 1-app 文档系统(19 篇)
保留目录骨架与文件名,内容全部以当前源码为依据重写:范围与版本锚点 (2.0.0-Beta.1 / crate 0.1.0 / com.openless.app)、架构分层与 Core 模块地图、 桌面验收清单(功能域→源码入口→证据)、Linux 交接 8 篇(沿用 L01-L12 登记体系, 锚点落到 settings.rs/audio.rs/ports.rs 等)、后端接口契约、Android 计划、 运营指南 5 篇(以 provider descriptor 与实际 CSP/.gitmodules 为锚)。 原过程中的计划叙述、截图与历史收口记录不再保留。
This commit is contained in:
1 parent
639c2fbf9b
commit
34cab16fce
19 files changed
+381
-1857
No files matched your search
@@ -1,81 +1,31 @@
|
||||
# Windows / macOS 2.0 功能保留验收
|
||||
# 桌面验收清单(Windows / macOS / Android)
|
||||
|
||||
范围依据:[当前2.0需求](./2.0-requirements.md)。本清单定义必须验收什么,不预填“已通过”。
|
||||
基线:各平台实际交付的 Tauri 1.x 能力,以及 PR #1019 已明确承诺的 2.0 能力。
|
||||
状态:要求清单,不是通过声明;更新:2026-09-07。每个功能域给出源码入口与必须提供的真实证据;自动化测试通过不等于设备验收通过。证据分级见[范围](2.0-requirements.md)第 4 节。
|
||||
|
||||
## 1. 逐项保留的用户能力
|
||||
## 1. 功能域 → 源码入口 → 验收要求
|
||||
|
||||
每一行均检查入口、成功路径、失败/取消、重启后状态和旧数据;不能只检查接口存在。
|
||||
|
||||
| ID | 功能组 | 必须保留并验收 |
|
||||
| 域 | 源码入口(`openless-all/app/`) | 必须的真实证据 |
|
||||
| --- | --- | --- |
|
||||
| D01 | 启动与生命周期 | 首次配置、权限引导、正常/最小化启动、单实例、第二次启动动作、原有CLI入口、退出释放资源 |
|
||||
| D02 | 主听写 | Hold/Toggle/Auto、自定义组合键、停止/Esc、组合键仲裁、去抖/收尾冷却、无声停止、设备故障与重试 |
|
||||
| D03 | 音频与反馈 | 默认/指定麦克风、设备刷新/切换、音量预览互斥、录音静音及恢复、提示音、胶囊状态和终态可见性 |
|
||||
| D04 | 云ASR/LLM/Omni | 各平台1.x已支持provider的认证、模型、批量/实时、润色/翻译、代理、超时/取消和错误反馈;实验总开关有效 |
|
||||
| D05 | 渠道与凭据 | 创建/改名/删除/启停/排序/活动渠道、端点/模型/自定义头等设置、验证/列模型、无鉴权自建端点、OAuth既有流程 |
|
||||
| D06 | 本地模型 | 原平台目录/镜像/下载/续传/取消/删除、准备/加载/保活/释放、原子激活、模型切换、真实推理与失败补偿 |
|
||||
| D07 | 风格/语言 | 原文/轻度/结构化/正式等原有模式、工作语言、目标语言、简繁、上下文窗口和前台应用归因 |
|
||||
| D08 | 全局快捷键 | 听写、翻译、QA、选区、切换风格、打开主窗、指定风格包及各平台实际提供的Agent入口;冲突、重绑与重启恢复 |
|
||||
| D09 | 插入 | 普通/流式、Unicode/换行、原目标恢复、final reconciliation、剪贴板恢复/降级;Inserted/PasteSent/CopiedFallback/OutcomeUnknown不混淆 |
|
||||
| D10 | QA | 文本/语音、多轮、选区/上下文、实时输出、停止/取消/关闭/重开、历史偏好;慢启动/转写期间仍可取消 |
|
||||
| D11 | 选区操作 | 原有润色、预览、确认/取消/撤回、stale保护;原平台及本PR承诺的Selection Voice意图与编辑流程 |
|
||||
| D12 | Less Computer | 已承诺的文本/语音入口、多轮、增量/工具/审批、拒绝/取消、CLI检测/配置/工作目录、进程树清理 |
|
||||
| D13 | 历史/录音/统计 | 列表与原有操作、原始/最终文本、真实provider/model/耗时、重新润色/转写、录音播放/导出、独立保留上限、统计热图 |
|
||||
| D14 | 词典与纠错 | 增删/启停、预设、规则顺序、词条命中、建议接受/拒绝;平台已有编辑观察及隐私开关 |
|
||||
| D15 | 风格包 | 创建/编辑/启用/激活/重置/删除、提示词诊断、ZIP导入导出、快捷键和活动状态联动 |
|
||||
| D16 | 风格市场 | 原有列表/详情/安装/下载/上传、点赞/收藏/作品管理、GitHub登录/取消/退出及错误恢复 |
|
||||
| D17 | 手机输入 | 服务/端口/PIN、地址和二维码、TLS/H5、默认模式、手机PCM、电脑落字开关、停止/取消/断连、连接隔离 |
|
||||
| D18 | 设置和维护 | 原有主题/字号/语言、胶囊、托盘、启动选项、网络诊断/日志、更新渠道及检查/下载/安装/重启 |
|
||||
| D19 | 1.x数据 | preferences、渠道/凭据、风格包、词典/规则、历史/录音、模型及自定义目录;幂等、失败保留来源、不覆盖新配置 |
|
||||
| 启动/生命周期 | `src/App.tsx`(`?window=` 分支)、`src/components/CoreStartupScreen.tsx`、`src-tauri/src/lib.rs` 启动合同 | 启动快照与版本校验、错误可读与重查、崩溃后恢复 |
|
||||
| 听写 | `src-tauri/src/coordinator/dictation.rs` + Core `dictation_engine.rs` | 首帧、热键触发、取消、静默自动停止、落字 reconciliation、错误恢复 |
|
||||
| 快捷键与插入 | `src-tauri/src/lib.rs`(`global_hotkey_runtime`)、`src/components/ShortcutRecorder.tsx`、`windows-ime/` | 全局注册/冲突提示/重绑/重启还原;Windows IME 落字与回退 |
|
||||
| AI 服务与渠道 | `src/pages/settings/{ChannelList,ProvidersSection}.tsx`、`src-tauri/src/commands/{providers,channels,credentials}.rs` | 渠道增删改/启停/验证结果展示/凭据安全存储(明文不落盘) |
|
||||
| 本地模型 | `src/pages/LocalAsr/`、`src-tauri/src/commands/local_asr.rs`、Core `model_store.rs` | 下载/取消/激活原子性;Windows Foundry/Sherpa;macOS Apple Speech/MLX |
|
||||
| 历史 | `src/pages/History.tsx`、`src-tauri/src/commands/history.rs` | 列表/详情切换(窄窗)、重润色结果保留、录音归档与导出 |
|
||||
| 风格包与市场 | `src/pages/{Style,Marketplace}.tsx`、`src-tauri/src/commands/{style_packs,marketplace}.rs` | 导入/应用/导入导出失败反馈;市场浏览/安装/OAuth |
|
||||
| 划词与 QA | `src/pages/{SelectionAsk,SelectionPolishPreview,SelectionVoiceIntentPicker,QaPanel}.tsx` | 触发→意图→预览→应用/取消/撤回;epoch 防旧事件覆盖 |
|
||||
| Less Computer | `src/pages/LessComputer{Panel,Glow}.tsx`、`src-tauri/src/coding_agent/` | 工具审批/输出/取消;Windows Job 对象与进程清理 |
|
||||
| 手机输入 | `src-tauri/src/remote_server/`、远程输入页面 | 配对/TLS/会话桥接/弱网与陈旧地址处理 |
|
||||
| 胶囊与多屏 | `src/components/Capsule.tsx`、`src-tauri/src/coordinator/capsule_focus.rs` | 跟随光标所在屏、多显示器/多 Space 正确显示 |
|
||||
| 音频提示 | `src/components/AudioCue.tsx`、`src-tauri/src/audio_mute.rs` | 提示音、系统静音/恢复所有终态恢复音量 |
|
||||
| 设置与更新 | `src/pages/settings/`(7 分类,`navigation.ts`)、`src/components/{AutoUpdate,AutoUpdateGate}.tsx` | 自动保存提示、焦点返回、更新安装失败可见与手动兜底 |
|
||||
|
||||
功能是否属于1.x必须核对实际入口和生产路径;仅有预留配置字段或旧路线图不算已交付。
|
||||
## 2. 平台特有门槛
|
||||
|
||||
## 2. Windows 原生验收
|
||||
- **Windows**:TSF/IME 管线(`windows-ime/` 工程;`scripts/windows-ime-*.ps1`、`scripts/windows-package-msvc.*`);Foundry/Sherpa 本地 ASR;`scripts/windows-*-smoke.ps1` 套件在真机执行。
|
||||
- **macOS**:AX/TIS 授权流、Apple Speech、MLX(`scripts/macos-mlx-*.mjs`、`scripts/macos-capsule-spaces-contract.test.mjs`)、签名与公证(`scripts/build-mac.sh`、release workflow)。
|
||||
- **Android**:APK 工作流(`.github/workflows/android-apk.yml`)+ 悬浮窗/无障碍/Shizuku 契约(`scripts/android-*.test.mjs`)。
|
||||
|
||||
- 左右修饰键、组合键、全局监听及原有应用内回退;停止/重绑时注销旧监听。
|
||||
- TSF DLL安装、Profile/category注册、键盘列表选项、管道/会话生命周期、原窗口/控件指纹和输入法恢复。
|
||||
- TSF/SendInput/Paste、非TSF降级开关、CRLF/Unicode;原生timeout/cancel和派发后断连不得触发重复落字。
|
||||
- 普通编辑器、浏览器、聊天框、终端,以及无权限/目标失效场景;不能把假成功写入历史。
|
||||
- Foundry/Sherpa按当前实际支持模型与运行时验证,包含取消、异常、保活及旧释放任务不得卸载新模型。
|
||||
- 托盘/任务栏/窗口、自启与系统麦克风;真实CLI满stdin、取消、孤孙和自然完成行为。
|
||||
- 新装、1.x升级、更新/卸载/回滚;只把实际生成并验证的安装包类型列为通过。跳过MSI不能算MSI成功。
|
||||
## 3. 完成口径
|
||||
|
||||
## 3. macOS 原生验收
|
||||
|
||||
- 麦克风/Accessibility拒绝与重新授权,Secure Input、密码等敏感场景边界。
|
||||
- 关闭光标上下文时零文档读取;前台应用元数据仍用于既有归因/换行规则。
|
||||
- AX选区/上下文、TIS输入源准备/恢复、普通与流式落字,Auto/ShiftReturn/LineFeed/Return。
|
||||
- AppKit主线程、NSPanel、胶囊、菜单栏/Dock、多显示器、窗口重开和焦点行为。
|
||||
- Apple Speech、Qwen/MLX/C、Whisper按原有OS/架构支持矩阵交付;不把Apple Silicon能力当作Intel能力。
|
||||
- Finder启动时的PATH、外部CLI检测与取消、Keychain及自启。
|
||||
- 声明支持的arm64/x86_64构建、安装/升级/更新,正式分发的签名、公证与Gatekeeper。
|
||||
|
||||
当前2.0 macOS普通听写复用了选区路径的app/PID级目标恢复;1.x普通听写未调用该恢复路径,不能将它描述为既有基线能力。精确恢复同应用内原控件/光标仍未得到证明。
|
||||
|
||||
## 4. 证据与阻塞规则
|
||||
|
||||
每个D编号按Windows/macOS分别记录:
|
||||
|
||||
| 字段 | 记录内容 |
|
||||
| --- | --- |
|
||||
| 基线与范围 | 1.x版本/提交、实际入口、OS/架构;原平台不支持的明确写“不适用” |
|
||||
| 2.0生产路径 | UI/热键/CLI → Tauri Adapter → Core → 原生效果/事件,指出删除的旧业务副本 |
|
||||
| 自动证据 | 行为回归、类型/编译/链接、目标runner与精确head;不把mock结果称作设备成功 |
|
||||
| 设备/安装证据 | 安装包、OS/目标应用、步骤、期望/实际、日志或截图;涉及数据须用测试资料 |
|
||||
| 结论 | 已实现且验证 / 已实现待设备验收 / 未实现 / 原平台不适用 |
|
||||
|
||||
- 原平台已有功能出现未实现、错误结果、数据丢失或取消失效:本批桌面2.0阻塞项。
|
||||
- 平台声明支持但只有构建/fixture证据:保留待验收,不直接标“完整支持”。
|
||||
- Linux应用/UI/原生验收未完成:移交egui团队,不单独阻塞本批桌面2.0;共享Core缺陷仍阻塞。
|
||||
- 正式GitHub评审、签名和发布权限按项目流程处理;内部子代理通过不替代这些门禁。
|
||||
|
||||
## 5. 2026-09-06 发布收口决定
|
||||
|
||||
按维护者决定,优先完成2.0交付;本次提交收口后不再启动新的全面审查轮次。影响输入目标、结果正确性、数据和主要流程的已确认问题保留修复;低影响显示边界暂缓,不将其扩大为新的发布阻塞。
|
||||
|
||||
- QA语音启动极快失败、首条Recording尚未被事件桥消费时,胶囊可能没有错误提示;QA面板仍接收Core错误事件。
|
||||
- 极短跨会话切换中,尚未呈现首帧的QA与旧Selection Voice自动隐藏定时器,仍可能出现胶囊提示被忽略或提前隐藏。已呈现QA的旧事件/定时器覆盖后继原生提示已补epoch检查。
|
||||
- 上述为源码时序分析的显示风险,不宣称已在真实设备稳定复现,也不等于数据、回答或录音结果错误。完整显示origin接线及旧Selection Voice定时器统一留待后续体验修复。
|
||||
|
||||
现有设备、安装包、签名、公证和最终head CI证据仍分别记录;本次收口不把未执行项改写为通过。
|
||||
安装包、签名、公证与最终 head CI 证据分别记录;未执行项不得改写为通过。设备/安装/升级/回滚验收是发布门,不属于本清单可由代码自证的部分。
|
||||
+21
-58
@@ -1,71 +1,34 @@
|
||||
# OpenLess 2.0:当前需求与交付范围
|
||||
# OpenLess 2.0:范围与完成条件
|
||||
|
||||
更新:2026-09-06。本文是本轮范围与验收的权威入口,优先于此前迁移计划、复核记录中的旧范围。
|
||||
初始盘点基线为 `2b3155722758a2b027f94e7032accf468d03106c`;后续实现/合同修复以本文件所在提交为准。
|
||||
状态:canonical(2026-09-07 以源码为准重写);更新:2026-09-07。
|
||||
|
||||
## 1. 目标
|
||||
## 1. 平台与版本锚点
|
||||
|
||||
1. **Windows、macOS 是本批 2.0 完整支持的平台。** 各自在 Tauri 1.x 中实际已有的用户功能,必须在 2.0 保留并完整实现。
|
||||
2. **Linux 本批交付可接入的 Core。** egui 团队接管 Linux Host Adapter 的剩余实现、生产接线、egui UI 与 Linux 产品验收。
|
||||
3. Linux 交接采用[独立目录](./linux-egui-handoff/README.md)中的小文档;逐项说明现成能力、真实缺口、接口、实施责任与验收条件。
|
||||
|
||||
## 2. Windows/macOS 的“完整”
|
||||
|
||||
- 比较的是用户可执行的完整操作:入口 → Core 业务 → 原生效果 → 结果/历史/错误反馈。只有类型、接口、按钮或 fixture 不算实现。
|
||||
- 以迁移前各平台实际交付的 1.x 功能为基线。现有源码锚点是 `v1.3.18-tauri` 和 PR 的 1.x base `fc9824ee`;还须覆盖其中较新的 Beta 能力及本 PR 已承诺的功能。
|
||||
- 不得因修改架构而丢失功能、设置效果、旧数据、快捷键、取消、异常恢复或平台行为。
|
||||
- Windows 的 TSF/Foundry/Sherpa、macOS 的 AX/TIS/Apple Speech/MLX 等按各自原有 OS/架构支持范围验收,不要求移植另一平台的专有机制。
|
||||
- Windows Less Computer 等本 PR 已明确增加的交付仍保留,不借重新定义 1.x 基线撤销。
|
||||
- 1.x 的预留字段或未实现设计不自动成为“已交付功能”;也不能把真实存在的实现误标为从未支持。每项须有源码/产品入口证据。
|
||||
- 原生能力必须以真实结果表达,不能用 `Unsupported`、no-op、伪成功或仅有单元测试替代该平台原先可用的功能。
|
||||
|
||||
详细功能与平台验收见[桌面验收清单](./2.0-desktop-acceptance.md)。该清单是需求,不是“全部已通过”的声明。
|
||||
|
||||
## 3. 责任划分
|
||||
|
||||
| 交付 | 本轮 Core/Tauri 工作 | egui 团队接手 |
|
||||
| 目标 | 载体 | 版本锚点 |
|
||||
| --- | --- | --- |
|
||||
| 共享业务 | Core 领域实现、状态机、配置/凭据/模型迁移、取消/错误/结果规则 | 调用 Core;发现业务接口缺项时回报 Core 负责人 |
|
||||
| Windows/macOS | Tauri/React 接线、原生 Adapter、完整 1.x 功能保留、自动与设备验收 | 不属于 Linux 接入任务 |
|
||||
| Linux 接入面 | 可用的公开接口、DTO/事件、Adapter 合同、fixture、示例和缺口文档 | Linux 原生 Adapter 剩余实现、全局热键、窗口/托盘/权限/更新等宿主效果 |
|
||||
| Linux UI | 提供已有代码和行为合同作起点 | egui/eframe 页面、交互、事件消费、错误展示、完整产品流程 |
|
||||
| Linux 发布 | 保留现有验证脚本与已有证据供复用 | Linux 打包、真实桌面/设备、安装升级回滚与正式发布验收 |
|
||||
| Windows / macOS / Android | Tauri 2 Host + React 界面(`src-tauri/` + `src/`) | `src-tauri/tauri.conf.json` 与 `package.json` 均为 `2.0.0-Beta.1`;identifier `com.openless.app` |
|
||||
| Linux | egui/eframe Host + 原生 UI(`linux-egui/`,crate `openless-linux-egui`) | crate 版本 0.1.0;发布版本跟随上方 2.0.0-Beta.1 |
|
||||
| 共享业务 | `crates/openless-core`(平台无关,crate 版本 0.1.0) | 公开合同 `openless-all/app/contract/backend-2.0.json` |
|
||||
|
||||
这里的“egui 团队”包含本次明确移交的 **Linux Host 工作**。egui 框架本身不会自动完成系统全局热键、音频、Secret Service 或安装器。
|
||||
显示版本与内部 crate 版本是两个概念;发布版本号、标签与渠道以仓库发布流程(`RELEASING.md`、GitHub workflows)为准。
|
||||
|
||||
## 4. Core 对 Linux 的交付门
|
||||
## 2. “完整”的定义(Windows / macOS)
|
||||
|
||||
“预留 Core”要求能接入、能验证,不是未来再实现的空接口:
|
||||
- 比较对象是用户可执行的操作链:入口 → Core 业务 → 原生效果 → 结果/历史/错误反馈。仅类型、按钮、fixture 或接口存在不算实现。
|
||||
- 功能基线为各平台 1.x 实际交付(标签 `v1.3.18-tauri` 及其 beta 线);架构迁移不得丢失功能、旧数据、快捷键、取消与异常恢复路径。
|
||||
- 平台特有能力按各自 OS 范围验收,不要求跨平台移植:Windows 的 TSF/IME(`windows-ime/` 工程)、Foundry/Sherpa 本地 ASR;macOS 的 AX/TIS、Apple Speech、MLX。
|
||||
- 原生能力以真实结果表达;`Unsupported`、no-op、伪成功或仅单元测试通过不构成交付。
|
||||
|
||||
1. Linux 可依赖 `openless-core`,Core 不依赖 Tauri/egui 或要求调用 Tauri 私有模块。
|
||||
2. 业务规则有真实实现;平台动作由明确的 Host Interface 承接。缺 Linux 原生效果可以标注未接入,不能把缺少 Core 业务实现推给 UI。
|
||||
3. 提供 `2.0.0` 启动合同、DTO、事件顺序/重放、session/取消、错误和能力语义。
|
||||
4. 提供可运行的无设备 fixture/headless 示例及对应 contract tests。
|
||||
5. Linux 现有 Adapter、UI 和包构建作为可复用起点保留;每项分别标明“已有实现”“接线缺口”“原生验证待补”。
|
||||
6. 确定缺口在交接目录有责任、实现位置、Core 调用点、依赖与验收要求。
|
||||
|
||||
## 5. 本批 2.0 完成条件
|
||||
## 3. 完成条件
|
||||
|
||||
| 范围 | 完成条件 |
|
||||
| --- | --- |
|
||||
| Windows | 各项原有能力完成真实生产接线;无未关闭的确定功能/迁移缺陷;通过必要自动测试、安装与目标应用/设备验收 |
|
||||
| macOS | 同上,并覆盖所声明支持的 Intel/Apple Silicon 与权限、输入源、原生模型、签名分发要求 |
|
||||
| 共享 Core | 跨平台依赖/合同/迁移/会话与错误语义通过验证,可供 Linux 团队直接接入 |
|
||||
| Linux 本轮移交 | 本文第4节及拆分交接资料完整;无需等待 Linux 应用所有功能、UI、真实设备和安装升级完成 |
|
||||
| Windows | [桌面验收清单](2.0-desktop-acceptance.md)各域生产接线 + 安装、目标应用与设备验收 |
|
||||
| macOS | 同上,另加 Intel/Apple Silicon、权限、输入源、本地模型、签名与公证 |
|
||||
| Android | APK 工作流与悬浮窗管线按 [Android 计划](android-mobile-apk-overlay-plan.md) |
|
||||
| Linux(本批移交) | 可接入的 Core + 交接资料完整;应用侧缺口按[登记表](linux-egui-handoff/02-gap-register.md) L01–L12 推进,不阻塞 Windows/macOS 交付 |
|
||||
| 共享 Core | 合同、迁移、会话与取消语义可被 Linux 直接接入,见[契约](linux-egui-backend-contract.md) |
|
||||
|
||||
**Linux 应用侧的未完成项不再阻塞本批 Windows/macOS 2.0 交付。**
|
||||
但缺陷若位于共享 Core、会破坏 Windows/macOS,或使承诺的 Linux Core 接口不可调用,仍是本轮问题。
|
||||
## 4. 证据分级
|
||||
|
||||
验收报告必须区分:源码已实现、自动测试通过、目标平台编译通过、安装运行通过、设备交互通过。CI 和内部审查不替代正式发布所需的真实行为证据。
|
||||
|
||||
## 6. 现有成果和其它平台
|
||||
|
||||
- 已完成的 PR 修复、Linux 代码、构建和测试不回退、不删除;本次调整的是责任与完成条件。
|
||||
- Android 不纳入本次新增的“首批完整支持”目标;若保留现有构建,其已有合同不能因共享 Core 修改而无故破坏。
|
||||
- 本次是需求与交接范围更新;当前代码版本快照仍为 `2.0.0-Beta.1`。版本号、标签与正式发布按项目发布流程执行。
|
||||
|
||||
## 7. 后续报告口径
|
||||
|
||||
- 分别报告“Windows 2.0 完成度”“macOS 2.0 完成度”“Core 移交完成度”“Linux 团队待办”。
|
||||
- Linux 宿主未接线应写作“Core 已有,Linux Host/UI 待实现”,不要写作“Core 缺失”,也不要写作“Linux 功能已完成”。
|
||||
- 若接口本身尚不足以承载原有业务,列为 Core 缺口并由本轮团队修复。
|
||||
源码已实现 / 自动测试通过 / 目标平台编译通过 / 安装运行通过 / 设备交互通过,五级分开表述。CI、单元测试与内部审查不替代真实发布所需的行为证据;macOS 上的构建或 cfg 排除不能冒充 Linux 原生结果。
|
||||
@@ -1,348 +1,32 @@
|
||||
# OpenLess Android APK 与悬浮窗实施计划
|
||||
# Android APK 与悬浮窗实施计划
|
||||
|
||||
> 状态:实施中 / 后端分层已落地
|
||||
> 日期:2026-06-07
|
||||
> 范围:Android APK v1(应用内录音)→ IME v2(跨 App 输入)→ 悬浮窗 v3;不改桌面语义
|
||||
状态:实施中(后端分层已落地);更新:2026-09-07(以源码为准重写)。
|
||||
|
||||
目标是把现有 OpenLess 桌面应用扩展为 Android APK,并在手机端提供可用的语音输入体验。当前项目是 React/Vite + Tauri v2 + Rust 后端,Tauri 官方支持 Android 构建;现仓库已增加 Android 分层与脚手架,桌面核心能力(全局热键、托盘、桌面浮窗、TSF IME 等)通过 `#[cfg(not(mobile))]` 收口。
|
||||
## 1. 现有管线(源码锚点)
|
||||
|
||||
核心策略:先产出可安装 APK,再做 Android 输入法服务,最后做跨 App 悬浮窗。桌面功能保持不受影响。
|
||||
| 组成 | 位置 |
|
||||
| --- | --- |
|
||||
| Kotlin 宿主 | `android/kotlin/`(含 `androidTest/`、`test/`) |
|
||||
| AIDL 桥接 | `android/aidl/com/` |
|
||||
| Manifest 合成 | `android/manifests/`(基础)+ `scripts/merge-android-updater-manifest.mjs`、`merge-android-overlay-manifest.mjs`、`merge-android-v1-manifest.mjs` |
|
||||
| Shizuku | `scripts/merge-android-shizuku-manifest.mjs`、`patch-android-shizuku-deps.mjs`(含各自 `.test.mjs`) |
|
||||
| 前端片段 | `android/frontend/`(经 vite 别名 `@android` 被 `src/` 引用:`AndroidPermissionsPanel`、`androidMicrophonePermission`、`androidIpc`、`androidTypes`) |
|
||||
| Rust 桥接 | `src-tauri/src/android/`(jni/native_bridge/insert/overlay/shizuku/updater 等) |
|
||||
| CI | `.github/workflows/android-apk.yml`;打包脚本 `scripts/copy-android-scaffolding.mjs`、`configure-android-release-signing.mjs` |
|
||||
| 更新器 | 公钥检查 `scripts/check-android-updater-pubkey.mjs`;manifest updater 合成 |
|
||||
|
||||
参考依据:
|
||||
- Tauri Android 依赖:Android Studio、SDK/NDK、`ANDROID_HOME`、`NDK_HOME`、Android Rust targets。
|
||||
- Tauri Android 构建命令:`tauri android init`,`tauri android build --apk`。
|
||||
- Android 悬浮窗:Android 8.0+ 使用 `SYSTEM_ALERT_WINDOW` + `TYPE_APPLICATION_OVERLAY`。
|
||||
## 2. 契约与边界测试(npm test 自动执行)
|
||||
|
||||
---
|
||||
`scripts/android-ipc-import-boundary.test.mjs`(pretest 强制)+ `android-accessibility-{enabled-detection,paste-cache,selection-ipc}-contract`、`android-credential-keystore-contract`、`android-insert-tier-fallback-contract`。IPC 面与 `contract/backend-2.0.json` 的 `androidJni` 对齐。
|
||||
|
||||
## 1. 目标与非目标
|
||||
## 3. 剩余工作
|
||||
|
||||
### 目标
|
||||
- 悬浮窗(overlay)与无障碍链路的真机验收(契约测试之外的真实设备证据)。
|
||||
- Shizuku 插件路径在真机的安装/授权/失败反馈闭环。
|
||||
- 发布签名配置的密钥管理与出包验证(`configure-android-release-signing.mjs` 流程化)。
|
||||
|
||||
- **APK v1**:应用内主窗口、设置、历史、云端 ASR/LLM、基础录音、复制结果
|
||||
- **IME v2**:`OpenLessImeService` 作为跨 App 输入主路径
|
||||
- **悬浮窗 v3**:前台服务 + `TYPE_APPLICATION_OVERLAY`,仅作录音控制入口
|
||||
- **平台能力查询**:前端通过 `get_platform_capabilities` 隐藏桌面专属设置项
|
||||
- **桌面零破坏**:所有适配经 `#[cfg(not(mobile))]` / `#[cfg(mobile)]` 分层
|
||||
## 4. 参考
|
||||
|
||||
### 非目标
|
||||
|
||||
- APK 首版不纳入本地 ASR(Foundry、Sherpa、Qwen 桌面假设)
|
||||
- 首版不承诺直接写入其他 App(无 IME 时走复制兜底)
|
||||
- Accessibility 跨 App 输入不作为默认路径
|
||||
- 悬浮窗不承担最终文本插入职责
|
||||
- 不改桌面现有功能语义
|
||||
|
||||
### 明确边界
|
||||
|
||||
- **不动** macOS / Windows / Linux 热键、托盘、胶囊、QA 浮窗逻辑
|
||||
- **Android 命令名与桌面一致**;不支持的命令返回明确 unavailable 状态
|
||||
- **Coordinator 听写主链路复用**;`end_session` 在 Android 增加 IME commit 分支
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构定位
|
||||
|
||||
```
|
||||
openless-all/app/
|
||||
package.json # tauri:android:* scripts
|
||||
vite.config.ts # TAURI_ENV_PLATFORM → 0.0.0.0 + HMR
|
||||
src-tauri/
|
||||
tauri.conf.json # bundle.android.minSdkVersion ≥ 26
|
||||
tauri.android.conf.json # 单 main 窗口(无 capsule/qa/tray)
|
||||
capabilities/
|
||||
default.json # 桌面
|
||||
mobile.json # Android 主窗口
|
||||
android-scaffolding/ # Kotlin 模板(init 后复制到 gen/android)
|
||||
src/
|
||||
lib.rs # desktop/mobile run() 分层
|
||||
android_ime.rs # IME 状态 + commit(JNI 桩)
|
||||
android_overlay.rs # 悬浮窗权限/状态(JNI 桩)
|
||||
permissions.rs # Android 麦克风 runtime permission 分支
|
||||
persistence.rs # Android 凭据加密文件桩
|
||||
types.rs # PlatformCapabilities
|
||||
commands.rs # get_platform_capabilities + 桌面命令 stub
|
||||
coordinator/dictation.rs # end_session Android IME 分支
|
||||
```
|
||||
|
||||
平台分层示意:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ React UI(能力查询 → 隐藏桌面专属设置) │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Tauri commands(同名 IPC;mobile 返回 unavailable stub) │
|
||||
├──────────────┬──────────────────────────────────────────┤
|
||||
│ desktop │ mobile (Android) │
|
||||
│ hotkey/tray │ in-app dictation + cloud ASR │
|
||||
│ capsule/qa │ android_ime (v2) / android_overlay(v3) │
|
||||
│ TSF/AX/粘贴 │ IME commit (v1); clipboard TBD │
|
||||
└──────────────┴──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
> **v1 剪贴板**:APK v1 不使用 Android 剪贴板兜底(未接 arboard);跨 App 文本输入依赖后续 IME/JNI 接线。
|
||||
|
||||
---
|
||||
|
||||
## 3. 模块设计
|
||||
|
||||
### 3.1 `PlatformCapabilities`(`types.rs`)
|
||||
|
||||
```rust
|
||||
pub struct PlatformCapabilities {
|
||||
pub platform: String,
|
||||
pub supports_ime_input: bool,
|
||||
pub supports_overlay: bool,
|
||||
pub supports_desktop_hotkey: bool,
|
||||
pub supports_tray: bool,
|
||||
pub supports_local_asr: bool,
|
||||
pub supports_capsule_overlay: bool,
|
||||
}
|
||||
```
|
||||
|
||||
- Android:`supportsImeInput=true`(v2 起)、`supportsOverlay=true`(v3 起)、`supportsDesktopHotkey=false`、`supportsTray=false`
|
||||
- 桌面:按 OS 填真实能力
|
||||
|
||||
### 3.2 `android_ime.rs`
|
||||
|
||||
- `get_android_ime_status()` → 是否已启用 OpenLess 输入法
|
||||
- `commit_text(text)` → 通过 JNI 提交到 `InputConnection`(桩 → 日志 + 返回未连接)
|
||||
- Kotlin:`OpenLessImeService` 继承 `InputMethodService`
|
||||
|
||||
### 3.3 `android_overlay.rs`
|
||||
|
||||
- `get_android_overlay_status()` → `SYSTEM_ALERT_WINDOW` 授权状态
|
||||
- `request_android_overlay_permission()` → 跳转 `OverlayPermissionActivity`
|
||||
- Kotlin:`OpenLessOverlayService`(前台服务 + overlay window)
|
||||
|
||||
### 3.4 `permissions.rs` / `persistence.rs`
|
||||
|
||||
- 麦克风:Android runtime permission 分支(JNI 桩;未接线时 `NotDetermined`)
|
||||
- 凭据:Android 加密 JSON 文件桩(`credentials.enc.json`),不沿用桌面 keyring
|
||||
|
||||
### 3.5 `coordinator/dictation.rs`
|
||||
|
||||
`end_session` 插入阶段:
|
||||
|
||||
```
|
||||
#[cfg(target_os = "android")]
|
||||
android_ime::commit_text → Inserted
|
||||
失败 → copy_fallback → CopiedFallback
|
||||
#[cfg(not(mobile))]
|
||||
现有 Windows TSF / AX / paste 路径
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 原生层接口
|
||||
|
||||
| 组件 | 职责 |
|
||||
|---|---|
|
||||
| `MainActivity` | Tauri WebView + 权限状态桥接 |
|
||||
| `OpenLessImeService` | 接收识别结果,`commitText` 到当前输入框 |
|
||||
| `OpenLessOverlayService` | 悬浮窗开始/停止录音、显示状态 |
|
||||
| `OverlayPermissionActivity` | 引导用户授权 `SYSTEM_ALERT_WINDOW` |
|
||||
|
||||
Rust ↔ Kotlin 通信:Tauri mobile plugin / `jni`(脚手架阶段为桩,init 后接线)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 实施里程碑
|
||||
|
||||
### M0 环境与文档(本期)
|
||||
|
||||
- 扩展本计划文档(元数据、架构、文件表、风险)
|
||||
- `package.json`:`tauri:android:init|dev|build`
|
||||
- `vite.config.ts`:mobile dev host `0.0.0.0` + HMR
|
||||
- `tauri.conf.json`:`bundle.android.minSdkVersion: 26`
|
||||
- `tauri.android.conf.json` + `capabilities/mobile.json`
|
||||
|
||||
### M1 Rust 分层 + APK 骨架(本期)
|
||||
|
||||
- `lib.rs` desktop/mobile `run()` 分层
|
||||
- `Cargo.toml` gate 桌面专属依赖
|
||||
- `PlatformCapabilities` + 命令 stub
|
||||
- `permissions.rs` / `persistence.rs` Android 分支
|
||||
- `cargo check` 桌面通过;Android target 尽力验证
|
||||
|
||||
### M2 IME v2
|
||||
|
||||
- 接线 `OpenLessImeService` JNI
|
||||
- 设置页显示输入法启用状态
|
||||
- 跨 App 提交验收
|
||||
|
||||
### M3 悬浮窗 v3
|
||||
|
||||
- 接线 `OpenLessOverlayService`
|
||||
- 授权引导 + 前台服务稳定性
|
||||
|
||||
---
|
||||
|
||||
## 6. 文件触达表
|
||||
|
||||
| 文件 | 变更 |
|
||||
|---|---|
|
||||
| `docs/android-mobile-apk-overlay-plan.md` | 扩展为完整实施规划(本文档) |
|
||||
| `openless-all/app/package.json` | `tauri:android:*` scripts |
|
||||
| `.github/workflows/android-apk.yml` | Android debug APK CI |
|
||||
| `openless-all/app/scripts/merge-android-v1-manifest.mjs` | v1 manifest merge (RECORD_AUDIO) |
|
||||
| `openless-all/app/vite.config.ts` | mobile dev server / HMR |
|
||||
| `openless-all/app/src-tauri/tauri.conf.json` | `bundle.android` |
|
||||
| `openless-all/app/src-tauri/tauri.android.conf.json` | 单 main 窗口 |
|
||||
| `openless-all/app/src-tauri/capabilities/mobile.json` | Android 权限集 |
|
||||
| `openless-all/app/src-tauri/Cargo.toml` | gate desktop deps |
|
||||
| `openless-all/app/src-tauri/src/lib.rs` | mobile/desktop 分层 |
|
||||
| `openless-all/app/src-tauri/src/types.rs` | `PlatformCapabilities` 等 |
|
||||
| `openless-all/app/src-tauri/src/commands.rs` | 能力查询 + mobile stub |
|
||||
| `openless-all/app/src-tauri/src/permissions.rs` | Android 麦克风 |
|
||||
| `openless-all/app/src-tauri/src/persistence.rs` | Android 凭据 |
|
||||
| `openless-all/app/src-tauri/src/android_ime.rs` | 新增 |
|
||||
| `openless-all/app/src-tauri/src/android_overlay.rs` | 新增 |
|
||||
| `openless-all/app/src-tauri/src/coordinator/dictation.rs` | `end_session` Android 分支 |
|
||||
| `openless-all/app/src-tauri/android-scaffolding/*.kt` | Kotlin 模板 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 风险与对策
|
||||
|
||||
| 风险 | 对策 |
|
||||
|---|---|
|
||||
| 本机无 Android SDK / NDK | 文档记录手动脚手架;`android-scaffolding/` 供 init 后复制 |
|
||||
| `global-hotkey` / `enigo` / `arboard` 无法编 Android | `Cargo.toml` `cfg(not(mobile))` gate |
|
||||
| `keyring` 在 Android 不可用 | 加密文件桩 + 后续 Keystore 接线 |
|
||||
| 桌面构建被 mobile 分层破坏 | 桌面 `cargo check` 为 CI 门禁;mobile 代码 `#[cfg(mobile)]` 隔离 |
|
||||
| JNI 未接线时 IME/overlay 假成功 | 状态查询返回 `enabled=false`;commit 走 copy fallback |
|
||||
| 多窗口配置污染移动端 | `tauri.android.conf.json` 仅声明 `main` |
|
||||
| 本地 ASR 拖慢 Android 首版 | 明确排除;`supportsLocalAsr=false` |
|
||||
| `SYSTEM_ALERT_WINDOW` 用户拒绝 | 设置页显示状态;悬浮窗功能降级为应用内入口 |
|
||||
|
||||
---
|
||||
|
||||
## Android APK CI Workflow
|
||||
|
||||
GitHub Actions workflow: [`.github/workflows/android-apk.yml`](../.github/workflows/android-apk.yml)
|
||||
|
||||
### Capability platform isolation
|
||||
|
||||
Desktop permissions live in `capabilities/default.json` with `"platforms": ["macOS", "windows", "linux"]`, so updater, autostart, and multi-window permissions do not apply on Android. Android uses `capabilities/mobile.json` with `"platforms": ["android"]` for the main-window permission set. In-app updates use a custom Rust updater (`src-tauri/src/android/updater.rs`) because `tauri-plugin-updater` does not support Android.
|
||||
|
||||
### Triggers & channels
|
||||
|
||||
| Trigger | Build mode | Behavior |
|
||||
|---|---|---|
|
||||
| `workflow_dispatch` | **release** if `ANDROID_KEYSTORE_*` secrets configured; else **debug (unsigned)** | Upload Actions artifacts; non-blocking fallback with job summary notice when unsigned |
|
||||
| Push tag `v*-tauri` / `v*-beta-tauri` | **release** (required secrets) | Signed release APKs + minisign `.sig` + `latest-android-{arch}[-beta].json` → attach to GitHub Release |
|
||||
|
||||
`OPENLESS_RELEASE_CHANNEL` matches desktop `release-tauri.yml`: `-beta-tauri` → beta (prerelease manifests); otherwise stable.
|
||||
|
||||
Tag releases require secrets: `TAURI_SIGNING_PRIVATE_KEY` (minisign), `ANDROID_KEYSTORE_BASE64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, `ANDROID_KEY_PASSWORD` (APK signing).
|
||||
|
||||
### APK naming
|
||||
|
||||
| ABI | Release (tag) | Debug (dispatch) |
|
||||
|---|---|---|
|
||||
| arm64-v8a | `OpenLess_<version>_arm64-v8a.apk` | `OpenLess-android-debug-arm64-v8a-run-<n>.apk` |
|
||||
| armeabi-v7a | `OpenLess_<version>_armeabi-v7a.apk` | `OpenLess-android-debug-armeabi-v7a-run-<n>.apk` |
|
||||
| x86 | `OpenLess_<version>_x86.apk` | `OpenLess-android-debug-x86-run-<n>.apk` |
|
||||
| x86_64 | `OpenLess_<version>_x86_64.apk` | `OpenLess-android-debug-x86_64-run-<n>.apk` |
|
||||
|
||||
Actions artifact names: `openless-android-release-{abi}` (tag) or `openless-android-debug-{abi}` (dispatch).
|
||||
|
||||
Updater manifests: `latest-android-aarch64.json` (arm64), `latest-android-armv7.json`, `latest-android-i686.json`, `latest-android-x86_64.json` (+ `-beta` / `-mirror` variants).
|
||||
|
||||
### Command chain (CI)
|
||||
|
||||
```bash
|
||||
cd openless-all/app
|
||||
npm ci && npm run build
|
||||
npm run tauri -- android init --ci
|
||||
node scripts/copy-android-scaffolding.mjs
|
||||
node scripts/merge-android-v1-manifest.mjs
|
||||
node scripts/merge-android-overlay-manifest.mjs
|
||||
node scripts/merge-android-updater-manifest.mjs
|
||||
# tag only:
|
||||
node scripts/configure-android-release-signing.mjs
|
||||
npm run tauri:android:build:debug # dispatch
|
||||
npm run tauri:android:build:release # tag
|
||||
# tag only:
|
||||
node scripts/sign-android-apks.mjs <apks...>
|
||||
OPENLESS_UPDATE_APK_DIR=... OPENLESS_UPDATE_TARGET=android OPENLESS_UPDATE_ARCH=aarch64 node scripts/write-updater-manifest.mjs
|
||||
```
|
||||
|
||||
`ci.yml` also runs `cargo check --target aarch64-linux-android` as a lightweight mobile cfg gate.
|
||||
|
||||
### Manifest merge
|
||||
|
||||
- `merge-android-v1-manifest.mjs` — `RECORD_AUDIO` (v1 in-app dictation)
|
||||
- `merge-android-overlay-manifest.mjs` — overlay + accessibility service
|
||||
- `merge-android-updater-manifest.mjs` — `REQUEST_INSTALL_PACKAGES` + `FileProvider` for APK install
|
||||
|
||||
### In-app updater (Android)
|
||||
|
||||
Custom Rust module [`openless-all/app/src-tauri/src/android/updater.rs`](../openless-all/app/src-tauri/src/android/updater.rs) + shared helpers [`updater_logic.rs`](../openless-all/app/src-tauri/src/android/updater_logic.rs). Desktop continues to use `tauri-plugin-updater`.
|
||||
|
||||
**Manifest URLs** (generated by [`write-updater-manifest.mjs`](../openless-all/app/scripts/write-updater-manifest.mjs)):
|
||||
|
||||
| Channel | Filename | GitHub path |
|
||||
|---|---|---|
|
||||
| Stable | `latest-android-{arch}.json` | `releases/latest/download/...` |
|
||||
| Beta | `latest-android-{arch}-beta.json` | `releases/download/{v*-beta-tauri tag}/...` |
|
||||
|
||||
Client tries mirror URL first (`-mirror.json`), then direct GitHub. Beta tag is resolved from `releases.atom` (first `-beta-tauri` entry).
|
||||
|
||||
**User-facing behavior**:
|
||||
|
||||
- **Settings → About**: manual “Check stable update” always fetches stable manifest.
|
||||
- **Settings → Advanced**: Beta toggle sets `prefs.updateChannel` for background checks; “Check Beta update” always fetches beta manifest (independent of toggle).
|
||||
- **Settings → Advanced → Auto-update** (Android): `autoUpdateCheck` toggle — when on, `AutoUpdateGate` checks on launch (+4s) and every 60 minutes using `updateChannel`, then **automatically downloads**, minisign-verifies, and opens the **system APK installer**. When off, only manual buttons run.
|
||||
- **Desktop**: `autoUpdateCheck` only auto-checks; user confirms in `UpdateDialog` before download/install/restart.
|
||||
|
||||
**Install flow**: download to app cache → minisign verify → JNI `install_apk_from_path` → Android system installer. Over-the-air replace is **not** implemented in-app; the user completes install via the system UI. Requires matching APK signature for upgrade.
|
||||
|
||||
**Prefs**: `updateChannel` (`stable` | `beta`) = background auto-update channel only; manual buttons pass explicit channel and ignore this pref.
|
||||
|
||||
---
|
||||
|
||||
## 8. 验收标准
|
||||
|
||||
### 构建验证
|
||||
|
||||
```bash
|
||||
cd openless-all/app
|
||||
npm run build
|
||||
cargo check --manifest-path src-tauri/Cargo.toml
|
||||
# 需 Android SDK / NDK(与 CI 一致:debug APK):
|
||||
npm run tauri:android:init
|
||||
npm run merge:android-v1-manifest
|
||||
npm run tauri:android:build
|
||||
# 等价于 CI 的 debug 构建:
|
||||
# CI=true npm run tauri -- android init --ci && node scripts/merge-android-v1-manifest.mjs && CI=true npm run tauri:android:build
|
||||
```
|
||||
|
||||
### APK v1
|
||||
|
||||
- 首次启动进入主界面
|
||||
- 麦克风授权流程可触发
|
||||
- 应用内录音 → 云端转写 → 历史 + 复制
|
||||
- 桌面专属命令不导致前端白屏(返回 unavailable)
|
||||
- 桌面 `cargo check` 仍通过
|
||||
|
||||
### IME v2 / 悬浮窗 v3
|
||||
|
||||
见原 Summary 中 Test Plan 章节(启用输入法后跨 App 提交;悬浮窗授权与前台服务稳定性)。
|
||||
|
||||
---
|
||||
|
||||
## Compatibility fixes(2026-06-07)
|
||||
|
||||
- **`app_invoke_handler_mobile`**:仅保留 dictation / settings / credentials / history / cloud ASR / platform capabilities / Android IME·overlay / permissions / marketplace / style packs / mic devices;已移除 `get_hotkey_*`、`set_shortcut_recording_active` 及全部 desktop-only 命令(热键 setter、updater、local ASR、coding agent、tray 等)。前端 `ipc.ts` 在 `supportsDesktopHotkey === false` 时本地返回 stub,不再 invoke 这些命令。
|
||||
- **`mobile_stubs`**:`unicode_keystroke` 补齐 `typed_chars()` / `Partial`(与 coordinator 流式插入一致);`shortcut_binding::binding_from_legacy_trigger` 与桌面实现对齐。
|
||||
- **`Cargo.toml`**:`enigo` / `global-hotkey` / updater / single-instance / autostart 仅在 `cfg(not(mobile))`;Android 侧 `jni` + `ndk-context` 已声明。
|
||||
|
||||
---
|
||||
|
||||
## 9. 相关参考
|
||||
|
||||
- Tauri Android:https://v2.tauri.app/develop/mobile/
|
||||
- 主听写链路:`openless-all/app/src-tauri/src/coordinator/dictation.rs`
|
||||
- Windows IME unavailable 模式:`openless-all/app/src-tauri/src/windows_ime_profile.rs`
|
||||
- Tauri Android:<https://v2.tauri.app/develop/mobile/>
|
||||
- 主听写链路:`src-tauri/src/coordinator/dictation.rs`
|
||||
- Windows IME unavailable 模式对照:`src-tauri/src/windows_ime_profile.rs`
|
||||
+31
-35
@@ -1,49 +1,45 @@
|
||||
# Core 2.0 架构
|
||||
# OpenLess 2.0 架构
|
||||
|
||||
状态:canonical;阶段:implementation-backed;更新:2026-09-07;源码基线:`fc38edd1`,应用版本 `2.0.0-Beta.1`,公开合同 `2.0.0`。
|
||||
状态:canonical(2026-09-07 以源码为准重写);更新:2026-09-07。
|
||||
|
||||
## 分层与入口
|
||||
## 1. 分层与工作区
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
UI[React 桌面界面] --> IPC[typed IPC / 启动合同]
|
||||
IPC --> Tauri[Tauri Host]
|
||||
Egui[Linux egui 界面] --> Linux[Linux Host facade]
|
||||
Tauri --> Core[openless-core 共享业务]
|
||||
Linux --> Core
|
||||
Core --> Ports[Host interfaces]
|
||||
Ports --> Native[录音 / 系统凭据 / 热键 / 目标输入 / 窗口]
|
||||
Core --> Events[带 sequence 与 session 的事件]
|
||||
Events --> Tauri
|
||||
Events --> Linux
|
||||
```
|
||||
应用唯一源是 `openless-all/app/`。Cargo workspace 成员为 `crates/openless-core` + `linux-egui`;`src-tauri`(及其 `backend-tests` 测试 crate)被 exclude,独立构建。
|
||||
|
||||
应用工作目录为 `openless-all/app`。
|
||||
|
||||
| 层 | 实现位置 | 职责 |
|
||||
| 层 | 位置 | 职责 |
|
||||
| --- | --- | --- |
|
||||
| React | `src/App.tsx`、`src/components/FloatingShell.tsx`、`src/pages/` | 启动显示、页面、草稿、操作与结果反馈 |
|
||||
| IPC | `src/lib/ipc/shared.ts`、`src/lib/ipc/` | `2.0.0` 启动检查,typed invoke,业务调用前等待 Core 可用 |
|
||||
| 共享 Core | `crates/openless-core/src/lib.rs`、`api.rs`、`domains.rs` | 公开门面、领域服务、状态机、配置、迁移与会话规则 |
|
||||
| 原生接口 | `crates/openless-core/src/ports.rs` | 录音、插入、资源、目标上下文、窗口等 Host effect 合同 |
|
||||
| Tauri Host | `src-tauri/src/commands/`、`core_adapters.rs`、`tauri_coordinator_host.rs`、`tauri_events.rs` | 连接现有 Windows/macOS 原生实现并桥接 React |
|
||||
| Linux Host/UI | `linux-egui/src/{backend,lib,main}.rs` | 共享 Core 接入、现有 Linux Adapter、egui 页面与事件显示 |
|
||||
| 合同 | `contract/backend-2.0.json`、`crates/openless-core/tests/`、前端 IPC 测试 | 版本、DTO、事件和领域行为回归 |
|
||||
| 界面(桌面/移动) | `src/`(React/TypeScript/i18next,五语言) | 唯一桌面 UI;页面、设置分类、窗口分支;不另写业务规则 |
|
||||
| Host(Win/mac/Android) | `src-tauri/`(crate `2.0.0-Beta.1`) | IPC 命令面(`src/lib.rs` 两个 `generate_handler!`,共 188 个注册);原生 Adapter:窗口、热键、音频、凭据、插入、IME、coordinator |
|
||||
| 共享 Core | `crates/openless-core/`(crate `0.1.0`) | 全部业务规则与状态机;平台动作只经 `ports.rs` 接口注入 |
|
||||
| Linux Host + UI | `linux-egui/`(crate `openless-linux-egui` 0.1.0) | 与 Core 同进程:`backend.rs` 组装 `OpenLessBackend`,`main.rs` 跑 egui 界面,不依赖 Tauri/WebKitGTK |
|
||||
|
||||
## 听写与状态
|
||||
Android 侧:`src-tauri/src/android/`(JNI/桥接)+ `android/`(aidl、kotlin、manifests、frontend);`android/frontend` 经 vite 别名 `@android` 被 `src/` 引用;manifest 由 `scripts/merge-android-*.mjs` 合成。
|
||||
|
||||
UI/热键发起 → Core 获取会话及真实目标 → Host 录音与识别 → Core 转写/润色 → Host 向原目标输入 → 真实结果、历史与事件反馈。停止录音、取消会话和正常结束分别调用已有门面,页面导航不接管录音资源。
|
||||
## 2. 数据流
|
||||
|
||||
主窗口与胶囊、问答、选区预览、Agent 等 WebView 共用启动合同检查。布局改进只改变启动说明,不跳过检查。`recordingReady` 由首帧 PCM 证据驱动,不能用定时器伪造就绪。
|
||||
- 桌面:React → 类型化 IPC 门面(`src/lib/ipc/`)→ Tauri command → Core;Core 事件由 Host 转发回界面。
|
||||
- Linux:egui UI → `LinuxHost`(`lib.rs`:`snapshot` / `subscribe` / `save_settings` / `drain_events` 等)→ `OpenLessBackend` → Core,类型化 Rust 接口,不经 IPC。
|
||||
- 浏览器预览:provider 公开目录由 Core 生成到 `src/lib/ipc/provider-descriptors.generated.json`(`cargo run --locked -p openless-core --example export_provider_descriptors` 重新生成;只含公开元数据,无凭据);原生端走同一受启动合同保护的 IPC。
|
||||
- 旧 React command/event 名称只保留在 Tauri 兼容 Adapter;跨平台合同以 `contract/backend-2.0.json` 为准。
|
||||
|
||||
事件以 `sequence` 去重,以 `sessionId` 归属;Unicode delta 的 offset 是 scalar 数量。QA 全量 messages 校准与 Agent 多轮重放仍遵循各自规则。具体语义的规范来源是 [Linux 事件与会话合同](linux-egui-handoff/06-events-and-sessions.md)及对应代码。
|
||||
## 3. Core 模块地图(按域,见 `src/lib.rs` pub mod 清单)
|
||||
|
||||
凭据状态、provider descriptor 与有效 pipeline 由 Core 提供。UI 不在渠道 ID 上猜认证规则,不回显已有密钥,不把“已配置”当成设备或网络测试成功。普通写入、已发送粘贴、已复制回退和未知结果分别反馈。
|
||||
- 听写链路:`dictation_engine` / `dictation_context` / `audio` / `external_audio` / `silence_auto_stop` / `streaming_insert` / `hotkey_interpreter` / `voice_session`
|
||||
- 服务与凭据:`provider_rules` / `provider_registry` / `provider_resolution` / `provider_service` / `provider_transport` / `cloud_providers` / `providers` / `omni` / `llm_gemini` / `credentials`(+`credentials_legacy`) / `endpoint_security` / `net`
|
||||
- 本地模型:`model_store` / `local_asr_service` / `local_asr_catalog` / `asr/`(云与本地 provider 实现)
|
||||
- 文本加工:`polish` / `prompt_compose`(+`prompts/`,`include_str!` 编译进二进制) / `output_cleaning` / `correction` / `vocabulary`
|
||||
- 知识与历史:`history` / `activity` / `style_packs` / `style_pack_store`(+`style_pack_archive`) / `marketplace`
|
||||
- 交互域:`qa_service` / `selection_service` / `selection_voice_service`(+`selection_voice_intent`) / `edit_plan` / `less_computer` / `coding_agent`(+`coding_agent_guard`) / `remote_input_service` / `auxiliary` / `cli`
|
||||
- 基座:`api` / `events` / `ports` / `settings` / `preferences` / `persistence` / `config` / `errors` / `types` / `shared_types` / `shortcut_types` / `domains` / `android_types` / `host_document/` / `testing` / `vendor/`
|
||||
|
||||
## 平台边界
|
||||
## 4. Host 注入点
|
||||
|
||||
当前完整支持范围、Linux 交接要求与发布验收统一指向 [2.0 当前范围](2.0-requirements.md)。Linux 有运行与界面起点,但 [缺口登记](linux-egui-handoff/02-gap-register.md)仍存在。React 的 Linux 外观预览不能替代 egui 产品验收。
|
||||
Core 不直接做平台动作;Host 通过 `ports.rs` 接口注入能力。注入清单以 `linux-egui/src/backend.rs` 的 builder 为准:`with_task_spawner` / `with_recorder` / `with_auxiliary_polisher` / `with_text_inserter` / `with_credential_store` / `with_services` / `with_host_actions` / `with_settings_runtime` / `with_local_asr_runtime` / `with_polish_failure_policy`。业务规则缺失时回报 Core 修复,不在 UI/Host 复制规则。
|
||||
|
||||
## 验证入口
|
||||
## 5. 窗口体系
|
||||
|
||||
`npm test` 会先构建 React,再跑前端与源码合同;其中 `check-hotkey-injection.mjs` 还会运行 Core 的实际快捷键测试。Core 全量验证使用 `cargo test -p openless-core --locked`;Linux 使用 `cargo test -p openless-linux-egui --locked`,原生目标验证遵循 [Linux 验收](linux-egui-handoff/07-acceptance.md)。所列命令不代表已执行;验证结论以当次运行输出为准。
|
||||
`tauri.conf.json` 声明 `main`、`capsule` 两个窗口;其余窗口由运行时按 `?window=` 种类创建(`src/App.tsx` 分支):`qa`、`selection` 追问、划词润色预览、语音意图选择、Less Computer(含 glow 变体)与移动端变体。Linux 单实例由 `linux-egui/src/single_instance.rs` 守护并转发启动意图。
|
||||
|
||||
## 6. 验证入口
|
||||
|
||||
在 `openless-all/app/`:`npm test`(构建 + 前端/合同测试,含 Core 快捷键回归);`cargo fmt --check`(三 crate);`cargo test -p openless-core --locked`;`cargo test -p openless-linux-egui --locked`。`src-tauri` 按平台独立构建,macOS 需 `git submodule update --init --recursive`(vendored qwen-asr C 引擎)。
|
||||
+19
-24
@@ -1,32 +1,27 @@
|
||||
# Alibaba Bailian ASR models
|
||||
# 阿里百炼(DashScope)ASR 模型
|
||||
|
||||
OpenLess uses one Alibaba Bailian provider and selects the DashScope protocol from the model name. Configure the API key, region or workspace endpoint, and model in Settings > Providers.
|
||||
状态:canonical(2026-09-07 以源码为准重写);更新:2026-09-07。
|
||||
|
||||
| Mode | Model examples | Behavior |
|
||||
| --- | --- | --- |
|
||||
| Realtime WebSocket | `fun-asr-realtime`, `fun-asr-flash-8k-realtime`, `paraformer-realtime-v2`, `sensevoice-realtime-v1` | Streams text while recording. OpenLess downsamples 16 kHz recorder audio for 8 kHz models. |
|
||||
| Qwen realtime | `qwen3-asr-flash-realtime`, versioned snapshots | Streams text through the Qwen Realtime WebSocket API. |
|
||||
| Synchronous recording | `fun-asr-flash-*`, `qwen3-asr-flash`, `qwen-audio-3.0-asr-flash` | Sends the recording after capture and waits for one synchronous response. These models are intended for short recordings (`qwen-audio-*-streaming` variants are not supported). |
|
||||
| Asynchronous file transcription | `fun-asr`, `fun-asr-mtl`, versioned snapshots, `paraformer-v2` | Uploads the recording, starts an asynchronous task, polls it, then downloads the transcript. |
|
||||
## 1. 代码中的定义
|
||||
|
||||
Future dated snapshots that retain one of these model prefixes are routed to the same protocol, so they can be entered manually before they are added to the model picker.
|
||||
- Provider:`bailian`,`authRequirement = api_key`;默认端点 `wss://dashscope.aliyuncs.com/api-ws/v1/inference/`;默认模型 `fun-asr-realtime`。
|
||||
- 协议按模型名选择(DashScope WebSocket 推理协议);验证探针 `asr_silence`。
|
||||
- 目录来源:`provider_rules::provider_descriptors` → 生成文件 `src/lib/ipc/provider-descriptors.generated.json`(重新生成:`cargo run --locked -p openless-core --example export_provider_descriptors`)。
|
||||
|
||||
## Endpoints
|
||||
## 2. 当前静态模型目录(14 个)
|
||||
|
||||
The default endpoint is `dashscope.aliyuncs.com`. A workspace or regional host may also be entered, for example `https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com`. OpenLess preserves the host and derives the protocol-specific path and scheme automatically.
|
||||
| 模型 | 说明 |
|
||||
| --- | --- |
|
||||
| `fun-asr-realtime`(默认) | Fun-ASR 实时转写 |
|
||||
| `fun-asr-flash-8k-realtime` | 8k 采样实时版 |
|
||||
| `fun-asr-flash-2026-06-15`、`fun-asr`、`fun-asr-2025-11-07`、`fun-asr-2025-08-25` | Fun-ASR 各版本 |
|
||||
| `fun-asr-mtl`、`fun-asr-mtl-2025-08-25` | 多语种(MTL) |
|
||||
| `qwen3-asr-flash-realtime`、`qwen3-asr-flash-realtime-2026-02-10`、`qwen3-asr-flash-realtime-2025-10-27`、`qwen3-asr-flash` | Qwen3 ASR 实时/闪速 |
|
||||
| `qwen-audio-3.0-asr-flash` | Qwen Audio 3.0 ASR |
|
||||
| `paraformer-v2` | Paraformer v2 |
|
||||
|
||||
API keys are region-specific. The endpoint and API key must belong to the same region.
|
||||
模型 ID 直选与目录以生成文件为准;新增模型先改 Core `provider_rules.rs` 再重新生成目录,不要手工编辑生成文件。
|
||||
|
||||
## Temporary uploads
|
||||
## 3. 在应用内配置
|
||||
|
||||
Asynchronous models require a URL. OpenLess obtains a temporary upload policy from DashScope, uploads the local WAV to the returned OSS host, and submits the resulting `oss://` URL with `X-DashScope-OssResourceResolve: enable`.
|
||||
|
||||
Alibaba documents this temporary storage as a development and low-concurrency facility. It is rate limited, and uploaded URLs expire after 48 hours. OpenLess uploads one file per completed recording; it does not retain or reuse the temporary URL.
|
||||
|
||||
For deployment or high-concurrency use, use stable Alibaba Cloud OSS storage and the official DashScope API directly.
|
||||
|
||||
## References
|
||||
|
||||
- [Fun-ASR non-real-time HTTP API](https://help.aliyun.com/en/model-studio/fun-asr-recorded-speech-recognition-http-api)
|
||||
- [Qwen-ASR API reference](https://help.aliyun.com/en/model-studio/qwen-asr-api-reference)
|
||||
- [DashScope temporary file upload](https://help.aliyun.com/en/model-studio/get-temporary-file-url)
|
||||
设置 → AI 服务 → 语音识别 → 添加渠道,选择百炼;填入 API Key(DashScope),选择模型;保存后执行“验证”。
|
||||
+30
-7
@@ -1,11 +1,34 @@
|
||||
# OpenLess 文档入口
|
||||
|
||||
状态:canonical;阶段:implementation-backed;更新:2026-09-07。
|
||||
状态:canonical;更新:2026-09-07。本目录 2026-09-07 以源代码为唯一依据重写(基于本地 `beta` `639c2fbf`);文档与代码冲突时,以代码为准修文档,不保留历史叙述。
|
||||
|
||||
- [Core 2.0 当前范围](2.0-requirements.md):Windows/macOS 交付边界与 Linux 移交责任,范围的规范来源。
|
||||
- [2.0 架构](architecture.md):共享 Core、平台 Host、界面与事件如何连接。
|
||||
- [桌面验收清单](2.0-desktop-acceptance.md):逐项功能保留和原生验收要求。
|
||||
- [Linux 接口契约](linux-egui-backend-contract.md):长接口与历史实现参考,范围冲突以下一条为准。
|
||||
- [Linux 接入交接](linux-egui-handoff/README.md):Linux 已有能力与剩余工作;从对应小文档继续阅读。
|
||||
## 范围与架构
|
||||
|
||||
已了结的规划、审计与复审过程文档已于 2026-09-07 清理;范围与完成口径一律以当前范围、架构与验收清单为准。
|
||||
- [2.0 范围](2.0-requirements.md):平台边界、版本锚点与完成条件。
|
||||
- [架构](architecture.md):Core / 平台 Host / 界面分层、模块地图与验证入口。
|
||||
- [桌面验收清单](2.0-desktop-acceptance.md):功能域 → 源码入口 → 必须的真实证据。
|
||||
|
||||
## Linux 接入交接(egui,目录内互链)
|
||||
|
||||
- [交接总览](linux-egui-handoff/README.md):阅读顺序、责任与复用起点。
|
||||
- [01 Core 合同](linux-egui-handoff/01-core-contract.md)、[02 缺口登记](linux-egui-handoff/02-gap-register.md)(L01–L12)、[03 热键与窗口](linux-egui-handoff/03-hotkeys-and-windows.md)、[04 页面与领域](linux-egui-handoff/04-ui-domains.md)、[05 原生宿主与数据](linux-egui-handoff/05-native-host-and-data.md)、[06 事件与会话](linux-egui-handoff/06-events-and-sessions.md)、[07 验收](linux-egui-handoff/07-acceptance.md)。
|
||||
|
||||
## 接口契约
|
||||
|
||||
- [Linux egui 后端契约](linux-egui-backend-contract.md):`contract/backend-2.0.json`、启动快照、事件面与公开签名。
|
||||
|
||||
## 平台与运营
|
||||
|
||||
- [Android APK / 悬浮窗计划](android-mobile-apk-overlay-plan.md)(实施中)
|
||||
- [火山引擎 ASR 配置](volcengine-setup.md)
|
||||
- [讯飞(iflytek)ASR 配置](xfyun-asr.md)
|
||||
- [百炼(DashScope)ASR 模型](bailian-asr-models.md)
|
||||
- [Tauri CSP 边界](tauri-csp.md)
|
||||
- [qwen-asr 子模块升级清单](qwen-asr-submodule-upgrade-checklist.md)
|
||||
|
||||
## 验证入口(在 `openless-all/app` 执行)
|
||||
|
||||
- `npm test`:构建 React + 全部前端/合同测试(含 Core 快捷键回归)。
|
||||
- `cargo fmt --check`:openless-core、linux-egui、src-tauri 三 crate。
|
||||
- `cargo test -p openless-core --locked`、`cargo test -p openless-linux-egui --locked`。
|
||||
- `src-tauri` 被 workspace exclude,按平台独立构建;macOS 构建需 `git submodule update --init --recursive`。
|
||||
@@ -1,778 +1,38 @@
|
||||
# Linux egui 后端接口契约(2.0.0)
|
||||
|
||||
> **2026-09-06更新**:接手请先读[Linux交接目录](./linux-egui-handoff/README.md)中的短文档;本文保留长接口与历史实现参考,不再作为唯一交接材料。
|
||||
> 当前范围以[2.0需求](./2.0-requirements.md)为准:Windows/macOS完整保留各自Tauri 1.x功能;Linux交付可接入Core。egui团队承接剩余Linux Host原生接线、UI、设备与发布验收,不仅负责绘制;Linux产品未完成不单独阻塞本批Windows/macOS交付。
|
||||
>
|
||||
> 2026-09-04 当前状态:2.0.0 facade、repository、Provider/Credential、听写 Pipeline、Local ASR
|
||||
> 原子激活、主热键仲裁、录音生命周期、上下文/手改观察、静默重试、插入 reconciliation、
|
||||
> Transcript reducer、Less Computer/Coding Agent 以及全部领域 Interface 均由 Core 持有。
|
||||
> Tauri 与 Linux 共用云 ASR/LLM/Omni/Auxiliary Implementation;平台 Host 只注入原生录音、
|
||||
> 凭据、窗口、进程、焦点和插入 Adapter。Linux production factory 已注入真实 QA、Remote Input、
|
||||
> Selection preview/revert 与打包的 Qwen runtime;AppImage 会在 listener 前安装 fcitx5 插件。
|
||||
> 自动 contract 不替代真实平台证据:Ubuntu/Windows/macOS/Android 的设备、安装、升级和签名
|
||||
> 结果仍须单独记录,当前责任与门槛按[桌面验收](./2.0-desktop-acceptance.md)和[Linux验收](./linux-egui-handoff/07-acceptance.md)区分;以下旧日期状态不能证明Linux无剩余缺口。
|
||||
>
|
||||
> 原生预加载必须使用请求的 target/provider type;平台无法准备流式时以 `supports_streaming=false`
|
||||
> 保留一次性落字。Remote stop 保持可取消的 session,socket 下行只转发本连接所属 session 的事件。
|
||||
状态:canonical(2026-09-07 以源码为准重写);更新:2026-09-07。范围以[2.0 需求](2.0-requirements.md)为准;本文是长接口与实现参考,交接材料以[交接目录](linux-egui-handoff/README.md)为准。
|
||||
|
||||
## 1. 依赖与职责
|
||||
## 1. 合同文件
|
||||
|
||||
Linux UI 只能依赖 `openless-linux-egui -> openless-core`。`openless-core` 的正常
|
||||
依赖树不得出现 `tauri`、`wry`、`webkit2gtk`、`egui` 或 `eframe`;Tauri 只能在
|
||||
`src-tauri` adapter 中出现。
|
||||
`openless-all/app/contract/backend-2.0.json`(`contractVersion` 2.0.0)顶层键:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
openless-core = { path = "../crates/openless-core" }
|
||||
```
|
||||
|
||||
核心拥有业务状态、会话、provider 选择、持久化和语义事件。Linux host 拥有
|
||||
Tokio runtime、窗口/托盘、fcitx5、麦克风、凭据和系统操作。egui frame 只读取
|
||||
快照、非阻塞地 drain 事件,并在事件到达后请求重绘。
|
||||
|
||||
## 2. 构造与生命周期
|
||||
|
||||
核心构造不创建窗口、不申请权限、不注册全局热键,也不启动网络任务。宿主按
|
||||
以下顺序初始化:
|
||||
|
||||
1. 解析 `data_dir`、`cache_dir`、资源目录、locale 和 `PlatformCapabilities`。
|
||||
2. 创建各 platform port,组装 `BackendDependencies`。
|
||||
3. 调用 `OpenLessBackend::new`。
|
||||
4. 创建事件订阅和 host action sink。
|
||||
5. 调用 `start()`,把返回的 `StartupSnapshot` 放入 UI view model。
|
||||
6. 只有启动成功后才启用热键、设备 watcher 和下载 watcher。
|
||||
|
||||
Linux 生产宿主不自行组装 provider,也不读取 credential account。以下生产 factory 必须在Host已有的Tokio runtime上下文中调用;同步GUI初始化用短作用域`runtime.enter()`包住构造,随后退出作用域再启动运行时任务。具体executor生命周期与自定义注入约定见[当前接入说明](./linux-egui-handoff/01-core-contract.md)。
|
||||
|
||||
```rust
|
||||
let runtime = LinuxBackendBuilder::from_shared_providers(config)?.build()?;
|
||||
let backend = Arc::clone(&runtime.backend);
|
||||
```
|
||||
|
||||
`LinuxBackendBuilder::new(config, transcription, polisher)` 仅供测试或特殊宿主显式替换 provider;
|
||||
egui UI 不应调用它,也不应注册 router 或解析 endpoint/model/extra headers/temperature。
|
||||
|
||||
关闭时先禁止新会话,取消活动任务,停止 host watcher,flush 必要持久化,停止
|
||||
事件桥,再调用幂等的 `shutdown()`。
|
||||
|
||||
```rust
|
||||
let backend = Arc::new(OpenLessBackend::new(config, dependencies)?);
|
||||
let mut events = backend.subscribe();
|
||||
let startup = backend.start().await?;
|
||||
let _session = backend.start_dictation().await?;
|
||||
let result = backend.stop_dictation().await?;
|
||||
backend.shutdown().await?;
|
||||
```
|
||||
|
||||
`start()` 和 `shutdown()` 可重复调用;重复启动不会重复发布
|
||||
`BackendStarted`,重复关闭不会 panic。`snapshot()` 是同步、无副作用的 owned
|
||||
读取,不返回内部锁或引用:
|
||||
|
||||
```rust
|
||||
let current: BackendSnapshot = backend.snapshot();
|
||||
```
|
||||
|
||||
## 3. 当前公开接口
|
||||
|
||||
### 3.1 Facade
|
||||
|
||||
| 方法 | 线程/等待 | 语义 |
|
||||
| --- | --- | --- |
|
||||
| `new(config, deps)` | 同步 | 校验配置并注入依赖;不启动副作用 |
|
||||
| `start()` | async | 初始化核心生命周期;幂等 |
|
||||
| `shutdown()` | async | 停止核心生命周期;幂等 |
|
||||
| `snapshot()` | 同步 | 返回可克隆快照 |
|
||||
| `subscribe()` | 同步 | 创建独立事件订阅 |
|
||||
| `start_dictation()` | async | 建立唯一活动 session,`Starting` 成功后进入 `Recording` |
|
||||
| `stop_dictation()` | async | `Transcribing -> Polishing -> Inserting -> Completed`,执行 ASR/润色和插入 |
|
||||
| `cancel_dictation(session)` | async | 取消指定或当前 session;session 不匹配时报错 |
|
||||
|
||||
同一时刻最多一个 dictation session。UI 不应通过按钮状态猜测可用性;调用仍需
|
||||
处理 `Busy`、`InvalidState` 和 `Cancelled`。
|
||||
|
||||
### 3.2 已建立的领域 Interface
|
||||
|
||||
数据领域直接由深 facade 提供,所有宿主共用同一份 repository 和规则:
|
||||
|
||||
| 分组 | 主要操作 |
|
||||
| 键 | 内容 |
|
||||
| --- | --- |
|
||||
| preferences | `get_preferences`;完整文档和单项更新统一通过 `LinuxHost` 的 validated settings Interface |
|
||||
| credentials/channels | status、显式 `SecretValue` read/write/remove、channel CRUD/reorder/test、active provider |
|
||||
| history/activity | list/recent/append/update/delete/clear、activity snapshot/bump |
|
||||
| vocabulary/correction | list/add/remove/enable/hits、preset、correction-rule lifecycle;`enabled_vocabulary_phrases()` 与 `asr_vocabulary_phrases()` 返回 Core 过滤/排序后的 owned 词条 |
|
||||
| style packs | list/get/create/update/activate/enable/reset/delete、安全 ZIP import/export;`preview_style_pack_runtime(style_pack)` 返回由 Core 统一组装的单轮/多轮 prompt 诊断 |
|
||||
| dictation | start/stop/cancel、snapshot、session-scoped progress 与插入终态 |
|
||||
| `startupSnapshot` | 启动快照结构与版本校验规则;UI 必须先消费快照再渲染 |
|
||||
| `backendEvent` | 语义事件清单、顺序与重放规则 |
|
||||
| `lessComputerVoice` | Less Computer 语音事件面 |
|
||||
| `androidJni` | Android JNI 合同(src-tauri android 桥接共用) |
|
||||
| `linuxFacade` | Linux 专用 facade 面(`LinuxHost` 公开方法对应) |
|
||||
| `enums` | 共享枚举(provider 类型、状态、错误等) |
|
||||
|
||||
有平台、网络、进程或 runtime 变化点的复杂领域通过
|
||||
`OpenLessBackend::services() -> &BackendServices` 暴露:
|
||||
## 2. Linux 侧公开签名(源码为准)
|
||||
|
||||
| Interface | 已冻结的 use-case |
|
||||
| --- | --- |
|
||||
| `ProviderApi` | validate、list models,按 ASR/LLM/Omni 和可选 channel 选择 |
|
||||
| `LocalAsrApi` | settings/catalog/status/remote info、目录/模型/镜像/keep-loaded 设置、download/prepare/preload/release/delete/test |
|
||||
| `ModelStore` | Core 统一 catalog、HF tree 分页、文件过滤、Range/断点下载、SHA-256、staging/ready sentinel、归档解压与 1.x 模型目录迁移 |
|
||||
| `SelectionApi` | snapshot、begin polish、confirm、cancel、revert |
|
||||
| `SelectionVoiceApi` | begin/mark processing、Core-owned transcript 处理、intent confirm、edit delivery、QA preview create/revise、preview apply ticket/finish、cancel/revert |
|
||||
| `QaApi` | snapshot、toggle recording、按每轮token stop_recording、submit text、edit-instruction mode、session cancel、dismiss |
|
||||
| `LessComputerApi` | submit、cancel、dismiss、begin turn、approval decision;Core 统一 provider/model/permission/workdir/prompt/guard/continuation |
|
||||
| `RemoteInputApi` | 同步 status、configure、显式读取/重新生成 pairing PIN、locale、local IPs、connect/disconnect、start/feed/stop/cancel stream |
|
||||
| `MarketplaceApi` | list/detail/install/download/upload/like/delete、my lists、GitHub device flow、logout |
|
||||
| `CodingAgentApi` | detect/list models/risk/run/cancel/approve |
|
||||
| `PlatformApi` | microphone devices/permission、accessibility permission、permission request、hotkey status |
|
||||
| `AuxiliaryApi` | 对既有文本执行单轮 repolish;对宿主提供的规范 PCM 执行单轮 retranscription |
|
||||
- `linux-egui/src/lib.rs`:`pub struct LinuxHost`;`LinuxHost::new(Arc<OpenLessBackend>)`、`with_settings_runtime`、`backend()`、`subscribe() -> EventSubscription`、`snapshot() -> BackendSnapshot`、`save_settings(...)`、`update_settings_strict(...)`、`drain_events(...)`、`feed_less_computer_pcm(&[u8])`。
|
||||
- `linux-egui/src/backend.rs`:`LinuxBackendRuntime`;`LinuxBackendBuilder::from_shared_providers(BackendConfig)` + `with_task_spawner / with_recorder / with_auxiliary_polisher / with_text_inserter / with_credential_store / with_services / with_host_actions / with_settings_runtime / with_local_asr_runtime / with_polish_failure_policy` → `build() -> LinuxBackendRuntime`。
|
||||
- 事件消费:`drain_events`(`lib.rs`)批量取走 Core 语义事件;订阅经 `EventSubscription`。
|
||||
|
||||
`BackendServices::unsupported()` 是正式的降级 Adapter:每个调用返回
|
||||
`BackendErrorCode::Unsupported`,不会启动 task 或返回空成功。egui crate 不得直接 include
|
||||
`src-tauri/src/*.rs`,也不得为了暂时可用而复制业务逻辑。
|
||||
## 3. 注入接口(`crates/openless-core/src/ports.rs`)
|
||||
|
||||
`preview_style_pack_runtime(style_pack)` 是同步、无 I/O 的 Core 查询。它读取当前偏好和已启用
|
||||
词典,使用与生产润色路径相同的 prompt composer,返回 `StylePackRuntimeDiagnostics`(包括
|
||||
单轮/多轮 prompt、上下文 premise、热词块及字符数)。宿主只渲染返回 DTO;不得在 Tauri 或
|
||||
egui 中重新拼接 prompt、过滤热词或推导字符数。
|
||||
`AudioRecorder`、`TextPolisher`、`TextInserter`、`CredentialStore`、`SettingsRuntime`、`TaskSpawner`、`EditObservationSink`/`EditObservationAdapter`(默认 `NoopEditObservationAdapter`)、`LinuxHostActions`。缺省实现表示"未接线",不是"不支持"。
|
||||
|
||||
### 3.3 `ProviderApi`
|
||||
## 4. 与桌面共享
|
||||
|
||||
`ProviderApi::validate` 与 `list_models` 由 Core `ProviderService` 统一实现。Tauri
|
||||
`commands/providers.rs` 只负责 `kind` 字符串解析、请求构造和旧错误字符串转换;Linux
|
||||
生产入口 `LinuxBackendBuilder::from_shared_providers` 注入同一 service,egui 不读取凭据或
|
||||
构造 HTTP/WS client。
|
||||
- 云 ASR/LLM/Omni/Auxiliary 实现两端共用(Core `asr/`、`provider_*`、`omni`、`llm_gemini`);平台 Host 只注入原生录音、凭据、窗口、进程、焦点与插入 Adapter。
|
||||
- 旧 React command/event 名称只保留在 Tauri 兼容 Adapter;Linux 与 Core 同进程,经类型化 Rust Interface 调用。
|
||||
- provider 公开目录:Core `provider_rules::provider_descriptors` → 生成 `src/lib/ipc/provider-descriptors.generated.json`(`cargo run --locked -p openless-core --example export_provider_descriptors`)。
|
||||
|
||||
- `ProviderRequest { kind, channel_id }`:`channel_id = None` 使用该类别的 active provider;显式
|
||||
channel 必须存在于 metadata。Omni 不接受 channel id,返回 `InvalidArgument`。
|
||||
- channel 的 `provider_type` 来自 credential metadata,不能用 channel id 猜协议;凭据通过
|
||||
`CredentialKey { namespace, provider_id, account }` 读取,A/B channel 不会串线。
|
||||
- `validate` 使用与正式 ASR/LLM/Omni 调用相同的 Core provider 构造路径;静音探活不保存用户音频。
|
||||
- `list_models` 对 Codex、Bailian、Qwen、Mimo、ElevenLabs 等无远端列表接口返回 Core 静态清单;
|
||||
OpenAI-compatible 与 Gemini 使用受大小上限约束的远端列表响应,并校验 JSON schema。
|
||||
- 参数/模型缺失返回 `InvalidArgument`,凭据或网络/HTTP/WS 失败返回 `Provider`,超时/连接失败
|
||||
标记 `retryable`,取消返回 `Cancelled`,native/未知 provider 返回 `Unsupported`。
|
||||
- 模型响应最大 2 MiB;状态码只以 `providerHttpStatus:<code>` 形式返回。API key、token、Authorization、
|
||||
endpoint credential 和响应 body 不进入 DTO、错误详情、日志或 `Debug`。
|
||||
## 5. 行为约定(合同级)
|
||||
|
||||
Core、Tauri wire 和 Linux factory contract 覆盖 channel 隔离、Omni 拒绝、静态/远端模型解析、
|
||||
错误脱敏和非 `Unsupported` 生产接线。未注入的其他领域仍稳定返回 `Unsupported`。
|
||||
|
||||
当前 provider 管理面已可供 egui 调用。模型列表请求通过 Core `ProviderTransport` seam;生产实现使用
|
||||
无 redirect、显式 timeout 和 2 MiB 响应上限,`FakeProviderTransport` 已覆盖 HTTP 状态、超时、取消、
|
||||
无效 JSON、响应过大和 redirect 拒绝,并验证秘密不会出现在请求 `Debug`、错误或 fixture 输出。Core
|
||||
静态模型清单已按迁移前 Tauri 顺序完成 provider parity 与去重测试;LLM extra headers/temperature
|
||||
写入已按显式 `provider_id` 定位并有 A/B channel 回归测试。上述 Interface 收口升级为 2.0.0 公共调用面,
|
||||
但真实 provider 网络、keyring/Secret Service、取消/超时和各平台 runner 仍需在 M9/M10 留下独立证据,
|
||||
不能用 fake、WSL 或本地 parser/unit 测试代替。
|
||||
|
||||
Less Computer 文字入口可直接提交;语音/物理热键入口必须先建立 Core capture lease,
|
||||
再把同一个 session id 贯穿宿主录音、取消和提交:
|
||||
|
||||
```rust
|
||||
let result = backend.submit_less_computer("列出当前目录的文件".to_string()).await?;
|
||||
|
||||
let session_id = SessionId::new();
|
||||
backend.begin_less_computer_capture(session_id)?;
|
||||
// Host starts its recorder/native ASR. Startup failure must release the lease:
|
||||
// backend.abort_less_computer_capture(session_id)?;
|
||||
|
||||
// 录音热键完成转写后,使用同一 session 以便 Esc/release 精确取消:
|
||||
let result = backend
|
||||
.submit_less_computer_with_session(session_id, transcript)
|
||||
.await?;
|
||||
|
||||
// Esc/cancel must signal Core before the host drops recorder/ASR resources.
|
||||
backend.cancel_less_computer(Some(session_id)).await?;
|
||||
// If submission has not promoted the lease to a run, release the capture lease:
|
||||
backend.abort_less_computer_capture(session_id)?;
|
||||
```
|
||||
|
||||
两个 submit 方法都只接受用户文本;provider、executable、model、permission mode、workdir、
|
||||
autonomous prompt、命令护栏、审批重跑和 dsh continuation 均由 Core 从 preferences 与实例状态
|
||||
解析。`begin_less_computer_capture` 是实例级、session-scoped 的互斥 lease;重复 reserve 返回
|
||||
`Busy`,`less_computer_active_session` 可用于重连/诊断,`less_computer_capture_cancelled` 在
|
||||
宿主释放 capture lease 前保持可见。`abort_less_computer_capture` 只释放仍处于 capture 阶段的
|
||||
lease,对已提升为 Agent run 的 session 是幂等 no-op。`LessComputerRuntimeAdapter` 只负责宿主
|
||||
进程/Git/临时文件/stream transport;未注入 runtime 时返回 `Unsupported`。同一实例同时提交
|
||||
第二轮返回 `Busy`,不能覆盖当前运行。
|
||||
|
||||
返回的 `LessComputerRunResult` 只表达该轮唯一终态:
|
||||
`Completed { text, cost_usd }`、`Failed { message }` 或 `Cancelled`。UI 不应根据 delta、窗口关闭
|
||||
或进程退出自行猜测终态;必须等待对应的 `LessComputerEvent`。
|
||||
|
||||
Linux 生产 factory 会注入 Core `MarketplaceApi`。需要把归档保存到用户文件系统时,UI 调用
|
||||
`LinuxHost::download_marketplace_archive(pack_id, target)`:Core 负责 HTTP/OAuth/大小限制与 ZIP
|
||||
校验,Linux host 只接受已有父目录下的绝对路径,以 create-new 方式写入并拒绝覆盖;写入或
|
||||
`sync_all` 失败时删除不完整文件。UI 不处理 bearer token,也不自行重复校验归档。
|
||||
|
||||
### 3.3 `AuxiliaryApi`
|
||||
|
||||
`AuxiliaryApi` 是“重新润色”和“从既有录音重新转写”的共享 use-case,不拥有文件选择、
|
||||
WAV 解码、窗口或进度 UI:宿主先把输入转换为契约数据,再调用 Core。
|
||||
|
||||
- `repolish(RepolishRequest)` 接收 owned `raw_text`、可选 `style_pack_id` 和可选
|
||||
`front_app`。Core 在调用开始时冻结 preferences、指定或当前 style pack、启用词典以及
|
||||
LLM/Omni provider;它不解析 ASR provider。该方法只执行一轮,不写 history/activity、
|
||||
不插入文本、不改变 active style pack,也不发流式 delta。当前模式不使用 polisher 时,
|
||||
原文原样返回。
|
||||
- `retranscribe_pcm(Vec<u8>)` 只接受非空、偶数字节的 16 kHz mono signed Int16
|
||||
little-endian PCM;文件读取、WAV header 去除和格式转换归宿主。Core 只冻结 ASR provider,
|
||||
不要求 LLM/Omni 凭据,且把全部 PCM 精确送入一个 `TranscriptionSession` 后只 finalize 一次。
|
||||
成功返回 `RetranscriptionResult { text, duration_ms, asr }`;`asr` 是 Adapter 实际使用的
|
||||
provider/model(包含默认模型解析后的值),不是 UI 提交值。
|
||||
- 失败返回 `RetranscriptionFailure { error, attempted_asr }`。ASR session 尚未建立时
|
||||
`attempted_asr` 可为 `None`;建立后必须携带实际归因。Foundry 的 terminal fallback 使用
|
||||
`details.terminal = "foundry_fallback"`,`is_terminal() == true` 且 `retryable == false`;
|
||||
其他 transcription 启动/finalize 失败统一标为可重试。
|
||||
- 如果调用方 future 在 finalize 完成前被丢弃,Core 的取消 guard 通过宿主注入的
|
||||
`TaskSpawner` 调用该 session 的幂等 `cancel()`;宿主不能对同一 PCM 自动启动第二次转写。
|
||||
|
||||
Linux 生产 factory 已注入共享 Auxiliary polisher 与 transcription router;它与 Tauri 使用同一套
|
||||
provider 选择、凭据路由、默认值、取消和错误语义。native/local ASR 仍需 Linux 对应 runtime;
|
||||
未注册的 native/local provider 稳定返回 `Unsupported`,不能把 headless fixture 当作真实能力。
|
||||
|
||||
### 3.4 `SelectionVoiceApi`
|
||||
|
||||
Selection Voice 的业务入口是高层 use-case,不是让宿主拼装 prompt 或 EditPlan 的工具箱:
|
||||
|
||||
- 宿主完成平台录音/ASR 后,先调用 `mark_processing(session_id)`,再把未加工的 transcript 交给
|
||||
`process_transcript(session_id, transcript)`。Core 依次执行 correction rules、指令润色、按冻结偏好
|
||||
选择 manual/heuristic/prompt/auto intent;auto 模型失败时只由 Core 回退 heuristic。宿主不得预先
|
||||
润色、分类或传入模型分类结果。
|
||||
- `SelectionVoiceDisposition::Question` 只要求宿主打开 QA surface、提交 Core 返回的 instruction,
|
||||
成功后调用 `complete`;`AwaitingIntent` 只要求显示 prompt 并把用户选择交给 `confirm_intent`。
|
||||
- `SelectionVoiceDisposition::Edit` 后调用 `prepare_edit(session_id, owner)`。Core 根据
|
||||
`selection_polish_output_mode` 返回 `SelectionVoiceEditAction::OpenConversation`,或在内部完成
|
||||
translation target 推断、provider 调用、EditPlan 解析/应用后返回 `ReadyToApply { preview }`。
|
||||
- QA 编辑模式调用 `edit_preview(SelectionVoiceEditRequest)`。首次调用建立或复用匹配的 Core session;
|
||||
后续调用以当前 preview 为 draft,只保留一步 revert。`replaced_existing` 是 QA 按钮状态的唯一真相,
|
||||
`answer_text()` 是稳定的 assistant message 投影;Adapter 不应重复格式化 summary。
|
||||
- 真正替换文本仍是双阶段握手:Core `begin_preview_apply` 返回 ticket,平台 Adapter 用 opaque target
|
||||
校验/插入,再以 `finish_preview_apply(ticket_id, outcome)` 回报。只有 `Inserted` 或
|
||||
`CopiedFallback` 才会消费 preview 并写 history/activity;`Failed` 保留 preview。宿主无法确认
|
||||
插入结果时必须返回错误,且不得自动重试。
|
||||
|
||||
`resolve_instruction`、`set_preview` 和 `replace_preview` 是 compatibility/headless 测试原语,不是新 UI
|
||||
工作流入口。平台仍拥有麦克风、ASR native handle、窗口/热键、焦点恢复和 opaque insertion target;
|
||||
correction、prompt、intent、EditPlan、translation 和 output-mode 判断不得进入 Tauri 或 egui。
|
||||
|
||||
### 3.5 设置事务
|
||||
|
||||
egui view model 必须从同一份快照取得偏好和 revision,再通过 `LinuxHost` 提交完整文档:
|
||||
|
||||
```rust
|
||||
let snapshot = host.snapshot();
|
||||
let outcome = host.update_settings_strict(
|
||||
preferences,
|
||||
snapshot.preferences_revision,
|
||||
)?;
|
||||
```
|
||||
|
||||
公开入口只有两种产品语义:
|
||||
|
||||
| 方法 | 冲突策略 | style 字段 | 适用场景 |
|
||||
| --- | --- | --- | --- |
|
||||
| `save_settings(preferences, revision)` | `Reconcile`:按 Core 固定优先级恢复旧值或停用低优先级键 | 保留当前值,防止陈旧整表覆盖刚发生的 style 切换 | 设置页整表保存 |
|
||||
| `update_settings_strict(preferences, revision)` | `Reject`:任何冲突返回 `InvalidArgument` | 使用提交值 | 单项快捷键或明确的聚焦更新 |
|
||||
|
||||
Core 在单写入 gate 内完成 legacy 字段同步、style-pack 对齐、冲突校验/协调和 typed effect plan,
|
||||
再按 `prepare -> commit effects -> persist once -> publish once` 执行。Linux Adapter 只消费计划中的
|
||||
显式目标,不读取或修改 `UserPreferences`;失败时按 typed receipt 逆序恢复已应用的 fcitx5/凭据
|
||||
副作用。成功只增加一次 `preferences_revision` 并发布一次 `PreferencesChanged`。
|
||||
|
||||
revision 不匹配时返回 `BackendErrorCode::Busy`、`retryable = true`,`details` 包含
|
||||
`expectedPreferencesRevision` 和 `actualPreferencesRevision`。UI 必须重新读取 snapshot/偏好、合并
|
||||
用户仍想保留的编辑后再提交;不得无条件重放陈旧整表。
|
||||
|
||||
Linux settings Adapter 当前支持 dictation、QA、Selection Polish、translation、Coding Agent 的 fcitx5 热键和
|
||||
active ASR provider metadata;启动时同步保存的热键,禁用 Coding Agent 时解绑其语音键。修改 switch-style、open-app、style-pack hotkey 或
|
||||
Windows keyboard effect 会稳定返回 `Unsupported`,且不写偏好、不增加 revision、不发布事件。
|
||||
UI 不得直接调用 `OpenLessBackend::set_preferences*`;这些低层兼容方法不属于 Linux UI Interface,
|
||||
也不能绕过 `LinuxHost` 的 revision、冲突和补偿契约。
|
||||
|
||||
## 4. DTO 字段契约
|
||||
|
||||
### 4.1 `DictationStateSnapshot`
|
||||
|
||||
| 字段 | 类型 | 规则 |
|
||||
| --- | --- | --- |
|
||||
| `phase` | `DictationPhase` | `idle/starting/recording/transcribing/polishing/inserting/completed/cancelled/failed` |
|
||||
| `sessionId` | `SessionId?` | 非 `idle` 时存在;用于丢弃晚到结果 |
|
||||
| `elapsedMs` | `u64` | 毫秒,不在 UI 侧换算成秒后再回写 |
|
||||
| `level` | `f32` | 规范化到 `0..=1`;无音频时为 `0` |
|
||||
| `message` | `String?` | 非敏感、用户可读提示;不可放 token/PIN |
|
||||
| `translationActive` | `bool` | 会话开始时冻结;用于两个宿主显示当前会话的翻译状态 |
|
||||
|
||||
阶段是后端事实,不由 UI 猜测:
|
||||
|
||||
| phase | 含义 | egui 操作规则 |
|
||||
| --- | --- | --- |
|
||||
| `idle` | 无活动 session | 允许开始 |
|
||||
| `starting` | engine/录音资源正在启动 | 显示准备态;允许取消,不允许重复开始/停止 |
|
||||
| `recording` | 正在采集音频 | 允许停止或取消 |
|
||||
| `transcribing` | ASR 正在处理/输出增量 | 禁用重复停止;可显示 `TranscriptDelta` |
|
||||
| `polishing` | LLM/规则润色正在处理 | 可显示 `PolishDelta` |
|
||||
| `inserting` | 已提交文字插入请求 | 不自动重试;等待明确/fallback/unknown 结果 |
|
||||
| `completed` | 本 session 唯一成功终态 | 读取 `DictationResult` 后清理本地 session |
|
||||
| `cancelled` | 用户、关闭或 generation guard 取消 | 丢弃该 session 后续增量 |
|
||||
| `failed` | 本 session 失败 | 按 `BackendErrorCode` 提供重试或用户操作 |
|
||||
|
||||
### 4.2 `DictationResult`
|
||||
|
||||
`sessionId`、`rawText`、`polishedText` 和 `inserted` 均为 owned 字段。公开 Rust 类型
|
||||
`DictationInsertStatus` 是 `InsertStatus` 的稳定契约别名;`inserted` 的 serde 值为
|
||||
`inserted`、`copiedFallback` 或 `unknown`。`unknown` 表示插入请求超时后结果不可证明,
|
||||
UI 必须显示待确认状态,不得自动重试以免重复输入。
|
||||
|
||||
### 4.3 能力与秘密
|
||||
|
||||
`PlatformCapabilities` 只描述能力布尔值和平台标识。`CredentialsStatus` 只包含
|
||||
已配置的 provider id;凭据值、Authorization header、PIN 和完整请求永不进入
|
||||
快照、事件、错误 details 或日志。
|
||||
|
||||
`PreferencesChanged` 事件只携带单调递增的 `revision`,UI 收到后重新调用设置
|
||||
查询;不要把任意 JSON 或秘密塞进事件 payload。
|
||||
|
||||
所有 DTO 使用 Rust 的 `serde` 定义;与 React IPC 的兼容字段由 Tauri adapter
|
||||
显式转换,不能让 egui 依赖 React 字段名。
|
||||
|
||||
### 4.4 `QaSnapshot` 与 Remote Input
|
||||
|
||||
`QaSnapshot` 是 QA view model 的唯一状态来源:
|
||||
|
||||
| 字段 | 类型 | 规则 |
|
||||
| --- | --- | --- |
|
||||
| `phase` | `QaPhase` | `idle/recording/thinking/awaiting_approval/completed/cancelled/failed` |
|
||||
| `sessionId` | `SessionId?` | 每轮 turn 的 generation token;每个成功 follow-up turn 都分配新 ID,所有 progress、cancel 和迟到结果 guard 均绑定此 ID |
|
||||
| `conversationId` | `SessionId?` | 同一面板成功多轮间稳定的 Selection Voice preview owner;仅在 dismiss/clear 后清空,不用于接受上一轮迟到结果 |
|
||||
| `messages` | `QaMessage[]` | 有序 user/assistant message log;UI 不自行补写 provider 结果 |
|
||||
| `editInstructionMode` | `bool` | 只能在非活动 turn 修改;活动 turn 修改返回 `Busy` |
|
||||
| `pendingApprovalToken` | `String?` | 只用于显示和提交明确审批;不得当作跨 session 全局 token |
|
||||
| `lastError` | `String?` | 已脱敏的公开错误;不得包含 provider body、Authorization 或选择全文 |
|
||||
|
||||
`QaLevel` 规范化为 `0..=1`;`AnswerDelta` 只对匹配当前 `sessionId` 且处于
|
||||
`thinking/awaiting_approval` 的 turn 有效。成功 turn 保留 `conversationId`,`sessionId` 继续标识
|
||||
该轮终态;下一轮开始时必须替换为新的 generation token,因此上一轮迟到 delta 不能污染 follow-up。
|
||||
`dismiss()` 幂等地取消活动 runtime、清空 snapshot、清理与 `conversationId` 匹配的 Selection
|
||||
Voice preview 并请求宿主隐藏面板;窗口 focus、NSPanel 和键盘仲裁不是 `QaSnapshot` 字段。
|
||||
|
||||
`LessComputerEvent` 是 Coding Agent 对话的唯一 UI 事件源:
|
||||
|
||||
| `kind` | 字段 | 规则 |
|
||||
| --- | --- | --- |
|
||||
| `voice_state` | `sessionId`, `phase`, `level`, `elapsedMs` | Core语音快照;phase为`starting/recording/transcribing/idle`,按原`seq`去重;旧session终态不得覆盖新录音 |
|
||||
| `user` | `text`, `fresh` | Core 接受输入后发布;`fresh=true` 表示 dismiss 后的新会话 |
|
||||
| `started` | — | runtime 已启动 |
|
||||
| `delta` | `text` | 增量输出,按事件 `seq` 去重后追加 |
|
||||
| `tool` | `name` | 仅展示工具活动;UI 不执行工具 |
|
||||
| `compaction` | — | provider 正在压缩上下文,可作为非阻塞提示 |
|
||||
| `approval` | `token`, `command`, `reason` | 只展示脱敏 command/reason,并把 token 原样回传 `approve`;不得写日志/持久化 |
|
||||
| `completed` | `text`, `costUsd?` | 唯一成功终态 |
|
||||
| `error` | `message` | 唯一失败终态,message 已脱敏 |
|
||||
| `cancelled` | — | 唯一取消终态 |
|
||||
|
||||
Core 维护实例级 conversation flag、最多两轮 dsh continuation、approval token registry 和
|
||||
90 秒 approval timeout。`dismiss()` 会取消当前 runtime、拒绝所有 pending approval、清空
|
||||
continuation,并使下一轮 `user.fresh=true`。egui 只保存渲染所需的派生消息,不复制上述状态机。
|
||||
|
||||
语音显示还可从`backend.event_publisher().latest_less_computer_voice_state()`读取最后一条有效投影,保留原session/seq且占用固定一条空间。Tauri既有`less_computer_sync`返回可选`voiceState`,即使长转写的阶段事件已被2048条replay驱逐,重开也能恢复;该投影不推进聊天事件水位。合同版本仍为`2.0.0`,没有新增IPC入口。
|
||||
|
||||
`RemoteInputStatus` 字段和规则如下:
|
||||
|
||||
| 字段 | 类型 | 规则 |
|
||||
| --- | --- | --- |
|
||||
| `enabled` / `running` | `bool` | 前者是期望配置,后者是真实 transport 状态;两者不能互相替代 |
|
||||
| `port` | `u16` | `1..=65535`;端口变化由 `configure` 串行 stop/restart |
|
||||
| `urls` | `String[]` | 仅在 transport 成功绑定后存在 |
|
||||
| `locale` | `String` | 仅接受 `zh-CN/zh-TW/en/ja/ko` |
|
||||
| `connectionCount` | `usize` | 当前认证连接数,不包含已断开的历史连接 |
|
||||
| `activeSessionId` | `SessionId?` | 任一连接正在推流时存在;仅作状态展示,不代替 connection/session 校验 |
|
||||
|
||||
pairing PIN 只能通过 `read_pairing_pin()` 的 `SecretValue` 显式读取;不得加入
|
||||
`RemoteInputStatus`、事件、错误、`Debug` 或普通日志。PCM frame 必须为非空、偶数字节、最多
|
||||
65536 bytes 的 signed Int16 little-endian;音频格式固定 16 kHz mono。每个连接最多一个活动
|
||||
stream,重复 start 返回 `Busy` 且不得覆盖原 session lease。stop/cancel/disconnect 后的 frame
|
||||
返回 `Cancelled`,宿主不得自动新建 session 重放。transport restart 必须先取消旧 stream、
|
||||
使旧 connection/session lease 失效;旧 lease 再次 start/feed/stop 时稳定返回 `Cancelled`。
|
||||
|
||||
## 5. 事件契约
|
||||
|
||||
M0 的可机器读取基线位于
|
||||
[`linux-egui-command-event-baseline.json`](./linux-egui-command-event-baseline.json)。该文件
|
||||
记录从当前 Tauri handler、React `listen()` 和 Rust `emit*()` 调用点观察到的 command/event
|
||||
名称;迁移期间新增或删除名称必须先更新该基线,再更新对应 adapter 和兼容测试。
|
||||
|
||||
`BackendEvent` 包含 `sequence: u64`、可选 `session_id` 和 `BackendEventKind`。
|
||||
每个 backend 实例的 sequence 从 1 开始严格递增;事件总线是通知而不是状态
|
||||
真相。backend 同时保留最近 2048 条实例级 backlog;`replay_events_after(sequence)` 返回
|
||||
`events/oldestSequence/latestSequence/truncated`。`truncated=true` 表示调用方的游标早于当前
|
||||
backlog,必须先重新读取 facade/领域 snapshot,再从最新 sequence 续订。
|
||||
|
||||
Less Computer 的 Tauri compatibility UI 使用同一 replay 水位:mount 时先安装实时 listener,
|
||||
再以 `afterSequence` 拉取 replay;同步窗口内到达的事件先进入 pending 队列,随后按
|
||||
`replay.events -> pending` 顺序合并。带 `seq` 的事件按最大已应用 sequence 去重,无 `seq` 的
|
||||
legacy fallback 仍需保留。`truncated=true` 时旧派生时间线不再可信,调用方先清空本地时间线,
|
||||
把水位重置为 `oldestSequence - 1`,从本次保留的 replay 重新构建,再接续 pending;最终水位
|
||||
至少推进到 `latestSequence`。egui 不需要复用 React helper,但必须实现相同的顺序与去重语义。
|
||||
|
||||
| 事件 | UI 处理 |
|
||||
| --- | --- |
|
||||
| `BackendStarted` / `BackendStopping` | 更新宿主生命周期状态 |
|
||||
| `DictationStateChanged` | 替换录音/处理/终态和 level |
|
||||
| `TranscriptDelta` | 按 session 和 offset 合并原文增量 |
|
||||
| `PolishDelta` | 按 session 和 offset 合并润色增量 |
|
||||
| `DictationCompleted` | 展示结果并根据 `inserted` 显示确认/回退提示 |
|
||||
| `InsertFallback` | 显示 clipboard/fallback 状态,不重试插入 |
|
||||
| `PreferencesChanged` | 使设置缓存失效并重新读取 |
|
||||
| `CredentialsChanged` | 更新 provider 配置状态,不显示秘密 |
|
||||
| `HistoryChanged` / `VocabularyChanged` / `StylePacksChanged` | 使列表缓存失效 |
|
||||
| `DownloadProgress` | 更新模型下载进度 |
|
||||
| `PermissionChanged` / `HotkeyStatusChanged` | 更新能力和降级文案 |
|
||||
| `Notification` | 放入非阻塞通知队列 |
|
||||
| `CodingAgentTest` | 按 `session_id` 消费 started/delta/tool/completed/error/cancelled 流 |
|
||||
| `LessComputerEvent` | 按 payload `seq` 构建会话时间线;fresh user 清空旧会话 |
|
||||
| `LocalAsrPrepareProgress` / `LocalAsrDownloadProgress` | 按 runtime/model 更新本地模型准备与下载状态 |
|
||||
| `LocalAsrEngineChanged` | 替换当前本地 ASR runtime 快照 |
|
||||
| `MicrophoneDevicesChanged` | 使设备列表失效并重新调用 `PlatformApi::microphone_devices()` |
|
||||
| `QaLevel` / `QaState` | 按 QA session 更新录音电平、阶段、增量和消息列表 |
|
||||
| `RemoteInputStatusChanged` / `RemoteInputFailed` | 更新远程输入状态/错误;事件永不携带 pairing PIN |
|
||||
| `VocabularySuggestionsChanged` | 替换当前待确认纠正建议 |
|
||||
|
||||
当前 29 个 `BackendEventKind` 已由机器基线和 serde fixture 校验。30 个旧 Tauri event 均已
|
||||
分类;12 个原 `migrationRequired` 事件现已通过 typed core event 进入同一事件总线,再由
|
||||
`tauri_events.rs` 映射为旧 React 名称。复杂领域后续迁移只能替换事件生产者的
|
||||
Implementation,不能重新建立 host-only 状态流。
|
||||
|
||||
订阅可能返回 `EventRecvError::Lagged(n)`。收到该错误时,UI 必须丢弃本地增量,
|
||||
重新读取 `snapshot()` 或对应领域查询,然后继续订阅;不能静默使用旧状态。
|
||||
`Closed` 表示 backend 已被销毁。终态事件只发布一次,旧 session 的任何晚到
|
||||
事件都必须被 session id guard 丢弃。
|
||||
|
||||
egui frame 不得 `.block_on()`。`EventSubscription` 同时提供异步 `recv()` 和非阻塞
|
||||
`try_recv()`;frame 只应使用后者:
|
||||
|
||||
```text
|
||||
后台订阅任务 -> 有界 UI channel -> frame 每次非阻塞 drain
|
||||
-> 更新 view model
|
||||
-> request_repaint()
|
||||
```
|
||||
|
||||
最小的非阻塞循环如下(`Empty` 只表示本帧没有更多事件,`Lagged` 必须触发快照重同步):
|
||||
|
||||
```rust
|
||||
loop {
|
||||
match events.try_recv() {
|
||||
Ok(event) => view_model.apply(event),
|
||||
Err(EventRecvError::Empty) => break,
|
||||
Err(EventRecvError::Lagged(_)) => {
|
||||
view_model.replace_from_snapshot(backend.snapshot());
|
||||
break;
|
||||
}
|
||||
Err(EventRecvError::Closed) => view_model.mark_backend_closed(),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`openless-linux-egui` 另提供 `drain_events()`,把上述循环收敛成
|
||||
`EventDrainOutcome::{Idle, Lagged, Closed}`;它不引用 egui 类型,因此 UI 组可在
|
||||
`eframe::App::update` 中直接调用,只有 `Lagged` 时才回读 `LinuxHost::snapshot()`。
|
||||
|
||||
## 6. 错误与取消
|
||||
|
||||
核心返回 `BackendError { code, message, retryable, details }`。UI 只判断 code,
|
||||
不解析 message。
|
||||
|
||||
| code | 典型情况 | UI 行为 |
|
||||
| --- | --- | --- |
|
||||
| `invalid_argument` | 参数或 session id 不合法 | 修正输入;不重试原请求 |
|
||||
| `invalid_state` | 未启动或无活动 session | 重新读取 snapshot |
|
||||
| `busy` | 已有活动 session | 禁用重复开始 |
|
||||
| `cancelled` | 用户取消、关闭或 session 过期 | 清理本地草稿 |
|
||||
| `permission_denied` | 麦克风/辅助功能被拒绝 | 提供系统设置入口 |
|
||||
| `unsupported` | fcitx5、托盘或平台能力不存在 | 显示降级,不假装成功 |
|
||||
| `provider` | ASR/LLM 请求失败 | 依据 `retryable` 提供重试 |
|
||||
| `persistence` | 读写数据失败 | 保留当前页面状态并提示 |
|
||||
| `platform` | host adapter 失败 | 显示平台诊断 |
|
||||
| `internal` | 未分类内部错误 | 显示通用错误并记录 request id(若有) |
|
||||
|
||||
取消必须绑定 session。取消与停止并发时,停止路径在调用 inserter 前再次检查
|
||||
session/phase;已取消 session 不得产生插入副作用。shutdown 对活动 session 发送
|
||||
`Cancelled` 状态,并等待 host 侧录音/热键任务退出。
|
||||
|
||||
Windows TSF Adapter 把失败分成两类:连接/准备阶段的 definite failure 可以按冻结的策略
|
||||
尝试 SendInput/clipboard fallback;请求写入 pipe 后的超时、断连或无法判定的响应属于
|
||||
outcome-unknown,返回明确的 `BackendError` 且不得再次插入。Core 的公开结果只包含
|
||||
`Inserted`、`CopiedFallback` 或错误;这样即使 TSF 提交迟到,也不会与 fallback 形成重复文本。
|
||||
|
||||
`DictationEngine::finish` 返回 `Result<EngineResult, EngineFailure>`。`EngineFailure`
|
||||
除 `BackendError` 外,还携带 `EngineFailureStage::{Transcribing, Polishing}`、可选原文、
|
||||
录音时长、ASR/润色实测耗时和实际录音归档状态。facade 据此统一写失败历史:
|
||||
|
||||
- ASR 启动、录音停止或 ASR finalize 失败使用 `transcribeFailed`;
|
||||
- ASR 返回空白文本使用 `emptyTranscript`;
|
||||
- 禁止回退的润色失败使用 `polishFailed`,并保留已产生的原文;
|
||||
- 插入失败使用 `insertFailed`,并保留原文、最终文本和 `polishSource`;
|
||||
- 失败不增加 activity,Failed 事件仍携带原 session id,资源释放后同步 snapshot 回到 Idle。
|
||||
|
||||
## 7. Host ports
|
||||
|
||||
`BackendDependencies` 由宿主注入,核心不在方法内部创建系统对象:
|
||||
|
||||
- `TaskSpawner`:后台任务执行器;Linux 可使用 Tokio,测试可使用确定性执行器。
|
||||
- `DictationEngine`:完整的 `start(session, progress) -> finish(session, progress) ->
|
||||
cancel(session)` 录音、ASR/润色生命周期;`EngineProgressSink` 使用
|
||||
`RecordingLevel { elapsed_ms, level }`、`EngineStage`、`TranscriptDelta` 和 `PolishDelta`
|
||||
回报进度;`cancel` 也会在 backend shutdown 时调用,不暴露窗口或 UI 类型。迟到进度若
|
||||
session 已失效会返回 `Cancelled`,adapter 必须停止发送。
|
||||
- `AudioRecorder` / `ActiveRecording`:宿主采集设备音频并输出规范化的
|
||||
16 kHz / mono / signed Int16 little-endian PCM;`ActiveRecording::stop(self)` 消费句柄,
|
||||
保证 finish/cancel 竞争时最多释放一次。可恢复录音通过 `RecordingArchive` 精确表示,
|
||||
`is_available()` 报告真实状态,`read_pcm()` 支持冻结 provider 的 silent retry,`discard()`
|
||||
删除实际归档;`RecordingEvent::Fatal` 实时进入 Core controller,不得仅按 preferences 猜测
|
||||
`hasAudioRecording` 或等到 stop 才报错。
|
||||
- `TranscriptionEngine` / `TranscriptionSession`:在录音前建立 ASR session,持续消费 PCM,
|
||||
`finish()` 返回最终原文,`cancel()` 终止 provider 请求。
|
||||
- `TextPolisher`:接收最终原文并产生润色结果/可选增量;失败是否回退原文由
|
||||
`PolishFailurePolicy` 统一决定,宿主不得另写一套 fallback 判断。
|
||||
- `TextInserter`:fcitx5、AX、TSF 或 clipboard fallback 的统一会话接口:
|
||||
`begin(session, context)` 在录音/ASR 启动前捕获 opaque target 并返回
|
||||
`TextInsertionSession::{supports_streaming,write,copy,finish,cancel}`。Core `ActiveTextInsertion` 独占 streamed prefix、
|
||||
Unicode tail、final divergence 与 clipboard fallback reconciliation;Host 报告已消费源 Unicode scalar 前缀长度,包含按约定吞掉的 CR,失败字符不计入。平台准备不能流式时返回 `supports_streaming=false`,仍可一次性落字。
|
||||
target 无法恢复时必须返回明确 copied/error,不能向当前焦点盲写;所有终态都要恢复输入法、
|
||||
剪贴板和平台资源。
|
||||
- `HostActions`:`ShowDictationFeedback`、`HideDictationFeedback`、打开系统设置、
|
||||
外部 URL 和通知等语义动作;不传窗口 label。
|
||||
|
||||
此外已经存在:
|
||||
|
||||
- `CredentialStore`:status、显式 secret read/write/remove、provider channel metadata;
|
||||
- `ResourceResolver`:只解析相对资源路径,拒绝绝对路径与 `..` traversal;
|
||||
- `TaskSpawner`:由宿主注入 runtime,core 不创建窗口线程或专用全局 runtime;
|
||||
- `BackendServices`:复杂领域 Adapter 集合,缺失时使用稳定的 unsupported 实现。
|
||||
- `QaRuntimeAdapter`:捕获 selection host context、持有 recorder/ASR/LLM/Coding Agent 资源并执行
|
||||
`prepare_text/start_recording/finish_recording/answer/cancel`;Core 拥有 session、phase、messages
|
||||
和迟到结果 guard,Adapter 不得复制这些状态。Tauri 与 Linux production factory 均注入真实
|
||||
runtime;编辑/回答路由继续由 `QaService` 决定,Adapter 只执行上下文、录音和 provider effect。
|
||||
- `RemoteInputRuntimeAdapter`:PIN secret persistence、TLS/socket/WSS/H5、local IP 与共享听写桥接;
|
||||
Core 拥有配置、连接/session 关联、PCM 校验和 transport 生命周期规则。
|
||||
- `TranscriptionRouter` / `TextPolisherRouter` / `DictationEngineRouter`:分别按会话快照中的
|
||||
ASR、LLM 与 traditional/Omni 选择固定 Adapter;`provider_id` 是 channel/scoped credential
|
||||
标识,`provider_type` 才是协议路由 key,二者不能混用;ID、type、model 在 session 开始时
|
||||
一次冻结,运行中切换 active channel 或更新注册项只影响下一会话,缺失 provider 返回
|
||||
`Unsupported`。重复 session 必须原子返回 `Busy`,不得覆盖原 Adapter 或 cancellation route。
|
||||
- `SharedCloudTranscriptionEngine` / `SharedCloudTextPolisher` /
|
||||
`SharedAuxiliaryTextPolisher` / `SharedOmniDictationEngine`:Core 生产 Implementation,负责
|
||||
credential account、默认 endpoint/model、协议选择、extra headers/temperature 校验、流式输出、
|
||||
取消和 session 占用;宿主注入 `CredentialStore`、`AudioRecorder` 和 `TaskSpawner`,UI 不接触
|
||||
这些细节。实时 ASR provider 的发送、接收和关闭任务必须使用该 `TaskSpawner`,core 不得创建
|
||||
私有 Tokio runtime。
|
||||
Omni 的 API key、endpoint、model、extra headers 和 temperature 必须使用
|
||||
`CredentialKey.providerId == DictationContext.omni.providerId` 读取;宿主不得在读取期间改写或借用
|
||||
active provider。活动 provider 切换只影响下一会话,任何公开错误都不得包含旧、新 provider secret。
|
||||
|
||||
`PipelineDictationEngine` 固定执行顺序为:启动 ASR session → 启动录音并推送 PCM/level →
|
||||
停止录音 → finalize ASR → 按会话归档策略处理成功录音 → 发布最终原文 delta → 润色 →
|
||||
发布最终润色 delta → 返回结果。ASR 失败和空转写保留可恢复录音;非空 ASR 成功且
|
||||
`recordAudioForDebug == false` 时请求 Adapter 删除归档;删除失败时继续报告真实的
|
||||
`hasAudioRecording == true`,不能产生“历史显示无录音但文件仍在”的假状态。
|
||||
Facade 在 session capture 时冻结启用的 correction rules;Pipeline 在 ASR 成功后、任何
|
||||
Less Computer/polisher 调用前先应用,并只在实际变化时把规则前文本写入 `asrTranscript`。
|
||||
非流式最终文本在插入前按同一规则收口;已流式落字的路径不得事后改写 history 制造屏幕/记录
|
||||
不一致。禁用或格式无效的规则不生效,读取失败只记录非敏感 warning,不丢整段听写。
|
||||
|
||||
History 的 provider 归因也由 facade 统一完成:traditional 流程从冻结 context 记录 ASR
|
||||
channel/model,并仅在实际使用 LLM 时记录 LLM channel/model;multimodal 流程的 ASR 字段和
|
||||
`asrMs` 为 `None`,LLM 字段记录冻结的 Omni channel/model,`polishMs` 保留 Omni 调用耗时。
|
||||
成功和失败记录遵循同一规则,宿主不得自行重写归因。
|
||||
这些细分 ports 只用于宿主组装和测试注入,不是 UI use-case;egui view model 只调用 facade。
|
||||
热键状态通过 `PlatformApi` 查询;Host 只发送携带同一 `press_id` 和单调时间的
|
||||
pressed/released/combined 边沿。Core `HotkeyInterpreter` 统一解释 Toggle/Hold/Auto、modifier
|
||||
grace、250ms debounce 和 450ms terminal cooldown;组合键在 start await 前后均能取消同一代次。
|
||||
Host 不得再保存 cooldown、began-session 或重复的 mode policy。
|
||||
|
||||
## 8. Linux 非 UI Adapter 契约
|
||||
|
||||
`openless-linux-egui` 已交付以下宿主能力,不包含 `eframe::App`:
|
||||
|
||||
- `LinuxCredentialStore`:secret value 只写 Linux Secret Service/keyring;
|
||||
`credential-metadata.json` 仅保存 channel、active provider 与已配置 key 标识,并原子替换;
|
||||
删除按namespace+channel清理全部secret,失败保留可重试元数据;读操作不暴露尚未提交索引的孤立secret。
|
||||
只有Host提供`BackendConfig.home_dir`时才尝试导入旧`com.openless.app` vault/分片与该目录下的旧JSON;Core解析旧格式,Host执行幂等写入,新配置优先,全部成功才写标记,保留旧来源。None配置不隐式访问系统旧凭据或真实HOME。
|
||||
- `LinuxResourceLayout` / `LinuxResourceResolver`:分别定义 development、AppImage、deb、rpm
|
||||
的资源根与 fcitx5 插件相对路径;
|
||||
- fcitx5 Adapter:availability、DBus commit、selection read、hotkey sync、clipboard fallback;
|
||||
普通听写使用 `CaptureDictationTarget(s: session) -> b`、`CommitDictationTarget(ss: session, text) -> b`、
|
||||
`CancelDictationTarget(s: session) -> b` 冻结并释放原输入上下文;新目标不能替换活动 session 的原目标。
|
||||
Selection 同时冻结原上下文及 surrounding text/cursor/anchor;取消选中、移动光标或修改文本后拒绝替换,全局 PRIMARY 不能证明原选区仍存在。
|
||||
`CommitText(s: text) -> b` 的 `true` 才表示文字已提交到输入上下文,`false` 表示当前没有
|
||||
可用焦点输入上下文(例如启动或无焦点/headless 场景)。Rust Adapter 必须把 `false` 转为
|
||||
明确的插入失败/平台错误并按策略决定 clipboard fallback,不能向 Core 或 UI 报告假成功;
|
||||
DBus 调用本身也不得因 no-context 让 fcitx5 进程崩溃。AppImage 可从版本化资源同步用户插件,
|
||||
deb/rpm 只验证系统安装,不能覆盖用户文件;
|
||||
- `LinuxCapabilitySnapshot`:明确区分 X11、Wayland 与 headless,以及 tray、overlay、fcitx5、
|
||||
updater 和麦克风能力;未知权限返回 `Unknown`/`Unsupported`,不伪造 granted;
|
||||
- `LinuxHostActions`:线程安全队列、非阻塞 drain 与可选 wake/repaint callback;
|
||||
- `LinuxSettingsRuntime`:只执行 Core `SettingsEffectPlan` 的显式目标,通过 fcitx5 DBus 同步
|
||||
dictation/QA/Selection Polish/translation/Coding Agent,并通过 Linux credential metadata 同步 active ASR
|
||||
provider;不支持能力返回稳定 `Unsupported`,失败按 receipt 逆序恢复;
|
||||
- `LinuxCpalRecorder`:选择偏好设备或默认输入设备,在专用线程持有 cpal stream,把常见
|
||||
sample format 下混、重采样和量化为 core PCM 契约,并报告 `0..=1` level;runtime fault
|
||||
实时进入 Core recording controller,不等待用户 stop。
|
||||
- `Fcitx5HotkeyListener`:监听 dictation press/release/combined、QA、selection polish 和
|
||||
translation signals,提供非阻塞 `drain()`、`take_error()` 和可停止/join 生命周期;
|
||||
selection 信号调用共享 `SelectionApi`,空闲态先到达的 translation 信号会固定到下一次
|
||||
dictation press 的会话快照,活动 session 不会被中途改写。
|
||||
- `SingleInstanceBroker`:私有 Unix socket + process lock;第二实例把 typed launch intent 转发
|
||||
给 primary 并等待 acknowledgement,primary 非阻塞 drain 后通过 `LinuxHost` 调用 core。
|
||||
- `LinuxBackendBuilder::from_shared_providers(config)`:唯一生产 factory,组装 Core 共享云
|
||||
ASR/LLM/Omni/Auxiliary router、Marketplace、QA、Remote Input、Selection、打包 Qwen runtime、
|
||||
传统 `PipelineDictationEngine`、recorder、inserter、credentials、platform services、host actions
|
||||
与 settings runtime,返回不包含 egui 类型的
|
||||
`LinuxBackendRuntime`;`new(...)` 只用于测试/特殊宿主。
|
||||
- `LinuxHost::download_marketplace_archive`:保存 Core 已校验归档;只接受绝对 filesystem path,
|
||||
不创建缺失父目录、不覆盖已有文件,失败时不遗留部分文件。
|
||||
|
||||
`linux-egui/src/main.rs` 已使用 `eframe::run_native` 接入 `LinuxHost`、Core event、fcitx5
|
||||
hotkey 与 Single Instance Adapter;UI 不读取 Core 私有模块,也不复制业务规则。
|
||||
|
||||
## 9. 测试夹具
|
||||
|
||||
`openless_core::testing` 提供:
|
||||
|
||||
- `RecordingHostActions`:记录 host action 顺序;
|
||||
- `FixtureAudioRecorder`:推送固定 PCM/level,并记录 stop 次数;
|
||||
- `FixtureTranscriptionEngine` / `FixtureTextPolisher`:固定 ASR/润色结果、错误和取消行为;
|
||||
- `FixtureDictationEngine::successful/failing`:在不测试细分 Pipeline 时提供固定结果;
|
||||
- `FixtureTextInserter::with_outcome/failing`:覆盖 inserted、fallback、unknown 和失败,并通过
|
||||
`actions()` 暴露 prepare/insert/cancel 的 session-scoped 调用顺序。
|
||||
- `FixtureSelectionRuntime`:记录 capture、preview、apply、revert、cancel;Linux production
|
||||
Adapter 以 fcitx5 ticket 实现可见 preview、confirm/cancel/revert 与 stale guard。
|
||||
- `RecordingRemoteInputRuntime`:不绑定 socket 的内存 transport,记录 server/audio
|
||||
start/stop/cancel 次数和 PCM frame,用于验证单 connection 单 stream、restart 取消、stale
|
||||
lease 与 secret-surface 契约;它不代表生产宿主具备 WSS 能力。
|
||||
- `LinuxCapabilityFixture::x11_full/wayland_degraded/headless`:覆盖 X11 完整能力、
|
||||
Wayland/fcitx5/托盘/权限降级以及无桌面会话;这些 fixture 只描述状态,不探测测试机。
|
||||
|
||||
egui view model 测试应只使用这些 fixture 和 `BackendSnapshot`/事件,不启动窗口、
|
||||
麦克风、网络或真实凭据库。最低 contract test 集合:
|
||||
|
||||
1. 启动/关闭幂等和事件顺序;
|
||||
2. 开始 → 处理 → 完成的主链路;
|
||||
3. ASR 失败、插入失败、fallback、unknown;
|
||||
4. 错误 session 取消不会改变活动 session;
|
||||
5. 事件 lagged 后 snapshot resync;
|
||||
6. 序列化 DTO 不包含秘密字段。
|
||||
|
||||
## 10. 能力降级矩阵
|
||||
|
||||
| 能力 | Linux 状态 | UI 规则 |
|
||||
| --- | --- | --- |
|
||||
| 全局热键 | available / unavailable | 不可用时隐藏快捷键设置或给出降级说明 |
|
||||
| fcitx5 插入 | plugin missing / ready | missing 时允许 clipboard fallback,不能假成功 |
|
||||
| 托盘 | available / unavailable | 不可用时保留主窗口内退出入口 |
|
||||
| 悬浮反馈 | X11 / Wayland limitation | feedback 失败不升级为 ASR 失败 |
|
||||
| 本地 ASR | model absent / ready | 显示下载、准备、释放状态 |
|
||||
| 自动更新 | package-dependent | 不显示假更新按钮 |
|
||||
| 麦克风 | granted / denied / no device | 区分权限拒绝和无设备 |
|
||||
|
||||
## 11. 版本与变更流程
|
||||
|
||||
当前代码常量为 `openless_core::BACKEND_CONTRACT_VERSION = "2.0.0"`。运行时 wire 只接受
|
||||
2.0.0;1.x 兼容仅存在于 preferences、history、activity、credentials、model 和 style-pack
|
||||
持久化迁移读取器中,不暴露 legacy runtime contract 常量。本文的 contract version 随破坏性
|
||||
接口变更递增。新增可选 DTO 字段必须有默认
|
||||
值;删除字段、改变枚举值、改变事件顺序或单位必须:
|
||||
|
||||
1. 更新 contract version;
|
||||
2. 在计划文档的待决事项和迁移表记录影响;
|
||||
3. 同时更新 Tauri mapping、Linux fixture 和示例;
|
||||
4. 先让 contract tests 通过,再通知 egui 组切换。
|
||||
|
||||
egui 组发现缺少能力时,应提交一个只依赖 facade/DTO/event 的可复现测试;不得
|
||||
读取 core 私有字段或复制内部实现。
|
||||
|
||||
### 11.1 从 0.1.0 迁移到 0.2.0
|
||||
|
||||
0.2.0 把可变偏好读取收敛为每会话 `Arc<DictationContext>`,属于有意的破坏性 Interface
|
||||
变更:
|
||||
|
||||
- `DictationEngine::start` 新增 context;Pipeline 在 session 生命周期内持有同一快照;
|
||||
- `AudioRecorder::start`、`TranscriptionEngine::start`、`TextPolisher::polish` 和
|
||||
`TextInserter::insert` 都接收同一 context;
|
||||
- UI 不构造 context。宿主仍调用 `OpenLessBackend::start_dictation()`,或在需要翻译时调用
|
||||
`start_dictation_with_options(DictationStartOptions)`;facade 从 preferences、active style pack、
|
||||
provider metadata 与 vocabulary 一次性生成快照;
|
||||
- 会话开始后修改麦克风、provider、模型、语言、风格包或插入策略只影响下一会话;
|
||||
- 自定义 Adapter 必须停止在执行中重新读取 preferences,并只使用传入 context。
|
||||
|
||||
### 11.2 从 0.2.0 迁移到 1.0.0
|
||||
|
||||
1.0.0 扩展了听写结果和润色结果,用于让共享 core 独立持久化完整历史:
|
||||
|
||||
- `TextPolisher::polish` 的成功类型从 `String` 改为 `PolishOutput`;Adapter 应把最终文本放入
|
||||
`text`,组合润色加翻译时把润色后的源文放入 `source_text`,其他模式使用 `None`;
|
||||
- `DictationEngine::finish` 的错误类型从 `BackendError` 改为 `EngineFailure`;Adapter 必须标注
|
||||
`EngineFailureStage`,并在已经产生时保留原文、录音时长、ASR/润色耗时和归档状态;
|
||||
- `EngineResult` 新增 `polish_source`、`polish_failed`、`asr_ms`、`polish_ms` 和
|
||||
`has_audio_recording`;公开 `DictationResult` 新增 `polish_source` 与 `duration_ms`;
|
||||
- `ActiveRecording::has_archived_recording` 被 `archive() -> Option<Arc<dyn RecordingArchive>>`
|
||||
取代,使 Pipeline 能在 stop 消费录音句柄后精确保留失败录音或删除成功录音;
|
||||
- `DictationResult` 的两个新增 serde 字段均有兼容默认值,因此 0.2.0 JSON fixture 仍可读取:
|
||||
缺失 `polishSource` 时为 `None`,缺失 `durationMs` 时为 `0`;
|
||||
- 宿主不得在 Tauri/egui Adapter 重复拆解组合翻译输出或重复写入成功 history;这些语义由
|
||||
core Pipeline 与 facade 统一负责。
|
||||
- `TextInserter` 现在具有 `prepare/insert/cancel` 会话生命周期,且三个方法都接收或绑定
|
||||
`SessionId`;旧的只实现 `insert(context, text)` 的 Adapter 必须迁移。Core 在 engine 启动前
|
||||
调用 prepare,并在启动失败、处理失败、取消和 shutdown 路径调用幂等 cancel。
|
||||
- `DictationInsertionContext` 额外冻结 `windows_sendinput_newline_mode` 与
|
||||
`android_insert_strategy`,平台 Adapter 不得在会话执行中重新读取偏好。
|
||||
|
||||
### 11.3 从 1.0.0 迁移到 2.0.0
|
||||
|
||||
2.0.0 将跨平台业务 Implementation 收口到 `openless-core`:
|
||||
|
||||
- 模型清单、Range 下载、断点索引、SHA-256、staging/ready sentinel 和旧目录迁移统一由
|
||||
`ModelStore` 提供;宿主只注入模型根目录、原生 runtime 和 typed progress sink;
|
||||
- Coding Agent 由 Core `CodingAgentRunner` 统一构造四种 provider 的请求、解析 stream、过滤
|
||||
`session_id` 并产生唯一终态,宿主只实现进程创建、stdio、kill/wait 和临时文件;
|
||||
- 文档窗口、最小差异/词汇学习和其它协议纯函数位于 Core,Tauri/Linux 仅保留 AX、窗口、
|
||||
输入法、socket、keyring 等 Adapter;
|
||||
- 1.x preferences/history/activity、凭据元数据、旧模型根目录/mirror/sentinel 与 style-pack
|
||||
origin 字段继续按迁移规则读取,不因 contract 升级丢失。
|
||||
|
||||
## 12. 当前交付状态
|
||||
|
||||
| 交付物 | 状态 |
|
||||
| --- | --- |
|
||||
| `openless-core` package 和无 Tauri 依赖门禁 | 已建立 |
|
||||
| facade 生命周期、听写状态机、事件 sequence | 已建立 |
|
||||
| headless Linux host 示例 | 已完成,位于 `linux-egui/examples/headless_host.rs`;覆盖生命周期、数据领域、Less Computer、听写、Selection/Selection Voice、QA 与 Remote Input contract;真实 socket/窗口/设备证据另记 |
|
||||
| fake host/recorder/transcription/polisher/engine/inserter/selection/remote transport | 已建立;fixture 固定完整业务状态,Linux production Adapter 另实现真实 TLS/WS、fcitx5 preview/revert 与 cpal effect |
|
||||
| preferences/history/activity/vocabulary/correction/style-pack/credentials 共享实现 | 已建立;Tauri/Linux persistence Adapter 使用同一 Core mutation 与 active policy |
|
||||
| Linux validated settings Interface | 已建立;`save_settings`/`update_settings_strict` 强制携带 snapshot revision,Core 统一校验、协调、持久化、事件和补偿,Linux Adapter 只消费显式 target |
|
||||
| 全部复杂领域 DTO/Interface 与 unsupported 语义 | 已建立,位于 `domains.rs` / `BackendServices` |
|
||||
| 2.0 公共 re-export 边界 | 已冻结;`openless-core`/`openless-linux-egui` 只公开 facade/DTO/event/host Interface/fixture,repository 与内部状态机不属于 UI 契约;`check-linux-public-surface.ps1` 防止边界回退 |
|
||||
| Tauri command/event 完整迁移 | Core 业务路径已收口;React/CLI/Android JNI/Remote Input/桌面听写使用 2.0 contract,Tauri 仅保留 command/event wire 与平台 Adapter |
|
||||
| 复杂领域真实共享 Adapter | Core `ModelStore`、`CodingAgentRunner`、Voice session、Provider policy、Remote Input 与 Style Pack 已接入;Linux Qwen runtime 已进入打包链,Foundry/Sherpa 与真实设备/发布物仍需平台证据 |
|
||||
| 会话级 provider router | 已建立;ID/type/model 在 session 开始时固定,Core 持有云 ASR/LLM/Omni 协议 Implementation;Tauri 与 Linux 注册同一共享实现,Tauri 另行追加 native/local ASR |
|
||||
| provider 验证/模型列表管理面 | 已建立;Core `ProviderService` 统一 channel-scoped credential、静态/远端模型列表、验证探活和错误脱敏;Tauri command 只做 wire 转换,Linux shared factory 注入同一 service;真实网络/keyring 和平台 runner 仍按主计划 M9/M10 留证 |
|
||||
| Linux credentials/resources/fcitx5/capabilities/host-actions | 已建立非 UI Adapter 和 contract tests;WSL Ubuntu 已显式通过真实 Secret Service set/read/remove、fcitx5 plugin/method/listener/signal contract;无焦点输入时 plugin 不抛异常导致 fcitx5 崩溃 |
|
||||
| Linux cpal 录音、共享 Pipeline builder、热键 listener、第二实例 intent 转发 | 已建立;selection/translation 已路由到共享 Interface;WSL 当前无 ALSA 设备时 cpal contract 已证明稳定分类错误,真实设备和桌面 runtime 生命周期仍见计划 M8/M9 |
|
||||
| Linux 打包 workflow/manifest 契约 | 已建立但正式发布仍需真实 Ubuntu 安装、运行、升级和回滚证明 |
|
||||
| egui UI | 已使用真实 `eframe::run_native`,覆盖 startup/error、听写、QA、Remote Input、Selection preview/revert、Less Computer/approval、Provider/Credential、模型、history 与 settings;视觉深化不属于 2.0 Core 收口 |
|
||||
|
||||
完整验收以主计划第 12 节为准;本契约证明 Linux UI 可以在不依赖 Tauri 的前提下使用冻结的
|
||||
2.0.0 Interface。真实 Ubuntu 原生能力、发行包与安装升级回滚仍由 Linux runner 门禁证明。
|
||||
|
||||
## Less Computer 语音接口(2.0)
|
||||
|
||||
`OpenLessBackend::start_less_computer_voice(session_id, recording_control)` 返回 Core-owned
|
||||
`LessComputerVoiceSession`。Host 必须只发送 16 kHz、mono、signed 16-bit little-endian
|
||||
PCM;空帧、奇数长度和累计超过 provider 上限会返回 `InvalidArgument`。`finish` 只允许调用
|
||||
一次,ASR 失败或空 transcript 不会启动 Agent,并释放 capture lease;`cancel` 同时取消
|
||||
ASR/Agent 并释放尚未提升的 lease。`recording_control` 是窄平台 effect:Core 的共享
|
||||
`SilenceAutoStop`/fault controller 决定 stop 或 cancel,Host 只关闭自己持有的 capture handle。
|
||||
|
||||
实时 provider 的 interim 文本通过既有 `BackendEventKind::TranscriptDelta` 发布,使用同一
|
||||
`session_id` 且 `offset` 单调递增;批式 provider 只发布一次 `is_final=true`。Agent 阶段继续
|
||||
使用 `LessComputerEvent`(approval、stream、completed、cancelled、error),不新增 ASR 事件
|
||||
类型,Backend contract 版本为 `2.0.0`。
|
||||
|
||||
Linux `LinuxHotkeyEvent::{LessComputerPressed,LessComputerReleased,LessComputerCombined}`
|
||||
只表达热键边沿;Hold/Toggle/Auto(Auto 长按阈值 350ms)由 Core 解释。三种录音入口与
|
||||
`silence_auto_stop_enabled` 共用同一设置,冲突时保留当前会话并返回 `Busy`。
|
||||
- 原生预加载必须使用请求的 target/provider type;平台无法准备流式时以 `supports_streaming=false` 保留一次性落字。
|
||||
- Remote stop 保持可取消的 session;socket 下行只转发本连接所属 session 的事件。
|
||||
- 自动 contract 不替代真实平台证据:Ubuntu/Windows/macOS/Android 的设备、安装、升级与签名结果按[验收](linux-egui-handoff/07-acceptance.md)与[桌面验收](2.0-desktop-acceptance.md)分别记录。
|
||||
@@ -1,68 +1,29 @@
|
||||
# 01:Core接入合同
|
||||
# 01:Core 合同(Linux 可调用的面)
|
||||
|
||||
前置:[交接入口](./README.md)。本文只约束接入,不规定页面布局。
|
||||
状态:canonical(2026-09-07 以源码为准重写);更新:2026-09-07。前置:[交接入口](./README.md)。本文只约束接入,不规定页面布局。
|
||||
|
||||
## 1. 可以依赖的层
|
||||
## 1. 合同文件与启动门
|
||||
|
||||
`egui UI → LinuxHost / Linux Adapter → openless-core`。
|
||||
- 公开合同:`openless-all/app/contract/backend-2.0.json`,顶层键 `contractVersion` / `startupSnapshot` / `backendEvent` / `lessComputerVoice` / `androidJni` / `linuxFacade` / `enums`。启动时校验版本;UI 必须先消费 startup snapshot 再渲染状态。
|
||||
- 构造:`LinuxBackendBuilder::from_shared_providers(config)`(`linux-egui/src/backend.rs`)创建与 Tauri 同源的 Core 后端;云 ASR/LLM/Omni 实现两端共用,不复制第二套。
|
||||
- 事件:Core `events.rs` 语义事件 → `LinuxHost::drain_events`(`lib.rs`)→ UI reducer;订阅用 `LinuxHost::subscribe` 返回 `EventSubscription`。
|
||||
- 会话/取消:所有长操作(听写、转写、下载、QA、Agent)走 Core session 取消语义;UI 只触发与显示,不自建取消规则。
|
||||
|
||||
- Core拥有provider协议、渠道/模型/偏好事务、会话与取消、业务路由、纠错/历史、Agent命令和结果语义。
|
||||
- Linux Host拥有音频、全局热键、窗口、fcitx5、Secret Service、TLS/socket、进程与文件效果。
|
||||
- UI拥有页面、编辑草稿、进度/错误展示和事件消费;不另建业务状态机。
|
||||
- 不引用Tauri源码、不读取Core私有模块、不把`AppHandle`/egui类型加入Core公开合同。
|
||||
## 2. Core 公开模块(`crates/openless-core/src/lib.rs`)
|
||||
|
||||
## 2. 构造、启动、退出
|
||||
按域的模块地图见[架构文档](../architecture.md)第 3 节。Linux 直接相关:
|
||||
|
||||
1. Host确定显式`BackendConfig`中的data/cache/home等路径及能力;不要在测试中隐式使用真实用户目录。
|
||||
2. 使用`LinuxBackendBuilder::from_shared_providers(config)?.build()?`复用生产组装;测试可用显式注入入口。
|
||||
3. 创建事件订阅/Host action消费,再调用`LinuxNativeRuntime::start`;该方法内部启动Core,调用方不重复启动。
|
||||
4. 检查启动快照`running`和合同版本`2.0.0`。未就绪时禁用业务操作并展示错误。
|
||||
5. 从Core读取设置/渠道/模型/各领域快照,启动非阻塞UI消费;原生热键须同步保存的配置。
|
||||
6. 退出时禁止新请求,取消活动会话,停止Host listener/服务并调用幂等shutdown。
|
||||
- 业务门面:`api`(对外接口)、`domains`(领域出口)、`events`、`ports`(Host 注入接口,含 `NoopEditObservationAdapter` 等默认实现)。
|
||||
- 听写:`dictation_engine`、`silence_auto_stop`、`streaming_insert`、`hotkey_interpreter`。
|
||||
- 服务:`provider_*` 系列、`omni`、`credentials`、`endpoint_security`。
|
||||
- 本地模型:`model_store`、`local_asr_service`、`local_asr_catalog`。
|
||||
- 领域:`history`、`vocabulary`、`correction`、`style_pack_store`、`marketplace`、`qa_service`、`selection_*`、`less_computer`、`remote_input_service`。
|
||||
|
||||
现有[backend.rs](../../openless-all/app/linux-egui/src/backend.rs)、[runtime.rs](../../openless-all/app/linux-egui/src/runtime.rs)和[headless示例](../../openless-all/app/linux-egui/examples/headless_host.rs)是实际构造依据。
|
||||
## 3. 注入点(`ports.rs`)
|
||||
|
||||
`from_shared_providers`和未显式注入executor的`build`必须在Host已经创建的Tokio runtime内调用;同步GUI初始化可用短作用域`runtime.enter()`包住构造,退出该作用域再`block_on`启动。builder捕获同一runtime的Handle,录音回调、静音终止和资源析构即使来自普通OS线程也能提交任务。未进入runtime时构造明确失败,不新建runtime或静默丢任务。使用自定义`LinuxBackendBuilder::new`的Host可通过`with_task_spawner`提供自己的实现,但它必须接受任意原生线程调用;Host保持executor存活直至shutdown与原生清理完成。不要用只查“当前线程runtime”的默认spawner承接cpal回调。
|
||||
Host 必须实现并注入:`AudioRecorder`、`TextPolisher`、`TextInserter`、`CredentialStore`、`SettingsRuntime`、`TaskSpawner`、`EditObservationAdapter`(未接时为 `NoopEditObservationAdapter`,见缺口 L02)、`LinuxHostActions`。注入清单与默认值以 `backend.rs` builder 为准;缺省实现表示"未接线",不是"不支持"。
|
||||
|
||||
## 3. 公开能力与调用位置
|
||||
## 4. 纪律
|
||||
|
||||
| 领域 | 入口/来源 | Host/UI使用要求 |
|
||||
| --- | --- | --- |
|
||||
| 听写 | `OpenLessBackend` start/stop/cancel、snapshot | 使用Core session;不要另做Hold/Toggle/Auto/静音/重试策略 |
|
||||
| 设置 | `LinuxHost::save_settings` / `update_settings_strict` | 带读到的revision;执行Core effect plan,失败按receipt恢复 |
|
||||
| 渠道/凭据 | Core channel facade、`ProviderApi` | 保留channel ID与provider type区别;默认值/认证规则来自descriptor |
|
||||
| 模型 | `LocalAsrApi` / `ModelStore` / activation | 用一次activation事务,不拆成UI连续写prefs和active provider |
|
||||
| 历史/词典/纠错/风格包 | Core facade,[api.rs](../../openless-all/app/crates/openless-core/src/api.rs) | 通过领域方法修改;不直接写JSON或重做ZIP/文本规则 |
|
||||
| QA | `services().qa` | stop使用每轮token;conversation owner不等于每轮capture token |
|
||||
| Selection/Selection Voice | 对应`services()`接口 | 预览/确认/撤回/意图由Core决定;Host保存原生目标并执行效果 |
|
||||
| Less Computer / Agent | 对应`services()`及Core语音入口 | 复用命令、审批、解析与取消;Host只启动/终止进程 |
|
||||
| Remote Input | `services().remote_input` | Core处理认证/会话/帧序号;Host负责TLS/WSS与H5传输 |
|
||||
| 能力/权限 | Core snapshot + Linux capability probe | 未实现/未授权/未知要区分,UI不能凭平台名伪造可用 |
|
||||
|
||||
DTO和平台Interface集中在[domains.rs](../../openless-all/app/crates/openless-core/src/domains.rs)、[ports.rs](../../openless-all/app/crates/openless-core/src/ports.rs)。
|
||||
|
||||
## 4. 业务不变量
|
||||
|
||||
- 听写、QA、Selection Voice、Less Computer共享语音互斥:Busy拒绝新会话,不抢占旧会话。
|
||||
- provider/channel/model与上下文按会话冻结;只有已有合同允许的停止时翻译切换可更新该轮。
|
||||
- Core按实际管线解析渠道:Omni不依赖传统ASR/LLM,Less/Selection录音只依赖ASR,QA文本不依赖ASR。Raw允许没有可用LLM;若停止时改为翻译,Pipeline使用冻结的LLM或`deferred_llm_error`,不会偷偷切到录音期间新增/启用的渠道。
|
||||
- 停止、取消、timeout、设备fault与迟到结果必须保持单一终态,不能让旧任务改变新session。
|
||||
- 逻辑取消会立即使本代token失效;原生初始化、stop或ASR清理仍在途时,Core资源hold继续阻止新语音。收到取消终态不表示已经可以绕过Core强行打开另一个麦克风。
|
||||
- 自定义`DictationEngine`须原样接收/转发`start_voice_capture`与`start_audio_capture`的`CancellationToken`;ASR启动返回后、开麦前及原生初始化完成后均检查它,迟到句柄必须关闭。生产factory已接好,不要替换成一个永不取消的新token。
|
||||
- `Inserted`、`PasteSent`、`CopiedFallback`、`NotRequested`和错误`OutcomeUnknown`不可互换。结果未知不得自动再插一次。
|
||||
- 流式尾段协调、纠错执行顺序、历史/统计归因留在Core;Host只回报真实效果。
|
||||
- 插入`begin()`本身也可能切换输入源。Core在准备前登记同一可等待结果,取消/丢弃调用方不能跳过尚未完成的原生恢复。
|
||||
- `TextInserter::capture_target()`在听写认领后、上下文/凭据等待和反馈前同步调用;焦点敏感Host返回仅持本轮原生目标的插入器,异步`begin()`再准备输入源。原生句柄不进入Core DTO,无需插入时不捕获;不依赖焦点的Adapter保留默认`None`。Linux Host接入原目标快照时应覆写此入口,不能等凭据读取后重新抓当前焦点。
|
||||
|
||||
## 5. 秘密和数据
|
||||
|
||||
- 凭据秘密只通过`CredentialStore`/`SecretValue`传递;通用状态、事件、日志和可序列化UI快照不包含API密钥、OAuth access/refresh token或PIN。配对页面通过显式`read_pairing_pin`受控显示PIN,不广播到通用状态。
|
||||
- 审批票据(如`Approval.token`、`pending_approval_token`)是Core业务合同的一部分,不是上述凭据秘密;UI须按原合同接收、关联并回传,不能自行伪造。
|
||||
- Linux已经提供旧凭据解码/迁移起点;只有显式home目录才触发旧来源访问,完成标记最后提交。
|
||||
- 设置revision冲突后重新读快照合并草稿,不无条件重放旧整表。
|
||||
- 1.x数据格式兼容与runtime `2.0.0`握手是两回事:继续读旧数据,不接受旧runtime合同冒充已就绪。
|
||||
|
||||
## 6. 接口缺口的回报
|
||||
|
||||
提交:用户操作、已有公开入口、缺少的业务结果/Host效果、期望事件/错误、最小复现或fixture。
|
||||
Core负责人修共享规则/接口;egui团队修平台适配/界面。详情按[缺口登记](./02-gap-register.md)分类,不能通过公开私有内部对象绕过。
|
||||
- 保留启动、版本校验、事件与退出合同;改动先过 `cargo test -p openless-core --locked` 与合同测试。
|
||||
- 合同测试不替代真实平台证据(见[07 验收](07-acceptance.md))。
|
||||
@@ -1,53 +1,35 @@
|
||||
# 02:Linux 缺口登记与实施顺序
|
||||
|
||||
状态:draft;阶段:partial-implementation;更新:2026-09-07。
|
||||
|
||||
初始盘点基线:`2b315572`;本次UI整理基线:`fc38edd1`,更新反映当前工作区。责任依据:[当前范围](../2.0-requirements.md)。以下是源码盘点,不是通过声明。
|
||||
状态:canonical(2026-09-07 以源码为准重写,L-ID 体系沿用);更新:2026-09-07。本表是源码盘点,不是通过声明。
|
||||
|
||||
## 1. 状态定义
|
||||
|
||||
- **接线缺口**:Core 已有业务,Linux Host 尚未执行必要的原生效果。
|
||||
- **界面缺口**:已有 Core/Host 入口,生产 UI 未提供完整操作。
|
||||
- **待实测**:存在真实实现,但还需要对应桌面、设备或安装证据。
|
||||
- 本表由 egui 团队推进 Linux Host/UI;若发现共享接口不能承载该业务,回报 Core 负责人修复,不在 UI 复制规则。
|
||||
- **待实测**:存在真实实现,还需要对应桌面、设备或安装证据。
|
||||
|
||||
## 2. 待办登记
|
||||
|
||||
| ID | 当前状态与明确缺口 | 关闭标准 / 详细说明 |
|
||||
| ID | 状态与缺口(源码锚点) | 关闭标准 |
|
||||
| --- | --- | --- |
|
||||
| L01 | 接线+界面:`switch_style`、`open_app`、`style_packs` 热键修改被 Linux settings 明确拒绝 | 实际全局注册、触发、重绑、失败恢复和重启还原;分别声明 X11/Wayland 支持。[热键](./03-hotkeys-and-windows.md) |
|
||||
| L02 | 接线:HostContext/EditObservation 仍为默认 Noop,Selection 的 source_app 为 None | 按隐私设置捕获真实应用/允许的上下文;原生手改观察能产生 Core 纠错建议并拒绝迟到结果。[原生](./05-native-host-and-data.md) |
|
||||
| L03 | 接线:CPAL 无 RecordingArchive,未执行录音期间系统静音/恢复;缺提示音与胶囊 | 录音归档、保留策略、失败恢复、同归档重试和历史重转可用;所有终态恢复音量,反馈可见。[原生](./05-native-host-and-data.md) |
|
||||
| L04 | 接线+界面:Selection Voice 未形成 Linux 生产触发/捕获/意图路由;QA 缺编辑模式/应用/撤回 UI | 完整触发至 Core intent、预览、目标核验、应用/取消/撤回;不能用已有 Selection polish 页面代替。[领域](./04-ui-domains.md) |
|
||||
| L05 | 界面:词典、纠错规则/建议、风格包管理没有页面 | 各领域增删改/启停/预设/导入导出和失败反馈;通过 Core facade 持久化。[领域](./04-ui-domains.md) |
|
||||
| L06 | 界面:Marketplace service 已接入,市场页面缺失 | 浏览、安装、上传/下载、点赞/作品管理、设备 OAuth 登录/取消/退出;复用 Core 协议。[领域](./04-ui-domains.md) |
|
||||
| L07 | 界面+L03依赖:历史仅只读最近20条,缺完整历史/统计/录音操作 | 浏览和原有历史操作、重润色/重转写、录音播放/导出/清理与统计;真实归因不由 UI 拼装。[领域](./04-ui-domains.md) |
|
||||
| L08 | 界面:本地 Qwen 有下载/激活/取消,缺完整模型管理和运行时控制 | 路径/镜像、详情/状态、删除、预载/释放和准备/测试取消;真实推理另附证据。[领域](./04-ui-domains.md) |
|
||||
| L09 | 界面部分改善:已增加领域导航、开始/环境引导、独立现有设置和手机输入页、跨页审批/取消;多数 Linux 适用设置、Agent 检测/模型/路径/权限配置仍缺失 | 设置有实际消费者、revision/错误处理;保留现有 Less Computer 输出/审批/取消流程。本次未关闭L09。[领域](./04-ui-domains.md) |
|
||||
| L10 | 接线+界面:无托盘/自启;通知仅状态栏,重启仅提示,AppImage能力判定不等于更新器 | 窗口/后台运行、通知、自启、检查/下载/安装更新与重启有真实 Host 效果。[原生](./05-native-host-and-data.md) |
|
||||
| L11 | 已实现待实测:fcitx5 输入/选区、CPAL、Secret Service、Qwen、CLI 进程、Remote TLS/H5 | 通过真实桌面/设备矩阵;未通过的逐项记录,不把整模块称为缺失。[验收](./07-acceptance.md) |
|
||||
| L12 | 已有包构建,待 Linux 产品验收与正式分发 | 完成上述应用缺口及 Linux 安装/升级/回滚、签名和更新证据;不阻塞 Windows/macOS 首批交付。[验收](./07-acceptance.md) |
|
||||
| L01 | 接线+界面:`switch_style` / `open_app` / `style_packs` 热键修改在 `settings.rs:56-61` 被明确拒绝 | 实际全局注册、触发、重绑、失败恢复与重启还原;分别声明 X11/Wayland 支持 |
|
||||
| L02 | 接线:未注入 `EditObservationAdapter`,Core 默认 `NoopEditObservationAdapter`(`ports.rs:539`)生效,Selection `source_app` 为 None | 按隐私设置捕获真实应用/允许的上下文;手改观察能产生纠错建议并拒绝迟到结果 |
|
||||
| L03 | 接线:`audio.rs` `LinuxCpalRecorder` 无录音归档,未执行录音期间系统静音/恢复;缺提示音与胶囊 | 录音归档、保留策略、失败恢复、历史重转写可用;所有终态恢复音量 |
|
||||
| L04 | 接线+界面:Selection Voice 未形成生产触发/捕获/意图路由;QA 缺编辑模式/应用/撤回 UI | 完整触发至 Core intent、预览、目标核验、应用/取消/撤回 |
|
||||
| L05 | 界面:词典、纠错规则/建议、风格包管理没有页面(`ui_state.rs` 10 页无对应项) | 各领域增删改/启停/预设/导入导出与失败反馈,经 Core facade 持久化 |
|
||||
| L06 | 界面:marketplace 服务已接入(`marketplace.rs`),市场页面缺失 | 浏览、安装、上传/下载、设备 OAuth 登录/退出,复用 Core 协议 |
|
||||
| L07 | 界面+L03 依赖:历史仅只读最近条目,缺完整历史/统计/录音操作 | 浏览与既有历史操作、重润色/重转写、录音播放/导出/清理与统计 |
|
||||
| L08 | 界面:本地 Qwen 有下载/激活/取消,缺完整模型管理与运行时控制 | 路径/镜像、详情/状态、删除、预载/释放与准备/测试取消;真实推理另附证据 |
|
||||
| L09 | 界面部分改善:10 页导航、开始/环境引导、独立设置与手机输入页、跨页审批/取消已落地;多数 Linux 适用设置、Agent 检测/模型/路径/权限配置仍缺失 | 设置有实际消费者、revision/错误处理;保留 Less Computer 输出/审批/取消流程 |
|
||||
| L10 | 接线+界面:无托盘/自启(`capabilities.rs` 仅探测 `supports_tray`,未实现);通知仅状态栏;AppImage 能力判定不等于更新器 | 窗口/后台运行、通知、自启、检查/下载/安装更新与重启有真实 Host 效果 |
|
||||
| L11 | 已实现待实测:fcitx5 输入/选区(`fcitx5.rs`)、CPAL(`audio.rs`)、Secret Service(`credentials.rs`)、Qwen 运行时、CLI 进程、Remote TLS/H5(`remote_input.rs`) | 通过真实桌面/设备矩阵;逐项记录,不把整模块称为缺失 |
|
||||
| L12 | 已有包构建(deb/rpm/AppImage workflow),待 Linux 产品验收与正式分发 | 完成上述应用缺口与安装/升级/回滚、签名和更新证据;不阻塞 Windows/macOS 首批交付 |
|
||||
|
||||
## 3. 已有且应直接复用
|
||||
|
||||
- 云 ASR/LLM/Omni、Provider descriptor 和渠道管理、Linux vault、Core ModelStore、Generic Qwen runtime。
|
||||
- 普通听写与现有 fcitx5 热键、ticket 化落字、Selection polish 预览/撤回、QA 文本/语音、Less Computer 工具审批。
|
||||
- Remote Input TLS/H5、配对和会话桥接、单实例、Core 合同校验、事件重放、native shutdown。
|
||||
|
||||
对应实现位置见[Core合同](./01-core-contract.md)、[领域](./04-ui-domains.md)和[原生](./05-native-host-and-data.md)。这些代码是接入起点,不需要另建第二套后端。
|
||||
|
||||
## 4. 依赖顺序和完成口径
|
||||
## 3. 依赖顺序
|
||||
|
||||
1. 保留启动、版本校验、事件与退出合同,跑通一个现有听写流程。
|
||||
2. 补 L01–L03 原生能力;页面可并行,但历史音频/自动纠错不能脱离其 Host 依赖单独宣布完成。
|
||||
3. 补 L04–L09 领域入口及完整成功/失败/取消流程,再补 L10 桌面集成。
|
||||
4. 按 L11–L12 获取真实环境与发布证据。
|
||||
|
||||
任务记录格式:`ID / owner / commit / 已完成效果 / 自动证据 / 设备证据 / 剩余限制`。
|
||||
“待实测”不是“未实现”,也不是“已完成”;新发现必须按层归属,不能仅因 Linux 故障就判定 Core 缺失。
|
||||
|
||||
## 5. 本轮 UI 进展(2026-09-06)
|
||||
|
||||
`L04/L07/L08/L09/L11 / Linux UI / 共享工作区未提交`:现有领域从单页拆为导航分页;开始页与环境准备解释fcitx/plugin、桌面会话、麦克风与Secret Service的实际可知状态。原生未连接时保留诊断步骤,未展示成功。Agent审批固定在页面外,QA/Selection/Agent后台事件保留操作或导航提示,未改变Core接口或扩充原生适配。
|
||||
|
||||
本轮只改善入口与显示,不关闭Selection Voice、完整历史/模型管理、Agent高级配置、托盘/更新器及Linux设备/分发缺口。实现、官方来源、定向命令和实际自动证据统一记录在[04本轮记录](./04-ui-domains.md#5-2026-09-06-导航与-linux-准备引导),不在此重复维护。
|
||||
任务记录格式:`ID / owner / commit / 已完成效果 / 自动证据 / 设备证据 / 剩余限制`。“待实测”不是“未实现”,也不是“已完成”。
|
||||
@@ -1,51 +1,19 @@
|
||||
# 03:全局热键与窗口
|
||||
|
||||
对应缺口:L01、L10。系统注册和窗口效果由 egui 团队的 Linux Host 负责,业务动作继续调用 Core。
|
||||
状态:canonical(2026-09-07 以源码为准重写);更新:2026-09-07。对应缺口:L01、L10。系统注册和窗口效果由 Linux Host 负责,业务动作继续调用 Core。
|
||||
|
||||
## 1. 历史与当前差异
|
||||
## 1. 已有实现
|
||||
|
||||
Tauri 1.x 在 **X11** 上已有切换风格、打开主窗和风格包热键,经过 `global-hotkey` 路径。
|
||||
源码锚点为 `v1.3.18-tauri` 的 `src-tauri/src/lib.rs` 三个 `start_*_hotkey_listener` 和 `coordinator/hotkey_loops.rs` 的实际处理分支。
|
||||
这不能证明原生 Wayland/fcitx 路径曾具备同等能力,也不能写成“1.x 从未实现”。
|
||||
- **fcitx5 热键监听**:`hotkeys.rs` `Fcitx5HotkeyListener::start()/drain()/take_error()`——经 fcitx5 插件路由热键事件(已实现待实测,见 L11)。
|
||||
- **fcitx5 输入/选区**:`fcitx5.rs`——插件安装(AppImage 在 listener 前安装)、PRIMARY 选区、落字。
|
||||
- **单实例**:`single_instance.rs` `SingleInstanceGuard::acquire(path)` + `SingleInstanceBroker::acquire_or_forward()/drain()`——二次启动意图转发给首实例。
|
||||
- **设置侧拒绝逻辑**:`settings.rs:56-61` 对 `switch_style` / `open_app` / `style_packs` 三类热键修改给出明确拒绝信息(L01 的直接证据)。
|
||||
- **能力探测**:`capabilities.rs` `LinuxCapabilitySnapshot`(session 类型、`PlatformCapabilities`、权限快照),`supports_tray` 只是探测字段。
|
||||
|
||||
当前 Linux 已用 fcitx5 接听写、QA、选区润色、翻译和 Less Computer;但以下三项仍缺 Host 接线:
|
||||
## 2. 缺口(L01)
|
||||
|
||||
| Core配置目标 | 当前缺口 | 触发后的业务入口 |
|
||||
| --- | --- | --- |
|
||||
| `HotkeyRuntimeTarget.switch_style` | Linux settings 拒绝修改,没有对应 native event 分支 | `activate_previous_style_pack` |
|
||||
| `HotkeyRuntimeTarget.open_app` | 同上 | `request_host_action(HostAction::ShowMain)` |
|
||||
| `HotkeyRuntimeTarget.style_packs` | 同上 | `activate_style_pack`,使用绑定的稳定风格包ID |
|
||||
全局热键修改(改绑 `switch_style`、`open_app`、`style_packs`)被 settings 拒绝,因为 Host 尚未提供真实的系统级注册/重绑/失败恢复/重启还原。关闭标准:实际注册、触发、重绑、失败恢复与重启还原,分别声明 X11/Wayland 支持范围。
|
||||
|
||||
入口见[Core api.rs](../../openless-all/app/crates/openless-core/src/api.rs)与[HostAction](../../openless-all/app/crates/openless-core/src/ports.rs)。
|
||||
## 3. 缺口(L10,窗口侧)
|
||||
|
||||
## 2. 应修改的生产路径
|
||||
|
||||
- [settings.rs](../../openless-all/app/linux-egui/src/settings.rs):`reject_unsupported_hotkey_changes`、`LinuxSettingsEffects::apply_hotkeys` 和 prepare/commit/restore。
|
||||
- [lib.rs](../../openless-all/app/linux-egui/src/lib.rs):`LinuxHotkeyEvent` 消费及业务派发。
|
||||
- [runtime.rs](../../openless-all/app/linux-egui/src/runtime.rs):启动同步、listener 重连及 pump。
|
||||
- [fcitx5.rs](../../openless-all/app/linux-egui/src/fcitx5.rs)与[插件](../../openless-all/scripts/linux-fcitx5-plugin):按所选原生机制扩展注册和事件传输。
|
||||
- [main.rs](../../openless-all/app/linux-egui/src/main.rs):快捷键编辑、冲突/不可用提示及窗口消费。
|
||||
|
||||
不能仅删除 `Unsupported` 判断:只有原生注册和实际事件路径一起完成,配置才算生效。
|
||||
|
||||
## 3. 注册事务
|
||||
|
||||
1. UI 提交草稿和读取时的 preferences revision,经 LinuxHost 调 Core 设置验证。
|
||||
2. Host 按 effect plan 注册新目标、清除旧目标;资源占用/无权限必须返回错误。
|
||||
3. 部分失败按 receipt 恢复旧注册及配置;不能保存“新键”却仍监听“旧键”。
|
||||
4. 启动和 fcitx5 重连读取同一 Core target 重新同步;退出/禁用时释放注册。
|
||||
5. 听写的 Hold/Toggle/Auto、去抖/冷却仍归 Core;Host 提供真实边沿,不另建解释器。
|
||||
|
||||
X11 与 Wayland/桌面环境分别报告支持情况。窗口内的 egui 键盘事件不等于系统全局热键;焦点离开 OpenLess 后仍须按承诺范围工作。
|
||||
|
||||
## 4. 窗口和后台行为
|
||||
|
||||
现有 `ShowMain`/`FocusMain` 已映射 egui viewport,单实例有转发机制;保留这些实现。
|
||||
尚需完整托盘/后台窗口行为、录音反馈层、桌面通知与真正重启。`ShowDictationFeedback`/`HideDictationFeedback` 当前不产生效果,通知只进状态栏。
|
||||
|
||||
窗口聚焦不得覆盖录音开始时捕获的输入目标。关闭业务面板应取消其会话;隐藏主窗、退出进程、关闭面板是不同操作。
|
||||
|
||||
## 5. 关闭证据
|
||||
|
||||
逐项覆盖:首次保存、重绑、旧键失效、冲突和回滚、重启还原、插件重载、后台触发、禁用/退出释放;风格包删除/禁用后的绑定也需一致。
|
||||
测试结果分别标注 X11/Wayland、桌面环境、实际前台应用。目标环境无法提供某动作时明确禁用并说明限制,不返回假成功。
|
||||
托盘、窗口/后台运行策略、通知、自启、更新器均未实现(capabilities 仅探测)。参考[05](05-native-host-and-data.md)第 4 节。
|
||||
@@ -1,112 +1,28 @@
|
||||
# 04:页面与领域操作
|
||||
|
||||
状态:draft;阶段:partial-implementation;更新:2026-09-07。页面实现与自动证据不等于 Linux 原生验收通过。
|
||||
状态:canonical(2026-09-07 以源码为准重写);更新:2026-09-07。页面定义:`linux-egui/src/ui_state.rs` `Page` 枚举(10 页);主循环与交互:`main.rs`。
|
||||
|
||||
对应 L04–L09。现有[main.rs](../../openless-all/app/linux-egui/src/main.rs)是可复用的真实 UI 起点,但并非功能齐全的产品。
|
||||
接口查[Core api.rs](../../openless-all/app/crates/openless-core/src/api.rs)与[domains.rs](../../openless-all/app/crates/openless-core/src/domains.rs)。
|
||||
## 1. 十页现状
|
||||
|
||||
## 1. 已有页面与需要补齐的操作
|
||||
|
||||
| 领域 | 当前生产UI | egui团队接入要求 |
|
||||
| 页面 | 已有操作 | 主要缺口 |
|
||||
| --- | --- | --- |
|
||||
| Provider/渠道 | ASR/LLM/Omni渠道创建、重命名、启停、排序、激活、删除、编辑、列模型和校验已有 | 复用 descriptor;完善 vault 锁定/错误恢复、状态刷新和正式交互,不重做认证规则 |
|
||||
| 本地模型 | Qwen列表、下载、激活、取消已有 | 接 `LocalAsrApi` 的路径/镜像、model_card/remote_info、状态、删除、preload/release、prepare/test_model及取消;激活使用 Core 原子事务 |
|
||||
| QA | 文本/语音、流式回答、取消/关闭已有 | 接 `set_edit_instruction_mode`、编辑预览、应用/撤回及状态;用每轮 recording token 停止录音 |
|
||||
| Selection polish | 可编辑预览、确认、取消、完成后撤回已有 | 保留原目标/session核验和错误反馈,不将剪贴板旧选区当有效原目标 |
|
||||
| Selection Voice | Core service已有,Linux完整触发/意图路由与UI缺失 | 见下节;不是只加一个按钮即可完成 |
|
||||
| Less Computer | 文本/语音、输出/工具状态、Allow/Deny审批和取消已有 | 接 `CodingAgentApi` 的detect/list_models/run_test/cancel_test和provider/model/executable/workdir/permission配置;不自建审批与续聊策略 |
|
||||
| 风格包 | 无管理页 | 接Core列表、创建/编辑、启停/激活、内置重置、删除、提示词诊断、ZIP导入/导出;快捷键依赖L01 |
|
||||
| 词典/纠错 | 无页面 | 接词条增删/启停、预设、规则;pending corrections接受/拒绝/清空。自动建议来源依赖L02 |
|
||||
| Marketplace | service和下载归档Host已有,无页面 | 接 `MarketplaceApi` 的列表/详情/安装/下载/上传、点赞/作品、设备OAuth启动/轮询/取消/退出 |
|
||||
| 历史/统计 | 仅最近20条只读和插入状态 | 完整浏览及历史操作、删除/清空、重润色/重转写、复制/导出、录音播放/定位/清理、活动统计;音频先补L03 |
|
||||
| 设置 | 环境准备与现有streaming_insert、Agent启用;Remote开关/端口独立到手机输入页 | 补Linux适用的麦克风/静音、语言/翻译、模式/热键、QA历史、选区、隐私/日志和外观;每项必须有真实消费者 |
|
||||
| Remote Input | 独立页面保留开关、地址、PIN与重置,增加服务状态/连接数/陈旧地址提示及LAN/证书说明,TLS/H5已接 | 继续补二维码、完整连接错误恢复与证书信任体验;复用服务并进行真手机验收 |
|
||||
| Start | 下一步引导、环境准备说明(fcitx/插件、桌面会话、麦克风、Secret Service 的真实可知状态;未连接时保留诊断步骤,不展示成功) | — |
|
||||
| Dictation | 普通听写(复用 Core `dictation_engine`)、ticket 化落字 | — |
|
||||
| Qa | QA 文本/语音(`qa.rs`)、全局待办、审批、取消 | 编辑模式/应用/撤回 UI(L04) |
|
||||
| Selection | 划词润色预览/撤回(`selection.rs`) | Selection Voice 生产触发/捕获/意图路由(L04) |
|
||||
| Agent | Less Computer 工具调用、审批固定在页面外、后台事件保留 | Agent 检测/模型/路径/权限配置(L09) |
|
||||
| Services | AI 服务配置、渠道管理(`settings.rs` + Core provider 面) | Omni 有效模式配置入口完整性(L09) |
|
||||
| Models | 本地 Qwen 下载/激活/取消 | 完整模型管理:路径/镜像/详情/删除/预载/释放(L08) |
|
||||
| Remote | 手机输入独立页、连接状态、陈旧地址处理 | — |
|
||||
| History | 只读最近条目 | 完整历史/统计/录音操作(L07,依赖 L03) |
|
||||
| Settings | 现有设置独立页 | 词典/纠错/风格包/市场无页面(L05/L06);多数设置缺实际消费者(L09) |
|
||||
|
||||
不要给 Linux 暴露无实现的 Windows TSF 或 macOS AX 权限按钮。当前 Linux 本地运行时为 Generic Qwen;Foundry/Apple MLX 不是这一轮要求跨平台移植的功能。
|
||||
## 2. 跨页约定
|
||||
|
||||
## 2. Selection Voice 接入链
|
||||
- Agent 审批固定在页面外展示,QA/Selection/Agent 的后台事件保留操作或导航提示。
|
||||
- 事件消费不得因导航停止(见[06](06-events-and-sessions.md));会话取消语义由 Core 承担。
|
||||
- 每个旧操作保留真实调用;页面增删不改变 Core 接口。
|
||||
|
||||
1. Linux Host 捕获原生目标、选区及会话身份,接入 Core 的共享语音互斥和停止/取消路径。
|
||||
2. 使用 `SelectionVoiceApi` 的 `begin`、`process_transcript`、`confirm_intent`;由 Core 决定 `route_disposition`。
|
||||
3. UI 消费 Selection Voice 事件,展示意图选择或路由后的 QA/编辑结果;当前 main.rs 尚未消费这些事件。
|
||||
4. 编辑经过 `prepare_edit`、`begin_preview_apply`,Host 按 ticket 核验并执行替换,再以真实结果调用 `finish_preview_apply`。
|
||||
5. 目标失效、取消、重复确认和未知插入结果都应保持安全,不能绕过 Core 事务直接粘贴。
|
||||
## 3. 新增页面的实施要求
|
||||
|
||||
Linux QA 的 target rekey 适配和现有 Selection runtime可以复用;headless直接调用Core的测试不算生产接线完成。
|
||||
QA 内的预览必须传当前 turn token,使用 `QaApi::begin_edit_preview_apply`/`revert_edit_preview`;原生应用完成后用 `dismiss_session` 只关闭所属回合。不要拆成读取 conversation owner 后直接改 Selection Voice,再无条件关闭 QA。独立 Selection Voice 的 `begin_preview_apply`/`revert_preview` 是无异步原生效果的同步状态操作;`PasteSent` 只表示已发送,不能标成 `Inserted` 或失败。
|
||||
|
||||
## 3. 共同交互规则
|
||||
|
||||
- UI只保留编辑草稿、焦点等显示状态;持久化变更走Core facade,不直接读写JSON、凭据或风格ZIP内容。
|
||||
- 设置携带revision,冲突后重读;渠道ID与provider类型分开,默认值/认证来自descriptor。
|
||||
- 长操作在Host runtime调度,事件驱动重绘;egui frame不阻塞等待网络、模型下载或进程。
|
||||
- 字段禁用、加载、取消、错误、空列表、重试与重开须齐备;按钮存在但效果未接入应标明不可用。
|
||||
- 历史重转使用Auxiliary服务与`apply_history_retranscription`,真实provider/model/timing由Core生成;不要根据当前设置猜历史归因。
|
||||
- API密钥只写入不读回;配对PIN通过显式配对接口受控显示,不进入通用日志/状态广播。
|
||||
|
||||
## 4. 关闭标准
|
||||
|
||||
每个领域交付“用户入口 → Core调用 → Host效果 → 事件/持久化”的实际流程;验证成功、失败、取消、重新打开和重启。
|
||||
领域UI完成不自动关闭相关原生缺口;L02/L03/L01分别验收上下文、音频归档和全局热键。
|
||||
|
||||
## 5. 2026-09-06 导航与 Linux 准备引导
|
||||
|
||||
页面复用现有 Core 与 Linux Host 接口。
|
||||
|
||||
| 入口 | 当前行为 |
|
||||
| --- | --- |
|
||||
| 开始 | 环境准备 → AI 服务或本地模型 → 听写;按 Core 有效模式分别显示 ASR/LLM 或 Omni 配置,已配置与校验通过分开;连接后可直达问答、选区润色、Agent、手机输入、历史 |
|
||||
| 工作页面 | 听写、问答、选区润色、Less Computer 分页;每页独立滚动,草稿与会话保存在同一个 App 状态中 |
|
||||
| 准备与管理 | AI 服务保留原渠道操作;本地模型保留列表/下载/激活/取消;历史保留最近20条只读与插入结果分类 |
|
||||
| 环境与设置 | 只提供已有消费者的流式插入与Agent启用,保留revision保存;手机输入配置共用原设置草稿,保存/重新读取明确说明影响 |
|
||||
| 手机输入 | 配置、保存、状态刷新、连接数、配对码重置;停止、地址陈旧时隐藏PIN和URL,状态读取失败清除旧显示 |
|
||||
|
||||
宽窗口使用左侧导航;小于760逻辑像素时改用可换行的顶部导航。主循环仍先无条件`poll`再渲染当前页面,保留50ms重绘、sequence去重、session归属、lag重放、退出shutdown与Esc语音取消。
|
||||
|
||||
- QA/Selection/Agent与听写/模型/Remote后台事件给对应导航增加提醒,访问该页面只清除该页未读状态,不取消后台工作。
|
||||
- `ShowQa`、`ShowSelectionPreview`、`ShowLessComputer`打开所属页面;普通`ShowMain`保留当前页面。隐藏窗口/面板的既有Host语义保留。
|
||||
- Agent审批固定在页面滚动区外:显示原command与原token对应的允许/拒绝;所有页面可直接取消Agent。取消请求与审批结果仍走原Core facade。
|
||||
- QA有打开、原文本/语音操作、关闭及显式取消;全局任务栏保留问答取消、选区预览取消和当前语音取消。选区确认/撤回仍核验原session。
|
||||
- Agent `VoiceState`仅增加导航提示,不把录音session当聊天session,不伪造首帧或电平。完整typed语音反馈与投影恢复仍按[06](./06-events-and-sessions.md)继续接入。
|
||||
|
||||
### 环境状态的证据边界
|
||||
|
||||
启动失败仍能查看开始页与环境引导。现有插件安装检查的Ready/Updated/Missing/错误结果被保留;Updated/Missing仍阻止启动原生运行时。未连接Core的领域页面展示准备入口,不开放假可用操作。
|
||||
|
||||
- 桌面类型来自`LinuxCapabilitySnapshot`的环境判断,不是端到端桌面可用性测试。
|
||||
- fcitx探测是现有D-Bus Peer Ping,只能说明探测有响应;插件文件存在不能证明加载,加载不能证明任意目标应用落字。
|
||||
- “重新检测会话与D-Bus”在后台执行,只更新环境探测;不会重新安装插件或启动Core。本次启动的插件检查结果保留,修复后需重启OpenLess。
|
||||
- 麦克风显示未验证录音/当前环境不支持,不将Unknown当授权成功。Secret Service没有独立连接/解锁探测,UI明确说明未知,并引导用户解锁桌面密钥环后通过原渠道保存/验证获得真实结果。
|
||||
- 不因X11 overlay或AppImage auto-update能力flag显示假浮层、托盘或更新器,也不新增Windows/macOS专属设置。
|
||||
|
||||
可复制的`fcitx5-diagnose`用于诊断;`fcitx5-remote -r`仅重载配置,不能称为重新安装或重启插件。新插件仍未加载时,指引重新登录桌面并重启OpenLess。Wayland指向桌面/工具包对应官方指导,不给所有桌面统一写环境变量。
|
||||
|
||||
### 定向验证与剩余实测
|
||||
|
||||
导航状态位于[ui_state.rs](../../openless-all/app/linux-egui/src/ui_state.rs),可在非Linux平台测试。主文件定向测试覆盖后台审批跨页可见、迟到终态隔离、QA/Selection草稿与取消提示、启动失败引导、陈旧Remote地址/PIN隐藏,并保留原两轮QA/Agent回归。
|
||||
|
||||
在`openless-all/app`执行:
|
||||
|
||||
```sh
|
||||
rustfmt --edition 2021 --check --config skip_children=true linux-egui/src/main.rs linux-egui/src/ui_state.rs
|
||||
cargo test -p openless-linux-egui --locked
|
||||
```
|
||||
|
||||
完整主文件的`linux_app::tests`由Linux cfg控制;macOS直接运行上面的Cargo测试不会编译egui主界面。Linux机器还须执行:
|
||||
|
||||
```sh
|
||||
cargo check -p openless-linux-egui --all-targets --locked
|
||||
cargo test -p openless-linux-egui --bin openless-linux-egui --locked
|
||||
```
|
||||
|
||||
原生依赖沿用[07验收](./07-acceptance.md)与现有CI。真机仍须覆盖420×400短窄窗口与宽窗口、中文字体、后台QA/Selection/Agent触发、跨页审批/取消、插件首次安装及重载、X11/Wayland目标应用、麦克风、密钥环、真实Qwen/CLI与手机TLS。自动布局绘制不是实际窗口/设备验收。
|
||||
|
||||
本次实际自动证据(macOS,Homebrew rustc 1.97.1):
|
||||
|
||||
- `cargo test -p openless-linux-egui --locked`:50个Host单元测试、4个Host合同测试及首轮2个导航测试通过;设备测试在此平台为0个,不能作设备证据。
|
||||
- 为检查Linux cfg内的主界面,在独立临时crate复制本次`main.rs`/`ui_state.rs`,仅临时启用其UI编译,使用仓库已有eframe 0.31.1和同一Core/Linux Host路径依赖;`cargo test --offline`共9个测试通过(含最新3个导航测试、原2个事件回归和新增4个场景绘制测试)。未运行生产启动函数,未改变仓库Cargo清单或锁文件。该证据仅覆盖macOS上的UI类型、状态与无窗口绘制,Host仍使用macOS cfg。
|
||||
- 尝试`cargo check -p openless-linux-egui --all-targets --target x86_64-unknown-linux-gnu --locked`,因目标`core/std`缺失(E0463)失败;本机Docker服务未运行。**没有Linux目标编译通过证据**,须在配置好原生依赖的Linux环境执行上方两条命令。
|
||||
|
||||
### 官方资料
|
||||
|
||||
来源、版本、获取日期和有效期以各官方项目发布页为准。
|
||||
L04–L08 每个领域补完整成功/失败/取消路径后再算完成;领域功能扩展时优先拆分 `main.rs`(当前较大),避免继续单文件增长。
|
||||
@@ -1,60 +1,29 @@
|
||||
# 05:原生宿主、数据和系统集成
|
||||
# 05:原生宿主、数据与系统集成
|
||||
|
||||
对应 L02、L03、L10、L11。平台效果归 Linux Host;现有代码可复用,具体缺口如下。
|
||||
状态:canonical(2026-09-07 以源码为准重写);更新:2026-09-07。对应 L02、L03、L10、L11。平台效果归 Linux Host;现有代码可复用。
|
||||
|
||||
## 1. 音频、存档和反馈
|
||||
## 1. 音频(`audio.rs`)
|
||||
|
||||
现有[audio.rs](../../openless-all/app/linux-egui/src/audio.rs)提供 CPAL 设备、PCM 转换、音量和致命错误回调;Core决定静音停止和重试策略。
|
||||
- 已有:`LinuxCpalRecorder`(CPAL 录制,可指定首选设备)——已实现待实测(L11)。
|
||||
- 缺口(L03):无录音归档(RecordingArchive)、未执行录音期间系统静音/恢复、缺提示音与胶囊。关闭标准:归档、保留策略、失败恢复、同归档重试与历史重转写可用;所有终态恢复音量且反馈可见。
|
||||
|
||||
- `LinuxCpalRecorder::start`目前只消费计划中的设备名称,没有执行录音期间系统静音/恢复。
|
||||
- `LinuxActiveRecording`没有覆写`archive()`;Core默认返回None。因此录音归档、失败归档恢复、同归档静默重试的音频来源尚不完整。
|
||||
- 补充[RecordingPlan](../../openless-all/app/crates/openless-core/src/dictation_context.rs)的适用效果及[RecordingArchive/read_pcm](../../openless-all/app/crates/openless-core/src/ports.rs);`archive_enabled=false`(QA/划词语音)时不得创建临时归档,不能用成功后删除代替不落盘。主听写/Less Computer 的保存与清理须遵循独立音频保留设置,不能误用历史条数。
|
||||
- 用户取消、设备断开、启动失败和正常停止都必须恢复已改变的系统音量;不要覆盖录音期间用户主动修改的新状态。
|
||||
- 提示音、录音胶囊/overlay尚未接入;消费Core阶段/反馈事件,避免UI自己猜测处理已结束。
|
||||
- Less Computer成功非debug录音的归档清理由Core执行;Host需提供真实`RecordingArchive`和可执行的`discard`。未提供archive不等于已经验证文件保留策略,失败/debug场景也不能无条件删除。
|
||||
## 2. 上下文与观察(L02)
|
||||
|
||||
验收:真实输入设备切换/拔出、无声停止、权限失败、归档开关/上限、失败重试与取消、历史录音播放/重转、各终态音量恢复。
|
||||
Core `ports.rs` 提供 `EditObservationSink` / `EditObservationAdapter`(`ports.rs:523-539`);Linux 未注入实现,默认 `NoopEditObservationAdapter` 生效,Selection 的 `source_app` 为 None。关闭标准:按隐私设置捕获真实应用/允许的上下文;原生手改观察能产生 Core 纠错建议并拒绝迟到结果。
|
||||
|
||||
## 2. 输入目标、上下文和手改观察
|
||||
## 3. 数据与凭据
|
||||
|
||||
[fcitx5.rs](../../openless-all/app/linux-egui/src/fcitx5.rs)已有ticket化插入、失败复制与结果区分;[selection.rs](../../openless-all/app/linux-egui/src/selection.rs)已有捕获/应用/取消/撤回和原目标核验。
|
||||
- `credentials.rs`:Secret Service 凭据存储(L11 待实测)。
|
||||
- `model_store`(Core):模型目录与下载;Linux 管理 UI 缺口见 L08。
|
||||
- `remote_input.rs`:Remote Input TLS/H5、配对与会话桥接(L11 待实测)。
|
||||
- `resources.rs`:打包资源路径。
|
||||
|
||||
**确定缺口**:Linux factory 未覆写默认`NoopHostContextAdapter`与`NoopEditObservationAdapter`,选区`source_app`仍为None。
|
||||
接入[HostContextAdapter与观察合同](../../openless-all/app/crates/openless-core/src/ports.rs),采集平台能够可靠提供的应用身份和授权上下文,报告无法提供的能力。
|
||||
## 4. 桌面集成(L10)
|
||||
|
||||
- 关闭光标上下文时不读取文档;必要前台应用元数据与文档内容分开处理。
|
||||
- 记录开始时固定原目标,不因主窗获得焦点而改成当前窗口;迟到观察须核验generation/session。
|
||||
- 不把PRIMARY旧值或剪贴板副本当成当前原控件仍匹配的证明。
|
||||
- Core继续拥有流式尾段协调与纠错规则;Host不能因未知结果再粘贴一次。
|
||||
未实现:托盘(`capabilities.rs` 仅探测 `supports_tray`)、自启、通知(仅状态栏)、检查/下载/安装更新与重启。AppImage 能力判定(`LinuxPackageKind`)不等于更新器已接好。关闭标准:各能力有真实 Host 效果与失败反馈,不以配置状态伪造就绪。
|
||||
|
||||
需在X11/Wayland各自的GTK、Qt、浏览器、终端验证;组合环境不支持可靠替换时明确降级/禁用,不伪造已插入。
|
||||
## 5. 进程与运行时
|
||||
|
||||
## 3. 凭据和1.x数据
|
||||
|
||||
[credentials.rs](../../openless-all/app/linux-egui/src/credentials.rs)已有Secret Service、channel metadata与旧凭据迁移;[backend.rs](../../openless-all/app/linux-egui/src/backend.rs)仅在显式home路径下启用旧来源访问。
|
||||
|
||||
- 复用CredentialStore,不在UI读取vault数据库、推导账号名或保存明文密钥。
|
||||
- 旧来源读取、目标写入、完成标记的顺序必须可重入;失败保留来源,不能凭“开始迁移”删除旧数据。
|
||||
- 只操作OpenLess所属账号/命名空间;测试用独立data/cache/home和测试keyring,不触碰真实凭据。
|
||||
- vault锁定/拒绝/不存在分别展示,保存失败不可显示成功;渠道删除不能误删其他渠道的秘密。
|
||||
- 旧模型和自定义目录迁移复用Core ModelStore,不由UI复制目录并猜测ready状态。
|
||||
|
||||
## 4. 模型、进程与Remote
|
||||
|
||||
| 已有实现 | 需要保留/验证 |
|
||||
| --- | --- |
|
||||
| Linux Generic Qwen runtime、Core下载/激活事务 | 包内runtime路径、真实推理、timeout/cancel和旧释放不卸载新模型;Foundry/MLX不在Linux移植要求内 |
|
||||
| Linux ProcessAdapter | 使用Core AgentCommand;验证桌面PATH、stdin背压、取消后进程组清理、审批/拒绝与自然结束 |
|
||||
| Remote TLS/WSS、H5、Core配对/音频桥接 | 多连接隔离、首帧、停止时仍可取消、断线恢复、端口占用、手机证书信任;不要另写认证或会话状态机 |
|
||||
|
||||
这些模块有生产实现;缺真实凭据、模型或设备结果时写“待实测”,不要整模块标“未实现”。
|
||||
|
||||
若Host实现驻留模型缓存,须在`ModelRuntimeAdapter::claim_lease/preload_lease/release_lease`中原子维护模型与激活代次;同属Generic不代表同一缓存。普通使用应撤销旧激活的释放权,迟到加载不能覆盖新缓存,也不能取消仍有效的旧会话。Core负责激活失败补偿与metadata提交,Host不得仅按模型ID查询后再无条件释放整个runtime。
|
||||
|
||||
## 5. 系统集成仍需实现
|
||||
|
||||
现有[main.rs](../../openless-all/app/linux-egui/src/main.rs)将tray能力传为false;通知仅显示状态栏,`RequestRestart`仅提示手动重启,缺自启与实际更新器。
|
||||
[capabilities.rs](../../openless-all/app/linux-egui/src/capabilities.rs)按AppImage布局判断`supports_auto_update`,这只是能力判定,不能当作更新服务已完成的证据。
|
||||
|
||||
egui团队补窗口/托盘/通知/自启,以及AppImage检查、验证、下载、替换和重启;系统包遵循其分发方式,不硬套AppImage更新流程。直到接线完成,UI不得依据此flag展示假可用操作。
|
||||
缺fcitx5插件时当前程序进入启动失败页;产品可用性方案须明确安装/重载指引,若选择允许配置模式则只开放不依赖该插件的操作。
|
||||
- `runtime.rs`:任务派生与生命周期。
|
||||
- `coding_agent.rs`:CLI 进程管理(L11 待实测)。
|
||||
- `single_instance.rs`:单实例守护与启动意图转发(已实现)。
|
||||
@@ -1,63 +1,22 @@
|
||||
# 06:事件、会话与取消
|
||||
|
||||
入口:[Core events.rs](../../openless-all/app/crates/openless-core/src/events.rs)、[types.rs](../../openless-all/app/crates/openless-core/src/types.rs)、[Linux主循环](../../openless-all/app/linux-egui/src/main.rs)。
|
||||
当前Linux已有事件消费/重放和若干时序修复,正式UI应保留这些语义,不仅复制页面布局。
|
||||
状态:canonical(2026-09-07 以源码为准重写);更新:2026-09-07。
|
||||
|
||||
## 1. 订阅与恢复
|
||||
入口:Core `events.rs` / `types.rs`、`linux-egui/src/lib.rs`(`LinuxHost`)、`linux-egui/src/main.rs`(主循环)。
|
||||
|
||||
1. 建立订阅后初始化Core,读取startup和领域快照,检查`running`及`contract_version`。
|
||||
2. 按`sequence`去重,按`session_id`归属分发;不能将任何最新事件都归给当前页面。
|
||||
3. 消费落后时使用已有replay/快照恢复机制;不要跳过缺失事件后继续在旧状态上追加。
|
||||
4. 配置变更按revision刷新缓存;UI草稿不能反向覆盖较新的后台配置。
|
||||
5. 事件到达后请求重绘;不要在每帧启动新订阅、网络请求或后台任务。
|
||||
## 1. 订阅与消费
|
||||
|
||||
恢复时保留各领域的不同策略:听写恢复状态,QA以完整messages校准,Less Computer按轮归属处理;不是一个通用“取最新事件”就能替代。
|
||||
- `LinuxHost::subscribe()` 返回 `EventSubscription`;`drain_events()` 批量取走语义事件交给 UI reducer。
|
||||
- 主循环必须在每帧消费事件;导航切换不得停止事件消费或丢弃会话(QA/Selection/Agent 的后台事件保留操作或导航提示)。
|
||||
|
||||
听写`DictationStateSnapshot.recording_ready`的JSON字段为`recordingReady`,每轮初始为`false`;`phase=recording`仅说明原生启动已返回,不能据此提前显示麦克风就绪。AudioRecorder必须先向consumer交付非空PCM,再报告该帧的level;Core仅在这个首帧回调后将`recordingReady`置为`true`,即使首帧是`elapsedMs=0,level=0`也必须发布状态。不得用启动定时器或预填零电平伪造首帧。UI在`starting/recording`且`recordingReady=false`时显示待命;终态退出待命,新会话重新从`false`开始。旧JSON缺字段时Core按`false`读取。
|
||||
## 2. 会话与取消
|
||||
|
||||
Less Computer语音提供`voice_state {sessionId, phase, level, elapsedMs}`及`BackendEventPublisher::latest_less_computer_voice_state()`固定一条最新投影。`phase`为`starting/recording/transcribing/idle`;投影保留原seq,供阶段事件被有界replay驱逐后恢复显示,不能推进聊天去重水位。Linux Host/UI接手此显示消费;旧session的Idle或迟到电平不得覆盖当前录音。
|
||||
该语音投影复用相同首PCM合同:原生start返回后仍保留`starting`,首帧level才转为`recording`;Host/UI不能自行补发就绪态。
|
||||
- 所有长操作走 Core session:创建/取消/终态由 Core 裁决,UI 只触发与显示。
|
||||
- Remote stop 保持可取消的 session;迟到结果按 epoch 丢弃(桌面同规则,见[桌面验收](../2.0-desktop-acceptance.md)划词域)。
|
||||
- 事件顺序与重放语义以 `contract/backend-2.0.json` 的 `backendEvent` 与 Core `events.rs` 为准;UI 不得自行重排或合并事件。
|
||||
|
||||
## 2. TranscriptDelta不是简单追加
|
||||
## 3. 已有时序约定(保留,不因重写界面丢失)
|
||||
|
||||
`offset`表示Unicode scalar(Rust `char`)数量,不是UTF-8字节或UTF-16单元。
|
||||
新文本为旧文本前`offset`个scalar加`delta.text`,原尾段被替换。优先复用`TranscriptAccumulator`。
|
||||
|
||||
例:已有`你好世`,收到`offset=2,text="世界"`,结果是`你好世界`,不是`你好世世界`。
|
||||
中文、emoji、provider interim回修与最终delta都需测试。越界offset是错误,不截断猜测;迟到的上一轮delta不能改新轮文本。
|
||||
|
||||
## 3. QA与Less Computer的归属
|
||||
|
||||
- QA对话owner与每轮录音token不同。停止录音使用该轮token,不能把延迟的静音回调转换为无条件toggle。
|
||||
- QA增量事件的`messages=None`并不表示清空历史;追加chunk,完整消息到达时再校准。
|
||||
- 面板关闭/重开时丢弃旧owner的迟到输出;语音finish期间仍须能取消实际ASR。
|
||||
- Less Computer每个新User轮更新当前session,`fresh`仅决定是否清除会话显示;续聊也必须接收新轮输出与审批。
|
||||
- 工具/审批事件按Core标识关联,不在UI另建可绕过Core的approval token registry。
|
||||
- Less Computer录音反馈使用typed `voice_state`:`sessionId`、`phase`(starting/recording/transcribing/idle)、`level`、`elapsedMs`。与同一事件流的sequence一起消费,旧session的idle/电平不能清除新录音;Linux页面接入这组反馈仍由egui团队完成。
|
||||
|
||||
源码参考:[qa.rs](../../openless-all/app/linux-egui/src/qa.rs)、[Core QA/Agent接口](../../openless-all/app/crates/openless-core/src/domains.rs)。
|
||||
|
||||
## 4. 取消和原生资源
|
||||
|
||||
共享语音使用Busy拒绝新会话。停止录音、取消整个会话和正常结束不是同一动作。
|
||||
异步启动前登记owner;每个慢await返回后核验身份和取消状态,迟到得到的资源直接关闭。
|
||||
|
||||
录音移交给finish后仍要保留共享取消句柄;不能`take()`唯一资源后让取消路径找不到ASR。
|
||||
Core已有capture控制与单一终态规则;Host保留有效句柄并如实返回错误,UI只显示最终状态。
|
||||
Core资源hold覆盖在途原生初始化与收尾;关闭页面、取消回复或丢弃调用future不能让旧任务恢复新会话的静音/输入源。不要把Core暂时返回Busy当成可以在Host绕开的锁。
|
||||
|
||||
Remote socket下行只发本连接所属session;客户端stop后到finish结束之间仍保留取消路由。
|
||||
停止native listener/服务后再shutdown Core;退出过程中不接新业务。
|
||||
|
||||
## 5. 插入结果
|
||||
|
||||
- `Inserted`:原生确认写入;`PasteSent`:派发粘贴但不能证明目标接收。
|
||||
- `CopiedFallback`:已经复制且需要用户处理;`NotRequested`:本次没有请求插入。
|
||||
- `OutcomeUnknown`是错误语义:原生可能已写入,不能自动重试造成双写。
|
||||
|
||||
UI通知、历史和Remote结果不得把这些统一显示为“输入成功”。
|
||||
|
||||
## 6. 最小回归集合
|
||||
|
||||
重复/乱序/lag重放、Unicode回修、两轮QA、两轮Less Computer、旧session迟到事件;Starting/录音/转写/finish各阶段取消;旧静音回调到达新轮;面板关闭重开;插入未知结果不重复提交。
|
||||
复用[host contract](../../openless-all/app/linux-egui/tests/host_contract.rs)和Core各领域contract;fixture通过之后仍需验证原生Host效果。
|
||||
- 听写终态、录音停止与音量恢复事件必须成对(见[05](05-native-host-and-data.md) L03)。
|
||||
- 审批/待办事件跨页存活;审批动作固定在页面外展示。
|
||||
- 启动阶段先消费 startup snapshot,再消费增量事件(见[01](01-core-contract.md))。
|
||||
@@ -1,72 +1,30 @@
|
||||
# 07:验收和证据
|
||||
# 07:验收与证据
|
||||
|
||||
状态:canonical(2026-09-07 以源码为准重写);更新:2026-09-07。
|
||||
|
||||
## 1. 两个不同的完成门
|
||||
|
||||
- **本批Core移交门**:可调用的`2.0.0`合同、真实共享业务、平台Interface、无设备示例/fixture、跨平台依赖检查和逐项缺口文档。
|
||||
- **egui团队Linux产品门**:补齐[登记项](./02-gap-register.md),取得真实桌面、设备、安装升级与正式分发证据。
|
||||
- **本批 Core 移交门**:可调用的 `2.0.0` 合同、真实共享业务、平台 Interface、无设备示例/fixture、跨平台依赖检查、逐项缺口文档(已完成,见[01](01-core-contract.md))。
|
||||
- **egui 团队 Linux 产品门**:补齐[登记项](02-gap-register.md) L01–L12,取得真实桌面、设备、安装升级与正式分发证据。
|
||||
|
||||
第二项不阻塞本批Windows/macOS 2.0;共享Core缺陷或不可调用的承诺接口仍属于本批问题。
|
||||
Windows/macOS自己的完整功能与设备验收见[桌面清单](../2.0-desktop-acceptance.md)。
|
||||
## 2. 自动验证(在 `openless-all/app/` 执行)
|
||||
|
||||
## 2. 无设备验证
|
||||
|
||||
在仓库`openless-all/app`目录执行;Linux原生依赖安装方式沿用[CI](../../.github/workflows/ci.yml),不要把Windows上的Linux stub编译当作Linux目标验证。
|
||||
|
||||
```sh
|
||||
cargo test -p openless-core --locked
|
||||
cargo test -p openless-linux-egui --locked
|
||||
cargo run -p openless-linux-egui --example headless_host --locked
|
||||
cargo check -p openless-linux-egui --all-targets --locked
|
||||
pwsh -NoProfile -File "scripts/check-core-deps.ps1"
|
||||
pwsh -NoProfile -File "scripts/check-core-deps.ps1" -Package openless-linux-egui
|
||||
pwsh -NoProfile -File "scripts/check-core-secret-surface.ps1"
|
||||
pwsh -NoProfile -File "scripts/check-core-test-isolation.ps1"
|
||||
pwsh -NoProfile -File "scripts/check-core-runtime-seam.ps1"
|
||||
pwsh -NoProfile -File "scripts/check-linux-public-surface.ps1"
|
||||
```
|
||||
|
||||
headless示例使用fixture,不证明音频/fcitx5/Secret Service可用。上述命令是接手方可复用的验证入口,不表示本次文档更新重新执行过测试。
|
||||
|
||||
插件从仓库根目录构建,在已安装Fcitx5开发依赖的Linux环境执行;build目录使用独立路径:
|
||||
|
||||
```sh
|
||||
cmake -S "openless-all/scripts/linux-fcitx5-plugin" -B "build/linux-fcitx5-contract" -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON
|
||||
cmake --build "build/linux-fcitx5-contract"
|
||||
ctest --test-dir "build/linux-fcitx5-contract" --output-on-failure
|
||||
```
|
||||
|
||||
这验证C++目标合同;不替代真实前台应用输入。保留发行版最低Fcitx版本编译覆盖,避免只在较新本机头文件通过。
|
||||
|
||||
## 3. 必须显式执行的环境测试
|
||||
|
||||
返回仓库的`openless-all/app`目录。以下测试默认ignored,只有在适当的测试账号、运行中服务和设备上才执行:
|
||||
|
||||
```sh
|
||||
cargo test -p openless-linux-egui --test cpal_contract -- --ignored
|
||||
cargo test -p openless-linux-egui --test fcitx5_contract -- --ignored
|
||||
OPENLESS_RUN_SECRET_SERVICE_CONTRACT=1 cargo test -p openless-linux-egui --test secret_service_contract -- --ignored
|
||||
```
|
||||
|
||||
以上为Linux shell命令。Secret Service须在已启动测试keyring的DBus会话中执行;其显式环境开关只授权该次测试。
|
||||
先阅读相应[测试文件](../../openless-all/app/linux-egui/tests):fcitx5测试会修改热键并尝试向焦点目标提交文本,必须使用隔离桌面/测试编辑器,结束后恢复配置;keyring测试会写入并移除测试凭据。
|
||||
CPAL测试允许明确分类的无设备/权限错误,fcitx5部分调用也允许不可用错误;测试绿色不等于录到音频或成功落字,仍需下面的真机流程。不要导出真实密钥。
|
||||
|
||||
| 环境/领域 | 必须记录的实际结果 |
|
||||
| 验证 | 命令 |
|
||||
| --- | --- |
|
||||
| X11与Wayland,声明支持的桌面环境 | 安装/加载插件、后台全局热键、重绑/冲突/重载、主窗与托盘行为;分别声明限制 |
|
||||
| GTK/Qt/浏览器/终端 | 普通/流式Unicode落字、换行、切焦点、目标失效、选区确认/撤回、未知结果不双写 |
|
||||
| 音频 | 真麦克风、切换/拔出、静音恢复、录音archive/重试/重转、Starting至finish全过程取消 |
|
||||
| Provider/vault/model | 锁定与解锁、保存失败恢复、旧数据幂等迁移、真实云请求、Qwen推理与取消/释放 |
|
||||
| QA/Selection Voice/Agent | 多轮、意图/预览、审批/拒绝、旧事件隔离、关闭重开、CLI stdin与进程树清理 |
|
||||
| Remote | 真手机LAN/TLS、PIN更新、首帧、并发隔离、断连/重连、端口冲突与stop后cancel |
|
||||
| deb/rpm/AppImage | 内容/依赖、安装/启动、插件路径、1.x升级数据、卸载/回滚、权限和签名/更新 |
|
||||
| 格式 | `cargo fmt --check`(openless-core / linux-egui / src-tauri) |
|
||||
| Core | `cargo test -p openless-core --locked` |
|
||||
| Linux Host/合同 | `cargo test -p openless-linux-egui --locked` |
|
||||
| Linux 目标编译 | `cargo check -p openless-linux-egui --all-targets --target x86_64-unknown-linux-gnu --locked`(需配置好带 `core/std` 的目标工具链) |
|
||||
| 打包 | `release-linux-egui.yml`(deb/rpm/AppImage,独立 manifest) |
|
||||
|
||||
仅包内文件存在、`--help`成功或CI绿色,不能证明上述完整用户流程。
|
||||
## 3. 真机矩阵(L11/L12)
|
||||
|
||||
## 4. Linux分发与回报
|
||||
- 桌面会话:X11 与 Wayland 分开记录。
|
||||
- 输入:fcitx5 插件热键路由、PRIMARY 选区、落字目标应用矩阵。
|
||||
- 音频:CPAL 设备、系统静音/恢复终态。
|
||||
- 凭据:Secret Service(含锁定/未解锁态)。
|
||||
- 打包:AppImage / deb / rpm 安装、升级、回滚、卸载残留;签名与分发渠道证据。
|
||||
|
||||
已有[Linux打包脚本](../../openless-all/app/scripts/package-linux-egui.sh)和[独立工作流](../../.github/workflows/release-linux-egui.yml)供复用。
|
||||
无正式签名、manual URL或调试签名的制品按验证产物记录,不宣传为正式更新源;只有管理员可按[发布政策](../../RELEASING.md)发布。
|
||||
## 4. 记录格式
|
||||
|
||||
每项关闭记录:`L编号 / commit / OS与桌面版本 / 前提 / 操作 / 期望 / 实际 / 自动日志 / 设备证据 / 仍有限制`。
|
||||
接口不足附最小复现、期望Core方法/事件/错误回报Core负责人;平台实现或UI缺口由egui团队继续负责。
|
||||
`ID / owner / commit / 已完成效果 / 自动证据 / 设备证据 / 剩余限制`。“待实测”不是“未实现”,也不是“已完成”;验收报告按证据分级表述(见[范围](../2.0-requirements.md)第 4 节)。
|
||||
@@ -1,36 +1,26 @@
|
||||
# Linux egui 接入交接
|
||||
# Linux 接入交接总览
|
||||
|
||||
更新:2026-09-06;接口以本目录所在提交的代码为准。初始缺口盘点基于`2b315572`,后续资源生命周期与VoiceState合同更新见01/06。需求依据:[2.0范围](../2.0-requirements.md)。
|
||||
状态:canonical(2026-09-07 以源码为准重写,基线 `openless-linux-egui` + Core `639c2fbf` 后的 beta);更新:2026-09-07。
|
||||
|
||||
本批2.0完整交付Windows/macOS。Linux交付共享Core及接入资料;egui团队负责余下Linux Host、UI、集成和Linux发布验收。
|
||||
已有`linux-egui`代码和包构建是可复用起点,不等于Linux产品已完成。
|
||||
## 1. 责任与范围
|
||||
|
||||
## 阅读顺序
|
||||
Linux Host Adapter 的剩余实现、全局热键、窗口/托盘/权限/更新等宿主效果、egui 界面与 Linux 产品验收由 egui 团队承接;共享业务全部在 `openless-core`,发现业务接口缺项时回报 Core 负责人,不在 UI/Host 复制规则。Linux 产品完成度不阻塞 Windows/macOS 交付(见[范围](../2.0-requirements.md))。
|
||||
|
||||
| 文档 | 回答什么问题 |
|
||||
| --- | --- |
|
||||
| [01 Core接入合同](./01-core-contract.md) | 从哪里接、哪些规则归Core、生命周期和秘密边界 |
|
||||
| [02 缺口与实施顺序](./02-gap-register.md) | 当前Linux还缺什么、由谁做、依赖顺序和关闭标准 |
|
||||
| [03 全局热键与窗口](./03-hotkeys-and-windows.md) | 三类1.x X11热键缺口及原生注册、恢复和窗口效果 |
|
||||
| [04 页面与领域操作](./04-ui-domains.md) | 已有页面能做什么、哪些Core能力尚缺操作入口 |
|
||||
| [05 原生宿主与数据](./05-native-host-and-data.md) | 音频/插入/凭据/模型/进程/托盘/更新的接入与验证 |
|
||||
| [06 事件与会话](./06-events-and-sessions.md) | sequence、重放、Unicode增量、QA/Agent和取消时序 |
|
||||
| [07 验收与证据](./07-acceptance.md) | 自动命令、设备矩阵、Linux发布条件和回报模板 |
|
||||
## 2. 已有起点(直接复用,不另建第二套后端)
|
||||
|
||||
## 工作规则
|
||||
- 组装:`linux-egui/src/backend.rs` — `LinuxBackendBuilder::from_shared_providers(BackendConfig)` + `with_*` 注入(recorder/polisher/inserter/credential store/host actions/settings runtime/local ASR runtime/polish failure policy)。
|
||||
- Host 门面:`lib.rs` `LinuxHost` — `snapshot` / `subscribe` / `save_settings` / `update_settings_strict` / `drain_events` / `backend()`。
|
||||
- 已实现模块:`audio.rs`(CPAL 录制)、`credentials.rs`(Secret Service)、`fcitx5.rs`(输入/选区)、`hotkeys.rs`(fcitx5 热键监听)、`selection.rs`、`qa.rs`、`coding_agent.rs`、`marketplace.rs`、`remote_input.rs`、`settings.rs`、`single_instance.rs`、`capabilities.rs`、`resources.rs`、`runtime.rs`、`ui_state.rs`。
|
||||
- 页面:`ui_state.rs` `Page` 枚举 10 页(Start/Dictation/Qa/Selection/Agent/Services/Models/Remote/History/Settings),主循环 `main.rs`。
|
||||
- 打包:`release-linux-egui.yml`(deb/rpm/AppImage,独立 manifest)。
|
||||
|
||||
- 先复用现有Core和Linux Adapter;不复制Tauri业务、不让UI读取秘密或私有Coordinator状态。
|
||||
- Linux系统功能由团队实现Host Adapter,egui框架只提供UI能力,不会自动注册全局热键或管理系统服务。
|
||||
- Core接口不足:带最小复现、所需业务语义和期望事件回报Core负责人;不要在UI临时补另一套规则。
|
||||
- 文档中的“已有”仅表示本快照有相应代码/合同;设备与发布证据单独确认。
|
||||
- 任务关闭时更新[缺口登记](./02-gap-register.md)及对应小文档,附准确commit、测试和设备结果。
|
||||
## 3. 阅读顺序
|
||||
|
||||
## 入口文件
|
||||
1. [01 Core 合同](01-core-contract.md):可调用的 Core 面与合同文件。
|
||||
2. [02 缺口登记](02-gap-register.md):L01–L12 现状、责任与关闭标准(推进主表)。
|
||||
3. [03 热键与窗口](03-hotkeys-and-windows.md)、[05 原生宿主与数据](05-native-host-and-data.md):接线缺口细节。
|
||||
4. [04 页面与领域](04-ui-domains.md):10 页现状与界面缺口。
|
||||
5. [06 事件与会话](06-events-and-sessions.md):订阅、重放、取消语义。
|
||||
6. [07 验收](07-acceptance.md):两个完成门与证据要求。
|
||||
|
||||
- [Core公开出口](../../openless-all/app/crates/openless-core/src/lib.rs)
|
||||
- [Linux生产factory](../../openless-all/app/linux-egui/src/backend.rs)
|
||||
- [LinuxHost facade](../../openless-all/app/linux-egui/src/lib.rs)
|
||||
- [现有egui主循环/UI](../../openless-all/app/linux-egui/src/main.rs)
|
||||
- [headless示例](../../openless-all/app/linux-egui/examples/headless_host.rs)
|
||||
|
||||
旧的[长接口文档](../linux-egui-backend-contract.md)保留历史参考;范围冲突以新需求和本目录为准。
|
||||
接口细节见[后端契约](../linux-egui-backend-contract.md);长接口与历史实现参考保留在契约文档。
|
||||
@@ -1,51 +1,26 @@
|
||||
# qwen-asr submodule 升级检查清单
|
||||
# qwen-asr 子模块升级清单
|
||||
|
||||
`openless-all/app/src-tauri/vendor/qwen-asr` 是 macOS 本地 Qwen3-ASR 的 C 引擎来源。OpenLess 只从组织 fork `https://github.com/Open-Less/qwen-asr.git` 拉取 submodule;`antirez/qwen-asr` 只作为上游同步来源,不直接进入主仓构建链路。
|
||||
状态:canonical(2026-09-07 以源码为准重写);更新:2026-09-07。
|
||||
|
||||
## 升级原则
|
||||
## 1. 子模块(`.gitmodules`,仓库根)
|
||||
|
||||
- 不直接把 `.gitmodules` 改回个人上游仓库。
|
||||
- 不在未审查 upstream diff 的情况下推进 submodule commit。
|
||||
- 每次升级只推进 submodule 指针;除非编译或 FFI 必需,不混入 OpenLess 主项目逻辑改动。
|
||||
- 保留当前锁定 commit 的可回滚性,必要时能 `git checkout <old-commit> -- openless-all/app/src-tauri/vendor/qwen-asr` 回退。
|
||||
| 子模块 | 路径 | 来源 |
|
||||
| --- | --- | --- |
|
||||
| qwen-asr | `openless-all/app/src-tauri/vendor/qwen-asr` | <https://github.com/Open-Less/qwen-asr.git>(组织 fork;`antirez/qwen-asr` 只作上游同步来源,不直接进构建) |
|
||||
| qwen3-asr-rs | `openless-all/app/src-tauri/vendor/qwen3-asr-rs` | <https://github.com/Open-Less/qwen3_asr_rs.git> |
|
||||
|
||||
## 操作步骤
|
||||
qwen-asr 是 macOS 本地 Qwen3-ASR 的 **C 引擎**,由 src-tauri 构建链接(vendored);qwen3-asr-rs 为 Rust 运行时来源。Linux workspace(openless-core + linux-egui)不包含 src-tauri,不依赖这两个子模块。
|
||||
|
||||
1. 在 `Open-Less/qwen-asr` fork 中同步 upstream。
|
||||
2. 审查 fork 中待引入 commit:
|
||||
- C 源码:`qwen_asr*.c`、`qwen_asr*.h`
|
||||
- 构建脚本 / 模型下载脚本
|
||||
- 新增二进制、大文件、网络下载地址或 shell 命令
|
||||
- FFI API 是否改变:`qwen_load`、`qwen_free`、`qwen_set_token_callback`、`qwen_transcribe_audio`、`qwen_transcribe_stream`
|
||||
- Dictation 行为是否改变:C 后端必须继续使用 stream、发送 `local-asr-token`,并保留 0.5 秒尾部静音收尾;MLX 后端保持 batch
|
||||
3. 在 OpenLess 主仓更新 submodule:
|
||||
```bash
|
||||
git submodule sync --recursive openless-all/app/src-tauri/vendor/qwen-asr
|
||||
git submodule update --init --recursive openless-all/app/src-tauri/vendor/qwen-asr
|
||||
cd openless-all/app/src-tauri/vendor/qwen-asr
|
||||
git fetch origin
|
||||
git checkout <reviewed-commit>
|
||||
cd -
|
||||
```
|
||||
4. 验证 submodule 来源仍是组织 fork:
|
||||
```bash
|
||||
git config --file .gitmodules --get submodule.openless-all/app/src-tauri/vendor/qwen-asr.url
|
||||
git -C openless-all/app/src-tauri/vendor/qwen-asr remote get-url origin
|
||||
git submodule status openless-all/app/src-tauri/vendor/qwen-asr
|
||||
git diff --submodule=log -- openless-all/app/src-tauri/vendor/qwen-asr
|
||||
```
|
||||
5. 至少运行:
|
||||
```bash
|
||||
cd openless-all/app
|
||||
npm run build
|
||||
cargo check --manifest-path src-tauri/Cargo.toml
|
||||
```
|
||||
macOS 发布前还要用 `INSTALL=0 ./scripts/build-mac.sh` 验证 C 源编译和链接。
|
||||
## 2. 升级步骤
|
||||
|
||||
## PR / commit 说明必须包含
|
||||
1. `git submodule update --init --recursive`(先在干净工作区)。
|
||||
2. 在子模块目录 `git fetch && git checkout <目标提交>`(只用 Open-Less fork 的提交;记录提交号)。
|
||||
3. 主仓 `git add` 子模块指针并提交(提交信息注明引擎版本/目的)。
|
||||
4. macOS 构建验证:`src-tauri` 按平台构建通过;如涉及 Metal/工具链,先跑 `npm run check:macos-metal-toolchain`;相关契约:`scripts/macos-compiler-runtime-contract.test.mjs`、`stage-macos-mlx-metallib.mjs`(MLX 路径)。
|
||||
5. CI 注意:`scripts/ci-disable-macos-qwen3.mjs` 控制 macOS CI 中 qwen3 的启用范围,升级后核对其仍符合当前 CI 策略。
|
||||
6. 行为验证:本地 ASR 下载/激活/取消与听写回归(见[桌面验收](2.0-desktop-acceptance.md)本地模型域)。
|
||||
|
||||
- 旧 submodule commit 和新 submodule commit。
|
||||
- 已审查的 upstream commit 范围。
|
||||
- 是否有 FFI API、模型文件结构、下载脚本或构建参数变化。
|
||||
- 是否改变 stream/batch、token 回调、取消门控或尾部静音语义。
|
||||
- 已运行的验证命令和未覆盖的平台。
|
||||
## 3. 纪律
|
||||
|
||||
- 不修改 vendored 引擎源码本身的问题;修复走 fork 上游,再 bump 指针。
|
||||
- 子模块指针升级必须与构建验证同批提交,不留"指针先行"状态。
|
||||
+32
-6
@@ -1,10 +1,36 @@
|
||||
# Tauri CSP 边界
|
||||
|
||||
OpenLess 的桌面 WebView 已在 `openless-all/app/src-tauri/tauri.conf.json` 启用最小 CSP。该策略只放开前端渲染和 Tauri IPC 必需的来源:
|
||||
状态:canonical(2026-09-07 以源码为准重写);更新:2026-09-07。
|
||||
|
||||
- `script-src 'self'`:脚本只允许随应用打包的前端产物;Tauri 会在构建时为自身注入脚本补齐必要 nonce / hash。
|
||||
- `connect-src 'self' ipc: http://ipc.localhost http://localhost:1420 ws://localhost:1420`:允许同源连接、Tauri IPC,以及 Vite 开发模式的本机 HTTP / HMR 连接。
|
||||
- `style-src 'self' 'unsafe-inline' https://fonts.googleapis.com` 与 `font-src https://fonts.gstatic.com`:当前 React 页面大量使用 `style={{ ... }}` 保持设计稿像素对齐,因此短期保留 inline style;外部字体只允许 Google Fonts 相关域名。
|
||||
- `object-src 'none'`、`base-uri 'none'`、`form-action 'none'`、`frame-ancestors 'none'`:关闭插件对象、base URL、表单提交和嵌入入口。
|
||||
桌面 WebView 的 CSP 定义在 `openless-all/app/src-tauri/tauri.conf.json`(`app.security.csp`),当前值为:
|
||||
|
||||
Provider 校验、ASR/LLM 请求、更新检查、本地 ASR 模型下载等网络访问都在 Rust / Tauri plugin 侧执行,不通过 WebView 的 `fetch` 直连外部服务,因此不在 WebView CSP 中放开 provider 域名。QA markdown 的 sanitizer 仍是第一道防线;CSP 只作为纵深防御。
|
||||
```
|
||||
default-src 'self' customprotocol: asset:
|
||||
script-src 'self'
|
||||
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com
|
||||
font-src 'self' https://fonts.gstatic.com
|
||||
img-src 'self' asset: http://asset.localhost blob: data: https://github.com https://avatars.githubusercontent.com
|
||||
connect-src 'self' ipc: http://ipc.localhost http://localhost:1420 ws://localhost:1420
|
||||
media-src 'self' data: asset: http://asset.localhost blob:
|
||||
object-src 'none'
|
||||
base-uri 'none'
|
||||
form-action 'none'
|
||||
frame-ancestors 'none'
|
||||
```
|
||||
|
||||
## 放开项与用途
|
||||
|
||||
| 指令 | 用途 |
|
||||
| --- | --- |
|
||||
| `default-src 'self' customprotocol: asset:` | 仅打包产物与 Tauri asset 协议 |
|
||||
| `script-src 'self'` | 脚本只允许应用自带前端产物(Tauri 构建时注入 nonce/hash) |
|
||||
| `style-src` + Google Fonts | 界面样式与在线字体 |
|
||||
| `img-src` 的 github/avatars | GitHub OAuth 头像(`GithubLoginModal`);`asset:/asset.localhost/blob:/data:` 服务于本地资源与音频波形 |
|
||||
| `connect-src` 的 `ipc:/ipc.localhost` | Tauri IPC;`localhost:1420` 仅为 Vite 开发/HMR |
|
||||
| `media-src` | 录音回放与提示音 |
|
||||
|
||||
`object-src`、`base-uri`、`form-action`、`frame-ancestors` 全部关闭。
|
||||
|
||||
## 修改规则
|
||||
|
||||
新增外部来源必须先确认用途与最小化范围(只加到对应指令),不得放宽 `object-src/base-uri/form-action/frame-ancestors`;改动随 `npm test` 与安全审查一起验证。
|
||||
+12
-43
@@ -1,50 +1,19 @@
|
||||
# OpenLess 火山 ASR 配置
|
||||
# 火山引擎(volcengine)ASR 配置
|
||||
|
||||
1. 登录火山引擎
|
||||
<https://console.volcengine.com/auth/login/>
|
||||
状态:canonical(2026-09-07 以源码为准重写);更新:2026-09-07。
|
||||
|
||||

|
||||
## 1. 代码中的定义
|
||||
|
||||
2. 创建旧版应用
|
||||
创建时勾选 `豆包流式语音识别模型2.0 小时版`
|
||||
<https://console.volcengine.com/speech/app?opt=create>
|
||||
- Provider:`volcengine`(labelKey `asrVolcengine`),定义于 Core `provider_rules.rs`;`authRequirement = Volcengine`(专用鉴权形态),无内置默认端点/模型(`defaultEndpoint` / `defaultModel` 为空,按通道配置)。
|
||||
- 验证探针:`asr_silence_allows_no_final`(静音段允许无 final 帧,验证以可取消的静音探测完成)。
|
||||
- 凭据字段(`provider_rules.rs:300-302`):`volcengine_auth_mode`(鉴权模式,如 ApiKey/官方端点模式)、`volcengine_app_key`、`volcengine_access_key`(布尔项 + 模式选择;具体取值在设置界面录入,凭据走系统安全存储,不落明文)。
|
||||
|
||||

|
||||
## 2. 在应用内配置
|
||||
|
||||
3. 打开豆包流式语音识别模型 2.0 管理页
|
||||
`APP ID` 和 `Access Token` 在页面最下方
|
||||
<https://console.volcengine.com/speech/service/10038?AppID=&opt=create>
|
||||
设置 → AI 服务 → 语音识别 → 添加渠道,选择火山引擎;按界面提示填入鉴权字段,保存后执行“验证”得到真实验证结果(成功/失败与时间会记录在渠道列表)。
|
||||
|
||||

|
||||
## 3. 端点与排错
|
||||
|
||||
4. 复制到 OpenLess 的 `Settings` 页面
|
||||
|
||||
打开:
|
||||
|
||||
`Settings -> Providers -> ASR`
|
||||
|
||||

|
||||
|
||||
填这两个:
|
||||
|
||||
- `APP ID`
|
||||
- `Access Token`
|
||||
|
||||
不用填:
|
||||
|
||||
- `Secret Key`
|
||||
|
||||
## 新版控制台(API Key 方式)
|
||||
|
||||
新版豆包语音控制台统一使用单个 `API Key` 鉴权,无需 `APP ID` / `Access Token`(旧版应用方式见上文)。
|
||||
|
||||
1. 在新版语音控制台创建 API Key
|
||||
<https://console.volcengine.com/speech/new/setting/apikeys>
|
||||
|
||||
2. 在 OpenLess 的 `Settings -> Providers -> ASR` 中:
|
||||
|
||||
- 鉴权模式选择「新版控制台 API Key」
|
||||
- 填入上一步创建的 `API Key`
|
||||
- `Resource ID` 保持默认 `volc.seedasr.sauc.duration`(豆包流式语音识别模型 2.0 · 小时版)
|
||||
|
||||
新旧两种模式共享同一 WebSocket 端点(`wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async`),仅握手鉴权头不同(新版为 `X-Api-Key` 单头)。官方接口文档:<https://www.volcengine.com/docs/6561/1354869>
|
||||
- ApiKey 模式使用火山官方实时 ASR 端点(历史修复 #931 后的行为,以 `crates/openless-core/src/asr/volcengine.rs` 当前实现为准)。
|
||||
- 弱网行为:连接超时与重试在 Host/Core 实现,失败信息展示在渠道验证结果中。
|
||||
- 开通服务、创建应用与获取密钥属火山控制台操作,以[火山官方文档](https://www.volcengine.com/docs)为准;本仓库只维护代码行为。
|
||||
+12
-32
@@ -1,39 +1,19 @@
|
||||
# OpenLess 讯飞 ASR 配置
|
||||
# 讯飞(iflytek)ASR 配置
|
||||
|
||||
OpenLess 支持讯飞开放平台的**实时语音转写(RTASR,标准版)**,用于把录音实时转写成文字。
|
||||
状态:canonical(2026-09-07 以源码为准重写);更新:2026-09-07。
|
||||
|
||||
## 所需服务
|
||||
## 1. 代码中的定义
|
||||
|
||||
- 服务:讯飞开放平台 **实时语音转写(标准版)**
|
||||
- 接口文档:<https://www.xfyun.cn/doc/asr/rtasr/API.html>
|
||||
- Provider:`iflytek`(labelKey `asrIflytek`),`authRequirement = Xfyun`;无内置默认端点/模型。
|
||||
- 凭据(`crates/openless-core/src/asr/xfyun.rs:62-69`):`app_id` + `api_key`(两者非空才视为已配置);连接时以 `app_id + timestamp` 计算 `signa` 签名(`compute_signa`,xfyun.rs:637)。
|
||||
- 验证探针:`asr_silence_allows_no_final`。
|
||||
- 对接的是讯飞开放平台实时语音转写(RTASR);协议细节以 `asr/xfyun.rs` 当前实现为准。
|
||||
|
||||
> 说明:录音文件转写(IFASR)、实时语音转写大模型(202 方言 / 37 语种免切)目前
|
||||
> **未接入**。若你有这些需求,欢迎在 issue #842 留言。
|
||||
## 2. 在应用内配置
|
||||
|
||||
## 凭证获取
|
||||
设置 → AI 服务 → 语音识别 → 添加渠道,选择讯飞;填入 AppID 与 API Key,保存后执行“验证”。验证结果(成功/失败与时间)显示在渠道列表。
|
||||
|
||||
1. 登录讯飞开放平台:<https://www.xfyun.cn/>
|
||||
2. 控制台 → 我的应用 → 创建应用,添加 **实时语音转写** 服务(需先在服务页领取免费额度或购买)。
|
||||
3. 在服务管理页查看并复制 **AppID** 与 **API Key**。
|
||||
4. 打开 OpenLess `Settings → Providers → ASR`,选择「讯飞实时语音转写」,填入 AppID 与 API Key。
|
||||
## 3. 开通与排错
|
||||
|
||||
## 音频约束
|
||||
|
||||
- 采样率 16 kHz、位深 16 bit、单声道 PCM(与 OpenLess 录音输出完全一致,无需转码)。
|
||||
- 支持中文普通话、中英混合与英文;方言/小语种需在讯飞控制台开通后由 `lang` 参数指定,
|
||||
首期 OpenLess 固定使用中文普通话(`lang=cn`)。
|
||||
- IP 白名单:若在讯飞控制台开启了 IP 白名单,请把运行 OpenLess 机器的公网 IP 加入,
|
||||
否则握手会返回 `10105 illegal access`。
|
||||
|
||||
## 已知限制
|
||||
|
||||
- 标准版 RTASR **没有请求参数级热词**:OpenLess 的词典热词不会传给讯飞,个性化热词
|
||||
请在讯飞控制台上传。
|
||||
- 实时出字由服务端驱动:OpenLess 的胶囊 UI 在收尾阶段一次性展示最终文本(与其它
|
||||
流式 provider 行为一致)。
|
||||
|
||||
## 排障
|
||||
|
||||
- `10105` / `10110`:AppID / API Key 错误、IP 白名单未放行,或账号未开通实时语音转写服务。
|
||||
- `10800`:超过授权连接数(并发路数已满)。
|
||||
- `37005`:超过 15 秒未发送音频(弱网时偶发,可重试)。
|
||||
- 服务开通、AppID/API Key 获取在[讯飞开放平台控制台](https://www.xfyun.cn/)完成(外部平台操作,以官方文档为准)。
|
||||
- 鉴权失败多为 AppID 与 Key 不配套或时间偏差;渠道验证结果会给出可读错误。
|
||||
Reference in new issue
Block a user