Spaces:
Sleeping
fix(voice): KI-256 — PTT primary + live voice BETA confirm gate
Browse filesPer user decision: keep BOTH PTT (default + reliable) and live voice
(BETA), but make the trade-off explicit so users don't enable always-on
voice expecting production reliability.
Architecture (no change to STT/TTS layer — Sarvam preserved everywhere):
- PTT (default): MediaRecorder → Sarvam STT (authoritative, Indic-aware)
+ Web Speech runs in parallel ONLY for live interim transcript display
in the gray-italic strip. Sarvam transcript wins on release.
- Live voice (BETA, off by default, requires one-time accept): existing
useStreamingVoice with Web Speech driving utterance commit + Sarvam
blob as cleanup. Same path as before, just gated behind a warning.
- Sarvam TTS for ALL bot replies regardless of input mode.
Indic preserved: when user types in Hindi/Tamil/Marathi (via PTT or
live), Sarvam STT transcribes the blob. Web Speech may garble the
interim display on Indic but its result is discarded — the Sarvam
text submitted to /api/chat is correct.
Frontend changes (page.tsx only):
- Clicking the "Voice off · Always-on (BETA)" pill now shows a
confirm() listing the 3 known instability modes (cut-off mid sentence,
echo, late utterance pickup) + recommending PTT + noting Sarvam handles
Hindi/Indic correctly. User must accept once; persisted via
localStorage.insurance_live_beta_ack so they don't get nagged again.
- Pill colour shifted from emerald → amber when live is on, to signal
BETA state visually even after acceptance.
- Pill label: "Voice off · Always-on (BETA)" / "Voice on · BETA —
unstable" / "Listening… (BETA)". Title tooltips updated.
- userPrefersLive default unchanged (already false, see KI-131/ADR-033).
- Stale localStorage wipe-on-load also unchanged.
Sarvam pipeline, Web Speech interim display in PTT, useStreamingVoice
hook, voice_resilience.ts — ALL untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- frontend/src/app/page.tsx +29 -7
|
@@ -1459,24 +1459,46 @@ export default function Page() {
|
|
| 1459 |
{!live.micPermissionDenied ? (
|
| 1460 |
<button
|
| 1461 |
type="button"
|
| 1462 |
-
onClick={() =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1463 |
className={`flex items-center gap-1.5 px-2 py-0.5 rounded-full border text-xs font-medium transition cursor-pointer ${
|
| 1464 |
userPrefersLive
|
| 1465 |
-
? "border-
|
| 1466 |
: "border-[var(--border)] text-[var(--muted-foreground)] hover:border-[var(--primary)] hover:text-[var(--primary)]"
|
| 1467 |
}`}
|
| 1468 |
title={userPrefersLive
|
| 1469 |
-
? "
|
| 1470 |
-
: "
|
| 1471 |
>
|
| 1472 |
<span className={`inline-block w-2 h-2 rounded-full ${
|
| 1473 |
userPrefersLive
|
| 1474 |
-
? (live.recording ? "bg-red-500 animate-pulse" : "bg-
|
| 1475 |
: "bg-gray-400"
|
| 1476 |
}`} />
|
| 1477 |
{userPrefersLive
|
| 1478 |
-
? (live.recording ? "Listening…" : "Voice on
|
| 1479 |
-
: "Voice off
|
| 1480 |
</button>
|
| 1481 |
) : (
|
| 1482 |
<span className="text-rose-500 text-xs" title="Allow mic in your browser site settings, or use the 🎤 push-to-talk button">
|
|
|
|
| 1459 |
{!live.micPermissionDenied ? (
|
| 1460 |
<button
|
| 1461 |
type="button"
|
| 1462 |
+
onClick={() => {
|
| 1463 |
+
// KI-256 — show a one-time confirm when enabling live voice
|
| 1464 |
+
// so users know it's beta + may cut them off / echo.
|
| 1465 |
+
// Once they accept, persist a flag so we don't nag again.
|
| 1466 |
+
if (!userPrefersLive) {
|
| 1467 |
+
const seen = (typeof window !== "undefined")
|
| 1468 |
+
? window.localStorage.getItem("insurance_live_beta_ack") === "1"
|
| 1469 |
+
: true;
|
| 1470 |
+
if (!seen) {
|
| 1471 |
+
const ok = window.confirm(
|
| 1472 |
+
"Always-on voice is BETA and currently unstable:\n\n" +
|
| 1473 |
+
"• May cut you off mid-sentence\n" +
|
| 1474 |
+
"• May echo the bot's own voice\n" +
|
| 1475 |
+
"• May pick up later utterances incorrectly\n\n" +
|
| 1476 |
+
"Push-to-talk (🎤 button) is fully stable and uses Sarvam STT (handles Hindi/Indic correctly).\n\n" +
|
| 1477 |
+
"Enable always-on anyway?"
|
| 1478 |
+
);
|
| 1479 |
+
if (!ok) return;
|
| 1480 |
+
try { window.localStorage.setItem("insurance_live_beta_ack", "1"); } catch { /* ignore */ }
|
| 1481 |
+
}
|
| 1482 |
+
}
|
| 1483 |
+
setUserPrefersLive((p) => !p);
|
| 1484 |
+
}}
|
| 1485 |
className={`flex items-center gap-1.5 px-2 py-0.5 rounded-full border text-xs font-medium transition cursor-pointer ${
|
| 1486 |
userPrefersLive
|
| 1487 |
+
? "border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-100 dark:border-amber-700 dark:bg-amber-900/30 dark:text-amber-300"
|
| 1488 |
: "border-[var(--border)] text-[var(--muted-foreground)] hover:border-[var(--primary)] hover:text-[var(--primary)]"
|
| 1489 |
}`}
|
| 1490 |
title={userPrefersLive
|
| 1491 |
+
? "Always-on voice is BETA — may cut you off / echo. PTT (🎤) is more reliable."
|
| 1492 |
+
: "Always-on voice (BETA — unstable). Prefer 🎤 push-to-talk for reliable input."}
|
| 1493 |
>
|
| 1494 |
<span className={`inline-block w-2 h-2 rounded-full ${
|
| 1495 |
userPrefersLive
|
| 1496 |
+
? (live.recording ? "bg-red-500 animate-pulse" : "bg-amber-500 animate-pulse")
|
| 1497 |
: "bg-gray-400"
|
| 1498 |
}`} />
|
| 1499 |
{userPrefersLive
|
| 1500 |
+
? (live.recording ? "Listening… (BETA)" : "Voice on · BETA — unstable")
|
| 1501 |
+
: "Voice off · Always-on (BETA)"}
|
| 1502 |
</button>
|
| 1503 |
) : (
|
| 1504 |
<span className="text-rose-500 text-xs" title="Allow mic in your browser site settings, or use the 🎤 push-to-talk button">
|