Merged PR 16: wizard chatbot

wizard chatbot
master
Tudor Stanciu 2020-06-06 00:31:36 +00:00
commit 34fd21a4b6
18 changed files with 689 additions and 218 deletions

656
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -28,10 +28,12 @@
"react-i18next": "^11.4.0", "react-i18next": "^11.4.0",
"react-redux": "6.0.1", "react-redux": "6.0.1",
"react-router-dom": "5.0.0", "react-router-dom": "5.0.0",
"react-simple-chatbot": "^0.6.1",
"recharts": "^1.8.5", "recharts": "^1.8.5",
"redux": "4.0.1", "redux": "4.0.1",
"redux-thunk": "2.3.0", "redux-thunk": "2.3.0",
"reselect": "4.0.0" "reselect": "4.0.0",
"styled-components": "^5.1.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.3.4", "@babel/core": "7.3.4",

View File

@ -42,3 +42,10 @@ docker run --restart=always -p 5005:80 -d cloud.canister.io:5000/tstanciu/revers
Rename container Rename container
docker rename <containet_id> reverse-proxy-frontend docker rename <containet_id> reverse-proxy-frontend
############################################################################################## ##############################################################################################
import { makeStyles, useTheme } from "@material-ui/core/styles";
const theme = useTheme();
https://www.flaticon.com/free-icon/wizard_2534554?term=wizard&page=1&position=64
https://lucasbassetti.com.br/react-simple-chatbot/#/docs/previous-value

BIN
public/icons/wizard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -55,7 +55,8 @@
"Server": { "Server": {
"Title": "Server", "Title": "Server",
"Subtitle": "Expand to see details", "Subtitle": "Expand to see details",
"Thoughts": "This reverse proxy is the only open gate to a secret creation land. There any impulse or thought can fly free and can be materialized without limits.", "Thoughts": "This reverse proxy is the only open gate to a secret creation land. There any impulse or thought can fly free and can be materialized without limits. If you don't believe it, ask the ",
"Wizard": "wizard",
"ServerHostName": "Server host", "ServerHostName": "Server host",
"ApiHostName": "API host", "ApiHostName": "API host",
"Domain": "Domain", "Domain": "Domain",
@ -81,5 +82,15 @@
}, },
"Notifications": { "Notifications": {
"MessageSaved": "Message saved" "MessageSaved": "Message saved"
},
"Chatbot": {
"Wizard": {
"Message1": "I'm the wizard.",
"Message2": "I know everything about nothing. What do you want to ask me?",
"Message3": "Please hurry. I don't have time to waste.",
"Message5": "I don't have time for that. Ask me something serious.",
"Message7": "I don't think I understand '{previousValue}'. You mean, like, flowers?",
"Message9": "I think you're wasting my time. Farewell!"
}
} }
} }

View File

@ -46,7 +46,8 @@
"Server": { "Server": {
"Title": "Server", "Title": "Server",
"Subtitle": "Extindeţi pentru a vedea detalii", "Subtitle": "Extindeţi pentru a vedea detalii",
"Thoughts": "Acest reverse proxy este singura poartă deschisă către un teren secret al creației. Acolo orice impuls sau gând poate zbura liber și poate fi materializat fără limite.", "Thoughts": "Acest reverse proxy este singura poartă deschisă către un teren secret al creației. Acolo orice impuls sau gând poate zbura liber și poate fi materializat fără limite. Dacă nu crezi, întreabă-l pe ",
"Wizard": "vrăjitor",
"ServerHostName": "Gazdă server", "ServerHostName": "Gazdă server",
"ApiHostName": "Gazdă API", "ApiHostName": "Gazdă API",
"Domain": "Domeniu", "Domain": "Domeniu",
@ -72,5 +73,15 @@
}, },
"Notifications": { "Notifications": {
"MessageSaved": "Mesaj salvat" "MessageSaved": "Mesaj salvat"
},
"Chatbot": {
"Wizard": {
"Message1": "Eu sunt vrăjitorul.",
"Message2": "Știu totul despre nimic. Ce vrei sa ma intrebi?",
"Message3": "Te rog grabeste-te. Nu am timp de pierdut.",
"Message5": "Nu am timp pentru asta. Întreabă-mă ceva serios.",
"Message7": "Nu cred că am înțeles '{previousValue}'. Vrei să spui, cum ar fi, flori?",
"Message9": "Cred că îmi irosesti timpul. Ramas bun!"
}
} }
} }

View File

@ -11,6 +11,7 @@ import { connect } from "react-redux";
import { bindActionCreators } from "redux"; import { bindActionCreators } from "redux";
import { loadFrontendSession } from "../features/frontendSession/actionCreators"; import { loadFrontendSession } from "../features/frontendSession/actionCreators";
import ToastNotifier from "../features/snackbar/components/ToastNotifier"; import ToastNotifier from "../features/snackbar/components/ToastNotifier";
import BotsManager from "../features/chatbot/components/BotsManager";
function App({ actions }) { function App({ actions }) {
useEffect(() => { useEffect(() => {
@ -25,6 +26,7 @@ function App({ actions }) {
return ( return (
<Suspense fallback={<div></div>}> <Suspense fallback={<div></div>}>
<Header /> <Header />
<BotsManager />
<br /> <br />
<div style={contentStyle}> <div style={contentStyle}>
<Switch> <Switch>

View File

@ -0,0 +1,9 @@
import * as types from "./actionTypes";
export function summonWizard() {
return { type: types.SUMMON_WIZARD };
}
export function dismissBot() {
return { type: types.DISMISS_BOT };
}

View File

@ -0,0 +1,2 @@
export const DISMISS_BOT = "DISMISS_BOT";
export const SUMMON_WIZARD = "SUMMON_WIZARD";

View File

@ -0,0 +1,4 @@
export const botType = {
none: Symbol("none"),
wizard: Symbol("wizard")
};

View File

@ -0,0 +1,56 @@
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { botType } from "../botType";
import Wizard from "./Wizard";
import { makeStyles } from "@material-ui/core/styles";
import { dismissBot } from "../actionCreators";
const useStyles = makeStyles(theme => ({
bot: {
position: "fixed",
bottom: theme.spacing(2),
right: theme.spacing(2),
zIndex: 1
},
botPosition: {
position: "absolute"
}
}));
const BotsManager = ({ bot, actions }) => {
const [type, setType] = useState(bot.type);
const classes = useStyles();
useEffect(() => {
if (bot.type) setType(bot.type);
}, [bot.type]);
return (
<div className={classes.botPosition}>
<div className={classes.bot}>
{type === botType.wizard && <Wizard dismissBot={actions.dismissBot} />}
</div>
</div>
);
};
BotsManager.propTypes = {
bot: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
};
function mapStateToProps(state) {
return {
bot: state.bot
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ dismissBot }, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(BotsManager);

View File

@ -0,0 +1,92 @@
import React from "react";
import PropTypes from "prop-types";
import ChatBot from "react-simple-chatbot";
import { ThemeProvider } from "styled-components";
import { useTheme } from "@material-ui/core/styles";
import { useTranslation } from "react-i18next";
const Wizard = ({ dismissBot }) => {
const theme = useTheme();
const { t } = useTranslation();
const botTheme = {
background: "#f5f8fb",
fontFamily: "monospace",
headerBgColor: theme.palette.primary.main,
headerFontColor: "#fff",
headerFontSize: "16px",
botBubbleColor: theme.palette.primary.main,
botFontColor: "#fff",
userBubbleColor: "#fff",
userFontColor: "#4a4a4a"
};
const steps = [
{
id: "1",
message: t("Chatbot.Wizard.Message1"),
trigger: "2"
},
{
id: "2",
message: t("Chatbot.Wizard.Message2"),
trigger: "3"
},
{
id: "3",
message: t("Chatbot.Wizard.Message3"),
trigger: "4"
},
{
id: "4",
user: true,
trigger: "5"
},
{
id: "5",
message: t("Chatbot.Wizard.Message5"),
trigger: "6"
},
{
id: "6",
user: true,
trigger: "7"
},
{
id: "7",
message: t("Chatbot.Wizard.Message7"),
trigger: "8"
},
{
id: "8",
user: true,
trigger: "9"
},
{
id: "9",
message: t("Chatbot.Wizard.Message9"),
end: true
}
];
const handleEnd = () => {
setTimeout(dismissBot, 3000);
};
return (
<ThemeProvider theme={botTheme}>
<ChatBot
handleEnd={handleEnd}
steps={steps}
botAvatar="public/icons/wizard.png"
headerTitle="Zirhan"
/>
</ThemeProvider>
);
};
Wizard.propTypes = {
dismissBot: PropTypes.func.isRequired
};
export default Wizard;

View File

@ -0,0 +1,16 @@
import * as types from "./actionTypes";
import initialState from "../../redux/reducers/initialState";
import { botType } from "./botType";
export default function chatbotReducer(state = initialState.snackbar, action) {
switch (action.type) {
case types.SUMMON_WIZARD:
return { ...state, type: botType.wizard };
case types.DISMISS_BOT:
return { ...state, type: botType.none };
default:
return state;
}
}

View File

@ -31,7 +31,8 @@ const ServerComponent = ({
serverHost, serverHost,
openAbout, openAbout,
handleOpenInNewTab, handleOpenInNewTab,
showMessageForAuthor showMessageForAuthor,
summonWizard
}) => { }) => {
const classes = useStyles(); const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
@ -92,6 +93,7 @@ const ServerComponent = ({
data={data} data={data}
serverHost={serverHost} serverHost={serverHost}
handleOpenInNewTab={handleOpenInNewTab} handleOpenInNewTab={handleOpenInNewTab}
summonWizard={summonWizard}
/> />
)} )}
</CardContent> </CardContent>
@ -133,7 +135,8 @@ ServerComponent.propTypes = {
serverHost: PropTypes.string, serverHost: PropTypes.string,
openAbout: PropTypes.func.isRequired, openAbout: PropTypes.func.isRequired,
handleOpenInNewTab: PropTypes.func.isRequired, handleOpenInNewTab: PropTypes.func.isRequired,
showMessageForAuthor: PropTypes.func.isRequired showMessageForAuthor: PropTypes.func.isRequired,
summonWizard: PropTypes.func.isRequired
}; };
export default ServerComponent; export default ServerComponent;

View File

@ -6,6 +6,7 @@ import { loadServerData, loadSystemVersion } from "../actionCreators";
import ServerComponent from "./ServerComponent"; import ServerComponent from "./ServerComponent";
import { withRouter } from "react-router-dom"; import { withRouter } from "react-router-dom";
import MessageForAuthorContainer from "../../messageForAuthor/components/MessageForAuthorContainer"; import MessageForAuthorContainer from "../../messageForAuthor/components/MessageForAuthorContainer";
import { summonWizard } from "../../chatbot/actionCreators";
const ServerContainer = ({ actions, data, serverHost, history }) => { const ServerContainer = ({ actions, data, serverHost, history }) => {
const [openMessageForAuthor, setOpenMessageForAuthor] = useState(false); const [openMessageForAuthor, setOpenMessageForAuthor] = useState(false);
@ -41,6 +42,7 @@ const ServerContainer = ({ actions, data, serverHost, history }) => {
openAbout={openAbout} openAbout={openAbout}
handleOpenInNewTab={handleOpenInNewTab} handleOpenInNewTab={handleOpenInNewTab}
showMessageForAuthor={showMessageForAuthor} showMessageForAuthor={showMessageForAuthor}
summonWizard={actions.summonWizard}
/> />
<MessageForAuthorContainer <MessageForAuthorContainer
open={openMessageForAuthor} open={openMessageForAuthor}
@ -66,7 +68,10 @@ function mapStateToProps(state) {
function mapDispatchToProps(dispatch) { function mapDispatchToProps(dispatch) {
return { return {
actions: bindActionCreators({ loadServerData, loadSystemVersion }, dispatch) actions: bindActionCreators(
{ loadServerData, loadSystemVersion, summonWizard },
dispatch
)
}; };
} }

View File

@ -7,7 +7,12 @@ import styles from "../../../components/common/styles/gridStyles";
const useStyles = makeStyles(styles); const useStyles = makeStyles(styles);
const ServerSummary = ({ data, serverHost, handleOpenInNewTab }) => { const ServerSummary = ({
data,
serverHost,
handleOpenInNewTab,
summonWizard
}) => {
const classes = useStyles(); const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
@ -42,6 +47,10 @@ const ServerSummary = ({ data, serverHost, handleOpenInNewTab }) => {
<Grid item xs={12} sm={12} md={12}> <Grid item xs={12} sm={12} md={12}>
<Typography variant="body2" gutterBottom color="textSecondary"> <Typography variant="body2" gutterBottom color="textSecondary">
{t("Server.Thoughts")} {t("Server.Thoughts")}
<Link href="#" onClick={summonWizard} variant="body2">
{t("Server.Wizard")}
</Link>
.
</Typography> </Typography>
</Grid> </Grid>
</Grid> </Grid>
@ -51,7 +60,8 @@ const ServerSummary = ({ data, serverHost, handleOpenInNewTab }) => {
ServerSummary.propTypes = { ServerSummary.propTypes = {
data: PropTypes.object.isRequired, data: PropTypes.object.isRequired,
serverHost: PropTypes.string, serverHost: PropTypes.string,
handleOpenInNewTab: PropTypes.func.isRequired handleOpenInNewTab: PropTypes.func.isRequired,
summonWizard: PropTypes.func.isRequired
}; };
export default ServerSummary; export default ServerSummary;

View File

@ -9,6 +9,7 @@ import releaseNotesReducer from "../../features/releaseNotes/reducer";
import frontendSessionReducer from "../../features/frontendSession/reducer"; import frontendSessionReducer from "../../features/frontendSession/reducer";
import snackbarReducer from "../../features/snackbar/reducer"; import snackbarReducer from "../../features/snackbar/reducer";
import chartsReducer from "../../features/charts/chartsReducer"; import chartsReducer from "../../features/charts/chartsReducer";
import chatbotReducer from "../../features/chatbot/reducer";
const rootReducer = combineReducers({ const rootReducer = combineReducers({
frontendSession: frontendSessionReducer, frontendSession: frontendSessionReducer,
@ -18,6 +19,7 @@ const rootReducer = combineReducers({
releaseNotes: releaseNotesReducer, releaseNotes: releaseNotesReducer,
charts: chartsReducer, charts: chartsReducer,
snackbar: snackbarReducer, snackbar: snackbarReducer,
bot: chatbotReducer,
ajaxCallsInProgress: ajaxStatusReducer ajaxCallsInProgress: ajaxStatusReducer
}); });

View File

@ -18,5 +18,8 @@ export default {
message: null, message: null,
type: null type: null
}, },
bot: {
type: null
},
ajaxCallsInProgress: 0 ajaxCallsInProgress: 0
}; };