From 29809d9cd3a40a9c2c26ae6a91134c228526a58c Mon Sep 17 00:00:00 2001 From: Melorise <0d00@0721.hk> Date: Fri, 4 Sep 2026 17:32:12 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E9=81=BF=E5=85=8D=E5=BF=AB=E9=80=9F?= =?UTF-8?q?=E5=81=9C=E6=AD=A2=E9=87=8D=E6=96=B0=E5=BC=80=E5=90=AF=E7=BD=91?= =?UTF-8?q?=E9=A1=B5=E5=BD=95=E9=9F=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 停止请求早于网页录音状态落地时,等待实际进入录音后再点击一次停止;网页仍在录音或尚未点击停止时不再完成外部会话。 AI-assisted-by: Codex AI-model: gpt-5.6-sol AI-reasoning-effort: medium Co-authored-by: Codex --- src/preload/backend.ts | 23 ++++++++++++++++------- src/preload/speech-session-state.ts | 4 ++++ src/preload/stop-completion.ts | 4 ++-- test/speech-session-state.test.ts | 11 ++++++++++- test/stop-completion.test.ts | 14 +++++++++++--- 5 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/preload/backend.ts b/src/preload/backend.ts index b2524e1..0084a7d 100644 --- a/src/preload/backend.ts +++ b/src/preload/backend.ts @@ -1,7 +1,7 @@ import { ipcRenderer } from 'electron'; import type { BackendControlCommand, BackendPageStatus, BackendTranscriptPayload } from '../backends/contracts'; import { getBackendWebAdapter } from '../backends/web-registry'; -import { shouldClickMicrophoneOnCancel } from './speech-session-state'; +import { shouldClickMicrophoneOnCancel, shouldClickMicrophoneOnStop } from './speech-session-state'; import { shouldFinishAfterStop } from './stop-completion'; const backendId = process.argv @@ -180,9 +180,7 @@ function stopSpeech(): void { if (!active || stopping || !adapter) return; stopping = true; const stoppedAt = Date.now(); - const wasRecordingAtStop = adapter.isRecording(document); - adapter.findMicrophone(document)?.click(); - diagnose('stop-clicked', { microphone: controlSnapshot(adapter.findMicrophone(document)) }); + let stopClicked = false; const finish = (): void => { emitCurrentEditorText(); resetSession(); @@ -192,11 +190,22 @@ function stopSpeech(): void { const checkCompletion = (): void => { if (!active || !stopping || !adapter) return; const now = Date.now(); + const isRecording = adapter.isRecording(document); + if (shouldClickMicrophoneOnStop(isRecording, stopClicked)) { + const microphone = adapter.findMicrophone(document); + if (microphone) { + microphone.click(); + stopClicked = true; + diagnose('stop-clicked', { microphone: controlSnapshot(adapter.findMicrophone(document)) }); + } + stopTimer = setTimeout(checkCompletion, 75); + return; + } if (shouldFinishAfterStop({ elapsedMs: now - stoppedAt, quietMs: now - lastTranscriptAt, - wasRecordingAtStop, - isRecording: adapter.isRecording(document), + stopClicked, + isRecording, ...adapter.stopCompletion })) { diagnose('stop-complete', { elapsedMs: now - stoppedAt }); @@ -206,7 +215,7 @@ function stopSpeech(): void { stopTimer = setTimeout(checkCompletion, 75); }; clearTimeout(stopTimer); - stopTimer = setTimeout(checkCompletion, 75); + checkCompletion(); } function cancelSpeech(): void { diff --git a/src/preload/speech-session-state.ts b/src/preload/speech-session-state.ts index a819dc3..ddd2f47 100644 --- a/src/preload/speech-session-state.ts +++ b/src/preload/speech-session-state.ts @@ -1,3 +1,7 @@ export function shouldClickMicrophoneOnCancel(active: boolean, stopping: boolean): boolean { return active && !stopping; } + +export function shouldClickMicrophoneOnStop(isRecording: boolean, stopClicked: boolean): boolean { + return isRecording && !stopClicked; +} diff --git a/src/preload/stop-completion.ts b/src/preload/stop-completion.ts index fd4abb5..2918725 100644 --- a/src/preload/stop-completion.ts +++ b/src/preload/stop-completion.ts @@ -1,7 +1,7 @@ export interface StopCompletionInput { readonly elapsedMs: number; readonly quietMs: number; - readonly wasRecordingAtStop: boolean; + readonly stopClicked: boolean; readonly isRecording: boolean; readonly minimumWaitMs: number; readonly quietWindowMs: number; @@ -9,7 +9,7 @@ export interface StopCompletionInput { } export function shouldFinishAfterStop(input: StopCompletionInput): boolean { + if (!input.stopClicked || input.isRecording) return false; if (input.elapsedMs >= input.timeoutMs) return true; - if (input.wasRecordingAtStop && input.isRecording) return false; return input.elapsedMs >= input.minimumWaitMs && input.quietMs >= input.quietWindowMs; } diff --git a/test/speech-session-state.test.ts b/test/speech-session-state.test.ts index fd2bd74..bbdea41 100644 --- a/test/speech-session-state.test.ts +++ b/test/speech-session-state.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import test from 'node:test'; -import { shouldClickMicrophoneOnCancel } from '../src/preload/speech-session-state'; +import { shouldClickMicrophoneOnCancel, shouldClickMicrophoneOnStop } from '../src/preload/speech-session-state'; test('停止命令已经点击过麦克风后,取消不得再次点击并重新开启录音', () => { assert.equal(shouldClickMicrophoneOnCancel(true, true), false); @@ -13,3 +13,12 @@ test('录音期间直接取消时仍应点击麦克风停止网页录音', () => test('本地会话未激活时取消不得点击麦克风', () => { assert.equal(shouldClickMicrophoneOnCancel(false, false), false); }); + +test('停止到达但网页尚未开始录音时不得再次点击开始按钮', () => { + assert.equal(shouldClickMicrophoneOnStop(false, false), false); +}); + +test('网页异步进入录音后只点击一次停止按钮', () => { + assert.equal(shouldClickMicrophoneOnStop(true, false), true); + assert.equal(shouldClickMicrophoneOnStop(true, true), false); +}); diff --git a/test/stop-completion.test.ts b/test/stop-completion.test.ts index 91ec8ea..611ddf4 100644 --- a/test/stop-completion.test.ts +++ b/test/stop-completion.test.ts @@ -5,7 +5,7 @@ import { shouldFinishAfterStop } from '../src/preload/stop-completion'; const base = { elapsedMs: 700, quietMs: 500, - wasRecordingAtStop: true, + stopClicked: true, isRecording: false, minimumWaitMs: 250, quietWindowMs: 450, @@ -16,6 +16,10 @@ test('网页仍在录音时不得提前提交', () => { assert.equal(shouldFinishAfterStop({ ...base, isRecording: true }), false); }); +test('尚未点击停止时不得提交', () => { + assert.equal(shouldFinishAfterStop({ ...base, stopClicked: false }), false); +}); + test('网页结束且最终文本静默窗口满足时立即提交', () => { assert.equal(shouldFinishAfterStop(base), true); }); @@ -24,6 +28,10 @@ test('网页结束后仍有文本修正时继续等待', () => { assert.equal(shouldFinishAfterStop({ ...base, quietMs: 120 }), false); }); -test('网页状态未回切时由超时兜底结束', () => { - assert.equal(shouldFinishAfterStop({ ...base, elapsedMs: 4_500, isRecording: true }), true); +test('网页仍在录音时超时也不得提交', () => { + assert.equal(shouldFinishAfterStop({ ...base, elapsedMs: 4_500, isRecording: true }), false); +}); + +test('网页已停止时超时可以结束静默等待', () => { + assert.equal(shouldFinishAfterStop({ ...base, elapsedMs: 4_500, quietMs: 120 }), true); }); From 857953dd1f9f5ee536588b83b3609c0efe1fd101 Mon Sep 17 00:00:00 2001 From: Melorise <0d00@0721.hk> Date: Fri, 4 Sep 2026 17:36:03 +0800 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E8=87=B3=201.0.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 同步应用、Fcitx5 addon、AppStream、发行版打包元数据、维护约定和更新日志。 AI-assisted-by: Codex AI-model: gpt-5.6-sol AI-reasoning-effort: medium Co-authored-by: Codex --- AGENTS.md | 2 +- CHANGELOG.md | 6 ++++++ CLAUDE.md | 2 +- linux/fcitx5/CMakeLists.txt | 2 +- linux/fcitx5/tama-asr.conf.in | 2 +- package.json | 2 +- packaging/README.md | 6 +++--- packaging/arch/PKGBUILD | 2 +- packaging/arch/PKGBUILD-from-source | 2 +- packaging/arch/README.md | 4 ++-- packaging/assets/tama-asr.metainfo.xml | 1 + packaging/deb/build-deb.sh | 2 +- packaging/rpm/build-rpm.sh | 2 +- packaging/rpm/tama-asr-binary.spec | 3 +++ 14 files changed, 24 insertions(+), 14 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 7710875..135bfb7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## 项目范围 -TamaASR 是仅面向 Linux 的 Fcitx5 桌面语音输入工具。项目名为 `TamaASR`,分发包名为 `tama-asr`,当前版本为 `1.0.0`。 +TamaASR 是仅面向 Linux 的 Fcitx5 桌面语音输入工具。项目名为 `TamaASR`,分发包名为 `tama-asr`,当前版本为 `1.0.2`。 不得恢复 Windows、PowerShell 或 Tauri 输出路径。发行版差异只能存在于依赖说明和打包配置,不能进入语音输入核心逻辑。 diff --git a/CHANGELOG.md b/CHANGELOG.md index eb7e9d5..7d8e3da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ 本项目遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/) 的格式。 +## [1.0.2] - 2026-09-04 + +### 修复 + +- 修复快速松开快捷键时,网页尚未完成异步启动便再次点击麦克风,导致外部会话已结束但网页仍在录音的问题。 + ## [1.0.1] - 2026-09-01 ### 变更 diff --git a/CLAUDE.md b/CLAUDE.md index 7710875..135bfb7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ ## 项目范围 -TamaASR 是仅面向 Linux 的 Fcitx5 桌面语音输入工具。项目名为 `TamaASR`,分发包名为 `tama-asr`,当前版本为 `1.0.0`。 +TamaASR 是仅面向 Linux 的 Fcitx5 桌面语音输入工具。项目名为 `TamaASR`,分发包名为 `tama-asr`,当前版本为 `1.0.2`。 不得恢复 Windows、PowerShell 或 Tauri 输出路径。发行版差异只能存在于依赖说明和打包配置,不能进入语音输入核心逻辑。 diff --git a/linux/fcitx5/CMakeLists.txt b/linux/fcitx5/CMakeLists.txt index 16ab19a..641774f 100644 --- a/linux/fcitx5/CMakeLists.txt +++ b/linux/fcitx5/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.16) -project(tama-asr-fcitx5 VERSION 1.0.1 LANGUAGES CXX) +project(tama-asr-fcitx5 VERSION 1.0.2 LANGUAGES CXX) include(GNUInstallDirs) include(CTest) diff --git a/linux/fcitx5/tama-asr.conf.in b/linux/fcitx5/tama-asr.conf.in index d38f03a..d4cb915 100644 --- a/linux/fcitx5/tama-asr.conf.in +++ b/linux/fcitx5/tama-asr.conf.in @@ -6,7 +6,7 @@ Comment[zh_CN]=将 TamaASR 网页后端连接到当前输入上下文 Type=SharedLibrary Library=@TAMA_ASR_LIBRARY_PATH@ Category=Module -Version=1.0.1 +Version=1.0.2 OnDemand=False Configurable=False diff --git a/package.json b/package.json index 81e9f7e..c43cd1d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "tama-asr", "productName": "TamaASR", - "version": "1.0.1", + "version": "1.0.2", "private": true, "packageManager": "pnpm@11.21.0", "author": "TamaASR contributors", diff --git a/packaging/README.md b/packaging/README.md index f9649ae..8c3717d 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -1,13 +1,13 @@ # Linux 分发打包 -TamaASR 1.0.1 的系统包由两部分组成: +TamaASR 1.0.2 的系统包由两部分组成: - Electron 应用安装到 `/opt/tama-asr`,命令入口为 `/usr/bin/tama-asr`; - Fcitx5 常驻 addon 由目标发行版中的 CMake/Fcitx5 开发包编译,并安装到该环境报告的 addon 目录。 安装包同时提供桌面入口、项目图标和 `/etc/xdg/autostart/tama-asr.desktop`,因此安装后的默认行为是随桌面会话启动。设置页关闭自启动时,应用需要在用户级 autostart 目录写入覆盖项,不能删除系统包所属文件。 -`tama-asr` 1.0.1 的 deb、RPM 和 Arch 元数据均声明替换旧 `meloasr` 包;通过对应发行版的包管理器安装新包时,旧应用、Fcitx5 addon 和桌面文件会一并卸载。 +`tama-asr` 1.0.2 的 deb、RPM 和 Arch 元数据均声明替换旧 `meloasr` 包;通过对应发行版的包管理器安装新包时,旧应用、Fcitx5 addon 和桌面文件会一并卸载。 ## 统一构建契约 @@ -42,7 +42,7 @@ pnpm run build:linux:dir packaging/deb/build-deb.sh ``` -产物位于 `packaging/out/tama-asr_1.0.1_.deb`。依赖名称按 Debian/Ubuntu 系列填写,但尚未在各发行版完成安装验证。 +产物位于 `packaging/out/tama-asr_1.0.2_.deb`。依赖名称按 Debian/Ubuntu 系列填写,但尚未在各发行版完成安装验证。 ## rpm diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index f3541ce..f1234bb 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -1,5 +1,5 @@ pkgname=tama-asr -pkgver=1.0.1 +pkgver=1.0.2 pkgrel=1 pkgdesc='Web-backed voice input for Fcitx5' arch=('x86_64' 'aarch64') diff --git a/packaging/arch/PKGBUILD-from-source b/packaging/arch/PKGBUILD-from-source index c010f20..73f357c 100644 --- a/packaging/arch/PKGBUILD-from-source +++ b/packaging/arch/PKGBUILD-from-source @@ -1,5 +1,5 @@ pkgname=tama-asr -pkgver=1.0.1 +pkgver=1.0.2 pkgrel=1 pkgdesc='Web-backed voice input for Fcitx5' arch=('x86_64') diff --git a/packaging/arch/README.md b/packaging/arch/README.md index 8ee7376..bbab619 100644 --- a/packaging/arch/README.md +++ b/packaging/arch/README.md @@ -8,7 +8,7 @@ ```bash curl --fail --location --remote-name \ - https://github.com/Melorise/TamaASR/releases/download/v1.0.1/PKGBUILD + https://github.com/Melorise/TamaASR/releases/download/v1.0.2/PKGBUILD makepkg --syncdeps --cleanbuild ``` @@ -20,7 +20,7 @@ makepkg --syncdeps --cleanbuild ```bash curl --fail --location --remote-name \ - https://github.com/Melorise/TamaASR/releases/download/v1.0.1/PKGBUILD-from-source + https://github.com/Melorise/TamaASR/releases/download/v1.0.2/PKGBUILD-from-source makepkg --syncdeps --cleanbuild -p PKGBUILD-from-source ``` diff --git a/packaging/assets/tama-asr.metainfo.xml b/packaging/assets/tama-asr.metainfo.xml index f1fbec9..5f99cc1 100644 --- a/packaging/assets/tama-asr.metainfo.xml +++ b/packaging/assets/tama-asr.metainfo.xml @@ -16,6 +16,7 @@ tama-asr + diff --git a/packaging/deb/build-deb.sh b/packaging/deb/build-deb.sh index 8e70fa8..4954a64 100755 --- a/packaging/deb/build-deb.sh +++ b/packaging/deb/build-deb.sh @@ -2,7 +2,7 @@ set -euo pipefail project_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)" -version="${TAMA_ASR_VERSION:-1.0.1}" +version="${TAMA_ASR_VERSION:-1.0.2}" arch="${TAMA_ASR_DEB_ARCH:-$(dpkg --print-architecture)}" app_dir="${1:-${TAMA_ASR_ELECTRON_APP_DIR:-${project_dir}/release/linux-unpacked}}" work_dir="${project_dir}/packaging/out/deb-root" diff --git a/packaging/rpm/build-rpm.sh b/packaging/rpm/build-rpm.sh index cf76ad4..67e5cf9 100755 --- a/packaging/rpm/build-rpm.sh +++ b/packaging/rpm/build-rpm.sh @@ -2,7 +2,7 @@ set -euo pipefail project_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)" -version="${TAMA_ASR_VERSION:-1.0.1}" +version="${TAMA_ASR_VERSION:-1.0.2}" app_dir="${1:-${TAMA_ASR_ELECTRON_APP_DIR:-${project_dir}/release/linux-unpacked}}" stage_dir="${project_dir}/packaging/out/rpm-root" top_dir="${project_dir}/packaging/out/rpmbuild" diff --git a/packaging/rpm/tama-asr-binary.spec b/packaging/rpm/tama-asr-binary.spec index 7ac7d80..a49ceb5 100644 --- a/packaging/rpm/tama-asr-binary.spec +++ b/packaging/rpm/tama-asr-binary.spec @@ -38,6 +38,9 @@ tar -C %{buildroot} -xzf %{SOURCE0} /etc/xdg/autostart/tama-asr.desktop %changelog +* Fri Sep 04 2026 TamaASR contributors - 1.0.2-1 +- 修复快速停止可能使网页录音状态与外部会话不同步的问题。 + * Tue Sep 01 2026 TamaASR contributors - 1.0.1-1 - Redesign the settings and overlay-position interfaces.