Adding a Horizontally scrollable component to display Skills based on metrics

In this blog post, I will discuss about the implementation of a horizontally scrollable component to display skill based on metrics. The purpose of the implementation is to show top skills based on metrics related to usage, ratings, etc in SUSI.AI Skills CMS.

Implementational details

  • We call this component SkillCardScrollList which takes in a list of cards to be displayed along with some other properties and returns an UI, as shown in the above GIF.
  • The parameters that the component takes are:
    • scrollId: It is a required field of the type String. It is the id name of the horizontally scrollable div.
    • skills: It contains an array of cards that are to displayed inside the container.
    • languageValue: It represents the language of the skills that are shown.
    • skillUrl: It contains the URL that the app would be taken to, on clicking individual Skill Card.
    • modelValue: It contains the model that the skill belongs to.
  • Here is a sample of how it is used in the BrowseSkill component, for showing the Top Rated Skills in a SkillCardsScrollList
<SkillCardScrollList
    scrollId="topRated"
    skills={this.state.topRatedSkills}
    modalValue={this.state.modalValue}
    languageValue={this.state.languageValue}
    skillUrl={this.state.skillUrl}
/>

 

  • The reason behind passing an unique scrollId as a prop to the component is that, there was a need to trigger the scroll event of the scrollable div n the click of left and right Floating Action Buttons (FABs) as shown in the UI. And, on multiple imports of this component, there would have been inconsistent scroll behaviour seen, had it not been unique.
  • Following in the code block of the component, which will be explained in details, that deals with the main implementation –
.
.
.
.
  scrollLeft = () => {
    let parentEle = document.getElementById(this.props.scrollId);
    let scrollValue = $(parentEle).scrollLeft() - 200;
    $(parentEle)
      .stop()
      .animate({ scrollLeft: scrollValue }, 100);
  };

  scrollRight = () => {
    // Similar function of scrollLeft
  };

  loadSkillCards = () => {
    let cards = [];
    Object.keys(this.state.skills).forEach(el => {
      .
      /* Each skill object is passed and then pushed to the cards
        array*/
      .
      );
    });
    // Set the cards array in the state 
    this.setState({
      cards,
    });
  };

  render() {
    return (
      <div
        style={{
          marginTop: '20px',
          marginBottom: '40px',
          textAlign: 'justify',
          fontSize: '0.1px',
          width: '100%',
        }}
      >
        <div>
          <div
            id={this.props.scrollId}
            className="scrolling-wrapper"
            style={styles.gridList}
          >
            <FloatingActionButton
              mini={true}
              backgroundColor={'#4285f4'}
              style={styles.leftFab}
              onClick={this.scrollLeft}
            >
              <NavigationChevronLeft />
            </FloatingActionButton>
            {this.state.cards}
            <FloatingActionButton
              mini={true}
              backgroundColor={'#4285f4'}
              style={styles.rightFab}
              onClick={this.scrollRight}
            >
              <NavigationChevronRight />
            </FloatingActionButton>
          </div>
        </div>
      </div>
    );
  }
}

 

  • The div with class scrolling-wrapper is actually scrolled on the click of the left and right FAB. For choosing the correct div to be scrolled, there was a necessary condition of an unique id as explained earlier, which has been set to the div.
  • For making the component horizontally scrollable, specific CSS rules are added to the div. They are –
gridList: {
  margin: '10px',
  textAlign: 'center',
  overflowX: 'scroll',
  overflowY: 'hidden',
  whiteSpace: 'nowrap',
},
leftFab: {
  position: 'absolute',
  left: 260,
  marginTop: 75,
},
rightFab: {
  position: 'absolute',
  right: 0,
  marginTop: 75,
  marginRight: 10,
},

 

  • The CSS rules for the FABs make them fixed in a position and only lets the card list scroll.
  • Lastly, there was a issue regarding the presence of horizontal scroll-bar been shown, which makes the UI look a bit unpleasant.

  • It was hidden with a pseudo selector CSS rule.
div.scrolling-wrapper::-webkit-scrollbar {
    display: none;
}

 

This was the implementation for the horizontally scrollable component for displaying Skill List based on a standard metrics. I hope, you found the blog helpful in making the understanding of the implementation better.

Resources

Continue ReadingAdding a Horizontally scrollable component to display Skills based on metrics

Change Text-to-Speech Voice Language of SUSI in SUSI iOS

SUSI iOS app now enables the user to change the text-to-speech voice language within the app. Now, the user can select any language of their choice from the list of 37 languages list. To change the text-to-speech voice language, go to, Settings > Change SUSI’s Voice, choose the language of your choice. Let see here how this feature implemented.

Apple’s AVFoundation API is used to implement the text-to-speech feature in SUSI iOS. AVFoundation API offers 37 voice languages which can be used for text-to-speech voice accent. AVFoundation’s AVSpeechSynthesisVoice API can be used to select a voice appropriate to the language of the text to be spoken or to select a voice exhibiting a particular local variant of that language (such as Australian or South African English).

To print the list of all languages offered by AVFoundation:

import AVFoundation

print(AVSpeechSynthesisVoice.speechVoices())

Or the complete list of supported languages can be found at Languages Supported by VoiceOver.

When the user clicks Change SUSI’s voice in settings, a screen is presented with the list of available languages with the language code.

Dictionary holds the list of available languages with language name and language code and used as Data Source for tableView.

var voiceLanguagesList: [Dictionary<String, String>] = []

When user choose the language and click on done, we store language chosen by user in UserDefaults:

UserDefaults.standard.set(voiceLanguagesList[selectedVoiceLanguage][ControllerConstants.ChooseLanguage.languageCode], forKey: ControllerConstants.UserDefaultsKeys.languageCode)
UserDefaults.standard.set(voiceLanguagesList[selectedVoiceLanguage][ControllerConstants.ChooseLanguage.languageName], forKey: ControllerConstants.UserDefaultsKeys.languageName)

Language name with language code chosen by user displayed in settings so the user can know which language is currently being used for text-to-speech voice.

To select a voice for use in speech, we obtain an AVSpeechSynthesisVoice instance using one of the methods in Finding Voices and then set it as the value of the voice property on an AVSpeechUtterance instance containing text to be spoken.

Earlier stored language code in UserDefaults shared instance used for setting the text-to-speech language for AVSpeechSynthesisVoice.

if let selectedLanguage = UserDefaults.standard.object(forKey: ControllerConstants.UserDefaultsKeys.languageCode) as? String {
speechUtterance.voice = AVSpeechSynthesisVoice(language: selectedLanguage)
}

AVSpeechUtterance is responsible for a chunk of text to be spoken, along with parameters that affect its speech.

Resources –

  1. UserDefaults: https://developer.apple.com/documentation/foundation/userdefaults
  2. AVSpeechSynthesisVoice: https://developer.apple.com/documentation/avfoundation/avspeechsynthesisvoice
  3. AVFoundation: https://developer.apple.com/av-foundation/
  4. SUSI iOS Link: https://github.com/fossasia/susi_iOS
Continue ReadingChange Text-to-Speech Voice Language of SUSI in SUSI iOS

Skill Ratings Over Time

The SUSI SKill CMS provides an option to rate and review a skill. These feedbacks help the skill creators to improve the skills. Also, the ratings and reviews can be updated by the reviewer. But the CMS only provides the current rating of a skill. What if a user or a developer wants to see how that skill has performed over time? Are there any improvements in the skill or not?

For that, we need the skill ratings over time !

Server side implementation

Create a ratingsOverTime.json file to store the monthly average rating of the skills and make a JSONTray object for that in src/ai/susi/DAO.java file. The JSON file contains the timestamp for every month, the average ratings on a skill in that month and the total number of ratings in that month.

public static JsonTray ratingsOverTime;

Path ratingsOverTime_per = susi_skill_rating_dir.resolve("ratingsOverTime.json");
Path ratingsOverTime_vol = susi_skill_rating_dir.resolve("ratingsOverTime_session.json");
ratingsOverTime = new JsonTray(ratingsOverTime_per.toFile(), ratingsOverTime_vol.toFile(), 1000000);
OS.protectPath(ratingsOverTime_per);
OS.protectPath(ratingsOverTime_vol);

Now whenever a user rates a skill, the data in ratingsOverTime.json needs to be updated. For this fetch the overall rating data of the current month. Multiply the average rating with the total number of ratings (count) of that month.

sum = average_rating X number_of_ratings

Then add the rating given by the current user to this sum and divide by count + 1 to again get the new average rating. Also increment the total number of ratings by 1.

new_sum = sum + rating_by_user

new_avg = new_sum/(count+1)

number_of_ratings =  number_of_ratings + 1

float totalRating = skillRating * ratingCount;
float newAvgRating = (totalRating + skill_stars)/(ratingCount + 1);
ratingObject.put("rating", newAvgRating);
ratingObject.put("count", ratingCount + 1);

Now we have got the ratings over time stored in ratingsOverTime.json file. An API to access this data is also required. So create an API GetRatingOverTime.java returns the ratings over time of a particular skill. The API has the following attributes :

Endpoint : /cms/getRatingsOverTime.json

Minimum user role : anonymous

Parameters : model, group, language and skill

JSONArray skillRatings = languageName.getJSONArray(skill_name);
result.put("skill_name", skill_name);
result.put("ratings_over_time", skillRatings);
return new ServiceResponse(result);

It fetches the data corresponding to the skill from ratingsOverTime.json and returns it to the CMS.

Add this API to SusiServer.java

//Skill ratings over time
GetRatingsOverTime.class

References

Continue ReadingSkill Ratings Over Time

Creating Feedback Logs for Analysis

The thumbs up and thumbs down feedback on the clients is meant for the improvement of the skills in SUSI.AI. So we need to scope the feedback system to a particular interaction rather than skill as a whole. The feedback logs can be used for various kinds of analysis and machine learning.

Server side implementation

Components of Feedback Log:

  • User ID – For identification of a feedback given by a particular user. For consistency in data, the user should not be able to change the feedback over the same interaction.
  • Interaction:
    • User query
    • SUSI Reply
  • Client location – The response of a skill may not be interesting for the users of a particular country. That means the skill should give localised results.
  • Skill path – The path on the server where the skill is stored.

Create a feedbackLogs.json file to store the logs of feedback given by the user and make a JSONTray object for that in src/ai/susi/DAO.java file. The JSON file contains the above mentioned components.

public static JsonTray feedbackLogs; 

Path feedbackLogs_per = susi_skill_rating_dir.resolve("feedbackLogs.json");
Path feedbackLogs_vol = susi_skill_rating_dir.resolve("feedbackLogs_session.json");
feedbackLogs = new JsonTray(feedbackLogs_per.toFile(), feedbackLogs_vol.toFile(), 1000000);
OS.protectPath(feedbackLogs_per);
OS.protectPath(feedbackLogs_vol);

Create FeedbackLogService.java file that acts as an API to create the feedback logs. The API accepts the feedback data from the client and stores it into the json file using DAO object. The user should be logged in to give feedback on an interaction. So keep the minimum user role as USER to access the API.

JSONObject feedbackLogObject = new JSONObject();
feedbackLogObject.put("timestamp", timestamp);
feedbackLogObject.put("uuid", idvalue);
feedbackLogObject.put("feedback", skill_rate);
feedbackLogObject.put("user_query", user_query);
feedbackLogObject.put("susi_reply", susi_reply);
feedbackLogObject.put("country_name", country_name);
feedbackLogObject.put("country_code", country_code);
feedbackLogObject.put("skill_path", skill_path);

The API is accessible at /cms/feedbackLog.json endpoint.

Send feedback log from Web Client

The feedback API should be called only if the user is logged in. When the user presses the feedback buttons fetch the required data for log (access token, user query, susi response, country and user feedback) and POST them on the feedbackLog.json API.

let rateEndPoint =   BASE_URL + '/cms/feedbackLog.json?model=' + skill.model + '&group=' + skill.group + '&language=' + skill.language + '&skill=' + skill.skill + '&rating=' + rating + '&access_token=' + accessToken + '&user_query=' + interaction.userQuery + '&susi_reply=' + interaction.susiReply + '&country_name=' + country.countryName + '&country_code=' + country.countryCode ;

$.ajax({
  url: rateEndPoint,
  success: function(response) {
      console.log('Skill rated successfully');
  }
})

References

Continue ReadingCreating Feedback Logs for Analysis

Showing only those languages for which skills are available

SUSI.AI is available for almost all the internationally recognised languages of the world. An author is allowed to create a skill in any of these languages. But there are some languages for which skills have not been created yet. So only those languages should be shown in the SUSI Skill CMS for which the skills are available. The approach is that all the languages must be listed while creating the skills but only non-empty languages must be listed while filtering skills on the CMS category page.

Updating the get languages API

  1. Add an API parameter in GetAllLanguages.java to fetch the group name. It is used to fetch the list of languages for which skills are available in that particular group. If no group is passed it means that the all the languages are to be listed. For that, we can use any group and show all the languages in that group. Say “Knowledge”.

String group_name = call.get("group", null);
if (group_name == null) {
    File group = new File(model, "Knowledge");
}

 

  1. Now check if the file inside the group folder is a directory. If yes then add it to the list of languages to be returned.

String[] languages = group.list((current, name) -> new File(current, name).isDirectory());

 

  1. If the languages corresponding to a particular category are to be fetched then first checked if the group is “All” or any specific group. Since the “All” category is not stored as such so we need to iterate over all the groups present in the parent directory ie, the model directory.

String[] group_names = model.list((current, name) -> new File(current, name).isDirectory());

 

  1. Now iterate over all the groups present in the group_names array and list the files present in it. Apply a filter to the list that accepts a file only if it is a directory and not empty ie, contains at least 1 language. Add that file to the list of languages.

group.list(new FilenameFilter() {
	@Override
	public boolean accept(File file, String s) {
		Boolean accepted = new File(file, s).list().length > 1;
		if (accepted) {
			if (!languages.contains(s)) {
		    	languages.add(s);
			}
		}
		return accepted;
	}
});

 

  1. The processing of getting languages for a particular group is same, just the iteration over the model directory is not required.

Resources

Continue ReadingShowing only those languages for which skills are available

STOP action implementation in SUSI iOS

You may have experienced, you can stop Google home or Amazon Alexa during the ongoing task. The same feature is available for SUSI too. Now, SUSI can respond to ‘stop’ action and stop ongoing tasks (e.g. SUSI is narrating a story and if the user says STOP, it stops narrating the story). ‘stop’ action is introduced to enable the user to make SUSI stop anything it’s doing.

Video demonstration of how stop action work on SUSI iOS App can be found here.

Stop action is implemented on SUSI iOS, Web chat, and Android. Here we will see how it is implemented in SUSI iOS.

When you ask SUSI to stop, you get following actions object from server side:

"actions": [{"type": "stop"}]

Full JSON response can be found here.

When SUSI respond with ‘stop’ action, we create a new action type ‘stop’ and assign `Message` object `actionType` to ‘stop’.

Adding ‘stop’ to action type:

enum ActionType: String {
... // other action types
case stop
}

Assigning to the message object:

if type == ActionType.stop.rawValue {
message.actionType = ActionType.stop.rawValue
message.message = ControllerConstants.stopMessage
message.answerData = AnswerAction(action: action)
}

A new collectionView cell is created to respond user with “stoped” text.

Registering the stopCell:

collectionView?.register(StopCell.self, forCellWithReuseIdentifier: ControllerConstants.stopCell)

Add cell to the chat screen:

if message.actionType == ActionType.stop.rawValue {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ControllerConstants.stopCell, for: indexPath) as? StopCell {
cell.message = message
let message = ControllerConstants.stopMessage
let estimatedFrame = self.estimatedFrame(message: message)
cell.setupCell(estimatedFrame, view.frame)
return cell
}
}

AVFoundation’s AVSpeechSynthesizer API is used to stop the action:

func stopSpeakAction() {
speechSynthesizer.stopSpeaking(at: AVSpeechBoundary.immediate)
}

This method immediately stops the speak action.

Final Output:

Resources – 

  1. About SUSI: https://chat.susi.ai/overview
  2. JSON response for ‘stop’ action: https://api.susi.ai/susi/chat.json?timezoneOffset=-330&q=susi+stop
  3. AVSpeechSynthesisVoice: https://developer.apple.com/documentation/avfoundation/avspeechsynthesisvoice
  4. AVFoundation: https://developer.apple.com/av-foundation/
  5. SUSI iOS Link: https://github.com/fossasia/susi_iOS
  6. SUSI Android Link: https://github.com/fossasia/susi_android
  7. SUSI Web Chat Link: https://chat.susi.ai/
Continue ReadingSTOP action implementation in SUSI iOS

Implementing the Feedback section on Skills CMS

In this blog post, we are going to implement the Skill feedback system on the Skills CMS. The features that are added by this implementation are displaying all the comments/feedbacks of the user, ability to add new feedback and also option to edit a previous feedback that was added.

The UI interacts with the back-end server via two APIs –

Detailed explanation of the implementation

  • The first task was to create a separate component for the feedback section – SkillFeedbackCard.js, along with the CSS file SkillFeedbackCard.css
  • Create ES6 function to get all the Feedbacks of a skill,namely getFeedback(), on the parent component, i.e, SkillListing.js
getFeedback = () => {
    let getFeedbackUrl = `${urls.API_URL}/cms/getSkillFeedback.json`;
    let modelValue = 'general';
    this.groupValue = this.props.location.pathname.split('/')[1];
    this.languageValue = this.props.location.pathname.split('/')[3];
    getFeedbackUrl = getFeedbackUrl + '?model=' + modelValue + '&group=' + this.groupValue + '&language=' + this.languageValue + '&skill=' + this.name;

    let self = this;
    // Get skill feedback of the visited skill
    $.ajax({
        url: getFeedbackUrl,
        dataType: 'jsonp',
       crossDomain: true,
        jsonp: 'callback',
        success: function (data) {
            self.saveSkillFeedback(data.feedback);
        },
        error: function(e) {
            console.log(e);
        }
    });
};

saveSkillFeedback = (feedback = []) => {
    this.setState({
        skill_feedback: feedback
    })
}

 

  • This above code contains the function getFeedback(), that makes an API call to the server for getting all the feedbacks. On successfully getting the response, the feedback array of the response is then passed to a function, saveSkillFeedback(), which in turn updates the skill_feedback state, which was declared in the constructor. This re-renders the components and displays the feedback in the UI.
{
  "feedback": [
    {
      "feedback": "Awesome skill!",
      "email": "coolakshat24@gmail.com",
      "timestamp": "2018-06-12 19:28:39.297"
    },
    {
      "feedback": "Awesome skill!",
      "email": "akshatnitd@gmail.com",
      "timestamp": "2018-06-12 21:35:53.048"
    }
  ],
  "session": {"identity": {
    "type": "host",
    "name": "141.101.98.18_a7ab9c4d",
    "anonymous": true
  }},
  "skill_name": "aboutsusi",
  "accepted": true,
  "message": "Skill feedback fetched"
}

 

  • Then, we go ahead and create the function that is responsible for posting new feedback and editing them as well.
postFeedback = (newFeedback) => {

    let baseUrl = urls.API_URL + '/cms/feedbackSkill.json';
    let modelValue = 'general';
    this.groupValue = this.props.location.pathname.split('/')[1];
    this.languageValue = this.props.location.pathname.split('/')[3];
    let postFeedbackUrl = baseUrl + '?model=' + modelValue + '&group=' + this.groupValue + '&language=' + this.languageValue + '&skill=' + this.name + '&feedback=' + newFeedback + '&access_token='+cookies.get('loggedIn');

    let self = this;
    $.ajax({
        url: postFeedbackUrl,
        dataType: 'jsonp',
        jsonp: 'callback',
        crossDomain: true,
        success: function (data) {
            self.getFeedback()
        },
        error: function(e) {
            console.log(e);
        }
    });
};

 

  • This above code snippet contains the function postFeedback(newFeedback), that takes the user feedback and make an API call to update it on the server.
  • All the required functions are ready. Now we add the SkillFeedbackCard.js component on the SkillListing.js component and pass useful data in the props.
<SkillFeedbackCard
    skill_name={this.state.skill_name}
    skill_feedback={this.state.skill_feedback}
    postFeedback={this.postFeedback}
/>
  • The next step is creating the UI for the SkillFeedbackCard.js component. We have used standard Material-UI components for creating the UI, that includes List, ListItem, Divider, IconButton, etc.
  • Code snippet for the Feedback ListItem  –
<ListItem
    key={index}
    leftAvatar={<CircleImage name={data.email.toUpperCase()} size='40' />}
    primaryText={data.email}
    secondaryText={<p> {data.feedback} </p>}
/>

 

  • The next part of the UI implementation creating option to edit and post feedback.
  • Code snippet for the Post feedback section  –
className=“feedback-textbox”> id=“post-feedback” hintText=“Skill Feedback” defaultValue=“” errorText={this.state.errorText} multiLine={true} fullWidth={true} /> label=“Post” primary={true} backgroundColor={‘#4285f4’} style={{ margin: 10 }} onClick={this.postFeedback} />

 


 

  • For the edit section, I have used a Dialog box for it. Code snippet for the Edit feedback section  –
<Dialog
    title="Edit Feedback"
    actions={actions}
    modal={false}
    open={this.state.openDialog}
    onRequestClose={this.handleClose}
>
    <TextField
        id="edit-feedback"
        hintText="Skill Feedback"
        defaultValue={userFeedback}
        errorText={this.state.errorText}
        multiLine={true}
        fullWidth={true}
    />
</Dialog>

 

This was the implementation for the Skill Feedback System on the Skills CMS and I hope, you found the blog helpful in making the understanding of the implementation better.

Resources

 

Continue ReadingImplementing the Feedback section on Skills CMS

Displaying skill rating for each skill on skill page of SUSI SKILL CMS

SUSI exhibits several skills which are managed by the SUSI Skill CMS, it essentially is a client which allows users to create/update skills conveniently since for each skill it is important to have the functionality of rating system so developers can get to know which skills are performing better than the rest and consequently improve them, thus a skill rating system which allows the users to give positive or negative feedback for each skill is implemented on the server.

Fetching skill_rating from the server

  1. Fetch skill data for which ratings are to be displayed through ajax calls
    API Endpoint –

    /cms/getSkillMetadata.json?
    

  2. Parse the received metadata object to get positive and negative ratings for that skill
  3. if(skillData.skill_rating) {
           	let positive_rating = skillData.skill_rating.positive;
            	let negative_rating = skillData.skill_rating.negative;
    }
    

    Sample API response

    {
      "skill_metadata": {
        "model": "general",
        "group": "Knowledge",
        "language": "en",
        "developer_privacy_policy": null,
        "descriptions": "Want to know about fossasia, just ask susi to tell that, Susi tells about the SUSI.AI creators",
        "image": "images/creator_info.png",
        "author": "madhav rathi",
        "author_url": "https://github.com/madhavrathi",
        "author_email": null,
        "skill_name": "Creator Info",
        "terms_of_use": null,
        "dynamic_content": false,
        "examples": [
          "Who created you?",
          "what is fossasia?"
        ],
        "skill_rating": {
          "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": 0
        },
        "creationTime": "2018-03-17T16:38:29Z",
        "lastAccessTime": "2018-06-15T15:51:50Z",
        "lastModifiedTime": "2018-03-17T16:38:29Z"
      },
      "accepted": true,
      "message": "Success: Fetched Skill's Metadata",
      "session": {"identity": {
        "type": "host",
        "name": "162.158.166.37_d80fb5c9",
        "anonymous": true
      }}
    }
    

  4. Set the react state of the component to store positive and negative rating.
  5. this.setState({
      positive_rating,
      negative_rating
    })
    

  6. Use react-icons to fetch like and dislike icon components from font-awesome.
  7. npm i -S react-icons
    

  8. Import the corresponding icons in the SkillPage component
  9. import { FaThumbsOUp, FaThumbsODown } from 'react-icons/lib/fa/'
    

  10. Display the rating count along with their icons
  11. <div className="rating">
        <div className="positive">
             <FaThumbsOUp />
             {this.state.positive_rating}
         </div>
           <div className="negative">
                 <FaThumbsODown />
                 {this.state.negative_rating}
             </div>
    </div>
    

Example

References

Continue ReadingDisplaying skill rating for each skill on skill page of SUSI SKILL CMS

SUSI AI 5 Star Skill Rating System

For making a system more reliable and robust, continuous evaluation is quite important. So is in case of SUSI AI. User feedback is important to improve SUSI skills and create new ones. Previously we had only thumbs up / thumbs down as a feedback method, from the susi chat client. But now a 5 star rating system has been added to the SUSI Skill CMS so that users can rate a skill there. Before the implementation of API  let’s look how data is stored in SUSI AI Susi_server uses DAO in which skill rating is stored as JSONTray.

The server side implementation

A new java class has been created for the API, FiveStarRateSkillService.java.

public class FiveStarRateSkillService extends AbstractAPIHandler implements APIHandler {
    private static final long serialVersionUID =7947060716231250102L;
    @Override
    public BaseUserRole getMinimalBaseUserRole() {
        return BaseUserRole.USER;
    }
    @Override
    public JSONObject getDefaultPermissions(BaseUserRole baseUserRole) {
        return null;
    }
    @Override
    public String getAPIPath() {
        return "/cms/rateSkill.json";
    }
...
}

The getMinimalBaseRole method tells the minimum User role required to access this servlet it can also be ADMIN, USER or ANONYMOUS. In our case it is USER. A user needs to be logged in to rate a skill on a scale of 1-5 stars.  The API runs at “/cms/fiveStarRateSkill.json” endpoint.

Next, create serviceImpl method in the above class to handle the request from the client and respond to it.

1. Fetch the required query parameters and store them in variables. They include skill model, group, language, skill name and starts that the user has given in the rating.

String skill_name = call.get("skill", null);
String skill_stars = call.get("stars", null);

2. Then check if the skill exists. If not them throw an exception. Otherwise, increment the count of the corresponding rating. The rating object has keys as one_star, two_star, three_star, four_star and five_star that has the count of that star rating.       

if (skill_stars.equals("1")) {
    skillName.put("one_star", skillName.getInt("one_star") + 1 + "");
}
else if (skill_stars.equals("2")) {
    skillName.put("two_star", skillName.getInt("two_star") + 1 + "");
}
else if (skill_stars.equals("3")) {
    skillName.put("three_star", skillName.getInt("three_star") + 1 + "");
}
else if (skill_stars.equals("4")) {
    skillName.put("four_star", skillName.getInt("four_star") + 1 + "");
}
else if (skill_stars.equals("5")) {
    skillName.put("five_star", skillName.getInt("five_star") + 1 + "");
}

3. Re-calculate the total rating done on that skill and its average rating and update the object. If the skill has not been already rated then create a new rating object and initialize it with the 0 star counts.

public JSONObject createRatingObject(String skill_stars) {
        JSONObject skillName = new JSONObject();
        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);
}

The complete FiveStarRateSkillService.java is available here : –

https://github.com/fossasia/susi_server/blob/development/src/ai/susi/server/api/cms/FiveStarRateSkillService.java

Rating a skill

Sample endpoint

https://api.susi.ai/cms/fiveStarRateSkill.json?model=general&group=Knowledge&language=en&skill=aboutsusi&stars=3&callback=p&_=1526813916145

This gives 3 star rating to the “aboutsusi” skill.

Parameters

  • Model
  • Group
  • Language
  • Skill
  • Stars

Response

{
  "ratings": {
    "one_star": 0,
    "four_star": 0,
    "five_star": 1,
    "total_star": 1,
    "three_star": 0,
    "avg_star": 5,
    "two_star": 0
  },
  "session": {"identity": {
    "type": "email",
    "name": "1anuppanwar@gmail.com",
    "anonymous": false
  }},
  "accepted": true,
  "message": "Skill ratings updated"
}

Getting the stats of Skill Ratings

Sample endpoint

http://127.0.0.1:4000/cms/getSkillRating.json?model=general&group=Knowledge&language=en&skill=aboutsusi&callback=p&_=1526813916145

This fetches the current ratings of the “aboutsusi” skill.

Parameters

  • Model
  • Group
  • Language
  • Skill

Response

{
    "session": {
        "identity": {
            "type": "host",
            "name": "172.68.144.159_81c88a10",
            "anonymous": true
        }
    },
    "skill_name": "aboutsusi",
    "accepted": true,
    "message": "Skill ratings fetched",
    "skill_rating": {
        "negative": "0",
        "positive": "0",
        "stars": {
            "one_star": 0,
            "four_star": 2,
            "five_star": 1,
            "total_star": 4,
            "three_star": 1,
            "avg_star": 4,
            "two_star": 0
        },
        "feedback_count": 3
    }
}

Conclusion

So this 5 star rating system will help in improving the SUSI skills. Also, it will help in making better decisions when we have multiple similar skills and we have to choose one to respond to the user query.

References

Continue ReadingSUSI AI 5 Star Skill Rating System