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: