If we choose the PromptDialog then the option of PromptDialog must be selected. Is it mandatory to select the option?
Without selecting the option and the user asks for a new query as well as Bot will response according to the new query.
In simple, how do we ignore without select option and take the new request and provide the answer to the user by Bot?
What you are looking for is called user interruptions, what happens is every time a user sends a message to your bot, the first thing you will check is that if its an "interruption" of an ongoing dialog, or it's a totally new message.
If it's an interruption, you can handle it, you can start a new dialog, finish and then continue where the user left off.
Check the Bot Service Docs here for Handling User Interruption
There can be two approaches for this :
Approach 1: Use 'Handle Interruptions', but it comes with a cost. It will check so for all messages sent by user whether in or out of a dialog/prompt.
Any useful input entered for any text prompt could match any case in switch, which might end that conversation there itself.
Also, it could slow down the process
Approach 2: (If you want to do this only for a choice prompt) There is a validation function that can be attached to any kind of prompt.
Suppose CheckChoicePromptValidatoris your choice prompt validator. You can do something like this inside it:
private async Task<bool> CheckChoicePromptValidator(PromptValidatorContext<FoundChoice> promptContext, CancellationToken cancellationToken)
{
if(promptContext.Recognized.Succeeded)
{
return await Task.FromResult(true);
}
else
{
var userInput = promptContext.Context.Activity.Text;
// You can use LUIS instead of switch case
switch(userInput.ToLower())
{
case "cancel":
case "quit":
case "reset":
await promptContext.Context.SendActivityAsync(MessageFactory.Text("Cancelling!"), cancellationToken);
var dc = await BotUtil.Dialogs.CreateContextAsync(promptContext.Context, cancellationToken);
await dc.CancelAllDialogsAsync(cancellationToken);
return await Task.FromResult(false);
}
}
}
It will only handle the interruptions when user enters something other than choices.
Related
I have an Adaptive Card with an Action.Submit button, which when pressed I want to return a failure response dependant on the contents of the activity, or at least not display the 'Success' notification, as seen below.
In the bot, OnMessageActivityAsync is called when the button is pressed, in the example below I want to send a failure response when the user has submitted an empty name or a name containing numbers. As you can see, I am sending a validation message to the user, but the 'Success' notification is displayed making for a confusing user experience.
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
var value = JsonConvert.DeserializeObject<JObject>(turnContext.Activity.Value.ToString());
value.TryGetValue("name", out var name);
if (name == null || name.ToString().ToCharArray().Any(char.IsDigit))
{
await turnContext.SendActivityAsync("Account creation failed: Name cannot be empty or contain numbers", cancellationToken: cancellationToken);
// Send failure response
}
}
I have tried throwing an exception in order to make the request fail, which is caught by my OnTurnError handler, which I rethrew, this does make the request fail, but the UI says 'Unable to reach app. Please try again' instead of 'Success'. Which still slightly better, but still poor experience wise, and it feels like there should be a better way because throwing, rethrowing is horrible.
I know that the Action.Execeute button is designed to replace Action.Submit in the Universal Action Model, but the documentation says to implement Action.Submit as a fallback, which is what I am trying to do.
You can use the Adaptive card validations that are available. Check this documentation.
I setup a bot that have a normal stack of dialog with each dialog storing some state data in Cosmos DB in Azure. When a dialog end, I use the OnEndDialogAsync to delete the data specific to that dialog.
My question is, how do I detect when the entire conversation end so that I can delete the whole thing? Or does conversation never end?
My current code that delete each dialog data on end:
protected override async Task OnEndDialogAsync(ITurnContext context, DialogInstance instance, DialogReason reason, CancellationToken cancellationToken)
{
DialogStateDictionary dictionary = await Dependencies.StateAccessor.GetAsync(context, () => null);
if (dictionary != null && dictionary.ContainsKey(DialogID) == true)
{
dictionary[DialogID] = null;
}
await Dependencies.StateAccessor.SetAsync(context, dictionary);
}
The code to delete the entire thing would be:
await Dependencies.StateAccessor.SetAsync(context, null);
The concept of a conversation "ending" will be channel-specific. In Web Chat you can have your client respond to the browser leaving the page by letting your bot know. In channels like Teams the conversation is effectively permanent, but you can always arbitrarily define any point in the conversation as the "end" by having your bot reset its state like you're doing. Perhaps you could have a confirm prompt that asks the user "Will that be all?" and if the user says "yes" then the bot could say "Goodbye" or something.
Not sure which channel you are using, recommend to look at endofconversation activity type.
We are currently working on the proactive chat bot and We want to welcome users with welcome text and prompt choices. I can do prompt choices to work with conversation update but not sure how to do if respone = yes.
I already tried putting in Main dialog but you can't put in the conversation update. using waterfall method.
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
//original
if (turnContext == null)
{
throw new ArgumentNullException(nameof(turnContext));
}
//Welcome user when they join the conversation
if (turnContext.Activity.Type == ActivityTypes.ConversationUpdate)
{
if (turnContext.Activity.MembersAdded != null)
{
foreach (var member in turnContext.Activity.MembersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync($"Ah yes, {WelcomeText}", cancellationToken: cancellationToken);
var dialogContext = await dialogs.CreateContextAsync(turnContext, cancellationToken);
await dialogContext.PromptAsync("choicePrompt",
new PromptOptions
{
Prompt = dialogContext.Context.Activity.CreateReply(" Your task: + taskName + failed because of the following error message: it wasn't able to Auto Login to the runner."),
Choices = new[] { new Choice { Value = "Rerun your Task" }, new Choice { Value = "No" } }.ToList()
}
);
}
}
}
}
}
I expect the output to be Choices with Rerun your task and NO. if they say no, end dialog.
You should not use conversationUpdate as a means for greeting a user. As detailed in this blog, conversationUpdate is triggered when both the bot and the user join the conversation. However, in a production environment, that doesn't always happen simultaneously. The result is the dialog stack isn't fully constructed and, subsequently, doesn't always have access to the details it needs for sending a greeting (for example, a user's name). The end result is a broken user experience.
I don't know which platform / channel you are using, but the blog referenced suggests using back channel in a web chat environment for monitoring and triggering an event to send a welcome message upon a user joining a conversation. If this is for web chat, keep in mind that the blog is slightly outdated. It explains using BotChat which is the v3 web chat method. The v4 method uses the enhanced WebChat which affords greater functionality.
You can experiment with the middleware options to see if there is another option that might work for you. I was unable to find an appropriate one. That being said, the Bot Framework SDKs are open source and always welcome user generated solutions, should you find one.
If the user hasn't talked to the bot in a while, I run the following code in the MessageController (before calling Converasation.SendAsync()) to clear the conversation:
// get client from bot Activity
client = activity.GetStateClient();
if (user.LastMessageTime < DateTime.UtcNow.AddHours(-ConversationFreshnessHours))
{
// clear the state
client.BotState.DeleteStateForUser(activity.ChannelId, activity.From.Id);
}
// update last message time stamp
await user.UpdateLastMessageTime();
Note: The user object above is our docdb user that we load based on the channelId and userId. The property LastMessageTime is set after we (might) clear state.
However, after doing this, the bot fails after any call to Wait fails with the InvalidNeedException with message:
invalid need: expected Call, have Wait
I wrote some code to inspect the dialog stack in the private methods and discovered that when this code gets invoked an additional dialog is added to the stack at the base of my normal stack.
Normal Frame 0:
Wait: Wait IMessageActivity for Sage.Bot.Dialogs.RootDialog.Entry.Rest have IMessageActivity Microsoft.Bot.Connector.Activity`
Frame 0 after calling DeleteStateForUser:
Wait: Wait Object for Microsoft.Bot.Builder.Dialogs.Chain+LoopDialog`1[System.Object].ResumeAsync.Rest have Object
For some reason when this dialog is on on the stack, it causes the above exception.
Does anyone know how to clear the conversation without causing this problem?
Please check out how the DeletProfileScorable clears the user state:
https://github.com/Microsoft/BotBuilder/blob/497252e8d9949be20baa2cebaa6ce56de04461cf/CSharp/Library/Microsoft.Bot.Builder/Dialogs/DeleteProfileScorable.cs
this.stack.Reset();
botData.UserData.Clear();
botData.PrivateConversationData.Clear();
await botData.FlushAsync(token);
await botToUser.PostAsync(Resources.UserProfileDeleted);
If you use autofac to load bot data and retrieve the stack, you can duplicate this code in your MessageController:
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(new System.Threading.CancellationToken());
var stack = scope.Resolve<IDialogStack>();
stack.Reset();
botData.UserData.Clear();
botData.PrivateConversationData.Clear();
await botData.FlushAsync(token);
}
so right now I'm using Microsoft.Bot.Builder.Dialogs.Conversation.SendAsync and Microsoft.Bot.Builder.Dialogs.Conversation.ResumeAsync to implement a way to pause and resume conversation but it seems impossible to 'exit' or go back to the previous state. It's stuck in the conversation dialog.
Do I just implement a 'Cancel' command? If so, what data do I need to clear so that it will be back to the original state?
public static readonly IDialog<string> dialog = Chain
.PostToChain()
.Switch(
new Case<Message, IDialog<string>>((msg) =>
{
var regex = new Regex("login", RegexOptions.IgnoreCase);
return regex.IsMatch(msg.Text);
}, (ctx, msg) =>
{
return Chain.ContinueWith(new ChatDialog(msg),
async (context, res) =>
{
var token = await res;
//var valid = await Helpers.ValidateAccessToken(token);
//var name = await Helpers.GetProfileName(token);
var name = "User";
context.UserData.SetValue("name", name);
return Chain.Return($"You are logged in as: {name}");
});
})
).Unwrap().PostToUser();
so if I send a 'login' it will go and start a new ChatDialog conversation but it seems to get stuck in this state. Even if I try to send another command, it will keep asking for login. Do I implement another Case class to handle a 'Cancel' command? Or should it automatically cancel the conversation when the user sends the same 'login' command more than once? Seems kinda clunky to have to send a 'cancel' command separately.
I think you are missing the DefaultCase. Check this. It shows the implementation of the DefaultCase for the Facebook Auth Sample. BTW, in that sample they also have a Logout command.
I would consider how your users will interpret the end of the conversation, and think about those scenarios and how people end conversations.
You can add code to handle resetting or the end of a conversation based on specific keywords, and by using the GlobalMessageHandler pattern.
https://github.com/Microsoft/BotBuilder-Samples/tree/master/CSharp/core-GlobalMessageHandlers
Also, expect users to just "hang up" / close the window once they are done.
A good set of metrics can help collect information on how people are using the bot for the owners to improve it.
i.e: Did interaction X lead on to expected interaction Y, or what was the last interaction we saw for this conversation... etc.