|
2 | 2 |
|
3 | 3 | import type React from "react"; |
4 | 4 | import { useState, useRef, useEffect } from "react"; |
5 | | -import { ArrowBigUp, X, Loader2 } from "lucide-react"; |
| 5 | +import { ArrowBigUp, X, Loader2, Mic, Square } from "lucide-react"; |
6 | 6 | import { Button } from "@/components/ui/button"; |
| 7 | +import { |
| 8 | + Tooltip, |
| 9 | + TooltipContent, |
| 10 | + TooltipProvider, |
| 11 | + TooltipTrigger, |
| 12 | +} from "@/components/ui/tooltip"; |
| 13 | +import { useSpeechRecognition } from "@/hooks/useSpeechRecognition"; |
7 | 14 | import { Textarea } from "@/components/ui/textarea"; |
8 | 15 | import { ScrollArea } from "@/components/ui/scroll-area"; |
9 | 16 | import ChatMessage from "@/components/chat/ChatMessage"; |
@@ -52,6 +59,21 @@ export default function ChatInterface({ selectedAgentName, selectedNamespace, se |
52 | 59 | const isCreatingSessionRef = useRef<boolean>(false); |
53 | 60 | const [isFirstMessage, setIsFirstMessage] = useState<boolean>(!sessionId); |
54 | 61 |
|
| 62 | + const { |
| 63 | + isListening, |
| 64 | + isSupported: isVoiceSupported, |
| 65 | + startListening, |
| 66 | + stopListening, |
| 67 | + error: voiceError, |
| 68 | + } = useSpeechRecognition({ |
| 69 | + onResult(transcriptText) { |
| 70 | + setCurrentInputMessage(transcriptText); |
| 71 | + }, |
| 72 | + onError(msg) { |
| 73 | + toast.error(msg); |
| 74 | + }, |
| 75 | + }); |
| 76 | + |
55 | 77 | const { handleMessageEvent } = createMessageHandlers({ |
56 | 78 | setMessages: setStreamingMessages, |
57 | 79 | setIsStreaming, |
@@ -136,6 +158,11 @@ export default function ChatInterface({ selectedAgentName, selectedNamespace, se |
136 | 158 | return; |
137 | 159 | } |
138 | 160 |
|
| 161 | + // Stop voice recording if active before sending |
| 162 | + if (isListening) { |
| 163 | + stopListening(); |
| 164 | + } |
| 165 | + |
139 | 166 | const userMessageText = currentInputMessage; |
140 | 167 | setCurrentInputMessage(""); |
141 | 168 | setChatStatus("thinking"); |
@@ -410,6 +437,36 @@ export default function ChatInterface({ selectedAgentName, selectedNamespace, se |
410 | 437 | /> |
411 | 438 |
|
412 | 439 | <div className="flex items-center justify-end gap-2 mt-4"> |
| 440 | + {isVoiceSupported && ( |
| 441 | + <TooltipProvider> |
| 442 | + <Tooltip> |
| 443 | + <TooltipTrigger asChild> |
| 444 | + <Button |
| 445 | + type="button" |
| 446 | + variant={isListening ? "destructive" : "default"} |
| 447 | + size="icon" |
| 448 | + onClick={isListening ? stopListening : startListening} |
| 449 | + disabled={chatStatus !== "ready"} |
| 450 | + className={isListening ? "animate-pulse" : ""} |
| 451 | + aria-label={isListening ? "Stop listening" : "Voice input"} |
| 452 | + > |
| 453 | + {isListening ? ( |
| 454 | + <Square className="h-4 w-4" aria-hidden /> |
| 455 | + ) : ( |
| 456 | + <Mic className="h-4 w-4" aria-hidden /> |
| 457 | + )} |
| 458 | + </Button> |
| 459 | + </TooltipTrigger> |
| 460 | + <TooltipContent side="top"> |
| 461 | + {voiceError |
| 462 | + ? voiceError |
| 463 | + : isListening |
| 464 | + ? "Stop listening" |
| 465 | + : "Voice input — click and speak"} |
| 466 | + </TooltipContent> |
| 467 | + </Tooltip> |
| 468 | + </TooltipProvider> |
| 469 | + )} |
413 | 470 | <Button type="submit" className={""} disabled={!currentInputMessage.trim() || chatStatus !== "ready"}> |
414 | 471 | Send |
415 | 472 | <ArrowBigUp className="h-4 w-4 ml-2" /> |
|
0 commit comments