Showing sample queries in SUSI.AI Bots

We need to give the user a good start to their chat with SUSI.AI. Engaging the users with some good skills at the start of the conversation, can leave a good impression about SUSI.AI. In SUSI messenger bots, we show up with some sample queries to try, during the conversation with SUSI.AI. In this blog, SUSI_Tweetbot and SUSI_FBbot are used as examples.

These queries are shown as quick replies i.e. the user can click on any of these sample queries and get an answer from SUSI.AI.  

Facebook:

When the user clicks on the “Start chatting” button, we send a descriptive message on what can the user ask to SUSI.AI .

Code snippet used for this step is:

var queryUrl = 'http://api.susi.ai/susi/chat.json?q='+'Start+chatting';
var startMessage = '';
// Wait until done and reply
request({
        url: queryUrl,
        json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
        startMessage = body.answers[0].actions[0].expression;
    }
else{
    startMessage = errMessage;
    }
sendTextMessage(sender, startMessage, 0);

Just a text message is not much engaging. To further enhance the experience of the user, we show some quick reply options to the user. We have finalized some skills to show to the user:

Due to the character limit for the text shown on buttons, we try to show short queries as shown in the above picture. This way the user gets an idea about what type of queries can be asked.

Generic template, help us achieve this feature in SUSI_FBbot.

The code snippet used:

var messageT = {
               "type": "template",
               "payload": {
                "template_type": "generic",
                "elements": [{
                                    "title": 'You can try the following:',
                                    "buttons": [{                                               
                                               "type":"postback",
                                               "title":"What is FOSSASIA?",                                  
                                               "payload":"What is FOSSASIA?"            
                                            }]
                            }]
                }
            };
sendTextMessage(sender, messageT, 1);

As seen in the code above, each button has a corresponding postback text. So that whenever that button is clicked the postback text is sent to our chat automatically:

This postback text acts as a query to SUSI API which fetches the response from the server and shows it back to the user.

Twitter:

As SUSI.AI bots must be generic among all the messenger platforms available , we will inculcate the same skills available in SUSI_FBbot to SUSI_Tweetbot. The quick reply feature provided by Twitter devs help us to accomplish this task at hand.

As in SUSI_FBbot a descriptive message is shown to the users first and then some quick reply options following it.

Message_create event helps in adding quick replies:

var msg = {
               "event": {
               "type": "message_create",
               "message_create": {
                   "target": {
                       "recipient_id": senderId
                    },
                    "message_data": {
                        "text": "You can try the following:",
                        "quick_reply": {
                            "type": "options",
                            "options": [{
                                "label": "What is FOSSASIA?",
                                "metadata": "external_id_4"
                            }]
                        }
                    }
                }
           }
    };
T.post('direct_messages/events/new', msg, sent);

One thing to keep in mind while coding is to send the quick reply message after the initial descriptive message i.e. the code used to send quick replies should be written inside the function, which sends the descriptive message first and aafter that step is complete it runs the code for quick replies. If we accidentally write quick reply code outside that function, it’s highly likely to find bugs in the replies by SUSI.AI.

Resources

  1. Speed up customer service with quick replies and welcome messages by Ian Cairns from Twitter blog.
  2. Link Ads to Messenger, Enhanced Mobile Websites, Payments and More by Seth Rosenberg from Facebook developers blog

Making SUSI.AI reach more users through messenger bots

SUSI.AI learns from the queries asked to it by the users. More are the number of queries asked, the better is the learning by SUSI.AI. More are the number of users involved with SUSI.AI, better is the amount of content available to SUSI to learn from. Now, the challenge in front of us is to indulge more users with SUSI.AI. In this blog post, SUSI Tweetbot and SUSI FBbot are used as examples to show how we increase our user base through messenger bots.

Twitter:

Twitter has a 328 million user base according to this article’s data. Integration of SUSI.AI to just Twitter makes it available to around 300 million users. Even if some percentage of these users start using SUSI.AI, increase in the user base of SUSI.AI could be exponential. Increasing the user base is advantageous as it provides with more training data for SUSI.AI to learn from.

Sharing by public tweet:

Integrating to it is just the first step towards increasing SUSI.AI’s user base. Next step is to reach the users and indulge them into chatting with SUSI.AI.

Suppose a user asked something to SUSI.AI and really liked the reply from it. He/she wants to share it with his/her followers. This can prove to be a golden opportunity for us, to increase the reach of SUSI.AI. This way we can indulge their friends to try SUSI.AI and have an amazing time with it.

It becomes clear that sharing messages is an indispensable feature and can help us a lot. Twitter doesn’t provide sharing with friends through direct messages but with a feature much better than it. We can share that message as a public tweet and cover more users including the followers of the user.

 

To show this button we use Call to action support by twitter:

"message_data": {
          "text": txt,
          "ctas": [{
                      "type": "web_url",
                      "label": "Share with your followers",
                      "url": ""
          }]
}

The url key in the above code must have a value that redirects to a U.I. that allows to publicly tweet this reply by SUSI.AI.

Using this “https://twitter.com/intent/tweet?text=” as the url value shows a new page with an empty tweet message, as the text query in the url has no value. We set the text field with a value such that we end up like this:

and after tweeting it:

Sending a direct message link with the tweet:

Twitter provides with a lot of features when sending direct messages. Shifting a user from tweets to direct messages is beneficial in a way that we can efficiently tell the user about the capabilities of SUSI.AI and show important links to him/her like of its repository, web chat client etc.

When a user tweets to the SUSI.AI page with a query, we reply with a tweet back to the user. Along with that, we provide a link to privately message SUSI.AI account if the user wants to.

This way if user ends up visiting SUSI.AI in a chat window:

To achieve this in SUSI Tweetbot, Twitter provides with a direct message url. This url – https://twitter.com/messages/compose?recipient_id= redirects us to the chat window of the account having that recipient id, passed as a query string. In our case the url turns out to be – https://twitter.com/messages/compose?recipient_id=871446601000202244 as “871446601000202244” is the recipient id of @SusiAI1 account on twitter.

If we send this url as a text in our “tweet back” to the user, Twitter beautifully shows it as a clickable button with the label as “send a private message” as shown above.

Hence we call the tweet function like this:

tweetIt('@' + from + ' ' + message + date +"\nhttps://twitter.com/messages/compose?recipient_id=871446601000202244");

Facebook:

As we all know Facebook is the giant of social networking sites. Integrating SUSI.AI to Facebook is beneficial for us.

Unlike Twitter, in Facebook we can share messages with other friends through direct messaging to them. The last topic of this blog post walks you through on adding sharing feature in SUSI FBbot:

We also take advantage from the SUSI FBbot to make SUSI.AI better. We can direct the users using SUSI messenger bots to SUSI.AI repository and show them all the required information on how to contribute to the project.

The best way to do this is by showing a “How to contribute” button when the user clicks on “Get started” in the messenger bot.

This blog post will help you showing buttons along with the replies (by SUSI.AI).

When the user clicks this button, we send two messages back to the user as shown:

This way through bots, we have somehow got the user to visit SUSI.AI repository and contribute to it or indulge him/her in the discussion, through our Gitter channel on what can be the next steps in improving SUSI.AI.

Resources:

  1. Link Ads to Messenger, Enhanced Mobile Websites, Payments and More by Seth Rosenberg from Facebook developers blog.
  2. Drive discovery of bots and other customer experiences in direct messages by  Travis Lull from Twitter blog.

Skill Editor in SUSI Skill CMS

SUSI Skill CMS is a web application built on ReactJS framework for creating and editing SUSI skills easily. It follows an API centric approach where the SUSI Server acts as an API server. In this blogpost we will see how to add a component which can be used to create a new skill SUSI Skill CMS.

For creating any skill in SUSI we need four parameters i.e model, group, language, skill name. So we need to ask these 4 parameters from the user. For input purposes we have a common card component which has dropdowns for selecting models, groups and languages, and a text field for skill name input.

<SelectField
    floatingLabelText="Model"
    value={this.state.modelValue}
    onChange={this.handleModelChange}
>
    {models}
</SelectField>
<SelectField
    floatingLabelText="Group"
    value={this.state.groupValue}
    onChange={this.handleGroupChange}
>
    {groups}
</SelectField>
<SelectField
    floatingLabelText="Language"
    value={this.state.languageValue}
    onChange={this.handleLanguageChange}
>
    {languages}
</SelectField>
<TextField
    floatingLabelText="Enter Skill name"
    floatingLabelFixed={true}
    hintText="My SUSI Skill"
    onChange={this.handleExpertChange}
/>
<RaisedButton label="Save" backgroundColor="#4285f4" labelColor="#fff" style={{marginLeft:10}} onTouchTap={this.saveClick} />

This is the card component where we get the user input. We have API endpoints on SUSI Server for getting the list of models, groups and languages. Using those APIs we inflate the dropdowns.
Then the user needs to edit the skill. For editing of skills we have used Ace Editor. Ace is an code
editor written in pure javascript. It matches the features native editors like Sublime and TextMate.

To use Ace we need to install the component.

npm install react-ace --save                        

This command will install the dependency and update the package.json file in our project with this dependency.

To use this editor we need to import AceEditor and place it in the render function of our react class.

<AceEditor
    mode=" markup"
    theme={this.state.editorTheme}
    width="100%"
    fontSize={this.state.fontSizeCode}
    height= "400px"
    value={this.state.code}
    name="skill_code_editor"
    onChange={this.onChange}
    editorProps={{$blockScrolling: true}}
/>

Now we have a page that looks something like this

Now we need to handle the click event when a user clicks on the save button.

First we check if the user is logged in or not. For this we check if we have the required cookies and the access token of the user.

 if(!cookies.get('loggedIn')) {
            notification.open({
                message: 'Not logged In',
                description: 'Please login and then try to create/edit a skill',
                icon: <Icon type="close-circle" style={{ color: '#f44336' }} />,
            });
            return 0;
        }

If the user is not logged in then we show him a error notification and asks him to login.

Then we check if he has filled all the required fields like name of the skill etc. and after that we call an API Endpoint on SUSI Server that will finally store the skill in the skill_data_repo.

let url= “http://api.susi.ai/cms/modifySkill.json”
$.ajax({
    url:url,
    dataType: 'jsonp',
    jsonp: 'callback',
    crossDomain: true,
    success: function (data) {
        console.log(data);
        if(data.accepted===true){
            notification.open({
                message: 'Accepted',
                description: 'Your Skill has been uploaded to the server',
                icon: <Icon type="check-circle" style={{ color: '#00C853' }} />,
            });
           }
    }
});

In the success function of ajax call we check if accepted parameter is true from the server or not. If accepted is true then we show user a notification with a message that “Your Skill has been uploaded to the server”.

To see this component running please visit http://skills.susi.ai/skillEditor.

Resources

Material-UI: http://www.material-ui.com/

Ace Editor: https://github.com/securingsincity/react-ace

Ajax: http://api.jquery.com/jquery.ajax/

Universal Cookies: https://www.npmjs.com/package/universal-cookie

Uploading Images to SUSI Server

SUSI Skill CMS is a web app to create and modify SUSI Skills. It needs API Endpoints to function and SUSI Server makes it possible. In this blogpost, we will see how to add a servlet to SUSI Server to upload images and files.

The CreateSkillService.java file is the servlet which handles the process of creating new Skills. It requires different user roles to be implemented and hence it extends the AbstractAPIHandler.

Image upload is only possible via a POST request so we will first override the doPost method in this servlet.

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  resp.setHeader("Access-Control-Allow-Origin", "*"); // enable CORS

resp.setHeader enables the CORS for the servlet. This is required as POST requests must have CORS enables from the server. This is an important security feature that is provided by the browser.

        Part file = req.getPart("image");
        if (file == null) {
            json.put("accepted", false);
            json.put("message", "Image not given");
        }

Image upload to servers is usually a Multipart Request. So we get the part which is named as “image” in the form data.

When we receive the image file, then we check if the image with the same name exists on the server or not.

Path p = Paths.get(language + File.separator + “images/” + image_name);

        if (image_name == null || Files.exists(p)) {
                json.put("accepted", false);
                json.put("message", "The Image name not given or Image with same name is already present ");
            }

If the same file is present on the server then we return an error to the user requesting to give a unique filename to upload.

Image image = ImageIO.read(filecontent);
BufferedImage bi = this.createResizedCopy(image, 512, 512, true);
if(!Files.exists(Paths.get(language.getPath() + File.separator + "images"))){
   new File(language.getPath() + File.separator + "images").mkdirs();
           }
ImageIO.write(bi, "jpg", new File(language.getPath() + File.separator + "images/" + image_name));

Then we read the content for the image in an Image object. Then we check if images directory exists or not. If there is no image directory in the skill path specified then create a folder named “images”.

We usually prefer square images at the Skill CMS. So we create a resized copy of the image of 512×512 dimensions and save that copy to the directory we created above.

BufferedImage createResizedCopy(Image originalImage, int scaledWidth, int scaledHeight, boolean preserveAlpha) {
        int imageType = preserveAlpha ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
        BufferedImage scaledBI = new BufferedImage(scaledWidth, scaledHeight, imageType);
        Graphics2D g = scaledBI.createGraphics();
        if (preserveAlpha) {
            g.setComposite(AlphaComposite.Src);
        }
        g.drawImage(originalImage, 0, 0, scaledWidth, scaledHeight, null);
        g.dispose();
        return scaledBI;
    }

The function above is used to create a  resized copy of the image of specified dimensions. If the image was a PNG then it also preserves the transparency of the image while creating a copy.

Since the SUSI server follows an API centric approach, all servlets respond in JSON.

       resp.setContentType("application/json");
       resp.setCharacterEncoding("UTF-8");
       resp.getWriter().write(json.toString());’

At last, we set the character encoding and the character set of the output. This helps the clients to parse the data easily.

To see this endpoint in live send a POST request at http://api.susi.ai/cms/createSkill.json.

Resources

Apache Docs: https://commons.apache.org/proper/commons-fileupload/using.html

Multipart POST Request Tutorial: http://www.codejava.net/java-se/networking/upload-files-by-sending-multipart-request-programmatically

Java File Upload tutorial: https://ursaj.com/upload-files-in-java-with-servlet-api

Jetty Project: https://github.com/jetty-project/

How to Implement Feedback System in SUSI iOS

The SUSI iOS app provides responses for various queries but the response is always not accurate. To improve the response, we make use of the feedback system, which is the first step towards implementing Machine Learning on the SUSI Server. The way this works is that for every query, we present the user with an option to upvote or downvote the response and based on that a positive or negative feedback is saved on the server. In this blog, I will explain how this feedback system was implemented in the SUSI iOS app.

Steps to implement:

We start by adding the UI which is two buttons, one with a thumbs up and the other with a thumbs down image.

textBubbleView.addSubview(thumbUpIcon)
textBubbleView.addSubview(thumbDownIcon)
textBubbleView.addConstraintsWithFormat(format: "H:[v0]-4-[v1(14)]-2-[v2(14)]-8-|", views: timeLabel, thumbUpIcon, thumbDownIcon)
textBubbleView.addConstraintsWithFormat(format: "V:[v0(14)]-2-|", views: thumbUpIcon)
textBubbleView.addConstraintsWithFormat(format: "V:[v0(14)]-2-|", views: thumbDownIcon)
thumbUpIcon.isUserInteractionEnabled = true
thumbDownIcon.isUserInteractionEnabled = true

Here, we add the subviews and assign constraints so that these buttons align to the bottom right next to each other. Also, we enable the user interaction for these buttons.

We know that the user can rate the response by pressing either of the buttons added above. To do that we make an API call to the endpoint below:

BASE_URL+'/cms/rateSkill.json?'+'model='+model+'&group='+group+'&skill='+skill+’&language’+language+’&rating=’+rating

Here, the BASE_URL is the url of the server, the other three params model, group, language and skill are retrieved by parsing the skill location parameter we get with the response. The rating is positive or negative based on which button was pressed by the user. The skill param in the response looks like this:

skills:
[
"/susi_skill_data/models/general/entertainment/en/quotes.txt"
]

Let’s write the method that makes the API call and responds to the UI that it was successful.

if let accepted = response[ControllerConstants.accepted] as? Bool {
  if accepted {
    completion(true, nil)
    return
  }
  completion(false, ResponseMessages.ServerError)
  return
}

Here after receiving a response from the server, we check if the `accepted` variable is true or not. Based on that, we pass `true` or `false` to the completion handler. Below the response we actually receive by making the request.

{
session: {
identity: {
type: "host",
name: "23.105.140.146",
anonymous: true
}
},
accepted: true,
message: "Skill ratings updated"
}

Finally, let’s update the UI after the request has been successful.

if sender == thumbUpIcon {
thumbDownIcon.tintColor = UIColor(white: 0.1, alpha: 0.7)
thumbUpIcon.isUserInteractionEnabled = false
thumbDownIcon.isUserInteractionEnabled = true
feedback = "positive"
} else {
thumbUpIcon.tintColor = UIColor(white: 0.1, alpha: 0.7)
thumbDownIcon.isUserInteractionEnabled = false
thumbUpIcon.isUserInteractionEnabled = true
feedback = "negative"
}
sender.tintColor = UIColor.hexStringToUIColor(hex: "#2196F3")

Here, we check the sender (the thumbs up or down button) and based on that pass the rating (positive or negative) and update the color of the button.

Below is the app in action with the feedback system.

Resources:

Advanced functionality in SUSI Tweetbot

SUSI AI is integrated to Twitter (blog). During the initial phase, SUSI Tweetbot had basic UI and functionalities like just “plain text” replies. Twitter provides with many more features like quick replies i.e. presenting to the user with some choices to choose from or visiting SUSI server repository by just clicking buttons during the chat etc.

All these features are provided to enhance the user experience with our chatbot on Twitter.

This blog post walks you through on adding these functionalities to the SUSI Tweetbot:

  1. Quick replies
  2. Buttons

    Quick replies:

    This feature provides options to the user to choose from.

    The user doesn’t need to type the next query but rather select a quick reply from the options available. This speeds up the process and makes it easy for the user. Also, it helps developers know all the possible queries which can come next, from the user. Hence, it helps in efficient coding on how to handle those queries.In SUSI Tweetbot this feature is used to welcome a new user to the SUSI A.I.’s chat window, as shown in the image above. The user can select any option among “Get started” and “Start chatting”.The “Get started” option is basically for introduction of SUSI A.I. to the user. While, “Start chatting” when clicked shows the user of what all queries the user can try.Let’s come to the code part on how to show these options and what events happen when a user selects one of the options.

    To show the Welcome message, we call SUSI API with the query as string “Welcome” and store the reply in message variable. The code snippet used:

var queryUrl = 'http://api.susi.ai/susi/chat.json?q=Welcome';
var message = '';
request({
    url: queryUrl,
    json: true
}, function (err, response, data) {
    if (!err && response.statusCode === 200) {
        message = data.answers[0].actions[0].expression;
    } 
    else {
        // handle error
    }
});

To show options with the message:

var msg =  {
        "welcome_message" : {
                    "message_data": {
                        "text": message,
                        "quick_reply": {
                              "type": "options",
                              "options": [
                                {
                                  "label": "Get started",
                                  "metadata": "external_id_1"
                                },
                                {
                                  "label": "Start chatting",
                                  "metadata": "external_id_2"
                                }
                              ]
                            }
                    }
                      }
       };
T.post('direct_messages/welcome_messages/new', msg, sent);

The line T.post() makes a POST request to the Twitter API, to register the welcome message with Twitter for our chatbot. The return value from this request includes a welcome message id in it corresponding to this welcome message.

We set up a welcome message rule for this welcome message using it’s id. By setting up the rule is to set this welcome message as the default welcome message shown to new users. Twitter also provides with custom welcome messages, information about which can be found in their official docs.

The welcome message rule is set up by sending the welcome message id as a key in the request body:

var welcomeId = data.welcome_message.id;
var welcomeRule = {
            "welcome_message_rule": {
                "welcome_message_id": welcomeId
            }
};
T.post('direct_messages/welcome_messages/rules/new', welcomeRule, sent);

Now, we are all set to show the new users with a welcome message.

Buttons:

Let’s go a bit further. If the user clicks on the option “Get started”, we want to show a basic introduction of SUSI A.I. to the user. Along with that we provide some buttons to visit the SUSI A.I. repository or experience chatting with SUSI A.I. on the web client.

The procedure followed to show buttons is almost the same as followed in case of options.This doc by Twitter proves to be helpful to get familiar with buttons.

As soon as a person clicks on “Get started” option, Twitter sends a message to our bot with the query as “Get started”.

For the message part, we call SUSI API with the query as string “Get started” and store the reply in a message variable. The code snippet used:

var queryUrl = 'http://api.susi.ai/susi/chat.json?q=Get+started';
var message = '';
request({
    url: queryUrl,
    json: true
}, function (err, response, data) {
    if (!err && response.statusCode === 200) {
        message = data.answers[0].actions[0].expression;
    } 
    else {
        // handle error
    }
});

Both the buttons to be shown with the message should have a corresponding url. So that after clicking the button a person is redirected to that url in a new browser tab.

To show buttons a simple message won’t help, we need to create an event. This event constitutes of our message and buttons. The buttons are referred to as call-to-action i.e. CTAs by Twitter dev’s. The maximum number of buttons in an event can not be more than three in number.

The code used to make an event in our case:

var msg = {
        "event": {
                "type": "message_create",
                "message_create": {
                              "target": {
                                "recipient_id": sender
                              },
                              "message_data": {
                                "text": message,
                                "ctas": [
                                  {
                                    "type": "web_url",
                                    "label": "View Repository",
                                    "url": "https://www.github.com/fossasia/susi_server"
                                  },
                                  {
                                    "type": "web_url",
                                    "label": "Chat on the web client",
                                    "url": "http://chat.susi.ai"
                                  }
                                ]
                              }
                            }
            }
        };

T.post('direct_messages/events/new', msg, sent);

The line T.post() makes a POST request to the Twitter API, to send this event to the concerned recipient guiding them on how to get started with the bot.

Resources:

  1. Speed up customer service with quick replies and welcome messages by Ian Cairns from Twitter blog.
  2. Drive discovery of bots and other customer experiences in direct messages by Travis Lull from Twitter blog.

Encoding and Decoding Images as Data in UserDefaults in SUSI iOS

In this blog post, I will be explaining how to encode and decode images and save them in UserDefaults so that the image persists even if it is removed from the Photos app. It happens a number of times that images are removed from the gallery by the users which results in the app loosing the image. So, to avoid this, we save the image by encoding it in a data object and save it inside UserDefaults. In SUSI iOS app we simply select an image from the image picker, encode it and save it in UserDefaults. To set the image, we simply fetch the image data from the UserDefaults and decode it to an image.

There are two ways we can do the encoding and decoding process:

  • Using Data object
  • Using Base64 string

For the scope of this tutorial, we will use the Data object.

Implementation Steps

  1. To use the image picker, we need to add permissions to `Info.plist` file.
<key>NSLocationWhenInUseUsageDescription</key>
<string>Susi is requesting to get your current location</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Susi needs to request your gallery access to select wallpaper</string>
  1. Select image from gallery

First, we present an alert which gives an option to select the image from the gallery.

// Show wallpaper options to set wallpaper or clear wallpaper
func showWallpaperOptions() {
  let imageDialog = UIAlertController(title: ControllerConstants.wallpaperOptionsTitle, message: nil, preferredStyle: UIAlertControllerStyle.alert)
  imageDialog.addAction(UIAlertAction(title: ControllerConstants.wallpaperOptionsPickAction, style: .default, handler: { (_: UIAlertAction!) in
  imageDialog.dismiss(animated: true, completion: nil)
  self.showImagePicker()
  }))
  imageDialog.addAction(UIAlertAction(title: ControllerConstants.wallpaperOptionsNoWallpaperAction, style: .default, handler: { (_: UIAlertAction!) in
    imageDialog.dismiss(animated: true, completion: nil)
    self.removeWallpaperFromUserDefaults()
  }))
  imageDialog.addAction(UIAlertAction(title: ControllerConstants.dialogCancelAction, style: .cancel, handler: { (_: UIAlertAction!) in
    imageDialog.dismiss(animated: true, completion: nil)
  }))
  self.present(imageDialog, animated: true, completion: nil)
}

Here, we create and UIAlertController with three options to select, one which presents the image picker controller, the second one removes the background wallpaper and the third dismisses the alert.

  1. Set the image as background view
// Callback when image is selected from gallery
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
  dismiss(animated: true, completion: nil)
  let chosenImage = info[UIImagePickerControllerOriginalImage] as? UIImage
  if let image = chosenImage {
    setBackgroundImage(image: image)
  }
}

We use the `didFinishPickingMediaWithInfo` delegate method to set the image as background. First we get the image using the the `info` dictionary using the `UIImagePickerControllerOriginalImage` key.

  1. Save the image in UserDefaults (encoding)
// Save image selected by user to user defaults
func saveWallpaperInUserDefaults(image: UIImage!) {
  let imageData = UIImageJPEGRepresentation(image!, 1.0)
  let defaults = UserDefaults.standard
  defaults.set(imageData, forKey: userDefaultsWallpaperKey)
}

We first convert the image to a data object using the `UIImageJPEGRepresentation` method followed by saving the data object in UserDefaults with the key `wallpaper`.

  1. Decode the data object back to UIImage 

Now whenever we need to decode the image, we simply get the data object from the UserDefaults and use it to display the image.

// Check if user defaults have an image data saved else return nil/Any
func getWallpaperFromUserDefaults() -> Any? {
  let defaults = UserDefaults.standard
  return defaults.object(forKey: userDefaultsWallpaperKey)
}

Below is the output when an image is selected and displayed as a background.

Resources:

Using The Dark and Light Theme in SUSI iOS

SUSI being an AI for interactive chat bots, provides answers to the users in the intelligent way. So, to make the SUSI iOS app more user friendly, the option of switching between themes was introduced. This also enables the user switch between themes based on the environment around. Any user can switch between the light and dark themes easily from the settings.

We start by declaring an enum called `theme` which contains two strings namely, dark and light.

enum theme: String {
    case light
    case dark
}

We can update the color scheme based on the theme selected very easily by checking the currently active theme and based on that check, we update the color scheme. To check the currently active theme, we define a variable in the `AppDelegate` which holds the value.

var activeTheme: String?

Below is the way the color scheme of the LoginViewController is set.

var activeTheme: String?func setupTheme() {
  let image = UIImage(named: ControllerConstants.susi)?.withRenderingMode(.alwaysTemplate)
  susiLogo.image = image
  susiLogo.tintColor = .white
  UIApplication.shared.statusBarStyle = .lightContent
  let activeTheme = AppDelegate().activeTheme
  if activeTheme == theme.light.rawValue {
    view.backgroundColor = UIColor.lightThemeBackground()
  } else if activeTheme == theme.dark.rawValue {
    view.backgroundColor = UIColor.darkThemeBackground()
  }
}

Here, we first get the image and set the rendering mode to `alwaysTemplate` so that we can change the tint color of the image. Next, we assign the image to the `IBOutlet` and change the tint color to `white`. We also change the status bar style to `lightContent`. Next, we check the active theme and change the view’s background color accordingly. For this method to execute, we call it inside, `viewDidLoad` so that the theme loads up as the view loads.

Next, lets add this option of switching between themes inside the `SettingsViewController`. We add a cell with `titleLabel` as `Change Theme` and use the collectionView’s delegate method of `didSelect` to show an alert. This alert contains three options, Dark theme, Light Theme and Cancel. Let’s code that method which presents the alert.

func themeToggleAlert() {
  let imageDialog = UIAlertController(title: ControllerConstants.toggleTheme, message: nil, preferredStyle: UIAlertControllerStyle.alert)
  imageDialog.addAction(UIAlertAction(title: theme.dark.rawValue.capitalized, style: .default, handler: { (_: UIAlertAction!) in
    imageDialog.dismiss(animated: true, completion: nil)
    AppDelegate().activeTheme = theme.dark.rawValue
    self.settingChanged(sender: self.imagePicker)
    self.setupTheme()
  }))
  imageDialog.addAction(UIAlertAction(title: theme.light.rawValue.capitalized, style: .default, handler: { (_: UIAlertAction!) in
    imageDialog.dismiss(animated: true, completion: nil)
    AppDelegate().activeTheme = theme.light.rawValue
    self.settingChanged(sender: self.imagePicker)
    self.setupTheme()
  }))
  imageDialog.addAction(UIAlertAction(title: ControllerConstants.dialogCancelAction, style: .cancel, handler: { (_: UIAlertAction!) in
    imageDialog.dismiss(animated: true, completion: nil)
  }))
  self.present(imageDialog, animated: true, completion: nil)
}

Here, we assign the alert view’s title and add 3 actions and their respective completion handlers. If we see inside these completion handlers, we can notice that we first dismiss the alert followed by updating the activeTheme variable in AppDelegate and call the `settingChanged` function which updates the user’s settings on the server. Finally, we update the color scheme.

Now, if we build and run the app and change the theme from the settings, we will notice that on returning to the chat view, the color scheme is not updated. The reason here is that we are setting up the theme on viewDidLoad which loads only once and is not executed until the controller is presented again. Here, we make use of the `viewDidAppear` method which executes every time the view appears.

override func viewDidAppear(_ animated: Bool) {
  super.viewDidAppear(animated)
  setupTheme()
}

To persist the selected theme, we used the UserDefaults to save the theme which got assigned everytime to the `activeTheme` variable when the app loads up.

UserDefaults.standard.set(AppDelegate().activeTheme, forKey: ControllerConstants.UserDefaultsKeys.theme)

On app launch, we assigned this User Default the value of the light theme as a default.

Below is the final output:

References:

Advanced functionality in SUSI FBbot

SUSI AI is integrated to Facebook (blog). During the initial phase, SUSI FBbot had basic UI and functionalities like just “plain text” replies. Facebook provides many more features like replies enclosed in templates (blog link), sharing the replies by SUSI A.I. with friends, get started button or a persistent menu to show quick reply options to the user etc. All these features to enhance the user experience with SUSI AI chatbot.

This blog post walks you through on adding these functionalities to the SUSI FBbot:

Adding Get Started button

A Get Started button is added to the SUSI FBbot to give the user a brief introduction about SUSI AI and what the user can try next.

Clicking on the get started button , will send the message as “Get Started” to the SUSI FBbot:

The reply message, provides the user with options to visit SUSI A.I. repository or to just start chatting.

To have this button in our bot, we use this code snippet:

// Add a get started button to the messenger
request({
    url: 'https://graph.facebook.com/v2.6/me/messenger_profile',
    qs: {access_token:token},
    method: 'POST',
    json: { 
      "get_started":{
        "payload":"GET_STARTED_PAYLOAD"
      }
    }
}, function(error, response, body) {
    // handle errors and response here
})

When a user clicks this button, a postback is sent to the webhook of SUSI FBbot with payload as “GET_STARTED_PAYLOAD”. When we receive such postback, we reply with a message as shown above using generic template.

Adding a persistent menu to the bot

If not at the start, while chatting with SUSI AI for sometime, it is quite possible that the user becomes curious to visit the repository of SUSI A.I. . So we need a quick access to the “Visit repository” button all the time. Persistent menu, helps us with this feature:

This way it is accessible at each point of time. Some other buttons can also be added to the menu like “Latest News” as shown in the image above.

To have a persistent menu for the SUSI FBbot, the following code snippet is used:

request({
        url: 'https://graph.facebook.com/v2.6/me/messenger_profile',
        qs: {access_token:token},
        method: 'POST',
        json: {
                "persistent_menu":[{
                    "locale":"default", 
                        "composer_input_disabled":false,
                        "call_to_actions":[{
                            "type":"web_url",
                            "title":"Visit Repository",
                            "url":"https://github.com/fossasia/susi_server",
                            "webview_height_ratio":"full"
                        }]
                 }]
            }
    }, function(error, response, body) {
        // handle errors and response
    })

We can add more buttons to the menu. JSON object having the required properties of that button can be appended to the key “call_to_actions” to do so.

Adding a messenger code to join SUSI FBbot

To enable Facebook users to chat with SUSI AI by scanning a code through messenger. This feature is added to the bot by making the following POST request:

request({
        url: 'https://graph.facebook.com/v2.6/me/messenger_codes',
        qs: {access_token:token},
        method: 'POST',
        json: {
                type: "standard",
                image_size: 1000
        }
    }, function(error, response, body) {
        // handle errors and response.
});

Adding message sharing feature

To increase the reach of SUSI A.I. to more users on Facebook, message sharing proves to be a big boon. The reply by SUSI A.I. to users can be shared with their friends. Along with the message we can also send a promotional message(related to SUSI A.I.), to the people with which the message was shared.

This sharing can end up having more users trying SUSI A.I., leading to increase the user base of SUSI AI and its popularity.

We can allow sharing of just the message(i.e. the reply) or a promotional message with it. In case of just the reply:

Clicking the share button, will share just the reply with another person. To add capabilities of sharing the reply along with one more message(prompting to try SUSI A.I.), some changes to the code are done:

We need to set the buttons property in generic template like:

buttons : [
            {
                "type":"element_share",
                    "share_contents": { 
                      "attachment": {
                        "type": "template",
                        "payload": {
                          "template_type": "generic",
                          "elements": [
                            {
                              "title": "I had an amazing chat with SUSI.",
                              "buttons": [
                                {
                                  "type": "web_url",
                                  "url": "https://m.me/asksusisu", 
                                  "title": "Chat with SUSI AI"
                                }
                              ]
                            }
                          ]
                        }
                      }
                   }
            } 
       ];

This way when a user shares the message with other, an extra message is sent with the original message, tempting the user to try a chat with SUSI A.I.:

Resources:

  1. By Seth Rosenberg from Facebook developers blogLink Ads to Messenger, Enhanced Mobile Websites, Payments and More.
  2. By Slobodan Stojanović from smashing magazineDevelop a chat bot with node js.