Bot framework v4.0 как выполнить предыдущий шаг водопада в диалоговом окне

Я пытаюсь создать диалог, в котором я определяю несколько шагов водопада. В контексте этого диалога мне иногда нужно вернуться к предыдущему шагу водопада в соответствии с выбором пользователя. Нашел такой способ:

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

        }

}

Кто-нибудь может показать мне, как это сделать, пожалуйста?


person Soufien Hajji    schedule 28.09.2018    source источник
comment
На самом деле у меня такая же проблема, если вы нашли решение, поделитесь им с нами   -  person Ziad Akiki    schedule 04.10.2018
comment
@ZiadAkiki прочтите мой ответ ниже, может быть, он вам поможет.   -  person Liam Kernighan    schedule 09.10.2018


Ответы (3)


Я не уверен, что это правильный и эффективный способ сделать это, но вы можете поэкспериментировать со свойством State функции context.ActiveDialog в своей функции Task<DialogTurnResult>.

context.ActiveDialog.State["stepIndex"] = (int)context.ActiveDialog.State["stepIndex"] -2;
person Liam Kernighan    schedule 09.10.2018
comment
Это работает. Если (нужно вернуться), то stepContext.ActiveDialog.State [stepIndex] = (int) stepContext.ActiveDialog.State [stepIndex] - 2; return await AskStep1Async (stepContext, cancellationToken); - person Oyen; 16.10.2018
comment
@Oyen Я просто вызываю NextAsync или ContiniueDialogAsync, как обычно, без каких-либо проблем. -1, если я хочу вернуться к тому же диалоговому окну, в котором я сейчас нахожусь, и встроенная проверка не подходит по какой-то причине, и -2, если я хочу вернуться к предыдущему. - person Liam Kernighan; 16.10.2018
comment
Да, я оказывал поддержку вашему автору, потому что он все еще не пометил его как ответ. - person Oyen; 16.10.2018

Диалоги «Водопад» не создавались с идеей «движения назад» для их обхода, хотя я вижу возможную необходимость в этом. Единственное решение, которое я нашел, - это разбить ваш водопад на меньшие «мини» водопады и вложить их в один большой водопад.

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

введите здесь описание изображения

person JJ_Wailes    schedule 17.10.2018
comment
Я понимаю. Вкратце, размещение ступеньки водопада, которую следует повторить, в голове миниатюрных ступеней водопада меньшего размера, должно помочь. Таким образом, повторное выполнение шага водопада выполняется путем замены диалога, содержащего мини-шаги водопада, на последнем шаге водопада. - person Soufien Hajji; 18.10.2018
comment
Точно. Таким образом, ваш пользователь может продолжить диалог с ботом, и, если вы выбрали XYZ, подтвердить? необходимы шаги, вы также можете их обойти. - person JJ_Wailes; 18.10.2018

Вы можете использовать параметр option в методе ReplaceDialogAsync и пропустить шаги с методом NextAsync.

Например, в моих шагах водопада (определенных в конструкторе):

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

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

Если вы хотите перейти ко второму шагу (в моем случае ActStepAsync) с последнего шага (FinalStepAsync), когда я собираюсь заменить диалоговое окно, я использую созданную метку в диалоговом окне:

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

Позже этот вы перейдете ко второму этапу

person Victor Muñoz    schedule 13.05.2020