Skip to content

Commit

Permalink
Merge pull request #1381 from Srayash/feature-TTS
Browse files Browse the repository at this point in the history
Feature: Added Text-To-Speech Functionality
  • Loading branch information
dartpain authored Oct 30, 2024
2 parents 3be74b1 + 5c99615 commit af2cef1
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 1 deletion.
18 changes: 18 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions frontend/src/assets/Loading.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions frontend/src/assets/speaker.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions frontend/src/assets/stopspeech.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
94 changes: 94 additions & 0 deletions frontend/src/components/TextToSpeechButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { useState, useRef } from 'react';
import Speaker from '../assets/speaker.svg?react';
import Stopspeech from '../assets/stopspeech.svg?react';
import LoadingIcon from '../assets/Loading.svg?react'; // Add a loading icon SVG here
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';

export default function SpeakButton({
text,
colorLight,
colorDark,
}: {
text: string;
colorLight?: string;
colorDark?: string;
}) {
const [isSpeaking, setIsSpeaking] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isSpeakHovered, setIsSpeakHovered] = useState(false);
const audioRef = useRef<HTMLAudioElement | null>(null);

const handleSpeakClick = async () => {
if (isSpeaking) {
// Stop audio if it's currently playing
audioRef.current?.pause();
audioRef.current = null;
setIsSpeaking(false);
return;
}

try {
// Set loading state and initiate TTS request
setIsLoading(true);

const response = await fetch(apiHost + '/api/tts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text }),
});

const data = await response.json();

if (data.success && data.audio_base64) {
// Create and play the audio
const audio = new Audio(`data:audio/mp3;base64,${data.audio_base64}`);
audioRef.current = audio;

audio.play().then(() => {
setIsSpeaking(true);
setIsLoading(false);

// Reset when audio ends
audio.onended = () => {
setIsSpeaking(false);
audioRef.current = null;
};
});
} else {
console.error('Failed to retrieve audio.');
setIsLoading(false);
}
} catch (error) {
console.error('Error fetching audio from TTS endpoint', error);
setIsLoading(false);
}
};

return (
<div
className={`flex items-center justify-center rounded-full p-2 ${
isSpeakHovered
? `bg-[#EEEEEE] dark:bg-purple-taupe`
: `bg-[${colorLight ? colorLight : '#FFFFFF'}] dark:bg-[${colorDark ? colorDark : 'transparent'}]`
}`}
>
{isLoading ? (
<LoadingIcon className="animate-spin" />
) : isSpeaking ? (
<Stopspeech
className="cursor-pointer fill-none"
onClick={handleSpeakClick}
onMouseEnter={() => setIsSpeakHovered(true)}
onMouseLeave={() => setIsSpeakHovered(false)}
/>
) : (
<Speaker
className="cursor-pointer fill-none"
onClick={handleSpeakClick}
onMouseEnter={() => setIsSpeakHovered(true)}
onMouseLeave={() => setIsSpeakHovered(false)}
/>
)}
</div>
);
}
10 changes: 9 additions & 1 deletion frontend/src/conversation/ConversationBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;

Expand Down Expand Up @@ -336,6 +336,14 @@ const ConversationBubble = forwardRef<
<CopyButton text={message} />
</div>
</div>
<div
className={`relative mr-5 block items-center justify-center lg:invisible
${type !== 'ERROR' ? 'group-hover:lg:visible' : 'hidden'}`}
>
<div>
<SpeakButton text={message} /> {/* Add SpeakButton here */}
</div>
</div>
{type === 'ERROR' && (
<div className="relative mr-5 block items-center justify-center">
<div>{retryBtn}</div>
Expand Down

0 comments on commit af2cef1

Please sign in to comment.