Testing Endpoints on Local Server

All servlets in SUSI.AI have a BaseUserRole defined. It represents the access level you need to access the endpoint corresponding to that servlet. The lowermost BaseUserRole a SUSI.AI servlet can have is ANONYMOUS, which means that anyone can access the endpoint corresponding to these endpoints. But if the BaseUserRole is higher than that, then you need an access token to access the endpoint. This blog post explains how you can get access token to access the endpoints on a local server.

What are endpoints in an API?

An endpoint in API is one end of a communication channel. When an API interacts with another system, the touchpoints of this communication are considered endpoints. For APIs, an endpoint can include a URL of a server or service. Each endpoint is the location from which APIs can access the resources they need to carry out their function.

APIs work using ‘requests’ and ‘responses.’ When an API requests information from a web application or web server, it will receive a response. The place that APIs send requests and where the resource lives, is called an endpoint.

For example, the endpoint for https://api.susi.ai/cms/getSkillRating.json?queryParameters would be /cms/getSkillRating.json.

Servlets and Endpoints in SUSI.AI

All servlets in our SUSI project define an endpoint and also define a BaseUserRole, that is, the amount of privileges required to access the information on those endpoints. If the BaseUserRole defined is ANONYMOUS, then anyone can access the endpoint directly. But if the BaseUserRole is anything higher than that, then we would need an access token to access that.

How to get Access Token?

If you’re trying to access the endpoints with BaseUserRole higher than ANONYMOUS on the actual hosted server, then you can simply login to https://chat.susi.ai and get the access token from the Network tab of the Developers Tool. We can then use that token and pass that as a query parameter along with the other parameters of that particular endpoint. For example,

http://localhost:4000/aaa/listUserSettings.json?access_token=6O7cqoMbzlClxPwg1is31Tz5pjVwo3

 

But, the problem arises when you are trying to access such endpoints on local server. The local User data is completely different from the server User data. Hence, we need to generate an access token in localhost itself.

To generate access token for local server, we need to follow these steps :

  1. First, we need to hit the /aaa/signup.json endpoint with a new account credentials which we want to register for the localhost session. This is done as shown in below example:

http://localhost:4000/aaa/signup.json?signup=anyEmail&password=anyPassword

 

  1. Then, we need to hit the /aaa/login.json endpoint with the same credentials you registered in the previous step. This is done as shown in below example:

http://localhost:4000/aaa/login.json?login=yourEmail&type=access-token&password=yourPassword

 

If you’ve entered the registered credentials correctly, then the output of the /aaa/login.json endpoint would be a JSON as shown below:

{
  "accepted": true,
  "valid_seconds": 604800,
  "access_token": "7JPi7zNwemg1YYnr4d9JIdZMaIWizV",
  "message": "You are logged in as anyemail",
  "session": {"identity": {
    "type": "host",
    "name": "127.0.0.1_4e75edbb",
    "anonymous": true
  }}
}

 

As it can be seen from the above JSON response, we get the access token which we needed. Hence, copy this access token and store it somewhere because you can now use this access token to access the endpoints with BaseUserRole as User for this localhost session.

Note that you’ll have to follow all the above steps again if you start a fresh localhost session.

Resources

Continue ReadingTesting Endpoints on Local Server

Implementing Five Star Rating UI in SUSI iOS

Five-star rating system introduced in SUSI to rate skills. SUSI enable the user to rate skills between 1 to 5 star. The five-star rating system is the best way to get feedback from the user. It also helps the developer for further development. Ratings help to better understand individual preferences and present a more personalized user experience. The user feedback helps products understand whether or not the content is valuable and improve offerings over time. This can benefit products with and without sophisticated personalization.

Let’s see how the five-star rating system is implemented in SUSI iOS.

Average ratings displayed near the Try It button – It shows the average rating of a particular skill.

Enable user to submit the rating of any skill between 1-star to 5-star.

The only logged-in user can submit the ratings for skills.

Rating chart that display number of rating for each star (1 to 5), the right labels of chart bars shows the number of users rated for a particular star with the percentage.

Average and total ratings for particular skills is also displayed near the bar chart.

Thumbs-up and thumbs-down ratings removed from the skill detail screen and replaced with 5-star ratings.

Implementation of Rating Chart

For the rating chart, we are using TEAChart class, which enable us to present rating data on bar charts.

Setting colors for bar chart:

We are using Google’s Material Design color for rating bars colors.

let barChartColors = [
UIColor.fiveStarRating(),
UIColor.fourStarRating(),
UIColor.threeStarRating(),
UIColor.twoStarRating(),
UIColor.oneStarRating()
]

Assigning colors to bars:

barChartView.barColors = barChartColors

Assign Data to the bars:

// Sample data
barChartView.data = [5, 1, 1, 1, 2]

Set background color and bar spacing:

barChartView.barSpacing = 3
barChartView.backgroundColor = UIColor.barBackgroundColor()

Final Output –

Resources –

  1. Material Design: https://material.io/design/
  2. SUSI iOS Link: https://github.com/fossasia/susi_iOS
Continue ReadingImplementing Five Star Rating UI in SUSI iOS

Implementing Map View in Devices Tab in Settings

The Table View implemented in the Devices tab in settings on SUSI.AI Web Client has a column “geolocation” which displays the latitudinal and longitudinal coordinates of the device. These coordinates needed to be displayed on a map. Hence, we needed a Map View apart from the Table View, dedicated to displaying the devices pinpointed on a map. This blog post explains how this feature has been implemented on the SUSI.AI Web Client.

Modifying the fetched data of devices to suitable format

We already have the fetched data of devices which is being used for the Table View. We need to extract the geolocation data and store it in a different suitable format to be able to use it for the Map View. The required format is as follows:

[
   {
      "location":{
         "lat": latitude1,
         "lng": longitude2
      }
   },
   {
      "location":{
         "lat": latitude1,
         "lng": longitude2
      }
   }
]

 

To modify the fetched data of devices to this format, we modify the apiCall() function to facilitate extraction of the geolocation info of each device and store them in an object, namely ‘mapObj’. Also, we needed variables to store the latitude and longitude to use as the center for the map. ‘centerLat’ and ‘centerLng’ variables store the average of all the latitudes and longitudes respectively. The following code was added to the apiCall() function to facilitate all the above requirements:

let mapObj = [];
let locationData = {
  lat: parseFloat(response.devices[i].geolocation.latitude),
  lng: parseFloat(response.devices[i].geolocation.longitude),
};
centerLat += parseFloat(response.devices[i].geolocation.latitude);
centerLng += parseFloat(response.devices[i].geolocation.longitude);
let location = {
  location: locationData,
};
mapObj.push(location);
centerLat = centerLat / mapObj.length;
centerLng = centerLng / mapObj.length;
if (mapObj.length) {
  this.setState({
    mapObj: mapObj,
    centerLat: centerLat,
    centerLng: centerLng,
  });
}

 

The following code was added in the return function of Settings.react.js file to use the Map component. All the modified data is passed as props to this component.

<MapContainer
  google={this.props.google}
  mapData={this.state.mapObj}
  centerLat={this.state.centerLat}
  centerLng={this.state.centerLng}
  devicenames={this.state.devicenames}
  rooms={this.state.rooms}
  macids={this.state.macids}
/>

 

The implementation of the MapContainer component is as follows:

 componentDidUpdate() {
    this.loadMap();
  }

  loadMap() {
    if (this.props && this.props.google) {
      const {google} = this.props;
      const maps = google.maps;
      const mapRef = this.refs.map;
      const node = ReactDOM.findDOMNode(mapRef);

      const mapConfig = Object.assign({}, 
        
          center: { lat: this.props.centerLat, lng: this.props.centerLng },
          zoom: 2,
        }
      )
      this.map = new maps.Map(node, mapConfig);
    }
  }

 

Let us go over the code of MapContainer component step by step.

  1. Firstly, the componentDidUpdate() function calls the loadMap function to load the google map.

  componentDidUpdate() {
    this.loadMap();
  }

 

  1. In the loadMap() function, we first check whether props have been passed to the MapContainer component. This is done by enclosing all contents of loadMap function inside an if statement as follows:

  if (this.props && this.props.google) {
    // All code of loadMap() function
  } 

 

  1. Then we set the prop value to google, and maps to google maps props. This is done as follows:

  const {google} = this.props;
  const maps = google.maps;

 

  1. Then we look for HTML div ref ‘map’ in the React DOM and name it ‘node’. This is done as follows:

  const mapRef = this.refs.map;
  const node = ReactDOM.findDOMNode(mapRef);

 

  1. Then we set the center and the default zoom level of the map using the props we provided to the MapContainer component.

  {
    center: { lat: this.props.centerLat, lng: this.props.centerLng },
    zoom: 2,
  }

 

  1. Then we create a new Google map on the specified node (ref=’map’) with the specified configuration set above. This is done as follows:

this.map = new maps.Map(node, mapConfig);

 

  1. In the render function of the MapContainer component, we return a div with a ref ‘map’ as follows:

 render() {
    return (
      <div ref="map" style={style}>
        loading map...
      </div>
    );
  }

 

This is how the Map View has been implemented in the Devices tab in Settings on SUSI.AI Web Client.

Resources

Continue ReadingImplementing Map View in Devices Tab in Settings

Implementing Table View in Devices Tab in Settings

We can connect to the SUSI.AI Smart Speaker using our mobile apps (Android or iOS). But there needs to be something implemented which can tell you what all devices are linked to your account. This is in consistency with the way how Google Home devices and Amazon Alexa devices have this feature implemented in their respective apps, which allow you to see the list of devices connected to your account. This blog post explains how this feature has been implemented on the SUSI.AI Web Client.

Fetching data of the connected devices from the server

The information of the devices connected to an account is stored in the Accounting object of that user. This is a part of a sample Accounting object of a user who has 2 devices linked to his/her account. This is the data that we wish to fetch. This data is accessible at the /aaa/listUserSettings.json endpoint.

{
  "devices": {
    "37-AE-5F-7B-CA-3F": {
      "name": "Device 1",
      "room": "Room 1",
      "geolocation": {
        "latitude": "50.34567",
        "longitude": "60.34567"
      }
    },
    "9D-39-02-01-EB-95": {
      "name": "Device 2",
      "room": "Room 2",
      "geolocation": {
        "latitude": "52.34567",
        "longitude": "62.34567"
      }
    }
  }
} 

 

In the Settings.react.js file, we make an AJAX call immediately after the component is mounted on the DOM. This AJAX call is made to the /aaa/listUserSettings.json endpoint. The received response of the AJAX call is then used and traversed to store the information of each connected device in a format that would be more suitable to use as a prop for the table.

apiCall = () => {
    $.ajax({
      url: BASE_URL + '/aaa/listUserSettings.json?' + 'access_token=' +
  cookies.get('loggedIn');,
      type: 'GET',
      dataType: 'jsonp',
      crossDomain: true,
      timeout: 3000,
      async: false,
      success: function(response) {
        let obj = [];
         // Extract information from the response and store them in obj object
        obj.push(myObj);
        this.setState({
          dataFetched: true,
          obj: obj,
        });
      }
      }.bind(this),
  };

 

This is how the extraction of keys takes place inside the apiCall() function. We first extract the keys of the ‘devices’ JSONObject inside the response. The keys of this JSONObject are the Mac Addresses of the individual devices. Then we traverse inside the JSONObject corresponding to each Mac Address and store the name of the device, room and also the geolocation information of the device in separate variables, and then finally push all this information inside an object, namely ‘myObj’. This JSONObject is then pushed to a JSONArray, namely ‘obj’. Then a setState() function is called which sets the value of ‘obj’ to the updated ‘obj’ variable.

let keys = Object.keys(response.devices);
keys.forEach(i => {
    let myObj = {
        macid: i,
        devicename: response.devices[i].name,
        room: response.devices[i].room,
        latitude: response.devices[i].geolocation.latitude,
        longitude: response.devices[i].geolocation.longitude,
    };
}

 

This way we fetch the information of devices and store them in a variable named ‘obj’. This variable will now serve as the data for the table which we want to create.

Creating table from this data

The data is then passed on to the Table component as a prop in the Settings.react.js file.

<TableComplex
    // Other props
    tableData={this.state.obj}
/>

 

The other props passed to the Table component are the functions for handling the editing and deleting of rows, and also for handling the changes in the textfield when the edit mode is on. These props are as follows:

<TableComplex
    startEditing={this.startEditing}
    stopEditing={this.stopEditing}
    handleChange={this.handleChange}
    handleRemove={this.handleRemove}
/> 

 

The 4 important functions which are passed as props to the Table component are:

  1. startEditing() function:

When the edit icon is clicked for any row, then the edit mode should be enabled for that row. This also changes the edit icon to a check icon. The columns corresponding to the room and the name of the device should turn into text fields to enable the user to edit them. The implementation of this function is as follows:

startEditing = i => {
    this.setState({ editIdx: i });
}; 

 

‘editIdx’ is a variable which contains the row index for which the edit mode is currently on. This information is passed to the Table component which then handles the editing of the row.

  1. stopEditing() function:

When the check icon is clicked for any row, then the edit mode should be disabled for that row and the updated data should be stored on the server. The columns corresponding to the room and the name of the device should turn back into label texts. The implementation of this function is as follows:

stopEditing = i => {
  let data = this.state.obj;
  this.setState({ editIdx: -1 });
  // AJAX call to server to store the updated data
  $.ajax({
    url:
      BASE_URL + '/aaa/addNewDevice.json?macid=' + macid + '&name=' + devicename + '&room=' + room + '&latitude=' + latitude + '&longitude=' + longitude + '&access_token=' + cookies.get('loggedIn'),
    dataType: 'jsonp',
    crossDomain: true,
    timeout: 3000,
    async: false,
    success: function(response) {
      console.log(response);
    },
  });
};

 

The value of ‘editIdx’ is also set to -1 as the editing mode is now off for all rows.

  1. handleChange() function:

When the edit mode is on for any row, if we change the value of any text field, then that updated value is stored so that we can edit it without the value getting reset to the initial value of the field.

handleChange = (e, name, i) => {
  const value = e.target.value;
  let data = this.state.obj;
  this.setState({
    obj: data.map((row, j) => (j === i ? { ...row, [name]: value } : row)),
  });
};

 

  1. handleRemove() function:

When the delete icon is clicked for any row, then that row should get deleted. This change should be reflected to us on the site, as well as on the server. Hence, The implementation of this function is as follows:

handleRemove = i => {
  let data = this.state.obj;
  let macid = data[i].macid;

  this.setState({
    obj: data.filter((row, j) => j !== i),
  });
  // AJAX call to server to delete the info of device with specified Mac Address
  $.ajax({
    url:
      BASE_URL + '/aaa/removeUserDevices.json?macid=' + macid + '&access_token=' + cookies.get('loggedIn'),
    dataType: 'jsonp',
    crossDomain: true,
    timeout: 3000,
    async: false,
    success: function(response) {
      console.log(response);
    },
  });
};  

 

This is how the Table View has been implemented in the Devices tab in Settings on SUSI.AI Web Client.

Resources

 

Continue ReadingImplementing Table View in Devices Tab in Settings

Add Feature to Post Feedback for a Skill in SUSI.AI Server

In this blog post, we are going to discuss on how the feature to post user feedback was implemented on the SUSI. AI server side. The API endpoint by which a user can post feedback for a Skill is https://api.susi.ai/cms/feedbackSkill.json.

It accepts five url parameters –

  • model – It tells the model to which the skill belongs. The default value is set to General.
  • group – It tells the group(category) to which the skill belongs. The default value is set to Knowledge.
  • language – It tells the language to which the skill belongs. The default value is set to en.
  • skill – It is the name of the skill to be given a feedback. It is a compulsory param.
  • feedback – It is the parameter that contains the string type feedback of the skill, that the user wants to update.

The minimalUserRole is set to USER for this API, as only logged-in users can use this API.

Going through the API development

  • The parameters are first extracted via the call object that is passed to the main function. The  parameters are then stored in variables. If any parameter is absent, then it is set to the default value.
  • There is a check if the skill exists. It is done by checking, if there exists a file corresponding to that skill. If no, then an exception is thrown.
  • This code snippet discusses the above two points –

@Override
public ServiceResponse serviceImpl(Query call, HttpServletResponse response, Authorization authorization, final JsonObjectWithDefault permissions) throws APIException {

        String model_name = call.get("model", "general");
        File model = new File(DAO.model_watch_dir, model_name);
        String group_name = call.get("group", "Knowledge");
        File group = new File(model, group_name);
        String language_name = call.get("language", "en");
        File language = new File(group, language_name);
        String skill_name = call.get("skill", null);
        File skill = SusiSkill.getSkillFileInLanguage(language, skill_name, false);
        String skill_feedback = call.get("feedback", null);

        JSONObject result = new JSONObject();
        if (!skill.exists()) {
            throw new APIException(422, "Skill does not exist.");
        }
.
.
.

 

  • Then the  feedbackSkill.json that was initialized in the DAO object, is then opened. It is a JSONTray, which is then parsed.
  • It is checked if the skill is previously rated or not. If yes, has the user rated it before or not. In the case, when the user has given a feedback for it already, we just need to make changes in the feedbackSkill.json file as the feedback_count key present in the skillRatings.json doesn’t change. In case, the user hasn’t given the feedback earlier, the changes also needs to be reflected on the skillRatings.json file. For this use,  the utility function addToSkillRatingJSON is called. The feedback_count of that skill is increased by one, as it is a unique feedback and changes need to be reflected on the file.
  • The response object is then sent with three key values mainly, apart from the session object. They are
    • accepted –  true – It tells that the skill feedback has been updated.
    • message – “Skill feedback updated”
    • feedback –  <feedback> – It is the string that the client has sent as feedback.

Here are code snippets and data objects that will help in understanding the API well –

  • addToSkillRating function –
    public void addToSkillRatingJSON(Query call) {
        String model_name = call.get("model", "general");
        String group_name = call.get("group", "Knowledge");
        String language_name = call.get("language", "en");
        String skill_name = call.get("skill", null);

        JsonTray skillRating = DAO.skillRating;
        JSONObject modelName = new JSONObject();
        JSONObject groupName = new JSONObject();
        JSONObject languageName = new JSONObject();
        JSONObject skillName = new JSONObject();
        if (skillRating.has(model_name)) {
            modelName = skillRating.getJSONObject(model_name);
            if (modelName.has(group_name)) {
                groupName = modelName.getJSONObject(group_name);
                if (groupName.has(language_name)) {
                    languageName = groupName.getJSONObject(language_name);
                    if (languageName.has(skill_name)) {

                        skillName = languageName.getJSONObject(skill_name);
                    }
                }
            }
        }

        if (skillName.has("feedback_count")) {

            int skillFeedback = skillName.getInt("feedback_count");
            skillName.put("feedback_count", skillFeedback + 1 );
        } else {

            skillName.put("feedback_count", 1);
        }
                        
        if (!skillName.has("stars")) {

            JSONObject skillStars = new JSONObject();
            skillStars.put("one_star", "0");
            skillStars.put("two_star", "0");
            skillStars.put("three_star", "0");
            skillStars.put("four_star", "0");
            skillStars.put("five_star", "0");
            skillStars.put("avg_star", "0");
            skillStars.put("total_star", "0");
            skillName.put("stars", skillStars);
        }

        languageName.put(skill_name, skillName);
        groupName.put(language_name, languageName);
        modelName.put(group_name, groupName);
        skillRating.put(model_name, modelName, true);
        return;
    }
}

 

  • Sample  of feedbackSkill.json file –

{
  "general": {
    "Knowledge": {
      "en": {
        "aboutsusi": [
          {
            "feedback": "The skill is awesome!",
            "email": "email1@domain.com",
            "timestamp": "2018-06-06 02:20:20.245"
          },
          {
            "feedback": "Great!",
            "email": "email2@domain.com",
            "timestamp": "2018-06-06 02:20:20.245"
          }
        ],
        "quotes": [
          {
            "feedback": "Wow!",
            "email": "email1@domain.com",
            "timestamp": "2018-06-06 02:19:53.86"
          }
        ]
      }
    }
  }
}
  • Sample  of skillRatings.json file –

{
  "general": {
    "Knowledge": {
      "en": {
        "aboutsusi": {
          "negative": "0",
          "positive": "0",
          "stars": {
            "one_star": 0,
            "four_star": 2,
            "five_star": 0,
            "total_star": 5,
            "three_star": 3,
            "avg_star": 3.4,
            "two_star": 0
          },
          "feedback_count": 2
        },
        "quotes": {
          "negative": "0",
          "positive": "0",
          "stars": {
            "one_star": 0,
            "four_star": 0,
            "five_star": 0,
            "total_star": 0,
            "three_star": 0,
            "avg_star": 0,
            "two_star": 0
          },
          "feedback_count": 1
        }
      }
    }
  }
}

 

I hope the development of creating the aforesaid API is clear and proved to be helpful for your understanding.

Continue ReadingAdd Feature to Post Feedback for a Skill in SUSI.AI Server

Making Tree View in SUSI botbuilder

SUSI botbuilder enables you to create your own bot which you can integrate in your own website. While creating the skill for the bot, you often need to visualize the dialog flow. The Tree View helps you to visualize the conversation flow. The queries from the user and the related responses from the bot can easily be understood by seeing the tree.

Installing package

We will be using react-orgchart to make the tree. React-orgchart offers minimum basic implementation of the tree and we can easily customise the nodes. To install the package, type the following command in your terminal:

npm i react-orgchart --save

 

Using the Orgchart

In the TreeView.js file, we import the orgchart and render it. The tree data and node component is passed to the OrgChart component. Tree data stores the format and conversations in the tree and the node component represents the rendering of the individual node. We can customise the node according to our needs.

const MyNodeComponent = ({node}) => {
      return (
‘initechNode’>{node.type===‘bot’ && ‘#4285f5’ style={styles.icon}/>}{node.type===‘user’ && } { node.name }

); }; <OrgChart tree={this.props.treeData} NodeComponent={MyNodeComponent} />

 

Generating the Tree Data

The data for the tree is generated by the code view. As user adds or edit skills in the code view, the skill language is interpreted and converted to a tree data structure. The generateTreeData() function in Build.js file does this job.

//generation of skills from the code view
for (let i = 0; i < lines.length; i++) {
  let bot_response = null;
  let line = lines[i];
  if (line && !line.startsWith('::') && !line.startsWith('!') && !line.startsWith('#')) {
    let user_query = line;
  while (true) {
    i++;
    if (i >= lines.length) {
      break;
    }
    line = lines[i];
    if (line && !line.startsWith('::') && !line.startsWith('!') && !line.startsWith('#')) {
      bot_response = lines[i];
    break;
  }
}
let obj = {
  user_query,
  bot_response,
};
skills.push(obj);
}
}

 

Thus, we are separating the skills in the code view and separating the bot and user response. A sample Tree data looks like:

const treeData = {
  name: 'Welcome!',
  children: [
    {
      name: 'User query 1',
      type: 'user',
      children: [
        {
          name: 'Answer for the user query',
          type: 'bot',
        }
      ]
    },
    {
      name: 'User query 2',
      type: 'user',
      children: [
        {
          name: 'Answer for the user query',
          type: 'bot',
        }
      ]
    },
    {
      name: 'User query 3',
      type: 'user',
      children: [
        {
          name: 'Answer for the user query',
          type: 'bot',
        }
      ]
    }
  ]
}

 

Result:

Resources

Continue ReadingMaking Tree View in SUSI botbuilder

Add auto copy code feature in botbuilder

SUSI botbuilder lets you create your own skill bot and deploy on your website. After you have customised your bot, you will get a javascript code that you need to paste in your website’s source code. To make the process of copying the code easy, we have developed a feature for auto copying of the code to your clipboard. You just need to click on a button “copy” and the code will be copied to your clipboard.

Installing package

We use react-copy-to-clipboard package to enable auto copy feature. Install it in your project using the following command.

npm i react-copy-to-clipboard --save

 

Adding code inside render function

Inside the render() function in react file, paste the following code where you want the copy button to be displayed. Here we need to provide the text to be copied to the CopyToClipboard component via the text props. We pass the script code. We get the access token for the bot via the saved cookie. Inside the children of CopyToClipboard we need to pass the copy button which we want to show.

<CopyToClipboard
  text={
    " +cookies.get('uuid') +"' data-group='" +
    group + "' data-language='" + language + "' data-skill='" + skill + "' src='" + api + "/susi-chatbot.js'>"
  }
  onCopy={() => this.setState({ copied: true })}
>
  <span className="copy-button">copy</span>
</CopyToClipboard>

 

Thus, when the user clicks on the “copy” button, the code will be automatically copied to the user’s clipboard.

Showing snackbar message

After the code has been copied to the user’s clipboard, we can show a snackbar message to inform the user. First we pass a function onCopy to the CopyToClipboard component. This sets the state variable copied to true. Then we have a snackbar component which displays the message.

<Snackbar
   open={this.state.copied}
   message="Copied to clipboard!"
   autoHideDuration={2000}
   onRequestClose={() => {
      this.setState({ copied: false });
   }}
/>

 

Result:

 

Resources

 

Continue ReadingAdd auto copy code feature in botbuilder

Create custom theme for SUSI chatbot web plugin

SUSI web plugin bot provides the features of SUSI as a chat window which users can plugin to their websites. They just need to copy the generated javascript code into their website and the SUSI bot plugin will be enabled. The user can customize the theme of the chatbot. This blog explains how the feature of applying custom theme is implemented.

Accepting user’s input

The react component of our concern is BotBuilderPages/Design.js. We use material-ui-color-picker component to accept user’s choice of color.

<ColorPicker
  className='color-picker'
  style={{display:'inline-block',float:'left'}}
  name='color'
  defaultValue={ this.state[component.component] }
  onChange={(color)=>
   this.handleChangeColor(component.component,color) }
/>

 

Similarly, there is a text field which accepts the url of an image. The values are stored in the component’s state variables.

Storing settings in server

The design settings are stored in the text format:

This forms part of the skill’s text file. When the user clicks on “save and deploy” button, the complete skill gets send to the server through the following API:

let settings = {
      async: true,
      crossDomain: true,
      url:
        urls.API_URL +
        '/cms/' +
        (this.state.updateSkillNow ? 'modifySkill.json' : 'createSkill.json'),
      method: 'POST',
      processData: false,
      contentType: false,
      mimeType: 'multipart/form-data',
      data: form,
    };

    $.ajax(settings)
      .done(function(response) {
        // successfully saved in server
      });

 

In the server, the skill is saved in susi_private_skill_data directory. Also, the design and configuration settings are stored in chatbot.json file.

Applying the settings to the bot

Now, in the susi-chatbot.js file, the custom theme settings are applied to the bot. The function getTheme() fetches the theme settings from the server via an ajax request. Then the function applyTheme() is executed which applies the theme to the chatbot.

$(".susi-sheet-content-container").css("background-color",botbuilderBackgroundBody);
if(botbuilderBodyBackgroundImg){
$(".susi-sheet-content-container").css("background-image","url("+botbuilderBodyBackgroundImg+")");
}

 

Similarly, other theme variables are applied as well. Thus we have customised the theme of the SUSI chatbot plugin.

Result:

Resources

 

Continue ReadingCreate custom theme for SUSI chatbot web plugin

Adding feature to preview chatbot

You can access all features of SUSI.AI on chat.susi.ai. But this restricts the usage of SUSI.AI to this site only. To solve this issue, we can use a Chatbot which can be embedded on any website and then can be used by the people who visit that website, hence generalising the usage of SUSI.AI.

How to embed SUSI.AI Chatbot on any website?

To embed SUSI.AI Chatbot on any website, go to skills.susi.ai and login and then check out the Botbuilder section. There you can find a deploy button which will give you a code which looks something like this :

<script type="text/javascript" id="susi-bot-script" data-userid="" data-group="" data-language="" data-skill="" src="https://skills.susi.ai/susi-chatbot.js" > </script>

 

As can be seen from the code above, it is simply a script tag with an id and a custom attribute data-token (which is the unique access token of the User for that session). The source file for this script is susi-chatbot.js. This is the file that is responsible for displaying the Chatbot on the screen.

But, let’s say that you configured your Chatbot in the entire Botbuilder process. So, we need to have a Preview button which could show us a preview of the Chatbot with the chosen configurations, otherwise we would have to test it out by embedding it on our website, which would be tiresome.

How does the Preview Button work?

The code for Botbuilder page is in the Botbuilder.js file.

The code for the Preview button is as follows :

  {!this.state.chatbotOpen?(<RaisedButton
        label='Preview'
        style={{ width: '148px' }}
        onClick={this.injectJS}
    />):(<RaisedButton
        label='Close Preview'
        style={{ width: '148px' }}
        onClick={this.handleChatbotClose}
  />)}

 

It can be seen that on clicking the ‘Preview’ button, injectJS() function is called, and on clicking the ‘Close Preview’ button, handleChatbotClose() function is called.

     injectJS = () => {
        const myscript = document.createElement('script');
        myscript.type = 'text/javascript';
        myscript.id = 'susi-bot-script';
        myscript.setAttribute('data-token',cookies.get('loggedIn'));
        myscript.src = '/susi-chatbot.js';
        myscript.async = true;
        document.body.appendChild(myscript);
        // some code
    };

     handleChatbotClose = () =>{
      // some code
      $('#susi-launcher-container').remove();
      $('#susi-frame-container').remove();
      $('#susi-launcher-close').remove();
      $('#susi-bot-script').remove();
    }

 

The above 2 functions are the key functions in understanding how the Preview button is previewing the Chatbot on the screen.

Let us see the functioning of injectJS() function first. Firstly, the createElement() method creates an element node of type ‘script’. We store this element node in a variable myscript. We then add attribute values to the script tag by using myscript.attribute_name = attribute_value. We can add ‘type’, ‘id’, ‘src’ and ‘async’ attributes in this way because they are predefined attributes.

However, we need to also add a custom attribute to store the access_token of the User, because we would need to pass down this information to detect the owner of the Chatbot. To add a custom attribute, we use the following code :

myscript.setAttribute('data-token',cookies.get('loggedIn'));

 

This add a custom attribute ‘data-token’ and sets its value to the access token of the session, which would later be used to identify the User.

This script is then appended to the body of the document. As soon as the script is appended to the page, 3 new <div> tags are created and added to the page’s HTML. These 3 <div> tags contain the code for the UI and functionality of the Chatbot itself.

Next, let us look at the functioning of handleChatbotClose() function. This function’s basic objective is to close the Chatbot and remove the 3 components that were rendered as a result of the injectJS() function, and also remove the script itself. As can be seen from the code of the function, we are removing the 4 components which can be seen in the above image of the developer console.

So, this is how the Preview functionality has been added to the Chatbot. Visit https://skills.susi.ai and login to check out the Botbuilder section to try it out.

Resources

Continue ReadingAdding feature to preview chatbot

How api.susi.ai responds to a query and send response

A direct way to access raw data for a query is https://api.susi.ai. It is an API of susi server which sends responses to a query by a user in form of a JSON (JavaScript Object Notation) object (more on JSON here). This JSON object is the raw form of any response to a query which is a bunch of key (attribute) value pair. This data is then send from the server to various APIs like chat.susi.ai, susi bots, android and ios apps of SUSI.AI.

Whenever a user in using an API, for example chat.susi.ai, the user send a query to the API. This query is then sent by the API as a request to the susi server. The server then process the request and sends the answer to the query in form of json data as a response to the request. This request is then used by the API to display the answer after applying stylings to the json data.

Continue ReadingHow api.susi.ai responds to a query and send response