diff --git a/sundynix-desktop/frontend/src/lib/voice.test.ts b/sundynix-desktop/frontend/src/lib/voice.test.ts index 9e973ce..a2fbe6d 100644 --- a/sundynix-desktop/frontend/src/lib/voice.test.ts +++ b/sundynix-desktop/frontend/src/lib/voice.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { micTapAction, nextAfterDrain, shouldSendMic, type VoiceState } from "./voice"; +import { nextAfterDrain, shouldSendMic, type VoiceState } from "./voice"; // 连续对话的纯决策函数:状态门控 / 播完去向 / 点按语义。 // VoiceClient 本体依赖 WebSocket/AudioContext(jsdom 难直测),决策逻辑抽纯函数在这测。 @@ -27,22 +27,3 @@ describe("nextAfterDrain 播完去向", () => { }); }); -describe("micTapAction 点按语义(对话开关)", () => { - it("聆听中点 → 退出对话", () => { - expect(micTapAction("listening", true)).toBe("stop"); - expect(micTapAction("listening", false)).toBe("stop"); - }); - - it("朗读中点(对话里)→ 打断但留在对话", () => { - expect(micTapAction("speaking", true)).toBe("interrupt"); - }); - - it("朗读中点(非对话)→ 开始对话(startConversation 内部先打断)", () => { - expect(micTapAction("speaking", false)).toBe("start"); - }); - - it.each(["idle", "ready", "connecting", "thinking"])("%s 态点 → 开始对话", (s) => { - expect(micTapAction(s, false)).toBe("start"); - expect(micTapAction(s, true)).toBe("start"); - }); -}); diff --git a/sundynix-desktop/frontend/src/lib/voice.ts b/sundynix-desktop/frontend/src/lib/voice.ts index d8c6ce7..5e19a37 100644 --- a/sundynix-desktop/frontend/src/lib/voice.ts +++ b/sundynix-desktop/frontend/src/lib/voice.ts @@ -34,15 +34,6 @@ export function nextAfterDrain(conversation: boolean): VoiceState { return conversation ? "listening" : "ready"; } -// micTapAction:点按麦克风的语义(对话开关)。 -// 聆听中点 → 退出对话;朗读中点(在对话里)→ 打断但留在对话;其余 → 开始对话。 -export type MicTapAction = "start" | "stop" | "interrupt"; -export function micTapAction(state: VoiceState, inConversation: boolean): MicTapAction { - if (state === "listening") return "stop"; - if (state === "speaking" && inConversation) return "interrupt"; - return "start"; -} - // 健壮性定时器时长(仅对话模式生效) const IDLE_EXIT_MS = 30_000; // 聆听空转:30s 无任何转写 → 自动退出对话(防 ASR 长连接白烧计费) const THINK_WATCHDOG_MS = 90_000; // 思考看门狗:final 后 90s 没等到朗读 → 回聆听继续对话 diff --git a/sundynix-desktop/frontend/src/shell/JarvisHud.tsx b/sundynix-desktop/frontend/src/shell/JarvisHud.tsx index 5ff67f7..6b5ba69 100644 --- a/sundynix-desktop/frontend/src/shell/JarvisHud.tsx +++ b/sundynix-desktop/frontend/src/shell/JarvisHud.tsx @@ -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 = { }; 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(null); const decRef = useRef(null); const sigRef = useRef(null); @@ -210,7 +212,15 @@ export function JarvisHud({ name, state, getLevel, transcript, reply, hint, onMi ESC -