mirror of
https://github.com/Melorise/TamaASR.git
synced 2026-11-07 14:39:32 +00:00
fix: 避免快速停止重新开启网页录音
停止请求早于网页录音状态落地时,等待实际进入录音后再点击一次停止;网页仍在录音或尚未点击停止时不再完成外部会话。 AI-assisted-by: Codex AI-model: gpt-5.6-sol AI-reasoning-effort: medium Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
+16
-7
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user