Actions after PromptDialog.Choice - c#

This shouldn't be too difficult, but I'm new to C# and can't seem to fix it. I have a Prompt.Dialog.Choice to confirm a message if the user want the inputs to be sent to a database or not.
I'm stuck in the ChoiceReceivedAsync where I want to make actions weather or not the user answers "yes" or "no", but how do I call these answers in my if-else sentence?
Here's the relevant code:
public enum booleanChoice { Yes, No }
public async Task StartAsync(IDialogContext context)
{
//var message = await result as IMessageActivity;
await context.PostAsync("Do you want you message to be sent?");
PromptDialog.Choice(
context: context,
resume: ChoiceReceivedAsync,
options: (IEnumerable<booleanChoice>)Enum.GetValues(typeof(booleanChoice)),
prompt: " ",
retry: "Please try again.",
promptStyle: PromptStyle.Auto
);
}
public async Task ChoiceReceivedAsync(IDialogContext context, IAwaitable<booleanChoice> result)
{
booleanChoice response = await result;
//Here's where I'm stuck:
//if (PromptDialog.Choice == "Yes"){//send the inputs to the databse}
//else (PromptDialog.Choice == "No"){//exit or enter the message again}
}

You just have to compare your result (response) to your enum values, like the following:
public async Task ChoiceReceivedAsync(IDialogContext context, IAwaitable<booleanChoice> result)
{
booleanChoice response = await result;
if (response.Equals(booleanChoice.Yes))
{
//send the inputs to the databse}
else
{
//exit or enter the message again
}
}

Related

Bot Framework: Loop through Prompts

I recently got into Microsoft's Bot Framework, and this will be one of my first exposure to asynchronous programming in C#. I am creating a prompt that is designed as a selection tree. Using an XML document, I designed a hierarchy of topics the user can select -- I then abstracted the parsing of the XML using a HelpTopicSelector class.
The flow is as follows:
User types "help"
Context forwards to HelpDialog
Help Dialog creates prompt with list of options provided by the HelpTopicSelector
When user selects a prompt option, HelpTopicSelector "selects" the choise and updates a new list of choices from the subtree
Create another prompt with updated topics
Repeat until the last selected topic is the last node - call Context.Done
The help dialog is called from a basic dialog as follows:
private async Task ActivityRecievedAsync(IDialogContext context, IAwaitable<object> result)
{
Activity activity = await result as Activity;
if (activity.Text == "test")
{
await context.PostAsync("works");
}
else if(activity.Text == "help")
{
await context.Forward(new HelpDialog(), this.ResumeAfterHelp, activity.AsMessageActivity(), System.Threading.CancellationToken.None);
await context.PostAsync("Done Selection!");
}
context.Wait(ActivityRecievedAsync);
}
I am almost certain the problem in my code lies in the "loop" nature of my HelpDialog, but I genuinely have no idea WHY it fails.
class HelpDialog : IDialog
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Reached Help Dialog!");
context.Wait(ActivityRecievedAsync);
}
private async Task ActivityRecievedAsync(IDialogContext context, IAwaitable<object> result)
{
var message = await result;
await context.PostAsync("HelpDialog: Activity Received");
await HandleTopicSelection(context);
context.Wait(ActivityRecievedAsync);
}
private async Task HandleTopicSelection(IDialogContext context)
{
List<string> topics = HelpTopicSelector.Instance.Topics;
PromptDialog.Choice<string>(context, TopicSelectedAsync, topics, "Select A Topic:");
// Unecessary?
context.Wait(ActivityRecievedAsync);
}
private async Task TopicSelectedAsync(IDialogContext context, IAwaitable<string> result)
{
string selection = await result;
if (HelpTopicSelector.Instance.IsQuestionNode(selection))
{
await context.PostAsync($"You asked: {selection}");
HelpTopicSelector.Instance.Reset();
context.Done<string>(selection);
}
else
{
HelpTopicSelector.Instance.SelectElement(selection);
await HandleTopicSelection(context);
}
// Unecessary?
context.Wait(ActivityRecievedAsync);
}
}
What I Expect:
I believe the await keyword should hold a Task's execution until the awaited Task is done.
Similarily, I believe Context.Wait is called in the end of Tasks to loop back to the AcitivtyReceived method, which effectively makes the bot wait for a user input.
Assuming that logic is true, the help dialog enters in the StartAsync method and hands control to the ActivityReceivedAsync which responds to the "message" passed by Context.Forward of the parent dialog. Then, it awaits the HandleTopic method which is responsible for the prompt. The prompt continues execution in the TopicSelectedAsync as indicated by the ResumeAfter argument.
The TopicSelectedAsync method checks if the selected topic is at the end of the XML tree, and if so, ends the Dialog by calling Context.Done. Otherwise, it awaits another HandleTopic method, which recursively creates another prompt - effectively creating a loop until the dialog ends.
Given how hacky this looks, I wasn't surprised to face an error. The bot emulator throws a "Stack is Empty" exception
.
After attempting to debug with break points, I notice the HelpDialog abruptly ends and exits when it enters TopicSelectedAsync method (specifically when it awaits the result). Visual Studio throws the following exception:
invalid need: Expected Call, have Poll.
EXTRA NOTE:
I tried coding this logic inside my BasicDialog class initially without forwarding to any other dialog. To my surprise, it almost worked flawlessly.
This survey dialog sample is similar to your scenerio: https://github.com/Microsoft/BotBuilder-Samples/blob/45d0f8767d6b71b3a11b060c893521d5150ede7f/CSharp/core-proactiveMessages/startNewDialogWithPrompt/SurveyDialog.cs
Modifying it to be a help dialog:
[Serializable]
public class HelpDialog : IDialog
{
public async Task StartAsync(IDialogContext context)
{
PromptDialog.Choice<string>(context, TopicSelectedAsync, HelpTopicSelector.Instance.Topics, "Select A Topic:", attempts: 3, retry: "Please select a Topic");
}
private async Task TopicSelectedAsync(IDialogContext context, IAwaitable<object> result)
{
try
{
string selection = await result as string;
if (HelpTopicSelector.Instance.IsQuestionNode(selection))
{
await context.PostAsync($"You asked: {selection}");
HelpTopicSelector.Instance.Reset();
context.Done<string>(selection);
}
else
{
await this.StartAsync(context);
}
}
catch (TooManyAttemptsException)
{
await this.StartAsync(context);
}
}
}
Calling it from a parent dialog like this (using context.Call() instead of .Forward()):
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
Activity activity = await result as Activity;
if (activity.Text == "test")
{
await context.PostAsync("works");
context.Wait(MessageReceivedAsync);
}
else if (activity.Text == "help")
{
context.Call(new HelpDialog(), ResumeAfterHelp);
await context.PostAsync("Called help dialog!");
}
}
private async Task ResumeAfterHelp(IDialogContext context, IAwaitable<object> result)
{
var selection = await result as string;
context.Wait(MessageReceivedAsync);
}
When supplying a method for Context.Wait(), you are actually supplying a continuation delegate. The next message received from the user will be sent to the method last .Wait() 'ed on. If you are forwarding, or calling a separate dialog, the parent should not then also call .Wait(). Also, when calling context.Done(), there should not also be a .Wait() afterwards in the same dialog.

Microsoft bot error - Exception: invalid need: expected Call, have Poll

I keep getting this error and I don't know how to fix it: Exception: invalid need: expected Call, have Poll
PromptDialog.Text(context, setEmail, "What is the contact's email? ");
PromptDialog.Text(context, setPhone, "What is the contact's phone number? ");
private async Task setPhone(IDialogContext context, IAwaitable<string> result)
{
this.contact1.Phone = await result;
ReturnContact(context, contact1);
}
private async Task setEmail(IDialogContext context, IAwaitable<string> result)
{
this.contact1.Email = await result;
ReturnContact(context, contact1);
}
the prompt dialogs are part of a different method. How do I prompt the user twice in a row without getting this error?
PromptDialog.Text was not designed to be called twice, because you need two different answers from a user, so in terms of botframework it is like two separate "transactions".
Rather than making a double call you need to create a cascade of calls, where you initiate the Phone question from the Email question handler:
[Serializable]
public class SomeDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
private async Task OnPhoneSet(IDialogContext context, IAwaitable<string> result)
{
var res = await result;
}
private async Task OnEmailSet(IDialogContext context, IAwaitable<string> result)
{
var res = await result;
PromptDialog.Text(context, OnPhoneSet, "What is the contact's phone number? ");
}
public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
PromptDialog.Text(context, OnEmailSet, "What is the contact's email? ");
}
}
The workflow is like the following:
User initiates a dialog for the first time. Callstack: StartAsync -> MessageReceivedAsync -> PromptDialog.Text(context, OnEmailSet). Now dialog is waiting for Email posted
User posts Email. Callstack: OnEmailSet -> PromptDialog.Text(context, OnPhoneSet. Now dialog is waiting for Phone posted
User posts Phone. Callstack: OnPhoneSet. On OnPhoneSet you'll do further actions, for example you can close dialog using Context.Done or something.

Bot Framework, promptchoice a list of object

I want to ask a user which widget it wants to use after searching for widgets, which results in a list of widgets. I want to be able to click on the name of a widget, and then get the URL of the widget. When I run the following code, I get invalid need: expected Call, have Poll.
public async Task SelectAfterSearch(IDialogContext context, List<Widget> widgetlist)
{
PromptDialog.Choice(context, this.OnWidgetSelected, GetListOfWidgets("list"), "Which one do you want more information about?", "Not a valid option", 3);
}
public async Task OnWidgetSelected(IDialogContext context, IAwaitable<Widget> result)
{
var chosen = await result;
await context.PostAsync($"You have chosen {chosen.Name}: {chosen.Url}");
}
You are missing a context.Wait at the end of your OnWidgetSelected method.
public async Task OnWidgetSelected(IDialogContext context, IAwaitable<Widget> result)
{
var chosen = await result;
await context.PostAsync($"You have chosen {chosen.Name}: {chosen.Url}");
context.Wait(...) // => usually you Wait on the MessageReceived method.
}

Prompt the user for a string in child dialog in a bot

I am currently playing around with Bots and LUIS.
So I have a running Bot. In my RootDialog, I handle all the intents that I get from LUIS. Now I want to check if an Entity is missing for an intent.
if (result.Entities.Count == 0) {
var ct = new CancellationToken();
await context.Forward(new ParameterDialog(), ResumeAfterParameterDialog, message, ct);
If there is no Entity I'm creating a new child dialog.
public class ParameterDialog : IDialog<object> {
public async Task StartAsync(IDialogContext context) {
context.Wait(MessageReceivedAsync);
}
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument) {
argument = new PromptDialog.PromptString("Please enter a parameter", "please try again", 2);
var prompt = await argument;
await context.PostAsync($"Your Parameter is: {prompt}");
context.Done(prompt);
}
}
If I could get user input I would then pass it back to my parent dialog.
Now I don't really know how I can stop the Bot and let it wait for user input.
Can someone please explain how I can accomplish that?
Thank you!
You are missing a context.Call of the PromptString dialog you are creating.
The context.Call method expects a dialog and a 'callback' method (ResumeAfter) that will be called once the dialog completes (in this case, when PromptString completes).
In your scenario your code should look like:
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var dialog = new PromptDialog.PromptString("Please enter a parameter", "please try again", 2);
context.Call(dialog, ResumeAfterPrompt)
}
private Task ResumeAfterPrompt(IDialogContext context, IAwaitable<string> result)
{
var parameter = await result;
context.Done(parameter);
}

MS Bot Framework Conversation with Decisions

I have successfully got a simple "What's the weather" bot using Luis up and running on both Skype and Twilio and it's excellent.
I have now been tasked with setting up what I can only think of describing as a "conversation" bot.
I have looked at as many examples as I can find on the interweb but I am unsure how to approach developing it, and I don't know whether I should be using the FormBuilder for my scenario.
Here is a flow chart of part of what I am trying to do...
I have got my form working down to the part where it branches at "Bill Available"...
I cannot work out how to "change direction" based on the answer.
This, as suspected, turned out to be much easier that I thought..
Here is my solution...
This is my controller:
/// <summary>
/// POST: api/Messages
/// Receive a message from a user and reply to it
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
// check if activity is of type message
if (activity != null && activity.GetActivityType() == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new Dialogs.BillSharkDialog());
}
else
{
HandleSystemMessage(activity);
}
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
And here is the "Dialog" with just 2 steps...
[Serializable]
public class BillSharkDialog : IDialog<object>
{
Model.Customer customer = new Model.Customer();
public async Task StartAsync(IDialogContext context)
{
context.Wait(WelcomeMessageAsync);
}
public async Task WelcomeMessageAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
IMessageActivity message = await argument;
await context.PostAsync("We're excited to start helping you save! Let's start by getting your name?");
context.Wait(CaptureCustomerNameAsync);
}
public async Task CaptureCustomerNameAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
IMessageActivity message = await argument;
customer.customerName = message.Text;
await context.PostAsync($"Thanks {message.Text}. Now we need your email address?");
context.Wait(CaptureCustomerEmailAsync);
}
}
You can obviously change the route by checking the incoming message..
Here is an example:
public async Task DoesCustomerHaveBillAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
IMessageActivity message = await argument;
switch (message.Text.ToLower())
{
case "yes":
await context.PostAsync($"Great. Go ahead and take a picture of the first couple of pages and attach them to this conversation.\n\n\nWhen you have finished, please send the message 'finished'.");
context.Wait(CustomerHasBillAsync);
break;
case "no":
await context.PostAsync($"That's OK. Do you happen to have the login information for your provider account?");
context.Wait(CustomerDoesntHaveBillAsync);
break;
default:
await context.PostAsync($"Sorry, I didn't undestand. Please reply with 'yes' or 'no'.");
context.Wait(DoesCustomerHaveBillAsync);
break;
}
}
You could consider using the SetNext method of a FormBuilder field to decide what should be coming next based on another field value.
You can take a look to the ContosoFlowers sample. In the Order form; something similar is being done.
public static IForm<Order> BuildOrderForm()
{
return new FormBuilder<Order>()
.Field(nameof(RecipientFirstName))
.Field(nameof(RecipientLastName))
.Field(nameof(RecipientPhoneNumber))
.Field(nameof(Note))
.Field(new FieldReflector<Order>(nameof(UseSavedSenderInfo))
.SetActive(state => state.AskToUseSavedSenderInfo)
.SetNext((value, state) =>
{
var selection = (UseSaveInfoResponse)value;
if (selection == UseSaveInfoResponse.Edit)
{
state.SenderEmail = null;
state.SenderPhoneNumber = null;
return new NextStep(new[] { nameof(SenderEmail) });
}
else
{
return new NextStep();
}
}))
.Field(new FieldReflector<Order>(nameof(SenderEmail))
.SetActive(state => !state.UseSavedSenderInfo.HasValue || state.UseSavedSenderInfo.Value == UseSaveInfoResponse.Edit)
.SetNext(
(value, state) => (state.UseSavedSenderInfo == UseSaveInfoResponse.Edit)
? new NextStep(new[] { nameof(SenderPhoneNumber) })
: new NextStep()))
.Field(nameof(SenderPhoneNumber), state => !state.UseSavedSenderInfo.HasValue || state.UseSavedSenderInfo.Value == UseSaveInfoResponse.Edit)
.Field(nameof(SaveSenderInfo), state => !state.UseSavedSenderInfo.HasValue || state.UseSavedSenderInfo.Value == UseSaveInfoResponse.Edit)
.Build();
}
}
}

Categories

Resources