Adaptive Cards as Formflow prompts

1k views Asked by At

I made a bot in C# using BotFramework, Adaptive Cards, LUIS and FormFlow. It/he is responsible for managing meetings in a team.

return new FormBuilder<MeetingRequestInput>()

    ...

    .Field(
        nameof(MeetingRequestInput.RequestedDate),
        "What date would you like to meet?"
    )

    ...

    .Build();

During tests we noticed problems when a user was supposed to type the desired meeting date/time (people would type dd/mm/yy, mm/dd/yy, dd-mm-yy, only dd, etcetra) so we would like to use some kind of "Form" with formatted inputs to avoid parsing problems and retaining usability.

I think we can't change the desired keyboard type (kinda like in mobile, where sometimes the keyboard only shows numbers, or shows a DateTime Picker), nor apply a pattern auto-complete, at least using BotFramework.

To solve this, I'd like to use an AdaptiveCard with Date and Time pickers in my FormFlow prompt, at least when the user is asked to type the requested date, like so:

Input example using Adaptive Cards

In this example, the user would fill the AdaptiveDateInput and the AdaptiveTimeInput, then press the Confirm button. Doing so, it would grab the values inside the inputs, then "type and send" for the user the desired DateTime in a specific template, avoiding the previous parsing problems.

Problem is, I can't replace the "normal" FormFlow Card (who's expecting a simple string as a prompt parameter) with an entire Adaptive Card. How do I solve this problem? Are AdaptiveCards the best answer or are there alternatives?

Right now I'm displaying the card manually like so:

AdaptiveCard card = new AdaptiveCard();
    card.Body = new List<AdaptiveElement>()
    {
        new AdaptiveTextBlock()
        {
            Text = "What date would you like to meet?"
        },
        new AdaptiveDateInput()
        {
            Value = DateTime.Now.ToShortDateString(),
            Id = "dateInp"
        },
        new AdaptiveTimeInput()
        {
            Value = DateTime.Now.ToShortTimeString(),
            Id = "timeInp"
        }
    };
    card.Actions = new List<AdaptiveAction>()
    {
        new AdaptiveSubmitAction()
        {
            Type = "Action.Submit",
            Title = "Confirm"
        }
    };
    var msg = context.MakeMessage();
    msg.Attachments.Add(
        new Attachment()
        {
            Content = card,
            ContentType = "application/vnd.microsoft.card.adaptive",
            Name = "Requested Date Adaptive Card"
        }
    );
    await context.PostAsync(msg);

I've read this question, but I don't know if we have the exact same problem. And their solution does not apply to me: even though I made the examples above in english, the bot will actually expect inputs in other languages, so yeah we can parse "February 2th" using a Recognizer, but we don't have the same luck with "2 de Fevereiro" nor "2 Fevralya".

1

There are 1 answers

0
Eric Dahlvang On

Using the FormBuilder.Prompter, you can customize FormFlow messages any way you like. However, since AdaptiveCards send responses in the .Value, the code will need to transfer .Value to .Text property before validation.

Here is an example of a FormFlow form that sends an AdaptiveCard for a RequestedDate field:

[Serializable]
public class AdaptiveCardsFormFlow
{
    public string Name { get; set; }
    public DateTime? RequestedDate { get; set; }

    public static IForm<AdaptiveCardsFormFlow> BuildForm()
    {
        IFormBuilder<AdaptiveCardsFormFlow> formBuilder = GetFormbuilder();

        var built = formBuilder
            .Field(nameof(Name), "What is your name?")
            .Field(nameof(RequestedDate))
            .Confirm("Is this information correct? {*}")
            .Build();

        return built;
    }

    private static AdaptiveCard GetDateCard()
    {
        AdaptiveCard card = new AdaptiveCard();
        card.Body = new List<AdaptiveElement>()
        {
            new AdaptiveTextBlock()
            {
                Text = "What date would you like to meet?"
            },
            new AdaptiveDateInput()
            {
                Value = DateTime.Now.ToShortDateString(),
                Id = "dateInp"
            },
            new AdaptiveTimeInput()
            {
                Value = DateTime.Now.ToShortTimeString(),
                Id = "timeInp"
            }
        };
        card.Actions = new List<AdaptiveAction>()
        {
            new AdaptiveSubmitAction()
            {
                Type = "Action.Submit",
                Title = "Confirm"
            }
        };
        return card;
    }

    private static IFormBuilder<AdaptiveCardsFormFlow> GetFormbuilder()
    {

        IFormBuilder<AdaptiveCardsFormFlow> formBuilder = new FormBuilder<AdaptiveCardsFormFlow>()
            .Prompter(async (context, prompt, state, field) =>
            {
                var preamble = context.MakeMessage();
                var promptMessage = context.MakeMessage();
                if (prompt.GenerateMessages(preamble, promptMessage))
                {
                    await context.PostAsync(preamble);
                }

                if (field != null && field.Name == nameof(AdaptiveCardsFormFlow.RequestedDate))
                {
                    var attachment = new Attachment()
                    {
                        Content = GetDateCard(),
                        ContentType = AdaptiveCard.ContentType,
                        Name = "Requested Date Adaptive Card"
                    };

                    promptMessage.Attachments.Add(attachment);
                }

                await context.PostAsync(promptMessage);

                return prompt;
            }).Message("Please enter your information to schedule a callback.");

        return formBuilder;
    }
}

Using this class:

 private class DateTimeInp
    {
        public string dateInp { get; set; }
        public string timeInp { get; set; }

        public DateTime? ToDateTime()
        {
            string fullDateTime = dateInp + " " + timeInp;
            DateTime toDateTime;
            if(DateTime.TryParse(fullDateTime, out toDateTime))
            {
                return toDateTime;
            }
            return null;
        }
    }

Then, in the Messages Controller, add the Adaptive Card's return value to the .Text property of the activity:

if(activity.Value != null)
{
    DateTimeInp input = JsonConvert.DeserializeObject<DateTimeInp>(activity.Value.ToString());
    var toDateTime = input.ToDateTime();
    if(toDateTime != null)
    {
        activity.Text = toDateTime.ToString();
    }
}