Displaying Private Skills and Drafts on SUSI.AI

The ListPrivateSkillService and ListPrivateDraftSkillService endpoint was implemented on SUSI.AI Server for SUSI.AI Admins to view the bots and drafts created by users respectively. This allows admins to monitor the bots and drafts created by users, and delete the ones which violate the guidelines. Also admins can see the sites where the bot is being used.

The endpoint of both ListPrivateSkillService and ListPrivateDraftSkillService is of GET type. Both of them have a compulsory access_token parameter but ListPrivateSkillService has an extra optional search parameter.

  • access_token(necessary): It is the access_token of the logged in user. It means this endpoint cannot be accessed in anonymous mode. 
  • search: It fetches a bot with the searched name.

The minimum user role is set to OPERATOR.

API Development

ListPrivateSkillService

For creating a list, we need to access each property of botDetailsObject, in the following manner:

Key → Group  → Language → Bot Name  → BotList

The below code iterates over the uuid of all the users having a bot, then over different groupNames,languageNames, and finally over the botNames. If search parameter is passed then it searches for the bot_name in the language object. Each botDetails object consists of bot name, language, group and key i.e uuid of the user which is then added to the botList array.

       JsonTray chatbot = DAO.chatbot;
       JSONObject botDetailsObject = chatbot.toJSON();
       JSONObject keysObject = new JSONObject();
       JSONObject groupObject = new JSONObject();
       JSONObject languageObject = new JSONObject();
       List botList = new ArrayList();
       JSONObject result = new JSONObject();

       Iterator Key = botDetailsObject.keys();
       List keysList = new ArrayList();

       while (Key.hasNext()) {
           String key = (String) Key.next();
           keysList.add(key);
       }

       for (String key_name : keysList) {
           keysObject = botDetailsObject.getJSONObject(key_name);
           Iterator groupNames = keysObject.keys();
           List groupnameKeysList = new ArrayList();

           while (groupNames.hasNext()) {
               String key = (String) groupNames.next();
               groupnameKeysList.add(key);
           }

           for (String group_name : groupnameKeysList) {
               groupObject = keysObject.getJSONObject(group_name);
               Iterator languageNames = groupObject.keys();
               List languagenamesKeysList = new ArrayList();

               while (languageNames.hasNext()) {
                   String key = (String) languageNames.next();
                   languagenamesKeysList.add(key);
               }

               for (String language_name : languagenamesKeysList) {
                   languageObject = groupObject.getJSONObject(language_name);

If search parameter is passed, then search for a bot with the given name and add the bot to the botList if it exists. It will return all bots which have bot name as the searched name.

                   if (call.get("search", null) != null) {
                       String bot_name = call.get("search", null);
                       if(languageObject.has(bot_name)){
                           JSONObject botDetails = languageObject.getJSONObject(bot_name);
                           botDetails.put("name", bot_name);
                           botDetails.put("language", language_name);
                           botDetails.put("group", group_name);
                           botDetails.put("key", key_name);
                           botList.add(botDetails);
                       }
                   }

If search parameter is not passed, then it will return all the bots created by the users.

                    else {
                       Iterator botNames = languageObject.keys();
                       List botnamesKeysList = new ArrayList();

                       while (botNames.hasNext()) {
                           String key = (String) botNames.next();
                           botnamesKeysList.add(key);
                       }

                       for (String bot_name : botnamesKeysList) {
                           JSONObject botDetails = languageObject.getJSONObject(bot_name);
                           botDetails.put("name", bot_name);
                           botDetails.put("language", language_name);
                           botDetails.put("group", group_name);
                           botDetails.put("key", key_name);
                           botList.add(botDetails);
                       }
                   }
               }
           }
       }

List of all bots, botList is return as server response.

ListPrivateDraftSkillService

For creating a list we need to iterate over each user and check whether the user has a draft bot. We get all the authorized clients from DAO.getAuthorizedClients(). We then iterate over each client and get their identity and authorization. We get the drafts of the client from DAO.readDrafts(userAuthorization.getIdentity()). We then iterate over each draft and add it to the drafts object. Each draft object consists of date created,date modified, object which contains draft bot information such as name,language,etc provided by the user while saving the draft, email Id and uuid of the user.

       JSONObject result = new JSONObject();
       List draftBotList = new ArrayList();
       Collection authorized = DAO.getAuthorizedClients();

       for (Client client : authorized) {
         String email = client.toString().substring(6);
         JSONObject json = client.toJSON();
         ClientIdentity identity = new ClientIdentity(ClientIdentity.Type.email, client.getName());
         Authorization userAuthorization = DAO.getAuthorization(identity);
         Map map = DAO.readDrafts(userAuthorization.getIdentity());
         JSONObject drafts = new JSONObject();

         for (Map.Entry entry: map.entrySet()) {
           JSONObject val = new JSONObject();
           val.put("object", entry.getValue().getObject());
           val.put("created", DateParser.iso8601Format.format(entry.getValue().getCreated()));
           val.put("modified", DateParser.iso8601Format.format(entry.getValue().getModified()));
           drafts.put(entry.getKey(), val);
         }
         Iterator keys = drafts.keySet().iterator();
         while(keys.hasNext()) {
           String key = (String)keys.next();
           if (drafts.get(key) instanceof JSONObject) {
             JSONObject draft = new JSONObject(drafts.get(key).toString());
             draft.put("id", key);
             draft.put("email", email);
             draftBotList.add(draft);
           }
         }
       }
       result.put("draftBots", draftBotList);

List of all drafts, draftBotList is returned as server response.

In conclusion, the admins can now see the bots and drafts created by the user and monitor where they are being used.

Resources

Continue ReadingDisplaying Private Skills and Drafts on SUSI.AI

Upload Avatar for a User in SUSI.AI Server

In this blog post, we are going to discuss on how the feature to upload the avatar for a user was implemented on the SUSI.AI Server. The API endpoint by which a user can upload his/her avatar image is https://api.susi.ai/aaa/uploadAvatar.json.

  • The endpoint is of POST type.
  • It accepts two request parameters –
  • image – It contains the entire image file sent from the client
  • access_token – It is the access_token for the user

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

Going through the API development

  • The image and access_token parameters are first extracted via the req object, that is passed to the main function. The  parameters are then stored in variables.
  • There is a check if the access_token and image exists. It it doesn’t, an error is thrown.
  • This code snippet discusses the above two points –

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    Part imagePart = req.getPart("image");
    if (req.getParameter("access_token") != null) {
        if (imagePart == null) {
            result.put("accepted", false);
            result.put("message", "Image file not received");
        } else {.
        }
    else{
        result.put("message","Access token are not given");
        result.put("accepted",false);
        resp.setContentType("application/json");
        resp.setCharacterEncoding("UTF-8");
        resp.getWriter().write(result.toString());
    }
}

 

  • Then the input stream is extracted from the imagePart and stored. And post that the identity is checked if it is valid.
  • The input stream is converted into the Image type using the ImageIO.read method.
  • The image is eventually converted into a BufferedImage using a function, described below.

public static BufferedImage toBufferedImage(Image img)
{
    if (img instanceof BufferedImage)
        return (BufferedImage) img;

    // Create a buffered image with transparency
    BufferedImage bimage = new BufferedImage(img.getWidth(null),     
    img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
    // Draw the image on to the buffered image
    Graphics2D bGr = bimage.createGraphics();
    bGr.drawImage(img, 0, 0, null);
    bGr.dispose();
    // Return the buffered image
    return bimage;
}

 

  • After that, the file path and name is set. The avatar for each user is stored in the /data/avatar_uploads/<uuid of the user>.jpg.
  • The avatar is written to the path using the ImageIO.write function. Once, the file is stored on the server, the success response is sent and the client side receives it.

Resources

Continue ReadingUpload Avatar for a User in SUSI.AI Server

Make a cumulative API to return skills based on standard metrics in SUSI.AI

In this blog post, we are going to discuss on how to make a cumulative API which returns skills based on different standard metrics. It was implemented to combine various API calls, that were made from various clients, thereby reducing the number of API calls made. It lead to an optimization in the page load time of various clients and also helped to reduce the 503 errors that were received, due to very frequent API hits. The API endpoint for it is https://api.susi.ai/cms/getSkillMetricsData.json.

It accepts 5 optional parameters –

  • model – It represents the model for which the skill are fetched. The default value is set to General.
  • group – It represents the group(category) for which the skill are fetched. The default value is set to Knowledge.
  • language – It represents the language for which the skill are fetched. The default value is set to en.
  • duration – It represents the duration based on which skills are fetched by standard metrics like usage.
  • count – It represents the number of skills to be returned on a particular metric.

The minimalUserRole is set to ANONYMOUS for this API, as the data is required for home-page and doesn’t need the user to be logged-in.

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 count param exists. If exists, then it is checked if it is of valid data-type. If no, an exception is thrown. And if count param doesn’t exist, it is set to default of 10.
  • This code snippet discusses the above two points –
@Override
public ServiceResponse serviceImpl(Query call, HttpServletResponse response, Authorization rights, 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", "All");
        String language_name = call.get("language", "en");
        int duration = call.get("duration", 0);
        JSONArray jsonArray = new JSONArray();
        JSONObject json = new JSONObject(true);
        JSONObject skillObject = new JSONObject();
        String countString = call.get("count", null);
        Integer count = null;

        if(countString != null) {
            if(Integer.parseInt(countString) < 0) {
                throw new APIException(422, "Invalid count value. It should be positive.");
            } else {
                try {
                    count = Integer.parseInt(countString);
                } catch(NumberFormatException ex) {
                    throw new APIException(422, "Invalid count value.");
                }
            }
        } else {
            count = 10;
        }
.
.
.

 

  • Then the skills are fetched based on the group name and then stored in a JSONArray named jsonArray. This array basically contains the metadata objects of each skill.
  • Now, we need to sort and filter these skills based on standard metrics like – Rating, Feedback Count, Usage Count, Latest skills.
  • The implementation for getting the skills based on the creation time is as follows. Rest, all the metrics were also implemented in the same fashion.

JSONObject skillMetrics = new JSONObject();
List<JSONObject> jsonValues = new ArrayList<JSONObject>();

// temporary list to extract objects from skillObject
for (int i = 0; i < jsonArray.length(); i++) {
    jsonValues.add(jsonArray.getJSONObject(i));
}

// Get skills based on creation date - Returns latest skills
Collections.sort(jsonValues, new Comparator<JSONObject>() {
    private static final String KEY_NAME = "creationTime";
    @Override
    public int compare(JSONObject a, JSONObject b) {
        String valA = new String();
        String valB = new String();
        int result = 0;

        try {
            valA = a.get(KEY_NAME).toString();
            valB = b.get(KEY_NAME).toString();
            result = valB.compareToIgnoreCase(valA);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return result;
    }
});

JSONArray creationDateData = getSlicedArray(jsonValues, count);
skillMetrics.put("latest", creationDateData);

.
.
.

 

  • The above code snippet deals with sorting the skills based on the creation time, that helps us to fetch the latest skills. The latest skills are stored on the skillMetrics object with the key name latest.
  • Similarly, the skills based on metrics like rating, feedback count and usage are stored with key names rating, feedback & usage respectively.

From the above snippet, we can also see a call to the function getSlicedArray. It takes a list of skills and count as input paramters. It returns the first ‘count’ skills from the list depending on the count value. The implementation of it is as follows –

private JSONArray getSlicedArray(List<JSONObject> jsonValues, Integer count)
{
    JSONArray slicedArray = new JSONArray();
    for (int i = 0; i < jsonValues.size(); i++) {
        if(count == 0) {
            break;
        } else {
            count --;
        }
        slicedArray.put(jsonValues.get(i));
    }
    return slicedArray;
}

 

  • The response object is then sent with 6 (six) key values mainly, apart from the session object. They are
    • accepted –  true – It tells that the skills have been fetched.
    • message – “Success: Fetched skill data based on metrics”
    • model –  It is the model that is sent on request params or the default value.
    • group –  It is the group that is sent on request params or the default value.
    • language – It is the language that is sent on request params or the default value.
    • metrics –  It is the main object of relevance for this API, which contains 4 child keys with values as an array of Skill Metadata objects.

{
  "accepted": true,
  "model": "general",
  "group": "All",
  "language": "en",
  "metrics": {
    "feedback": [
    {skill_1}, {skill_2}, ...
    ],
    "usage": [
    {skill_1}, {skill_2}, ...
    ],
    "rating": [
    {skill_1}, {skill_2}, ...
    ],
    "latest": [
    {skill_1}, {skill_2}, ...
    ]
  },
  "message": "Success: Fetched skill data based on metrics",
  "session": {
   ....
  }
}

 

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

References

Continue ReadingMake a cumulative API to return skills based on standard metrics in SUSI.AI

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