Using Picasso to Show Images in SUSI Android

Important skills of SUSI.AI are to display web search queries, maps of any location and provide a list of relevant information of a topic. This blog post will cover why Glide is replaced by Picasso to show images related to these action types and how it is implemented in SUSI Android . Picasso is a powerful image downloading and caching open source library developed by Square. Why Glide is replaced by Picasso to show images in SUSI Android? Previously we used Glide library to show preview in SUSI Android but we replace it because it was creating an error continuously. java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity at com.bumptech.glide.manager.RequestManagerRetriever  at Com.bumptech.glide.manager.RequestManagerRetriever.get(RequestManagerRetriever.java:102) at com.bumptech.glide.manager.RequestManagerRetriever.get(RequestManagerRetriever.java:87)at com.bumptech.glide.Glide.with(Glide.java:629) atnorg.fossasia.susi.ai.adapters.recycleradapters.WebSearchAdapter.onBindViewHolder(WebSearchAdapter.java:74) Reason for this error is when activity destroyed and again recreated the context used by glide is old one and  that activity already destroyed . Glide.with(context).load(imageList.get(0)) One solution of this error is to use context.getApplicationContext()  but it is a bad idea. Another solution is to replace glide by picasso and later one is good because picasso is also a very good image downloading and caching library. To use Picasso in your project you have to add dependency in build.gradle(Module) file. dependencies {   ...   compile "com.squareup.picasso:picasso:2.4.0"   ... } How Picasso is used in different actiontype Map "actions": [      {        "type": "map",        "latitude": "1.2896698812440377",        "longitude": "103.85006683126556",        "zoom": "13"      }    ] Link we used to retrieve image url is http://staticmap.openstreetmap.de/statucmap.php?center=latitude,longitude& zoom=zoom&size=lengthXbreadth Picasso will load image from this url and show image in the imageview. Here mapImage is the imageview in which map image is shown. Picasso.with(currContext).load(mapHelper.getMapURL())                        .into(mapImage, new  com.squareup.picasso.Callback() {                                @Override                            public void onSuccess() {                                pointer.setVisibility(View.VISIBLE);                            }                            @Override                            public void onError() {                                Log.d("Error", "map image can't loaded");                            }                        }); WebSearch When we query like “Search for fog” we get ‘query’ in reply from server "query": "fog" Now we use this query to retrieve image url which we used in Picasso to show images.Picasso load this image into previewImageView imageview. Image url is retrieved using  DuckDuckGo api. We are using url https://api.duckduckgo.com/?format=json&pretty=1&q=query&ia=meanings It gives a json response which contains image url Picasso.with(context).load(iconUrl)       .into(holder.previewImageView, new  com.squareup.picasso.Callback() {                   @Override                    public void onSuccess() {                          Log.d("Sucess","image loaded successfully");                    }                    @Override                    public void onError() {                        holder.previewImageView.setVisibility(View.GONE);                      }                 });      Here also com.squareup.picasso.Callback is use to find that image is loaded successfully or not. RSS When we query any like “dhoni” we get ‘link’ in reply from server "title": "Dhoni", "description": "", "link": "http://de.wikipedia.org/wiki/Dhoni" We use this link in android-link-preview library to retrieve relevant image url and then Picasso use this url to load image into imageview previewImageView. Picasso.with(currContext).load(imageList.get(0))       .fit().centerCrop()       .into(previewImageView); Reference Main site of Picasso library: http://square.github.io/picasso/ Blog link: https://code.tutsplus.com/tutorials/android-sdk-working-with-picasso--cms-22149

Continue ReadingUsing Picasso to Show Images in SUSI Android

Handling Change of Password of SUSI.AI Account

In this blog, we will talk about a very special case, where the user changes his password to his current one only, in other words, the user enters the same password in both current password and new password. This case is now being handled by SUSI.AI server. Considering the example of SUSI.AI Web Chat, we have following dialog when the user tries to change his/her password: Here the user can add his/her current password and new password. When the new password meets the minimum conditions (minimum 6 characters), then the user can press CHANGE button. We make ajax call to the server with the following endpoint: BASE_URL+'/aaa/changepassword.json?'+ 'changepassword=' + email + '&password=' + this.state.passwordValue + '&newpassword=' + this.state.newPasswordValue + '&access_token='+cookies.get('loggedIn'); Here we have 4 parameters: changepassword: This takes the email of the current user password: This is the password of the current user, which is saved in the state named “passwordValue” newpassword: This is the new password which the user enters access_token: These are access tokens which are fetched from cookies. These are defined on login and are deleted on logout. This is now handled on the server by a file named PasswordChangeService.java. Here we have to check whether the newpassword and password matches or not. In this file, we have a function named serviceImpl with return type ServiceResponse and takes in an argument: Query post (Query is the return type). The query is not the only argument, Please read from the file from resources mentioned below for all the argument. To handle our case we just need to work with the post. We extract the password, newpassword and email as follows: String useremail = post.get("changepassword", null); String password = post.get("password", null); String newpassword = post.get("newpassword",null); So to simply handle the case where password and newpassword matches, we define an if block in java and compare these two parameters as follows: if(password.equals(newpassword)){ result.put("message", "Your current password and new password matches"); result.put("accepted", false); return new ServiceResponse(result); } Here we put the message as “Your current password and new password matches” and make the accepted flag of result JSON as false. After this, we return the ServiceResponse. Now in our web chat client, the ajax call is as follows: $.ajax({ url: changePasswordEndPoint, dataType: 'jsonp', crossDomain: true, timeout: 3000, async: false, statusCode: { 422: function() { let msg = 'Invalid Credentials. Please check your Email or Password.'; let state = this.state; state.msg = msg; this.setState(state); } }, success: function (response) { let msg = response.message+'\n Please login again.'; let state = this.state; state.msg = msg; state.success = true; state.msgOpen = true; this.setState(state); }.bind(this), error: function(jqXHR, textStatus, errorThrown) { let msg = 'Failed. Try Again'; if (status === 'timeout') { msg = 'Please check your internet connection'; } let state = this.state; state.msg = msg; state.msgOpen = true; this.setState(state); }.bind(this) }); In our success method of ajax call,  we receive the JSON response in a variable named response and store this in the state in variable msg and set the state of success equal…

Continue ReadingHandling Change of Password of SUSI.AI Account

Change Background of Message Section in SUSI.AI Web Chat

In SUSI.AI Web Chat we pay special attentions to the UI to make it easy to use and attract more users. Many chat apps offer users customization such as changing the colors and background. As this is very popular we decided to give the option to customize the background of message section of SUSI.AI web chat. The UI of the message section had a default gray background which could not be modified by the user. The goal was now to allow the user to customize the look of his or her own client starting with the background of the message section. We added the settings to change the background image to Custom theme menu which occurs only when the user is logged in.The option looks like this: User can add URL of any image of his/her choice and it will be set as the background of message section. Now let’s take a look at the implementation of this option. We added messageBackgroundImage to our state of message section and initialised it to ‘ ’ (empty string). The TextField component looks like this: <TextField name="messageImg" style={{display:component.component==='body'?'block':'none'}} ref={(input) => { this.backImage = input }} onChange={ (e,value)=> this.handleChangeMessageBackground(value) } value={this.state.messageBackgroundImage} floatingLabelText="Message Background Image URL" /> OnChange method handles the input URL and this calls the function handleChangeMessageBackground. This function is having following implementation: handleChangeMessageBackground(backImage){ this.setState({messageBackgroundImage:backImage}); } It sets the messageBackgroundImage equal to the URL entered by the user. Now to change the background of message section we made a custom React style object messageBackgroundStyles: const messageBackgroundStyles = { backgroundImage: `url(${this.state.messageBackgroundImage})`, backgroundRepeat: 'no-repeat', backgroundSize: '100% 100%' } In the above object, backgroundImage: sets the message section background to the image on URL entered by user backgroundImage: Does not allows to the image to be repeated. backgroundSize: Fills the entire message section with the background image. Now this style was added to the component of message section : <div className='message-section'> <ul className='message-list' ref={(c) => { this.messageList = c; }} style={messageBackgroundStyles}> // Styles added // Some relevant code </ul> </div> Now we have following UI, where the user can modify the message section background with image of his or her own choice. User can add any image as background of message section by adding appropriate URL. The image will cover the entire message section without repeating itself. Background Image source:link This gives our user custom look according to his/her own choice. Resources States in React: https://facebook.github.io/react-native/docs/state.html Material-ui TextField: http://www.material-ui.com/#/components/text-field CSS image background properties: https://www.w3schools.com/cssref/playit.asp?filename=playcss_background-size&preval=cover Testing Link http://chat.susi.ai Background Image source of featured image : link

Continue ReadingChange Background of Message Section in SUSI.AI Web Chat

Teaching Susi

In this blog post, I’ll explain how you can add a skill to SUSI. Skills to be added in this tutorial: Ask SUSI for conversion of decimal into Binary . Ask SUSI to tell a quote. Skill development in SUSI is very interesting and easy. A comprehensive guide for skill development can be found here. We have a Susi Skill development environment based on an Etherpad. Are you unaware what an Etherpad is? It is a blank web page where you can just put in your text and everyone can collaborate. Here is a screenshot of what etherpad looks like: open http://dream.susi.ai name a dream (just pick a name for your tests in lower case letters) the etherpad is filled with a welcome message, just delete the content completely Ask SUSI for conversion of decimal into Binary “*” here represents any decimal number number.Suppose we enter a decimal number and want susi to return it’s binary equivalent. So to make our skill, first of all we should form a general query. Query: Convert * into binary or * in binary After defining our query we want susi to reply with relevant answer. For taking out the conversion from decimal to binary we can use JavaScript. We define Javascript syntax in etherpad as follows : !javascript:Binary for $1$ = $!$ (+$1$).toString(2); eol Explanation : “!javascript” allows us to print like javascript console. We can add JavaScript code by using this and do mathematical calculations to convert our decimal into binary. “Binary for $1$ = $!$”  represents output format where $1$ stores the value in “*” in query given by user to susi. $!$ will print the result of javascript code below. “(+$1$).toString(2);” This is a single line javascript code while converts value in “$1$” to binary, It’s output is reflected in “$!$” “eol” represents end of line, which signifies our code for skill is finished. In etherpad our skill would look like : “#” - used for commenting out lines in skill #The following code returns binary equivalent of a decimal number given by user convert * into binary || binary of * !javascript:Binary for $1$ = $!$ (+$1$).toString(2); eol Screenshot: Ask SUSI to tell a quote We can use external API’s for providing answer to user’s query. The external API used for telling the quote is Quotes Rest API created by They Said So. It offers a very good quotes platform and also the quotes are not repeated when several requests are made continuously. We need a query for our skill. Query: Tell me a quote. Now let’s say that we use this JSON response for fetching quote data. Our JSON object looks like: { success: { total: 1 }, contents: { quotes: [ { quote: "Let our advance worrying become advance thinking and planning.", length: "62", author: "Winston Churchill", tags: [ "anxiety", "inspire", "planning", "time-management" ], category: "inspire", date: "2017-05-31", permalink: "https://theysaidso.com/quote/o3aMOSUwOPqUnJv9YyfYHweF/winston-churchill-let-our-advance-worrying-become-advance-thinking-and-planning", title: "Inspiring Quote of the day", background: "https://theysaidso.com/img/bgs/man_on_the_mountain.jpg", id: "o3aMOSUwOPqUnJv9YyfYHweF" } ], copyright: "2017-19 theysaidso.com" } }…

Continue ReadingTeaching Susi

Reset SUSI.AI User Password & Parameter extraction from Link

In this blog I will discuss how does Accounts handle the incoming request to reset the password. If a user forgets his/her password, They can use forgot password button on http://accounts.susi.ai and It’s implementation is quite straightforward. As soon as a user enter his/her e-mail id and hits RESET button, an ajax call is made which first checks if the user email id is registered with SUSI.AI or not. If not, a failure message is thrown to user. But if the user is found to be registered in the database, An email is sent to him/her which contains a 30 characters long token. On the server token is hashed against the user’s email id and a validity of 7 days is set to it. Let us have a look at the Reset Password link one receives. http://accounts.susi.ai/?token={30 characters long token} On clicking this link, what it does is that user is redirected to http://accounts.susi.ai with token as a request parameter. At the client side, A search is made which evaluates whether the URL has a token parameter or not. This was the overview. Since, http://accounts.susi.ai is based on ReactJS framework, it is not easy alike the native php functionality, but much more logical and systematic. Let us now take a closer look at how this parameter is searched for, token extracted and validated. As you can see http://accounts.susi.ai and http://accounts.susi.ai/?token={token}, Both redirect the user to the same URL. So the first task that needs to be accomplished is check if a parameter is passed in the URL or not. First import addUrlProps and UrlQueryParamTypes from react-url-query package and PropTypes from prop-types package. These will be required in further steps. Have a look at the code and then we will understand it’s working. const urlPropsQueryConfig = { token: { type: UrlQueryParamTypes.string }, }; class Login extends Component { static propTypes = { // URL props are automatically decoded and passed in based on the config token: PropTypes.string, // change handlers are automatically generated when given a config. // By default they update that single query parameter and maintain existing // values in the other parameters. onChangeToken: PropTypes.func, } static defaultProps = { token: "null", } Above in the first step, we have defined by what parameter should the Reset Password triggered. It means, if and only if the incoming parameter in the URL is token, we move to next step, otherwise normal http://accounts.susi.ai page should be rendered. Also we have defined the data type of the token parameter to be UrlQueryParamTypes.string. PropTypes are attributes in ReactJS similar to tags in HTML. So again, we have defined the data type. onChangeToken is a custom attribute which is fired whenever token parameter is modified. To declare default values, we have used defaultProps function. If token is not passed in the URL, by default it makes it null. This is still not the last step. Till now we have not yet checked if token is there or not. This is done in the componentDidMount…

Continue ReadingReset SUSI.AI User Password & Parameter extraction from Link

Download SUSI.AI Setting Files from Data Folder

In this blog, I will discuss how the DownloadDataSettings servlet hosted on SUSI server functions. This post also covers a step by step demonstration on how to use this feature if you have hosted your own custom SUSI server and have admin rights to it. Given below is the endpoint where the request to download a particular file has to be made. /data/settings For systematic functionality and workflow, Users with admin login, are given a special access. This allows them to download the settings files and go through them easily when needed. There are various files which have email ids of registered users (accounting.json), user roles associated to them (authorization.json), groups they are a part of (groups.json) etc. To list all the files in the folder, use the given below end point: /aaa/listSettings.json How does the above servlet works? Prior to that, let us see how to to get admin rights on your custom SUSI.AI server. For admin login, it is required that you have access to files and folders on server. Signup with an account and browse to /data/settings/authorization.json Find the email id with which you signed up for admin login and change userRole to “admin”. For example, { "email:test@test.com": { "permissions": {}, "userRole": "user" } } If you have signed up with an email id “test@test.com” and want to give admin access to it, modify the userRole to “admin”. See below. { "email:test@test.com": { "permissions": {}, "userRole": "admin" } } Till now, server did not have any email id with admin login or user role equal to admin. Hence, this exercise is required only for the first admin. Later admins can use changeUserRole application and give/change/modify user roles for any of the users registered. By now you must have admin login session. Let’s see now how the download and file listing servlets work. First, the server creates a path by locally referencing settings folder with the help of DAO.data_dir.getPath(). This will give a string path to the data directory containing all the data-settings files. Now the server just has to make a JSONArray and has to pass a String array to JSONArray’s constructor, which will eventually be containing the name of all the data/settings files. If the process is not successfull ,then, “accepted” = false will be sent as an error to the user. The base user role to access the servlet is ADMIN as only admins are allowed to download data/setting files, The file name which you have to download has to be sent in a HTTP request as a get parameter. For example, if an admin has to download accounting.json to get the list of all the registered users, the request is to be made in the following way: BASE_URL+/data/settings?file=file_name *BASE_URL is the URL where the server is hosted. For standard server, use BASE_URL = http://api.susi.ai. In the initial steps, Server generates a path to data/settings folder and finds the file, name of which it receives in the request. If no filename is specified in the…

Continue ReadingDownload SUSI.AI Setting Files from Data Folder

Password Recovery Link Generation in SUSI with JSON

In this blog, I will discuss how the SUSI server processes requests using JSON when a request for password recovery is made.. The blog post will also cover some parts of the client side implementation as well for better insight. All the clients function in the same way. When you click on forget password button, client asks you that whether you want to recover password for your own custom server or the standard one. This choice of user defines the base URL where to make the forget password request. If you select custom server radio button, then you will be asked to enter the URL to your server, Otherwise standard base URL will be used. The API endpoint used will be /aaa/recoverpassword.json The client will make a POST request with “forgotemail” parameter containing the email id of the user making the request. Following the email id provided, server generates a client identity if and only if the email id is registered. If email id is not found in the database, server throws an exception with error code 422. String usermail = call.get("forgotemail", null); ClientCredential credential = new ClientCredential(ClientCredential.Type.passwd_login, usermail); ClientIdentity identity = new ClientIdentity(ClientIdentity.Type.email, credential.getName()); if (!DAO.hasAuthentication(credential)) { throw new APIException(422, "email does not exist"); } If the email id is found to be registered against a valid user in the database, call to a method is made which returns a random string of length passed in as a parameter. This returned random string acts as a token. Below is the implementation of the createRandomString(int length) method. public static String createRandomString(Integer length){ char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray(); StringBuilder sb = new StringBuilder(); Random random = new Random(); for (int i = 0; i < length; i++) { char c = chars[random.nextInt(chars.length)]; sb.append(c); } return sb.toString(); } This method is defined in AbsractAPIHandler class. The function createRandomString(int length) initialises an array with alphabet (both upper and lower cases) and integers 1 to 10. An object of StringBuilder is declared and initialised in the fol loop. Next, the token generated is hashed against the user’s email id. Since we have used a token of length 30, there will be 30! (30 factorial) combinations and hence chances of two tokens to be same is ver very low (approximately Zero). Validity for token is set for 7 days (i.e. one week). After that the token will expire and to reset the password a new token will be needed. String token = createRandomString(30); ClientCredential tokenkey = new ClientCredential(ClientCredential.Type.resetpass_token, token); Authentication resetauth = new Authentication(tokenkey, DAO.passwordreset); resetauth.setIdentity(identity); resetauth.setExpireTime(7 * 24 * 60 * 60); resetauth.put("one_time", true); Everything is set by now. Only thing left is send a mail to the user. For that we call a method sendEmail() of EmailHandler class. This requires 3 parameters. User email id, subject for the email, and the body of the email. The body contains a verification link. To get this verification link, getVerificationMailContent(String token) is called and token generated in the previous step is sent to it as a parameter.…

Continue ReadingPassword Recovery Link Generation in SUSI with JSON

How to Implement Memory like Servlets in SUSI.AI

In this blog, I'll be discussing about how a server gets previous messages from the Log files in SUSI server. SUSI AI clients, Android, iOS and web chat, follow a very simple rule. Whenever a user logs in to the app, the app makes a http GET call to the server in the background and in response, server returns the chat history. Link to the API endpoint -> http://api.susi.ai/susi/memory.json But parsing a lot of data might depend on the connection speed. If the connection is poor or lacking speed, the history would cost user’s time. To prevent this, server by default returns last 10 pair of messages. It is up to the client that how many messages they want to render. So for example, if the client requests last 5 messages, then the client has to make a GET request and pass the cognitions parameter. Hence the modified end point will be : http://api.susi.ai/susi/memory.json?cognitions=2 But how does the server process it? Let us see. Browse to susi_server/src/ai/susi/server/api/susi/UserService.java file. This is the main working servlet. If you are new and wondering how servlets for susi are implemented, Please go through this first how-to-add-a-new-servletapi-to-susi-server This is how serviceImpl() method looks like : @Override public ServiceResponse serviceImpl(Query post, HttpServletResponse response, Authorization user, final JsonObjectWithDefault permissions) throws APIException { int cognitionsCount = Math.min(10, post.get("cognitions", 10)); String client = user.getIdentity().getClient(); List<SusiCognition> cognitions = DAO.susi.getMemories().getCognitions(client); JSONArray coga = new JSONArray(); for (SusiCognition cognition: cognitions) { coga.put(cognition.getJSON()); if (--cognitionsCount <= 0) break; } JSONObject json = new JSONObject(true); json.put("cognitions", coga); return new ServiceResponse(json); } In the first step, we find the minimum of default value (i.e. 10) and the value of cognitions received as GET parameter. Messages equivalent to minimum variable are encoded in JSONArray and sent to the client. Whenever the server receives a valid signup request, It makes a directory with the name “email_emailid”. In this directory, a log.txt file is maintained which stores all the queries along with the other details associated with it. For example if user has signed up with the email id example@example.com, Then the path of this directory will be /data/susi/email_example@example.com. If a user queries “http://api.susi.ai/susi/chat.json?timezoneOffset=-330&q=flip+a+coin”,  then { "query": "flip a coin", "count": 1, "client_id": "", "query_date": "2017-06-30T12:22:05.918Z", "answers": [{ "data": [{ "0": "flip a coin", "token_original": "coin", "token_canonical": "coin", "token_categorized": "coin", "timezoneOffset": "-330", "answer": "tails", "skill": "/susi_skill_data/models/general/entertainment/en/flip_coin.txt", "_etherpad_dream": "cricket" }, "metadata": { "count": 1 }, "actions": [{ "type": "answer", "expression": "tails" }], "skills": ["/susi_skill_data/models/general/entertainment/en/flip_coin.txt"] }], "answer_date": "2017-06-30T12:22:05.928Z", "answer_time": 10, "language": "en" } The server has user’s identity. It will use this identity and store (will be appended) in the respective log file. The next steps in retrieving the message are pretty easy and includes getting the identity of the current user session. Use this identity to populate the JSONArray named coga. This is finally encoded in a JSONObject along with other basic details and returned to the clients where they render the data received, and show the messages in an appropriate way. Resources Start your own Jetty Server How…

Continue ReadingHow to Implement Memory like Servlets in SUSI.AI

Implementation of Responsive SUSI Web Chat Search Bar

When we were building the first phase of the SUSI Web Chat Application we didn’t consider about  the responsiveness as a main goal. The main thing we needed was a working application. This changed at a later stage. In this post I’m going to emphasize how we implemented the responsive design and problems we met while we were developing the design. When we were moving to Material-UI from static HTML CSS we were able to make most of the parts responsive. As an example App-bar of the application. We added App-bar like as follows: We made a separate component for App-bar and it includes the “searchfield” element. Material-UI app bar handles the responsiveness for some extent. We have to handle responsiveness of other sub-parts of the app bar manually. In “TopBar.react.js” I returned marterial-ui <Toolbar> element like this. <Toolbar> <ToolbarGroup > </ToolbarGroup> <ToolbarGroup lastChild={true}> //inside of this we have to include other components of the top bar inside this element </ToolbarGroup> </Toolbar> We have to add the search button inside the element. In this we added search component. This field has the ability to expand and collapse like this. It looks good. But it appears on mobile screen in a different way. This is how it appears on mobile devices. So we wanted to hide the SUSI logo on small sized screens. For that we wrote medial queries like this. @media only screen and (max-width: 860px){ .app-bar-search{ background-image: none; } .search{ width: 100px !important; } } Even in smaller screens it appears like this. To avoid that we minimized the width of the search bar in different screen sizes using media queries . @media only screen and (max-width: 480px){ .search{ width: 100px !important; } } @media only screen and (max-width: 360px){ .search{ width: 65px !important; } } But in even smaller screens it still appears in the same way. We can't show the search bar on small screens because the screen size is not enough to show the search bar. So we wrote another media query to hide all the elements of search component in small screens except close button. Because when we collapse the screen on search mode it hides all the search components and messagecomposer. To take it back to the chat mode we have to enable the close button on smaller screens. @media only screen and (max-width: 300px){ .displayNone{ display: none !important; } .displayCloseNone{ position: relative; top:6px !important; } } We have to define these two classes in “SearchField.react.js” file. <IconButton className='displayNone' <SearchIcon /> </IconButton> <TextField name='search' className='search displayNone' placeholder="Search..."/> <IconButton className='displayNone'> <UpIcon /> </IconButton> <IconButton className='displayNone'> <DownIcon /> </IconButton> <IconButton className='displayCloseNone'> <ExitIcon /> </IconButton> Since we have “Codacy” integrated into our Github Repository we have to change Codacy rules because we used “!important” in media queries to override inline style which comes from Material-UI. To change codacy rules we can simply login to the codacy and select the “code pattern ” button from the column left side. It shows the list of rules that Codacy checks.…

Continue ReadingImplementation of Responsive SUSI Web Chat Search Bar