Skip to main content

Creating the frontend

This tutorial is still being worked upon.

Create a WebChat component

import { useCallback, useEffect, useState } from 'react';

import { Button } from '@mui/material';

import { ChatState, ChatTranscriptItem } from '@neev/web-chat-api';
import {
ChatCloseButton,
ChatContainer,
ChatEndButton,
ChatInput,
ChatMessages,
ChatPopupContainer,
ChatPopupFooter,
ChatPopupHeader,
ChatToggleButton,
useChatHandler,
useChatStateSync,
useWebSocketHandler
} from '@neev/web-chat-react';

import { cJoin } from '../utils';
import Loading from './Loading';

import styles from './Chatbot.module.css';

// Use this values for demo only
const apiURL = 'https://cq70owwm4g.execute-api.eu-west-2.amazonaws.com/prod';
const region = 'us-east-1';

// const apiURL = '';
// const region = '';

interface IChatbotProps {
className?: string;
}

function ChatBot(props: IChatbotProps) {
// States and methods which are synced with other tabs
const {
isLoadingInAnotherTab,
setLoadingInThisTab,
isPopupOpen,
setIsPopupOpen,
participantToken,
setParticipantToken,
removeParticipantTokenFromStore,
resetSyncedStates
} = useChatStateSync({
onResetTrigger: () => {
reset();
}
});

const [chatState, setChatState] = useState<ChatState>(ChatState.NOT_STARTED);

// Check if the chat is already loading in another tab. Set chat state to resuming until the participant token is available.
useEffect(() => {
if (isLoadingInAnotherTab === true && chatState === ChatState.NOT_STARTED) {
setChatState(ChatState.RESUMING);
}
}, [chatState, isLoadingInAnotherTab]);

// messages or events received from the websocket
const [chatTranscript, setChatTranscript] = useState<ChatTranscriptItem[]>([]);

// startWebsocket: function to start the chat websocket connection
const { startWebsocket } = useWebSocketHandler({
onOpen: () => console.log('Websocket open'),
onMessage: (data) => console.log(data),
onConnection: () => {
setChatState(ChatState.ACTIVE);
setLoadingInThisTab(false);
console.log('connection established');
},
onEnded: () => {
setChatState(ChatState.ENDED);
removeParticipantTokenFromStore();
return console.log('Chat ended');
},
onEvent: (data) => console.log(data),
onTyping: () => console.log('user is typing'),
onMessageOrEvent: (data) => {
console.log(data);
setChatTranscript((prev) => [...prev, data]);
}
});

// Checking the environment variables
if (typeof apiURL !== 'string' || typeof region !== 'string') throw new Error('Env not set properly');

// chatHandler will help the user send messages and get the websocket URL
const chatHandler = useChatHandler(apiURL, region);

const resumeChat = useCallback(async (participantToken: string) => {
const websocketURL = await chatHandler.resumeChat(participantToken);
// TODO: type guard this
const transcript = (await chatHandler.fetchTranscription()) as {
Transcript: ChatTranscriptItem[];
};
console.log(transcript.Transcript);
setChatTranscript(transcript.Transcript.reverse());

return websocketURL;
}, [chatHandler]);

async function startChat() {
const websocketURL = await chatHandler.startChat({
category: 'website',
description: '',
name: '',
issue: '',
url: ''
});

if (!chatHandler.participantAPI?.participantToken) {
throw new Error('Unexpected error, participant not available');
}

setParticipantToken(chatHandler.participantAPI.participantToken);

return websocketURL;
}

// Watch for participant token, if it is available, resume the chat
useEffect(() => {
if ((chatState === ChatState.NOT_STARTED || chatState === ChatState.RESUMING) && participantToken) {
resumeChat(participantToken)
.then((websocketURL) => {
startWebsocket(websocketURL);
})
.catch((error) => {
setChatState(ChatState.ERROR);
console.error(error);
});
setChatState(ChatState.LOADING);
}
}, [chatState, participantToken, resumeChat, startWebsocket]);

function reset() {
resetSyncedStates();
setChatState(ChatState.NOT_STARTED);
setChatTranscript([]);
}

return (
<ChatContainer className={cJoin(styles.container, props.className)}>
{/* Floating action button which when clicked toggles the chat bot (popup) */}
<ChatToggleButton
isOpen={isPopupOpen}
onPopUpToggle={() => {
setIsPopupOpen(!isPopupOpen);
}}
>
<ChatPopupContainer>
<ChatPopupHeader
appBarClasses={{
root: styles.chatPopUpHeader
}}
>
Support
</ChatPopupHeader>
{chatState === ChatState.NOT_STARTED && (
<Button
sx={{
margin: '10px',
marginBlockStart: 'auto'
}}
variant="contained"
onClick={() => {
async function startOrResumeChat() {
setChatState(ChatState.LOADING);
setLoadingInThisTab(true);
let websocketURL;
if (participantToken) {
websocketURL = await resumeChat(participantToken);
} else {
websocketURL = await startChat();
}

startWebsocket(websocketURL);
}

startOrResumeChat().catch((err) => {
setChatState(ChatState.ERROR);
console.error(err);
});
}}
>
Start Chat
</Button>
)}
{(chatState === ChatState.LOADING || chatState === ChatState.RESUMING) && <Loading />}
{(chatState === ChatState.ACTIVE || chatState === ChatState.ENDED) && (
<>
<ChatMessages
chatTranscript={chatTranscript}
attachmentsEnabled={true}
onAttachmentClicks={() => {
// TODO: implement downloading attachments
console.log('download attachment');
}}
/>
{chatState === ChatState.ACTIVE && (
<ChatInput
onChatSubmit={(message) =>
chatHandler.sendMessage({
content: message
})
}
/>
)}
<ChatPopupFooter>
{chatState === ChatState.ACTIVE && (
<ChatEndButton
onClick={() => chatHandler.endChat()}
buttonClasses={{
root: styles.endChatButton
}}
>
End Chat
</ChatEndButton>
)}
{chatState === ChatState.ENDED && (
<ChatCloseButton
onClick={() => reset()}
buttonClasses={{
root: styles.endChatButton
}}
>
Close Chat
</ChatCloseButton>
)}
</ChatPopupFooter>
</>
)}
</ChatPopupContainer>
</ChatToggleButton>
</ChatContainer>
);
}

export default ChatBot;

That's it!

You'll be running a full chatbot on your website.