blob: 37673087438cc5b9699848bbd826a043a7ca97fb [file] [log] [blame]
/*
* Copyright 2015-2016 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// ChatBotViewController.swift
// WhiskBot
//
// Created by whisk on 1/18/17.
// Copyright © 2017 Avery Lamp. All rights reserved.
//
import UIKit
import JSQMessagesViewController
import OpenWhisk
import SwiftyJSON
import EventKit
class ChatBotViewController: JSQMessagesViewController, appleSpeechFeedbackProtocall{
var messageData = [JSQMessage]()
var context: JSON? // save context to continue conversation
var whisk: Whisk?
override func viewDidLoad() {
super.viewDidLoad()
if let path = Bundle.main.path(forResource: "IBMConstants", ofType: "plist"), let dict = NSDictionary(contentsOfFile: path) as? [String: AnyObject], let accessKey = dict["whisk_access_key"] as? String, accessKey != "", let accessToken = dict["whisk_access_token"] as? String, accessToken != "" {
let whiskCreds = WhiskCredentials(accessKey: accessKey, accessToken: accessToken)
whisk = Whisk(credentials: whiskCreds)
}else{
print("IBM OpenWhisk Credentials not found")
}
self.senderId = "User"
self.senderDisplayName = "Client"
self.finishSendingMessage(animated: true)
initialMessageFromConversation()
speechRecognizer.setupSpeechRecognition()
speechRecognizer.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.eventStore = EKEventStore()
self.reminders = [EKReminder]()
self.eventStore.requestAccess(to: .reminder) { (granted, error) in
if granted{
let predicate = self.eventStore.predicateForReminders(in: nil)
self.eventStore.fetchReminders(matching: predicate, completion: { (reminders) in
self.reminders = reminders
})
}else {
print("Permission not granted for reminders")
}
}
}
func initialMessageFromConversation(){
let initialMessageParam = [String:Any]()
do{
try whisk?.invokeAction(name: "Conversation", package: "", namespace: "", parameters: initialMessageParam as AnyObject?, hasResult: true, callback: { (result, error) in
if error == nil{
let jsonResult = JSON(result!)
//print("Result - \(jsonResult["result"]["output"]["text"])")
let incomingString = jsonResult["result"]["output"]["text"].arrayObject?.first as? String
let incomingMessage = JSQMessage(senderId: "Bluemix", displayName: "Bluemix", text: incomingString)
self.messageData.append(incomingMessage!)
DispatchQueue.main.async {
self.finishSendingMessage(animated: true)
self.context = jsonResult["result"]["context"]
self.context?["current_slack_channel"] = "general"
self.context?["last_reminder_text"] = "Reminder text"
}
}else{
print("error invoking - \(error)")
}
})
}catch{
print("Error thrown invoking whisk action")
}
}
override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
print("Send message - \(text)")
messageData.append(JSQMessage(senderId: "User", displayName: "Client", text: text))
var initialMessageParam = [String:Any]()
initialMessageParam["input"] = ["text":text]
initialMessageParam["context"] = self.context?.dictionaryObject
//print("Context - \(self.context?.dictionaryObject)")
do{
try whisk?.invokeAction(name: "Conversation", package: "", namespace: "", parameters: initialMessageParam as AnyObject?, hasResult: true, callback: { (result, error) in
if error == nil{
let jsonResult = JSON(result!)
let incomingString = jsonResult["result"]["output"]["text"].arrayObject?.first as? String
if incomingString != nil && incomingString != "" {
let incomingMessage = JSQMessage(senderId: "Bluemix", displayName: "Bluemix", text: incomingString)
self.messageData.append(incomingMessage!)
DispatchQueue.main.async {
self.finishSendingMessage(animated: true)
self.context = jsonResult["result"]["context"]
self.processActions(text: text, jsonResult: jsonResult)
if (incomingString?.contains("Translation -"))! {
let timeAfterOpen = 1.0
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(timeAfterOpen * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: {
print("Followup")
let followUpMessage = JSQMessage(senderId: "Bluemix", displayName: "Bluemix", text: "Is there anything else you would like me to do?")
self.messageData.append(followUpMessage!)
self.finishSendingMessage(animated: true)
})
}
}
}
}else{
print("error invoking - \(error)")
}
})
}catch{
print("Error thrown invoking whisk action")
}
self.finishSendingMessage(animated: true)
}
func processActions(text: String, jsonResult: JSON){
let lastNodeVisited = jsonResult["result"]["output"]["nodes_visited"].arrayObject?.first! as? String
print("Last Node - \(lastNodeVisited)")
if lastNodeVisited == "Reminder Text" {
self.context?["last_reminder_text"].string = text
}
if lastNodeVisited == "Reminder Time" {
let entities = jsonResult["result"]["entities"]
var dateString = ""
var timeString = ""
for entity in entities.arrayObject!{
if let dict = entity as? [String:Any]{
if dict["entity"] as? String == "sys-time" {
print("Sys-time - \(dict["value"] as! String)")
timeString = dict["value"] as! String
}
if dict["entity"] as? String == "sys-date" {
print("Sys-date - \(dict["value"] as! String)")
dateString = dict["value"] as! String
}
}
}
let todaysDate = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
if dateString == ""{
dateString = dateFormatter.string(from: todaysDate)
}
if timeString == "" {
timeString = "18:00:00"
}
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
if let dateToRemind = dateFormatter.date(from: "\(dateString) \(timeString)"){
print("Date to remind - \(dateToRemind)")
self.createReminder(text: (self.context?["last_reminder_text"].string)!, date: dateToRemind)
}
}
}
var isListening = false
var textBeforeSpeech = ""
var speechRecognizer = SpeechRecognizer()
override func didPressAccessoryButton(_ sender: UIButton!) {
print("Accessory button pressed")
isListening = true
speechRecognizer.startRecording()
}
//MARK : - UICollectionView Data source
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.messageData.count
}
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
let message = self.messageData[indexPath.item]
if !message.isMediaMessage{
cell.textView.textColor = UIColor.black
}
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageDataForItemAt indexPath: IndexPath!) -> JSQMessageData! {
return messageData[indexPath.item]
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = self.messageData[indexPath.item]
let bubbleFactory = JSQMessagesBubbleImageFactory()
if message.senderId == self.senderId{
return bubbleFactory?.outgoingMessagesBubbleImage(with: UIColor.black)
}else{
return bubbleFactory?.incomingMessagesBubbleImage(with: UIColor.lightGray)
}
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAt indexPath: IndexPath!) -> JSQMessageAvatarImageDataSource! {
let message = self.messageData[indexPath.item]
let bluemixAvatar = JSQMessagesAvatarImageFactory.avatarImage(with: #imageLiteral(resourceName: "bluemixLogo"), diameter: 30)
let clientAvatar = JSQMessagesAvatarImageFactory.avatarImage(with: #imageLiteral(resourceName: "openwhiskLogo"), diameter: 30)
if message.senderId == self.senderId{
return clientAvatar
}else{
return bluemixAvatar
}
}
var eventStore: EKEventStore!
var reminders: [EKReminder]!
func createReminder(text: String, date: Date){
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "E, d, h:mm"
let alert = UIAlertController(title: "Create Reminder?", message: "Would you like to create the reminder: \(text) - at \(dateFormatter.string(from: date))", preferredStyle: .actionSheet)
let confirmAction = UIAlertAction(title: "Yes", style: .default) { (action) in
let reminder = EKReminder(eventStore: self.eventStore)
reminder.title = text
reminder.calendar = self.eventStore.defaultCalendarForNewReminders()
reminder.addAlarm(EKAlarm(absoluteDate: date))
//reminder.completionDate = date
do {
try self.eventStore.save(reminder, commit: true)
print("Reminder set - \(text) - at \(dateFormatter.string(from: date))")
}catch{
print("Error creating and saving reminder - \(error)")
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive) { (action) in
print("Reminder creation canceled")
}
alert.addAction(confirmAction)
alert.addAction(cancelAction)
self.present(alert, animated: true, completion: nil)
}
//MARK : - Speech Recognition Delegates
func finalAppleRecognitionRecieved(phrase: String) {
print("Final Speech recieved - \(phrase)")
}
func partialAppleRecognitionRecieved(phrase: String) {
print("Partial Speech recieved - \(phrase)")
}
func errorAppleRecieved(error: String) {
print("SPEECH ERROR - \(error)")
}
}