Merged PR 17: chatbot api integration
commit
dd329559f2
|
@ -1,4 +1,6 @@
|
||||||
import * as types from "./actionTypes";
|
import * as types from "./actionTypes";
|
||||||
|
import api from "./api";
|
||||||
|
import { sendHttpRequest } from "../../redux/actions/httpActions";
|
||||||
|
|
||||||
export function summonWizard() {
|
export function summonWizard() {
|
||||||
return { type: types.SUMMON_WIZARD };
|
return { type: types.SUMMON_WIZARD };
|
||||||
|
@ -7,3 +9,130 @@ export function summonWizard() {
|
||||||
export function dismissBot() {
|
export function dismissBot() {
|
||||||
return { type: types.DISMISS_BOT };
|
return { type: types.DISMISS_BOT };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function loadBotSession(botName, userKey, botType) {
|
||||||
|
return async function(dispatch, getState) {
|
||||||
|
try {
|
||||||
|
const state = getState();
|
||||||
|
const session = state.bot.session[botType];
|
||||||
|
if (session && (session.loading || session.loaded)) {
|
||||||
|
//a session exists, so check if a chat is open
|
||||||
|
if (!state.bot.chat.loaded && !state.bot.chat.loading) {
|
||||||
|
dispatch(initializeChat(session.sessionId));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({ type: types.INITIALIZE_BOT_SESSION_STARTED, botType });
|
||||||
|
const externalId = state.frontendSession.sessionId;
|
||||||
|
const clientApplication = state.frontendSession.applicationCode;
|
||||||
|
const data = await dispatch(
|
||||||
|
sendHttpRequest(
|
||||||
|
api.getBotSession(botName, externalId, clientApplication, userKey)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
dispatch({
|
||||||
|
type: types.INITIALIZE_BOT_SESSION_SUCCESS,
|
||||||
|
payload: data,
|
||||||
|
botType
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(initializeChat(data.sessionId));
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeChat(sessionId) {
|
||||||
|
return async function(dispatch) {
|
||||||
|
try {
|
||||||
|
dispatch({ type: types.INITIALIZE_BOT_CHAT_STARTED });
|
||||||
|
const data = await dispatch(
|
||||||
|
sendHttpRequest(api.initializeChat(sessionId))
|
||||||
|
);
|
||||||
|
dispatch({
|
||||||
|
type: types.INITIALIZE_BOT_CHAT_SUCCESS,
|
||||||
|
payload: data
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeChat() {
|
||||||
|
return async function(dispatch, getState) {
|
||||||
|
try {
|
||||||
|
const { chatId } = getState().bot.chat;
|
||||||
|
if (!chatId) return;
|
||||||
|
|
||||||
|
const data = await dispatch(sendHttpRequest(api.closeChat(chatId)));
|
||||||
|
dispatch({
|
||||||
|
type: types.CLOSE_BOT_CHAT_SUCCESS,
|
||||||
|
payload: data
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveMessage(messageSourceId, messageDate, messageContent) {
|
||||||
|
return async function(dispatch, getState) {
|
||||||
|
try {
|
||||||
|
const { chatId } = getState().bot.chat;
|
||||||
|
if (!chatId) {
|
||||||
|
//the chat is not yet initialized. The message will be stored on the client and sent to the server next time.
|
||||||
|
dispatch({
|
||||||
|
type: types.STORE_BOT_MESSAGE,
|
||||||
|
message: { messageSourceId, messageDate, messageContent }
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await dispatch(checkStorage(chatId));
|
||||||
|
const event = await dispatch(
|
||||||
|
sendHttpRequest(
|
||||||
|
api.saveMessage(chatId, messageSourceId, messageDate, messageContent)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
dispatch({ type: types.SAVE_BOT_MESSAGE_SUCCESS, payload: event });
|
||||||
|
return event;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkStorage(chatId) {
|
||||||
|
return async function(dispatch, getState) {
|
||||||
|
try {
|
||||||
|
const messages = getState().bot.storage;
|
||||||
|
if (messages.length === 0) return;
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
messages.forEach(message => {
|
||||||
|
const promise = dispatch(
|
||||||
|
sendHttpRequest(
|
||||||
|
api.saveMessage(
|
||||||
|
chatId,
|
||||||
|
message.messageSourceId,
|
||||||
|
message.messageDate,
|
||||||
|
message.messageContent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
promises.push(promise);
|
||||||
|
});
|
||||||
|
|
||||||
|
//wait to save all stored messages to keep the order
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
//clear stored messages after save
|
||||||
|
dispatch({ type: types.CLEAR_BOT_STORAGE });
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,2 +1,11 @@
|
||||||
export const DISMISS_BOT = "DISMISS_BOT";
|
export const DISMISS_BOT = "DISMISS_BOT";
|
||||||
export const SUMMON_WIZARD = "SUMMON_WIZARD";
|
export const SUMMON_WIZARD = "SUMMON_WIZARD";
|
||||||
|
|
||||||
|
export const INITIALIZE_BOT_SESSION_STARTED = "INITIALIZE_BOT_SESSION_STARTED";
|
||||||
|
export const INITIALIZE_BOT_SESSION_SUCCESS = "INITIALIZE_BOT_SESSION_SUCCESS";
|
||||||
|
export const INITIALIZE_BOT_CHAT_STARTED = "INITIALIZE_BOT_CHAT_STARTED";
|
||||||
|
export const INITIALIZE_BOT_CHAT_SUCCESS = "INITIALIZE_BOT_CHAT_SUCCESS";
|
||||||
|
export const SAVE_BOT_MESSAGE_SUCCESS = "SAVE_BOT_MESSAGE_SUCCESS";
|
||||||
|
export const CLOSE_BOT_CHAT_SUCCESS = "CLOSE_BOT_CHAT_SUCCESS";
|
||||||
|
export const STORE_BOT_MESSAGE = "STORE_BOT_MESSAGE";
|
||||||
|
export const CLEAR_BOT_STORAGE = "CLEAR_BOT_STORAGE";
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { get, post } from "../../api/axiosApi";
|
||||||
|
const baseUrl = process.env.CHATBOT_API_URL;
|
||||||
|
|
||||||
|
const getBotSession = (botName, externalId, clientApplication, userKey) =>
|
||||||
|
get(
|
||||||
|
`${baseUrl}/system/initialize-session/${botName}/${externalId}/${clientApplication}/${userKey}`
|
||||||
|
);
|
||||||
|
const initializeChat = sessionId =>
|
||||||
|
get(`${baseUrl}/chat/initialize/${sessionId}`);
|
||||||
|
const closeChat = chatId =>
|
||||||
|
post(`${baseUrl}/chat/close`, {
|
||||||
|
chatId
|
||||||
|
});
|
||||||
|
const saveMessage = (chatId, messageSourceId, messageDate, messageContent) =>
|
||||||
|
post(`${baseUrl}/chat/message`, {
|
||||||
|
chatId,
|
||||||
|
messageSourceId,
|
||||||
|
messageDate,
|
||||||
|
messageContent
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getBotSession,
|
||||||
|
initializeChat,
|
||||||
|
closeChat,
|
||||||
|
saveMessage
|
||||||
|
};
|
|
@ -1,4 +0,0 @@
|
||||||
export const botType = {
|
|
||||||
none: Symbol("none"),
|
|
||||||
wizard: Symbol("wizard")
|
|
||||||
};
|
|
|
@ -2,10 +2,15 @@ import React, { useEffect, useState } from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { bindActionCreators } from "redux";
|
import { bindActionCreators } from "redux";
|
||||||
import { botType } from "../botType";
|
import { botType, bots, userKey } from "../constants";
|
||||||
import Wizard from "./Wizard";
|
import Wizard from "./Wizard";
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
import { dismissBot } from "../actionCreators";
|
import {
|
||||||
|
dismissBot,
|
||||||
|
loadBotSession,
|
||||||
|
closeChat,
|
||||||
|
saveMessage
|
||||||
|
} from "../actionCreators";
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
bot: {
|
bot: {
|
||||||
|
@ -24,13 +29,24 @@ const BotsManager = ({ bot, actions }) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (bot.type) setType(bot.type);
|
if (!bot.type) return;
|
||||||
|
setType(bot.type);
|
||||||
|
|
||||||
|
if (bot.type == botType.none) return;
|
||||||
|
actions.loadBotSession(bots.Zirhan, userKey.unknown, bot.type);
|
||||||
}, [bot.type]);
|
}, [bot.type]);
|
||||||
|
|
||||||
|
const dismissBot = () => {
|
||||||
|
actions.closeChat();
|
||||||
|
actions.dismissBot();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.botPosition}>
|
<div className={classes.botPosition}>
|
||||||
<div className={classes.bot}>
|
<div className={classes.bot}>
|
||||||
{type === botType.wizard && <Wizard dismissBot={actions.dismissBot} />}
|
{type === botType.wizard && (
|
||||||
|
<Wizard dismissBot={dismissBot} saveMessage={actions.saveMessage} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -49,7 +65,10 @@ function mapStateToProps(state) {
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
actions: bindActionCreators({ dismissBot }, dispatch)
|
actions: bindActionCreators(
|
||||||
|
{ dismissBot, loadBotSession, closeChat, saveMessage },
|
||||||
|
dispatch
|
||||||
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,9 @@ import ChatBot from "react-simple-chatbot";
|
||||||
import { ThemeProvider } from "styled-components";
|
import { ThemeProvider } from "styled-components";
|
||||||
import { useTheme } from "@material-ui/core/styles";
|
import { useTheme } from "@material-ui/core/styles";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { bots, messageSource } from "../constants";
|
||||||
|
|
||||||
const Wizard = ({ dismissBot }) => {
|
const Wizard = ({ dismissBot, saveMessage }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
@ -21,50 +22,68 @@ const Wizard = ({ dismissBot }) => {
|
||||||
userFontColor: "#4a4a4a"
|
userFontColor: "#4a4a4a"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getMessage = message => input => {
|
||||||
|
const currentDate = new Date();
|
||||||
|
let messageToSave = message;
|
||||||
|
if (message.includes("previousValue") && input.previousValue) {
|
||||||
|
messageToSave = message.replace("{previousValue}", input.previousValue);
|
||||||
|
}
|
||||||
|
saveMessage(messageSource.bot, currentDate, messageToSave);
|
||||||
|
return message;
|
||||||
|
};
|
||||||
|
const validate = text => {
|
||||||
|
const currentDate = new Date();
|
||||||
|
saveMessage(messageSource.user, currentDate, text);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{
|
{
|
||||||
id: "1",
|
id: "1",
|
||||||
message: t("Chatbot.Wizard.Message1"),
|
message: getMessage(t("Chatbot.Wizard.Message1")),
|
||||||
trigger: "2"
|
trigger: "2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2",
|
id: "2",
|
||||||
message: t("Chatbot.Wizard.Message2"),
|
message: getMessage(t("Chatbot.Wizard.Message2")),
|
||||||
trigger: "3"
|
trigger: "3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "3",
|
id: "3",
|
||||||
message: t("Chatbot.Wizard.Message3"),
|
message: getMessage(t("Chatbot.Wizard.Message3")),
|
||||||
trigger: "4"
|
trigger: "4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "4",
|
id: "4",
|
||||||
user: true,
|
user: true,
|
||||||
|
validator: validate,
|
||||||
trigger: "5"
|
trigger: "5"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "5",
|
id: "5",
|
||||||
message: t("Chatbot.Wizard.Message5"),
|
message: getMessage(t("Chatbot.Wizard.Message5")),
|
||||||
trigger: "6"
|
trigger: "6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "6",
|
id: "6",
|
||||||
user: true,
|
user: true,
|
||||||
|
validator: validate,
|
||||||
trigger: "7"
|
trigger: "7"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "7",
|
id: "7",
|
||||||
message: t("Chatbot.Wizard.Message7"),
|
message: getMessage(t("Chatbot.Wizard.Message7")),
|
||||||
trigger: "8"
|
trigger: "8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "8",
|
id: "8",
|
||||||
user: true,
|
user: true,
|
||||||
|
validator: validate,
|
||||||
trigger: "9"
|
trigger: "9"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "9",
|
id: "9",
|
||||||
message: t("Chatbot.Wizard.Message9"),
|
message: getMessage(t("Chatbot.Wizard.Message9")),
|
||||||
end: true
|
end: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -88,14 +107,15 @@ const Wizard = ({ dismissBot }) => {
|
||||||
handleEnd={handleEnd}
|
handleEnd={handleEnd}
|
||||||
steps={steps}
|
steps={steps}
|
||||||
botAvatar={getAvatar()}
|
botAvatar={getAvatar()}
|
||||||
headerTitle="Zirhan"
|
headerTitle={bots.Zirhan}
|
||||||
/>
|
/>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Wizard.propTypes = {
|
Wizard.propTypes = {
|
||||||
dismissBot: PropTypes.func.isRequired
|
dismissBot: PropTypes.func.isRequired,
|
||||||
|
saveMessage: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Wizard;
|
export default Wizard;
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
export const botType = {
|
||||||
|
none: "none",
|
||||||
|
wizard: "wizard"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bots = {
|
||||||
|
Zirhan: "Zirhan"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const userKey = {
|
||||||
|
unknown: "Unknown"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const messageSource = {
|
||||||
|
bot: 1,
|
||||||
|
user: 2
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
import * as types from "./actionTypes";
|
import * as types from "./actionTypes";
|
||||||
import initialState from "../../redux/reducers/initialState";
|
import initialState from "../../redux/reducers/initialState";
|
||||||
import { botType } from "./botType";
|
import { botType } from "./constants";
|
||||||
|
|
||||||
export default function chatbotReducer(state = initialState.bot, action) {
|
export default function chatbotReducer(state = initialState.bot, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
@ -10,6 +10,49 @@ export default function chatbotReducer(state = initialState.bot, action) {
|
||||||
case types.DISMISS_BOT:
|
case types.DISMISS_BOT:
|
||||||
return { ...state, type: botType.none };
|
return { ...state, type: botType.none };
|
||||||
|
|
||||||
|
case types.INITIALIZE_BOT_SESSION_STARTED:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
session: {
|
||||||
|
...state.session,
|
||||||
|
[action.botType]: { loading: true, loaded: false }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
case types.INITIALIZE_BOT_SESSION_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
session: {
|
||||||
|
...state.session,
|
||||||
|
[action.botType]: {
|
||||||
|
loading: false,
|
||||||
|
loaded: true,
|
||||||
|
...action.payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
case types.INITIALIZE_BOT_CHAT_STARTED:
|
||||||
|
return { ...state, chat: { loading: true, loaded: false } };
|
||||||
|
|
||||||
|
case types.INITIALIZE_BOT_CHAT_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
chat: { loading: false, loaded: true, ...action.payload }
|
||||||
|
};
|
||||||
|
|
||||||
|
case types.CLOSE_BOT_CHAT_SUCCESS:
|
||||||
|
return { ...state, chat: initialState.bot.chat };
|
||||||
|
|
||||||
|
case types.STORE_BOT_MESSAGE: {
|
||||||
|
const storage = [...state.storage];
|
||||||
|
storage.push(action.message);
|
||||||
|
return { ...state, storage };
|
||||||
|
}
|
||||||
|
|
||||||
|
case types.CLEAR_BOT_STORAGE:
|
||||||
|
return { ...state, storage: initialState.bot.storage };
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,10 @@ export default {
|
||||||
type: null
|
type: null
|
||||||
},
|
},
|
||||||
bot: {
|
bot: {
|
||||||
type: null
|
type: null,
|
||||||
|
session: {},
|
||||||
|
chat: { loading: false, loaded: false },
|
||||||
|
storage: []
|
||||||
},
|
},
|
||||||
ajaxCallsInProgress: 0
|
ajaxCallsInProgress: 0
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,8 @@ module.exports = {
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
"process.env.REVERSE_PROXY_API_URL": JSON.stringify(
|
"process.env.REVERSE_PROXY_API_URL": JSON.stringify(
|
||||||
"http://localhost:5050"
|
"http://localhost:5050"
|
||||||
)
|
),
|
||||||
|
"process.env.CHATBOT_API_URL": JSON.stringify("http://localhost:5061")
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: "src/index.html",
|
template: "src/index.html",
|
||||||
|
|
|
@ -3,7 +3,7 @@ const path = require("path");
|
||||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
const webpackBundleAnalyzer = require("webpack-bundle-analyzer");
|
const webpackBundleAnalyzer = require("webpack-bundle-analyzer");
|
||||||
const CopyPlugin = require('copy-webpack-plugin');
|
const CopyPlugin = require("copy-webpack-plugin");
|
||||||
|
|
||||||
process.env.NODE_ENV = "production";
|
process.env.NODE_ENV = "production";
|
||||||
process.env.PUBLIC_URL = "/reverse-proxy";
|
process.env.PUBLIC_URL = "/reverse-proxy";
|
||||||
|
@ -32,6 +32,9 @@ module.exports = {
|
||||||
"process.env.PUBLIC_URL": JSON.stringify(process.env.PUBLIC_URL),
|
"process.env.PUBLIC_URL": JSON.stringify(process.env.PUBLIC_URL),
|
||||||
"process.env.REVERSE_PROXY_API_URL": JSON.stringify(
|
"process.env.REVERSE_PROXY_API_URL": JSON.stringify(
|
||||||
"https://toodle.ddns.net/reverse-proxy-api"
|
"https://toodle.ddns.net/reverse-proxy-api"
|
||||||
|
),
|
||||||
|
"process.env.CHATBOT_API_URL": JSON.stringify(
|
||||||
|
"https://toodle.ddns.net/chatbot-api"
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
|
@ -52,9 +55,7 @@ module.exports = {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [{ from: "public", to: "public" }]
|
||||||
{ from: 'public', to: 'public' }
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
module: {
|
module: {
|
||||||
|
|
Loading…
Reference in New Issue