Skip to content

Commit 0003897

Browse files
committed
Implement chatbot UI
1 parent 8ac6134 commit 0003897

13 files changed

Lines changed: 1465 additions & 263 deletions

File tree

services/web/package-lock.json

Lines changed: 769 additions & 255 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

services/web/package.json

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6-
"@ant-design/icons": "^4.8.1",
6+
"@ant-design/icons": "^4.8.3",
77
"@testing-library/jest-dom": "^4.2.4",
88
"@testing-library/react": "^9.3.2",
99
"@testing-library/user-event": "^7.1.2",
@@ -14,21 +14,25 @@
1414
"jsonwebtoken": "^9.0.2",
1515
"prop-types": "^15.8.1",
1616
"react": "^18.2.0",
17+
"react-chatbot-kit": "^2.2.2",
1718
"react-dom": "^18.2.0",
1819
"react-linkify": "^1.0.0-alpha",
1920
"react-redux": "^7.2.9",
2021
"react-router-dom": "^5.3.4",
2122
"react-scripts": "^3.0.1",
2223
"redux": "^4.2.1",
2324
"redux-persist": "^6.0.0",
24-
"redux-saga": "^1.3.0"
25+
"redux-saga": "^1.3.0",
26+
"styled-components": "^6.1.8",
27+
"superagent": "^8.1.2"
2528
},
2629
"scripts": {
2730
"start": "react-scripts start",
2831
"build": "react-scripts build",
2932
"test": "react-scripts test",
3033
"eject": "react-scripts eject",
31-
"lint": "prettier --check src/**/*.{js,jsx}"
34+
"lint": "prettier --check src/**/*.{js,jsx}",
35+
"lint:fix": "prettier --write src/**/*.{js,jsx}"
3236
},
3337
"eslintConfig": {
3438
"extends": "react-app"
@@ -46,6 +50,9 @@
4650
]
4751
},
4852
"devDependencies": {
53+
"@babel/cli": "^7.24.1",
54+
"@babel/core": "^7.24.4",
55+
"@babel/preset-react": "^7.24.1",
4956
"copy-webpack-plugin": "^6.3.2",
5057
"eslint": "^6.8.0",
5158
"eslint-config-airbnb": "^18.2.1",
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
/*
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the “License”);
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an “AS IS” BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
import { APIService } from "../../constants/APIConstant";
17+
const superagent = require("superagent");
18+
19+
class ActionProvider {
20+
constructor(createChatBotMessage, setStateFunc, createClientMessage) {
21+
this.createChatBotMessage = createChatBotMessage;
22+
this.setState = setStateFunc;
23+
this.createClientMessage = createClientMessage;
24+
}
25+
handleNotInitialized = () => {
26+
const message = this.createChatBotMessage(
27+
"To initialize the chatbot, please type init and press enter.",
28+
{
29+
loading: true,
30+
terminateLoading: true,
31+
},
32+
);
33+
this.addMessageToState(message);
34+
};
35+
36+
handleInitialize = () => {
37+
this.addOpenApiKeyToState(null);
38+
this.addInitializingToState();
39+
const message = this.createChatBotMessage(
40+
"Please type your OpenAI API key and press enter.",
41+
{
42+
loading: true,
43+
terminateLoading: true,
44+
},
45+
);
46+
this.addMessageToState(message);
47+
};
48+
49+
handleInitialized = (api_key) => {
50+
if (!api_key) {
51+
const message = this.createChatBotMessage(
52+
"Please enter a valid OpenAI API key.",
53+
{
54+
loading: true,
55+
terminateLoading: true,
56+
},
57+
);
58+
this.addMessageToState(message);
59+
return;
60+
}
61+
localStorage.setItem("openapi_key", api_key);
62+
this.addOpenApiKeyToState(api_key);
63+
const initUrl = APIService.CHATBOT_SERVICE + "genai/init";
64+
superagent
65+
.post(initUrl)
66+
.send({ openai_api_key: api_key })
67+
.set("Accept", "application/json")
68+
.set("Content-Type", "application/json")
69+
.end((err, res) => {
70+
if (err) {
71+
console.log(err);
72+
const errormessage = this.createChatBotMessage(
73+
"Failed to initialize chatbot. Please reverify the OpenAI API key.",
74+
{
75+
loading: true,
76+
terminateLoading: true,
77+
},
78+
);
79+
this.addMessageToState(errormessage);
80+
return;
81+
}
82+
console.log(res);
83+
const successmessage = this.createChatBotMessage(
84+
"Chatbot initialized successfully.",
85+
{
86+
loading: true,
87+
terminateLoading: true,
88+
},
89+
);
90+
this.addMessageToState(successmessage);
91+
this.addNotInitializingToState();
92+
});
93+
return;
94+
};
95+
96+
handleChat = (message) => {
97+
const chatUrl = APIService.CHATBOT_SERVICE + "genai/ask";
98+
superagent
99+
.post(chatUrl)
100+
.send({ openai_api_key: this.state.openapiKey, question: message })
101+
.set("Accept", "application/json")
102+
.set("Content-Type", "application/json")
103+
.end((err, res) => {
104+
if (err) {
105+
console.log(err);
106+
const errormessage = this.createChatBotMessage(
107+
"Failed to get response from chatbot. Please reverify the OpenAI API key.",
108+
{
109+
loading: true,
110+
terminateLoading: true,
111+
},
112+
);
113+
this.addMessageToState(errormessage);
114+
return;
115+
}
116+
console.log(res);
117+
const successmessage = this.createChatBotMessage(res.body.response, {
118+
loading: true,
119+
terminateLoading: true,
120+
});
121+
this.addMessageToState(successmessage);
122+
return;
123+
});
124+
};
125+
126+
handleHelp = () => {
127+
const message = this.createChatBotMessage(
128+
"To initialize the chatbot, please type init and press enter. To clear the chat context, type clear or reset and press enter.",
129+
{
130+
loading: true,
131+
terminateLoading: true,
132+
},
133+
);
134+
this.addMessageToState(message);
135+
};
136+
137+
handleResetContext = () => {
138+
localStorage.removeItem("messages");
139+
this.clearMessages();
140+
const message = this.createChatBotMessage(
141+
"Chat context has been cleared.",
142+
{
143+
loading: true,
144+
terminateLoading: true,
145+
},
146+
);
147+
this.addMessageToState(message);
148+
};
149+
150+
addMessageToState = (message) => {
151+
this.setState((state) => ({
152+
...state,
153+
messages: [...state.messages, message],
154+
}));
155+
};
156+
157+
addOpenApiKeyToState = (api_key) => {
158+
this.setState((state) => ({
159+
...state,
160+
openapiKey: api_key,
161+
}));
162+
};
163+
164+
addInitializingToState = () => {
165+
this.setState((state) => ({
166+
...state,
167+
initializing: true,
168+
}));
169+
};
170+
171+
addNotInitializingToState = () => {
172+
this.setState((state) => ({
173+
...state,
174+
initializing: false,
175+
}));
176+
};
177+
178+
clearMessages = () => {
179+
this.setState((state) => ({
180+
...state,
181+
messages: [],
182+
}));
183+
};
184+
}
185+
186+
export default ActionProvider;
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the “License”);
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an “AS IS” BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
import React, { useState, useEffect } from "react";
17+
18+
import config from "./config.jsx";
19+
import MessageParser from "./MessageParser.jsx";
20+
import ActionProvider from "./ActionProvider.jsx";
21+
import Chatbot from "react-chatbot-kit";
22+
import { createChatBotMessage } from "react-chatbot-kit";
23+
import {
24+
PageHeader,
25+
Card,
26+
Row,
27+
Col,
28+
Tooltip,
29+
Button,
30+
Avatar,
31+
Descriptions,
32+
Layout,
33+
Alert,
34+
} from "antd";
35+
import { Space } from "antd";
36+
import Icon, { CloseSquareOutlined, DeleteOutlined } from "@ant-design/icons";
37+
import "./chatbot.css";
38+
39+
const PandaSvg = () => (
40+
<svg viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor">
41+
<path
42+
d="M437.333,21.355H74.667C33.493,21.355,0,54.848,0,96.021v213.333c0,41.173,33.493,74.667,74.667,74.667h48.256 l-36.821,92.032c-1.771,4.395-0.405,9.429,3.328,12.352c1.92,1.515,4.245,2.283,6.592,2.283c2.176,0,4.352-0.661,6.208-1.984 l146.56-104.683h188.587c41.173,0,74.667-33.493,74.667-74.667V96.021C512,54.848,478.507,21.355,437.333,21.355z"
43+
fill="#1890FF"
44+
/>
45+
</svg>
46+
);
47+
48+
const PandaIcon = (props) => <Icon component={PandaSvg} {...props} />;
49+
50+
const ChatBotComponent = () => {
51+
const [chatbotState, setChatbotState] = useState({
52+
messages: [],
53+
openapiKey: localStorage.getItem("openapi_key"),
54+
initializing: false,
55+
});
56+
57+
const [showBot, toggleBot] = useState(false);
58+
59+
const saveMessages = (messages, HTMLString) => {
60+
localStorage.setItem("chat_messages", JSON.stringify(messages));
61+
};
62+
63+
const loadMessages = () => {
64+
const messages = JSON.parse(localStorage.getItem("chat_messages"));
65+
return messages;
66+
};
67+
68+
const clearHistory = () => {
69+
localStorage.removeItem("chat_messages");
70+
};
71+
72+
// useEffect(() => {
73+
// // const handleOutsideClick = (e) => {
74+
// // // simple implementation, should be made more robust.
75+
// // try{
76+
// // if (!e.currentTarget.classList.includes("react-chatbot-kit")) {
77+
// // toggleBot(false)
78+
// // }
79+
// // }catch(e){
80+
// // console.log(e)
81+
// // }
82+
// // }
83+
84+
// // window.addEventListener("click", handleOutsideClick)
85+
86+
// // return () => {
87+
// // window.removeEventListener("click", handleOutsideClick)
88+
// // }
89+
// }, [])
90+
91+
return (
92+
<Row>
93+
<Col xs={10}>
94+
<div className="app-chatbot-container">
95+
<div style={{ maxWidth: "500px" }}>
96+
{showBot && (
97+
<Chatbot
98+
config={config}
99+
botAvator={
100+
<Icon
101+
icon={PandaIcon}
102+
className="app-chatbot-button-icon"
103+
style={{ fontSize: "40", color: "white" }}
104+
/>
105+
}
106+
actionProvider={ActionProvider}
107+
messageParser={MessageParser}
108+
saveMessages={saveMessages}
109+
messageHistory={loadMessages()}
110+
headerText={
111+
<Space>
112+
Exploit CrapBot &nbsp; &nbsp;
113+
<a
114+
style={{
115+
color: "white",
116+
fontWeight: "bold",
117+
background: "#0a5e9c",
118+
borderRadius: "0px",
119+
}}
120+
href="#"
121+
onClick={() => clearHistory()}
122+
>
123+
<DeleteOutlined />
124+
</a>
125+
&nbsp; &nbsp;
126+
<a
127+
style={{
128+
color: "white",
129+
fontWeight: "bold",
130+
background: "#0a5e9c",
131+
borderRadius: "0px",
132+
}}
133+
href="#"
134+
onClick={() => toggleBot((prev) => !prev)}
135+
>
136+
<CloseSquareOutlined />
137+
</a>
138+
</Space>
139+
}
140+
placeholderText={"Type something..."}
141+
close="true"
142+
/>
143+
)}
144+
<button
145+
className="app-chatbot-button"
146+
onClick={() => toggleBot((prev) => !prev)}
147+
>
148+
<PandaIcon style={{ fontSize: "24px" }} />
149+
</button>
150+
</div>
151+
</div>
152+
</Col>
153+
</Row>
154+
);
155+
};
156+
157+
export default ChatBotComponent;

0 commit comments

Comments
 (0)