Skip to content

Commit 58b2509

Browse files
Merge pull request #119 from neo4j-labs/create-chatbot-component
Create chatbot component
2 parents efd865b + 218e3f9 commit 58b2509

File tree

15 files changed

+830
-107
lines changed

15 files changed

+830
-107
lines changed

backend/temp.pdf

220 KB
Binary file not shown.

frontend/src/App.css

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
height: calc(-322px + 100dvh);
99
border: 1px solid #d1d5db;
1010
}
11+
.fileTableWithBothDrawers{
12+
width: calc(-650px + 100dvw) !important;
13+
height: calc(-322px + 100dvh);
14+
border: 1px solid #d1d5db;
15+
}
1116

1217
.fileTableWithExpansion .ndl-div-table {
1318
width: max-content !important;
@@ -25,7 +30,26 @@
2530
gap: 5px;
2631
position: relative;
2732
}
28-
33+
.contentWithBothDrawers{
34+
width: calc(-721px + 100dvw);
35+
height: calc(100dvh - 60px);
36+
padding: 3;
37+
display: flex;
38+
flex-direction: column;
39+
align-items: center;
40+
gap: 5px;
41+
position: relative;
42+
}
43+
.contentWithChatBot{
44+
width: calc(-385px + 100dvw);
45+
height: calc(100dvh - 60px);
46+
padding: 3;
47+
display: flex;
48+
flex-direction: column;
49+
align-items: center;
50+
gap: 5px;
51+
position: relative;
52+
}
2953
.contentWithNoExpansion {
3054
width: calc(100% - 64px);
3155
height: calc(100dvh - 60px);
@@ -60,6 +84,12 @@
6084
.ndl-data-grid-root .ndl-data-grid-navigation {
6185
padding-block: 5px !important;
6286
}
87+
.ndl-drawer .ndl-drawer-close-button {
88+
background-color: rgb(var(--theme-palette-neutral-bg-strong));
89+
width: 40px !important;
90+
height: 40px !important;
91+
right: 10px !important;
92+
}
6393
.custombutton {
6494
display: flex;
6595
align-items: center;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"listMessages": [
3+
{
4+
"id": 1,
5+
"message": "Hi, I need help with creating a Cypher query for Neo4j.",
6+
"user": "user",
7+
"datetime": "01/01/2024 00:00:00"
8+
},
9+
{
10+
"id": 2,
11+
"message": "Sure, I can help with that. What specific data are you looking to retrieve?",
12+
"user": "chatbot",
13+
"datetime": "01/01/2024 00:00:00"
14+
},
15+
{
16+
"id": 3,
17+
"message": "I need to find all employees who work in the IT department.",
18+
"user": "user",
19+
"datetime": "01/01/2024 00:00:00"
20+
},
21+
{
22+
"id": 4,
23+
"message":
24+
"Alright, you can use the following query: `MATCH (e:Employee)-[:WORKS_IN]->(d:Department {name: 'IT'}) RETURN e.name`. This query matches nodes labeled 'Employee' related to the 'IT' department and returns their names.",
25+
"user": "chatbot",
26+
"datetime": "01/01/2024 00:00:00"
27+
},
28+
{
29+
"id": 5,
30+
"message": "Thanks! And how do I get the total number of such employees?",
31+
"user": "user",
32+
"datetime": "01/01/2024 00:00:00"
33+
},
34+
{
35+
"id": 6,
36+
"message":
37+
"To get the count, use: `MATCH (e:Employee)-[:WORKS_IN]->(d:Department {name: 'IT'}) RETURN count(e)`. This counts all the distinct 'Employee' nodes related to the 'IT' department.",
38+
"user": "chatbot",
39+
"datetime": "01/01/2024 00:00:00"
40+
}
41+
]
42+
}
147 KB
Loading
60.8 KB
Loading
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/* eslint-disable no-confusing-arrow */
2+
import { useEffect, useRef, useState } from 'react';
3+
import { Button, Widget, Typography, Avatar, TextInput } from '@neo4j-ndl/react';
4+
import ChatBotUserAvatar from '../assets/images/chatbot-user.png'
5+
import ChatBotAvatar from '../assets/images/chatbot-ai.png';
6+
import { ChatbotProps } from '../types';
7+
8+
9+
10+
export default function Chatbot(props: ChatbotProps) {
11+
const { messages:listMessages,setMessages:setListMessages } = props;
12+
const [inputMessage, setInputMessage] = useState('');
13+
const formattedTextStyle = { color: 'rgb(var(--theme-palette-discovery-bg-strong))' };
14+
15+
const messagesEndRef = useRef<HTMLDivElement>(null);
16+
17+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
18+
setInputMessage(e.target.value);
19+
};
20+
21+
const simulateTypingEffect = (responseText: string, index = 0) => {
22+
if (index < responseText.length) {
23+
const nextIndex = index + 1;
24+
const currentTypedText = responseText.substring(0, nextIndex);
25+
26+
if (index === 0) {
27+
const date = new Date();
28+
const datetime = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
29+
setListMessages((msgs) => [
30+
...msgs,
31+
{ id: Date.now(), user: 'chatbot', message: currentTypedText, datetime: datetime, isTyping: true },
32+
]);
33+
} else {
34+
setListMessages((msgs) => msgs.map((msg) => (msg.isTyping ? { ...msg, message: currentTypedText } : msg)));
35+
}
36+
37+
setTimeout(() => simulateTypingEffect(responseText, nextIndex), 20);
38+
} else {
39+
setListMessages((msgs) => msgs.map((msg) => (msg.isTyping ? { ...msg, isTyping: false } : msg)));
40+
}
41+
};
42+
43+
const handleSubmit = (e: { preventDefault: () => void }) => {
44+
e.preventDefault();
45+
if (!inputMessage.trim()) {
46+
return;
47+
}
48+
const date = new Date();
49+
const datetime = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
50+
const userMessage = { id: 999, user: 'user', message: inputMessage, datetime: datetime };
51+
setListMessages((listMessages) => [...listMessages, userMessage]);
52+
setInputMessage('');
53+
54+
const chatbotReply = 'Hello Sir, how can I help you today?'; // Replace with getting a response from your chatbot through your APIs
55+
simulateTypingEffect(chatbotReply);
56+
};
57+
58+
const scrollToBottom = () => {
59+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
60+
};
61+
62+
useEffect(() => {
63+
scrollToBottom();
64+
}, [listMessages]);
65+
66+
return (
67+
<div className='n-bg-palette-neutral-bg-weak flex flex-col justify-between min-h-full max-h-full overflow-hidden w-[312px]'>
68+
<div className='flex overflow-y-auto pb-12 min-w-full' style={{scrollbarWidth:"thin",overflowX:"hidden"}}>
69+
<Widget className='n-bg-palette-neutral-bg-weak' header='' isElevated={false}>
70+
<div className='flex flex-col gap-4 gap-y-4'>
71+
{listMessages.map((chat) => (
72+
<div
73+
ref={messagesEndRef}
74+
key={chat.id}
75+
className={`flex gap-2.5 items-end ${chat.user === 'chatbot' ? 'flex-row' : 'flex-row-reverse'} `}
76+
>
77+
<div className='w-8 h-8'>
78+
{chat.user === 'chatbot' ? (
79+
<Avatar
80+
className='-ml-4'
81+
hasStatus
82+
name='KM'
83+
shape='square'
84+
size='x-large'
85+
source={ChatBotAvatar}
86+
status='online'
87+
type='image'
88+
/>
89+
) : (
90+
<Avatar
91+
className=''
92+
hasStatus
93+
name='KM'
94+
shape='square'
95+
size='x-large'
96+
source={ChatBotUserAvatar}
97+
status='online'
98+
type='image'
99+
/>
100+
)}
101+
</div>
102+
<Widget
103+
header=''
104+
isElevated={true}
105+
className={`p-4 self-start ${chat.user === 'chatbot' ? 'n-bg-palette-neutral-bg-strong' : 'n-bg-palette-primary-bg-weak'
106+
}`}
107+
>
108+
<div>
109+
{chat.message.split(/`(.+?)`/).map((part, index) =>
110+
index % 2 === 1 ? (
111+
<span key={index} style={formattedTextStyle}>
112+
{part}
113+
</span>
114+
) : (
115+
part
116+
)
117+
)}
118+
</div>
119+
<div className='text-right align-bottom pt-3'>
120+
<Typography variant='body-small'>{chat.datetime}</Typography>
121+
</div>
122+
</Widget>
123+
</div>
124+
))}
125+
</div>
126+
</Widget>
127+
</div>
128+
<div className='n-bg-palette-neutral-bg-weak flex gap-2.5 bottom-0 p-2.5 w-full'>
129+
<form onSubmit={handleSubmit} className='flex gap-2.5 w-full'>
130+
<TextInput
131+
className='n-bg-palette-neutral-bg-default flex-grow-7 w-full'
132+
type='text'
133+
value={inputMessage}
134+
fluid
135+
onChange={handleInputChange}
136+
/>
137+
<Button type='submit'>Submit</Button>
138+
</form>
139+
</div>
140+
</div>
141+
);
142+
}

frontend/src/components/Content.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,15 @@ import CustomAlert from './Alert';
1010
import { extractAPI } from '../utils/FileAPI';
1111
import { ContentProps } from '../types';
1212
import { updateGraphAPI } from '../services/UpdateGraph';
13+
const Content: React.FC<ContentProps> = ({ isExpanded, showChatBot, openChatBot }) => {
1314

14-
const Content: React.FC<ContentProps> = ({ isExpanded }) => {
1515
const [init, setInit] = useState<boolean>(false);
1616
const [openConnection, setOpenConnection] = useState<boolean>(false);
1717
const [connectionStatus, setConnectionStatus] = useState<boolean>(false);
1818
const { setUserCredentials, userCredentials } = useCredentials();
1919
const { filesData, files, setFilesData, setModel, model } = useFileContext();
2020
const [errorMessage, setErrorMessage] = useState<string>('');
2121
const [showAlert, setShowAlert] = useState<boolean>(false);
22-
2322
useEffect(() => {
2423
if (!init) {
2524
let session = localStorage.getItem('neo4j.connection');
@@ -156,7 +155,14 @@ const Content: React.FC<ContentProps> = ({ isExpanded }) => {
156155
const openGraphUrl = ` https://bloom-latest.s3.eu-west-2.amazonaws.com/assets/index.html?connectURL=${userCredentials?.userName}@${localStorage.getItem('hostname')}%3A${localStorage.getItem('port') ?? '7687'
157156
}&search=Show+me+a+graph`;
158157

159-
const classNameCheck = isExpanded ? 'contentWithExpansion' : 'contentWithNoExpansion';
158+
const classNameCheck =
159+
isExpanded && showChatBot
160+
? 'contentWithBothDrawers'
161+
: isExpanded
162+
? 'contentWithExpansion'
163+
: showChatBot
164+
? 'contentWithChatBot'
165+
: 'contentWithNoExpansion';
160166
return (
161167
<>
162168
<CustomAlert open={showAlert} handleClose={handleClose} alertMessage={errorMessage} />
@@ -206,7 +212,7 @@ const Content: React.FC<ContentProps> = ({ isExpanded }) => {
206212
style={{ flexFlow: 'row', marginTop: '5px', alignSelf: 'flex-start' }}
207213
>
208214
<LlmDropdown onSelect={handleDropdownChange} isDisabled={disableCheck} />
209-
<Flex flexDirection='row' gap='2' style={{ alignSelf: 'flex-end' }}>
215+
<Flex flexDirection='row' gap='4' style={{ alignSelf: 'flex-end' }}>
210216
<Button
211217
loading={filesData.some((f) => f?.status === 'Processing')}
212218
disabled={disableCheck}
@@ -218,6 +224,13 @@ const Content: React.FC<ContentProps> = ({ isExpanded }) => {
218224
<Button href={openGraphUrl} target='_blank' disabled={disableCheckGraph} className='ml-0.5'>
219225
Open Graph
220226
</Button>
227+
<Button
228+
onClick={() => {
229+
openChatBot();
230+
}}
231+
>
232+
Q&A Chat
233+
</Button>
221234
</Flex>
222235
</Flex>
223236
</div>

frontend/src/components/FileTable.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ const FileTable: React.FC<FileTableProps> = ({ isExpanded, connectionStatus, set
137137
? item.status
138138
: getFileFromLocal(`${item.fileName}`) != null
139139
? item.status
140+
: item.status === 'Completed'
141+
? item.status
140142
: 'N/A',
141143
model: item?.model ?? model,
142144
id: uuidv4(),

frontend/src/components/Layout/DrawerDropzone.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,13 @@ const DrawerDropzone: React.FC<DrawerProps> = ({ isExpanded }) => {
5050
<Drawer
5151
expanded={isExpanded}
5252
isResizeable={false}
53+
position='left'
5354
type='push'
5455
closeable={false}
55-
onExpandedChange={function Ha() {}}
56+
key={"leftdrawer"}
57+
onExpandedChange={function Ha() {
58+
59+
}}
5660
>
5761
<Drawer.Body style={{ overflow: 'hidden', height: 'intial' }}>
5862
<div className='flex h-full flex-col'>

frontend/src/components/Layout/PageLayout.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import DrawerDropzone from './DrawerDropzone';
22
import Content from '../Content';
33
import SideNav from './SideNav';
44
import { useState } from 'react';
5+
import RightSideBar from '../RightSideBar';
56

67
export default function PageLayout() {
78
const [isExpanded, setIsexpanded] = useState<boolean>(true);
9+
const [showChatBot, setShowChatBot] = useState<boolean>(false);
10+
811
return (
912
<div style={{ maxHeight: 'calc(100vh - 60px)', display: 'flex', overflow: 'hidden' }}>
1013
<SideNav
@@ -17,7 +20,16 @@ export default function PageLayout() {
1720
}}
1821
/>
1922
<DrawerDropzone isExpanded={isExpanded} />
20-
<Content isExpanded={isExpanded} />
23+
<Content
24+
openChatBot={() => {
25+
setShowChatBot(true);
26+
}}
27+
isExpanded={isExpanded}
28+
showChatBot={showChatBot}
29+
/>
30+
<RightSideBar closeChatBot={()=>{
31+
setShowChatBot(false);
32+
}}showChatBot={showChatBot}></RightSideBar>
2133
</div>
2234
);
2335
}

0 commit comments

Comments
 (0)