机器人框架v4.0如何在对话中执行上一个瀑布流步骤

14

我正在尝试创建一个对话框,其中包含多个瀑布步骤。在这个对话框的上下文中,根据用户的选择,有时需要返回到先前的瀑布步骤。我找到了这种方法:

 await stepContext.ReplaceDialogAsync("Name of the dialog");

然而,这种方法会重新执行整个对话,这不是我所需要的。

事实上,我创建的瀑布步骤有三步:

  • ChoiceCallStepAsync: 第一步将列出用户的前10个通话,并提供选项以显示较旧的通话记录
  • ShowCallStepAsync: 第二步将显示用户选择的通话记录,或如果用户点击“显示更旧”则返回第一步
  • EndDialog: 第三步将终止对话

我的代码如下:

public class ListAllCallsDialog : ComponentDialog
    {

        // Dialog IDs
        private const string ProfileDialog = "ListAllCallsDialog";



        /// <summary>
        /// Initializes a new instance of the <see cref="ListAllCallsDialog"/> class.
        /// </summary>
        /// <param name="loggerFactory">The <see cref="ILoggerFactory"/> that enables logging and tracing.</param>
        public ListAllCallsDialog(ILoggerFactory loggerFactory)
            : base(nameof(ListAllCallsDialog))
        {
            // Add control flow dialogs
            var waterfallSteps = new WaterfallStep[]
            {
                   ListAllCallsDialogSteps.ChoiceCallStepAsync,
                   ListAllCallsDialogSteps.ShowCallStepAsync,
                   ListAllCallsDialogSteps.EndDialog,
            };
            AddDialog(new WaterfallDialog(ProfileDialog, waterfallSteps));
            AddDialog(new ChoicePrompt("cardPrompt"));
        }

        /// <summary>
        /// Contains the waterfall dialog steps for the main dialog.
        /// </summary>
        private static class ListAllCallsDialogSteps
        {
            static int callListDepth = 0;
            static List<string> Calls;
            public static async Task<DialogTurnResult> ChoiceCallStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
            {
                await stepContext.Context.SendActivityAsync(
                   "Right now i'm in list all calls dialog",
                   cancellationToken: cancellationToken);
                GetAllCalls();
                return await stepContext.PromptAsync("cardPrompt", GenerateOptions(stepContext.Context.Activity, callListDepth), cancellationToken);
            }

            public static async Task<DialogTurnResult> ShowCallStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
            {
                // Get the text from the activity to use to show the correct card
                var text = stepContext.Context.Activity.Text.ToLowerInvariant();
                if(text == "Show older")
                    //Go back to the first step
                else if(text == "Show earlier")
                    //Go back to the first step
                else
                    await stepContext.Context.SendActivityAsync(
                   "The call you choose is : " + text.ToString(),
                   cancellationToken: cancellationToken);
                   return await stepContext.ContinueDialogAsync();

            }

            public static async Task<DialogTurnResult> EndDialog(WaterfallStepContext stepContext, CancellationToken cancellationToken)
            {
                await stepContext.Context.SendActivityAsync(
               "Getting back to the parent Dialog",
               cancellationToken: cancellationToken);
                return await stepContext.EndDialogAsync(null, cancellationToken);
            }

            /// <summary>
            /// Creates options for a <see cref="ChoicePrompt"/> so the user may select an option.
            /// </summary>
            /// <param name="activity">The message activity the bot received.</param>
            /// <returns>A <see cref="PromptOptions"/> to be used in a prompt.</returns>
            /// <remarks>Related type <see cref="Choice"/>.</remarks>
            private static PromptOptions GenerateOptions(Activity activity, int callListDepth)
            {
                // Create options for the prompt
                var options = new PromptOptions()
                {
                    Prompt = activity.CreateReply("Please choose a call from the list below"),
                    Choices = new List<Choice>(),
                };


                for(int i=10*callListDepth; i <= 10 * (callListDepth + 1); i++)
                {
                    if (Calls.ElementAtOrDefault(i) != null)
                      options.Choices.Add(new Choice() { Value = Calls[i] });

                }
                options.Choices.Add(new Choice() { Value = "Show older" });
                if(callListDepth!=0)
                    options.Choices.Add(new Choice() { Value = "Show earlier" });
                return options;
            }
            private static void GetAllCalls()
            {

                //List of all calls found
                for (int i = 0; i < 30; i++)
                  Calls.Add("Call" + i.ToString());
            }

        }

有人可以给我演示一下如何做吗?


1
我其实也遇到了同样的问题,如果你找到了解决方案,请与我们分享。 - Ziad Akiki
@ZiadAkiki,请看下面我的回答,或许能帮到你。 - Liam Kernighan
3个回答

20

我不确定这是否是正确和有效的方法,但您可以在Task<DialogTurnResult>函数中尝试使用context.ActiveDialogState属性进行实验。

context.ActiveDialog.State["stepIndex"] = (int)context.ActiveDialog.State["stepIndex"] -2;

2
这个有效。如果(需要返回),则stepContext.ActiveDialog.State["stepIndex"] = (int)stepContext.ActiveDialog.State["stepIndex"] - 2;return await AskStep1Async(stepContext, cancellationToken); - Oyen
2
@Oyen 我只是像往常一样调用NextAsync或ContiniueDialogAsync,没有任何问题。如果我想返回到当前所在的对话框,则为-1,如果内置验证由于某种原因不适用,则为-2,如果想回到上一个对话框,则为-2。 - Liam Kernighan
是的,我支持你的回答,因为他们还没有将其标记为答案。 - Oyen

6

瀑布对话框并没有设计“向后遍历”的概念,尽管我可以理解这种可能性的存在。我找到的唯一解决方案是将你的瀑布对话拆分成较小的“微型”瀑布对话,并将它们嵌套到一个较大的瀑布对话中。

        // define and add waterfall dialogs (main)
        WaterfallStep[] welcomeDialogSteps = new WaterfallStep[]
        {
            MainDialogSteps.PresentMenuAsync,
            MainDialogSteps.ProcessInputAsync,
            MainDialogSteps.RepeatMenuAsync,
        };

然后在MainDialogSteps.ProcessInputAsync中:

        public static async Task<DialogTurnResult> ProcessInputAsync(
            WaterfallStepContext stepContext,
            CancellationToken cancellationToken)
        {
            var choice = (FoundChoice)stepContext.Result;
            var dialogId = Lists.WelcomeOptions[choice.Index].DialogName;

            return await stepContext.BeginDialogAsync(dialogId, null, cancellationToken);
        }

这使用户可以在主对话栈内启动新的对话。我提供的其中一种选项是提示一个电话号码列表:

        WaterfallStep[] phoneChoiceDialogSteps = new WaterfallStep[]
        {
            PhoneChoicePromptSteps.PromptForPhoneAsync,
            PhoneChoicePromptSteps.ConfirmPhoneAsync,
            PhoneChoicePromptSteps.ProcessInputAsync,
        };

        Add(new WaterfallDialog(Dialogs.PhonePrompt, phoneChoiceDialogSteps));

最后,在PhoneChoicePromptSteps.ProcessInputAsync中,我允许从确认ReplaceDialogAsync的选择中选择“否”,并有效地重置了这个较小的瀑布流,而不影响整个瀑布流的其余部分:

 public static async Task<DialogTurnResult> ProcessInputAsync(
            WaterfallStepContext stepContext,
            CancellationToken cancellationToken)
        {
            if ((bool)stepContext.Result)
            {
                await stepContext.Context.SendActivityAsync(
                    $"Calling {stepContext.Values[Outputs.PhoneNumber]}",
                    cancellationToken: cancellationToken);
                return await stepContext.EndDialogAsync(null, cancellationToken);
            }
            else
            {
                return await stepContext.ReplaceDialogAsync(Dialogs.PhonePrompt, null, cancellationToken);
            } 
        }

enter image description here


我明白。简而言之,将应该重新执行的瀑布步骤放在小型瀑布步骤的头部应该可以解决问题。这样,通过在最终瀑布步骤中用包含小型瀑布步骤的对话框替换自身来重新执行此瀑布步骤。 - Soufien Hajji
3
没问题。这样,你的用户就可以继续与机器人对话,如果需要进一步的“您选择了 XYZ,请确认吗?”步骤,你也可以循环执行该步骤。 - JJ_Wailes

2
你可以在方法“ReplaceDialogAsync”中使用选项参数,并使用方法“NextAsync”跳过步骤。
例如,在我的瀑布流步骤(在构造函数中定义):
        AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
        {
            IntroStepAsync,
            ActStepAsync,
            FinalStepAsync
        }));

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

如果你想从最后一步(FinalStepAsync)通过到达第二步(在我的情况下是ActStepAsync),当我要替换对话框时,我会在对话框中创建一个标签。
private const string FLAG = "MY_FLAG";

当我调用最终步骤的方法时,我会这样做:
return await stepContext.ReplaceDialogAsync(InitialDialogId, FLAG, cancellationToken);

所以,如果上下文具有标志,则我只需要在第一步中勾选该选项。
    // Use the text provided in FinalStepAsync or the default if it is the first time.
        var messageText = stepContext.Options?.ToString() ?? "welcome-message";
        if (messageText.Equals(FLAG_REPROMPT))
        {
            return await stepContext.NextAsync(null,cancellationToken);
        }

稍后,这是第二步。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接