mirror of
https://github.com/Melorise/TamaASR.git
synced 2026-11-07 14:39:32 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16ddf53186 | ||
|
|
cc4d747a3a | ||
|
|
88a91892d9 | ||
|
|
e6f67be337 | ||
|
|
cbe34e91ef | ||
|
|
8f8244af05 | ||
|
|
6ea57c6375 | ||
|
|
adfa4df236 | ||
|
|
cd3fc9a7d7 |
@@ -204,8 +204,10 @@ jobs:
|
||||
path: release
|
||||
merge-multiple: true
|
||||
|
||||
- name: 附带构建使用的 flake.lock
|
||||
run: cp flake.lock release/flake.lock
|
||||
- name: 附带发行元数据
|
||||
run: |
|
||||
cp flake.lock release/flake.lock
|
||||
cp packaging/arch/PKGBUILD release/PKGBUILD
|
||||
|
||||
- uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
## 项目范围
|
||||
|
||||
MeloASR 是仅面向 Linux 的 Fcitx5 桌面语音输入工具。项目名为 `MeloASR`,分发包名为 `meloasr`,当前版本为 `0.1.11`。
|
||||
MeloASR 是仅面向 Linux 的 Fcitx5 桌面语音输入工具。项目名为 `MeloASR`,分发包名为 `meloasr`,当前版本为 `0.1.15`。
|
||||
|
||||
不得恢复 Windows、PowerShell 或 Tauri 输出路径。发行版差异只能存在于依赖说明和打包配置,不能进入语音输入核心逻辑。
|
||||
|
||||
除非用户另有要求,文档、界面文字和代码注释使用简体中文。
|
||||
|
||||
`AGENTS.md` 与 `CLAUDE.md` 必须始终逐字一致。修改其中任意一份时必须把相同内容同步到另一份,并在提交前使用字节比较确认一致。
|
||||
|
||||
## 核心不变量
|
||||
|
||||
- 网页编辑器中的当前完整内容是唯一可信识别结果。
|
||||
@@ -79,6 +81,14 @@ npm run build:fcitx5
|
||||
ctest --test-dir linux/fcitx5/build --output-on-failure
|
||||
```
|
||||
|
||||
验证必须与实际改动范围对应,禁止为了展示“完整”而机械扩大测试范围:
|
||||
|
||||
- TypeScript、preload 或 renderer 改动运行 `npm test` 和 `npm run build`;
|
||||
- 只有修改 Fcitx5 C++、CMake 构建逻辑或本地协议实现时,才运行 `npm run build:fcitx5` 和 `ctest`;仅同步发行版本号不要求重测未改动的 Fcitx5 逻辑;
|
||||
- FHS 发行版是首要维护目标,FHS 构建或打包问题必须在对应 Debian、Ubuntu、RPM 或 Arch 环境处理,不得默认用 Nix 环境替代;Nix 只用于验证 Nix 打包路径;
|
||||
- 项目依赖未安装时,按 `package-lock.json` 运行 `npm ci`,不能以 `tsc` 或其它工具缺失为由跳过正常验证;
|
||||
- 不得声称添加了未实际添加的测试,也不得把无关环境中的成功构建计入目标平台验证结果。
|
||||
|
||||
最低动态回归:连续三轮;文本缩短、改词和补标点;finish 仅一次;cancel/reset/disconnect;ready gate;登录探测;后端切换;多屏位置和键盘微调;托盘退出/重启;自启动;GTK、Qt、Chromium 和终端;X11 与 Wayland。
|
||||
|
||||
不要声称没有真实验证的发行版包或元宝端到端识别已经通过。
|
||||
@@ -87,6 +97,26 @@ ctest --test-dir linux/fcitx5/build --output-on-failure
|
||||
|
||||
deb、rpm、Arch/pacman 和 Nix 都必须包含 Electron 应用、desktop/icon/autostart 文件和 Fcitx5 addon。桌面 ID、包名、Socket、addon 名称和版本保持一致。仓库地址和许可证确定前,不得把占位值作为正式发行元数据。
|
||||
|
||||
版本发布时同步 `package.json`、`package-lock.json`、CHANGELOG、AppStream、Fcitx5 addon 版本和各发行版打包元数据,并运行 `scripts/release-metadata.mjs <version>` 校验发布说明。
|
||||
|
||||
`v*` Git 标签由远程 Release 工作流产生。除非用户明确要求,本地禁止创建、移动或删除发布标签,也禁止推送提交或标签。“更新版本”或“更新发版”本身不等于授权本地打标签。
|
||||
|
||||
## 工程判断与性能修改
|
||||
|
||||
- 先区分稳定对象与对象内部的可变内容。一次语音会话中编辑器根元素视为稳定对象,可以缓存;识别文本所在的子树内容持续变化,必须重新序列化完整结果。
|
||||
- 不得用没有实际证据的极端场景否定正常工程假设,例如假设同一录音会话中的编辑器根元素会被持续重建。若稳定对象意外失效,应采用明确失败策略,不能为猜测场景增加常态热路径开销。
|
||||
- 性能结论必须区分静态代码分析与真实测量。没有 profiler、计时或内存采样时,不得给出伪精确的性能收益。
|
||||
- 优化热路径时先消除重复 DOM 查询、重复序列化、重复 IPC 和无意义轮询,不得破坏网页常驻、实时启动录音和完整文本同步这些产品前提。
|
||||
|
||||
## 工作边界与交付检查
|
||||
|
||||
- 开始操作前确认实际 Git 仓库根目录,不能仅依赖外层工作区路径。
|
||||
- 用户要求实现修改时,应完成代码、对应验证、版本与 CHANGELOG 更新等明确要求;遇到缺失依赖要解决或准确报告真实阻塞,不能自行缩减交付范围。
|
||||
- 不得为了反驳用户而引入没有证据的假设。用户指出实现与事实矛盾时,应先核对代码链路并修正判断。
|
||||
- 只修改任务相关文件,不运行与改动无关的构建,不擅自扩大发布、标签、推送或外部操作范围。
|
||||
- 提交前检查 `git diff --check`、发行元数据校验和对应测试;提交后检查 `git status --short`、提交内容和工作树是否干净。
|
||||
- 只要修改了仓库文件,就必须在本轮创建对应 Git commit,不得留下未提交改动;如果不准备或不能提交,就不得修改文件。Tag 和 push 仍需分别获得用户明确授权,不能从修改或 commit 推定。
|
||||
|
||||
## 提交约定
|
||||
|
||||
按功能粒度提交,只包含相关文件。提交前运行对应验证。AI 辅助提交尾部统一添加:
|
||||
|
||||
@@ -2,6 +2,34 @@
|
||||
|
||||
本项目遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/) 的格式。
|
||||
|
||||
## [0.1.15] - 2026-08-26
|
||||
|
||||
### 修复
|
||||
|
||||
- Arch PKGBUILD 改为直接下载 GitHub Release 标签对应的源码归档,不再要求发布者或用户手动放置源码包。
|
||||
- 修正 GitHub 标签归档的解压目录处理,使 `makepkg` 可以直接完成构建、测试和打包。
|
||||
- 远程 Release 现在附带当前版本的 `PKGBUILD`,Arch 用户无需再从源码树中单独提取打包文件。
|
||||
|
||||
## [0.1.14] - 2026-08-26
|
||||
|
||||
### 性能
|
||||
|
||||
- 语音会话开始时缓存网页编辑器根元素,后续文本更新直接序列化该元素,避免每次更新重复查询 DOM、计算样式和读取布局。
|
||||
- 复用会话启动时的初始文本序列化结果,减少一次重复的完整编辑器遍历。
|
||||
|
||||
## [0.1.13] - 2026-08-24
|
||||
|
||||
### 变更
|
||||
|
||||
- 设置页改为按需创建,关闭窗口后立即销毁其 renderer 以降低常驻内存。
|
||||
- 缩小设置页默认与最小窗口尺寸,使其匹配当前功能密度。
|
||||
|
||||
## [0.1.12] - 2026-08-23
|
||||
|
||||
### 变更
|
||||
|
||||
- 全平台启动时强制禁用 Electron GPU 硬件加速,降低后台网页 renderer 的无效显存与内存占用。
|
||||
|
||||
## [0.1.11] - 2026-08-23
|
||||
|
||||
### 变更
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
## 项目范围
|
||||
|
||||
MeloASR 是仅面向 Linux 的 Fcitx5 桌面语音输入工具。项目名为 `MeloASR`,分发包名为 `meloasr`,当前版本为 `0.1.11`。
|
||||
MeloASR 是仅面向 Linux 的 Fcitx5 桌面语音输入工具。项目名为 `MeloASR`,分发包名为 `meloasr`,当前版本为 `0.1.15`。
|
||||
|
||||
不得恢复 Windows、PowerShell 或 Tauri 输出路径。发行版差异只能存在于依赖说明和打包配置,不能进入语音输入核心逻辑。
|
||||
|
||||
除非用户另有要求,文档、界面文字和代码注释使用简体中文。
|
||||
|
||||
`AGENTS.md` 与 `CLAUDE.md` 必须始终逐字一致。修改其中任意一份时必须把相同内容同步到另一份,并在提交前使用字节比较确认一致。
|
||||
|
||||
## 核心不变量
|
||||
|
||||
- 网页编辑器中的当前完整内容是唯一可信识别结果。
|
||||
@@ -79,6 +81,14 @@ npm run build:fcitx5
|
||||
ctest --test-dir linux/fcitx5/build --output-on-failure
|
||||
```
|
||||
|
||||
验证必须与实际改动范围对应,禁止为了展示“完整”而机械扩大测试范围:
|
||||
|
||||
- TypeScript、preload 或 renderer 改动运行 `npm test` 和 `npm run build`;
|
||||
- 只有修改 Fcitx5 C++、CMake 构建逻辑或本地协议实现时,才运行 `npm run build:fcitx5` 和 `ctest`;仅同步发行版本号不要求重测未改动的 Fcitx5 逻辑;
|
||||
- FHS 发行版是首要维护目标,FHS 构建或打包问题必须在对应 Debian、Ubuntu、RPM 或 Arch 环境处理,不得默认用 Nix 环境替代;Nix 只用于验证 Nix 打包路径;
|
||||
- 项目依赖未安装时,按 `package-lock.json` 运行 `npm ci`,不能以 `tsc` 或其它工具缺失为由跳过正常验证;
|
||||
- 不得声称添加了未实际添加的测试,也不得把无关环境中的成功构建计入目标平台验证结果。
|
||||
|
||||
最低动态回归:连续三轮;文本缩短、改词和补标点;finish 仅一次;cancel/reset/disconnect;ready gate;登录探测;后端切换;多屏位置和键盘微调;托盘退出/重启;自启动;GTK、Qt、Chromium 和终端;X11 与 Wayland。
|
||||
|
||||
不要声称没有真实验证的发行版包或元宝端到端识别已经通过。
|
||||
@@ -87,6 +97,26 @@ ctest --test-dir linux/fcitx5/build --output-on-failure
|
||||
|
||||
deb、rpm、Arch/pacman 和 Nix 都必须包含 Electron 应用、desktop/icon/autostart 文件和 Fcitx5 addon。桌面 ID、包名、Socket、addon 名称和版本保持一致。仓库地址和许可证确定前,不得把占位值作为正式发行元数据。
|
||||
|
||||
版本发布时同步 `package.json`、`package-lock.json`、CHANGELOG、AppStream、Fcitx5 addon 版本和各发行版打包元数据,并运行 `scripts/release-metadata.mjs <version>` 校验发布说明。
|
||||
|
||||
`v*` Git 标签由远程 Release 工作流产生。除非用户明确要求,本地禁止创建、移动或删除发布标签,也禁止推送提交或标签。“更新版本”或“更新发版”本身不等于授权本地打标签。
|
||||
|
||||
## 工程判断与性能修改
|
||||
|
||||
- 先区分稳定对象与对象内部的可变内容。一次语音会话中编辑器根元素视为稳定对象,可以缓存;识别文本所在的子树内容持续变化,必须重新序列化完整结果。
|
||||
- 不得用没有实际证据的极端场景否定正常工程假设,例如假设同一录音会话中的编辑器根元素会被持续重建。若稳定对象意外失效,应采用明确失败策略,不能为猜测场景增加常态热路径开销。
|
||||
- 性能结论必须区分静态代码分析与真实测量。没有 profiler、计时或内存采样时,不得给出伪精确的性能收益。
|
||||
- 优化热路径时先消除重复 DOM 查询、重复序列化、重复 IPC 和无意义轮询,不得破坏网页常驻、实时启动录音和完整文本同步这些产品前提。
|
||||
|
||||
## 工作边界与交付检查
|
||||
|
||||
- 开始操作前确认实际 Git 仓库根目录,不能仅依赖外层工作区路径。
|
||||
- 用户要求实现修改时,应完成代码、对应验证、版本与 CHANGELOG 更新等明确要求;遇到缺失依赖要解决或准确报告真实阻塞,不能自行缩减交付范围。
|
||||
- 不得为了反驳用户而引入没有证据的假设。用户指出实现与事实矛盾时,应先核对代码链路并修正判断。
|
||||
- 只修改任务相关文件,不运行与改动无关的构建,不擅自扩大发布、标签、推送或外部操作范围。
|
||||
- 提交前检查 `git diff --check`、发行元数据校验和对应测试;提交后检查 `git status --short`、提交内容和工作树是否干净。
|
||||
- 只要修改了仓库文件,就必须在本轮创建对应 Git commit,不得留下未提交改动;如果不准备或不能提交,就不得修改文件。Tag 和 push 仍需分别获得用户明确授权,不能从修改或 commit 推定。
|
||||
|
||||
## 提交约定
|
||||
|
||||
按功能粒度提交,只包含相关文件。提交前运行对应验证。AI 辅助提交尾部统一添加:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# MeloASR
|
||||
|
||||
大模型的进步推动了自然语言处理的进化,语音输入正在成为主流。在其它桌面平台下,微信输入法,豆包输入法,千问输入法等支持语音输入甚至以此为卖点的新兴中文输入法正在成为主流。
|
||||
大模型的进步推动了自然语言处理的进化,语音输入正在成为主流。在其它桌面平台下,微信输入法,豆包输入法,千问输入法等支持语音输入甚至以此为卖点的新兴中文输入法层出不穷。
|
||||
而在linux下,相关领域却长期空白,现有实现主要聚焦于以下几种类型:
|
||||
|
||||
|
||||
api调用——调用各大ai厂商的语音转文字api。显然,该方案最大的问题在于——要钱。
|
||||
api调用——调用各大ai厂商的语音转文字api。显然,该方案最大的问题在于——要钱。
|
||||
本地模型——识别准确率低,技术落后,和其他平台的能实时校正的ai语音输入根本不是一个量级的东西,尤其是针对中文环境。
|
||||
|
||||
我们注意到,很多大模型的网页对话窗口,都支持语音输入,而且完全免费——因为语音输入最后只是输入到文本框,并没有发送实际的对话请求,而大模型就算是付费也往往是根据对话请求来计费的。
|
||||
|
||||
Generated
+3
-3
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1787360063,
|
||||
"narHash": "sha256-dt4WdcvsA8/RCe+VZZwqU0X+XMM3wBbGCWA0/sFWzGo=",
|
||||
"lastModified": 1787498568,
|
||||
"narHash": "sha256-9i/VTdusq/+NM/tz+J1Re+ojkMB8MBf0QshnYfzHz30=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2c423e03bbafcff28bfadc6781a4a8257f205cb5",
|
||||
"rev": "56c02bc00adcf003215cc4bd996d6efaf4cff188",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(meloasr-fcitx5 VERSION 0.1.11 LANGUAGES CXX)
|
||||
project(meloasr-fcitx5 VERSION 0.1.15 LANGUAGES CXX)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(CTest)
|
||||
|
||||
@@ -6,7 +6,7 @@ Comment[zh_CN]=将 MeloASR 网页后端连接到当前输入上下文
|
||||
Type=SharedLibrary
|
||||
Library=@MELOASR_LIBRARY_PATH@
|
||||
Category=Module
|
||||
Version=0.1.11
|
||||
Version=0.1.15
|
||||
OnDemand=False
|
||||
Configurable=False
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "meloasr",
|
||||
"version": "0.1.11",
|
||||
"version": "0.1.15",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "meloasr",
|
||||
"version": "0.1.11",
|
||||
"version": "0.1.15",
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.0",
|
||||
"electron": "42.3.0",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "meloasr",
|
||||
"productName": "MeloASR",
|
||||
"version": "0.1.11",
|
||||
"version": "0.1.15",
|
||||
"private": true,
|
||||
"author": "MeloASR contributors",
|
||||
"description": "基于网页语音识别后端和 Fcitx5 的 Linux 实时语音输入工具",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# Linux 分发打包
|
||||
|
||||
MeloASR 0.1.11 的系统包由两部分组成:
|
||||
MeloASR 0.1.15 的系统包由两部分组成:
|
||||
|
||||
- Electron 应用安装到 `/opt/meloasr`,命令入口为 `/usr/bin/meloasr`;
|
||||
- Fcitx5 常驻 addon 由目标发行版中的 CMake/Fcitx5 开发包编译,并安装到该环境报告的 addon 目录。
|
||||
@@ -40,7 +40,7 @@ npm run build:linux:dir
|
||||
packaging/deb/build-deb.sh
|
||||
```
|
||||
|
||||
产物位于 `packaging/out/meloasr_0.1.11_<arch>.deb`。依赖名称按 Debian/Ubuntu 系列填写,但尚未在各发行版完成安装验证。
|
||||
产物位于 `packaging/out/meloasr_0.1.15_<arch>.deb`。依赖名称按 Debian/Ubuntu 系列填写,但尚未在各发行版完成安装验证。
|
||||
|
||||
## rpm
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pkgname=meloasr
|
||||
pkgver=0.1.11
|
||||
pkgver=0.1.15
|
||||
pkgrel=1
|
||||
pkgdesc='Web-backed voice input for Fcitx5'
|
||||
arch=('x86_64')
|
||||
@@ -7,11 +7,13 @@ url='https://github.com/Melorise/MeloASR'
|
||||
license=('MPL2')
|
||||
depends=('fcitx5' 'gtk3' 'nss' 'libxss' 'mesa' 'alsa-lib')
|
||||
makedepends=('nodejs' 'npm' 'cmake' 'gcc' 'nlohmann-json')
|
||||
source=("${pkgname}-${pkgver}.tar.gz")
|
||||
source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
_source_dir="MeloASR-${pkgver}"
|
||||
|
||||
build() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||
cd "${srcdir}/${_source_dir}"
|
||||
npm ci
|
||||
npm run build:linux:dir
|
||||
cmake -S linux/fcitx5 -B build/fcitx5 \
|
||||
@@ -21,13 +23,13 @@ build() {
|
||||
}
|
||||
|
||||
check() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||
cd "${srcdir}/${_source_dir}"
|
||||
npm test
|
||||
ctest --test-dir build/fcitx5 --output-on-failure
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||
cd "${srcdir}/${_source_dir}"
|
||||
|
||||
install -d "${pkgdir}/opt/meloasr"
|
||||
cp -a release/linux-unpacked/. "${pkgdir}/opt/meloasr/"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# Arch Linux 构建
|
||||
|
||||
发布时把项目源码归档为 `meloasr-0.1.11.tar.gz`,与 `PKGBUILD` 放在同一目录,随后运行:
|
||||
远程 Release 会附带当前版本的 `PKGBUILD`。下载后直接运行:
|
||||
|
||||
```bash
|
||||
curl --fail --location --remote-name \
|
||||
https://github.com/Melorise/MeloASR/releases/download/v0.1.15/PKGBUILD
|
||||
makepkg --syncdeps --cleanbuild
|
||||
```
|
||||
|
||||
正式发布必须把 `source` 改为仓库的不可变 release URL,并以 `updpkgsums` 写入真实 SHA-256;当前源码包仍保留 `SKIP`,不能直接作为正式 Arch 发布源。
|
||||
`PKGBUILD` 会根据 `pkgver` 直接下载 GitHub Release 标签对应的源码归档,无需手动下载、重命名或放置源码包。源码地址固定为 `https://github.com/Melorise/MeloASR/archive/refs/tags/v${pkgver}.tar.gz`,因此同一份 `PKGBUILD` 可以重复构建该版本。
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
<binary>meloasr</binary>
|
||||
</provides>
|
||||
<releases>
|
||||
<release version="0.1.15" date="2026-08-26"/>
|
||||
<release version="0.1.14" date="2026-08-26"/>
|
||||
<release version="0.1.13" date="2026-08-24"/>
|
||||
<release version="0.1.12" date="2026-08-23"/>
|
||||
<release version="0.1.11" date="2026-08-23"/>
|
||||
<release version="0.1.10" date="2026-08-23"/>
|
||||
<release version="0.1.9" date="2026-08-23"/>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
project_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
version="${MELOASR_VERSION:-0.1.11}"
|
||||
version="${MELOASR_VERSION:-0.1.15}"
|
||||
arch="${MELOASR_DEB_ARCH:-$(dpkg --print-architecture)}"
|
||||
app_dir="${1:-${MELOASR_ELECTRON_APP_DIR:-${project_dir}/release/linux-unpacked}}"
|
||||
work_dir="${project_dir}/packaging/out/deb-root"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
project_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
version="${MELOASR_VERSION:-0.1.11}"
|
||||
version="${MELOASR_VERSION:-0.1.15}"
|
||||
app_dir="${1:-${MELOASR_ELECTRON_APP_DIR:-${project_dir}/release/linux-unpacked}}"
|
||||
stage_dir="${project_dir}/packaging/out/rpm-root"
|
||||
top_dir="${project_dir}/packaging/out/rpmbuild"
|
||||
|
||||
@@ -36,6 +36,18 @@ tar -C %{buildroot} -xzf %{SOURCE0}
|
||||
/etc/xdg/autostart/meloasr.desktop
|
||||
|
||||
%changelog
|
||||
* Wed Aug 26 2026 MeloASR contributors - 0.1.15-1
|
||||
- Download the Arch package source directly from the matching GitHub release tag.
|
||||
|
||||
* Wed Aug 26 2026 MeloASR contributors - 0.1.14-1
|
||||
- Cache the active web editor for the duration of each speech session.
|
||||
|
||||
* Mon Aug 24 2026 MeloASR contributors - 0.1.13-1
|
||||
- Create the settings renderer only when it is opened and destroy it on close.
|
||||
|
||||
* Sun Aug 23 2026 MeloASR contributors - 0.1.12-1
|
||||
- Disable Electron GPU acceleration for background web renderers.
|
||||
|
||||
* Sun Aug 23 2026 MeloASR contributors - 0.1.11-1
|
||||
- Release only the selected speech backend renderer.
|
||||
- Keep the overlay as a non-interactive session status indicator.
|
||||
|
||||
+3
-3
@@ -10,6 +10,8 @@ import { TrayController } from './tray-controller';
|
||||
import type { BackendStatusPayload, Point } from '../shared/contracts';
|
||||
|
||||
app.setName('MeloASR');
|
||||
app.disableHardwareAcceleration();
|
||||
app.commandLine.appendSwitch('disable-gpu');
|
||||
const singleInstance = app.requestSingleInstanceLock();
|
||||
|
||||
if (!singleInstance) {
|
||||
@@ -37,9 +39,7 @@ async function bootstrap(): Promise<void> {
|
||||
const repositoryUrl = process.env.MELOASR_REPOSITORY_URL?.trim() ||
|
||||
'https://github.com/Melorise/MeloASR';
|
||||
const settingsWindow = new SettingsWindow(settings, backends, overlay, sessions, repositoryUrl);
|
||||
const settingsWindowReady = settingsWindow.create();
|
||||
registerIpc({ settings, autoStart, bridge, backends, overlay, sessions, settingsWindow });
|
||||
await settingsWindowReady;
|
||||
const tray = new TrayController(
|
||||
backends,
|
||||
settingsWindow,
|
||||
@@ -66,7 +66,7 @@ async function bootstrap(): Promise<void> {
|
||||
await backends.ensure(settings.get().backendId);
|
||||
tray.refresh();
|
||||
|
||||
app.on('second-instance', () => settingsWindow.show());
|
||||
app.on('second-instance', () => void settingsWindow.show());
|
||||
app.on('before-quit', () => { (app as typeof app & { isQuitting?: boolean }).isQuitting = true; });
|
||||
app.on('will-quit', () => {
|
||||
tray.destroy();
|
||||
|
||||
+29
-16
@@ -9,6 +9,7 @@ import type { SettingsStore } from './settings-store';
|
||||
|
||||
export class SettingsWindow {
|
||||
window: BrowserWindow | null = null;
|
||||
private creating: Promise<BrowserWindow> | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly settings: SettingsStore,
|
||||
@@ -18,12 +19,23 @@ export class SettingsWindow {
|
||||
private readonly repositoryUrl: string | null
|
||||
) {}
|
||||
|
||||
async create(): Promise<void> {
|
||||
this.window = new BrowserWindow({
|
||||
width: 720,
|
||||
height: 820,
|
||||
minWidth: 640,
|
||||
minHeight: 680,
|
||||
private async ensureWindow(): Promise<BrowserWindow> {
|
||||
if (this.window && !this.window.isDestroyed()) return this.window;
|
||||
if (this.creating) return this.creating;
|
||||
this.creating = this.createWindow();
|
||||
try {
|
||||
return await this.creating;
|
||||
} finally {
|
||||
this.creating = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async createWindow(): Promise<BrowserWindow> {
|
||||
const window = new BrowserWindow({
|
||||
width: 600,
|
||||
height: 650,
|
||||
minWidth: 560,
|
||||
minHeight: 560,
|
||||
show: false,
|
||||
title: 'MeloASR 设置',
|
||||
autoHideMenuBar: true,
|
||||
@@ -35,18 +47,19 @@ export class SettingsWindow {
|
||||
sandbox: true
|
||||
}
|
||||
});
|
||||
this.window.on('close', (event) => {
|
||||
if ((app as typeof app & { isQuitting?: boolean }).isQuitting) return;
|
||||
event.preventDefault();
|
||||
this.window?.hide();
|
||||
this.window = window;
|
||||
window.on('closed', () => {
|
||||
this.overlay.endPositioning();
|
||||
if (this.window === window) this.window = null;
|
||||
});
|
||||
this.window.on('hide', () => this.overlay.endPositioning());
|
||||
await this.window.loadFile(path.join(__dirname, '..', 'renderer', 'settings.html'));
|
||||
await window.loadFile(path.join(__dirname, '..', 'renderer', 'settings.html'));
|
||||
return window;
|
||||
}
|
||||
|
||||
show(): void {
|
||||
this.window?.show();
|
||||
this.window?.focus();
|
||||
async show(): Promise<void> {
|
||||
const window = await this.ensureWindow();
|
||||
window.show();
|
||||
window.focus();
|
||||
this.publish();
|
||||
}
|
||||
|
||||
@@ -60,7 +73,7 @@ export class SettingsWindow {
|
||||
const definition = this.backends.activeDefinition();
|
||||
const decision = resolveDebugLoginAction(Boolean(current.loginNoticeShown[definition.id]), confirmed);
|
||||
if (decision.action === 'prompt') {
|
||||
this.show();
|
||||
await this.show();
|
||||
this.window?.webContents.send('settings:login-notice', { backendLabel: definition.label });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export class TrayController {
|
||||
create(): void {
|
||||
this.tray = new Tray(trayImage('loading'));
|
||||
this.tray.setToolTip('MeloASR · 正在加载');
|
||||
this.tray.on('click', () => this.settingsWindow.show());
|
||||
this.tray.on('click', () => void this.settingsWindow.show());
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export class TrayController {
|
||||
this.tray.setImage(trayImage(state));
|
||||
this.tray.setToolTip(`MeloASR · ${status.detail}`);
|
||||
this.tray.setContextMenu(Menu.buildFromTemplate([
|
||||
{ label: '设置', click: () => this.settingsWindow.show() },
|
||||
{ label: '设置', click: () => void this.settingsWindow.show() },
|
||||
{ label: '打开登录/调试页面', click: () => void this.settingsWindow.openDebug() },
|
||||
{ type: 'separator' },
|
||||
{ label: '重启', click: this.onRestart },
|
||||
|
||||
+10
-7
@@ -11,6 +11,7 @@ const adapter = getBackendWebAdapter(backendId);
|
||||
|
||||
let editorObserver: MutationObserver | undefined;
|
||||
let pageObserver: MutationObserver | undefined;
|
||||
let activeEditor: HTMLElement | null = null;
|
||||
let active = false;
|
||||
let stopping = false;
|
||||
let lastSpeechText = '';
|
||||
@@ -68,12 +69,11 @@ function schedulePageStatus(): void {
|
||||
function emitCurrentEditorText(): void {
|
||||
emitScheduled = false;
|
||||
if (!active || !adapter) return;
|
||||
const editor = adapter.findEditor(document);
|
||||
if (!editor) {
|
||||
if (!activeEditor?.isConnected) {
|
||||
fail(`${adapter.definition.label}输入框在录音期间消失,本轮已停止`);
|
||||
return;
|
||||
}
|
||||
const text = adapter.serialize(editor);
|
||||
const text = adapter.serialize(activeEditor);
|
||||
if (text === lastSpeechText) return;
|
||||
lastSpeechText = text;
|
||||
transcriptRevision += 1;
|
||||
@@ -119,6 +119,7 @@ function resetSession(): void {
|
||||
stopping = false;
|
||||
lastTranscriptAt = 0;
|
||||
editorObserver?.disconnect();
|
||||
activeEditor = null;
|
||||
diagnose('session-reset');
|
||||
}
|
||||
|
||||
@@ -147,28 +148,30 @@ function startSpeech(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
const initialText = adapter.serialize(editor);
|
||||
diagnose('start-targets', {
|
||||
editorLength: adapter.serialize(editor).length,
|
||||
editorLength: initialText.length,
|
||||
microphone: controlSnapshot(microphone)
|
||||
});
|
||||
|
||||
clearTimeout(stopTimer);
|
||||
placeCaretAtEnd(editor);
|
||||
lastSpeechText = adapter.serialize(editor);
|
||||
lastSpeechText = initialText;
|
||||
transcriptRevision = 0;
|
||||
lastTranscriptAt = Date.now();
|
||||
active = true;
|
||||
stopping = false;
|
||||
activeEditor = editor;
|
||||
observeEditor(editor);
|
||||
microphone.click();
|
||||
diagnose('start-clicked', { microphone: controlSnapshot(adapter.findMicrophone(document)) });
|
||||
window.setTimeout(() => diagnose('start-after-250ms', {
|
||||
microphone: controlSnapshot(adapter.findMicrophone(document)),
|
||||
editorLength: adapter.findEditor(document) ? adapter.serialize(adapter.findEditor(document)!).length : null
|
||||
editorLength: active && editor.isConnected ? adapter.serialize(editor).length : null
|
||||
}), 250);
|
||||
window.setTimeout(() => diagnose('start-after-1000ms', {
|
||||
microphone: controlSnapshot(adapter.findMicrophone(document)),
|
||||
editorLength: adapter.findEditor(document) ? adapter.serialize(adapter.findEditor(document)!).length : null
|
||||
editorLength: active && editor.isConnected ? adapter.serialize(editor).length : null
|
||||
}), 1_000);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user