| // src/components/voice/VoiceMessageButton.tsx | |
| import { useState, useCallback } from "react"; | |
| import { useSpeechRecognition } from "@/hooks/useSpeechRecognition"; | |
| import { Mic, X } from "lucide-react"; | |
| import { toast } from "react-toastify"; | |
| interface VoiceMessageButtonProps { | |
| onTranscript: (text: string) => void; | |
| placeholder?: string; | |
| className?: string; | |
| } | |
| export function VoiceMessageButton({ | |
| onTranscript, | |
| placeholder = "Speak your message...", | |
| className = "", | |
| }: VoiceMessageButtonProps) { | |
| const [dictating, setDictating] = useState(false); | |
| const stt = useSpeechRecognition({ | |
| language: "en-US", | |
| continuous: false, | |
| onResult: (transcript: string, isFinal: boolean) => { | |
| if (isFinal) { | |
| onTranscript(transcript); | |
| setDictating(false); | |
| stt.stopListening(); | |
| toast.success("Voice message transcribed!"); | |
| } | |
| }, | |
| onError: (err: any) => { | |
| console.error("Speech recognition error", err); | |
| toast.error("Could not hear you. Please try again."); | |
| setDictating(false); | |
| }, | |
| }); | |
| const startDictation = useCallback(() => { | |
| setDictating(true); | |
| stt.resetTranscript(); | |
| stt.startListening(); | |
| }, [stt]); | |
| const cancelDictation = useCallback(() => { | |
| stt.abortListening?.() ?? stt.stopListening?.(); | |
| setDictating(false); | |
| }, [stt]); | |
| if (!stt.browserSupportsSpeechRecognition) { | |
| return null; | |
| } | |
| if (dictating) { | |
| return ( | |
| <div className={`flex items-center gap-2 bg-red-50 dark:bg-red-900/20 rounded-full px-4 py-2 ${className}`}> | |
| <div className="flex gap-0.5 items-end h-4"> | |
| {[1, 2, 3, 4].map((i) => ( | |
| <div | |
| key={i} | |
| className="w-0.5 bg-red-500 rounded-full animate-pulse" | |
| style={{ height: `${Math.max(3, Math.random() * 16)}px` }} | |
| /> | |
| ))} | |
| </div> | |
| <span className="text-sm text-red-600 dark:text-red-400 flex-1"> | |
| {stt.interimTranscript || "Listening..."} | |
| </span> | |
| <button onClick={cancelDictation} className="p-1 text-red-400 hover:text-red-600"> | |
| <X className="w-4 h-4" /> | |
| </button> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <button | |
| onClick={startDictation} | |
| className={`p-2 text-gray-400 hover:text-primary transition rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 ${className}`} | |
| title="Voice input" | |
| > | |
| <Mic className="w-5 h-5" /> | |
| </button> | |
| ); | |
| } |
Xet Storage Details
- Size:
- 2.46 kB
- Xet hash:
- 7ccb6fbf30af9164908b1dafa2010ae66e3e324a26eb04ab321cf69c186524f8
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.