The SUSI.AI server responds to API calls with answers to the queries made. These answers might contain an action, for example a web search, where the client needs to make a web search request to fetch different web pages based on the query. Thus, we need to add a link preview in the iOS Client for each such page extracting and displaying the title, description and a main image of the webpage.
At first we make the API call adding the query to the query parameter and get the result from it.
API Call:
http://api.susi.ai/susi/chat.json?timezoneOffset=-330&q=amazon
And get the following result:
{
"query": "amazon",
"count": 1,
"client_id": "aG9zdF8xMDguMTYyLjI0Ni43OQ==",
"query_date": "2017-06-02T14:34:15.675Z",
"answers": [
{
"data": [{
"0": "amazon",
"1": "amazon",
"timezoneOffset": "-330"
}],
"metadata": {
"count": 1
},
"actions": [{
"type": "answer",
"expression": "I don't know how to answer this. Here is a web search result:"
},
{
"type": "websearch",
"query": "amazon"
}]
}],
"answer_date": "2017-06-02T14:34:15.773Z",
"answer_time": 98,
"language": "en",
"session": {
"identity": {
"type": "host",
"name": "108.162.246.79",
"anonymous": true
}
}
}
After parsing this response, we first recognise the type of action that needs to be performed, here we get `websearch` which means we need to make a web search for the query. Here, we use `DuckDuckGo’s` API to get the result.
API Call to DuckDuckGo:
http://api.duckduckgo.com/?q=amazon&format=json
I am adding just the first object of the required data since the API response is too long.
Path: $.RelatedTopics[0]
{
"Result": "<a href=\"https://duckduckgo.com/Amazon.com\">Amazon.com</a>Amazon.com, also called Amazon, is an American electronic commerce and cloud computing company...",
"Icon": {
"URL": "https://duckduckgo.com/i/d404ba24.png",
"Height": "",
"Width": ""
},
"FirstURL": "https://duckduckgo.com/Amazon.com",
"Text": "Amazon.com Amazon.com, also called Amazon, is an American electronic commerce and cloud computing company..."
}
For the link preview, we need an image logo, URL and a description for the same so, here we will use the `Icon.URL` and `Text` key. We have our own class to parse this data into an object.
class WebsearchResult: NSObject {
var image: String = "no-image"
var info: String = "No data found"
var url: String = "https://duckduckgo.com/"
var query: String = ""
init(dictionary: [String:AnyObject]) {
if let relatedTopics = dictionary[Client.WebsearchKeys.RelatedTopics] as? [[String : AnyObject]] {
if let icon = relatedTopics[0][Client.WebsearchKeys.Icon] as? [String : String] {
if let image = icon[Client.WebsearchKeys.Icon] {
self.image = image
}
}
if let url = relatedTopics[0][Client.WebsearchKeys.FirstURL] as? String {
self.url = url
}
if let info = relatedTopics[0][Client.WebsearchKeys.Text] as? String {
self.info = info
}
if let query = dictionary[Client.WebsearchKeys.Heading] as? String {
let string = query.lowercased().replacingOccurrences(of: " ", with: "+")
self.query = string
}
}
}
}
We now have the data and only thing left is to display it in the UI.
Within the Chat Bubble, we need to add a container view which will contain the image, and the text description.
let websearchContentView = UIView()
let searchImageView: UIImageView = {
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
imageView.contentMode = .scaleAspectFit
// Placeholder image assigned
imageView.image = UIImage(named: "no-image")
return imageView
}()
let websiteText: UILabel = {
let label = UILabel()
label.textColor = .white
return label
}()
func addLinkPreview(_ frame: CGRect) {
textBubbleView.addSubview(websearchContentView)
websearchContentView.backgroundColor = .lightGray
websearchContentView.frame = frame
websearchContentView.addSubview(searchImageView)
websearchContentView.addSubview(websiteText)
// Add constraints in UI
websearchContentView.addConstraintsWithFormat(format: "H:|-4-[v0(44)]-4-[v1]-4-|", views: searchImageView, websiteText)
websearchContentView.addConstraintsWithFormat(format: "V:|-4-[v0]-4-|", views: searchImageView)
websearchContentView.addConstraintsWithFormat(format: "V:|-4-[v0(44)]-4-|", views: websiteText)
}
Next, in the Collection View, while checking other action types, we add checking for `websearch` and then call the API there followed by adding frame size and calling the `addLinkPreview` function.
else if message.responseType == Message.ResponseTypes.websearch {
let params = [
Client.WebsearchKeys.Query: message.query!,
Client.WebsearchKeys.Format: "json"
]
Client.sharedInstance.websearch(params, { (results, success, error) in
if success {
cell.message?.websearchData = results
message.websearchData = results
self.collectionView?.reloadData()
self.scrollToLast()
} else {
print(error)
}
})
cell.messageTextView.frame = CGRect(x: 16, y: 0, width: estimatedFrame.width + 16, height: estimatedFrame.height + 30)
cell.textBubbleView.frame = CGRect(x: 4, y: -4, width: estimatedFrame.width + 16 + 8 + 16, height: estimatedFrame.height + 20 + 6 + 64)
let frame = CGRect(x: 16, y: estimatedFrame.height + 20, width: estimatedFrame.width + 16 - 4, height: 60 - 8)
cell.addLinkPreview(frame)
}
And set the collection View cell’s size.
else if message.responseType == Message.ResponseTypes.websearch {
return CGSize(width: view.frame.width, height: estimatedFrame.height + 20 + 64)
}
And we are done 🙂
Here is the final version how this would look like on the device:

