14 KiB
OpenLess 2.0 架构
状态:canonical,当前实现说明;更新:2026-09-23。平台范围见 2.0 需求,文件定位见 目录结构。
1. 分层与工作区
应用开发与构建源在 openless-all/app/。下文源码路径以该目录为基准。根 Cargo workspace 成员为 crates/openless-core + linux-egui;src-tauri(及其 backend-tests 测试 crate)被 exclude,独立构建。Core 是与 Host 同进程的业务库。
| 层 | 位置 | 职责 |
|---|---|---|
| 界面(Win/mac/Android) | src/(React/TypeScript/i18next,八种界面语言) |
页面、设置、窗口分支;调用 typed IPC,展示快照与事件 |
| Host(Win/mac/Android) | src-tauri/(crate openless) |
src/lib.rs 注册命令;适配窗口、热键、音频、凭据、插入、IME 和生命周期 |
| 共享 Core | crates/openless-core/ |
业务规则、会话、服务调用和数据仓储;通过 trait 接入 Host 能力 |
| Linux Host + UI | linux-egui/(crate openless-linux-egui) |
backend.rs 组装 OpenLessBackend,main.rs 实现 egui/eframe UI,不依赖 Tauri/WebKitGTK |
Android 侧:src-tauri/src/android/(JNI/桥接)+ android/(aidl、kotlin、manifests、frontend);android/frontend 经 Vite 别名 @android 被 src/ 引用;manifest 由 scripts/merge-android-*.mjs 合成。Linux 已有可复用 Host/UI 起点,剩余能力与产品验收见 交接目录。
2. 数据流
flowchart TB
React["React 页面 / 窗口"] --> IPC["src/lib/ipc · typed wrapper"]
IPC --> Tauri["Tauri commands / coordinator / core_adapters"]
Egui["egui UI"] --> Linux["LinuxHost / LinuxBackendBuilder"]
Tauri --> Core["OpenLessBackend · Core"]
Linux --> Core
Core --> Stores["历史 / 设置 / 词库 / 风格包仓储"]
Core --> Network["云端 provider / 风格包 API"]
Core --> Ports["录音 / 插入 / 凭据 / 本地模型等接口"]
Ports --> Native["Host 原生实现"]
Core --> Events["BackendEvent · 语义事件"]
Events --> Tauri
Events --> Linux
- 桌面: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。 - 语言目录:
contract/language-catalog.json是工作语言原生名、识别代码与 Apple locale 的单一来源;ReactlanguageCatalog.ts和 Corelanguage_catalog.rs读取同一份数据,识别服务仍在运行时判断具体语种是否可用。界面语言独立保存在ol.locale,启动和切换经既有set_remote_locale将解析后的语言同步给原生托盘与手机远程输入页。 - 旧 React command/event 名称只保留在 Tauri 兼容 Adapter;跨平台合同以
contract/backend-2.0.json为准。
启动时,src/App.tsx 经 src/lib/ipc/shared.ts 请求 get_startup_snapshot,校验合同版本和 backend 运行状态后进入业务界面。Core 事件定义在 events.rs,Tauri 的转译入口为 src-tauri/src/tauri_events.rs,Linux 直接订阅类型化事件。
听写主链由 dictation_engine.rs 管理:触发会话 → 录音/ASR → 清理与润色 → Host 插入 → 历史与事件。Tauri 在 coordinator/dictation_core.rs 接入该链路;本地 ASR 的模型管理归 Core,原生执行实现分别位于 Host。取消、失败和旧会话事件处理也属于该业务链,而不是页面各自实现。
Android 悬浮窗在录音中转入追问时,Host 先捕获原选区,Core 的 stop_dictation_for_qa 按原会话 ID 完成转写并释放听写资源,跳过听写润色与文字插入,再由 QaApi::submit_captured_text 接收已录问题和选区。重复手势由 Host 的异步锁合并;QA 不重新抓取已经变化的前台选区。
3. Core 模块地图(按域,见 src/lib.rs pub mod 清单)
- 听写链路:
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 注入点
操作系统集成由 Host 提供。Core 的 ports.rs、config.rs、domains.rs 和 credentials.rs 定义录音、插入、任务执行、系统动作、领域运行时及凭据接口;Core 自身仍包含 HTTP 调用和框架无关的文件仓储。
Tauri 在 src-tauri/src/coordinator.rs 构造 Core,core_adapters.rs 组装原生依赖并对接已有 persistence。Linux 在 linux-egui/src/backend.rs 使用 LinuxBackendBuilder,注入音频、凭据、服务、设置、本地 ASR 和平台动作。BackendConfig 由 Host 提供数据、缓存、资源路径与平台能力。
业务规则缺失时应修复 Core;平台能力缺失时修复对应 Host。设置值、测试 fixture 或 Unsupported 实现不能代表原生能力已就绪。
5. 窗口体系
src-tauri/tauri.conf.json 声明 main、capsule 两个窗口。src/main.tsx 读取 ?window=,src/App.tsx 按类型加载胶囊、qa、selection-polish-preview、selection-voice-intent、less-computer 和 less-computer-glow;未指定类型时进入主界面。各 WebView 共用前端入口,重页面按需加载;移动端再依据平台能力选择布局。Linux 单实例由 linux-egui/src/single_instance.rs 守护并转发启动意图。
主窗口默认逻辑尺寸为 1300×835,允许用户调整;macOS 原生窗口按钮左侧和顶部均留出 16px,前端保留 44px 拖动区。桌面侧栏宽 226px,主内容从版本行下方开始,设置面板单独限制高度并在内部滚动。
Siri、Classic、Typeless 三种胶囊共用 Core 的 CapsuleStyle,窗口尺寸与点击范围在保存偏好时同步。胶囊按显示器工作区底部定位,避开未自动隐藏的 Dock/任务栏;可见期间重新检查工作区。带正文的浮窗使用不透明底色,聊天面板另叠加细噪点纹理,圆角外部仍保留透明区域。
思考动画覆盖转写、润色和原生文字写入,输入完成后才收尾。macOS 流式键盘输入由会话内串行 worker 维护原控件和累计 UTF-16 终点;每批发送后不再等待 AX 长确认,finish/cancel 在已接收写入之后等待最终屏障,再恢复输入源。AX 仅读选区范围,不读正文;采用 250 ms 无进展预算和 10 秒总预算。不可读、提交型 Return 或预算耗尽时明确降级到按键已发送语义,不重新粘贴已发送文字。目标应用的实际输入表现仍需设备验收。
Less Computer 面板将听写与直接语音提交分开:麦克风把转写填入草稿供编辑,语音模式和快捷键可直接提交给 Agent。Core 的 voice_state 事件携带会话 ID、模式、实时转写及收尾结果;波形使用实际音量采样。停止和取消均绑定指定会话,延迟请求不能结束下一段录音;开麦与文字发送互斥。工具过程默认折叠,运行状态只在真实活动步骤显示动效,右侧工作台汇总当前轮次;历史和多会话仍标为暂不可用。登录弹窗打开时,听写结果只更新草稿,不抢走弹窗或授权浏览器的焦点。
选区直接润色在捕获文字和原输入目标后显示处理中提示,重复快捷键的 Busy 返回不覆盖该提示。已有语音选区入口在松开快捷键后继续显示思考动画,直到处理/替换完成,或交给确认和预览面板。录音提示音的 Web Audio context 在恢复超时或音频时钟冻结时丢弃并最多重试一次,重试沿用原请求的取消和迟到边界。
界面启动等待所选语言资源就绪;语言选择持久化到 ol.locale,其他 WebView 通过存储事件同步,日期、数字和默认风格展示随语言变化。用户修改的风格名称、说明和内容保持原文。旧版两种强制排版字段只保留数据兼容,界面清除其布局效果,窄屏改由响应式布局处理。
6. 存储与外部服务
| 数据或连接 | 所有者与源码入口 |
|---|---|
| 历史、活动、偏好、词库、纠错、风格包 | Core 对应仓储模块;Tauri src-tauri/src/persistence/ 提供平台路径及兼容存储适配 |
| 模型与录音文件 | Core model_store.rs、Host 本地运行时和 persistence/paths.rs;录音归档受设置控制 |
| 服务凭据 | Core CredentialStore 合同,Tauri keyring/Android Keystore 或 Linux credentials.rs 适配 |
| 云端 ASR / LLM | Core provider 目录、选择与传输模块;平台本地引擎位于 src-tauri/src/asr/local/ 或 Linux Host |
| 风格包市场 | Core marketplace.rs 管理 HTTP、GitHub device flow 与本地安装;地址由 MarketplaceConfig 注入,内置默认值在该模块 |
| 加密云同步 | Core cloud_sync_e2ee 复用 GitHub 登录并交换独立同步会话,通过 /v1/... 保存客户端加密快照;protocol/documents/store 分别负责加密协议、登记与合并、仓库及系统凭据的受控恢复。默认关闭,凭据、本地基线和回滚日志不交给 UI。详情与验证边界见 加密云同步客户端 |
| 旧手动同步 | cloud_sync.rs 和 /me/sync 保留有限明文快照合同;旧入口不上传新加密文档中的服务密钥。已注册加密恢复 gate 的仓库拒绝旧多文件恢复,防止绕过受控恢复;未注册的旧 Host 保留原合同。见 旧同步合同 |
| 风格图标 | React src/lib/stylePackIcon.ts 清理上传的 SVG 并转成 PNG;set_style_pack_icon / read_style_pack_icon 经 Core style_pack_store.rs 保存资源、校验读取范围并返回图片 data URL。图标沿用 ZIP 的 64 KiB 限制,与风格包一起导出 |
| 局域网手机输入 | Core remote_input_service.rs 定义共享业务,Tauri remote_server/ 提供本机网络入口和网页资源 |
应用不会把普通听写交给风格包市场后端。市场安装完成后使用本地风格包;官网也不参与应用的业务调用。
模型远程元数据只在下载详情选择模型时读取,界面按模型与镜像合并请求并缓存五分钟。Sherpa 离线解码通过 src-tauri/src/asr/local/blocking_decode.rs 串行运行;超时或取消不会提前释放原生任务持有的模型和许可,任务结束后才允许下一次解码。长期参考数据、训练准备和历史快照不是 Core 的在线训练服务。
macOS 凭据在 persistence/credentials.rs 使用单个 credentials.v2 钥匙串项目;首次迁移读取旧分块一次并保留旧项目供旧版本使用。读取失败返回错误,不能降级成“未配置”;ASR 配置状态从同一次成功读取的快照生成。其他平台保留各自存储限制与适配。
7. 验证入口
以下命令均在 openless-all/app/ 执行,按变更范围选择:
| 范围 | 命令与依据 |
|---|---|
| 前端与合同 | npm test;pretest 先构建,scripts/frontend-test-runner.mjs 发现前端测试和脚本合同检查,含 Core 快捷键回归 |
| Core/Linux 格式 | cargo fmt --all --check,仅根 workspace |
| Core | cargo test -p openless-core --locked |
| Linux Host | cargo test -p openless-linux-egui --locked;原生能力在 Linux 目标环境验证 |
| Tauri 格式与编译 | cargo fmt --manifest-path src-tauri/Cargo.toml --check、cargo check --locked --manifest-path src-tauri/Cargo.toml |
| Tauri 库与独立回归 crate | 依平台选 cargo test --locked --manifest-path src-tauri/Cargo.toml --lib、cargo test --locked --manifest-path src-tauri/backend-tests/Cargo.toml;适用矩阵见 CI |
源码构建 Tauri 前执行 git submodule update --init --recursive。其 manifest 含受 target 条件控制的本地 path 依赖,Cargo 解析仍需对应子模块;部分 CI 作业通过专用脚本去除非目标依赖。Core/Linux workspace 排除 Tauri,不需要为这些独立检查初始化 Tauri 子模块。