diff --git a/client/src/App.jsx b/client/src/App.jsx index 5fcb7343..c61d9178 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -8,7 +8,7 @@ import UserProfile from './Components/userProfile'; import Sidebar from './Components/sideBar'; import Navbar from './Components/navBar'; import ChatLogManager from './Components/chatLogManager'; -import ChatInterface from './Components/chatInterface'; + import MoodLogging from './Components/moodLogging'; import MoodLogs from './Components/moodLogs'; import CheckInForm from './Components/checkInForm'; @@ -31,13 +31,8 @@ function App() { - {user?.userId ? : } - } /> - - - - } /> + } /> + } /> } /> } /> diff --git a/client/src/Assets/Styles/MoodLogging.css b/client/src/Assets/Styles/MoodLogging.css index 6e22f337..054dc4e0 100644 --- a/client/src/Assets/Styles/MoodLogging.css +++ b/client/src/Assets/Styles/MoodLogging.css @@ -1,34 +1,32 @@ .mood-logging-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 100vh; - font-family: 'Arial', sans-serif; - - + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 100vh; + font-family: "Arial", sans-serif; } .mood-logging { - width: 90%; - max-width: 500px; - background-color: white; - padding: 25px; - border-radius: 10px; - box-shadow: 0 10px 25px rgba(0,0,0,0.1); - transition: box-shadow 0.3s ease-in-out; + width: 90%; + max-width: 500px; + background-color: white; + padding: 25px; + border-radius: 10px; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); + transition: box-shadow 0.3s ease-in-out; } .mood-logging:hover { - box-shadow: 0 20px 45px rgba(0,0,0,0.15); + box-shadow: 0 20px 45px rgba(0, 0, 0, 0.15); } .input-group { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } -input[type="text"] { +/* input[type="text"] { padding: 12px 20px; margin: 10px 0; border: 2px solid #ccc; @@ -41,35 +39,34 @@ input[type="text"] { input[type="text"]:focus { border-color: #007BFF; outline: none; -} +} */ .submit-button { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - background-color: #007BFF; - color: white; - padding: 12px 20px; - border: none; - border-radius: 5px; - cursor: pointer; - font-size: 18px; - margin-top: 20px; - transition: background-color 0.3s ease-in-out; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + background-color: #007bff; + color: white; + padding: 12px 20px; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 18px; + margin-top: 20px; + transition: background-color 0.3s ease-in-out; } .submit-button:hover { - background-color: #0056b3; + background-color: #0056b3; } - .message { - text-align: center; - margin-top: 15px; - padding: 10px; - font-size: 16px; - color: #2c3e50; - border-radius: 5px; - background-color: #e0e0e0; -} \ No newline at end of file + text-align: center; + margin-top: 15px; + padding: 10px; + font-size: 16px; + color: #2c3e50; + border-radius: 5px; + background-color: #e0e0e0; +} diff --git a/client/src/Assets/Styles/MoodLogs.css b/client/src/Assets/Styles/MoodLogs.css index caeb37c4..6a311859 100644 --- a/client/src/Assets/Styles/MoodLogs.css +++ b/client/src/Assets/Styles/MoodLogs.css @@ -1,43 +1,43 @@ -.mood-logs { +/* .mood-logs { max-width: 600px; margin: 50px auto; padding: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); border-radius: 8px; background-color: white; -} +} */ h2 { - display: flex; - align-items: center; /* This aligns the icon with the text vertically */ - font-size: 24px; /* Adjust text size as needed */ + display: flex; + align-items: center; /* This aligns the icon with the text vertically */ + font-size: 24px; /* Adjust text size as needed */ } .icon-large { - font-size: 40px; /* or any other size */ - margin-right: 7px; /* Adds space between the icon and the text */ + font-size: 40px; /* or any other size */ + margin-right: 7px; /* Adds space between the icon and the text */ } .mood-logs h2 { - text-align: center; - color: #333; + text-align: center; + color: #333; } ul { - list-style-type: none; - padding: 0; + list-style-type: none; + padding: 0; } li { - padding: 10px; - border-bottom: 1px solid #ccc; + padding: 10px; + border-bottom: 1px solid #ccc; } li:last-child { - border-bottom: none; + border-bottom: none; } .error { - color: red; - text-align: center; + color: red; + text-align: center; } diff --git a/client/src/Components/authComponent.jsx b/client/src/Components/authComponent.jsx index 9f86d02b..5361b373 100644 --- a/client/src/Components/authComponent.jsx +++ b/client/src/Components/authComponent.jsx @@ -1,206 +1,257 @@ -import React, { useState, useContext } from 'react'; -import axios from 'axios'; -import apiServerAxios from '../api/axios'; -import { useNavigate } from 'react-router-dom'; -import { UserContext } from './userContext'; -import { Link } from 'react-router-dom'; -import {TextField, Button, Paper, CssBaseline, Snackbar, Alert, - Tab, Tabs, Box, CircularProgress,Select, InputLabel,FormControl,MenuItem, IconButton, Typography, Tooltip} from '@mui/material'; -import { createTheme, ThemeProvider, styled } from '@mui/material/styles'; -import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; -import PersonAddIcon from '@mui/icons-material/PersonAdd'; -import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; -import { Visibility, VisibilityOff } from '@mui/icons-material'; -import { Checkbox, FormGroup, FormControlLabel } from '@mui/material'; -import InfoIcon from '@mui/icons-material/Info'; - +import { useState, useContext } from "react"; +import apiServerAxios from "../api/axios"; +import { useNavigate } from "react-router-dom"; +import { UserContext } from "./userContext"; +import { Link } from "react-router-dom"; +import { + TextField, + Button, + Paper, + CssBaseline, + Snackbar, + Alert, + Tab, + Tabs, + Box, + CircularProgress, + Select, + InputLabel, + FormControl, + MenuItem, + IconButton, + Typography, + Tooltip, + Container, + Grid, +} from "@mui/material"; +import { createTheme, ThemeProvider, styled } from "@mui/material/styles"; +import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; +import PersonAddIcon from "@mui/icons-material/PersonAdd"; +import VisibilityOffIcon from "@mui/icons-material/VisibilityOff"; +import { Visibility, VisibilityOff } from "@mui/icons-material"; +import { Checkbox, FormGroup, FormControlLabel } from "@mui/material"; +import InfoIcon from "@mui/icons-material/Info"; const theme = createTheme({ palette: { primary: { - main: '#556cd6', + // main: "#556cd6", + main: "rgba(255, 255, 255, 0.8)", }, secondary: { - main: '#19857b', + main: "#19857b", }, background: { - default: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)', - paper: '#fff', - }, + // default: "linear-gradient(#4e54c8 30%, #8f94fb 90%)", + default: + "linear-gradient(to bottom right, #121111 60%, #201739 72%, #eec9e6 100%)", + paper: "#fff", + }, }, typography: { fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', h5: { fontWeight: 600, - color: '#444', + color: "#444", }, button: { - textTransform: 'none', - fontWeight: 'bold', + textTransform: "none", + fontWeight: "bold", }, }, components: { MuiButton: { styleOverrides: { root: { - margin: '8px', + // margin: "8px", }, }, }, }, }); - const StyledForm = styled(Paper)(({ theme }) => ({ - marginTop: theme.spacing(12), - display: 'flex', - flexDirection: 'column', - alignItems: 'center', + margin: theme.spacing(10), + display: "flex", + flexDirection: "column", + alignItems: "center", padding: theme.spacing(4), borderRadius: theme.shape.borderRadius, boxShadow: theme.shadows[10], - width: '90%', - maxWidth: '450px', + width: "90%", + maxWidth: "450px", opacity: 0.98, - backdropFilter: 'blur(10px)', + backdropFilter: "blur(10px)", })); +const commonTextFieldStyles = { + input: { + color: "#fff", + backgroundColor: "rgba(255, 255, 255, 0.2)", + borderRadius: 1, + }, + label: { + color: "rgba(255, 255, 255, 0.8)", + "&.Mui-focused": { + color: "#fff", + }, + }, + "& .MuiOutlinedInput-root": { + "& fieldset": { + borderColor: "rgba(255, 255, 255, 0.5)", + }, + "&:hover fieldset": { + borderColor: "#fff", + }, + "&.Mui-focused fieldset": { + borderColor: "#fff", + }, + }, +}; + function AuthComponent() { const navigate = useNavigate(); const [isAuthenticated, setIsAuthenticated] = useState(false); const { setUser } = useContext(UserContext); const [activeTab, setActiveTab] = useState(0); - const [username, setUsername] = useState(''); - const [email, setEmail] = useState(''); - const [showForgotPassword, setShowForgotPassword] = useState(false); - const [password, setPassword] = useState(''); - const [showPassword, setShowPassword] = useState(false); - const [name, setName] = useState(''); - const [age, setAge] = useState(''); - const [gender, setGender] = useState(''); - const [placeOfResidence, setPlaceOfResidence] = useState(''); - const [fieldOfWork, setFieldOfWork] = useState(''); - const [loading, setLoading] = useState(false); - const [open, setOpen] = useState(false); // State to control Snackbar visibility - const [message, setMessage] = useState(''); // State to hold the message - const [severity, setSeverity] = useState('info'); // State to control the type of alert - const mentalStressors = [ - { id: 'job_search', name: 'Stress from job search' }, - { id: 'classwork', name: 'Stress from classwork' }, - { id: 'social_anxiety', name: 'Social anxiety' }, - { id: 'impostor_syndrome', name: 'Impostor Syndrome' }, - { id: 'career_drift', name: 'Career Drift' }, + const [username, setUsername] = useState(""); + const [email, setEmail] = useState(""); + const [showForgotPassword, setShowForgotPassword] = useState(false); + const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + const [name, setName] = useState(""); + const [age, setAge] = useState(""); + const [gender, setGender] = useState(""); + const [placeOfResidence, setPlaceOfResidence] = useState(""); + const [fieldOfWork, setFieldOfWork] = useState(""); + const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); // State to control Snackbar visibility + const [message, setMessage] = useState(""); // State to hold the message + const [severity, setSeverity] = useState("info"); // State to control the type of alert + const mentalStressors = [ + { id: "job_search", name: "Stress from job search" }, + { id: "classwork", name: "Stress from classwork" }, + { id: "social_anxiety", name: "Social anxiety" }, + { id: "impostor_syndrome", name: "Impostor Syndrome" }, + { id: "career_drift", name: "Career Drift" }, ]; - + const [selectedStressors, setSelectedStressors] = useState([]); const handleStressorChange = (event) => { const value = event.target.value; const newSelection = selectedStressors.includes(value) - ? selectedStressors.filter(item => item !== value) - : [...selectedStressors, value]; + ? selectedStressors.filter((item) => item !== value) + : [...selectedStressors, value]; setSelectedStressors(newSelection); -}; + }; const handleLogin = async (e) => { e.preventDefault(); setLoading(true); try { - const response = await apiServerAxios.post('/api/user/login', { username, password }); - if (response && response.data) { - const userId = response.data.userId; - localStorage.setItem('token', response.data.access_token); // Ensure this is correctly saving the token - console.log('Token stored:', localStorage.getItem('token')); // Confirm the token is stored - setMessage('Login successful!'); - setSeverity('success'); - setIsAuthenticated(true); - setUser({ userId }); - navigate('/'); - console.log('User logged in:', userId); - } else { - throw new Error('Invalid response from server'); - } + const response = await apiServerAxios.post("/user/login", { + username, + password, + }); + if (response && response.data) { + const userId = response.data.userId; + localStorage.setItem("token", response.data.access_token); // Ensure this is correctly saving the token + console.log("Token stored:", localStorage.getItem("token")); // Confirm the token is stored + setMessage("Login successful!"); + setSeverity("success"); + setIsAuthenticated(true); + setUser({ userId }); + navigate("/"); + console.log("User logged in:", userId); + } else { + throw new Error("Invalid response from server"); + } } catch (error) { - console.error('Login failed:', error); - setMessage('Login failed: ' + (error.response?.data?.msg || 'Unknown error')); - setSeverity('error'); + console.error("Login failed:", error); + setMessage( + "Login failed: " + (error.response?.data?.msg || "Unknown error") + ); + setSeverity("error"); setShowForgotPassword(true); } setOpen(true); setLoading(false); -}; + }; const handleSignUp = async (e) => { - e.preventDefault(); - setLoading(true); - try { - const response = await apiServerAxios.post('/api/user/signup', { - username, - email, - password, - name, - age, - gender, - placeOfResidence, - fieldOfWork, - mental_health_concerns: selectedStressors - }); - if (response && response.data) { - const userId = response.data.userId; - localStorage.setItem('token', response.data.access_token); // Ensure this is correctly saving the token - console.log('Token stored:', localStorage.getItem('token')); // Confirm the token is stored - setMessage('User registered successfully!'); - setSeverity('success'); - setIsAuthenticated(true); - setUser({ userId }); - navigate('/'); - console.log('User registered:', userId); - } else { - throw new Error('Invalid response from server'); - } - - } catch (error) { - console.error('Signup failed:', error); - setMessage(error.response?.data?.error || 'Failed to register user.'); - setSeverity('error'); - } - setLoading(false); - setOpen(true); - }; + e.preventDefault(); + setLoading(true); + try { + const response = await apiServerAxios.post("/user/signup", { + username, + email, + password, + name, + age, + gender, + placeOfResidence, + fieldOfWork, + mental_health_concerns: selectedStressors, + }); + if (response && response.data) { + const userId = response?.data?.userId; + localStorage.setItem("token", response?.data?.access_token); // Ensure this is correctly saving the token + console.log("Token stored:", localStorage.getItem("token")); // Confirm the token is stored + setMessage("User registered successfully!"); + setSeverity("success"); + setIsAuthenticated(true); + setUser({ userId }); + navigate("/"); + console.log("User registered:", userId); + } else { + throw new Error("Invalid response from server"); + } + } catch (error) { + console.error("Signup failed:", error); + setMessage(error.response?.data?.error || "Failed to register user."); + setSeverity("error"); + } + setLoading(false); + setOpen(true); + }; - const handleAnonymousSignIn = async (e) => { - e.preventDefault(); - setLoading(true); - try { - const response = await apiServerAxios.post('/api/user/anonymous_signin'); - if (response && response.data) { - const userId = "0"; - const isAnon = true; - localStorage.setItem('token', response.data.access_token); // Ensure this is correctly saving the token - console.log('Token stored:', localStorage.getItem('token')); // Confirm the token is stored - setMessage('Anonymous sign-in successful!'); - setSeverity('success'); - setIsAuthenticated(true); - setUser({ userId, isAnon }); - navigate('/'); - } else { - throw new Error('Invalid response from server'); - } - } catch (error) { - console.error('Anonymous sign-in failed:', error); - setMessage('Anonymous sign-in failed: ' + (error.response?.data?.msg || 'Unknown error')); - setSeverity('error'); - } - setLoading(false); - setOpen(true); - }; + const handleAnonymousSignIn = async (e) => { + e.preventDefault(); + setLoading(true); + try { + const response = await apiServerAxios.post("/user/anonymous_signin"); + if (response && response.data) { + const userId = "0"; + const isAnon = true; + localStorage.setItem("token", response.data.access_token); // Ensure this is correctly saving the token + console.log("Token stored:", localStorage.getItem("token")); // Confirm the token is stored + setMessage("Anonymous sign-in successful!"); + setSeverity("success"); + setIsAuthenticated(true); + setUser({ userId, isAnon }); + navigate("/"); + } else { + throw new Error("Invalid response from server"); + } + } catch (error) { + console.error("Anonymous sign-in failed:", error); + setMessage( + "Anonymous sign-in failed: " + + (error.response?.data?.msg || "Unknown error") + ); + setSeverity("error"); + } + setLoading(false); + setOpen(true); + }; const handleChange = (event, newValue) => { setActiveTab(newValue); }; const handleClose = (event, reason) => { - if (reason === 'clickaway') { + if (reason === "clickaway") { return; } setOpen(false); @@ -212,119 +263,345 @@ function AuthComponent() { return ( - - - - + + + + } label="Login" /> } label="Sign Up" /> } label="Anonymous" /> - - {activeTab === 0 && ( -
- setUsername(e.target.value)} /> - setPassword(e.target.value)} + + {activeTab === 0 && ( + + setUsername(e.target.value)} + sx={commonTextFieldStyles} + /> + setPassword(e.target.value)} InputProps={{ endAdornment: ( - + {showPassword ? : } ), - }} /> - - {showForgotPassword && ( - - Forgot your password? Reset it here - - )} + }} + sx={commonTextFieldStyles} + /> + + {showForgotPassword && ( + + Forgot your password?{" "} + + Reset it here + + + )} - )} - {activeTab === 1 && ( -
- setUsername(e.target.value)} /> - setEmail(e.target.value)} /> - setPassword(e.target.value)} - InputProps={{ - endAdornment: ( - - {showPassword ? : } - - ), - }} /> - setName(e.target.value)} /> - setAge(e.target.value)} /> - - - Gender - - - - setPlaceOfResidence(e.target.value)} /> - setFieldOfWork(e.target.value)} /> - - - Select any mental stressors you are currently experiencing to help us better tailor your therapy sessions. - - {mentalStressors.map(stressor => ( - } - label={ - - {stressor.name} - {getStressorDescription(stressor.id)}} arrow placement="right"> - - - } - /> - ))} - - - - )} - {activeTab === 2 && ( -
- -
- )} -
+ )} + {activeTab === 1 && ( +
+ + + setUsername(e.target.value)} + sx={commonTextFieldStyles} + /> + + + setEmail(e.target.value)} + sx={commonTextFieldStyles} + /> + + + setPassword(e.target.value)} + InputProps={{ + endAdornment: ( + + {showPassword ? : } + + ), + }} + sx={commonTextFieldStyles} + /> + + + setName(e.target.value)} + sx={commonTextFieldStyles} + /> + + + setAge(e.target.value)} + sx={commonTextFieldStyles} + /> + + + + Gender + + + + + setPlaceOfResidence(e.target.value)} + sx={commonTextFieldStyles} + /> + + + setFieldOfWork(e.target.value)} + sx={commonTextFieldStyles} + /> + + + + + + Select any mental stressors you are currently experiencing + to help us better tailor your therapy sessions. + + {mentalStressors.map((stressor) => ( + + } + sx={{ display: "flex" }} + label={ + + {stressor.name} + + {getStressorDescription(stressor.id)} + + } + arrow + placement="right" + > + + + + } + /> + ))} + + +
+ )} + {activeTab === 2 && ( +
+ +
+ )} +
- + {message} -
-
+ +
); } // Define a function to return descriptions based on stressor id function getStressorDescription(stressorId) { - switch(stressorId) { - case 'job_search': - return 'Feelings of stress stemming from the job search process.'; - case 'classwork': - return 'Stress related to managing coursework and academic responsibilities.'; - case 'social_anxiety': - return 'Anxiety experienced during social interactions or in anticipation of social interactions.'; - case 'impostor_syndrome': - return "Persistent doubt concerning one's abilities or accomplishments coupled with a fear of being exposed as a fraud."; - case 'career_drift': - return "Stress from uncertainty or dissatisfaction with one's career path or progress."; - default: - return 'No description available.'; + switch (stressorId) { + case "job_search": + return "Feelings of stress stemming from the job search process."; + case "classwork": + return "Stress related to managing coursework and academic responsibilities."; + case "social_anxiety": + return "Anxiety experienced during social interactions or in anticipation of social interactions."; + case "impostor_syndrome": + return "Persistent doubt concerning one's abilities or accomplishments coupled with a fear of being exposed as a fraud."; + case "career_drift": + return "Stress from uncertainty or dissatisfaction with one's career path or progress."; + default: + return "No description available."; } } -export default AuthComponent +export default AuthComponent; diff --git a/client/src/Components/chatComponent.jsx b/client/src/Components/chatComponent.jsx index 580f0bb9..84fe50be 100644 --- a/client/src/Components/chatComponent.jsx +++ b/client/src/Components/chatComponent.jsx @@ -1,325 +1,399 @@ -import React, { useState, useEffect, useContext, useCallback, useRef } from 'react'; -import axios from 'axios'; -import apiServerAxios from '../api/axios'; -import { InputAdornment, IconButton, Box, Card, CardContent, Typography, TextField, Button, List, ListItem, ListItemAvatar, ListItemText, CircularProgress, Snackbar, Divider, Avatar, Tooltip } from '@mui/material'; -import MuiAlert from '@mui/material/Alert'; -import SendIcon from '@mui/icons-material/Send'; -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 LibraryAddIcon from '@mui/icons-material/LibraryAdd'; -import { UserContext } from './userContext'; -import Aria from '../Assets/Images/Aria.jpg'; // Adjust the path to where your logo is stored -import RecordRTC from 'recordrtc'; +import React, { + useState, + useEffect, + useContext, + useCallback, + useRef, +} from "react"; +import axios from "axios"; +import apiServerAxios from "../api/axios"; +import { + InputAdornment, + IconButton, + Box, + Card, + CardContent, + Typography, + TextField, + Button, + List, + ListItem, + ListItemAvatar, + ListItemText, + CircularProgress, + Snackbar, + Divider, + Avatar, + Tooltip, + Switch, +} from "@mui/material"; +import MuiAlert from "@mui/material/Alert"; +import SendIcon from "@mui/icons-material/Send"; +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 LibraryAddIcon from "@mui/icons-material/LibraryAdd"; +import { UserContext } from "./userContext"; +import Aria from "../Assets/Images/Aria.jpg"; // Adjust the path to where your logo is stored +import RecordRTC from "recordrtc"; const TypingIndicator = () => ( - - -
-
-
-
-
-
+ + +
+
+
+
+
+
); - const ChatComponent = () => { - const { user, voiceEnabled , setVoiceEnabled} = useContext(UserContext); - - const userId = user?.userId; - const [chatId, setChatId] = useState(null); - const [turnId, setTurnId] = useState(0); - const [input, setInput] = useState(''); - const [messages, setMessages] = useState([]); - const [isRecording, setIsRecording] = useState(false); - const [mediaRecorder, setMediaRecorder] = useState(null); - const audioChunksRef = useRef([]); - const [isLoading, setIsLoading] = useState(false); - const [welcomeMessage, setWelcomeMessage] = useState(''); - const [isFetchingMessage, setIsFetchingMessage] = useState(false); - const [open, setOpen] = useState(false); - const [snackbarMessage, setSnackbarMessage] = useState(''); - const [snackbarSeverity, setSnackbarSeverity] = useState('info'); - const [currentPlayingMessage, setCurrentPlayingMessage] = useState(null); - - - const handleToggleVoice = (event) => { - event.preventDefault(); // Prevents the IconButton from triggering form submissions if used in forms - setVoiceEnabled(!voiceEnabled); + const { user, voiceEnabled, setVoiceEnabled } = useContext(UserContext); + + const userId = user?.userId; + const [chatId, setChatId] = useState(null); + const [turnId, setTurnId] = useState(0); + const [input, setInput] = useState(""); + const [messages, setMessages] = useState([]); + const [isRecording, setIsRecording] = useState(false); + const [mediaRecorder, setMediaRecorder] = useState(null); + const audioChunksRef = useRef([]); + const [isLoading, setIsLoading] = useState(false); + const [welcomeMessage, setWelcomeMessage] = useState(""); + const [isFetchingMessage, setIsFetchingMessage] = useState(false); + const [open, setOpen] = useState(false); + const [snackbarMessage, setSnackbarMessage] = useState(""); + const [snackbarSeverity, setSnackbarSeverity] = useState("info"); + const [currentPlayingMessage, setCurrentPlayingMessage] = useState(null); + + const handleToggleVoice = (event) => { + event.preventDefault(); // Prevents the IconButton from triggering form submissions if used in forms + setVoiceEnabled(!voiceEnabled); + }; + + 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 setVoiceAndSpeak = () => { + 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 }; - 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 setVoiceAndSpeak = () => { - 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); - }; - - if (synth.getVoices().length === 0) { - synth.onvoiceschanged = setVoiceAndSpeak; - } else { - setVoiceAndSpeak(); - } + setCurrentPlayingMessage(text); + synth.speak(utterance); }; + if (synth.getVoices().length === 0) { + synth.onvoiceschanged = setVoiceAndSpeak; + } else { + setVoiceAndSpeak(); + } + }; - const fetchWelcomeMessage = useCallback(async () => { - if (!userId) return; - setIsLoading(true); - setIsFetchingMessage(true); - try { - const response = await apiServerAxios.post(`/api/ai/mental_health/welcome/${userId}`, { - headers: { - "Content-Type": "application/json" - } - }); - if (response && response.data) { - const data = response.data - 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 { - console.error('Failed to fetch welcome message:', data); - setWelcomeMessage('Error fetching welcome message.'); - } - } catch (error) { - console.error('Network or server error:', error); - } finally { - setIsLoading(false); - setIsFetchingMessage(false); - } - }, [userId]); - // Fetch initial message when component mounts - useEffect(() => { - fetchWelcomeMessage(); - }, []); - - - const handleSnackbarClose = (event, reason) => { - if (reason === 'clickaway') { - return; + const fetchWelcomeMessage = useCallback(async () => { + if (!userId) return; + setIsLoading(true); + setIsFetchingMessage(true); + try { + const response = await apiServerAxios.post( + `/ai/mental_health/welcome/${userId}`, + { + headers: { + "Content-Type": "application/json", + }, } - setOpen(false); - }; - - const finalizeChat = useCallback(async () => { - if (chatId === null) return; - setIsLoading(true); - try { - const response = await apiServerAxios.patch(`/api/ai/mental_health/finalize/${userId}/${chatId}`, { - headers: { 'Content-Type': 'application/json' } - }); - if (response) { - setSnackbarMessage('Chat finalized successfully'); - setSnackbarSeverity('success'); - // Reset chat state to start a new chat - setChatId(null); - setTurnId(0); - setMessages([]); - // Optionally, fetch a new welcome message or reset other relevant states - fetchWelcomeMessage(); // assuming fetchWelcomeMessage resets or initiates a new chat session - } else { - setSnackbarMessage('Failed to finalize chat'); - setSnackbarSeverity('error'); - } - } catch (error) { - setSnackbarMessage('Error finalizing chat'); - setSnackbarSeverity('error'); - } finally { - setIsLoading(false); - setOpen(true); + ); + if (response && response.data) { + const data = response.data; + setWelcomeMessage(data.message); + if (voiceEnabled && data.message) { + // Ensure voice is enabled and the message is not empty + speak(data.message); } - }, [userId, chatId, fetchWelcomeMessage]); - - const sendMessage = useCallback(async () => { - if (!input.trim() || chatId === undefined) return; - console.log(chatId); - setIsLoading(true); - - - try { - const body = { - prompt: input, - turn_id: turnId - }; - const response = await apiServerAxios.post(`/api/ai/mental_health/${userId}/${chatId}`, { - headers: { - "Content-Type": "application/json" - }, - ...body - }); - - const data = response.data; - if (response && data) { - 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 { - console.error('Failed to send message:', data.error || "Unknown error occurred"); - setSnackbarMessage(data.error || "An error occurred while sending the message."); - setSnackbarSeverity('error'); - setOpen(true); - } - } catch (error) { - console.error('Failed to send message:', error); - setSnackbarMessage('Network or server error occurred.'); - setSnackbarSeverity('error'); - setOpen(true); - } finally { - setIsLoading(false); + setChatId(data.chat_id); + console.log(data.chat_id); + } else { + console.error("Failed to fetch welcome message:", data); + setWelcomeMessage("Error fetching welcome message."); + } + } catch (error) { + console.error("Network or server error:", error); + } finally { + setIsLoading(false); + setIsFetchingMessage(false); + } + }, [userId]); + // Fetch initial message when component mounts + useEffect(() => { + fetchWelcomeMessage(); + }, []); + + const handleSnackbarClose = (event, reason) => { + if (reason === "clickaway") { + return; + } + setOpen(false); + }; + const finalizeChat = useCallback(async () => { + if (chatId === null) return; + setIsLoading(true); + try { + const response = await apiServerAxios.patch( + `/ai/mental_health/finalize/${userId}/${chatId}`, + { + headers: { "Content-Type": "application/json" }, } - }, [input, userId, chatId, turnId]); - + ); + if (response) { + setSnackbarMessage("Chat finalized successfully"); + setSnackbarSeverity("success"); + // Reset chat state to start a new chat + setChatId(null); + setTurnId(0); + setMessages([]); + // Optionally, fetch a new welcome message or reset other relevant states + fetchWelcomeMessage(); // assuming fetchWelcomeMessage resets or initiates a new chat session + } else { + setSnackbarMessage("Failed to finalize chat"); + setSnackbarSeverity("error"); + } + } catch (error) { + setSnackbarMessage("Error finalizing chat"); + setSnackbarSeverity("error"); + } finally { + setIsLoading(false); + setOpen(true); + } + }, [userId, chatId, fetchWelcomeMessage]); - + const sendMessage = useCallback(async () => { + if (!input.trim() || chatId === undefined) return; + console.log(chatId); + setIsLoading(true); - // Function to handle recording start - // Function to check supported MIME types for recording -const getSupportedMimeType = () => { - if (MediaRecorder.isTypeSupported('audio/webm; codecs=opus')) { - return 'audio/webm; codecs=opus'; - } else if (MediaRecorder.isTypeSupported('audio/mp4')) { - // Fallback for Safari on iOS - return 'audio/mp4'; + try { + const body = { + prompt: input, + turn_id: turnId, + }; + const response = await apiServerAxios.post( + `/ai/mental_health/${userId}/${chatId}`, + { + headers: { + "Content-Type": "application/json", + }, + ...body, + } + ); + + const data = response.data; + if (response && data) { + 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 { + console.error( + "Failed to send message:", + data.error || "Unknown error occurred" + ); + setSnackbarMessage( + data.error || "An error occurred while sending the message." + ); + setSnackbarSeverity("error"); + setOpen(true); + } + } catch (error) { + console.error("Failed to send message:", error); + setSnackbarMessage("Network or server error occurred."); + setSnackbarSeverity("error"); + setOpen(true); + } finally { + setIsLoading(false); + } + }, [input, userId, chatId, turnId]); + + // Function to handle recording start + // Function to check supported MIME types for recording + const getSupportedMimeType = () => { + if (MediaRecorder.isTypeSupported("audio/webm; codecs=opus")) { + return "audio/webm; codecs=opus"; + } else if (MediaRecorder.isTypeSupported("audio/mp4")) { + // Fallback for Safari on iOS + return "audio/mp4"; } else { - // Default to WAV if no other formats are supported - return 'audio/wav'; + // Default to WAV if no other formats are supported + return "audio/wav"; } -}; + }; -// Function to start recording -const startRecording = () => { - navigator.mediaDevices.getUserMedia({ + // Function to start recording + const startRecording = () => { + navigator.mediaDevices + .getUserMedia({ audio: { - sampleRate: 44100, - channelCount: 1, - volume: 1.0, - echoCancellation: true - } - }) - .then(stream => { + sampleRate: 44100, + channelCount: 1, + volume: 1.0, + echoCancellation: true, + }, + }) + .then((stream) => { audioChunksRef.current = []; const mimeType = getSupportedMimeType(); let recorder = new MediaRecorder(stream, { mimeType }); - recorder.ondataavailable = e => { - audioChunksRef.current.push(e.data); + recorder.ondataavailable = (e) => { + audioChunksRef.current.push(e.data); }; recorder.start(); setMediaRecorder(recorder); setIsRecording(true); - }) - .catch(error => { - console.error('Error accessing microphone:', error); + }) + .catch((error) => { + console.error("Error accessing microphone:", error); // Handle error - show message to user - }); -}; + }); + }; -// Function to stop recording -const stopRecording = () => { + // Function to stop recording + const stopRecording = () => { if (mediaRecorder) { - mediaRecorder.stream.getTracks().forEach(track => track.stop()); - - mediaRecorder.onstop = () => { - const mimeType = mediaRecorder.mimeType; - const audioBlob = new Blob(audioChunksRef.current, { type: mimeType }); - sendAudioToServer(audioBlob); - setIsRecording(false); - setMediaRecorder(null); - }; + mediaRecorder.stream.getTracks().forEach((track) => track.stop()); + + mediaRecorder.onstop = () => { + const mimeType = mediaRecorder.mimeType; + const audioBlob = new Blob(audioChunksRef.current, { type: mimeType }); + sendAudioToServer(audioBlob); + setIsRecording(false); + setMediaRecorder(null); + }; - mediaRecorder.stop(); + mediaRecorder.stop(); } -}; + }; -// Function to send audio to server -const sendAudioToServer = (audioBlob) => { + // Function to send audio to server + const sendAudioToServer = (audioBlob) => { if (audioBlob.size === 0) { - console.error('Audio Blob is empty'); - // Handle error - show message to user - return; + console.error("Audio Blob is empty"); + // Handle error - show message to user + return; } const formData = new FormData(); - formData.append('audio', audioBlob); + formData.append("audio", audioBlob); setIsLoading(true); - apiServerAxios.post('/api/ai/mental_health/voice-to-text', formData, { - headers: { - 'Content-Type': 'multipart/form-data' - } + apiServerAxios + .post("/ai/mental_health/voice-to-text", formData, { + headers: { + "Content-Type": "multipart/form-data", + }, }) - .then(response => { + .then((response) => { const { message } = response.data; setInput(message); sendMessage(); - }) - .catch(error => { - console.error('Error uploading audio:', error); + }) + .catch((error) => { + console.error("Error uploading audio:", error); // Handle error - show message to user - }) - .finally(() => { + }) + .finally(() => { setIsLoading(false); - }); -};// Remove audioChunks from dependencies to prevent re-creation - - - // Handle input changes - const handleInputChange = useCallback((event) => { - const inputText = event.target.value; - const words = inputText.split(/\s+/); - if (words.length > 200) { - // If the word count exceeds 200, prevent further input by not updating the state - setInput(input => input.split(/\s+/).slice(0, 200).join(" ")); - setSnackbarMessage('Word limit reached. Only 200 words allowed.'); - setSnackbarSeverity('warning'); - setOpen(true); - } else { - setInput(inputText); - } - }, []); - - const messageIcon = (message) => { - return message === currentPlayingMessage ? : ; + }); + }; // Remove audioChunks from dependencies to prevent re-creation + + // Handle input changes + const handleInputChange = useCallback((event) => { + const inputText = event.target.value; + const words = inputText.split(/\s+/); + if (words.length > 200) { + // If the word count exceeds 200, prevent further input by not updating the state + setInput((input) => input.split(/\s+/).slice(0, 200).join(" ")); + setSnackbarMessage("Word limit reached. Only 200 words allowed."); + setSnackbarSeverity("warning"); + setOpen(true); + } else { + setInput(inputText); } + }, []); + + const messageIcon = (message) => { + return message === currentPlayingMessage ? ( + + ) : ( + + ); + }; - return ( - <> - - - - - - - - setVoiceEnabled(e.target.checked)} - icon={} - checkedIcon={} - 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: 'primary.main', - }, - }, - }} - /> - - - - + + + + + + + + setVoiceEnabled(e.target.checked)} + icon={} + checkedIcon={} + 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: "primary.main", + }, + }, + }} + /> + + + + + + + + + + + {welcomeMessage.length === 0 && ( + + + + Welcome to Your Mental Health Companion + + + )} + + {isFetchingMessage ? ( + + ) : ( + messages.length === 0 && ( + + + + {welcomeMessage} + {voiceEnabled && welcomeMessage && ( + speak(welcomeMessage)} + size="small" + sx={{ ml: 1 }} + > + {messageIcon(welcomeMessage)} + + )} + + + ) + )} + + {messages.map((msg, index) => ( + + + {msg.sender === "agent" && ( + + )} + + + {msg.message} + {voiceEnabled && msg.sender === "agent" && ( speak(msg.message)} + size="small" + sx={{ ml: 1 }} > - + {messageIcon(msg.message)} - + )} - - {welcomeMessage.length === 0 && ( - - - - Welcome to Your Mental Health Companion - - )} - - - {isFetchingMessage ? : - (messages.length === 0 && ( - - - - {welcomeMessage} - {voiceEnabled && welcomeMessage && ( - speak(welcomeMessage)} size="small" sx={{ ml: 1, }}> - {messageIcon(welcomeMessage)} - )} - - )) - } - - {messages.map((msg, index) => ( - - - - {msg.sender === 'agent' && ( - - )} - - - - {msg.message} - {voiceEnabled && msg.sender === 'agent' && ( - speak(msg.message)} size="small" sx={{ ml: 1, }}> - {messageIcon(msg.message)} - )} - } primaryTypographyProps={{ - - sx: { - color: msg.sender === 'user' ? 'common.white' : 'text.primary', - //textAlign: msg.sender === 'user' ? 'right' : 'left', - bgcolor: msg.sender === 'user' ? 'primary.main' : 'grey.200', // You can adjust the background color here - borderRadius: '16px', // Adds rounded corners to the text - px: 2, // padding left and right within the text - py: 1, // padding top and bottom within the text - display: 'inline-block', // Ensures the background color wraps the text only - } - - }} /> - {msg.sender === 'user' && ( - - - - )} - - - ))} - - - - - - - {isRecording ? : } - {isRecording && } - - - ), - }} + } + primaryTypographyProps={{ + sx: { + color: + msg.sender === "user" + ? "common.white" + : "text.primary", + //textAlign: msg.sender === 'user' ? 'right' : 'left', + bgcolor: + msg.sender === "user" ? "primary.main" : "grey.200", // You can adjust the background color here + borderRadius: "16px", // Adds rounded corners to the text + px: 2, // padding left and right within the text + py: 1, // padding top and bottom within the text + display: "inline-block", // Ensures the background color wraps the text only + }, + }} + /> + {msg.sender === "user" && ( + + + + )} + + + ))} + + + + + + + {isRecording ? ( + + ) : ( + + )} + {isRecording && ( + - - {isLoading ? : ( - - )} - - - - - {snackbarMessage} - - - - - ); + )} + + + ), + }} + /> + + {isLoading ? ( + + ) : ( + + )} + + + + + {snackbarMessage} + + + + + ); }; - export default ChatComponent; diff --git a/client/src/Components/chatInterface.jsx b/client/src/Components/chatInterface.jsx deleted file mode 100644 index 82ee7565..00000000 --- a/client/src/Components/chatInterface.jsx +++ /dev/null @@ -1,502 +0,0 @@ -import React, { useState, useEffect, useContext, useCallback, useRef } from 'react'; -import axios from 'axios'; -import apiServerAxios from '../api/axios'; -import { InputAdornment, IconButton, Box, Card, CardContent, Typography, TextField, Button, List, ListItem, ListItemAvatar, ListItemText, CircularProgress, Snackbar, Divider, Avatar, Tooltip } from '@mui/material'; -import MuiAlert from '@mui/material/Alert'; -import SendIcon from '@mui/icons-material/Send'; -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 LibraryAddIcon from '@mui/icons-material/LibraryAdd'; -import { UserContext } from './userContext'; -import Aria from '../Assets/Images/Aria.jpg'; // Adjust the path to where your logo is stored - - -const ChatComponent = () => { - const { user,voiceEnabled , setVoiceEnabled} = useContext(UserContext); - const userId = user?.userId; - const [chatId, setChatId] = useState(0); - const [turnId, setTurnId] = useState(0); - const [input, setInput] = useState(''); - const [messages, setMessages] = useState([]); - const [isRecording, setIsRecording] = useState(false); - const [mediaRecorder, setMediaRecorder] = useState(null); - const audioChunksRef = useRef([]); - const [isLoading, setIsLoading] = useState(false); - const [open, setOpen] = useState(false); - const [snackbarMessage, setSnackbarMessage] = useState(''); - const [snackbarSeverity, setSnackbarSeverity] = useState('info'); - const [currentPlayingMessage, setCurrentPlayingMessage] = useState(null); - - const handleToggleVoice = (event) => { - event.preventDefault(); // Prevents the IconButton from triggering form submissions if used in forms - setVoiceEnabled(!voiceEnabled); - }; - - 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 setVoiceAndSpeak = () => { - 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); - }; - - if (synth.getVoices().length === 0){ - synth.onvoiceschanged = setVoiceAndSpeak; - } else { - setVoiceAndSpeak(); - } - }; - - const fetchWelcomeMessage = useCallback(async () => { - if (!userId) return; - setIsLoading(true); - setIsFetchingMessage(true); - try { - const response = await apiServerAxios.post(`/api/ai/mental_health/welcome/${userId}`, { - headers: { - "Content-Type": "application/json" - } - }); - if (response && response.data) { - const data = response.data - 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 { - console.error('Failed to fetch welcome message:', data); - setWelcomeMessage('Error fetching welcome message.'); - } - } catch (error) { - console.error('Network or server error:', error); - } finally { - setIsLoading(false); - setIsFetchingMessage(false); - } - }, [userId]); - // Fetch initial message when component mounts - useEffect(() => { - fetchWelcomeMessage(); - }, []); - - const handleSnackbarClose = (event, reason) => { - if (reason === 'clickaway') { - return; - } - setOpen(false); - }; - - const finalizeChat = useCallback(async () => { - if (chatId === null) return; - setIsLoading(true); - try { - const response = await fetch(`/api/ai/mental_health/finalize/${userId}/${chatId}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' } - }); - - const data = await response.json(); - if (response.ok) { - setSnackbarMessage('Chat finalized successfully'); - setSnackbarSeverity('success'); - // Reset chat state to start a new chat - setChatId(null); - setTurnId(0); - setMessages([]); - // Optionally, fetch a new welcome message or reset other relevant states - - } else { - setSnackbarMessage('Failed to finalize chat'); - setSnackbarSeverity('error'); - } - } catch (error) { - setSnackbarMessage('Error finalizing chat'); - setSnackbarSeverity('error'); - } finally { - setIsLoading(false); - setOpen(true); - } - }, [userId, chatId]); - - const sendMessage = useCallback(async () => { - if (!input.trim()) return; - console.log(chatId); - setIsLoading(true); - - - try { - const body = JSON.stringify({ - prompt: input, - turn_id: turnId - }); - const response = await fetch(`/api/ai/mental_health/${userId}/${chatId}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: body - }); - - const data = await response.json(); - console.log(data); - if (response.ok) { - setMessages(prev => [...prev, { message: input, sender: 'user' }, { message: data, sender: 'agent' }]); - setTurnId(prev => prev + 1); - setInput(''); - } else { - console.error('Failed to send message:', data); - setSnackbarMessage(data.error || "An error occurred while sending the message."); - setSnackbarSeverity('error'); - setOpen(true); - } - } catch (error) { - console.error('Failed to send message:', error); - setSnackbarMessage('Network or server error occurred.'); - setSnackbarSeverity('error'); - setOpen(true); - } finally { - setIsLoading(false); - - } - }, [input, userId, chatId, turnId]); - - - -// Function to check supported MIME types for recording -const getSupportedMimeType = () => { - if (MediaRecorder.isTypeSupported('audio/webm; codecs=opus')) { - return 'audio/webm; codecs=opus'; - } else if (MediaRecorder.isTypeSupported('audio/mp4')) { - // Fallback for Safari on iOS - return 'audio/mp4'; - } else { - // Default to WAV if no other formats are supported - return 'audio/wav'; - } -}; - - // Function to handle recording start -const startRecording = () => { - navigator.mediaDevices.getUserMedia({ - audio: { - sampleRate: 44100, - channelCount: 1, - volume: 1.0, - echoCancellation: true - } - }) - .then(stream => { - audioChunksRef.current = []; - const mimeType = getSupportedMimeType(); - let recorder = new MediaRecorder(stream, { mimeType }); - - recorder.ondataavailable = e => { - audioChunksRef.current.push(e.data); - }; - - recorder.start(); - setMediaRecorder(recorder); - setIsRecording(true); - }) - .catch(error => { - console.error('Error accessing microphone:', error); - // Handle error - show message to user - }); -}; - - // Function to stop recording -const stopRecording = () => { - if (mediaRecorder) { - mediaRecorder.stream.getTracks().forEach(track => track.stop()); - - mediaRecorder.onstop = () => { - const mimeType = mediaRecorder.mimeType; - const audioBlob = new Blob(audioChunksRef.current, { type: mimeType }); - sendAudioToServer(audioBlob); - setIsRecording(false); - setMediaRecorder(null); - }; - - mediaRecorder.stop(); - } -}; - - // Function to send audio to server -const sendAudioToServer = (audioBlob) => { - if (audioBlob.size === 0) { - console.error('Audio Blob is empty'); - // Handle error - show message to user - return; - } - - const formData = new FormData(); - formData.append('audio', audioBlob); - setIsLoading(true); - - apiServerAxios.post('/api/ai/mental_health/voice-to-text', formData, { - headers: { - 'Content-Type': 'multipart/form-data' - } - }) - }) - .then(response => { - const { message } = response.data; - setInput(message); - sendMessage(); - }) - .catch(error => { - console.error('Error uploading audio:', error); - // Handle error - show message to user - }) - .finally(() => { - setIsLoading(false); - }); -};// Remove audioChunks from dependencies to prevent re-creation - - - // Handle input changes - const handleInputChange = useCallback((event) => { - const inputText = event.target.value; - const words = inputText.split(/\s+/); - if (words.length > 200) { - // If the word count exceeds 200, prevent further input by not updating the state - setInput(input => input.split(/\s+/).slice(0, 200).join(" ")); - setSnackbarMessage('Word limit reached. Only 200 words allowed.'); - setSnackbarSeverity('warning'); - setOpen(true); - } else { - setInput(inputText); - } - }, []); - - const messageIcon = (message) => { - return message === currentPlayingMessage ? : ; - } - - - return ( - <> - - - - - - - - - setVoiceEnabled(e.target.checked)} - icon={} - checkedIcon={} - 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: 'primary.main', - }, - }, - }} - /> - - - - - - - - - - - - - - {messages.length === 0 && ( - - - - Welcome to Your Mental Health Companion - - )} - - - - - {messages.map((msg, index) => ( - - - - {msg.sender === 'agent' && ( - - )} - - - - {msg.message} - {voiceEnabled && msg.sender === 'agent' && ( - speak(msg.message)} size="small" sx={{ ml: 1, }}> - {messageIcon(msg.message)} - )} - } primaryTypographyProps={{ - - sx: { - color: msg.sender === 'user' ? 'common.white' : 'text.primary', - //textAlign: msg.sender === 'user' ? 'right' : 'left', - bgcolor: msg.sender === 'user' ? 'primary.main' : 'grey.200', // You can adjust the background color here - borderRadius: '16px', // Adds rounded corners to the text - px: 2, // padding left and right within the text - py: 1, // padding top and bottom within the text - display: 'inline-block', // Ensures the background color wraps the text only - } - - }} /> - {msg.sender === 'user' && ( - - - - )} - - - ))} - - - - - - - {isRecording ? : } - {isRecording && } - - - ), - }} - /> - - {isLoading ? : ( - - )} - - - - - {snackbarMessage} - - - - - ); -}; - - -export default ChatComponent; diff --git a/client/src/Components/chatLogManager.jsx b/client/src/Components/chatLogManager.jsx index 95cd15bf..9ae6c1cd 100644 --- a/client/src/Components/chatLogManager.jsx +++ b/client/src/Components/chatLogManager.jsx @@ -56,7 +56,7 @@ function ChatLogManager() { const downloadChatLogs = async (range = false) => { setLoading(true); try { - const endpoint = range ? '/api/user/download_chat_logs/range' : '/api/user/download_chat_logs'; + const endpoint = range ? '/user/download_chat_logs/range' : '/api/user/download_chat_logs'; const params = range ? { params: { start_date: startDate, end_date: endDate } } : {}; const response = await apiServerAxios.get(endpoint, { @@ -89,7 +89,7 @@ function ChatLogManager() { setDialogOpen(false); // Close dialog first setLoading(true); try { - const endpoint = dialogRange ? '/api/user/delete_chat_logs/range' : '/api/user/delete_chat_logs'; + const endpoint = dialogRange ? '/user/delete_chat_logs/range' : '/user/delete_chat_logs'; const params = dialogRange ? { params: { start_date: startDate, end_date: endDate } } : {}; const response = await apiServerAxios.delete(endpoint, { diff --git a/client/src/Components/checkInForm.jsx b/client/src/Components/checkInForm.jsx index e56513f3..5342f9ce 100644 --- a/client/src/Components/checkInForm.jsx +++ b/client/src/Components/checkInForm.jsx @@ -35,7 +35,7 @@ function CheckInForm({ userId, update }) { if (update && checkInId) { // Fetch existing check-in data setLoading(true); - apiServerAxios.get(`/api/check-in/${checkInId}`,{ + apiServerAxios.get(`/check-in/${checkInId}`,{ headers: { 'Authorization': `Bearer ${token}` // Ensure the Authorization header is set } @@ -66,7 +66,7 @@ function CheckInForm({ userId, update }) { return; } - const url = update ? `/api/check-in/${checkInId}` : '/api/check-in/schedule'; + const url = update ? `/check-in/${checkInId}` : '/api/check-in/schedule'; // Setup Axios request configuration const config = { headers: { diff --git a/client/src/Components/checkInsList.jsx b/client/src/Components/checkInsList.jsx index a26da391..ea41fcf8 100644 --- a/client/src/Components/checkInsList.jsx +++ b/client/src/Components/checkInsList.jsx @@ -64,7 +64,7 @@ function CheckInsList() { setLoading(true); try { - const response = await apiServerAxios.get(`/api/check-in/all?user_id=${userId}`, { + const response = await apiServerAxios.get(`/check-in/all?user_id=${userId}`, { headers: { 'Authorization': `Bearer ${token}` // Ensure the Authorization header is set } @@ -110,7 +110,7 @@ function CheckInsList() { const handleDeleteCheckIn = async () => { if (selectedCheckIn) { try { - await apiServerAxios.delete(`/api/check-in/${selectedCheckIn._id}`,{ + await apiServerAxios.delete(`/check-in/${selectedCheckIn._id}`,{ headers: { 'Authorization': `Bearer ${token}` // Ensure the Authorization header is set } diff --git a/client/src/Components/moodLogging.jsx b/client/src/Components/moodLogging.jsx index 8091572c..ce127ac3 100644 --- a/client/src/Components/moodLogging.jsx +++ b/client/src/Components/moodLogging.jsx @@ -22,7 +22,7 @@ function MoodLogging() { } try { - const response = await apiServerAxios.post('/api/user/log_mood', { mood, activities }, { + const response = await apiServerAxios.post('/user/log_mood', { mood, activities }, { headers: { Authorization: `Bearer ${token}` } diff --git a/client/src/Components/moodLogs.jsx b/client/src/Components/moodLogs.jsx index 68d881c4..f0f8435d 100644 --- a/client/src/Components/moodLogs.jsx +++ b/client/src/Components/moodLogs.jsx @@ -17,7 +17,7 @@ function MoodLogs() { } try { - const response = await apiServerAxios.get('/api/user/get_mood_logs', { + const response = await apiServerAxios.get('/user/get_mood_logs', { headers: { Authorization: `Bearer ${token}` } diff --git a/client/src/Components/navBar.jsx b/client/src/Components/navBar.jsx index cc7f3736..d38e68c1 100644 --- a/client/src/Components/navBar.jsx +++ b/client/src/Components/navBar.jsx @@ -35,7 +35,7 @@ function Navbar({ toggleSidebar }) { return; // Exit the function if no user ID is available } try { - const response = await apiServerAxios.get(`/api/check-in/missed?user_id=${userId}`, { + const response = await apiServerAxios.get(`/check-in/missed?user_id=${userId}`, { headers: { 'Authorization': `Bearer ${token}` // Ensure the Authorization header is set } diff --git a/client/src/Components/passwordReset.jsx b/client/src/Components/passwordReset.jsx index 04d21273..c98dc08a 100644 --- a/client/src/Components/passwordReset.jsx +++ b/client/src/Components/passwordReset.jsx @@ -23,7 +23,7 @@ function ResetPassword() { return; } try { - const response = await axios.post(`/api/user/reset_password/${token}`, { password }); + const response = await axios.post(`/user/reset_password/${token}`, { password }); setMessage(response.data.message); setIsError(false); // Navigate to auth page after a short delay diff --git a/client/src/Components/requestPasswordReset.jsx b/client/src/Components/requestPasswordReset.jsx index 8f4aa16b..35cc814e 100644 --- a/client/src/Components/requestPasswordReset.jsx +++ b/client/src/Components/requestPasswordReset.jsx @@ -14,7 +14,7 @@ function RequestPasswordReset() { e.preventDefault(); setIsLoading(true); try { - const response = await axios.post('/api/user/request_reset', { email }); + const response = await axios.post('/user/request_reset', { email }); setMessage(response.data.message); setIsError(false); } catch (error) { diff --git a/client/src/Components/userContext.jsx b/client/src/Components/userContext.jsx index 1ce624e9..f1b56bf1 100644 --- a/client/src/Components/userContext.jsx +++ b/client/src/Components/userContext.jsx @@ -52,7 +52,7 @@ export const UserProvider = ({ children }) => { console.error('No token available for logout'); return; // Exit the function if no token is available } - const response = await apiServerAxios.post('/api/user/logout', {}, { + const response = await apiServerAxios.post('/user/logout', {}, { headers: { 'Authorization': `Bearer ${token}` } @@ -72,7 +72,7 @@ export const UserProvider = ({ children }) => { const changePassword = async (userId, currentPassword, newPassword) => { try { const token = localStorage.getItem('token'); - const response = await apiServerAxios.patch(`/api/user/change_password/${userId}`, { + const response = await apiServerAxios.patch(`/user/change_password/${userId}`, { current_password: currentPassword, new_password: newPassword }, { diff --git a/client/src/Components/userProfile.jsx b/client/src/Components/userProfile.jsx index bd39b0d6..8df9ba59 100644 --- a/client/src/Components/userProfile.jsx +++ b/client/src/Components/userProfile.jsx @@ -133,7 +133,7 @@ function UserProfile() { } const fetchData = async () => { try { - const response = await apiServerAxios.get(`/api/user/profile/${userId}`); + const response = await apiServerAxios.get(`/user/profile/${userId}`); console.log("Fetched data:", response.data); const formattedData = { username: response.data.username || '', @@ -187,7 +187,7 @@ function UserProfile() { const handleSubmit = async (e) => { e.preventDefault(); try { - await apiServerAxios.patch(`/api/user/profile/${userId}`, user); + await apiServerAxios.patch(`/user/profile/${userId}`, user); setMessage('Profile updated successfully!'); setSeverity('success'); } catch (error) { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..30292ccc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "Mental-Health-Companion", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/server/agents/ai_agent.py b/server/agents/ai_agent.py index 0e08efa2..473dbefb 100644 --- a/server/agents/ai_agent.py +++ b/server/agents/ai_agent.py @@ -187,5 +187,6 @@ def _create_agent_tools(self, tool_names=[]) -> list[Tool]: ) result_tools = community_tools + custom_tools + print("Tools:", result_tools) - return result_tools + return result_tools \ No newline at end of file diff --git a/server/agents/mental_health_agent.py b/server/agents/mental_health_agent.py index 0d1b5846..1f39e83c 100644 --- a/server/agents/mental_health_agent.py +++ b/server/agents/mental_health_agent.py @@ -264,25 +264,15 @@ def run(self, message: str, with_history:bool =True, user_id: str=None, chat_id: chat_id = MentalHealthAIAgent.get_chat_id(user_id) - # if not with_history: - # return super().run(message) - # else: + # TODO: throw error if user_id, chat_id is set to None. session_id = f"{user_id}-{chat_id}" - # kwargs = { - # "timestamp": curr_epoch_time - # } + invocation = self.agent_executor.invoke( {"input": message, "user_id": user_id, "agent_scratchpad": []}, config={"configurable": {"session_id": session_id}}) - # This updates certain collections in the database based on recent history - # if (turn_id + 1) % PROCESSING_STEP == 0: - # # TODO - # self.exec_update_step(user_id, chat_id) - # pass - return invocation["output"] @@ -379,130 +369,4 @@ def perform_final_processes(self, user_id, chat_id): ) print(result) - pass - - - # def analyze_chat(self, text): - # """Analyze the chat text to determine emotional state and detect triggers.""" - # doc = nlp(text) - # emotions = [token for token in doc if token.dep_ == - # "amod" and token.head.pos_ == "NOUN"] - # triggers = [ent.text for ent in doc.ents if ent.label_ in [ - # "PERSON", "ORG", "GPE"]] - - # # Initialize a dictionary to hold detected patterns and suggested interventions - # patterns = {} - - # # Detecting various emotional states based on keywords - # lower_text = text.lower() # Convert text to lower case for case insensitive matching - # if any(keyword in lower_text for keyword in ["overwhelmed", "stressed", "too much", "can't handle", "pressure"]): - # patterns['Stress or Overwhelm'] = [ - # "Consider taking a short break to clear your mind.", - # "Engage in some deep breathing exercises or meditation to relax.", - # "Explore these resources on time management to help organize your tasks better." - # ] - # if any(keyword in lower_text for keyword in ["anxious", "worry", "nervous", "scared", "panic"]): - # patterns['Anxiety'] = [ - # "Try grounding techniques like the 5-4-3-2-1 method to calm your senses.", - # "It might be helpful to talk to a friend or counselor about your feelings.", - # "Check out these tips for managing anxiety and reducing stress." - # ] - # if any(word in lower_text for word in ["angry", "mad", "frustrated", "upset", "annoyed"]): - # patterns['Anger or Frustration'] = [ - # "Try counting to ten or practicing deep breathing.", - # "Engage in some physical activity to release energy.", - # "Consider learning more about conflict resolution skills." - # ] - # if any(word in lower_text for word in ["lonely", "alone", "isolated", "no one", "abandoned"]): - # patterns['Loneliness'] = [ - # "Consider joining community groups or online forums to connect with others.", - # "Reach out to family or friends for a chat.", - # "Explore resources to develop social skills or find social activities." - # ] - # if any(word in lower_text for word in ["scared", "fear", "terrified", "fright", "panic"]): - # patterns['Fear'] = [ - # "Practice controlled breathing to manage acute fear.", - # "Explore exposure therapy techniques under professional guidance.", - # "Seek professional help if fears persist." - # ] - # if any(word in lower_text for word in ["confused", "lost", "unclear", "disoriented", "bewildered"]): - # patterns['Confusion or Disorientation'] = [ - # "Organizational tools or apps might help structure daily tasks.", - # "Try mindfulness exercises to enhance mental clarity.", - # "Discussing these feelings with a mentor or counselor could be beneficial." - # ] - # if any(word in lower_text for word in ["grief", "loss", "mourn", "bereaved", "miss"]): - # patterns['Grief or Loss'] = [ - # "Joining support groups for similar experiences might help.", - # "Consider seeking grief counseling or therapy.", - # "Healthy grieving practices, such as memorializing the lost one, can be therapeutic." - # ] - # if any(word in lower_text for word in ["excited", "nervous", "jittery", "thrilled", "restless"]): - # patterns['Excitement or Nervousness'] = [ - # "Channel your excitement into productive activities.", - # "Use techniques like visualization or positive affirmations to calm nerves.", - # "Balance excitement with downtime to avoid burnout." - # ] - - # return {"emotions": emotions, "triggers": triggers, "patterns": patterns} - - # def _run(self, message: str, with_history=True, user_id=None, chat_id=None, turn_id=None): - # try: - # if not with_history: - # return super().run(message) - - # # memory = self.get_agent_memory(user_id, chat_id) - # memory = { - # "buffer": [] - # } - # if not memory: - # return "Error: Unable to retrieve conversation history." - - # if memory.buffer: - # addendum = f""" - # Previous Conversation Summary: - # {memory.buffer} - # """ - # self.system_message.content = f"{self.system_message.content}\n{addendum}" - - # agent_with_history = self.get_agent_with_history(memory) - - # # Analyze the message for emotional content - # analysis_results = self.analyze_chat(message) - # response_addendum = self.format_response_addendum(analysis_results) - - # # Invoke the agent with history context - # invocation = agent_with_history.invoke({"input": f"{message}\n{response_addendum}"}, config={ - # "configurable": {"user_id": user_id, "chat_id": chat_id}}) - - # self.write_agent_response_to_db( - # invocation, user_id, chat_id, turn_id) - - # return invocation["output"] - - # except Exception as e: - # return f"An error occurred: {str(e)}" - - # def format_response_addendum(self, analysis_results): - # patterns = analysis_results['patterns'] - # response_addendum = "" - # for state, suggestions in patterns.items(): - # response_addendum += f"Detected {state}: " + \ - # "; ".join(suggestions) + "\n" - # return response_addendum.strip() - - - - -# Agent Fact: -# Prepopulate to DB -# Chat Summary: -# Update every 5 chat turns -# Therapy Material -# Maybe not get it from DB at all? Just perform Bing search? -# User Entity: -# Can be saved from chat summary step, every 5 chat turns -# User Journey: -# Can be either updated at the end of the chat, or every 5 chat turns -# User Material: -# Possibly updated every 5 chat turns, at the end of a chat, or not at all \ No newline at end of file + pass \ No newline at end of file diff --git a/server/app.py b/server/app.py index 696287f9..6061356e 100644 --- a/server/app.py +++ b/server/app.py @@ -9,29 +9,19 @@ from flask_jwt_extended import JWTManager from models.subscription import db as sub_db -from routes.check_in import check_in_routes from services.db.agent_facts import load_agent_facts_to_db - -from routes.user import user_routes -from routes.ai import ai_routes +from config.config import Config +from routes import register_blueprints from flask_mail import Mail load_dotenv() - def run_app(): # Set up the app app = Flask(__name__) - app.config['JWT_SECRET_KEY'] = os.environ.get("JWT_SECRET_KEY") - app.config['SECRET_KEY'] = os.getenv("SECRET_KEY") - app.config['MAIL_USERNAME'] = os.getenv('MAIL_USERNAME') - app.config['MAIL_PASSWORD'] = os.getenv('MAIL_PASSWORD') - app.config['MAIL_PORT'] = 465 - app.config['MAIL_SERVER'] = os.getenv('MAIL_SERVER') - app.config['MAIL_USE_SSL'] = True - app.config['MAIL_USE_TLS'] = False - app.config['SECURITY_PASSWORD_SALT'] = os.getenv("SECURITY_PASSWORD_SALT") + + app.config.from_object(Config) # Debugging statements print("SECRET_KEY:", app.config['SECRET_KEY']) print("SECURITY_PASSWORD_SALT:", app.config['SECURITY_PASSWORD_SALT']) @@ -43,7 +33,7 @@ def run_app(): cors_config = { r"*": { - "origins": ["https://mental-health-app-web.azurewebsites.net", "127.0.0.1"], + "origins": ["https://mental-health-app-web.azurewebsites.net", "http://localhost:3000"], "methods": ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], "allow_headers": [ "Authorization", @@ -57,9 +47,7 @@ def run_app(): # Register routes - app.register_blueprint(user_routes) - app.register_blueprint(ai_routes) - app.register_blueprint(check_in_routes) + register_blueprints(app) # Base endpoint @app.get("/") @@ -89,5 +77,5 @@ def setup_sub_db(app): app, jwt, mail = run_app() load_agent_facts_to_db() setup_sub_db(app) - app.run(debug=True, host= HOST, port= PORT) + app.run(debug=False, host= HOST, port= PORT) diff --git a/server/config/config.py b/server/config/config.py new file mode 100644 index 00000000..c1ec9bc3 --- /dev/null +++ b/server/config/config.py @@ -0,0 +1,14 @@ +import os + +class Config: + """Set Flask configuration from environment variables.""" + SECRET_KEY = os.getenv("SECRET_KEY") + JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY") + MAIL_SERVER = os.getenv('MAIL_SERVER') + MAIL_PORT = 465 + MAIL_USERNAME = os.getenv('MAIL_USERNAME') + MAIL_PASSWORD = os.getenv('MAIL_PASSWORD') + MAIL_USE_SSL = True + MAIL_USE_TLS = False + SECURITY_PASSWORD_SALT = os.getenv("SECURITY_PASSWORD_SALT") + diff --git a/server/routes/__init__.py b/server/routes/__init__.py new file mode 100644 index 00000000..d3bea54d --- /dev/null +++ b/server/routes/__init__.py @@ -0,0 +1,9 @@ +from .user import user_routes +from .ai import ai_routes +from .check_in import check_in_routes + +def register_blueprints(app): + """Register Flask blueprints.""" + app.register_blueprint(user_routes) + app.register_blueprint(ai_routes) + app.register_blueprint(check_in_routes) diff --git a/server/services/azure.py b/server/services/azure.py index 689fd0bf..76657438 100644 --- a/server/services/azure.py +++ b/server/services/azure.py @@ -8,7 +8,7 @@ def get_azure_openai_variables(): load_dotenv() AOAI_ENDPOINT = os.environ.get("AOAI_ENDPOINT") AOAI_KEY = os.environ.get("AOAI_KEY") - AOAI_API_VERSION = "2023-09-01-preview" + AOAI_API_VERSION = "2024-05-01-preview" AOAI_EMBEDDINGS = os.getenv("EMBEDDINGS_DEPLOYMENT_NAME") AOAI_COMPLETIONS = os.getenv("COMPLETIONS_DEPLOYMENT_NAME") diff --git a/server/utils/consts.py b/server/utils/consts.py index d472f134..f6eca85f 100644 --- a/server/utils/consts.py +++ b/server/utils/consts.py @@ -11,9 +11,11 @@ You will speak in a natural, concise, and casual tone. Do not be verbose. Only answer questions pertaining to the user's personal concerns and life events. - If a message is unrelated to these topics, you must respond with something like, "I am a virtual therapy companion." + + If a message is unrelated to these topics, you must let usr know that you are "virtual therapy companion." - If you do not know the answer to a question, respond with \"I don't know.\" + If you do not know the answer to a question, respond with \"I don't know.\ + """ AGENT_FACTS = [