fix(voice): PTT 改为主交互——按住说话松开发送(修 PTT 形同虚设的 bug)

VAD 自动断句在真实环境不够稳(环境音/停顿都会误触发),改回按住说话。

修掉 PTT 之前根本不生效的三处:
- keydown 里 `if (!c) return`——用户没先点过麦克风按钮时 clientRef 为 null,
  按空格什么都不发生。改为懒建客户端(keydown 本身就是用户手势,可启 AudioContext)
- 被 `!inConversation()` 挡着:一旦点过麦进了连续对话,PTT 永久失效。改为 PTT 优先,
  必要时先退出对话模式
- 缺 e.repeat 守卫:按住不放会连发 keydown

交互统一为一套 PTT 语义:
- 按住空格 / 按住麦克风按钮(pointer 事件,覆盖鼠标触控) → 说话,松开发送
- 朗读中按按钮 = 打断
- 指针滑出/取消/窗口失焦都算松开,不会卡在录音态
- 录音中按钮变红缩小 + 电平条 + 呼吸环,一眼可见"正在听"

顺带:松开后标签改为"已停止收音"(准确说法——麦克风硬件仍开着以便快速重按,
只是不再上传音频,门控在 shouldSendMic);删掉已无人使用的 micTapAction 及其单测。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-25 14:52:26 +08:00
parent 08c4d94187
commit e60679ed7b
4 changed files with 83 additions and 72 deletions
@@ -13,7 +13,9 @@ interface Props {
transcript: string;
reply: string;
hint: string;
onMic: () => void;
// 按住说话(与语音坞/空格键同一套 PTT 语义):按下开始听、松开发送。
onPttDown: () => void;
onPttUp: () => void;
onClose: () => void;
}
@@ -30,7 +32,7 @@ const CONF: Record<VoiceState, Conf> = {
};
const GLYPH = "ABCDEFGHJKLMNPQRSTUVWXYZ0123456789#%@*<>/\\";
export function JarvisHud({ name, state, getLevel, transcript, reply, hint, onMic, onClose }: Props) {
export function JarvisHud({ name, state, getLevel, transcript, reply, hint, onPttDown, onPttUp, onClose }: Props) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const decRef = useRef<HTMLSpanElement>(null);
const sigRef = useRef<HTMLElement>(null);
@@ -210,7 +212,15 @@ export function JarvisHud({ name, state, getLevel, transcript, reply, hint, onMi
<X className="h-4 w-4" /> ESC
</button>
<button className="jhud-core" onClick={onMic} aria-label={hint} title={hint} />
<button
className="jhud-core"
onPointerDown={(e) => { e.preventDefault(); onPttDown(); }}
onPointerUp={onPttUp}
onPointerLeave={onPttUp}
onPointerCancel={onPttUp}
aria-label={hint}
title={hint}
/>
<div className={"jhud-word" + (amber ? " amber" : state === "listening" ? " live" : "")}>{hint}</div>
<div className="jhud-decode">