11import { useState , useEffect } from 'react' ;
22import { type Message } from '../model/Message' ;
3- import { fetchChatbotReply } from '../api/chatbot' ;
3+ import { type ChatSession } from '../model/ChatSession' ;
4+ import { fetchChatbotReply , createChatSession , deleteChatSession } from '../api/chatbot' ;
45import { Header } from './Header' ;
56import { Messages } from './Messages' ;
7+ import { Sidebar } from './Sidebar' ;
68import { Input } from './Input' ;
79import { chatbotStyles } from '../styles/styles' ;
810import { getChatbotText } from '../data/chatbotTexts' ;
11+ import { loadChatbotSessions , loadChatbotLastSessionId } from '../utils/chatbotStorage' ;
912import { v4 as uuidv4 } from 'uuid' ;
1013
1114/**
@@ -14,45 +17,197 @@ import { v4 as uuidv4 } from 'uuid';
1417export const Chatbot = ( ) => {
1518 const [ isOpen , setIsOpen ] = useState ( false ) ;
1619 const [ input , setInput ] = useState ( '' ) ;
20+ const [ sessions , setSessions ] = useState < ChatSession [ ] > ( loadChatbotSessions ) ;
21+ const [ currentSessionId , setCurrentSessionId ] = useState < string | null > ( loadChatbotLastSessionId ) ;
22+ const [ isSidebarOpen , setIsSidebarOpen ] = useState < boolean > ( false ) ;
23+ const [ isPopupOpen , setIsPopupOpen ] = useState < boolean > ( false ) ;
24+ const [ sessionIdToDelete , setSessionIdToDelete ] = useState < string | null > ( null ) ;
1725
1826 /**
19- * Messages shown in the chat.
20- * Initialized from sessionStorage to keep messages between refreshes .
27+ * Saving the chat sessions in the session storage only
28+ * when the component unmounts to avoid continuos savings .
2129 */
22- const [ messages , setMessages ] = useState < Message [ ] > ( ( ) => {
23- const saved = sessionStorage . getItem ( 'chatbot-messages' ) ;
24- return saved ? JSON . parse ( saved ) : [ ] ;
25- } ) ;
26- const [ loading , setLoading ] = useState ( false ) ;
27-
2830 useEffect ( ( ) => {
29- sessionStorage . setItem ( 'chatbot-messages' , JSON . stringify ( messages ) ) ;
30- } , [ messages ] ) ;
31+ const handleBeforeUnload = ( ) => {
32+ sessionStorage . setItem ( 'chatbot-sessions' , JSON . stringify ( sessions ) ) ;
33+ sessionStorage . setItem ( 'chatbot-last-session-id' , currentSessionId || '' ) ;
34+ } ;
35+
36+ window . addEventListener ( 'beforeunload' , handleBeforeUnload ) ;
37+
38+ return ( ) => {
39+ window . removeEventListener ( 'beforeunload' , handleBeforeUnload ) ;
40+ } ;
41+ } , [ sessions , currentSessionId ] ) ;
42+
43+ /**
44+ * Returns the messages of a chat session.
45+ * @param sessionId The sessionId of the chat session.
46+ * @returns The messages of the chat with id equals to sessionId
47+ */
48+ const getSessionMessages = ( sessionId : string | null ) => {
49+ if ( currentSessionId === null ) {
50+ console . error ( "No current session" )
51+ return [ ]
52+ }
53+ const chatSession = sessions . find ( item => item . id === sessionId ) ;
54+
55+ if ( chatSession ) {
56+ return chatSession . messages ;
57+ }
58+
59+ console . error ( `No session found with sessionId ${ sessionId } ` )
60+ return [ ]
61+ }
62+
63+ /**
64+ * Handles the delete process of a chat session.
65+ */
66+ const handleDeleteChat = async ( ) => {
67+ if ( sessionIdToDelete === null ) {
68+ console . error ( "No current selected to delete" )
69+ return
70+ }
71+
72+ await deleteChatSession ( sessionIdToDelete ) ;
73+ const updatedSessions = sessions . filter ( s => s . id !== sessionIdToDelete ) ;
74+ setSessions ( updatedSessions ) ;
75+ setIsPopupOpen ( false ) ;
76+ if ( updatedSessions . length === 0 ) {
77+ setCurrentSessionId ( null ) ;
78+ } else {
79+ setCurrentSessionId ( updatedSessions [ 0 ] . id ) ;
80+ }
81+ } ;
82+
83+ /**
84+ * Handles the creation process of a chat session.
85+ */
86+ const handleNewChat = async ( ) => {
87+ const id = await createChatSession ( ) ;
88+
89+ if ( id === "" ) {
90+ console . error ( "Add error showage for a couple of seconds." )
91+ return
92+ }
93+ const newSession : ChatSession = { id, messages : [ ] , createdAt : new Date ( ) . toISOString ( ) , isLoading : false } ;
94+ setSessions ( prev => [ newSession , ...prev ] ) ;
95+ setCurrentSessionId ( id ) ;
96+ } ;
3197
32- const clearMessages = ( ) => {
33- setMessages ( [ ] ) ;
34- // Once backend is ready, add logic to clear history chat
98+ const appendMessageToCurrentSession = ( message : Message ) => {
99+ setSessions ( prevSessions =>
100+ prevSessions . map ( session =>
101+ session . id === currentSessionId
102+ ? { ...session , messages : [ ...session . messages , message ] }
103+ : session
104+ )
105+ ) ;
35106 } ;
36107
108+ /**
109+ * Handles the send process in a chat session.
110+ */
37111 const sendMessage = async ( ) => {
38112 const trimmed = input . trim ( ) ;
39- if ( ! trimmed ) return ;
40-
113+ if ( ! trimmed || ! currentSessionId ) {
114+ console . error ( "No sessions available." )
115+ return ;
116+ }
117+ if ( ! trimmed ) {
118+ console . error ( "Empty message provided." )
119+ return ;
120+ }
41121 const userMessage : Message = {
42122 id : uuidv4 ( ) ,
43123 sender : 'user' ,
44124 text : trimmed ,
45125 } ;
46126
47- setMessages ( prev => [ ...prev , userMessage ] ) ;
48127 setInput ( '' ) ;
49- setLoading ( true ) ;
128+ setSessions ( prevSessions =>
129+ prevSessions . map ( session =>
130+ session . id === currentSessionId
131+ ? { ...session , isLoading : true }
132+ : session
133+ )
134+ ) ;
135+ appendMessageToCurrentSession ( userMessage ) ;
50136
51- const botReply = await fetchChatbotReply ( trimmed ) ;
52- setLoading ( false ) ;
53- setMessages ( prev => [ ...prev , botReply ] ) ;
137+ const botReply = await fetchChatbotReply ( currentSessionId ! , trimmed ) ;
138+ setSessions ( prevSessions =>
139+ prevSessions . map ( session =>
140+ session . id === currentSessionId
141+ ? { ...session , isLoading : false }
142+ : session
143+ )
144+ ) ;
145+ appendMessageToCurrentSession ( botReply ) ;
54146 } ;
55147
148+ const getChatLoading = ( ) : boolean => {
149+ const currentChat = sessions . find ( chat => chat . id === currentSessionId ) ;
150+
151+ return currentChat ? currentChat . isLoading : false ;
152+ }
153+
154+ const openSideBar = ( ) => {
155+ setIsSidebarOpen ( ! isSidebarOpen )
156+ }
157+
158+ const onSwitchChat = ( chatSessionId : string ) => {
159+ openSideBar ( ) ;
160+ setCurrentSessionId ( chatSessionId ) ;
161+ }
162+
163+ const openConfirmDeleteChatPopup = ( chatSessionId : string ) => {
164+ setSessionIdToDelete ( chatSessionId ) ;
165+ setIsPopupOpen ( true ) ;
166+ }
167+
168+ const getWelcomePage = ( ) => {
169+ return (
170+ < div
171+ style = { chatbotStyles . containerWelcomePage }
172+ >
173+ < div style = { chatbotStyles . boxWelcomePage } >
174+ < h2 style = { chatbotStyles . welcomePageH2 } > { getChatbotText ( "welcomeMessage" ) } </ h2 >
175+ < p > { getChatbotText ( "welcomeDescription" ) } </ p >
176+ < button style = { chatbotStyles . welcomePageNewChatButton }
177+ onClick = { handleNewChat }
178+ >
179+ { getChatbotText ( "createNewChat" ) }
180+ </ button >
181+ </ div >
182+ </ div >
183+ )
184+ }
185+
186+ const getDeletePopup = ( ) => {
187+ return (
188+
189+ < div
190+ style = { chatbotStyles . popupContainer }
191+ >
192+ < h2 style = { chatbotStyles . popupTitle } > { getChatbotText ( "popupTitle" ) } </ h2 >
193+ < p style = { chatbotStyles . popupMessage } >
194+ { getChatbotText ( "popupMessage" ) }
195+ </ p >
196+ < div style = { chatbotStyles . popupButtonsContainer } >
197+ < button style = { chatbotStyles . popupDeleteButton } onClick = { handleDeleteChat } >
198+ { getChatbotText ( "popupDeleteButton" ) }
199+ </ button >
200+ < button style = { chatbotStyles . popupCancelButton } onClick = { ( ) => {
201+ setIsPopupOpen ( false ) ;
202+ setSessionIdToDelete ( null ) ;
203+ } } >
204+ { getChatbotText ( "popupCancelButton" ) }
205+ </ button >
206+ </ div >
207+ </ div >
208+ )
209+ }
210+
56211 return (
57212 < >
58213 < button
@@ -64,11 +219,29 @@ export const Chatbot = () => {
64219
65220 { isOpen && (
66221 < div
67- style = { chatbotStyles . container }
222+ style = { { ... chatbotStyles . container , pointerEvents : isPopupOpen ? 'none' : 'auto' } }
68223 >
69- < Header clearMessages = { clearMessages } />
70- < Messages messages = { messages } loading = { loading } />
71- < Input input = { input } setInput = { setInput } onSend = { sendMessage } />
224+ { isSidebarOpen && (
225+ < Sidebar
226+ onClose = { ( ) => setIsSidebarOpen ( false ) }
227+ onCreateChat = { handleNewChat }
228+ onSwitchChat = { onSwitchChat }
229+ chatList = { sessions }
230+ activeChatId = { currentSessionId }
231+ openConfirmDeleteChatPopup = { openConfirmDeleteChatPopup }
232+ />
233+ ) }
234+ { isPopupOpen && (
235+ getDeletePopup ( )
236+ ) }
237+ < Header currentSessionId = { currentSessionId } openSideBar = { openSideBar } clearMessages = { openConfirmDeleteChatPopup } />
238+ { ( currentSessionId !== null ) ?
239+ < >
240+ < Messages messages = { getSessionMessages ( currentSessionId ) } loading = { getChatLoading ( ) } />
241+ < Input input = { input } setInput = { setInput } onSend = { sendMessage } />
242+ </ >
243+ : getWelcomePage ( )
244+ }
72245 </ div >
73246 ) }
74247 </ >
0 commit comments