Dialog is not continuing from where it waited for user input - c#

This code is using botframework 4
I have rootdialog which is calling an another dialog called choicedialog .From the choicedialog i am returning the DialogturnResult(waiting) from BeginDialogAsync method.After that the bot wait for the input from the user.
when the user enter something it should call the ContinueAsync Method in the ChoiceDialog.But the bot is calling the Rootdailog ContinueAsync .
what is the reason for this ?How can i solve this?
in the Controller onTurnAync Method
async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
if (turnContext.Activity.Type == ActivityTypes.Message)
{
var dc = await Dialogs.CreateContextAsync(turnContext, cancellationToken);
var dialogResult = await dc.ContinueDialogAsync();
if (!dc.Context.Responded)
{
// examine results from active dialog
switch (dialogResult.Status)
{
case DialogTurnStatus.Empty:
await dc.BeginDialogAsync(nameof(RootDialog));
break;
case DialogTurnStatus.Waiting:
// The active dialog is waiting for a response from the user, so do nothing.
break;
case DialogTurnStatus.Complete:
await dc.EndDialogAsync();
break;
default:
await dc.CancelAllDialogsAsync();
break;
}
}
}
}
rootDialog
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext context, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
var activity = context.Context.Activity;
context.SendTyping(activity);
var response = DataFromService();
if (response == null || response.StatusCode != 1)
{
await context.PostAsync(Messages.StandardErrorMessage);
}
if (response.Data != null)
{
return await dialog.BeginDialogAsync(context);
}
else
{
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
}
ChoiceDialog
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext context, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
choiceStep.SaveEntityDataInContext(context: context);
IList<IMessageActivity> messages = GenerateMessageActivity(context);
if (messages.IsCollectionValid())
{
foreach (var message in messages)
{
await context.PostActivityToUser(message);
}
}
var dialogResult = new DialogTurnResult(DialogTurnStatus.Waiting);
return dialogResult;
}

I don't see you saving the changes to DialogState anywhere. Usually this is done at the end of OnTurnAsync. You could possibly be using AutoSaveStateMiddleware, but you didn't mention that (and, frankly, I wouldn't recommend it).
Specifically you need to call SaveChangesAsync on the BotState instance that you called CreateProperty<DialogState>. Usually this is ConversationState.
If you don't do this, every turn will start with an empty dialog stack, thus the behavior you're describing where the root dialog is always being run.

Related

Why does my dialog send twice? C# Azure CoreBot

This is my waterfall
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
IntroStepAsync,
UserStepAsync,
ActStepAsync,
FinalStepAsync,
}));
InitialDialogId = nameof(WaterfallDialog);
This is my code in the Final Step Async
private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// Restart the main dialog with a different message the second time around
var newMessage = "How else I can help you?";
return await stepContext.ReplaceDialogAsync(InitialDialogId, newMessage, cancellationToken);
}
This is my IntroStepAsync
private async Task<DialogTurnResult> IntroStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
if (!_luisRecognizer.IsConfigured)
{
await stepContext.Context.SendActivityAsync(
MessageFactory.Text("NOTE: LUIS is not configured.", inputHint: InputHints.IgnoringInput), cancellationToken);
return await stepContext.NextAsync(null, cancellationToken);
}
// FinalStepAsync or default first time
var GreetText = stepContext.Options?.ToString() ?? "Hi there! I'm Saffi, your voice assistant. What is your name?";
var promptMessage = MessageFactory.Text(GreetText, GreetText, InputHints.ExpectingInput);
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken);
}
This is how my bot looks like
Why does the repeat happen and how do I get rid of it?

Why does my extension method does not cancel my task?

In UWP I wrote the following method in the model view:
private async Task RFIDRead()
{
try
{
await nur_reader.SetupRFIDReader();
string RFID_tag = await ProdSysCommon.ExtensionMethods.TimeoutAfter(nur_reader.PerformRFIDReading(), TimeSpan.FromSeconds(15));
}
catch (TimeoutException)
{
var dialog = new MessageDialog("No RFID found in time, please scan again");
dialog.Title = "Timeout Warning";
await dialog.ShowAsync();
}
The extension method that I use is the following:
public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout)
{
using (var timeoutCancellationTokenSource = new CancellationTokenSource())
{
var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));
if (completedTask == task)
{
timeoutCancellationTokenSource.Cancel();
return await task; // Very important in order to propagate exceptions
}
else
{
throw new TimeoutException("The operation has timed out.");
}
}
}
After I run this, I catch the timeout exception, but the nur_reader.PerformRFIDReading() keeps running. What do I do wrong with my extension method? How shall I change it, so it cancels the task?

Response to prompt dialog not moving to the next waterfall step

When I run the application for the first time, I am doing some initialization steps like asking for name and the communication mode(chat or speak), I used BeginDialogAsync to start the respective prompt dialog. But after responding to the prompt, it goes back to the MainDialog.cs I created, to check user input with LUIS, instead of going to the next waterfall step.
CODE
OptionsDialog.cs
private void InitializeWaterfallDialog()
{
var waterfallSteps = new WaterfallStep[]
{
InitialStepAsync,
FinalStepAsync
};
AddDialog(new WaterfallDialog($"{nameof(OptionsDialog)}.mainFlow", waterfallSteps));
AddDialog(new ChoicePrompt($"{nameof(OptionsDialog)}.communicationMode"));
InitialDialogId = $"{nameof(OptionsDialog)}.mainFlow";
}
private async Task<DialogTurnResult> InitialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync($"{nameof(OptionsDialog)}.communicationMode",
new PromptOptions
{
Prompt = MessageFactory.Text("Please select how you want to continue interacting with me."),
Choices = ChoiceFactory.ToChoices(new List<string> { "Chat", "Speak" }),
}, cancellationToken);
}
private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var attachments = new List<Attachment>();
var reply = MessageFactory.Attachment(attachments);
switch (((FoundChoice)stepContext.Result).Value)
{
case "Chat":
reply.Text = "Type something to start";
break;
case "Speak":
reply.Text = "This option is yet to be configured. Continuing with chat option. Type something to start";
break;
}
await stepContext.Context.SendActivityAsync(reply, cancellationToken);
return await stepContext.EndDialogAsync(null, cancellationToken);
}
}
MainDialog.cs
private void InitializeWaterfallDialog()
{
var waterfallSteps = new WaterfallStep[]
{
InitialStepAsync,
FinalStepAsync
};
AddDialog(new OptionsDialog($"{nameof(MainDialog)}.options", _botStateService));
AddDialog(new GreetingDialog($"{nameof(MainDialog)}.greeting", _botStateService));
AddDialog(new RevenueDialog($"{nameof(MainDialog)}.revenue", _botStateService, _botServices, _financialServices, _currencyService));
AddDialog(new QuarterlyRevenueDialog($"{nameof(MainDialog)}.quarterlyRevenue", _botStateService, _botServices, _financialServices, _currencyService));
AddDialog(new ProfitDialog($"{nameof(MainDialog)}.profit", _botStateService, _botServices, _financialServices, _currencyService));
AddDialog(new QuarterlyProfitDialog($"{nameof(MainDialog)}.quarterlyProfit", _botStateService, _botServices, _financialServices, _currencyService));
AddDialog(new StockPriceDialog($"{nameof(MainDialog)}.stockPrice", _botStateService, _botServices, _financialServices, _currencyService));
AddDialog(new PersonNameDialog($"{nameof(MainDialog)}.personName", _botStateService, _botServices));
AddDialog(new FarewellDialog($"{nameof(MainDialog)}.farewell", _botStateService, _botServices));
AddDialog(new WaterfallDialog($"{nameof(MainDialog)}.mainFlow", waterfallSteps));
InitialDialogId = $"{nameof(MainDialog)}.mainFlow";
}
private async Task<DialogTurnResult> InitialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
try
{
UserProfile userProfile = await _botStateService.UserProfileAccessor.GetAsync(stepContext.Context, () => new UserProfile());
var recognizerResult = await _botServices.Dispatch.RecognizeAsync(stepContext.Context, cancellationToken);
// Top intent tell us which cognitive service to use.
var topIntent = recognizerResult?.GetTopScoringIntent();
if(userProfile.HasRestartedConversation)
{
userProfile.HasRestartedConversation = false;
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.options", null, cancellationToken);
}
else
{
switch (topIntent?.intent)
{
case "GreetingIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.greeting", null, cancellationToken);
case "FindRevenueIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.revenue", null, cancellationToken);
case "FindQuarterlyRevenueIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.quarterlyRevenue", null, cancellationToken);
case "FindProfitIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.profit", null, cancellationToken);
case "FindQuarterlyProfitIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.quarterlyProfit", null, cancellationToken);
case "FindStockPriceIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.stockPrice", null, cancellationToken);
case "FindPersonNameIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.personName", null, cancellationToken);
case "FarewellIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.farewell", null, cancellationToken);
case "CommunicationSelectIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.options", null, cancellationToken);
case null:
break;
default:
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Sorry, I don’t have an answer for that. " +
$"(Please try something like 'What is revenue of google?')"), cancellationToken);
break;
}
}
await _botStateService.UserProfileAccessor.SetAsync(stepContext.Context, userProfile);
}
catch (Exception e)
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Sorry, there was an error sending your message. Please try again."), cancellationToken);
}
return await stepContext.NextAsync(null, cancellationToken);
}
After giving a response to the prompt in InitialStepAsync in OptionsDialog, it should go the next waterfall step, FinalStepAsync but it is going to the MainDialog and checking the response with LUIS.
The activity trigger code in the main bot program, DialogBot.cs:
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 _botStateService.UserState.SaveChangesAsync(turnContext, false, cancellationToken);
await _botStateService.ConversationState.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.Run(turnContext, _botStateService.DialogStateAccessor, cancellationToken);
}
Run method:
public static async Task Run(this Dialog dialog, ITurnContext turnContext, IStatePropertyAccessor<DialogState> accessor, CancellationToken cancellationToken = default(CancellationToken))
{
var dialogSet = new DialogSet(accessor);
dialogSet.Add(dialog);
var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken);
var results = await dialogContext.ContinueDialogAsync(cancellationToken);
if (results.Status == DialogTurnStatus.Empty)
{
await dialogContext.BeginDialogAsync(dialog.Id, null, cancellationToken);
}
}
The issue here is that after responding to the prompt, it is going to BeginDialogAsync instead of ContinueDialogAsync which is only happening the first time I run the bot. If I trigger the OptionsDialog any time later, the waterfall steps are being run properly.

Click on Cancel doesn't work on first click. But it works on next attempt

There is a function which try to scan given folder. While scanning, there is a cancel button available to user. but it doesn't cancel the scanning on first attempt, however it succeed on next attempt.
Here's my code skeleton:
private CancellationTokenSource _tokenSource;
CallingMethod()
{
var cancelationToken = GetCancelationToken();
Task.Run(() => { MethodA(true, stringVal, cancelationToken); }, cancelationToken);
}
internal void MethodA(bool isCreateNewRequested, string directoryPath, CancellationToken token)
{
bool sucess = true;
if (isCreateNewRequested)
sucess = MethodB(directoryPath, token);
if (token.IsCancellationRequested) return;
//some more code
}
private bool MethodB(string directoryPath, CancellationToken token)
{
var fileEntries = ProcessSubDirectories(directoryPath);
var totalFileCount = fileEntries.Count;
foreach (var fileEntry in fileEntries)
{
if (token.IsCancellationRequested) break;
//dosomething here
}
if (token.IsCancellationRequested) return false;
}
private CancellationToken GetCancelationToken()
{
_tokenSource?.Cancel();
_tokenSource = new CancellationTokenSource();
return _tokenSource.Token;
}
First you have to create simple cancellation token:
CancellationToken cToken = new CancellationToken();
just like that. This token has to be passed to async method, and then:
CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cToken);
Then use this cts.
if(cts.Cancelled) //written from head, so there may be other way to check it
cts.Token.ThrowIfCancellationRequested();
or:
cts.Cancel(true); //if you want to cancel manually something
cts.Token.ThrowIfCancellationRequested();
You cannot just return. You have to throw to cancel the operation.

Set time of Activity.Typing animation

I'm trying to create some animation during the time when I fetch the data from a server. "Typing" activity seems to be reasonable but it works only for ~4 seconds :
Activity reply = activity.CreateReply();
reply.Type = ActivityTypes.Typing;
reply.Text = null;
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
await connector.Conversations.ReplyToActivityAsync(reply);
I was trying to do async listening:
while (!_fetchEnded)
{
await connector.Conversations.ReplyToActivityAsync(reply);
Thread.Sleep(3000);
}
But bot it creates laggy behaviour. Is there a possibility to set the duration of "typing" activity or another way around to prevent turning the typing on and off?
Typing is displayed only a few seconds by default. You can force the display typing indicator longer by sending again typing events at a lower frequency.
Implementation example, where it will send events every 2 seconds, for 30 seconds max:
public async Task<HttpResponseMessage> Post([FromBody]Microsoft.Bot.Connector.Activity activity, CancellationToken token)
{
// Send Typing messages
var typingCancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var typingTask = SendTypingActivityUntilCancellation(activity, TimeSpan.FromSeconds(2), typingCancellation.Token);
try
{
// Activity treatment
if (activity.Type == ActivityTypes.Message)
{
// ...
}
else if (activity.Type == ActivityTypes.Event && activity.ChannelId == ChannelEnum.directline.ToString())
{
// ...
}
typingCancellation.Cancel();
await typingTask;
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (Exception e)
{
typingCancellation.Cancel();
await typingTask;
return Request.CreateResponse(HttpStatusCode.InternalServerError);
}
}
public async Task SendTypingActivityUntilCancellation(Activity activity, TimeSpan period, CancellationToken cancellationtoken)
{
try
{
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
Activity isTypingReply = activity.CreateReply();
isTypingReply.Type = ActivityTypes.Typing;
do
{
if (cancellationtoken.IsCancellationRequested == false)
{
await connector.Conversations.ReplyToActivityAsync(isTypingReply);
}
// Check again if token has not been canceled during the reply delay
if (cancellationtoken.IsCancellationRequested == false)
{
await Task.Delay(period);
}
}
while (cancellationtoken.IsCancellationRequested == false);
}
catch (OperationCanceledException)
{
//nothing to do.
}
}

Categories