| import ArrowRightAltIcon from "@mui/icons-material/ArrowRightAlt"; | |
| import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward"; | |
| import GraphicEqIcon from "@mui/icons-material/GraphicEq"; | |
| import PersonIcon from "@mui/icons-material/Person"; | |
| import { Box, IconButton, InputBase, Menu, MenuItem } from "@mui/material"; | |
| import React, { useState, useContext, useEffect, useCallback } from "react"; | |
| import ReactMarkdown from "react-markdown"; | |
| import { useNavigate } from "react-router-dom"; | |
| import remarkGfm from "remark-gfm"; | |
| import { AuthContext } from "../../../context/AuthContext"; | |
| import useWebSocket from "../../../hooks/useTextWebhook"; | |
| import { ICreateConversationResponse } from "../../../interfaces/conversation"; | |
| import { logout } from "../../../services/api/authService"; | |
| import { createConversation } from "../../../services/api/chatService"; | |
| interface ChatScreenProps { | |
| isOpen: boolean; | |
| toggleSidebar: () => void; | |
| } | |
| const ChatScreen: React.FC<ChatScreenProps> = ({ isOpen, toggleSidebar }) => { | |
| const [input, setInput] = useState<string>(""); | |
| const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); | |
| const { messages, sendMessage, connectWebSocket } = useWebSocket(); | |
| const authContext = useContext(AuthContext); | |
| const navigate = useNavigate(); | |
| useEffect(() => { | |
| const initializeConversation = async () => { | |
| try { | |
| const conv = await createConversation({ modality: "text" }); | |
| const sessionData: ICreateConversationResponse = await conv.data; | |
| const conversationId = sessionData.conversation_id; | |
| connectWebSocket(conversationId); | |
| } catch (error) { | |
| console.error("Failed to initialize conversation:", error); | |
| } | |
| }; | |
| initializeConversation(); | |
| }, [connectWebSocket]); | |
| const handleSendMessage = useCallback(() => { | |
| if (input.trim()) { | |
| try { | |
| sendMessage(input.trim()); | |
| setInput(""); | |
| } catch (error) { | |
| console.error("Failed to send message:", error); | |
| } | |
| } | |
| }, [input, sendMessage]); | |
| const handleVoiceInput = useCallback(() => { | |
| navigate("/voice-to-voice"); | |
| }, [navigate]); | |
| const handleBotIconClick = useCallback((event: React.MouseEvent<HTMLElement>) => { | |
| setAnchorEl(event.currentTarget); | |
| }, []); | |
| const handleMenuClose = useCallback(() => { | |
| setAnchorEl(null); | |
| }, []); | |
| const handleLogout = useCallback(async () => { | |
| try { | |
| await logout(); | |
| } finally { | |
| authContext?.logout(); | |
| } | |
| }, [authContext]); | |
| return ( | |
| <Box | |
| sx={{ | |
| display: "flex", | |
| flexDirection: "column", | |
| height: "100%", | |
| bgcolor: "#f9f9f9", | |
| color: "#333", | |
| }} | |
| > | |
| <Box | |
| sx={{ | |
| display: "flex", | |
| justifyContent: "space-between", | |
| alignItems: "center", | |
| px: 2, | |
| py: 1, | |
| borderBottom: "1px solid #e0e0e0", | |
| bgcolor: "#ffffff", | |
| zIndex: 10, | |
| }} | |
| > | |
| <Box sx={{ display: "flex", alignItems: "center" }}> | |
| {!isOpen && ( | |
| <IconButton onClick={toggleSidebar} size="large"> | |
| <ArrowRightAltIcon /> | |
| </IconButton> | |
| )} | |
| <Box | |
| sx={{ | |
| fontSize: "34px", | |
| fontWeight: "bold", | |
| ml: 1, | |
| }} | |
| > | |
| Chat Bot | |
| </Box> | |
| </Box> | |
| <Box> | |
| <IconButton size="large" onClick={handleBotIconClick}> | |
| <PersonIcon sx={{ fontSize: 34, color: "black" }} /> | |
| </IconButton> | |
| <Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}> | |
| <MenuItem onClick={handleLogout}>Logout</MenuItem> | |
| </Menu> | |
| </Box> | |
| </Box> | |
| <Box | |
| sx={{ | |
| flexGrow: 1, | |
| overflowY: "auto", | |
| p: 2, | |
| }} | |
| > | |
| {messages.map((msg, index) => ( | |
| <Box | |
| key={index} | |
| sx={{ | |
| display: "flex", | |
| alignItems: "center", | |
| justifyContent: msg.sender === "user" ? "flex-end" : "flex-start", | |
| my: 1, | |
| }} | |
| > | |
| {msg.sender === "bot" && ( | |
| <Box sx={{ mr: 1 }}> | |
| <PersonIcon sx={{ fontSize: 30, color: "#ccc" }} /> | |
| </Box> | |
| )} | |
| <Box | |
| sx={{ | |
| maxWidth: "70%", | |
| p: "10px 15px", | |
| borderRadius: "20px", | |
| bgcolor: msg.sender === "user" ? "#e0e0e0" : "#f5f5f5", | |
| color: msg.sender === "user" ? "#333" : "#555", | |
| }} | |
| > | |
| {msg.sender === "bot" ? ( | |
| <ReactMarkdown remarkPlugins={[remarkGfm]}> | |
| {`${msg.text}${!msg.isComplete ? "▊" : ""}`} | |
| </ReactMarkdown> | |
| ) : ( | |
| msg.text | |
| )} | |
| </Box> | |
| </Box> | |
| ))} | |
| </Box> | |
| <Box | |
| sx={{ | |
| display: "flex", | |
| alignItems: "center", | |
| p: 2, | |
| borderTop: "1px solid #e0e0e0", | |
| bgcolor: "#ffffff", | |
| position: "sticky", | |
| bottom: 0, | |
| zIndex: 10, | |
| }} | |
| > | |
| <InputBase | |
| fullWidth | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| onKeyDown={(e) => e.key === "Enter" && handleSendMessage()} | |
| placeholder="Type a message..." | |
| sx={{ | |
| flex: 1, | |
| px: 2, | |
| py: 1, | |
| border: "1px solid #ddd", | |
| borderRadius: "20px", | |
| bgcolor: "#f9f9f9", | |
| "& input::placeholder": { | |
| color: "#aaa", | |
| }, | |
| }} | |
| /> | |
| <IconButton | |
| onClick={input.length === 0 ? handleVoiceInput : handleSendMessage} | |
| sx={{ | |
| ml: 1, | |
| bgcolor: "text.primary", | |
| color: "white", | |
| "&:hover": { | |
| bgcolor: "darkgray", | |
| }, | |
| padding: "5px", | |
| borderRadius: "8px", | |
| }} | |
| > | |
| {input.length === 0 ? <GraphicEqIcon /> : <ArrowUpwardIcon />} | |
| </IconButton> | |
| </Box> | |
| </Box> | |
| ); | |
| }; | |
| export default ChatScreen; | |