diff --git a/src/App.tsx b/src/App.tsx
index 3406c34..0367d4c 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -36,6 +36,7 @@ import Config from './lib/config';
import Storage from './lib/storage';
import Voice from './lib/voice';
import useVoices from './hooks/useVoices';
+import textToSpeech from "./lib/eleven_labs";
interface CreateChatGPTMessageResponse {
answer: string;
@@ -238,7 +239,7 @@ function App() {
...oldMessages,
{ type: 'response', text: res.answer },
]);
- speak(res.answer);
+ textToSpeech(res.answer);
})
.catch((err: unknown) => {
console.warn(err);
diff --git a/src/design_system/Message.tsx b/src/design_system/Message.tsx
index 3ad4e53..b29c62e 100644
--- a/src/design_system/Message.tsx
+++ b/src/design_system/Message.tsx
@@ -1,5 +1,6 @@
import { KeyboardEventHandler } from 'react';
import { DollarSign, Terminal } from 'react-feather';
+import Typewriter from './typewriter';
interface MessageProps {
type: 'prompt' | 'response';
@@ -47,7 +48,13 @@ export default function Message({
)}
-
{text}
+
+ {type === 'response' ? (
+
+ ) : (
+ {text}
+ )}
+
);
}
diff --git a/src/design_system/typewriter.tsx b/src/design_system/typewriter.tsx
new file mode 100644
index 0000000..a71a079
--- /dev/null
+++ b/src/design_system/typewriter.tsx
@@ -0,0 +1,30 @@
+import React, { useEffect, useState } from 'react';
+import styles from './typing.module.css';
+
+const Typewriter = ({ text = "", typingDelay = 100, blinkingCursor = true }) => {
+ const [typewriterText, setTypewriterText] = useState("");
+ const [index, setIndex] = useState(0);
+ const words = text.replace(/\n/g, '
').split(' ');
+
+ useEffect(() => {
+ setTypewriterText("");
+ setIndex(0);
+ }, [text]);
+
+ useEffect(() => {
+ if (index < words.length) {
+ const timerId = setInterval(() => {
+ setTypewriterText((prevText) => prevText + (index > 0 ? ' ' : '') + words[index]);
+ setIndex((prevIndex) => prevIndex + 1);
+ }, typingDelay);
+
+ return () => clearInterval(timerId);
+ }
+ }, [index, words, typingDelay]);
+
+ return (
+
+ );
+};
+
+export default Typewriter;
diff --git a/src/design_system/typing.module.css b/src/design_system/typing.module.css
new file mode 100644
index 0000000..5188bef
--- /dev/null
+++ b/src/design_system/typing.module.css
@@ -0,0 +1,7 @@
+.blinking-cursor {
+ animation: blink 0.75s step-end infinite;
+}
+
+@keyframes blink {
+ 50% { opacity: 0 }
+}
diff --git a/src/lib/eleven_labs.ts b/src/lib/eleven_labs.ts
new file mode 100644
index 0000000..8a5ae80
--- /dev/null
+++ b/src/lib/eleven_labs.ts
@@ -0,0 +1,36 @@
+// Define your Eleven Labs API key
+const ELEVEN_LABS_API_KEY = ""; // Add your Eleven Labs API key here
+const sVoiceId = "21m00Tcm4TlvDq8ikWAM"; // This is the default voiceId. You can change it if needed.
+
+// Function for making a request to Eleven Labs API and playing the response
+export default async function textToSpeech(text: string, voiceId: string = sVoiceId): Promise {
+ // Preparing the request
+ const url = `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`;
+ const headers = new Headers();
+ headers.append("Accept", "audio/mpeg");
+ headers.append("Content-Type", "application/json");
+ headers.append("xi-api-key", ELEVEN_LABS_API_KEY);
+ const data = {
+ text: text,
+ voice_settings: { stability: 0, similarity_boost: 0 }
+ };
+ const requestOptions: RequestInit = {
+ method: 'POST',
+ headers: headers,
+ body: JSON.stringify(data)
+ };
+
+ // Making the request
+ const response = await fetch(url, requestOptions);
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ // Reading the response as Blob
+ const blob = await response.blob();
+
+ // Creating object URL and audio element to play the sound
+ const audioURL = URL.createObjectURL(blob);
+ const audio = new Audio(audioURL);
+ audio.play();
+}