Skip to content

Commit 391c73c

Browse files
feat: add voice support for all the agents (#1260)
closes: #687 * Add a voice support feature for the agents to increase the developer experience across the product. Demo: https://github.com/user-attachments/assets/160fb7e4-b380-4e9b-9319-f8db976b52a1 --------- Signed-off-by: AayushSaini101 <kumaraayush9810@gmail.com>
1 parent a8ba3e5 commit 391c73c

File tree

3 files changed

+283
-10
lines changed

3 files changed

+283
-10
lines changed

ui/package-lock.json

Lines changed: 27 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/src/components/chat/ChatInterface.tsx

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@
22

33
import type React from "react";
44
import { useState, useRef, useEffect } from "react";
5-
import { ArrowBigUp, X, Loader2 } from "lucide-react";
5+
import { ArrowBigUp, X, Loader2, Mic, Square } from "lucide-react";
66
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";
714
import { Textarea } from "@/components/ui/textarea";
815
import { ScrollArea } from "@/components/ui/scroll-area";
916
import ChatMessage from "@/components/chat/ChatMessage";
@@ -52,6 +59,21 @@ export default function ChatInterface({ selectedAgentName, selectedNamespace, se
5259
const isCreatingSessionRef = useRef<boolean>(false);
5360
const [isFirstMessage, setIsFirstMessage] = useState<boolean>(!sessionId);
5461

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+
5577
const { handleMessageEvent } = createMessageHandlers({
5678
setMessages: setStreamingMessages,
5779
setIsStreaming,
@@ -136,6 +158,11 @@ export default function ChatInterface({ selectedAgentName, selectedNamespace, se
136158
return;
137159
}
138160

161+
// Stop voice recording if active before sending
162+
if (isListening) {
163+
stopListening();
164+
}
165+
139166
const userMessageText = currentInputMessage;
140167
setCurrentInputMessage("");
141168
setChatStatus("thinking");
@@ -410,6 +437,36 @@ export default function ChatInterface({ selectedAgentName, selectedNamespace, se
410437
/>
411438

412439
<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+
)}
413470
<Button type="submit" className={""} disabled={!currentInputMessage.trim() || chatStatus !== "ready"}>
414471
Send
415472
<ArrowBigUp className="h-4 w-4 ml-2" />

0 commit comments

Comments
 (0)