List SUSI.AI Devices in Admin Panel

In this blog I’ll be explaining about the Devices Tab in SUSI.AI Admin Panel. Admins can now view the connected devices of the users with view, edit and delete actions. Also the admins can directly view the location of the device on the map by clicking on the device location of that user. Implementation List Devices Devices tab displays device name, macId, room, email Id, date added, last active, last login IP and location of the device. loadDevices function is called on componentDidMount which calls the fetchDevices API which fetches the list of devices from /aaa/getDeviceList.json endpoint. List of all devices is stored in devices array. Each device in the array is an object with the above properties. Clicking on the device location opens a popup displaying the device location on the map. loadDevices = () => { fetchDevices() .then(payload => { const { devices } = payload; let devicesArray = []; devices.forEach(device => { const email = device.name; const devices = device.devices; const macIdArray = Object.keys(devices); const lastLoginIP = device.lastLoginIP !== undefined ? device.lastLoginIP : '-'; const lastActive = device.lastActive !== undefined ? new Date(device.lastActive).toDateString() : '-'; macIdArray.forEach(macId => { const device = devices[macId]; let deviceName = device.name !== undefined ? device.name : '-'; deviceName = deviceName.length > 20 ? deviceName.substr(0, 20) + '...' : deviceName; let location = 'Location not given'; if (device.geolocation) { location = ( {device.geolocation.latitude},{device.geolocation.longitude} ); } const dateAdded = device.deviceAddTime !== undefined ? new Date(device.deviceAddTime).toDateString() : '-'; const deviceObj = { deviceName, macId, email, room: device.room, location, latitude: device.geolocation !== undefined ? device.geolocation.latitude : '-', longitude: device.geolocation !== undefined ? device.geolocation.longitude : '-', dateAdded, lastActive, lastLoginIP, }; devicesArray.push(deviceObj); }); }); this.setState({ loadingDevices: false, devices: devicesArray, }); }) .catch(error => { console.log(error); }); }; View Device View action redirects to users /mydevices?email<email>&macid=<macid>. This allows admin to have full control of the My devices section of the user. Admin can change device details and delete device. Also admin can see all the devices of the user from the ALL tab. To edit a device click on edit icon in the table, update the details and click on check icon. To delete a device click on the delete device which then asks for confirmation of device name and on confirmation deletes the device. Edit Device Edit actions opens up a dialog modal which allows the admin to update the device name and room. Clicking on the edit button calls the modifyUserDevices API which takes in email Id, macId, device name and room name as parameters. This calls the API endpoint /aaa/modifyUserDevices.json. handleChange = event => { this.setState({ [event.target.name]: event.target.value }); }; render() { const { macId, email, handleConfirm, handleClose } = this.props; const { room, deviceName } = this.state; return ( <React.Fragment> <DialogTitle>Edit Device Details for {macId}</DialogTitle> <DialogContent> <OutlinedTextField value={deviceName} label="Device Name" name="deviceName" variant="outlined" onChange={this.handleChange} style={{ marginRight: '20px' }} /> <OutlinedTextField value={room} label="Room" name="room" variant="outlined" onChange={this.handleChange} /> </DialogContent> <DialogActions> <Button key={1} color="primary" onClick={() => handleConfirm(email, macId, room, deviceName)}> Change </Button> <Button key={2} color="primary" onClick={handleClose}> Cancel </Button> </DialogActions>…

Continue ReadingList SUSI.AI Devices in Admin Panel

CRUD operations on Config Keys in Admin Panel of SUSI.AI

SUSI.AI Admin Panel now allows the Admin to create, read, update and delete config keys present in system settings. Config keys are API keys which are used to link the application to third party services like Google Maps, Google ReCaptcha, Google Analytics, Matomo, etc. The API key is a unique identifier that is used to authenticate requests associated with the project for usage and billing purposes. CRUD Operations Create Config Key To create a config key click on “Add Config Key” Button, a dialog opens up which has two field Key Name and Key Value. this.props.actions.openModal opens up the shared Dialog Modal. On clicking on “Create”, the createApiKey is called which takes in the two parameters. handleCreate = () => { this.props.actions.openModal({ modalType: 'createSystemSettings', type: 'Create', handleConfirm: this.confirmUpdate, keyName: this.state.keyName, keyValue: this.state.keyValue, handleClose: this.props.actions.closeModal, }); }; handleSave = () => { const { keyName, keyValue } = this.state; const { handleConfirm } = this.props; createApiKey({ keyName, keyValue }) .then(() => handleConfirm()) .catch(error => { console.log(error); }); }; Read Config Key API endpoint fetchApiKeys is called on componentDidMount and when Config Key is created, updated or deleted. fetchApiKeys = () => { fetchApiKeys() .then(payload => { let apiKeys = []; let i = 1; let keys = Object.keys(payload.keys); keys.forEach(j => { const apiKey = { serialNum: i, keyName: j, value: payload.keys[j], }; ++i; apiKeys.push(apiKey); }); this.setState({ apiKeys: apiKeys, loading: false, }); }) .catch(error => { console.log(error); }); }; Update Config Key To Update a config key click on edit from the actions column, Update Config Key dialog opens up which allows you to edit the key value. On clicking on update, the createApiKey API is called. handleUpdate = row => { this.props.actions.openModal({ modalType: 'updateSystemSettings', type: 'Update', keyName: row.keyName, keyValue: row.value, handleConfirm: this.confirmUpdate, handleClose: this.props.actions.closeModal, }); }; Delete Config Key To delete a config key click on delete from actions column, delete config key confirmation dialog opens up. On clicking on Delete, the deleteApiKey is called which takes in key name as parameter. handleDelete = row => { this.setState({ keyName: row.keyName }); this.props.actions.openModal({ modalType: 'deleteSystemSettings', keyName: row.keyName, handleConfirm: this.confirmDelete, handleClose: this.props.actions.closeModal, }); }; confirmDelete = () => { const { keyName } = this.state; deleteApiKey({ keyName }) .then(this.fetchApiKeys) .catch(error => { console.log(error); }); this.props.actions.closeModal(); }; In conclusion, CRUD operations of Config Keys help admins to manage third party services. With these operations the admin can manage the API keys of various services without having to look for them in the backend. Resources What is CRUD?CRUD in React SUSI.AI repository

Continue ReadingCRUD operations on Config Keys in Admin Panel of SUSI.AI

Dialog Component in SUSI.AI

Dialog Component in SUSI.AI is rendered in App.js to remove code redundancy. Redux is integrated in the Dialog component which allows us to open/close the dialog from any component by altering the modal states. This implementation allows us to get rid of the need of having dialog component in different components. Redux Code There are two actions and reducers which control the dialog component. Default state of isModalOpen is false and modalType is an empty string. To open a dialog modal the action openModal is dispatched, which sets isModalOpen to true and the modalType. To close a dialog modal the action closeModal is dispatched, which sets isModalOpen to default state i.e. false. import { handleActions } from 'redux-actions'; import actionTypes from '../actionTypes'; const defaultState = { modalProps: { isModalOpen: false, modalType: '', }, }; export default handleActions( { [actionTypes.UI_OPEN_MODAL](state, { payload }) { return { ...state, modalProps: { isModalOpen: true, ...payload, }, }; }, [actionTypes.UI_CLOSE_MODAL](state) { return { ...state, modalProps: defaultState.modalProps, }; }, } defaultState, ); Shared Dialog Component Dialog Modal can be opened from any component by dispatching an action.  To open a Dialog Modal: this.props.actions.openModal({modalType: [modal name]}); To close a Dialog Modal: this.props.actions.closeModal(); Shared Dialog Component has a DialogData object which contains objects with two main properties : Dialog component and Dialog size. Other props can also be passed along with these two properties such as fullScreen. Dialog Content of different Dialogs are present in their respective folders. Each Dialog Content has a Title, Content and Actions.Different Dialog types present are: Confirm Delete with Input: This dialog modal is used when a user deletes account, device and skill. Confirm Dialog: This dialog modal is used where confirmation is required from the user/admin such as on changing skill status, on password reset,etc.Share Dialog: This dialog modal opens up when the share icon is clicked in the chat.Standard Action Dialog: This dialog modal opens up on restore skill, delete feedback, system settings and bot.Tour Dialog: This dialog modal opens up SUSI.AI tour. To add a new Dialog to DialogSection, the steps are: Import the Dialog Content ComponentAdd the Dialog Component to DialogData object in the following manner: const DialogData = { [dialog componet name]: { Component : [imported dialog component name], size : [size of the Dialog Component]}, } Code (Reduced) const DialogData = {   login: { Component: Login, size: 'sm' }, } const DialogSection = props => {  const {    actions,    modalProps: { isModalOpen, modalType, ...otherProps },    visited,  } = props;  const getDialog = () => {    if (isModalOpen) {      return DialogData[modalType];    }    return DialogData.noComponent;  };  const { size, Component, fullScreen = false } = getDialog(); return ( <Dialog     maxWidth={size}     fullWidth={true}     open={isModalOpen || !visited}     onClose={isModalOpen ? actions.closeModal : actions.setVisited}      fullScreen={fullScreen}    >     <DialogContainer>       {Component ? <Component {...otherProps} /> : null}     </DialogContainer>  </Dialog> ) }; In conclusion, having a shared dialog component reduces redundant code and allows to have a similar Dialog UI across the…

Continue ReadingDialog Component in SUSI.AI

Different views for SUSI Skill Creator

SUSI Skill Creator is a service provided to easily create skills for SUSI. The skill can be written in the form of code. The coding syntax is described in SUSI skill tutorial. The problem with this is that we can't expect everyone to be great coders and be able to understand the document and easily create a skill. Hence, we needed some robust alternatives for people who don’t want to write the code. This is where UI View and Tree View comes in. What is UI View? UI View or User Interface View shows the skill in form of chat bubbles. This is useful for demonstrating how the actual chat will look like. Hello Hi. I’m SUSI. The following code will be shown as this in conversation view: What is Tree View? Tree view shows the conversation in form of a tree with its branching representing the chats. Hello|Hi Hi. I'm SUSI.|Hey there! The following code will be shown as this in tree view: How are the views synchronised? We’re basically not making any API calls or performing any major function in any of the views. The components of different views are solely for their own function. All the API calls are made in a parent component named SkillCreator. The main reason for creating a parent component is that we need the category, language, name and commit message options to appear on all the three views. Hence, it only makes sense to have a parent component for them and different components for the views. The default code (that appears in code editor by default) is in a state in SkillCreator. We pass this state to the code view component as a props. <CodeView   skillCode={this.state.code}   sendInfoToProps={this.sendInfoToProps} /> Now, we need to get any change made in the code in CodeView and change the code state of SkillCreator accordingly. For that, we have a function named sendInfoToProps which is passed as props to CodeView and is then called from CodeView whenever we make some change. This will be more clear after having a look at the function sendInfoToProps: sendInfoToProps = value => {  if (value) {   this.setState(    {      code: value.code ? value.code : this.state.code,      expertValue: value.expertValue        ? value.expertValue        : this.state.expertValue,      groupValue: value.groupValue        ? value.groupValue        : this.state.groupValue,      languageValue: value.languageValue        ? value.languageValue        : this.state.groupValue,   imagUrl: value.imageUrl ? value.imageUrl : this.state.imageUrl,    },    () => this.generateSkillData(),   );  } }; You can see that we update the code in SkillCreator and other states required. Also, you can notice the we’re calling generateSkillData function here. This is done to convert the code to skill data which is sent to Conversation view and Tree view as props. We’re calling generateSkillData as a callback function because setState is an asynchronous function. This will demonstrate how this is passed to Conversation view: <ConversationView   skillData={this.state.skillData}   handleDeleteNode={this.handleDeleteNode} /> Same is the case for this handleDeleteNode function. We have an option in both Conversation view and Tree view to allow the user to delete a conversation (originally written as skill in code view). On…

Continue ReadingDifferent views for SUSI Skill Creator

Code view and UI View in design tab of bot wizard

Design tab is for designing your SUSI AI chatbot. You can change the background colour, add an image as background color, change colour of user text box, bot text box etc. On SUSI AI bot wizard design tab, we have two views. These views show the same thing but in a different way for different people. The code view is mainly for the developers while the UI (User Interface) view is for a non-developer user because we can not expect them to know how to code even if it is easy. Code View The current code view of design tab looks like this: ::bodyBackground #ffffff ::bodyBackgroundImage ::userMessageBoxBackground #0077e5 ::userMessageTextColor #ffffff ::botMessageBoxBackground #f8f8f8 ::botMessageTextColor #455a64 ::botIconColor #000000 ::botIconImage For changing colours, you simply have to change the hex codes and for adding images as background, you need to add the url of image. You can also upload an image but that function is provided in the UI View. UI View The current UI view of design tab looks like this: Here, we have colour picker for user to choose a colour for various components of the chatbot. There’s also a small box with the same colour as specified in hex code for previewing. Switching and Synchronising between Code view and UI view We have different components for code view and UI view. In order to synchronise both the views, we need to have the same code in state of both components. To do that, we add the code in state of the parent component i.e. design.js and then pass it to code view and UI view as states. The following code snippet will demonstrate this: <CodeView     design={{         sendInfoToProps: this.sendInfoToProps,         updateSettings: this.updateSettings,         code: this.state.code,     }} /> You can see that we pass a whole object as props. Using this approach, same code is passed to both the views. This solves the first problem. The next problem is that if we do a change in code view then it should happen in the code state of parent file as well. Same goes for UI view. To do this, whenever we change the code in code view or UI view, we call a function in the parent file (design.js) which was passed as props and we pass the state of code view or UI view as arguments of this function. This function then updates the state accordingly. The name of the function is sendInfoToProps and it is as follows: sendInfoToProps = values => {     this.setState({ ...values }); }; You can see that it simply updates the state value as per the parameters passed to it. So this is how synchronisation works between different views. References: ReactJS Props: https://www.tutorialspoint.com/reactjs/reactjs_props_overview.htm 

Continue ReadingCode view and UI View in design tab of bot wizard

Modifying SUSI Skills using SUSI Skill CMS

SUSI Skill CMS is a complete solution right from creating a skill to modifying the skill. The skills in SUSI are well synced with the remote repository and can be completely modified using the Edit Skill feature of SUSI Skill CMS. Here’s how to Modify a Skill. Sign Up/Login to the website using your credentials in skills.susi.ai Choose the SKill which you want to edit and click on the pencil icon. The following screen allows editing the skill. One can change the Group, Language, Skill Name, Image and the content as well. After making the changes the commit message can be added to Save the changes. To achieve the above steps we require the following API Endpoints of the SUSI Server. http://api.susi.ai/cms/getSkillMetadata.json - This gives us the meta data which populates the various Skill Content, Image, Author etc. http://api.susi.ai/cms/getAllLanguages.json - This gives us all the languages of a Skill Group. http://api.susi.ai/cms/getGroups.json - This gives us all the list of Skill Groups whether Knowledge, Entertainment, Smalltalk etc. Now since we have all the APIs in place we make the following AJAX calls to update the Skill Process. Since we are detecting changes in all the fields (Group Value, Skill Name, Language Value, Image Value, Commit Message, Content changes and the format of the content), the AJAX call can only be sent when there is a change in the PR and there is no null or undefined value in them. For that, we make various form validations. They are as follows. We first detect whether the User is in a logged in state. if (!cookies.get('loggedIn')) { notification.open({ message: 'Not logged In', description: 'Please login and then try to create/edit a skill', icon: <Icon type="close-circle" style={{ color: '#f44336' }} />, }); } We check whether the image uploaded matches the format of the Skill image to be stored which is ::image images/imageName.png if (!new RegExp(/images\/\w+\.\w+/g).test(this.state.imageUrl)) { notification.open({ message: 'Error Processing your Request', description: 'image must be in format of images/imageName.jpg', icon: <Icon type="close-circle" style={{ color: '#f44336' }} />, }); } We check if the commit message is not null and notify the user if he forgot to add a message. if (this.state.commitMessage === null) { notification.open({ message: 'Please make some changes to save the Skill', icon: <Icon type="close-circle" style={{ color: '#f44336' }} />, }); } We also check whether the old values of the skill are completely similar to the new ones, in this case, we do not send the request. if (toldValues===newValues { notification.open({ message: 'Please make some changes to save the Skill', icon: <Icon type="close-circle" style={{ color: '#f44336' }} />, }); } To check out the complete code, go to this link. Next, if the above validations are successful, we send a POST request to the server and show the notification to the user accordingly, whether the changes to the Skill Data have been updated or not. Here’s the AJAX call snippet. // create a form object let form = new FormData();        /* Append the following fields from…

Continue ReadingModifying SUSI Skills using SUSI Skill CMS

Reset SUSI.AI User Password & Parameter extraction from Link

In this blog I will discuss how does Accounts handle the incoming request to reset the password. If a user forgets his/her password, They can use forgot password button on http://accounts.susi.ai and It’s implementation is quite straightforward. As soon as a user enter his/her e-mail id and hits RESET button, an ajax call is made which first checks if the user email id is registered with SUSI.AI or not. If not, a failure message is thrown to user. But if the user is found to be registered in the database, An email is sent to him/her which contains a 30 characters long token. On the server token is hashed against the user’s email id and a validity of 7 days is set to it. Let us have a look at the Reset Password link one receives. http://accounts.susi.ai/?token={30 characters long token} On clicking this link, what it does is that user is redirected to http://accounts.susi.ai with token as a request parameter. At the client side, A search is made which evaluates whether the URL has a token parameter or not. This was the overview. Since, http://accounts.susi.ai is based on ReactJS framework, it is not easy alike the native php functionality, but much more logical and systematic. Let us now take a closer look at how this parameter is searched for, token extracted and validated. As you can see http://accounts.susi.ai and http://accounts.susi.ai/?token={token}, Both redirect the user to the same URL. So the first task that needs to be accomplished is check if a parameter is passed in the URL or not. First import addUrlProps and UrlQueryParamTypes from react-url-query package and PropTypes from prop-types package. These will be required in further steps. Have a look at the code and then we will understand it’s working. const urlPropsQueryConfig = { token: { type: UrlQueryParamTypes.string }, }; class Login extends Component { static propTypes = { // URL props are automatically decoded and passed in based on the config token: PropTypes.string, // change handlers are automatically generated when given a config. // By default they update that single query parameter and maintain existing // values in the other parameters. onChangeToken: PropTypes.func, } static defaultProps = { token: "null", } Above in the first step, we have defined by what parameter should the Reset Password triggered. It means, if and only if the incoming parameter in the URL is token, we move to next step, otherwise normal http://accounts.susi.ai page should be rendered. Also we have defined the data type of the token parameter to be UrlQueryParamTypes.string. PropTypes are attributes in ReactJS similar to tags in HTML. So again, we have defined the data type. onChangeToken is a custom attribute which is fired whenever token parameter is modified. To declare default values, we have used defaultProps function. If token is not passed in the URL, by default it makes it null. This is still not the last step. Till now we have not yet checked if token is there or not. This is done in the componentDidMount…

Continue ReadingReset SUSI.AI User Password & Parameter extraction from Link

Implementing Login Functionality in SUSI Web Chat

SUSI Web Chat is fully equipped with all the accounting features which are being provided by the SUSI.AI API. This blog discloses all the API features one needs to know to embed the Login functionality in SUSI Web Chat. To embed the Login feature, first we create a form using material-ui.com components with the followng fields Email Password Note: We can also chose a Custom Server while logging in, here I have used the Standard Server ie. http://api.susi.ai to make the user Login The form can be made with the help of the following fields TextField for Email, props to be passed Name - email Value - this.state.email which gets the value of the current email floatingLabelText is Email, errorText is the message which we want to show when the email does not match the regex or its empty. Code Snippet - <TextField name="email" value={this.state.email} onChange={this.handleChange} errorText={this.emailErrorMessage} floatingLabelText="Email" /> PasswordField for Password Name - password Value - this.state.password which gets the value of the current email floatingLabelText is Password, errorText is the message which we want to show when the password is not filled. Code Snippet- <PasswordField name='password' value={this.state.password} onChange={this.handleChange} errorText={this.passwordErrorMessage} floatingLabelText='Password' /> The next elements are RadioButton groups taken from material-ui.com. This ensures the user signs into a standard server or even to a custom server. This is not compulsory as of now. And lastly we need a submit button, which is disabled until all the fields are filled. Code Snippet - <RaisedButton label="Login" type="submit" labelColor="#fff" disable={!this.state.validForm} /> For the full form, check out this file at Login.react.js A Sample UI could be as shown in the image Next after creating the Login Screen, we make the onSubmit prop which is to be hooked up with another function called handleSubmit. An example code snippet from Login.react.js- handleSubmit = (e) => { e.preventDefault(); // Get the trimmed values from the fields var email = this.state.email.trim(); var password = this.state.password.trim(); // Set the default server to login let BASE_URL = defaults.Server; // handle all the details of the chosen server let serverUrl = this.state.serverUrl; if(serverUrl.slice(-1) === '/'){ serverUrl = serverUrl.slice(0,-1); } if(serverUrl !== ''){ BASE_URL = serverUrl; } // if email and password is filled return true if (!email || !password) { return this.state.isFilled; } // Check the regex of email let validEmail = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(email); // Pass the parameters to the loginEndPoint let loginEndPoint = BASE_URL+'/aaa/login.json?type=access-token&login=' + this.state.email + '&password=' + this.state.password; // If email and password is filled and valid call AJAX if (email && validEmail) { // AJAX Calls } } Then we make the Ajax Calls and store the created token from hitting the URL at http://api.susi.ai/aaa/login.json?type=access-token&login=EMAIL&password=PASSWORD. We store the cookie in browser and generate a session for the user using a package ‘universal-cookies’. $.ajax({ url: loginEndPoint, dataType: 'jsonp', jsonpCallback: 'p', jsonp: 'callback', crossDomain: true, success: function (response) { cookies.set('serverUrl', BASE_URL, { path: '/' }); let accessToken = response.access_token; let state = this.state;// Adding the current State let time = response.valid_seconds; // Get the valid time of the…

Continue ReadingImplementing Login Functionality in SUSI Web Chat