How selected autoplay works and messages are created in webclient

In this post we will discuss the message model of SUSI.AI webclient and how youtube autoplay features selectively autoplays only the last played song from youtube. The answer or response to any query to susi is send by server in json format, which is just a raw form of data which is then used by webclient to create the message in desired form in different action types. Every response to a query is given in an array of action types with each element in the array is a snippet of HTML codes. This array is called the messageListItem. This messageListItem array is further, a part of a bigger array which contains all the codes for the query, answer and date elements. This array is called the messageListItems. This contains the final code block of all the messages displayed on the web client. All this is handled by two files MessageListItem.js and MessageSection.js and we will discuss their working below in detail. (more…)

Continue ReadingHow selected autoplay works and messages are created in webclient

Implementation of admin dashboard on Accounts

The admin dashboard for SUSI.AI is implemented in the susi accounts to access various admin features like list all users registered on SUSI and change user roles of other users. In this post we will discuss about the client side implementation of listing registered users and changing their user roles. (more…)

Continue ReadingImplementation of admin dashboard on Accounts

5 Star Skill Rating System in the SUSI.AI CMS

For making a system more reliable and robust, continuous evaluation is quite important. So is in the 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 rating system has been implemented on SkillListing.js file on the CMS. The CMS side Install the Recharts Data Visualization library. $ npm install --save recharts Install the Rating library for react. $ npm install --save react-ratings-declarative Import the Barchart, Cell, LabelList, Bar, XAxis, YAxis and Tooltip components from Recharts. import {BarChart, Cell, LabelList, Bar, XAxis, YAxis, Tooltip} from 'recharts'; Import the Ratings components from React Ratings Declarative. import Ratings from 'react-ratings-declarative'; Add average rating, total ratings, rating counts on each star and rating given by the users as state variables. this.state = { ... avg_rating: '', total_star: '', skill_ratings: [], rating : 0 ... } Load the skill ratings as soon as the page loads. getSkillRating.json API is used to get skill ratings. Fetch the stars related data from the API response and save them in the state variable using saveSkillRatings() function. $.ajax({ url: skillRatingUrl, success: function (data) { self.saveSkillRatings(data.skill_rating.stars) }, );  Store the skills rating data to be visualized on the charts. The rating data is kept in an array and store in a state variable. Put zero if the rating count is not available. This state variable is used by the Recharts library as an input for data visualization. saveSkillRatings = (skill_ratings) => { const ratings_data = [ {name: '5 ⭐', value: skill_ratings.five_star || 0}, {name: '4 ⭐', value: skill_ratings.four_star || 0}, {name: '3 ⭐', value: skill_ratings.three_star || 0}, {name: '2 ⭐', value: skill_ratings.two_star || 0}, {name: '1 ⭐', value: skill_ratings.one_star || 0}]; this.setState({ skill_ratings: ratings_data, avg_rating: skill_ratings.avg_star, total_star: skill_ratings.total_star }) } Create a function to that informs the server about the rating given by the current user. The changeRating() function calls the fiveStarRateSkill.json API with the parameters like who has rated the skill and what rating has been given. changeRating = (newRating) => { $.ajax({ url: changeRatingUrl, success: function(data) { console.log('Ratings accepted'); }, this.setState({ rating: newRating }); };  Check if the user is logged in and display the rating bar. The rating bar should appear only if the user is logged in. The document’s cookies hold the information about the logged in user. { cookies.get('loggedIn') ? <Ratings  rating={this.state.rating}  changeRating={this.changeRating}   >  <Ratings.Widget />  <Ratings.Widget />  <Ratings.Widget />  <Ratings.Widget />  <Ratings.Widget /> </Ratings> : null }  Display the BarChart of existing ratings, total ratings and average rating. It shows the count on each star. If the average rating is available in the state then show it, otherwise put zero in the…

Continue Reading5 Star Skill Rating System in the SUSI.AI CMS

Implementing YouTube Search API with WebClient

SUSI.AI is an assistant which enables us to create a lot of skills. These skills offer various functionalities which performs different actions. Therefore SUSI has implemented various action types. Some of these are:

  • Answer
  • Table
  • Maps
  • Rss
  • audio_play
  • video_play

When a user answers a query the server sends response in form of actions type. The client then scans the response JSON object and checks the type of action and performs the desired operations. This action types are verified by (more…)

Continue ReadingImplementing YouTube Search API with WebClient

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…

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

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 : 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   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…

Continue ReadingTesting Endpoints on Local Server

Added “table” type action support in SUSI android app

SUSI.AI has many actions supported by it, for eg: answer, anchor, map, piechart, websearch and rss.These actions are a few of those that can be supported in the SUSI.AI android app, but there are many actions implemented on the server side and the web client even has the implementation of how to handle the “table” type response. The table response is generally a JSON array response with different json objects, where each json object have similar keys, and the actions key in the JSON response has the columns of the table response which are nothing but the keys in the data object of the response. To implement the table type response in the susi android app a separate file needed to made to parse the table type response, since the keys and values both are required to the display the response. The file ParseTableSusiResponseHelper.kt was made which parsed the JSON object using the Gson converter factory to get the key value of the actions : "actions": [        {          "columns": {            "ingredients": "Ingredients",            "href": "Instructions Link",            "title": "Recipe"          },          "count": -1,          "type": "table"        }      ]   The inside the columns the keys and the values, both were extracted, values were to displayed in the title of the column and keys used were to extract the values from the “data” object of the response. The files TableColumn.java, TableData.java are POJO classes that were used for storing the table columns and the data respectively. The TableDatas.java class was used to store the column list and the data list for the table response. To fetch the table type response from the server a TableSusiResponse.kt file was added that contained serializable entities which were used to map the response values fetched from the server. A variable that contained the data stored in the “answers” key of the response was made of type of an ArrayList of TableAnswers. @SerializedName("answers") @Expose val answers: List<TableAnswer> = ArrayList() The TableAnswer.kt is another file added that contains serializable variables to store values inside the keys of the “answers” object. The actions object shown above is inside the answers object and it was stored in the form of an ArrayList of TableAction. @SerializedName("actions") @Expose val actions: List<TableAction> = ArrayList() Similar to TableAnswer.kt file TableAction.kt file also contains serializable variables that map the values stored in the “actions” object. In the retrofit service interface SusiService.java a new call was added to fetch the data from the server as follows : @GET("/susi/chat.json") Call<TableSusiResponse> getTableSusiResponse(@Query("timezoneOffset") int timezoneOffset,                                            @Query("longitude") double longitude,                                            @Query("latitude") double latitude,                                            @Query("geosource") String geosource,                                            @Query("language") String language,                                            @Query("q") String query); Now, after the data was fetched, the table response can be parsed using the Gson converter factory in the ParseTableSusiResponseHelper.kt file. Below is the implementation : fun parseSusiResponse(response: Response<TableSusiResponse>) {   try {       var response1 = Gson().toJson(response)       var tableresponse = Gson().fromJson(response1, TableBody::class.java)       for (tableanswer in tableresponse.body.answers) {           for (answer in tableanswer.actions) {               var map = answer.columns               val set = map?.entries               val iterator = set?.iterator()               while (iterator?.hasNext().toString().toBoolean()) {                   val entry = iterator?.next()                   listColumn.add(entry?.key.toString())                   listColVal.add(entry?.value.toString())…

Continue ReadingAdded “table” type action support in SUSI android app

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 - Material Design: https://material.io/design/ 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. Firstly, the componentDidUpdate() function calls the loadMap function to load the google map. componentDidUpdate() { this.loadMap(); }   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 }   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;   Then we look for HTML div ref ‘map’ in the React DOM and name it ‘node’. This is done as follows: const mapRef…

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…

Continue ReadingImplementing Table View in Devices Tab in Settings