Customer Support Chatbot | Part 4 | Coding the Main Dialog | Microsoft Bot Framework

This is the bot series on Customer Support Chatbot. This bot will allow you to track orders, raise complaints, get bills/invoices, and cancel orders. In this part, we are going to start coding the Main Dialog to show the above features to the user for selection in an adaptive card.

Pre-requisites

  1. Visual Studio
  2. Bot Framework Emulator

The links to the above requirements are available on the downloads page.

Video

Main Dialog

This will be the root dialog for all your sub-dialogs. You can call any number of sub-dialogs from here. Dialogs are nothing but a conversational flow. It is like a way of addressing your users sequentially.

We have used the Waterfall dialog model to create our conversational flow. To learn more about it, watch the below video.

In the Main Dialog, we are going to show the users four options to select -> Track Order, Raise Complaint, Get Bill/Invoice, and Cancel Order. We will show these options using an Adaptive card. Also, we will be capturing the user response and sending it back to the user.

In the Waterfall dialog model, we have used 3 steps –

  1. IntroStepAsync
  2. ActStepAsync
  3. FinalStepAsync

The first step will show the Adaptive card, the second step will capture the user response and send it to the user, and in the last step, we are restarting the Main Dialog.

We have created a reusable method to create the Adaptive card and send it to the user. Below is the code for that inside the MainDialog.

private async Task<DialogTurnResult> CreateAdaptiveCardAsync(List<string> listOfOptions, WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0))
            {
                // Use LINQ to turn the choices into submit actions
                Actions = listOfOptions.Select(choice => new AdaptiveSubmitAction
                {
                    Title = choice,
                    Data = choice,  // This will be a string
                }).ToList<AdaptiveAction>(),
            };
            // Prompt
            return await stepContext.PromptAsync(nameof(ChoicePrompt), new PromptOptions
            {
                Prompt = (Activity)MessageFactory.Attachment(new Attachment
                {
                    ContentType = AdaptiveCard.ContentType,
                    // Convert the AdaptiveCard to a JObject
                    Content = JObject.FromObject(card),
                }),
                Choices = ChoiceFactory.ToChoices(listOfOptions),
                // Don't render the choices outside the card
                Style = ListStyle.None,
            },
                cancellationToken);
        }
International Open Academy

The above reusable method, we will be calling from the IntroStepAsync by passing the list of options.

private async Task<DialogTurnResult> IntroStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            List<string> userOptions = new List<string> { "Track Orders", "Raise Complaints", "Get Bill/Invoice", "Cancel Order" };

            await stepContext.Context.SendActivityAsync(MessageFactory.Text("How can I help you? Please select one of the below option."), cancellationToken);

            return await CreateAdaptiveCardAsync(userOptions, stepContext, cancellationToken);
        }

Capture the User’s response and send it back to the user.

private async Task<DialogTurnResult> ActStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            stepContext.Values["UserSelection"] = ((FoundChoice)stepContext.Result).Value;
            await stepContext.Context.SendActivityAsync(MessageFactory.Text("You have selected " + (string)stepContext.Values["UserSelection"]), cancellationToken);
            return await stepContext.NextAsync(null, cancellationToken);
        }

The FinalStepAsync has no change as of now. it restarts the conversation from the beginning of MainDialog. Since we have used the ChoicePrompt inside the CreateAdaptiveCardAsync, we will have to declare it inside the constructor.

public MainDialog(ILogger<MainDialog> logger)
            : base(nameof(MainDialog))
        {
            
            Logger = logger;

            AddDialog(new TextPrompt(nameof(TextPrompt)));
            AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
            AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
            {
                IntroStepAsync,
                ActStepAsync,
                FinalStepAsync,
            }));

            // The initial child Dialog to run.
            InitialDialogId = nameof(WaterfallDialog);
        }
International Open Academy
International Open Academy

Below is what the complete code of the MainDialog looks like.

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// Generated with Bot Builder V4 SDK Template for Visual Studio CoreBot v4.12.2

using AdaptiveCards;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Choices;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Logging;
using Microsoft.Recognizers.Text.DataTypes.TimexExpression;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace CustomerSupportBot.Dialogs
{
    public class MainDialog : ComponentDialog
    {
        protected readonly ILogger Logger;

        // Dependency injection uses this constructor to instantiate MainDialog
        public MainDialog(ILogger<MainDialog> logger)
            : base(nameof(MainDialog))
        {
            
            Logger = logger;

            AddDialog(new TextPrompt(nameof(TextPrompt)));
            AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
            AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
            {
                IntroStepAsync,
                ActStepAsync,
                FinalStepAsync,
            }));

            // The initial child Dialog to run.
            InitialDialogId = nameof(WaterfallDialog);
        }

        private async Task<DialogTurnResult> IntroStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            List<string> userOptions = new List<string> { "Track Orders", "Raise Complaints", "Get Bill/Invoice", "Cancel Order" };

            await stepContext.Context.SendActivityAsync(MessageFactory.Text("How can I help you? Please select one of the below option."), cancellationToken);

            return await CreateAdaptiveCardAsync(userOptions, stepContext, cancellationToken);
        }

        private async Task<DialogTurnResult> ActStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            stepContext.Values["UserSelection"] = ((FoundChoice)stepContext.Result).Value;
            await stepContext.Context.SendActivityAsync(MessageFactory.Text("You have selected " + (string)stepContext.Values["UserSelection"]), cancellationToken);
            return await stepContext.NextAsync(null, cancellationToken);
        }

        
        private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            
            // Restart the main dialog with a different message the second time around
            var promptMessage = "What else can I do for you?";
            return await stepContext.ReplaceDialogAsync(InitialDialogId, promptMessage, cancellationToken);
        }

        private async Task<DialogTurnResult> CreateAdaptiveCardAsync(List<string> listOfOptions, WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0))
            {
                // Use LINQ to turn the choices into submit actions
                Actions = listOfOptions.Select(choice => new AdaptiveSubmitAction
                {
                    Title = choice,
                    Data = choice,  // This will be a string
                }).ToList<AdaptiveAction>(),
            };
            // Prompt
            return await stepContext.PromptAsync(nameof(ChoicePrompt), new PromptOptions
            {
                Prompt = (Activity)MessageFactory.Attachment(new Attachment
                {
                    ContentType = AdaptiveCard.ContentType,
                    // Convert the AdaptiveCard to a JObject
                    Content = JObject.FromObject(card),
                }),
                Choices = ChoiceFactory.ToChoices(listOfOptions),
                // Don't render the choices outside the card
                Style = ListStyle.None,
            },
                cancellationToken);
        }
    }
}

Remember, this is just the starting logic that we have written. As the project progresses, we will be changing the code accordingly.

Run the project and see the output in the emulator.

In the next part, we will create the sub-dialogs.

Thank you All!!! Hope you find this useful.

If you liked our content and it was helpful, you can buy us a coffee or a pizza. Thank you so much.


International Open Academy

Leave a Reply

Up ↑

%d bloggers like this: