Fetching responses from SUSI.AI Server for Botbuilder Build Views

In SUSI.AI, we use skill editor for creating and editing public/private skills. The editor we use is Ace editor. The skill is written in a code format documented here. This works fine for a developer but for a user with little experience in coding, this can be confusing. Hence, for providing more clarity as to what the skill does, I built conversation view and tree view along with code view for the skill editor.

Conversation view shows the skill in form of actual conversation between the user and the bot while tree views shows the same conversation in form of a tree. Earlier these views were implemented by converting the code view into an object containing user queries and SUSI responses.

While this works for simple skills, it obviously won’t work for a complex skill in which the responses are fetched from an API. Hence we needed live responses from SUSI server.

This is done similar to how preview works. We pass the whole skill in an instant parameter in the chat.json API along with the user query. This gives us the response from SUSI in form of a JSON.

API Call:

We send a GET request to the following URL:

https://api.susi.ai/susi/chat.json?q=userQuery&instant=wholeSkill

This contains two parameters:

  • q: The user query is passed in this parameter.
  • instant: The whole skill code (present in the code view) is passed in this parameter.

The response is a JSON providing response from SUSI.

Getting user queries:

We can not rely on user to provide the user queries in conversation view and tree view because the user has already provided it in the code view. Hence, we fetch the user queries from code view. This is simply done by dissecting the code and putting all the lines which don’t start with ::, !, #, {, } and “ in an array. Then we split the entries of this array wherever a vertical bar (|) is found. This provides us an array containing all the user queries. It’ll be clear from the following function:

fetchUserInputs = () => {
  let code = this.state.code;
  let userInputs = [];
  let userQueries = [];
  var lines = code.split('\n');
  for (let i = 0; i < lines.length; i++) {
    let line = lines[i];
    if (
      line &&
      !line.startsWith('::') &&
      !line.startsWith('!') &&
      !line.startsWith('#') &&
      !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('#')
        ) {
          break;
        }
      }
      userQueries.push(user_query);
    }
  }
  for (let i = 0; i < userQueries.length; i++) {
    let queries = userQueries[i];
    let queryArray = queries.trim().split('|');
    for (let j = 0; j < queryArray.length; j++) {
      userInputs.push(queryArray[j]);
    }
  }
  this.setState({ userInputs }, () => this.getResponses(0));
};

Getting response to a single query at a time:

Now, we have an array containing all the user queries but we can not simply run a loop through the array and then get responses for each query because the AJAX call that we’re making to fetch response is asynchronous. Hence, this will result in multiple AJAX calls in a very short period of time. This will cause a failure in fetching responses and conversation view won’t work. We definitely don’t want that.

To solve this problem, we get response for a single query at a time and make the next AJAX call only when the response for the current call is received. You can see in the code snippet provided in last section, after updating state of userInputs, we’re calling getResponses as a callback and passing 0. This 0 is the index of array which will be incremented on every successful AJAX call. The following code snippet will demonstrate this:

$.ajax({
  type: 'GET',
  url: url,
  contentType: 'application/json',
  dataType: 'json',
  success: function(data) {
  let answer;
  if (data.answers[0]) {
    // Putting response in an object along with user query
    if (responseNumber + 1 === userInputs.length) { // Stopping when responses are fetched for all user queries.
      this.setState({ loaded: true });
    }
    this.setState({ responseData }, () => // updating the response data
      this.getResponses(++responseNumber),  // Incrementing the index and calling getResnponses again as a callback when response data state is updated.
    );
  }.bind(this),
  error: function(err) {
    console.log(err);
  }.bind(this),
});

The code snippets I provided are used in conversation view. Same algorithm is used in tree view as well.

References:

Continue Reading

Implementing Tree View in PSLab Android App

When a task expands over sub tasks, it can be easily represented by a stem and leaf diagram. In the context of android it can be implemented using an expandable list view. But in a scenario where the subtasks has mini tasks appended to it, it is hard to implement it using the general two level expandable list views. PSLab android application supports many experiments to perform using the PSLab device. These experiments are divided into major sections and each experiments are listed under them.

The best way to implement this functionality in the android application is using a multi layer treeview implementation. In this context three layers are enough as follows;


This was implemented with the help from a library called AndroidTreeView. This blog will outline how to modify and implement it in PSLab android application.

Basic Idea

Tree view implementation simply follows the data structure “Tree” used in algorithms. Every tree has a root where it starts and from the root there will be branches which are connected using edges. Every edge will have a parent and child. To reach a child, one has to traverse through only one route.

Setting Up Dependencies

Implementing tree view begins with setting up dependencies in the gradle file in the project.

compile 'com.github.bmelnychuk:atv:1.2.+'

Creating UI for tree view

The speciality about this implementation is that it can be loaded into any kind of a layout such as a linearlayout, relativelayout, framelayout etc.

final TreeNode Root = TreeNode.root();
Root.addChildren(
       // Add child nodes here
);
// Set up the tree view
AndroidTreeView experimentsListTree = new AndroidTreeView(getActivity(), Root);
experimentsListTree.setDefaultAnimation(true);
[LinearLayout/RelativeLayout].addView(experimentsListTree.getView());

Creating a node holder

Trees are made of a collection of tree nodes. A holder for a tree node can be created using an object which extends the BaseNodeViewHolder class provided by the library. BaseNodeViewHolder requires a holder class which is generally static so that it can be accessed without creating an instance which nests textviews, imageviews and buttons.

Once the holder extends the BaseNodeViewHolder, it should override two methods as follows;

@Override
public View createNodeView(final TreeNode node, ClassContainingNodeData header) {

}

@Override
public void toggle(boolean active) {

}

createNodeView() which inflate the view and toggle() method which can be used to toggle clicks on the tree node in the UI.

The following code snippet shows how to create an object which extends the above mentioned class with the overridden methods.

public class ExperimentHeaderHolder extends TreeNode.BaseNodeViewHolder<ExperimentHeaderHolder.ExperimentHeader> {

    private ImageView arrow;

    public ExperimentHeaderHolder(Context context) {
            super(context);
    }

    @Override
    public View createNodeView(final TreeNode node, ExperimentHeader header) {

            final LayoutInflater inflater = LayoutInflater.from(context);
            final View view = inflater.inflate(R.layout.header_holder, null, false);

            TextView title = (TextView) view.findViewById(R.id.title);
            title.setText(header.title);

            arrow = (ImageView) view.findViewById(R.id.experiment_arrow);
        
            return view;
    }

    @Override
    public void toggle(boolean active) {
            arrow.setImageResource(active ? arrow_drop_up : arrow_drop_down);
    }

    public static class ExperimentHeader {

            public String title;

            public ExperimentHeader(String title) {
               this.title = title;
            }
    }
}

Creating a TreeNode

Once the holder is complete, we can move on to creating an actual tree node. TreeNode class requires an object which extends the BaseNodeViewHolder class as mentioned earlier. Also it requires a viewholder which it can use to inflate the view in the tree layout. The viewholder can be a different class. The importance of this different implementation can be explained as follows;

TreeNode treeNode = new TreeNode(new ExperimentHeaderHolder.ExperimentHeader(“Title”))
       .setViewHolder(new ExperimentHeaderHolder(context));

In the Saved Experiments section of PSLab android application, all the three levels shouldn’t implement the toggle behavior as a user clicks on the experiment (last level item), he doesn’t expect the icon to change like the ones in headers where an arrow points up and down when he clicks on it. In this case we can reuse a holder which has the title attribute while creating only a holder which does not override the toggle function to ignore icon toggling at the last level of the tree view. This explanation can be illustrated using a code snippet as follows;

new TreeNode(new ExperimentHeaderHolder.ExperimentHeader(“Title”))
       .setViewHolder(new IndividualExperimentHolder(context));

Creating parent nodes and finally the Root node

The final part of the implementation is to create parent nodes to group up similar experiments together. The TreeNode object supports a method call addChild() and addChildren(). addChild() method allows adding one tree node to the specific tree node and addChildren() method allows adding many tree nodes at the same time. Following code snippet illustrates how to add many tree nodes to a node and make it a parent node.

treeDiodeExperiments.addChildren(treeZener, treeDiode, treeDiodeClamp, treeDiodeClip, treeHalfRectifier, treeFullWave);

Setting a click listener

Click listener is a very important implementation. Each tree node can be attached with a click listener using the interface provided by the library as follows;

treeNode.setClickListener(new TreeNode.TreeNodeClickListener() {
   @Override
   public void onClick(TreeNode node, Object value) {

   }
});

The value object is the class attached to the holder and its attributes can be retireved by casting it to the specific class using casting methods;

String title = ((ExperimentHeaderHolder.ExperimentHeader) value).title;

Resources:

Continue Reading
Close Menu