Files
openless/docs/architecture.md
T

14 KiB
Raw Blame History

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 的单一来源;React languageCatalog.ts 和 Core language_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 子模块。

仅 Markdown 变动检查相对链接、源码路径与描述一致性。平台交付按 桌面验收、Linux 验收和 发布规范完成。