From 05f756963cdccfcf9df3110896ce33254c8c6d71 Mon Sep 17 00:00:00 2001 From: Srayash Date: Sat, 26 Oct 2024 03:29:54 +0530 Subject: [PATCH] Feature: Added Text-To-Speech Functionality --- frontend/src/assets/speaker.svg | 4 ++ frontend/src/assets/stopspeech.svg | 5 ++ .../src/components/TextToSpeechButton.tsx | 64 +++++++++++++++++++ .../src/conversation/ConversationBubble.tsx | 10 ++- 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 frontend/src/assets/speaker.svg create mode 100644 frontend/src/assets/stopspeech.svg create mode 100644 frontend/src/components/TextToSpeechButton.tsx diff --git a/frontend/src/assets/speaker.svg b/frontend/src/assets/speaker.svg new file mode 100644 index 00000000..ea947330 --- /dev/null +++ b/frontend/src/assets/speaker.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/stopspeech.svg b/frontend/src/assets/stopspeech.svg new file mode 100644 index 00000000..f77a235b --- /dev/null +++ b/frontend/src/assets/stopspeech.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/components/TextToSpeechButton.tsx b/frontend/src/components/TextToSpeechButton.tsx new file mode 100644 index 00000000..2ab7d1c2 --- /dev/null +++ b/frontend/src/components/TextToSpeechButton.tsx @@ -0,0 +1,64 @@ +import { useState } from 'react'; +import Speaker from '../assets/speaker.svg?react'; +import Stopspeech from '../assets/stopspeech.svg?react'; + +export default function SpeakButton({ + text, + colorLight, + colorDark, +}: { + text: string; + colorLight?: string; + colorDark?: string; +}) { + const [isSpeaking, setIsSpeaking] = useState(false); + const [isSpeakHovered, setIsSpeakHovered] = useState(false); + + const handleSpeakClick = (text: string) => { + if (isSpeaking) { + window.speechSynthesis.cancel(); + setIsSpeaking(false); + return; + } // Stop ongoing speech if already speaking + + const utterance = new SpeechSynthesisUtterance(text); + setIsSpeaking(true); + + utterance.onend = () => { + setIsSpeaking(false); // Reset when speech ends + }; + + utterance.onerror = () => { + console.error('Speech synthesis failed.'); + setIsSpeaking(false); + }; + + window.speechSynthesis.speak(utterance); + }; + + return ( +
+ {isSpeaking ? ( + handleSpeakClick(text)} + onMouseEnter={() => setIsSpeakHovered(true)} + onMouseLeave={() => setIsSpeakHovered(false)} + /> + ) : ( + handleSpeakClick(text)} + onMouseEnter={() => setIsSpeakHovered(true)} + onMouseLeave={() => setIsSpeakHovered(false)} + /> + )} +
+ ); +} diff --git a/frontend/src/conversation/ConversationBubble.tsx b/frontend/src/conversation/ConversationBubble.tsx index 2ccf1ca3..a9a05168 100644 --- a/frontend/src/conversation/ConversationBubble.tsx +++ b/frontend/src/conversation/ConversationBubble.tsx @@ -7,7 +7,6 @@ import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; import rehypeKatex from 'rehype-katex'; import 'katex/dist/katex.min.css'; - import DocsGPT3 from '../assets/cute_docsgpt3.svg'; import Dislike from '../assets/dislike.svg?react'; import Document from '../assets/document.svg'; @@ -23,6 +22,7 @@ import { } from '../preferences/preferenceSlice'; import classes from './ConversationBubble.module.css'; import { FEEDBACK, MESSAGE_TYPE } from './conversationModels'; +import SpeakButton from '../components/TextToSpeechButton'; const DisableSourceFE = import.meta.env.VITE_DISABLE_SOURCE_FE || false; @@ -336,6 +336,14 @@ const ConversationBubble = forwardRef< +
+
+ {/* Add SpeakButton here */} +
+
{type === 'ERROR' && (
{retryBtn}