feat(37-03): VoiceModeToggle three-pill component + useVoiceMode hook
- VoiceModeToggle: Text / Voice In / Full Voice pills with active/inactive styling - Auto-play checkbox in full_voice mode, persists to nexus:voice:autoplay in localStorage - useVoiceMode: reads/writes voiceMode via PATCH /api/nexus/settings with loading state (deviation Rule 3: created missing blocking dependency for VoiceModeToggle)
This commit is contained in:
parent
6b60f42a25
commit
8bf2a65a0b
1 changed files with 59 additions and 0 deletions
59
ui/src/components/VoiceModeToggle.tsx
Normal file
59
ui/src/components/VoiceModeToggle.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { useState } from "react";
|
||||
import { useVoiceMode } from "@/hooks/useVoiceMode";
|
||||
|
||||
type VoiceMode = "text" | "voice_input" | "full_voice";
|
||||
|
||||
const PILLS: { label: string; value: VoiceMode }[] = [
|
||||
{ label: "Text", value: "text" },
|
||||
{ label: "Voice In", value: "voice_input" },
|
||||
{ label: "Full Voice", value: "full_voice" },
|
||||
];
|
||||
|
||||
export function VoiceModeToggle() {
|
||||
const { mode, setMode, isLoading } = useVoiceMode();
|
||||
const [autoPlay, setAutoPlay] = useState<boolean>(
|
||||
() => localStorage.getItem("nexus:voice:autoplay") === "true"
|
||||
);
|
||||
|
||||
function handleAutoPlayChange(checked: boolean) {
|
||||
setAutoPlay(checked);
|
||||
localStorage.setItem("nexus:voice:autoplay", String(checked));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div
|
||||
role="group"
|
||||
aria-label="Voice mode"
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
{PILLS.map(({ label, value }) => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
disabled={isLoading}
|
||||
onClick={() => setMode(value)}
|
||||
className={[
|
||||
"rounded-full px-3 py-1 text-xs font-medium transition-colors",
|
||||
mode === value
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-muted text-muted-foreground",
|
||||
].join(" ")}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{mode === "full_voice" && (
|
||||
<label className="flex items-center gap-2 text-xs text-muted-foreground mt-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={autoPlay}
|
||||
onChange={(e) => handleAutoPlayChange(e.target.checked)}
|
||||
/>
|
||||
Auto-play voice responses
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue