В технологии появляется новый тренд — концепция диалоговых вычислений. В отличие от вызова серий команд при помощи нажатий и кликов в этом случае команды будут извлекаться из естественных диалогов. Мы используем диалог в качестве органичного пути для взаимодействия с аналоговым миром, и Xamarin позволяет легко добавлять нативный диалоговый интерфейс к цифровому миру и к Вашему будущему приложению. Стоит объединить интерфейс чата с Microsoft Bot Framework, и в результате получается естественный диалог, который позволяет Вам взаимодействовать с пользователями новым, но знакомым образом.
Интерфейс чата в приложении
Сильной стороной использования диалога в качестве интерфейса является то, что функция диалога устроена очень естественно, бесшовно и интуитивно понятно. В целях содействия такому взаимодействию важно достигать того, чтобы пользовательский интерфейс воспринимался естественно. С Xamarin можно спроектировать такой интерфейс, который прекрасно впишется в ожидания от платформы приложения. Чтобы это продемонстрировать мы разработаем интерфейс чата под iOS, навеянный приложением Xamarin Shopping. Мы спроектируем такой интерфейс, который будет действовать как чат-клиент для поддержки пользователей приложения покупок. Чтобы приступить к работе, мы создадим пустое приложение (iPhone) в Visual Studio.
Наше приложение iOS будет использовать чудесный контроллер представления сообщений под названием JSQMessagesViewController, который доступен в магазине компонентов Xamarin. Наверняка Вы сумеете без проблем подключить его к проекту, открыв Component Store и добавив к приложению JSQMessagesViewController.
Кроме того, поскольку мы будем осуществлять некоторые вызовы HTTP, мы должны также добавить System.Net.Http через диалоговое окно Add Reference.
Теперь, когда основной подготовительный этап пройден, мы можем приступить к написанию кода! И наша первоочередная задача — добавить в приложение UIViewController, ведь именно он будет основой для интерфейса чата. В качестве примера я создал UIViewController с названием ChatPageViewController и базовым классом MessagesViewController.
После того как контроллер представлений будет создан, нам следует объявить несколько переменных, заданных на уровне класса, и создать экземпляр объекта HttpClient для управления обменом данными с ботом в Azure. Переменные уровня класса должны выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 | MessagesBubbleImage outgoingBubbleImageData, incomingBubbleImageData; List messages = new List(); int messageCount = 0; private HttpClient _client; private Conversation _lastConversation; string DirectLineKey = "[Add Direct Line Key]"; //Tracking of which user said what User sender = new User { Id = "2CC8343", DisplayName = "You" }; User friend = new User { Id = "BADB229", DisplayName = "Xamarin Bot" }; //Holds the entire message history for a given session MessageSet ms = new MessageSet(); |
В методе ViewDidLoad мы должны создать экземпляр объекта HttpClient, используя объявленную нами переменную уровня класса, а также установить ряд параметров просмотра и напечатать приветственное сообщение. Метод ViewDidLoad должен выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | public override async void ViewDidLoad() { base.ViewDidLoad(); CollectionView.BackgroundColor = new UIColor(red: 0.00f, green: 0.12f, blue: 0.31f, alpha: 1.0f); Title = "Xamarin Shopping Bot"; //instantiate an HTTPClient, and set properties to our DirectLine bot _client = new HttpClient(); _client.BaseAddress = new Uri("https://directline.botframework.com/api/conversations/"); _client.DefaultRequestHeaders.Accept.Clear(); _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("BotConnector", DirectLineKey); var response = await _client.GetAsync("/api/tokens/"); if (response.IsSuccessStatusCode) { var conversation = new Conversation(); HttpContent contentPost = new StringContent(JsonConvert.SerializeObject(conversation), Encoding.UTF8, "application/json"); response = await _client.PostAsync("/api/conversations/", contentPost); if (response.IsSuccessStatusCode) { var conversationInfo = await response.Content.ReadAsStringAsync(); _lastConversation = JsonConvert.DeserializeObject(conversationInfo); } } // You must set your senderId and display name SenderId = sender.Id; SenderDisplayName = sender.DisplayName; // These MessagesBubbleImages will be used in the GetMessageBubbleImageData override var bubbleFactory = new MessagesBubbleImageFactory(); outgoingBubbleImageData = bubbleFactory.CreateOutgoingMessagesBubbleImage(UIColorExtensions.MessageBubbleLightGrayColor); incomingBubbleImageData = bubbleFactory.CreateIncomingMessagesBubbleImage(new UIColor(red: 0.88f, green: 0.07f, blue: 0.55f, alpha: 1.0f)); // Remove the AccessoryButton as we will not be sending pics InputToolbar.ContentView.LeftBarButtonItem = null; // Remove the Avatars CollectionView.CollectionViewLayout.IncomingAvatarViewSize = CoreGraphics.CGSize.Empty; CollectionView.CollectionViewLayout.OutgoingAvatarViewSize = CoreGraphics.CGSize.Empty; // Load some messagees to start messages.Add(new Message(friend.Id, friend.DisplayName, NSDate.DistantPast, "I am the Shopping Bot!")); FinishReceivingMessage(true); } |
Поскольку наш MessageViewController является фактически кастомизированным UICollectionViewController, мы должны добавить определённые методы, чтобы создать
UICollectionView.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath) { var cell = base.GetCell(collectionView, indexPath) as MessagesCollectionViewCell; // Override GetCell to make modifications to the cell // In this case darken the text for the sender var message = messages[indexPath.Row]; if (message.SenderId == SenderId) cell.TextView.TextColor = UIColor.Black; return cell; } public override nint GetItemsCount(UICollectionView collectionView, nint section) { return messages.Count; } public override IMessageData GetMessageData(MessagesCollectionView collectionView, NSIndexPath indexPath) { return messages[indexPath.Row]; } public override IMessageBubbleImageDataSource GetMessageBubbleImageData(MessagesCollectionView collectionView, NSIndexPath indexPath) { var message = messages[indexPath.Row]; if (message.SenderId == SenderId) return outgoingBubbleImageData; return incomingBubbleImageData; } public override IMessageAvatarImageDataSource GetAvatarImageData(MessagesCollectionView collectionView, NSIndexPath indexPath) { return null; } |
Наконец, есть несколько классов для создания и обработки сообщений и идентификаторов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | public class MessageSet { public BotMessage[] messages { get; set; } public string watermark { get; set; } public string eTag { get; set; } } public class BotMessage { public string id { get; set; } public string conversationId { get; set; } public DateTime created { get; set; } public string from { get; set; } public string text { get; set; } public string channelData { get; set; } public string[] images { get; set; } public Attachment[] attachments { get; set; } public string eTag { get; set; } } public class Attachment { public string url { get; set; } public string contentType { get; set; } } public class Conversation { public string conversationId { get; set; } public string token { get; set; } public string eTag { get; set; } } |
Теперь, когда все нужные объекты созданы и переменные установлены, можем приступить к отправке и получению сообщений от нашего бота! Наша первоочередная задача состоит в том, чтобы указать реакцию на нажатие кнопки «отправить». Здесь нам нужно собирать сообщения, отображать определённые индикаторы и отправлять сообщение к боту:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | public override async void PressedSendButton(UIButton button, string text, string senderId, string senderDisplayName, NSDate date) { //Clear the text and play a send sound InputToolbar.ContentView.TextView.Text = ""; InputToolbar.ContentView.RightBarButtonItem.Enabled = false; SystemSoundPlayer.PlayMessageSentSound(); //set message details and add to the message queue var message = new Message("2CC8343", "You", NSDate.Now, text); messages.Add(message); FinishReceivingMessage(true); //Show typing indicator to add to the natual feel of the bot ShowTypingIndicator = true; //send message to bot and await the message set ms = await SendMessage(text); //iterate through our message set, and print new messasges from the bot while (ms.messages.Length > messageCount) { if (ms.messages[messageCount].from == "XamarinBot") { ScrollToBottom(true); SystemSoundPlayer.PlayMessageReceivedSound(); var messageBot = new Message(friend.Id, friend.DisplayName, NSDate.Now, ms.messages[messageCount].text); messages.Add(messageBot); FinishReceivingMessage(true); InputToolbar.ContentView.RightBarButtonItem.Enabled = true; } messageCount++; } } |
Теперь мы добрались до последней части нашего кода, контроллера представления — и им является метод SendMessage. Здесь мы возьмём ввод текста из PressedSendButton и будем обмениваться данными с нашим ботом посредством Directline.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public async Task SendMessage(string messageText) { try { var messageToSend = new BotMessage() { text = messageText, conversationId = _lastConversation.conversationId }; var contentPost = new StringContent(JsonConvert.SerializeObject(messageToSend), Encoding.UTF8, "application/json"); var conversationUrl = "https://directline.botframework.com/api/conversations/" + _lastConversation.conversationId + "/messages/"; var response = await _client.PostAsync(conversationUrl, contentPost); var messageInfo = await response.Content.ReadAsStringAsync(); var messagesReceived = await _client.GetAsync(conversationUrl); var messagesReceivedData = await messagesReceived.Content.ReadAsStringAsync(); var messages = JsonConvert.DeserializeObject(messagesReceivedData); return messages; } catch (Exception ex) { Console.WriteLine(ex.Message); return null; } } |
И, наконец, мы должны использовать AppDelegate для запуска ChatPageViewController при открытии приложения, и мы сделаем это с помощью:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public override bool FinishedLaunching(UIApplication app, NSDictionary options) { // create a new window instance based on the screen size window = new UIWindow(UIScreen.MainScreen.Bounds); // If you have defined a root view controller, set it here: window.RootViewController = new UINavigationController(new ChatPageViewController()); // make the window visible window.MakeKeyAndVisible(); return true; } |
Сейчас, когда приложение создано, нам необходимо настроить бота для диалога.
Добавление Microsoft Bot Framework
Сервис бота состоит из двух частей, правая из которых — это language processor (LUIS), а вторая — приложение Bot Service, которое мы будем размещать в Azure. Для наших целей мы импортируем пользовательское приложение LUIS, созданное специально для этого примера. Чтобы произвести настройку LUIS, Вам нужно отправиться на luis.ai и зайти туда, используя учетную запись Microsoft. После того как Вы попадете на главную страницу, нужно кликнуть на New App (новое приложение), а затем на Import Existing App (импорт существующего приложения). Вы можете загрузить приложение LUIS в формате JSON на GitHub.
Теперь, когда сервис LUIS создан, настало время заняться созданием приложения бота! Возможно, Вы заметили, что для клиента нам нужно создать только несколько вызовов HTTP. Это происходит потому, что большая часть процесса обработки диалога выполняется в рамках приложения Bot Service. Это позволяет использовать один и тот же сервис Bot для различных платформ, в том числе для интеграций, в частности, со Skype. Приступая к работе, не забудете убедиться в том, что у вас установлен шаблон бота. Эти элементы могут быть загружены со страницы Bot Framework page.
Проект бота содержит в себе контроллер и диалог. Шаблон по умолчанию включает в себя контроллер, который возвращает ввод (echo input back) пользователю. Как только наш диалог будет настроен, мы вернёмся к контроллеру и предложим ему использовать вновь созданный диалог. Для того чтобы создать диалог добавьте новый файл класса в ваш проект, назвав его как-нибудь осмыслено. Сам класс должен наследовать от базового класса LuisDialog, и, кроме того, его следует обозначить как [Serializable].
В этом самом классе нам нужно создать метод для каждого Intent, определенный в нашем приложении LUIS.
1 2 3 4 5 6 7 | [LuisIntent("")] public async Task None(IDialogContext context, LuisResult result) { string message = "Sorry, I do not understand. Try asking me for order information or customer service help"; await context.PostAsync(message); context.Done(true); } |
Как вы можете видеть из фрагмента кода, мы создаем метод public async, который возвращает Task и принимает IDialogContext и LuisResult в качестве входных данных. IDialogContext является диалогом, который выполняется, в то время как LuisResult — это строка JSON, вернувшаяся из службы LUIS. Кроме того, этот метод помечен атрибутом [LuisIntent(«»)], и он обозначает тот LUIS Intent метод, который предназначен для ответа. Когда Intent является пустым, он перехватывает любой фрагмент активной речи, которая не может быть сопоставлена с известным Intent. В случае если этот метод был предназначен для обработки всех фрагментов активной речи, которые сопоставляются с целью «Обслуживания клиентов», тогда мы бы пометили этот метод с атрибутом [LuisIntent(«CustomerService»)]. Тем не менее так как это всеохватывающий intent, в наших интересах сообщить пользователю о том, что сказанное им было непонятно, и предположить альтернативные варианты того, что предположительно могло быть им сказано. Мы вывешиваем сообщение пользователю через context.PostAsync(message) и отмечаем этот диалог как завершенный.
Для более сложных диалогов или тех, в которых должны произойти другие задачи или поиск данных, может возникнуть необходимость обратиться к другим методам от одного из отмеченных LuisIntent методов. Например, следующий фрагмент кода из отмеченного LuisIntent метода обозначает обработчик завершения для вызова после получения результатов вопроса, заданного пользователю.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | [LuisIntent("CustomerService")] public async Task customerServiceRequest(IDialogContext context, LuisResult result) { foreach(var entity in result.Entities) { if (entity.Type == "ServiceKeyword" && customerSupportKeywords.Contains(entity.Entity.ToLower())) { switch(entity.Entity.ToLower()) { case "password": PromptDialog.Text(context, SupportUsernameEntered, "What is the email address associated with your account?"); break; } [...] } } } |
В этом усеченном методе мы обнаруживаем, что пользователь задает вопрос относительно своего пароля, и нам необходимо верифицировать пользователя путём запроса адреса его электронной почты. PromptDialog.Text запрашивает информацию от пользователя, а также содержит обработчик завершения, который вызывает SupportUsernameEntered.
1 2 3 4 5 6 7 8 9 10 11 12 13 | private async Task SupportUsernameEntered(IDialogContext context, IAwaitable result) { var item = await result; { if (Account.IsFound(item)) { [...send email...] await context.PostAsync("Account was found, we've sent a reset email to your registered email account"); context.Done(true); } [..] } } |
В рамках этого метода, мы подтверждаем учетную запись, и отправляем на электронную почту инструкцию по сбросу, если аккаунт был найден. Как только сообщение будет отправлено, мы можем решить, что поставленная цель выполнена, и завершить диалог. С тем чтобы получался естественный диалог между пользователем и приложением, служба Bot должна включать в себя много подобных цепочек речевого общения. Большую часть этих дочерних действий можно также определить и в LUIS, хотя в нашем примере разговор является более базовым и линейным.
Теперь, когда базовый Bot создан, нам нужно опубликовать бота в Azure. В этом нам может помочь официальная документация.
И, наконец, нам нужно настроить службу для обмена данными с ботом в Azure, используя сервис под названием Direct Line. Это обычная служба REST, которая позволяет установить связь между пользовательским клиентом и ботом. Документация Direct Line охватывает требуемые шаги для подключения DirectLine к экземпляру бота. Как только Direct Line будет настроена, Вы должны изменить переменную DirectLineKey в iOS View Controller в соответствии с API Key для Direct Line.
Вот и все! При запуске приложения мы сможем по-настоящему побеседовать с нашим ботом как показано ниже:
Framework Microsoft Bot — это невероятно мощная система, и мы здесь затронули лишь самую малую часть того, чего с её помощью можно достичь. Чтобы ознакомиться с полным примером, загрузите его с GitHub.
Автор: Cody Beyer
Источник: Официальный блог Xamarin
Написать ответ