簡體   English   中英

如何在 Web 頻道聊天機器人中顯示自適應卡片中的取消按鈕,該聊天機器人在 MS Bot 框架 SDK V4 中的 C# 中開發?

[英]How to have cancel button in Adaptive cards to be displayed in Web Channel chat bot developed in MS Bot framework SDK V4 in C#?

我在 C# 中使用 MS Bot Framework SDK V4 為 Web 頻道開發了聊天機器人,其中有多個瀑布對話框類,每個類都執行特定任務。 在主根對話框中,我有一組選項顯示選項 1、2、3、4...6。 現在當我 select 選項 5 我被重定向到一個新對話框 class 時

我有一張自適應卡,我設計了 3 組容器,一個通過文本框輸入文本,第二個容器有一些復選框可供選擇,第三個容器包含 2 個按鈕提交和取消按鈕。 對於這些按鈕,我將數據分別設置為 Cancel = 0 和 1。 在此選項 5 對話框中,我根據數據取消 0 或 1 進行控制,如果它是 1,我正在執行結束對話框並顯示默認顯示選項 1、2、3、4...6。 現在,我通過輸入有效值單擊提交按鈕,該過程已成功完成,結果當前對話框已結束,並再次顯示主要選項集。

在這里,我做了一些負面測試,我向上滾動並單擊上面顯示的取消按鈕。 這導致在選項集中顯示的第一個選項(選項 1 ) 1 到 6 選擇了默認值,並且即使我選擇了取消而不是第一個選項,該選項操作也會自動執行。 但是,當我向上滾動后顯示在自適應卡中的 select 提交按鈕顯示重試提示 select 以下任何一個選項時,這不會發生,當我單擊取消時,默認情況下它會轉到第一個選項。

請在下面找到對話相關和自適應卡相關數據:

{
    "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 對話框 class 代碼:

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();
    }
});

請注意,為了便於理解和其他目的,我故意沒有提供整個代碼。

我保留取消按鈕的主要想法是取消當前操作,以便用戶可以 go 到主對話框選項 select 執行任何其他任務

查詢是:

  1. 如果我的上述邏輯不正確,如何在自適應卡中啟用取消按鈕?
  2. 我們可以在自適應卡中設置取消按鈕嗎? 或者這是一個錯誤的假設,我們不能有取消選項?

2019年11月8日更新

以下更新是為了清楚和更好地理解我的查詢:

1) 當 BOT 通過 Web 啟動時,通道主根對話框在后端觸發,所有對話框和內容都添加到堆棧中:

下面是主根對話框 class 代碼:

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)由於初始對話框顯示選項對話框,因此在前端向用戶顯示以下提示選項:

選項1 選項2 選項3 選項4 選項5 選項6

這是我通過以下代碼實現的,這些代碼我在名為 DisplayOptionsDialog 的 class 中編寫:

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)由於問題 w.r.t 用戶選擇 Option5 我將直接 go 到 option5 對話框 class 代碼:

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. 用戶在自適應卡中提供的數據顯示為選項 5 的一部分並單擊提交按鈕用戶獲取創建的消息請求 ID 等,如上面的代碼所示,同時對話框結束,選項 1 到 6 的相同默認選項顯示為部分默認顯示選項對話框 class

  2. 現在用戶再次向上滾動並單擊提交按鈕,但我們觀察到用戶在默認選項對話框中,按照代碼和顯示的選項

顯示用戶:對不起,我不明白。 請從以下顯示的選項中選擇任何一項:

選項1 選項2 選項3 選項4 選項5 選項6

這是按需要和預期工作的,所以這里沒有問題。

  1. 這是我點擊提交按鈕多少次的情況

  2. 現在我上去單擊取消按鈕,這次控件直接轉到 Displayoptions->Option1 並打印了該塊中存在的語句

當我調試時,我注意到 displayoptions 對話框中的 stepcontext 具有預先填充或預先選擇為 Option1 的文本值或選項,而我沒有選擇該選項,因此它正在打印它下面的語句。

不知道它是如何做到的以及為什么這樣做。 所以我認為我自己可能會以這種方式包含取消按鈕(我所做的方式)是錯誤的可能還有另一種方式,我在這篇文章中詢問了如何在自適應卡中實現取消按鈕功能。

但是,如果我所做的是正確的方法,請告訴我為什么問題是 w.r.t 僅取消按鈕,當控制進入 DiaplayOptions 對話框時,選項 1 以某種方式被預先選擇,因為一切正常 w。 r.t 提交按鈕(在這種情況下任何時候都沒有問題)。

考慮到我更新的信息和查詢,您能幫我解決這個問題嗎?

我已收到您通過 email 收到的代碼,並設法提取了我的一些問題的答案。

我們知道您必須在將轉彎上下文的活動傳遞給對話框之前對其進行操作,否則您的文本提示將無法與基於對象的提交操作一起使用。

我要求的代碼在您的DialogExtensions.Run class 中:

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方法。

發生的事情是,您將序列化的 JSON 從自適應卡的提交操作傳遞到任何處於活動狀態的對話框中。 因此,如果活動對話框是一個選擇提示,它將嘗試將序列化的 JSON 解釋為選擇之一。 單擊取消按鈕時, JSON 將包含"Cancel": 1 ,並且1使識別器認為您想要 go 選項 1。

最簡單的解決方案當然是重新設計您的自適應卡,使其不包含任何數字,但這當然是一個臨時修復,可能不適用於您未來的所有場景。

您實際上並沒有說出您的預期/期望行為是什么,但我可以想到兩個主要選擇:

  1. 您希望在該提示之外單擊提交操作時忽略它們
  2. 無論哪個對話框處於活動狀態,您都希望取消按鈕取消任何對話框

我可以通過您的代碼猜測您可能打算讓按鈕僅在那個提示中起作用。 由於您使用的是 Web 聊天,因此您可能會考慮使用客戶端解決方案,您可以在其中制作自己的自適應卡片渲染器,允許在卡片使用后禁用提交操作。 我認為解決方案比您想要的更困難,但也有一些方法可以讓機器人在特定情況下忽略提交操作。 您可以查看Michael Richardson 的 Adaptive Card 提示以獲得一些想法,也可以投票支持我的 Adaptive Cards 社區項目

如果您希望取消按鈕適用於任何對話框,只需確保通過調用CancelAllDialogsAsync而不是ContinueDialogAsync來響應其活動。

如何在您的主根對話框中生成“響應”

這是我要求的行:

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

您從“主根對話框”中莫名其妙地省略了該行,盡管我注意到當您以DisplayOptionsDialog名稱冗余粘貼該代碼時,該行已包含在內。 將來,如果您不遺漏重要信息,或者至少在被問到時提供,您將能夠更快地獲得更好的幫助。

請參閱我最新的博客文章,了解有關在 Bot Framework 中使用自適應卡片的更多信息。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM