Bot Framework: How to exit Conversation? - c#

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.

Related

Detect when conversation end so I can delete stored conversation state data

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.

Is mandatory to select the option of PromptDialog?

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.

Is there way to put prompt choice in conversation update and tell what choice will do?

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.

Discord: how to catch direct message from user to bot?

I am creating own bot for Discrod server. So far I have managed to give it commands like "!roll" into the chat and bot catches it replies "you rolled 6" to the chat as well.
client.UsingCommands(input => { input.PrefixChar = '!' });
command.CreateCommand("roll").Do(async (e) => {
await channel.SendMessage(username + " rolls " + rng.Next(1, 7)) });
But I dislike the way people are typing commands into the chat because it might be disruptive at some point. I would like to create a possibility to call command by direct message from the user to the bot. You'd DM bot "roll" and it will write to the chat "andrew rolled 1".
But I have no idea how to do that or if it is even possible. Any ideas?
The current answers I found online were frustrating, this is how I did it (couldn't find any reference to the "Context" people kept saying, or the "IsPrivate"). Had to figure this stuff out just by inspecting the object themselves.
It appears the SocketMessage.Channel property changes from SocketTextChannel to SocketDMChannel depending on the source. Below is the method I made to determine if a message was a DM.
private bool IsPrivateMessage(SocketMessage msg) {
return (msg.Channel.GetType() == typeof(SocketDMChannel));
}
Hope this helps someone save the 5 hours this wasted me :)
One solution could be using Delegates/EventHandlers
var client = new DiscordClient();
client.MessageCreated += (s, e) =>
{
if (!e.Message.IsAuthor && e.Message.ToLower().Contains("roll")){
/*Code here*/
}
}
EventHandler<MessageEventArgs> handler = new EventHandler<MessageEventArgs>(HandleMessageCreated);
client.MessageCreated += handler;
Just make sure you place this delegate/EventHandler code below the Command Services setup, and the bot won't double-post when someone does !roll.
Also, you can use IsPrivate variable on channel Object to check if the message sent on the channel is a DM/PM channel or not.
In case you need the documentations for Discord.NET v0.9.6, here it is.
Also, you may want to consider using Discord.NET v1.0.0+ instead.

Completing Oauth without the use of threading

I am not exactly sure how to explain this so I'll give it my best shot.
Basically I have an application that connects to DropBox. One of the situations I have run into is with the registering process. At the moment during the registration process it connects to DropBox using the default browser, which it then requires the user to login and click allow app to use the service. In return you get a token which the app can use to connect to the service with. The problem I am having is getting the application to wait until the above process is completed. The only way I have figured out to get it to wait is to use system.threading(int). however if the person takes longer then the timeout then it fails to register properly.
I am hoping someone may be able to point me in the right direction and get it to wait without the threading function. I was hoping I could use a if loop or something but i have no idea how to do that properly.
here is the actual Oauth code:
private static OAuthToken GetAccessToken()
{
string consumerKey = "*****";
string consumerSecret = "****";
var oauth = new OAuth();
var requestToken = oauth.GetRequestToken(new Uri(DropboxRestApi.BaseUri), consumerKey, consumerSecret);
var authorizeUri = oauth.GetAuthorizeUri(new Uri(DropboxRestApi.AuthorizeBaseUri), requestToken);
Process.Start(authorizeUri.AbsoluteUri);
return oauth.GetAccessToken(new Uri(DropboxRestApi.BaseUri), consumerKey, consumerSecret, requestToken);
}
and here is the complete oauth function that is called when the registration button is clicked:
var accesstoken = GetAccessToken();
You need to make the Async (asynchronous) version of their GetAccessToken call. One that will call some function of yours when it is complete.
You could also loop until the information is ready, e.g.
while (dataIsNotReady()) {
Thread.Sleep(1000); // sleep for a bit. this is bad, locks up entire thread maybe even application while it sleeps. Make it shorter for less impact.
// TODO add a "timeout", i.e. only try this for X amount of time before breaking out
}
// Now we data is ready let's go
Update:
Perhaps you are better off using a library that can do it async for you e.g. this Dropbox C# library: https://github.com/dkarzon/DropNet

Categories

Resources