Skip to content

Commit

Permalink
issue #15 - done
Browse files Browse the repository at this point in the history
The user may enable voice responses from the AI.

-done
  • Loading branch information
dhrumilp12 committed Jun 14, 2024
1 parent 3290037 commit 932385f
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 14 deletions.
72 changes: 62 additions & 10 deletions client/src/Components/chatComponent.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import React, { useState, useEffect, useContext,useCallback, useRef } from 'react';
import axios from 'axios';
import { InputAdornment,IconButton,Box, Card, CardContent, Typography, TextField, Button, List, ListItem,ListItemAvatar, ListItemText, CircularProgress, Snackbar, Divider } from '@mui/material';
import { InputAdornment,IconButton,Box, Card, CardContent, Typography, TextField, Button, List, ListItem,ListItemAvatar, ListItemText, CircularProgress, Snackbar, Divider, Avatar } from '@mui/material';
import MuiAlert from '@mui/material/Alert';
import SendIcon from '@mui/icons-material/Send';
import ExitToAppIcon from '@mui/icons-material/ExitToApp';
import MicIcon from '@mui/icons-material/Mic';
import MicOffIcon from '@mui/icons-material/MicOff';
import PersonIcon from '@mui/icons-material/Person';
import VolumeUpIcon from '@mui/icons-material/VolumeUp';
import VolumeOffIcon from '@mui/icons-material/VolumeOff';
import { UserContext } from './userContext';
import Aria from '../Assets/Images/Aria.jpg'; // Adjust the path to where your logo is stored
import { Avatar } from '@mui/material';


const TypingIndicator = () => (
<Box sx={{ display: 'flex', alignItems: 'center', color: 'text.secondary' }}>
Expand All @@ -22,8 +24,9 @@ const TypingIndicator = () => (
</Box>
);


const ChatComponent = () => {
const { user } = useContext(UserContext);
const { user,voiceEnabled } = useContext(UserContext);
const userId = user?.userId;
const [chatId, setChatId] = useState(null);
const [turnId, setTurnId] = useState(0);
Expand All @@ -38,7 +41,38 @@ const ChatComponent = () => {
const [open, setOpen] = useState(false);
const [snackbarMessage, setSnackbarMessage] = useState('');
const [snackbarSeverity, setSnackbarSeverity] = useState('info');
const [currentPlayingMessage, setCurrentPlayingMessage] = useState(null);


const speak = (text) => {

if (!voiceEnabled || text === currentPlayingMessage) {
setCurrentPlayingMessage(null);
window.speechSynthesis.cancel(); // Stop the current speech synthesis
return;
}
const synth = window.speechSynthesis;
const utterance = new SpeechSynthesisUtterance(text);
const voices = synth.getVoices();
console.log(voices.map(voice => `${voice.name} - ${voice.lang} - ${voice.gender}`));

const femaleVoice = voices.find(voice => voice.name.includes("Microsoft Zira - English (United States)")); // Example: Adjust based on available voices

if (femaleVoice) {
utterance.voice = femaleVoice;
} else {
console.log("No female voice found");
}

utterance.onend = () => {
setCurrentPlayingMessage(null); // Reset after speech has ended
};

setCurrentPlayingMessage(text);
synth.speak(utterance);
};


const fetchWelcomeMessage = useCallback(async () => {
if (!userId) return;
setIsLoading(true);
Expand All @@ -54,6 +88,9 @@ const ChatComponent = () => {
console.log(data);
if (response.ok) {
setWelcomeMessage(data.message);
if (voiceEnabled && data.message) { // Ensure voice is enabled and the message is not empty
speak(data.message);
}
setChatId(data.chat_id);
console.log(data.chat_id);
} else {
Expand Down Expand Up @@ -132,7 +169,11 @@ const ChatComponent = () => {
const data = await response.json();
console.log(data);
if (response.ok) {
setMessages(prev => [...prev, { message: input, sender: 'user' }, { message: data, sender: 'agent' }]);
setMessages(prev =>[...prev, { message: input, sender: 'user' }, { message: data, sender: 'agent' }]);
// Speak the agent's message immediately after it's received and processed
if (voiceEnabled && data) { // Ensure voice is enabled and the message is not empty
speak(data);
}
setTurnId(prev => prev + 1);
setInput('');
} else {
Expand Down Expand Up @@ -216,14 +257,15 @@ const ChatComponent = () => {
}; // Remove audioChunks from dependencies to prevent re-creation





// Handle input changes
const handleInputChange = useCallback((event) => {
setInput(event.target.value);
}, []);

const messageIcon = (message) => {
return message === currentPlayingMessage ? <VolumeOffIcon /> : <VolumeUpIcon />;
}

return (
<>
<style>
Expand Down Expand Up @@ -274,8 +316,12 @@ const ChatComponent = () => {
<Typography variant="body1" gutterBottom sx={{ bgcolor: 'grey.200',borderRadius: '16px',
px: 2, // padding left and right within the text
py: 1, // padding top and bottom within the text
display: 'inline-block',}}>
display: 'flex', flexDirection: 'row', alignItems: 'center', flexWrap: 'nowrap'}}>
{welcomeMessage}
{voiceEnabled && welcomeMessage && (
<IconButton onClick={() => speak(welcomeMessage)} size="small" sx={{ ml: 1, }}>
{messageIcon(welcomeMessage)}
</IconButton>)}
</Typography>
</Box>
}
Expand All @@ -300,11 +346,17 @@ const ChatComponent = () => {
}}>

{msg.sender === 'agent' && (
<Avatar src={Aria} sx={{ width: 36, height: 36, mr: 1 }} alt="Aria" />
<Avatar src={Aria} sx={{ width: 36, height: 36, mr: 1 }} alt="Aria" />
)}


<ListItemText primary={msg.message} primaryTypographyProps={{
<ListItemText primary={<Box sx={{ display: 'flex', flexDirection: 'row', alignItems: 'center', flexWrap: 'nowrap'}}>
{msg.message}
{voiceEnabled && msg.sender === 'agent' && (
<IconButton onClick={() => speak(msg.message)} size="small" sx={{ ml: 1, }}>
{messageIcon(msg.message)}
</IconButton>)}
</Box>} primaryTypographyProps={{

sx: {
color: msg.sender === 'user' ? 'common.white' : 'text.primary',
Expand Down
42 changes: 39 additions & 3 deletions client/src/Components/navBar.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import React, {useContext} from 'react';
import React, {useContext, useState} from 'react';
import { useNavigate } from 'react-router-dom';
import { AppBar, Toolbar, IconButton, Typography, Badge } from '@mui/material';
import { AppBar, Toolbar, IconButton, Typography, Badge,Switch, Tooltip } from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu';
import NotificationsIcon from '@mui/icons-material/Notifications';
import AccountCircle from '@mui/icons-material/AccountCircle';
import SearchIcon from '@mui/icons-material/Search';
import { UserContext } from './userContext';
import VolumeOffIcon from '@mui/icons-material/VolumeOff';
import VolumeUpIcon from '@mui/icons-material/VolumeUp';


function Navbar({ toggleSidebar }) {

const navigate = useNavigate();
const { user } = useContext(UserContext);
const { voiceEnabled, setVoiceEnabled,user } = useContext(UserContext);

const handleProfileClick = () => {
if (user && user.userId) {
Expand All @@ -18,6 +22,12 @@ function Navbar({ toggleSidebar }) {
console.error("User ID not found");
}
};

const handleToggleVoice = (event) => {
event.preventDefault(); // Prevents the IconButton from triggering form submissions if used in forms
setVoiceEnabled(!voiceEnabled);
};

return (
<AppBar position="fixed" sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}>
<Toolbar>
Expand All @@ -31,6 +41,32 @@ function Navbar({ toggleSidebar }) {
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
Dashboard
</Typography>
<Tooltip title="Toggle voice responses">
<IconButton color="inherit" onClick={handleToggleVoice} sx={{ padding: 0 }}>
<Switch
checked={voiceEnabled}
onChange={(e) => setVoiceEnabled(e.target.checked)}
icon={<VolumeOffIcon />}
checkedIcon={<VolumeUpIcon />}
inputProps={{ 'aria-label': 'Voice response toggle' }}
color="default"
sx={{
height: 42, // Adjust height to align with icons
'& .MuiSwitch-switchBase': {
padding: '9px', // Reduce padding to make the switch smaller
},
'& .MuiSwitch-switchBase.Mui-checked': {
color: 'white',
transform: 'translateX(16px)',
'& + .MuiSwitch-track': {

backgroundColor: 'white',
},
},
}}
/>
</IconButton>
</Tooltip>
<IconButton color="inherit">
<Badge badgeContent={4} color="secondary">
<NotificationsIcon />
Expand Down
3 changes: 2 additions & 1 deletion client/src/Components/userContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useNavigate } from 'react-router-dom';
export const UserContext = createContext({ user: null });

export const UserProvider = ({ children }) => {
const [voiceEnabled, setVoiceEnabled] = useState(false);
const [user, setUser] = useState(null);
const navigate = useNavigate();
const logout = useCallback(async () => {
Expand Down Expand Up @@ -32,7 +33,7 @@ export const UserProvider = ({ children }) => {
}, [navigate]);

return (
<UserContext.Provider value={{ user, setUser, logout }}>
<UserContext.Provider value={{ user, setUser, logout,voiceEnabled, setVoiceEnabled }}>
{children}
</UserContext.Provider>
);
Expand Down

0 comments on commit 932385f

Please sign in to comment.