Functionality:
Fetch and display relevant answer from QnAMaker
This response must be followed by an adaptive card with checkboxes
Based on the values selected, invoke a web service to provide the user with required file links.
Display a feedback message 'Did this help?'
Direct the conversation flow based on user response.
Problem Statement: For the step 2 mentioned above, the button click is being handled at the bot.cs file and redirected to a new Dialog.
Everything works fine until displaying the feedback message(which is again invoked from a new Dialog). However, after this text prompt the
next step is not called and exits with an error: The given key 'dialogs' was not present in the dictionary.
Why does it show that error without going to the next step?
bot.cs
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
if (turnContext.Activity.Type == ActivityTypes.Message)
{
if (turnContext.Activity.Text != null)
{
if (!dc.Context.Responded)
{
// examine results from active dialog
switch (dialogResult.Status)
{
case DialogTurnStatus.Empty:
switch (topIntent)
{
case ...
}
break;
case ...
}
}
}
else if (string.IsNullOrEmpty(turnContext.Activity.Text))
{
await HandleSubmitActionAsync(turnContext, userProfile);
}
}
}
private async Task HandleSubmitActionAsync(ITurnContext turnContext, UserProfile userProfile)
{
if (value.Type == "GetCredentials")
{
userProfile.credentialsCard = true;
}
await dc.BeginDialogAsync(nameof(HandleButtonDialog));
}
HandleButtonDialog:
public HandleButtonDialog(BotServices _services, UserProfile _userProfile) : base(Name)
{
botServices = _services ?? throw new ArgumentNullException(nameof(_services));
userProfile = _userProfile;
var waterfallSteps = new WaterfallStep[]
{
GetFeedbackStepAsync,
FeedbackStepAsync,
FeedbackResponseStepAsync,
};
AddDialog(new WaterfallDialog(HBFeedbackDialog));
AddDialog(new TextPrompt("userFeed"));
}
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
if...
else if(userProfile.credentialsCard == true)
{
await dc.BeginDialogAsync(HandleCredentialsFeedbackDialog.Name);
}
}
HandleCredentialsFeedbackDialog:
public class HandleCredentialsFeedbackDialog : ComponentDialog
{
public HandleCredentialsFeedbackDialog(BotServices services, UserProfile _userProfile,string dialogId = null) : base(Name)
{
botServices = services ?? throw new ArgumentNullException(nameof(services));
userProfile = _userProfile;
// This array defines how the Waterfall will execute.
var waterfallSteps = new WaterfallStep[]
{
CredsValidate,
GetFeedbackStepAsync,
FeedbackStepAsync,
FeedbackResponseStepAsync,
};
AddDialog(new TextPrompt("userFeed"));
AddDialog(new WaterfallDialog(HBFeedbackDialog, waterfallSteps));
InitialDialogId = HBFeedbackDialog;
}
public async Task<DialogTurnResult> CredsValidate(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
VALIDATE THE CHECKBOX SELECTED VALUES
....
//Invoke Web Service
var qnaReuslt = await MakeBatchRequestCreds(stepContext.Context, finalSearchList.ToArray());
return await stepContext.PromptAsync("userFeed", new PromptOptions
{
Prompt = stepContext.Context.Activity.CreateReply("Did this help?")
});
}
}
Error Stack Trace:
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at Microsoft.Bot.Builder.Dialogs.ComponentDialog.RepromptDialogAsync(ITurnContext turnContext, DialogInstance instance, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder.Dialogs\ComponentDialog.cs:line 112
at Microsoft.Bot.Builder.Dialogs.ComponentDialog.ResumeDialogAsync(DialogContext outerDc, DialogReason reason, Object result, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder.Dialogs\ComponentDialog.cs:line 106
at Microsoft.Bot.Builder.Dialogs.DialogContext.EndDialogAsync(Object result, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder.Dialogs\DialogContext.cs:line 196
at AESAiLean.Dialogs.HandleButtonDialog.BeginDialogAsync(DialogContext dc, Object options, CancellationToken cancellationToken) in C:\Users\...\Dialogs\HandleButtonDialog.cs:line 199
at Microsoft.Bot.Builder.Dialogs.DialogContext.BeginDialogAsync(String dialogId, Object options, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder.Dialogs\DialogContext.cs:line 113
at AESAiLean.AESAiLeanBot.HandleSubmitActionAsync(ITurnContext turnContext, UserProfile userProfile) in C:\Users\...\Bots\AESAiLeanBot.cs:line 361
Related
I have a problem with my component dialogs in botframework V4.
I'm using a root dialog which contains a Waterfalldialog and an Setupdialog. The initial dialog is the Setupdialog. Like so:
public RootDialog(SetupDialog setupDialog)
: base(nameof(RootDialog))
{
AddDialog(setupDialog);
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
ProcessStep
}));
InitialDialogId = nameof(SetupDialog);
}
In the setup dialog I'm asking for some value. If the setup dialogs continues I check the value and if its like I wanted, I end the dialog.
public override Task<DialogTurnResult> ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default)
{
if (!int.TryParse(outerDc.Context.Activity.Text, out var recursions))
{
return outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text($"{outerDc.Context.Activity.Text} konnte nicht in eine Zahl konvertiert werden.")
});
}
return outerDc.EndDialogAsync(recursions);
}
If I end the dialog like this, shouldn't be ResumeDialog be called in the RootDialog?
Here the whole Dialogs:
public class RootDialog : ComponentDialog
{
private int recursions;
public RootDialog(SetupDialog setupDialog)
: base(nameof(RootDialog))
{
AddDialog(setupDialog);
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
ProcessStep
}));
InitialDialogId = nameof(SetupDialog);
}
public async Task<DialogTurnResult> ProcessStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Process step in root dialog")
});
if(Dialogs.Find(nameof(RecursiveDialog)) == null)
{
AddDialog(new RecursiveDialog(new DialogSet(), recursions));
}
if (recursions > 0)
{
return await stepContext.BeginDialogAsync(nameof(RecursiveDialog));
}
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Recursion lower or eqaul 0")
});
}
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default)
{
var dialogContext = CreateChildContext(outerDc);
await dialogContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Begin root dialog")
});
return await base.BeginDialogAsync(outerDc, options, cancellationToken);
}
public override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
if(innerDc.ActiveDialog != null)
{
return await innerDc.ContinueDialogAsync();
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
public override async Task<DialogTurnResult> ResumeDialogAsync(DialogContext outerDc, DialogReason reason, object result = null, CancellationToken cancellationToken = default)
{
recursions = Convert.ToInt32(result);
await outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text($"Resume root dialog with recursion value {recursions}")
});
return await outerDc.BeginDialogAsync(nameof(WaterfallDialog));
}
}
SetupDialog:
public class SetupDialog : ComponentDialog
{
public SetupDialog()
: base(nameof(SetupDialog))
{
AddDialog(new TextPrompt(nameof(TextPrompt)));
}
public override Task<DialogTurnResult> ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default)
{
if (!int.TryParse(outerDc.Context.Activity.Text, out var recursions))
{
return outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text($"{outerDc.Context.Activity.Text} konnte nicht in eine Zahl konvertiert werden.")
});
}
return outerDc.EndDialogAsync(recursions);
}
public override Task<DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default)
{
return innerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text("Wie viel rekursionen sollen erstellt werden?")
});
}
}
So, I got the ResumeDialog method to be triggerd.
To trigger the ResumeDialog method, the dialog that ended and the dialog you want to resume to has to be on the same dialog stack!
My scenario were BotDialogContext[RootDialog] -> RootDialogContext[SetupDialog], but I need a context like this BotDialogContext[RootDialog, SetupDialog].
One of the problems is, that every ComponentDialog you start creates its own DialogContext. So if you begin a dialog within a dialog its pushed on the stack of the inner DialogContext and so on. But the description of the ResumeDialog method is
Called when a child dialog on the parent's dialog stack completed this turn, returning control to this dialog component.
To put an child dialog on the parents dialog stack you had to call the BeginDialog method on the outer dialog context. This context also needs to have the "child dialog" in its dialogset.
Here my example:
RootDialog.cs:
public class RootDialog : ComponentDialog
{
private int recursions;
public RootDialog(SetupDialog setupDialog)
: base(nameof(RootDialog))
{
AddDialog(setupDialog);
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
ProcessStep
}));
InitialDialogId = nameof(SetupDialog);
}
public async Task<DialogTurnResult> ProcessStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Process step in root dialog")
});
if(Dialogs.Find(nameof(RecursiveDialog)) == null)
{
AddDialog(new RecursiveDialog(new DialogSet(), recursions));
}
if (recursions > 0)
{
return await stepContext.BeginDialogAsync(nameof(RecursiveDialog));
}
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Recursion lower or eqaul 0")
});
}
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default)
{
if (true)
{
return await outerDc.BeginDialogAsync(nameof(SetupDialog));
}
var dialogContext = CreateChildContext(outerDc);
await dialogContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Begin root dialog")
});
return await base.BeginDialogAsync(outerDc, options, cancellationToken);
}
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
if(innerDc.ActiveDialog != null)
{
return await innerDc.ContinueDialogAsync();
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
public override async Task<DialogTurnResult> ResumeDialogAsync(DialogContext outerDc, DialogReason reason, object result = null, CancellationToken cancellationToken = default)
{
recursions = Convert.ToInt32(result);
await outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text($"Resume root dialog with recursion value {recursions}")
});
return await outerDc.BeginDialogAsync(nameof(WaterfallDialog));
}
}
SetupDialog.cs:
public class SetupDialog : ComponentDialog
{
public SetupDialog()
: base(nameof(SetupDialog))
{
AddDialog(new TextPrompt(nameof(TextPrompt)));
}
public override Task<DialogTurnResult> ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default)
{
if (!int.TryParse(outerDc.Context.Activity.Text, out var recursions))
{
return outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text($"{outerDc.Context.Activity.Text} konnte nicht in eine Zahl konvertiert werden.")
});
}
return outerDc.EndDialogAsync(recursions);
}
public override Task<DialogTurnResult> BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default)
{
return base.BeginDialogAsync(outerDc, options, cancellationToken);
}
protected override Task<DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default)
{
return innerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text("Wie viel rekursionen sollen erstellt werden?")
});
}
}
DialogBot.cs:
public class DialogBot<T> : ActivityHandler
where T : Dialog
{
protected readonly DialogSet Dialogs;
protected readonly BotState ConversationState;
protected readonly BotState UserState;
protected readonly ILogger Logger;
public DialogBot(ConversationState conversationState, UserState userState, IEnumerable<Dialog> dialogs, ILogger<DialogBot<T>> logger)
{
ConversationState = conversationState;
UserState = userState;
Logger = logger;
Dialogs = new DialogSet(conversationState.CreateProperty<DialogState>(nameof(DialogState)));
foreach(var dialog in dialogs)
{
Dialogs.Add(dialog);
}
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
// Save any state changes that might have occured during the turn.
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Running dialog with Message Activity.");
var dc = await Dialogs.CreateContextAsync(turnContext, cancellationToken).ConfigureAwait(false);
if (dc.ActiveDialog != null)
{
await dc.ContinueDialogAsync();
}
else
{
// Run the Dialog with the new message Activity.
await dc.BeginDialogAsync(typeof(T).Name, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
}
}
}
Inside the IEnumerable are both (RootDialog & SetupDialog) to get both dialogs into to the BotDialogContext and DialogSet
I have 3 dialogs in my bot framework v4 C# project, having waterfall steps in each.
In my second dialog have to behave the same as the first dialog but it doesn't have to execute all the steps of the first dialog, which means I need to skip the first step.
So is there any method to invoke all the waterfall steps of the first dialog into the other dialog by skipping the first step of the first dialog.
A waterfall once formed is supposed to be executed in a step-by-step fashion. However, you can do the formation of your waterfall steps conditionally.
The condition here can be based on DialogId.
It's quite difficult to understand what exactly you're trying to do so, I've formed a sample solution for you. Hope you're trying to do the same.
Please refer the below code for the same :
MainDialog.cs
namespace EchoBot.Dialogs
{
public class MainDialog : ComponentDialog
{
private readonly BotStateService _botStateService;
public MainDialog(BotStateService botStateService) : base(nameof(MainDialog))
{
_botStateService = botStateService;
InitializeWaterfallDialog();
}
private void InitializeWaterfallDialog()
{
var waterfallSteps = new WaterfallStep[]
{
InitialStepAsync,
FinalStepAsync
};
AddDialog(new SecondDialog($"{nameof(MainDialog)}.second", _botStateService));
AddDialog(new FirstDialog($"{nameof(MainDialog)}.first", _botStateService));
AddDialog(new FirstDialog($"{nameof(SecondDialog)}.firstFromSecond", _botStateService));
AddDialog(new WaterfallDialog($"{nameof(MainDialog)}.mainFlow", waterfallSteps));
InitialDialogId = $"{nameof(MainDialog)}.mainFlow";
}
private async Task<DialogTurnResult> InitialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
if (Regex.Match(stepContext.Context.Activity.Text.ToLower(), "hi").Success)
{
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.second", null, cancellationToken);
}
else
{
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.first", null, cancellationToken);
}
}
private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.EndDialogAsync(null,cancellationToken);
}
} }
FirstDialog.cs
namespace EchoBot.Dialogs
{
public class FirstDialog : ComponentDialog
{
private readonly BotStateService _botStateService;
public FirstDialog(string dialogId, BotStateService botStateService) : base(dialogId)
{
_botStateService = botStateService ?? throw new ArgumentNullException(nameof(botStateService));
if (dialogId == $"{ nameof(MainDialog)}.first")
InitializeWaterfallDialog1();
else
InitializeWaterfallDialog2();
}
private void InitializeWaterfallDialog1()
{
var waterfallsteps = new WaterfallStep[]
{
GetAge,
GetCity,
FinalStepAsync
};
AddDialog(new WaterfallDialog($"{nameof(FirstDialog)}.mainFlow", waterfallsteps));
AddDialog(new NumberPrompt<int>($"{nameof(FirstDialog)}.age"));
AddDialog(new TextPrompt($"{nameof(FirstDialog)}.city"));
InitialDialogId = $"{nameof(FirstDialog)}.mainFlow";
}
private void InitializeWaterfallDialog2()
{
var waterfallsteps = new WaterfallStep[]
{
GetCity,
FinalStepAsync
};
AddDialog(new WaterfallDialog($"{nameof(FirstDialog)}.mainFlow", waterfallsteps));
AddDialog(new NumberPrompt<int>($"{nameof(FirstDialog)}.age"));
AddDialog(new TextPrompt($"{nameof(FirstDialog)}.city"));
InitialDialogId = $"{nameof(FirstDialog)}.mainFlow";
}
private async Task<DialogTurnResult> GetAge(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync($"{nameof(FirstDialog)}.age",
new PromptOptions
{
Prompt = MessageFactory.Text("Please enter your age."),
RetryPrompt = MessageFactory.Text("Please enter a valid age.")
}, cancellationToken);
}
private async Task<DialogTurnResult> GetCity(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync($"{nameof(FirstDialog)}.age",
new PromptOptions
{
Prompt = MessageFactory.Text("Please enter your city."),
RetryPrompt = MessageFactory.Text("Please enter a valid city.")
}, cancellationToken);
}
private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.EndDialogAsync(null, cancellationToken);
}
}}
SecondDialog.cs
namespace EchoBot.Dialogs
{
public class SecondDialog : ComponentDialog
{
private readonly BotStateService _botStateService;
public SecondDialog(string dialogId, BotStateService botStateService) : base(dialogId)
{
_botStateService = botStateService ?? throw new ArgumentNullException(nameof(botStateService));
InitializeWaterfallDialog();
}
private void InitializeWaterfallDialog()
{
var waterfallSteps = new WaterfallStep[]
{
InitialStepAsync,
FinalStepAsync
};
AddDialog(new WaterfallDialog($"{nameof(SecondDialog)}.mainFlow", waterfallSteps));
AddDialog(new TextPrompt($"{nameof(SecondDialog)}.name"));
InitialDialogId = $"{nameof(SecondDialog)}.mainFlow";
}
private async Task<DialogTurnResult> InitialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync($"{nameof(SecondDialog)}.name",
new PromptOptions
{
Prompt = MessageFactory.Text("Please enter your Name.")
}, cancellationToken);
}
private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
//Suppose you want to call First Dialog from here
return await stepContext.BeginDialogAsync($"{nameof(SecondDialog)}.firstFromSecond", null, cancellationToken);
}
}
}
Added one line - AddDialog(new FirstDialog($"{nameof(SecondDialog)}.firstFromSecond", _botStateService)); in MainDialog.cs. It's working fine for me.
Image 1 : When you go into second dialog from Main Dialog, it asks name and then skips first step of First Dialog (ie. age) and asks the second step ie. city.
Image 2 : When you go directly into first dialog from Main Dialog, it asks age and city both ie. it didn't skip first step.
Hope this is helpful. Ask in comments if you have any query!
In the bot classes we create ConversationState and UserState objects that we can access using its accessors and create properties on them, and then save the data we store.
But how could I do the same if I want to access the data from a Dialog that's called from the Bot class? I know I can pass an object through the Context using BeginDialogAsync options parameter. How could I pass two of them instead of just one and how to get them in the dialog class?
Is there a way to access ConversationState and UserState without having to pass them from dialog to dialog?
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
// Save any state changes that might have occured during the turn.
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Running dialog with Message Activity.");
// Run the Dialog with the new message Activity.
await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
}
In this function of the CoreBot sample we can see the ConversationState and UserState are saved but they are not modified anywhere else, and in the second function a DialogState property is created in the child dialog but it isn't used either that I can see? Can somebody explain why are they created and how to access them from inside the Dialog that is just called?
You can use dependency injection.
public class UserStateClass
{
public string name { get; set; }
}
and
public class YourDialog : ComponentDialog
{
private readonly IStatePropertyAccessor<UserStateClass> _userStateclassAccessor;
public YourDialog(UserState userState)
: base(nameof(YourDialog))
{
_userProfileAccessor = userState.CreateProperty<UserStateClass>("UserProfile");
WaterfallStep[] waterfallSteps = new WaterfallStep[]
{
FirstStepAsync,
};
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
}
private async Task<DialogTurnResult> FirstStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var userstate = await _userStateclassAccessor.GetAsync(stepContext.Context, () => new UserStateClass(), cancellationToken);
userstate.name = "pepe";
return await stepContext.EndDialogAsync();
}
}
I am using NLP Dispatch , through which i have a merged luis and QnA model, I am able to normally call and add my own logic inside top scoring intents block. Now i have a dialog class which i want to be called whenever a top scoring intent is being detected
namespace Microsoft.BotBuilderSamples
{
public class DispatchBot : ActivityHandler
{
private ILogger<DispatchBot> _logger;
private IBotServices _botServices;
public DispatchBot(IBotServices botServices, ILogger<DispatchBot> logger)
{
_logger = logger;
_botServices = botServices;
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
// First, we use the dispatch model to determine which cognitive service (LUIS or QnA) to use.
var recognizerResult = await _botServices.Dispatch.RecognizeAsync(turnContext, cancellationToken);
// Top intent tell us which cognitive service to use.
var topIntent = recognizerResult.GetTopScoringIntent();
// Next, we call the dispatcher with the top intent.
await DispatchToTopIntentAsync(turnContext, topIntent.intent, recognizerResult, cancellationToken);
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
const string WelcomeText = "I am here to make your bot experience much more easier";
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text($"Hi {member.Name}, I am your IT assistant at your service . {WelcomeText}"), cancellationToken);
}
}
}
private async Task DispatchToTopIntentAsync(ITurnContext<IMessageActivity> turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken)
{
switch (intent)
{
case "l_mts-bot-809f":
await ProcessHomeAutomationAsync(turnContext, recognizerResult.Properties["luisResult"] as LuisResult, cancellationToken);
break;
case "q_mts-bot":
await ProcessSampleQnAAsync(turnContext, cancellationToken);
break;
default:
_logger.LogInformation($"Dispatch unrecognized intent: {intent}.");
await turnContext.SendActivityAsync(MessageFactory.Text($"Dispatch unrecognized intent: {intent}."), cancellationToken);
break;
}
}
private Activity CreateResponse(IActivity activity, Attachment attachment)
{
var response = ((Activity)activity).CreateReply();
response.Attachments = new List<Attachment>() { attachment };
return response;
}
private async Task ProcessHomeAutomationAsync(ITurnContext<IMessageActivity> turnContext, LuisResult luisResult, CancellationToken cancellationToken)
{
_logger.LogInformation("ProcessHomeAutomationAsync");
// Retrieve LUIS result for Process Automation.
var result = luisResult.ConnectedServiceResult;
var topIntent = result.TopScoringIntent.Intent;
var entity = result.Entities;
if (topIntent == "welcome")
{
await turnContext.SendActivityAsync(MessageFactory.Text("Hi,This is your IT assistant"), cancellationToken);
}
if (topIntent == "None")
{
await turnContext.SendActivityAsync(MessageFactory.Text("Sorry I didnt get you!"), cancellationToken);
}
if (topIntent == "DateTenure")
{
// Here i want to call my dialog class
}
}
}
private async Task ProcessSampleQnAAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
_logger.LogInformation("ProcessSampleQnAAsync");
var results = await _botServices.SampleQnA.GetAnswersAsync(turnContext);
if (results.Any())
{
await turnContext.SendActivityAsync(MessageFactory.Text(results.First().Answer), cancellationToken);
}
else
{
await turnContext.SendActivityAsync(MessageFactory.Text("Sorry, could not find an answer in the Q and A system."), cancellationToken);
}
}
}
}
I want as and when my top intent is detected , my custom dialog comes into action and the handling of conversation is then should be handled by my dialog class.
private async Task DispatchToTopIntentAsync(ITurnContext<IMessageActivity> turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken)
{
switch (intent)
{
case "l_mts-bot-809f":
//I MADE CHANGES HERE
await dc.BeginDialogAsync(nameof(DIALOG_CLASS_I_WANT_TO_START));
break;
case "q_mts-bot":
await ProcessSampleQnAAsync(turnContext, cancellationToken);
break;
default:
_logger.LogInformation($"Dispatch unrecognized intent: {intent}.");
await turnContext.SendActivityAsync(MessageFactory.Text($"Dispatch unrecognized intent: {intent}."), cancellationToken);
break;
}
}
Just remember to add the dialog to either your main dialog class like this:
public MainDialog(UserState userState)
: base(nameof(MainDialog))
{
_userState = userState;
AddDialog(new DIALOG_CLASS_I_WANT_TO_START());
InitialDialogId = nameof(aDifferentDialogNotShownHere);
}
or to your startup.cs as a transient:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//all the other configure stuff...
// Register dialogs
services.AddTransient<MainDialog>();
services.AddTransient<DIALOG_CLASS_I_WANT_TO_START>();
I have a prompt that I'm displaying to the user that contains a prompt message and retry prompt message, see below:
return await ctx.PromptAsync(CancelCurrentDialogsPrompt, new PromptOptions
{
Prompt = MessageFactory.Text("Are you sure you'd like to cancel?"),
Choices = ChoiceFactory.ToChoices(confirmationOptionsList),
RetryPrompt = MessageFactory.Text("Please select or type yes/no")
}, cancellationToken);
When I run the BOT in the emulator, the prompt message and the retry prompt message appear at the same time, which I'm not expecting to happen, see below:
When I enter in a incorrect option, the retry prompt shows as expected. After selecting a correct value from the list the conversation continues as expected, with no incorrect dialogs.
--- UPDATE ---
I'm calling the cancel dialog using the following code from my bot.cs class
await dialogContext.BeginDialogAsync(nameof(CancelDialog));
When it is being called there is nothing on the dialog stack. Here is the code in my CancelDialog.cs
public class CancelDialog : ComponentDialog
{
public readonly BotDialogSet DialogSet;
private const string CancelWaterfallDialogs = "CancelWaterfallDialogs";
private const string CancelCurrentDialogsPrompt = "CancelCurrentDialogsPrompt";
public CancelDialog(BotDialogSet dialogSet) : base(nameof(CancelDialog))
{
DialogSet = dialogSet;
var waterfallSteps = new WaterfallStep[]
{
WouldYouLikeToCancel,
CompleteUsersSelectedAction
};
AddDialog(new WaterfallDialog(CancelWaterfallDialogs, waterfallSteps));
AddDialog(new ConfirmPrompt(CancelCurrentDialogsPrompt));
}
private static async Task<DialogTurnResult> WouldYouLikeToCancel (WaterfallStepContext ctx, CancellationToken cancellationToken)
{
return await ctx.PromptAsync(CancelCurrentDialogsPrompt, new PromptOptions
{
Prompt = MessageFactory.Text("Are you sure you'd like to cancel?"),
RetryPrompt = MessageFactory.Text("Are you sure you'd like to cancel? Please select or type yes/no")
}, cancellationToken);
}
private static async Task<DialogTurnResult> CompleteUsersSelectedAction(WaterfallStepContext ctx, CancellationToken cancellationToken)
{
if ((bool)ctx.Result)
{
await ctx.Parent.CancelAllDialogsAsync(cancellationToken);
return await ctx.EndBotDialogAsync(cancellationToken);
}
return await ctx.EndDialogAsync(cancellationToken: cancellationToken);
}
}
1- define the following:
AddDialog(new TextPrompt("YNValidator", YesNoValidator);
-add it after:
AddDialog(new ConfirmPrompt(CancelCurrentDialogsPrompt));
2- define YesOrNValidator:
private Task<bool>YorNValidator (PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)
{
//Test the value... and pass true or false
Task.FromResult(true);
}
3- Next REWRITE the WouldYouLikeToCancel
private static async Task<DialogTurnResult> WouldYouLikeToCancel (WaterfallStepContext ctx, CancellationToken cancellationToken)
{
var options= new PromptOptions
{
Prompt = MessageFactory.Text("Are you sure you'd like to cancel?"),
RetryPrompt = MessageFactory.Text("Are you sure you'd like to cancel? Please select or type yes/no")
}, cancellationToken);
return await stepContext.PromptAsync("YNValidator",options,cancellationToken);
}