如何在Adaptive卡中使取消按钮显示在C#的MS Bot框架SDK V4中开发的Web Channel聊天机器人中?

时间:2019-11-07 07:32:01

标签: c# json botframework chatbot adaptive-cards

我已经使用C#中的MS Bot Framework SDK V4为Web频道开发了聊天Bot,该Bot具有多个瀑布对话框类,每个类均执行特定任务。在主根对话框中,我显示了一组选项1,2,3,4 ... 6。现在,当我选择一个选项5时,我将重定向到新的对话框类,其中

我有一个自适应卡,我设计了3组容器,其中一个通过文本框输入文本,第二个容器有一些复选框可供选择,第三个容器包含2个按钮“提交”和“取消”按钮。对于这些按钮,我分别将数据设置为Cancel = 0和1。 在此选项5对话框中,我基于数据cancel-0或1(如果为1)进行控制,我正在结束对话框并显示默认显示选项1,2,3,4 ... 6。 现在,我通过输入有效值单击“提交”按钮,并且该过程已成功完成,结果当前对话框已结束,并且再次显示了主要选项集。

在这里,我进行了某种否定性测试,其中我向上滚动并单击了上面显示的“取消”按钮。这导致在选项1到6的集合中显示的第一个选项(选项1)默认为y,即使我选择了cancel而不是第一个选项,该选项操作也会自动执行。但这在我向上滚动后选择自适应卡中显示的提交按钮时显示重试提示以选择以下任一选项,而当我单击“取消”时,默认情况下它将变为第一选项。

请在下面找到对话框相关和自适应卡相关的数据:

{
    "type": "AdaptiveCard",
    "body": [
        {
            "type": "TextBlock",
            "size": "Large",
            "weight": "Bolder",
            "text": "Request For Model/License",
            "horizontalAlignment": "Center",
            "color": "Accent",
            "id": "RequestforModel/License",
            "spacing": "None",
            "wrap": true
        },
        {
            "type": "Container",
            "items": [
                {
                    "type": "TextBlock",
                    "text": "Requester Name* : ",
                    "id": "RequesterNameLabel",
                    "weight": "Bolder",
                    "wrap": true,
                    "spacing": "None"
                },
                {
                    "type": "Input.Text",
                    "placeholder": "Enter Requester Name",
                    "id": "RequesterName",
                    "spacing": "None"
                },
                {
                    "type": "TextBlock",
                    "text": "Requester Email* : ",
                    "id": "RequesterEmailLabel",
                    "weight": "Bolder",
                    "wrap": true,
                    "spacing": "Small"
                },
                {
                    "type": "Input.Text",
                    "placeholder": "Enter Requester Email",
                    "id": "RequesterEmail",
                    "style": "Email",
                    "spacing": "None"
                },
                {
                    "type": "TextBlock",
                    "text": "Customer Name* : ",
                    "id": "CustomerNameLabel",
                    "weight": "Bolder",
                    "wrap": true,
                    "spacing": "Small"
                },
                {
                    "type": "Input.Text",
                    "placeholder": "Enter Customer Name",
                    "id": "CustomerName",
                    "spacing": "None"
                },
                {
                    "type": "TextBlock",
                    "text": "Select Request Type : ",
                    "id": "RequestTypeText",
                    "horizontalAlignment": "Left",
                    "wrap": true,
                    "weight": "Bolder",
                    "size": "Medium",
                    "spacing": "Small"
                },
                {
                    "type": "Input.ChoiceSet",
                    "placeholder": "--Select--",
                    "choices": [
                        {
                            "title": "Both",
                            "value": "Both"
                        },
                        {
                            "title": "1",
                            "value": "1"
                        },
                        {
                            "title": "2",
                            "value": "2"
                        }
                    ],
                    "id": "RequestType",
                    "value": "Both",
                    "spacing": "None"
                }
            ],
            "horizontalAlignment": "Left",
            "style": "default",
            "bleed": true,
            "id": "Requesterdata"
        },
        {
            "type": "Container",
            "items": [
                {
                    "type": "TextBlock",
                    "text": "Select Asset* :",
                    "id": "Assetheader",
                    "horizontalAlignment": "Left",
                    "wrap": true,
                    "weight": "Bolder",
                    "size": "Medium",
                    "spacing": "Small"
                },
                {
                    "type": "Input.ChoiceSet",
                    "placeholder": "",
                    "choices": [
                        {
                            "title": "chekcbox1",
                            "value": "chekcbox1"
                        },
                        {
                            "title": "chekcbox2",
                            "value": "chekcbox2"
                        },
                        {
                            "title": "chekcbox3",
                            "value": "chekcbox3"
                        },
                        {
                            "title": "chekcbox4",
                            "value": "chekcbox4"
                        },
                        {
                            "title": "chekcbox5",
                            "value": "chekcbox5"
                        }
                    ],
                    "isMultiSelect": true,
                    "id": "AssetsList",
                    "wrap": true,
                    "spacing": "None"
                }
            ],
            "id": "Assetdata",
            "style": "default",
            "horizontalAlignment": "Left",
            "bleed": true
        },
        {
            "type": "Container",
            "items": [
                {
                    "type": "ActionSet",
                    "actions": [
                        {
                            "type": "Action.Submit",
                            "title": "Cancel",
                            "id": "CanclBtn",
                            "style": "positive",
                            "data": {
                                "Cancel": 1
                            }
                        },
                        {
                            "type": "Action.Submit",
                            "title": "Submit",
                            "id": "SubmitBtn",
                            "style": "positive",
                            "data": {
                                "Cancel": 0
                            }
                        }
                    ],
                    "id": "Action1",
                    "horizontalAlignment": "Center",
                    "spacing": "Small",
                    "separator": true
                }
            ]
        }
    ],
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.0",
    "id": "ModelLicenseRequestForm",
    "lang": "Eng"
}

主根对话框的以下代码:

AddStep(async (stepContext, cancellationToken) =>
{
    return await stepContext.PromptAsync(
        "choicePrompt",
        new PromptOptions
        {
            Prompt = stepContext.Context.Activity.CreateReply("Based on the access privileges assigned to you by your admin, below are the options you can avail. Please click/choose any one from the following: "),
            Choices = new[] { new Choice { Value = "option1" }, new Choice { Value = "option2" }, new Choice { Value = "option3" }, new Choice { Value = "option4" }, new Choice { Value = "option5" }, new Choice { Value = "option6" } }.ToList(),
            RetryPrompt = stepContext.Context.Activity.CreateReply("Sorry, I did not understand that. Please choose any one from the options displayed below: "),
        });
});

AddStep(async (stepContext, cancellationToken) =>
{
    if (response == "option1")
    {
        doing something
    }

    if (response == "option2")
    {
        return await stepContext.BeginDialogAsync(option2.Id, cancellationToken: cancellationToken);
    }

    if (response == "option3")
    {
        return await stepContext.BeginDialogAsync(option3.Id, cancellationToken: cancellationToken);
    }

    if (response == "option4")
    {
        return await stepContext.BeginDialogAsync(option4.Id, cancellationToken: cancellationToken);
    }

    if (response == "option5")
    {
        return await stepContext.BeginDialogAsync(option5.Id, cancellationToken: cancellationToken);
    }

    if (response == "option6")
    {
        return await stepContext.BeginDialogAsync(option6.Id, cancellationToken: cancellationToken);
    }

    return await stepContext.NextAsync();
});

选项5对话框的类代码:

AddStep(async (stepContext, cancellationToken) =>
{
    var cardAttachment = CreateAdaptiveCardAttachment("Adaptivecard.json");

    var reply = stepContext.Context.Activity.CreateReply();
    reply.Attachments = new List<Microsoft.Bot.Schema.Attachment>() { cardAttachment };

    await stepContext.Context.SendActivityAsync(reply, cancellationToken);
    var opts = new PromptOptions
    {
        Prompt = new Activity
        {
            Type = ActivityTypes.Message,
            // You can comment this out if you don't want to display any text. Still works.
        }
    };

    // Display a Text Prompt and wait for input
    return await stepContext.PromptAsync(nameof(TextPrompt), opts);
});

AddStep(async (stepContext, cancellationToken) =>
{
    var res = stepContext.Result.ToString();

    dynamic modelrequestdata = JsonConvert.DeserializeObject(res);

    string canceloptionvalidaiton = modelrequestdata.Cancel;
    if (canceloptionvalidaiton == "0")
    {
        // ...perform operation
        return await stepContext.EndDialogAsync();
    }
    else
    {
        return await stepContext.EndDialogAsync();
    }
});

请注意,我故意没有提供完整的代码,以便于理解和其他目的。

我保留取消按钮的主要想法是取消当前操作,以便用户可以转到主对话框选项,选择要执行的其他任务

查询为:

  1. 如果以上逻辑不正确,如何启用自适应卡中的取消按钮?
  2. 我们在自适应卡中可以有取消按钮吗?还是一个错误的假设,我们不能取消选择?

已于2019年11月8日更新

下面的更新是为了使我的查询更清楚和更好地理解:

1)通过Web通道启动BOT时,在后端触发主根对话框,该对话框将所有对话框和所有内容添加到堆栈中:

下面是主要的根对话框类代码:

using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Choices;
using Microsoft.Bot.Schema;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace EchoBot.Dialogs
{
    public class MainRootDialog : ComponentDialog
    {
        public MainRootDialog(UserState userState)
            : base("root")
        {
            _userStateAccessor = userState.CreateProperty<JObject>("result");

            AddDialog(DisplayOptionsDialog.Instance);
            AddDialog(Option1.Instance);
            AddDialog(Option2.Instance);
            AddDialog(Option3.Instance);
            AddDialog(Option4.Instance);
            AddDialog(Option5.Instance);
            AddDialog(Option6.Instance);          
            AddDialog(new ChoicePrompt("choicePrompt"));
            InitialDialogId = DisplayOptionsDialog.Id;
        }
    }
}

2)由于显示了初始对话框options对话框,因此在用户的前端显示了以下提示选项:

Option1 Option2 Option3 Option4 Option5 Option6

这是通过以下代码完成的,这些代码是我在名为DisplayOptionsDialog的类中编写的:

using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Choices;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace EchoBot.Dialogs
{
    public class DisplayOptionsDialog : WaterfallDialog
    {
        public DisplayOptionsDialog(string dialogId, IEnumerable<WaterfallStep> steps = null)
            : base(dialogId, steps)
        {
            AddStep(async (stepContext, cancellationToken) =>
            {               

                    return await stepContext.PromptAsync(
                        "choicePrompt",
                        new PromptOptions
                        {
                            Prompt = stepContext.Context.Activity.CreateReply("Below are the options you can avail. Please click/choose any one from the following: "),
                            Choices = new[] { new Choice { Value = "Option1" }, new Choice { Value = "Option2" }, new Choice { Value = "Option3" }, new Choice { Value = "Option4" }, new Choice { Value = "Option5" }, new Choice { Value = "Option6" }}.ToList(),
                            RetryPrompt = stepContext.Context.Activity.CreateReply("Sorry, I did not understand that. Please choose any one from the options displayed below: "),
                        });

            });

            AddStep(async (stepContext, cancellationToken) =>
            {
                var response = (stepContext.Result as FoundChoice)?.Value;

                if (response == "Option1")
                {
                    await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Otpion 1 selected")); //Here there is lot of actual data printing that i am doing but due //to some sensitive inoformation i have kept a simple statment that gets //displayed but in actual code it is just printing back or responding back few //statements which again printing only                                             
                }

                if (response == "Option2")
                {
                    return await stepContext.BeginDialogAsync(Option2.Id, cancellationToken: cancellationToken);
                }

                if (response == "Option3")
                {
                    return await stepContext.BeginDialogAsync(Option3.Id, cancellationToken: cancellationToken);
                }

                if (response == "Option4")
                {
                    return await stepContext.BeginDialogAsync(Option4.Id, cancellationToken: cancellationToken);
                }

                if (response == "Option5")
                {
                    return await stepContext.BeginDialogAsync(Option5.Id, cancellationToken: cancellationToken);
                }

                if (response == "Option6")
                {
                    return await stepContext.BeginDialogAsync(Option6.Id, cancellationToken: cancellationToken);
                }               

                return await stepContext.NextAsync();
            });

            AddStep(async (stepContext, cancellationToken) => 
            {
                return await stepContext.ReplaceDialogAsync(Id);
            });

        }

        public static new string Id => "DisplayOptionsDialog";

        public static DisplayOptionsDialog Instance { get; } = new DisplayOptionsDialog(Id);
    }
}

3)由于问题是用户选择Option5,因此我将直接转到option5对话框的类代码:

using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace EchoBot.Dialogs
{
    public class Option5Dialog : WaterfallDialog
    {
        public const string cards = @"./ModelAdaptivecard.json";
        public Option5Dialog(string dialogId, IEnumerable<WaterfallStep> steps = null)
           : base(dialogId, steps)
        {

            AddStep(async (stepContext, cancellationToken) =>
           {
               var cardAttachment = CreateAdaptiveCardAttachment(cards);

               var reply = stepContext.Context.Activity.CreateReply();
               reply.Attachments = new List<Microsoft.Bot.Schema.Attachment>() { cardAttachment };

               await stepContext.Context.SendActivityAsync(reply, cancellationToken);
               var opts = new PromptOptions
               {
                   Prompt = new Activity
                   {
                       Type = ActivityTypes.Message,
                       // You can comment this out if you don't want to display any text. Still works.
                   }
               };

               // Display a Text Prompt and wait for input
               return await stepContext.PromptAsync(nameof(TextPrompt), opts);
           });

            AddStep(async (stepContext, cancellationToken) =>
            {               
                var activityTextformat = stepContext.Context.Activity.TextFormat;


                if (activityTextformat == "plain")
                {
                    await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Sorry, i did not understand that please enter proper details in below displayed form and click on submit button for processing your request"));
                    return await stepContext.ReplaceDialogAsync(Id, cancellationToken: cancellationToken);
                }

                else
                {
                    var res = stepContext.Result.ToString();

                    dynamic modelrequestdata = JsonConvert.DeserializeObject(res);

                    string canceloptionvalidaiton = modelrequestdata.Cancel;

                    if (canceloptionvalidaiton == "0")
                    {
                        string ServiceRequesterName = modelrequestdata.RequesterName;
                        string ServiceRequesterEmail = modelrequestdata.RequesterEmail;
                        string ServiceRequestCustomerName = modelrequestdata.CustomerName;
                        string ServiceRequestType = modelrequestdata.RequestType;
                        string ServiceRequestAssetNames = modelrequestdata.AssetsList;


                        //checking wehther data is provided or not
                        if (string.IsNullOrWhiteSpace(ServiceRequesterName) || string.IsNullOrWhiteSpace(ServiceRequesterEmail) || string.IsNullOrWhiteSpace(ServiceRequestCustomerName) || string.IsNullOrWhiteSpace(ServiceRequestAssetNames))
                        {
                            await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Mandatory fields such as Requester name,Requester Email,Cusomter Name or Asset details are not selected are not provided"));

                            return await stepContext.ReplaceDialogAsync(Id, cancellationToken: cancellationToken);
                        }
                        else
                        {
                            await stepContext.Context.SendActivityAsync(MessageFactory.Text("Data recorded successfully"));
                            await stepContext.Context.SendActivityAsync(MessageFactory.Text("Thank You!.Looking forward to see you again."));

                            return await stepContext.EndDialogAsync();
                        }
                    }
                    else
                    {
                        await stepContext.Context.SendActivityAsync(MessageFactory.Text("Looks like you have cancelled the Model/License request"));
                        await stepContext.Context.SendActivityAsync(MessageFactory.Text("Thank You!.Looking forward to see you again."));

                        return await stepContext.EndDialogAsync();
                    }
                }
            });
        }
        public static new string Id => "Option5Dialog";

        public static Option5Dialog Instance { get; } = new Option5Dialog(Id);

        public static Microsoft.Bot.Schema.Attachment CreateAdaptiveCardAttachment(string filePath)
        {
            var adaptiveCardJson = File.ReadAllText(filePath);
            var adaptiveCardAttachment = new Microsoft.Bot.Schema.Attachment()
            {
                ContentType = "application/vnd.microsoft.card.adaptive",
                Content = JsonConvert.DeserializeObject(adaptiveCardJson),
            };
            return adaptiveCardAttachment;
        }
    }
}

在选项5的此过程中,发生或观察到了一些事情,以及在积极测试或消极测试中都发生了其他事情:

  1. 用户提供的自适应卡中的数据显示为Option5的一部分,然后单击“提交”按钮,用户将创建消息请求ID等,如上述代码所示,同时结束对话框和Option1的相同默认选项到6显示为defaultDisplayoptions对话框类的一部分

  2. 现在,用户再次向上滚动并单击“提交”按钮,但是正如我们观察到的那样,根据代码和显示的选项,用户位于defaultoptions对话框中

显示用户: 抱歉,我不明白。请从下面显示的选项中选择一个:

Option1 Option2 Option3 Option4 Option5 Option6

这正在按需且按预期工作,因此这里没有问题。

  1. 这是相同的情况,我曾经单击“提交”按钮

  2. 现在我上一次单击取消按钮,这次控件直接转到Displayoptions-> Option1,并且打印了该块中的语句

调试时,我注意到displayoptions对话框中的stepcontext已预先填充或预选为Option1的文本值或选项,而我没有选择该选项,因此它正在其下打印语句。

不确定其执行方式以及执行原因。因此,我以为自己可能以这种方式(我做的方式)包括了取消按钮(可能是错误的),因此我在本文中询问了查询如何在自适应卡中实现取消按钮功能。

但是,如果我做的是正确的方法,您能告诉我为什么这个问题是wrt only cancel button的地方,当控制权转到DiaplayOptions对话框时,选项1被预先选择了,为什么一切正常,wrt Submit按钮(在这种情况下,任何时候都没有问题)。

考虑到我更新的信息和查询,您能在问题上帮我吗?

1 个答案:

答案 0 :(得分:0)

我已经通过电子邮件收到了您的验证码,并设法提取了一些问题的答案。

  

我们知道,在将转弯上下文的活动传递给对话框之前,您必须对其进行操作,否则您的文本提示不能用于基于对象的提交操作。

我要的代码在您的DialogExtensions.Run类中:

Activity activity = dialogContext.Context.Activity;
object rawChannelData = activity.ChannelData;

if (dialogContext.Context.Activity.Value != null && dialogContext.Context.Activity.Text == null)
{
    dialogContext.Context.Activity.Text = turnContext.Activity.Value.ToString();
}

您可以看到放置它的位置不好,因为您显然忘记了它的存在。放置它的另一个不好的地方是您应该使用内置的DialogExtensions.RunAsync方法。

正在发生的事情是,您正在将来自Adaptive Card的Submit操作的序列化JSON传递到任何活动对话框中。因此,如果活动对话框是一个选择提示,它将尝试将序列化的JSON解释为选择之一。单击取消按钮后,该JSON将包含"Cancel": 1,而1使识别器认为您要使用选项1。

最简单的解决方案当然是重新加工您的自适应卡,使其不包含任何数字,但这当然是一个临时性修复,可能无法在您将来的所有情况下使用。

您实际上没有说出您期望/期望的行为是什么,但我可以想到两个主要选择:

  1. 您希望在该提示之外单击提交动作时将其忽略
  2. 无论哪个对话框处于活动状态,您都希望取消按钮取消任何对话框

我可以通过您的代码猜测您可能打算让按钮仅在该提示下起作用。由于使用的是Web Chat,因此可以考虑使用客户端解决方案,在该解决方案中,您将制作自己的Adaptive Cards渲染器,该渲染器允许在使用卡后禁用提交操作。我认为解决方案比您想要的要难得多,但是也有一些方法可以使漫游器在特定情况下忽略提交操作。您可以查看Michael Richardson's Adaptive Card prompt的一些想法,也可以投票赞成my Adaptive Cards community project

如果您希望取消按钮适用于任何对话框,只需确保通过调用CancelAllDialogsAsync而不是ContinueDialogAsync来响应其活动即可。

  

在主根对话框中如何生成“响应”

这是我要的那一行:

var response = (stepContext.Result as FoundChoice)?.Value;

您已经莫名其妙地从“主根对话框”中省略了该行,尽管我注意到当您以DisplayOptionsDialog的名称重复粘贴该代码时,该行包括在内。如果您不遗漏重要信息,或者至少在被询问时提供信息,将来您将能够更快地获得更好的帮助。

有关在Bot Framework中使用自适应卡的更多信息,请参考my latest blog post