Skip displaying form fields based on user confirmation - c#

I have some 10 properties in a class and based on those properties I have a form that asks for user input. I want to know if there's any mechanism that after initial 4-5 questions I ask user if he/she wants to ans more and if reply is yes then next set of fields/questions are asked.
I tried doing it with SetDefine but issue with SetDefine is that its called with each field so it asks the user to confirm with each fiels but I want it to only confirm with 1st optional field and based on the answer either skip all or get all.
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync($"Welcome to the Order helper!");
var orderFormDialog = FormDialog.FromForm(BuildOrderAdvanceStepSearchForm, FormOptions.PromptInStart);
context.Call(orderFormDialog, ResumeAfterOrdersFormDialog);
}
private IForm<OrderSearchQuery> BuildOrderAdvanceStepSearchForm()
{
return new FormBuilder<OrderSearchQuery>()
.Field(nameof(OrderSearchQuery.ItemNumber))
.Field(nameof(OrderSearchQuery.Draft))
.Field(nameof(OrderSearchQuery.Dispatched))
.Field(nameof(OrderSearchQuery.InTransit))
.Field(nameof(OrderSearchQuery.Delivered))
//.Confirm("Do you want to search with more options?.")
//.Field(nameof(OrderSearchQuery.AddField1))
.Field(new FieldReflector<OrderSearchQuery>(nameof(OrderSearchQuery.AddField1))
.SetDefine(OrderAdvanceStepConfirmation))
.Field(new FieldReflector<OrderSearchQuery>(nameof(OrderSearchQuery.AddField2))
.SetDefine(OrderAdvanceStepConfirmation))
.Field(new FieldReflector<OrderSearchQuery>(nameof(OrderSearchQuery.AddField3))
.SetDefine(OrderAdvanceStepConfirmation))
.Field(new FieldReflector<OrderSearchQuery>(nameof(OrderSearchQuery.AddField4))
.SetDefine(OrderAdvanceStepConfirmation))
.Field(new FieldReflector<OrderSearchQuery>(nameof(OrderSearchQuery.AddField5))
.SetDefine(OrderAdvanceStepConfirmation))
.Build();
}
private static async Task<bool> OrderAdvanceStepConfirmation(OrderSearchQuery state, Field<OrderSearchQuery> field)
{
field.SetPrompt(new PromptAttribute($"Do you want to search with more options?."));
return true;
}
private async Task ResumeAfterordersFormDialog(IDialogContext context, IAwaitable<OrderSearchQuery> result)
{
try
{
var searchQuery = await result;
await context.PostAsync($"Ok. Searching for orders...");
var count = 100;
if (count > 1)
{
await context.PostAsync($"I found total of 100 orders");
await context.PostAsync($"To get order details, you will need to provide more info...");
}
else
{
await context.PostAsync($"I found the order you were looking for...");
await context.PostAsync($"Now I can provide you information related to Consumer Package, Multi-Pack, Shelf Tray & Unit Load for this order.");
}
}
catch (FormCanceledException ex)
{
string reply;
if (ex.InnerException == null)
{
reply = "You have canceled the operation. Quitting from the order Search";
}
else
{
reply = $"Oops! Something went wrong :( Technical Details: {ex.InnerException.Message}";
}
await context.PostAsync(reply);
}
finally
{
//context.Done<object>(null);
}
}

I want it to only confirm with 1st optional field and based on the answer either skip all or get all.
You can use SetNext of FieldReflector.
For example create a enum for confirmation for example like this:
public enum Confirmation
{
Yes,
No
}
public Confirmation? Corfirm { get; set; }
And then you can build the Form like this:
return new FormBuilder<OrderSearchQuery>()
.Field(nameof(ItemNumber))
.Field(nameof(Draft))
.Field(new FieldReflector<OrderSearchQuery>(nameof(Corfirm))
.SetNext((value, state) =>
{
var selection = (Confirmation)value;
if (selection == Confirmation.Yes)
{
//go to step 1
return new NextStep();
}
else
{
//skip the following steps
state.Stpe1 = null;
state.Step2 = null;
return new NextStep(StepDirection.Complete);
}
})
)
.Field(nameof(Stpe1))
.Field(nameof(Step2)).Build();

Related

How to break formflow in every moment i type exit or cancel?

I'm creating a chatbot in .Net C# using BotFramework. In one of my dialog when i start to fill a form flow i cannot exit from flowform till in the moment i will fill all the flow . Exist any possibility to exit and to leave form ?
This is my code :
LuisDialog.cs :
[LuisIntent("balance")]
public async Task balance(IDialogContext context, LuisResult result)
{
var balanca = new FormDialog<BalanceForm>(
new BalanceForm(),
BalanceForm.BuildForm,
FormOptions.PromptInStart,
result.Entities);
context.Call<BalanceForm>(balanca, BalanceCompleted);
BalanceForm.cs
namespace BasicMultiDialog
{
[Serializable]
public class BalanceForm
{
[Prompt("What is your contract number?")]
public string contract;
public static IForm<BalanceForm> BuildForm()
{
OnCompletionAsyncDelegate<BalanceForm> wrapUpRequest = async
(context, state) =>
{
string wrapUpMessage = "Dear " + house.Firstname + "," + "your balance is " + house.Balance;
await context.PostAsync(wrapUpMessage);
}
};
return new FormBuilder<BalanceForm>().Message
("We have to ask you some information")
.Field(nameof(contract), validate: async (state, response) =>
{
var result = new ValidateResult();
return result;
}
})
.OnCompletion(wrapUpRequest)
//.Confirm("Are you sure: Yes or No ")
.Build();
}
}
}
Actually it's quite easy to cancel a form. If you type "help" or "choices" you can see a list of builtin form commands, and one of these is "quit." There are many terms you can use to quit such as "finish" or "bye." If you want to define your own terms, you can can configure the form commands like this:
var builder = new FormBuilder<BalanceForm>().Message
("We have to ask you some information")
.Field(nameof(contract), validate: async (state, response) =>
{
var result = new ValidateResult();
return result;
})
.OnCompletion(wrapUpRequest)
// Set the command term configuration on its own line
builder.Configuration.Commands[FormCommand.Quit].Terms = new[] { "exit", "cancel" };
return builder.Build();
Keep in mind that when a form is canceled, a FormCanceledException<T> is thrown. If you don't want this to display a message like "Sorry, my bot code is having an issue," you can catch the exception like this:
var balanca = new FormDialog<BalanceForm>(
new BalanceForm(),
BalanceForm.BuildForm,
FormOptions.PromptInStart,
result.Entities)
.Catch<BalanceForm, FormCanceledException<BalanceForm>>((dialog, ex) =>
{
// Handle the cancellation here and return an IDialog<BalanceForm>
});

Multiple QnA Service in one Bot using C#

I do have 3 QnA Service. I want them to be used in a single BOT at the same time. How can this be implemented using C#. My initial idea is to put the KB ID and Sub Key into an array (how to implement that or would an array works?).. I saw some code in Node.JS but I cannot figure it out how to convert the code in C#.
public class QnaDialog : QnAMakerDialog
{
public QnaDialog() : base(
new QnAMakerService(new QnAMakerAttribute(ConfigurationManager.AppSettings["QnaSubscriptionKey1"],
ConfigurationManager.AppSettings["QnaKnowledgebaseId1"], "Hmm, I wasn't able to find an article about that. Can you try asking in a different way?", 0.5)),
new QnAMakerService(new QnAMakerAttribute(ConfigurationManager.AppSettings["QnaSubscriptionKey2"],
ConfigurationManager.AppSettings["QnaKnowledgebaseId2"], "Hmm, I wasn't able to find an article about that. Can you try asking in a different way?", 0.5)),
new QnAMakerService(new QnAMakerAttribute(ConfigurationManager.AppSettings["QnaSubscriptionKey3"],
ConfigurationManager.AppSettings["QnaKnowledgebaseId4"], "Hmm, I wasn't able to find an article about that. Can you try asking in a different way?", 0.5))
)
{
}
}
You can use several QnAMaker knowledge bases in a single bot by providing several services in the attributes.
A basic implementation using QnAMakerDialog from Nuget package BotBuilder.CognitiveServices would be:
[Serializable]
[QnAMaker("QnaSubscriptionKey1", "QnaKnowledgebaseId1", "Hmm, I wasn't able to find an article about that. Can you try asking in a different way?", 0.50, 3)]
[QnAMaker("QnaSubscriptionKey2", "QnaKnowledgebaseId2", "Hmm, I wasn't able to find an article about that. Can you try asking in a different way?", 0.5, 3)]
[QnAMaker("QnaSubscriptionKey3", "QnaKnowledgebaseId3", "Hmm, I wasn't able to find an article about that. Can you try asking in a different way?", 0.5, 3)]
public class RootDialog : QnAMakerDialog
{
}
BUT (yes, there is a "but") you may encounter an exception during the treatment of your messages in some cases. As QnAMakerDialog is open-sourced (sources are here) you can easily discover that the problem is in the implementation of the return of the services calls, in MessageReceivedAsync:
var sendDefaultMessageAndWait = true;
qnaMakerResults = tasks.FirstOrDefault(x => x.Result.ServiceCfg != null)?.Result;
if (tasks.Count(x => x.Result.Answers?.Count > 0) > 0)
{
var maxValue = tasks.Max(x => x.Result.Answers[0].Score);
qnaMakerResults = tasks.First(x => x.Result.Answers[0].Score == maxValue).Result;
if (qnaMakerResults != null && qnaMakerResults.Answers != null && qnaMakerResults.Answers.Count > 0)
{
if (this.IsConfidentAnswer(qnaMakerResults))
{
await this.RespondFromQnAMakerResultAsync(context, message, qnaMakerResults);
await this.DefaultWaitNextMessageAsync(context, message, qnaMakerResults);
}
else
{
feedbackRecord = new FeedbackRecord { UserId = message.From.Id, UserQuestion = message.Text };
await this.QnAFeedbackStepAsync(context, qnaMakerResults);
}
sendDefaultMessageAndWait = false;
}
}
if (sendDefaultMessageAndWait)
{
await context.PostAsync(qnaMakerResults.ServiceCfg.DefaultMessage);
await this.DefaultWaitNextMessageAsync(context, message, qnaMakerResults);
}
In this code, this line of code will break if not all of your services have an answer for your question (ie: if at least one of your QnAMaker KB does not have an answer for your question)
tasks.Max(x => x.Result.Answers[0].Score);
Workaround: you can implement you own QnAMakerDialog by getting the sources and fixing the method like this for example:
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
if (message != null && !string.IsNullOrEmpty(message.Text))
{
var tasks = this.services.Select(s => s.QueryServiceAsync(message.Text)).ToArray();
await Task.WhenAll(tasks);
if (tasks.Any())
{
var sendDefaultMessageAndWait = true;
qnaMakerResults = tasks.FirstOrDefault(x => x.Result.ServiceCfg != null)?.Result;
var qnaMakerFoundResults = tasks.Where(x => x.Result.Answers.Any()).ToList();
if (qnaMakerFoundResults.Any())
{
var maxValue = qnaMakerFoundResults.Max(x => x.Result.Answers[0].Score);
qnaMakerResults = qnaMakerFoundResults.First(x => x.Result.Answers[0].Score == maxValue).Result;
if (qnaMakerResults?.Answers != null && qnaMakerResults.Answers.Count > 0)
{
if (this.IsConfidentAnswer(qnaMakerResults))
{
await this.RespondFromQnAMakerResultAsync(context, message, qnaMakerResults);
await this.DefaultWaitNextMessageAsync(context, message, qnaMakerResults);
}
else
{
feedbackRecord = new FeedbackRecord { UserId = message.From.Id, UserQuestion = message.Text };
await this.QnAFeedbackStepAsync(context, qnaMakerResults);
}
sendDefaultMessageAndWait = false;
}
}
if (sendDefaultMessageAndWait)
{
await context.PostAsync(qnaMakerResults.ServiceCfg.DefaultMessage);
await this.DefaultWaitNextMessageAsync(context, message, qnaMakerResults);
}
}
}
}

Adding verification step in formFlow - Check if topping is in stock

I am working on using the form flow example found Here
The example uses formFlow to help the user pick out the toppings they want on their sandwich.
I'm trying to add a verification step that checks if each of the toppings they add is in stock and if it isn't send an apology message and prompt the user to enter a different topping instead. A code example is seen below:
public static IForm<SandwichOrder> BuildForm()
{
return new FormBuilder<SandwichOrder>()
.Message("Welcome to the sandwich order bot!")
.Field(nameof(Sandwich))
.Field(nameof(Length))
.Field(nameof(Bread))
.Field(nameof(Cheese))
.Field(nameof(Topping),
validate: async (state, value) =>
{
foreach(var t in Topping)
{
if (!isToppinginStock)
{
// Apology message
//Code to ask topping question again
}
}
})
.Message("For sandwich toppings you have selected {Toppings}.")
.Build();
}
If anyone can help or point me in the right direction I would really appreciate it.
Something like the following should work for your scenerio:
.Field(nameof(Toppings),
validate: async (state, value) =>
{
var values = ((List<object>)value).OfType<ToppingOptions>();
var notInStock = GetOutOfStockToppings(values);
if(notInStock.Any())
return new ValidateResult { IsValid = false, Value = null, Feedback = $"These are not in stock: {string.Join(",", notInStock.ToArray())} Please choose again." };
return new ValidateResult { IsValid = true, Value = values};
})
static IEnumerable<ToppingOptions> NotInStock = new[] { ToppingOptions.Lettuce, ToppingOptions.Pickles };
private static IEnumerable<ToppingOptions> GetOutOfStockToppings(IEnumerable<ToppingOptions> toppings)
{
List<ToppingOptions> result = new List<ToppingOptions>();
foreach(var topping in toppings)
{
if (NotInStock.Contains(topping))
result.Add(topping);
}
return result;
}

Differentiate whether IDialog was called using context.Call() or context.Forward() in Microsoft Bot Framework

I have a subdialog in a bot built using MS bot framework that starts as follows - the standard way:
public async Task StartAsync(IDialogContext context)
{
var msg = "Let's find your flights! Tell me the flight number, city or airline.";
var reply = context.MakeMessage();
reply.Text = msg;
//add quick replies here
await context.PostAsync(reply);
context.Wait(UserInputReceived);
}
This dialog is called using two different ways, depending on whether in the previous screen the user tapped a button that says "Flights" or immediately entered a flight number. Here is the code from the parent dialog:
else if (response.Text == MainOptions[2]) //user tapped a button
{
context.Call(new FlightsDialog(), ChildDialogComplete);
}
else //user entered some text instead of tapping a button
{
await context.Forward(new FlightsDialog(), ChildDialogComplete,
activity, CancellationToken.None);
}
Question: how can I know (from within the FlightsDialog) whether that dialog was called using context.Call() or context.Forward()? This is because in the case of context.Forward(), StartAsync() shouldn't output the prompt asking the user to enter the flight number - they already did this.
The best idea I have is to save a flag in the ConversationData or user data, as below, and access it from the IDialog, but I thought there could be a better way?
public static void SetUserDataProperty(Activity activity, string PropertyName, string ValueToSet)
{
StateClient client = activity.GetStateClient();
BotData userData = client.BotState.GetUserData(activity.ChannelId, activity.From.Id);
userData.SetProperty<string>(PropertyName, ValueToSet);
client.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);
}
Unfortunately Forward actually calls Call (and then does some other stuff afterwards), so your Dialog wouldn't be able to differentiate.
void IDialogStack.Call<R>(IDialog<R> child, ResumeAfter<R> resume)
{
var callRest = ToRest(child.StartAsync);
if (resume != null)
{
var doneRest = ToRest(resume);
this.wait = this.fiber.Call<DialogTask, object, R>(callRest, null, doneRest);
}
else
{
this.wait = this.fiber.Call<DialogTask, object>(callRest, null);
}
}
async Task IDialogStack.Forward<R, T>(IDialog<R> child, ResumeAfter<R> resume, T item, CancellationToken token)
{
IDialogStack stack = this;
stack.Call(child, resume);
await stack.PollAsync(token);
IPostToBot postToBot = this;
await postToBot.PostAsync(item, token);
}
From https://github.com/Microsoft/BotBuilder/blob/10893730134135dd4af4250277de8e1b980f81c9/CSharp/Library/Dialogs/DialogTask.cs#L196

can i use the PromptDialog.choice continuously to ask many related infomation in botframework

I want to ask many questions continuously to user, and each question depends on the choose of the question before. I want to use PromptDialog.Choice() to implement it but i find if i do it like this, since the second user info, will still invoke the first callback function, and i don't know how to jump out from the function.
public void getchoose(IDialogContext context)
{
List<string> option_provide = null;
bool if_find = true;
foreach (var i in question.GetSortedProperties())
{
nowchoice = i;
if (!question.select<test_tableinfo>(i.Name, (string)i.GetValue(question), allOptions, out allOptions, out option_provide))
{
if (allOptions.Count() == 1)
{
context.PostAsync(allOptions.First().answer);
if_find = false;
context.Wait(MessageReceived);
}
if (option_provide.Count() < 1)
{
context.PostAsync("请联系客服");
if_find = false;
}
else if (option_provide.Count() == 1)
{
i.SetValue(question, option_provide[0]);
question.select(i.Name, (string)i.GetValue(question), allOptions, out allOptions, out option_provide);
}
else
{
PromptDialog.Choice<string>(context, AfterChoose, option_provide, "选择你需要的"+i.Name, "retry", 2);
if_find = false;
break;
}
}
}
if (if_find)
{
foreach (var i in allOptions)
{
context.PostAsync(i.answer);
}
context.Wait(MessageReceived);
}
}
and the callback function afterchoose
public async Task AfterChoose(IDialogContext context, IAwaitable<string> choice)
{
try
{
string temp = await choice;
if (temp != null)
nowchoice.SetValue(question, temp);
}
catch (Exception)
{
}
context.Reset();
getchoose(context);
}
i use reflection here to get the order of variables but it is not important for this question
I believe what you are looking for is already part of the Bot Framework. FormFlow is a feature that allows you to construct a model of the data that you wish to gather from the user and then ask a series of questions, which can be branched in necessary depending upon answers.
Check out the FormFlow documentation on the Bot Framework site.

Categories

Resources