I am trying to migrate my codes from the old Bot Framework V4(last January) to the new Bot framework v4 design. What I like is to call a dialog when the user input. "Get started". I can do this easily on the previous design but now I can't. Also this is just text now. what if i want to use a LUIS intent? Thank you.
protected override async Task<DialogTurnResult> OnBeginDialogAsync(
DialogContext innerDc,
object options,
CancellationToken cancellationToken = default(CancellationToken))
{
var result = await InterruptAsync(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnBeginDialogAsync(innerDc, options, cancellationToken);
}
protected override async Task<DialogTurnResult> OnContinueDialogAsync(
DialogContext innerDc,
CancellationToken cancellationToken)
{
var result = await InterruptAsync(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
private async Task<DialogTurnResult> InterruptAsync(
DialogContext innerDc,
CancellationToken cancellationToken)
{
if (innerDc.Context.Activity.Type == ActivityTypes.Message)
{
var text = innerDc.Context.Activity.Text.ToLowerInvariant();
switch (text)
{
case "help":
case "?":
await innerDc.Context.SendActivityAsync($"Show Help...",
cancellationToken: cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
case "get started":
return await innerDc.BeginDialogAsync(nameof(DialogA));
case "cancel":
case "quit":
await innerDc.Context.SendActivityAsync($"Cancelling",
cancellationToken: cancellationToken);
return await innerDc.CancelAllDialogsAsync();
}
}
return null;
}
DialogA
namespace practiceNewBot.Dialogs
{
public class DialogA : CancelAndHelpDialog
{
public DialogA() : base(nameof(DialogA))
{
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
FirstStepAsync,
}));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
private async Task<DialogTurnResult> FirstStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"start of dialog a"), cancellationToken);
return await stepContext.PromptAsync(
nameof(ChoicePrompt),
new PromptOptions
{
Prompt = MessageFactory.Text($"choose a dialog"),
Choices = new List<Choice>
{
new Choice
{
Value = "Dialog A Child",
Synonyms = new List<string>
{
"dialog a child",
},
},
new Choice
{
Value = "Dialog B Child",
Synonyms = new List<string>
{
"dialog b chilc",
},
},
new Choice
{
Value = "Cancel",
Synonyms = new List<string>
{
"cancel",
},
},
},
RetryPrompt = MessageFactory.Text("Please choose one of the options."),
},
cancellationToken: cancellationToken);
}
private async Task<DialogTurnResult> SecondStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var response = (stepContext.Result as FoundChoice).Value.ToLower();
switch (response)
{
case "dialog a child":
return await stepContext.BeginDialogAsync(nameof(DialogA), cancellationToken: cancellationToken);
case "dialog b child":
return await stepContext.BeginDialogAsync(nameof(DialogB), cancellationToken);
case "cancel":
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Ok, Cancelled."));
return await stepContext.CancelAllDialogsAsync(cancellationToken: cancellationToken);
default:
break;
}
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"end of dialog a"), cancellationToken);
return await stepContext.EndDialogAsync();
}
}
}
Related
I'm new to Bot Framework, if i made any mistake, how to correct it?
A simple scenario of my Bot which is used for flight booking, bot gets input from user for from city, to city, departure date, return date. After receiving return date, i have to validate whether the given date is greater than departure date.
How to pass values from StepContext to Promptvalidatorcontext?
public class Travel_FlightBookingDialog : ComponentDialog
{
public Travel_FlightBookingDialog(string dialogId) : base(nameof(Travel_FlightBookingDialog))
{
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
AddDialog(new ConfirmPrompt("confirmPrompt"));
AddDialog(new TextPrompt("FROMCITY", FromCityValidationAsync));
AddDialog(new TextPrompt("DEPARTUREDATE", DepartureValidationAsync));
AddDialog(new TextPrompt("ARRIVALDATE", ArrivalValidationAsync));
AddDialog(new TextPrompt("PAXCOUNT", PaxCountValidationAsync));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
BOOKINGTYPE,
FROMCITY,
TOCITY,
DEPARTUREDATE,
ARRIVALDATE,
PAXCOUNT,
CLASSOFBOOKING,
DIRECTORCONN,
LCCORFSC,
CONFIRM,
FINAL
}));
InitialDialogId = nameof(WaterfallDialog);
}
private async Task<bool> DepartureValidationAsync(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)
{
var DepartureDate = promptContext.Recognized.Value;
string valifinfo = ValidateDate(DepartureDate);
if (valifinfo == "")
{
return true;
}
else
{
await promptContext.Context.SendActivityAsync(valifinfo);
return false;
}
}
private async Task<bool> ArrivalValidationAsync(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)
{
**//HOW DO WE GET THE VALUE OF DEPARTURE DATE HERE BECAUSE
//HERE NEED TO VALIDATE - RETURN DATE SHOULD BE GREATER THAN DEPARTURE DATE**
}
private async Task<bool> FromCityValidationAsync(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)
{
var FromcityValidation = promptContext.Recognized.Value;
var count = Convert.ToString(promptContext.Recognized.Value).Length;
List<string> range;
if (count != 3)
{
range = IataHelper1.findAirport("", FromcityValidation);
var strlist = "";
foreach (string item in range)
{
if (item != "NODATA")
{
strlist += item + Environment.NewLine;
}
}
if (range.Count > 0)
{
if (range.Count == 1)
{
return true;
}
else if (range.Count > 1)
{
await promptContext.Context.SendActivityAsync($"Kindly Choose any one Airport from suggestion List :{Environment.NewLine} {strlist}");
return false;
}
}
else
{
await promptContext.Context.SendActivityAsync("Please Enter valid City name.....!!!!!");
return false;
}
}
else
{
range = IataHelper1.findAirport(FromcityValidation, "");
if (range != null)
{
foreach (string item in range)
{
if (item != "NODATA")
{
///return Task.FromResult(true);
return true;
}
}
}
else
{
await promptContext.Context.SendActivityAsync("Please Enter valid City Code.....!!!!!");
return false;
}
}
return false;
}
private async Task<DialogTurnResult> BOOKINGTYPE(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var pmoptions = new PromptOptions
{
Prompt = MessageFactory.Text($"Please Choose Booking Type :"),
Choices = GetBookingType()
};
return await stepContext.PromptAsync(nameof(ChoicePrompt), pmoptions, cancellationToken);
}
private async Task<DialogTurnResult> FROMCITY(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["BOOKINGTYPE"] = stepContext.Result;
var promptFromCity = stepContext.PromptAsync("FROMCITY", new PromptOptions()
{
Prompt = MessageFactory.Text($"Please enter Departure City Code or City Name (Eg: DEL or Delhi)"),
RetryPrompt = MessageFactory.Text("Please enter the valid City Code or City Name.....")
}, cancellationToken);
return await promptFromCity;
}
private async Task<DialogTurnResult> TOCITY(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["FROMCITY"] = stepContext.Result;
var promptToCity = stepContext.PromptAsync("FROMCITY", new PromptOptions()
{
Prompt = MessageFactory.Text("Please enter Arrival City Code or City Name (Eg: MAA or Chennai)"),
RetryPrompt = MessageFactory.Text("Please enter the valid City Code or City Name.....")
}, cancellationToken);
return await promptToCity;
}
private async Task<DialogTurnResult> DEPARTUREDATE(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["TOCITY"] = stepContext.Result;
var promptDepartureDate = stepContext.PromptAsync("DEPARTUREDATE", new PromptOptions()
{
Prompt = MessageFactory.Text("Please enter Departure Date : (Eg: DD-MM-YYYY format)..."),
RetryPrompt = MessageFactory.Text("Please enter the valid Date.....")
}, cancellationToken);
return await promptDepartureDate;
}
private async Task<DialogTurnResult> ARRIVALDATE(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["DEPARTUREDATE"] = stepContext.Result;
var bookingtype = (FoundChoice)stepContext.Values["BOOKINGTYPE"];
if (bookingtype.Value == "ONEWAY")
{
return await stepContext.NextAsync(null, cancellationToken);
}
var promptArrivalDate = stepContext.PromptAsync("ARRIVALDATE", new PromptOptions()
{
Prompt = MessageFactory.Text("Please enter Arrival Date in case of Roundtrip : (Eg: DD-MM-YYYY format)..."),
RetryPrompt = MessageFactory.Text("Please enter the valid Date.....")
}, cancellationToken);
return await promptArrivalDate;
}
private async Task<DialogTurnResult> PAXCOUNT(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["ARRIVALDATE"] = stepContext.Result;
var promptPaxCount = stepContext.PromptAsync("PAXCOUNT", new PromptOptions()
{
Prompt = MessageFactory.Text("Please enter No.Of Passengers : (Eg: AdultCount,ChildCount,InfantCount)..."),
}, cancellationToken);
return await promptPaxCount;
}
public string ValidateDate(string Date)
{
DateTime dateTimeObj = DateTime.ParseExact(Date, "dd-MM-yyyy", CultureInfo.InvariantCulture);
if (Date != "" && dateTimeObj < DateTime.Today)
{
return "Date should be Future or equal To Today Date";
}
return "";
}
}
You should use turnContext to pass information from stepContext to promptContext.
promptContext.context.turnState. =
I have the following code:
public class MainDialog : ComponentDialog
{
protected readonly IConfiguration Configuration;
protected readonly ILogger Logger;
protected int counter = 0;
protected bool HaveToken = false;
protected static string Token = "";
public MainDialog(IConfiguration configuration, ILogger<MainDialog> logger)
: base(nameof(MainDialog))
{
//cache the config from appstettings.json for later usage in LUIS & QnAMaker.
Configuration = configuration;
Logger = logger;
AddDialog(new OAuthPrompt(
nameof(OAuthPrompt),
new OAuthPromptSettings
{
ConnectionName = Configuration["ConnectionName"],
Text = "Bitte melden sie sich an.",
Title = "Login",
Timeout = 3000,
}));
AddDialog(new TextPrompt(nameof(TextPrompt)));
//Adding Dialogs and giving the Dialogs the config info for the QnAMaker connection.
AddDialog(new HelpDialog(Configuration, logger));
AddDialog(new SpellingDialog(Configuration, logger));
AddDialog(new CreateTeamDialog(Configuration, logger));
AddDialog(new DIGASDialog(Configuration, logger));
AddDialog(new GreetingDialog(Configuration, logger));
AddDialog(new OnboardingDialog(Configuration, logger));
//AddDialog(new LoginDialog(Configuration, logger));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
PromptStepAsync,
LoginStepAsync,
ProcessStepAsync,
IntroStepAsync,
ActStepAsync,
FinalStepAsync
}));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
private async Task<DialogTurnResult> PromptStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.BeginDialogAsync(nameof(OAuthPrompt),null, cancellationToken);
}
private async Task<DialogTurnResult> LoginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
if (HaveToken)
{
return await stepContext.NextAsync(cancellationToken);
}
if(stepContext.Result.GetType() == typeof(CancellationToken) )
{
return await stepContext.EndDialogAsync();
}
try
{
var tokenResponse = (TokenResponse)stepContext.Result;
Token = tokenResponse.Token;
HaveToken = true;
await stepContext.Context.SendActivityAsync(MessageFactory.Text("You are now logged in."), cancellationToken);
//await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Your token is: {tokenResponse.Token}"), cancellationToken);
//await OAuthhelper.ListMeAsync(stepContext.Context, tokenResponse);
return await stepContext.NextAsync(cancellationToken);
}
catch (Exception x)
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("Login was not successful please try again. " + x.Message), cancellationToken);
return await stepContext.EndDialogAsync();
}
}
private async Task<DialogTurnResult> ProcessStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
if (HaveToken)
{
return await stepContext.NextAsync(cancellationToken);
}
if (stepContext.Result != null)
{
// We do not need to store the token in the bot. When we need the token we can
// send another prompt. If the token is valid the user will not need to log back in.
// The token will be available in the Result property of the task.
var tokenResponse = stepContext.Result as TokenResponse;
// If we have the token use the user is authenticated so we may use it to make API calls.
if (tokenResponse?.Token != null)
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Your token is: {tokenResponse.Token}"), cancellationToken);
}
}
else
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("We couldn't log you in. Please try again later."), cancellationToken);
}
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
private async Task<DialogTurnResult> IntroStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.ContinueDialogAsync(cancellationToken);
}
private async Task<DialogTurnResult> ActStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var intentDetails = new QueryDetails();
// Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.)
intentDetails = stepContext.Result != null
?
await LuisHelper.ExecuteLuisQuery(Configuration, Logger, stepContext.Context, cancellationToken)
:
new QueryDetails();
switch (intentDetails.Intent)
{
case "Hilfe":
return await stepContext.BeginDialogAsync(nameof(HelpDialog), intentDetails, cancellationToken);
case "Schreibweise_Ausdruck":
return await stepContext.BeginDialogAsync(nameof(SpellingDialog), intentDetails, cancellationToken);
case "Team_erstellen":
return await stepContext.BeginDialogAsync(nameof(CreateTeamDialog), intentDetails, cancellationToken);
case "DIGAS":
return await stepContext.BeginDialogAsync(nameof(DIGASDialog), intentDetails, cancellationToken);
case "Begrueßung":
return await stepContext.BeginDialogAsync(nameof(GreetingDialog), intentDetails, cancellationToken);
case "Onboarding":
return await stepContext.BeginDialogAsync(nameof(OnboardingDialog), intentDetails, cancellationToken);
default:
await stepContext.Context.SendActivityAsync("Das habe ich nicht verstanden.");
return await stepContext.BeginDialogAsync(nameof(HelpDialog), intentDetails, cancellationToken);
}
}
private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
//await stepContext.Context.SendActivityAsync(MessageFactory.Text("Vielen Dank."), cancellationToken);
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
public static string getToken()
{
return Token;
}
}
After the Promptstep, the next step does not run until I give input. I more or less copied the AuthBot sample from Microsoft and modified it a little.
In the Promptstep the user authenticates himself, but after the prompt vanishes the Bot waits for more input instead of jumping to the next step.
The bot, where I copied the code from:
https://github.com/microsoft/BotBuilder-Samples/blob/main/samples/csharp_dotnetcore/46.teams-auth/Dialogs/MainDialog.cs
I could not test the sample, because it doesn't run on it's own.
When I testet my bot in the Emulator with ngrok tunneling, the token was recieved but the bot just exits and waits for input.
Here is what happens:
Why is LoginStep not starting after the prompt?
I think you should add ChoicePrompt too:
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
I want the first step of the WaterfallDialog to prompt when the class is called. Right now to only do this is to use the OnMembersAddedAsync method which sends like a welcome message. Which is not I needed.
I want the StartConversation to automatically fire when the class is called.
public class HomeOfficeDialogV3 : ComponentDialog
{
private readonly StateAccessor _StateAccessor;
private readonly DirectlineApi _TokenReq;
private readonly IndicatorDelay _IndicatorDelay;
private UserDataExtractedDto _UserDataExtractedDto;
public HomeOfficeDialogV3(StateAccessor userState)
{
_StateAccessor = userState;
_TokenReq = new DirectlineApi();
_IndicatorDelay = new IndicatorDelay();
var waterfallSteps = new WaterfallStep[]
{
StartConversation,
};
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)) { Style = ListStyle.SuggestedAction });
AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));
AddDialog(new NumberPrompt<int>(nameof(NumberPrompt<int>)));
InitialDialogId = nameof(WaterfallDialog);
}
public HomeOfficeDialogV3(UserDataExtractedDto userDataExtractedDto)
{
_UserDataExtractedDto = userDataExtractedDto;
}
public async Task<DialogTurnResult> StartConversation(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await _IndicatorDelay.ShowTypingIndicator(stepContext.Context, 1400);
return await stepContext.PromptAsync(nameof(ChoicePrompt),
new PromptOptions {
Prompt = MessageFactory.Text($"Did you know your office space can affect your well-being?"),
Choices = ChoiceFactory.ToChoices(new List<string> { "Hello Buddy 😊" })
}
);
}
}
public MainDialog(IConfiguration configuration, StateAccessor userState)
{
_StateAccessor = userState;
_TokenReq = new DirectlineApi();
_DialogsTurn = new DialogsTurn();
private DialogCurrentDayId _DialogCurrentDayId;
var waterfallSteps = new WaterfallStep[]
{
DialogsTurn
};
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
AddDialog(new WelcomeDiloagV1(userState));
AddDialog(new ExerciseDialogV2(userState));
AddDialog(new HomeOfficeDialogV3(userState));
InitialDialogId = nameof(WaterfallDialog);
}
public MainDialog(DialogCurrentDayId dialogCurrentDayId)
{
_DialogCurrentDayId = dialogCurrentDayId;
}
private async Task<DialogTurnResult> DialogsTurn(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var dialogTurnID = Convert.ToInt32(UserDataExtractedDto.DialogTurnID);
var dialogEnum = Enum.GetValues(typeof(DialogEnumerator)).Cast<DialogEnumerator>();
var currentDialog = String.Empty;
foreach(DialogEnumerator dialog in dialogEnum)
{
if (dialogTurnID > (int)dialog)
{
dialogTurnID = dialogEnum.Count();
}
if ((int)dialog == dialogTurnID)
{
currentDialog = dialog.ToString();
}
}
return await stepContext.BeginDialogAsync(currentDialog, null, cancellationToken);
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<MainDialog>();
services.AddTransient<IBot, MainBot<MainDialog>>();
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
foreach (var member in turnContext.Activity.MembersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
var token = turnContext.Activity.From.Name;
if (!String.IsNullOrEmpty(token))
{
var ConversationExistingId = await _StateAccessor.existingConversation.GetAsync(turnContext, () => new ExistingConversation(), cancellationToken);
if (_IsTest)
{
_UserDataFromReactWebChat.Token = _TokenTest;
_UserDataFromReactWebChat.UserId = _IdTest;
}
else
{
_UserDataFromReactWebChat.Token = turnContext.Activity.From.Name;
_UserDataFromReactWebChat.UserId = Int32.Parse(turnContext.Activity.From.Id);
}
var getDialogID = new DialogsTurn(_UserDataFromReactWebChat);
var initializeDialogsById = await getDialogID.GetNextDialog();
if (initializeDialogsById != null){
new MainDialog(initializeDialogsById);
}
await _IndicatorDelay.ShowTypingIndicator(turnContext, 5500);
var isGranted = String.Empty;
var userAllData = new UsersAllData(_UserDataFromReactWebChat);
var usersInfoFromWebApp = await userAllData.GetUsersInfoFromWebApp();
foreach (var i in usersInfoFromWebApp)
{
if(!String.IsNullOrEmpty(i.IsGranted))
{
isGranted = i.IsGranted.ToString();
}
new WelcomeDiloagV1(i);
new ExerciseDialogV2(i);
new HomeOfficeDialogV3(i);
//break;
}
if (isGranted == "true")
{
if (string.IsNullOrEmpty(ConversationExistingId.ConverstationId))
{
ConversationExistingId.ConverstationId = turnContext.Activity.Conversation.Id;
await _StateAccessor.existingConversation.SetAsync(turnContext, ConversationExistingId, cancellationToken);
await _StateAccessor.existingConversationState.SaveChangesAsync(turnContext);
}
else
{
var Conversation_id = ConversationExistingId.ConverstationId;
var TokenGenerated = _UserDataFromReactWebChat.Token;
}
}
else if (isGranted == "false")
{
await turnContext.SendActivityAsync(MessageFactory.Text("Unathorized requests."), cancellationToken);
}
}
else
{
await turnContext.SendActivityAsync(MessageFactory.Text($"Oops! Something went wrong."), cancellationToken);
}
}
}
}
I have multiple steps but I cut it out for this example.
If your bot is based on the document that you linked to then it will access the dialog stack from OnMessageActivityAsync using Dialog.RunAsync. You can do the same thing in OnMembersAddedAsync.
I have a problem with my component dialogs in botframework V4.
I'm using a root dialog which contains a Waterfalldialog and an Setupdialog. The initial dialog is the Setupdialog. Like so:
public RootDialog(SetupDialog setupDialog)
: base(nameof(RootDialog))
{
AddDialog(setupDialog);
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
ProcessStep
}));
InitialDialogId = nameof(SetupDialog);
}
In the setup dialog I'm asking for some value. If the setup dialogs continues I check the value and if its like I wanted, I end the dialog.
public override Task<DialogTurnResult> ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default)
{
if (!int.TryParse(outerDc.Context.Activity.Text, out var recursions))
{
return outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text($"{outerDc.Context.Activity.Text} konnte nicht in eine Zahl konvertiert werden.")
});
}
return outerDc.EndDialogAsync(recursions);
}
If I end the dialog like this, shouldn't be ResumeDialog be called in the RootDialog?
Here the whole Dialogs:
public class RootDialog : ComponentDialog
{
private int recursions;
public RootDialog(SetupDialog setupDialog)
: base(nameof(RootDialog))
{
AddDialog(setupDialog);
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
ProcessStep
}));
InitialDialogId = nameof(SetupDialog);
}
public async Task<DialogTurnResult> ProcessStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Process step in root dialog")
});
if(Dialogs.Find(nameof(RecursiveDialog)) == null)
{
AddDialog(new RecursiveDialog(new DialogSet(), recursions));
}
if (recursions > 0)
{
return await stepContext.BeginDialogAsync(nameof(RecursiveDialog));
}
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Recursion lower or eqaul 0")
});
}
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default)
{
var dialogContext = CreateChildContext(outerDc);
await dialogContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Begin root dialog")
});
return await base.BeginDialogAsync(outerDc, options, cancellationToken);
}
public override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
if(innerDc.ActiveDialog != null)
{
return await innerDc.ContinueDialogAsync();
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
public override async Task<DialogTurnResult> ResumeDialogAsync(DialogContext outerDc, DialogReason reason, object result = null, CancellationToken cancellationToken = default)
{
recursions = Convert.ToInt32(result);
await outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text($"Resume root dialog with recursion value {recursions}")
});
return await outerDc.BeginDialogAsync(nameof(WaterfallDialog));
}
}
SetupDialog:
public class SetupDialog : ComponentDialog
{
public SetupDialog()
: base(nameof(SetupDialog))
{
AddDialog(new TextPrompt(nameof(TextPrompt)));
}
public override Task<DialogTurnResult> ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default)
{
if (!int.TryParse(outerDc.Context.Activity.Text, out var recursions))
{
return outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text($"{outerDc.Context.Activity.Text} konnte nicht in eine Zahl konvertiert werden.")
});
}
return outerDc.EndDialogAsync(recursions);
}
public override Task<DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default)
{
return innerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text("Wie viel rekursionen sollen erstellt werden?")
});
}
}
So, I got the ResumeDialog method to be triggerd.
To trigger the ResumeDialog method, the dialog that ended and the dialog you want to resume to has to be on the same dialog stack!
My scenario were BotDialogContext[RootDialog] -> RootDialogContext[SetupDialog], but I need a context like this BotDialogContext[RootDialog, SetupDialog].
One of the problems is, that every ComponentDialog you start creates its own DialogContext. So if you begin a dialog within a dialog its pushed on the stack of the inner DialogContext and so on. But the description of the ResumeDialog method is
Called when a child dialog on the parent's dialog stack completed this turn, returning control to this dialog component.
To put an child dialog on the parents dialog stack you had to call the BeginDialog method on the outer dialog context. This context also needs to have the "child dialog" in its dialogset.
Here my example:
RootDialog.cs:
public class RootDialog : ComponentDialog
{
private int recursions;
public RootDialog(SetupDialog setupDialog)
: base(nameof(RootDialog))
{
AddDialog(setupDialog);
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
ProcessStep
}));
InitialDialogId = nameof(SetupDialog);
}
public async Task<DialogTurnResult> ProcessStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Process step in root dialog")
});
if(Dialogs.Find(nameof(RecursiveDialog)) == null)
{
AddDialog(new RecursiveDialog(new DialogSet(), recursions));
}
if (recursions > 0)
{
return await stepContext.BeginDialogAsync(nameof(RecursiveDialog));
}
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Recursion lower or eqaul 0")
});
}
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default)
{
if (true)
{
return await outerDc.BeginDialogAsync(nameof(SetupDialog));
}
var dialogContext = CreateChildContext(outerDc);
await dialogContext.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text("Begin root dialog")
});
return await base.BeginDialogAsync(outerDc, options, cancellationToken);
}
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
if(innerDc.ActiveDialog != null)
{
return await innerDc.ContinueDialogAsync();
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
public override async Task<DialogTurnResult> ResumeDialogAsync(DialogContext outerDc, DialogReason reason, object result = null, CancellationToken cancellationToken = default)
{
recursions = Convert.ToInt32(result);
await outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions
{
Prompt = MessageFactory.Text($"Resume root dialog with recursion value {recursions}")
});
return await outerDc.BeginDialogAsync(nameof(WaterfallDialog));
}
}
SetupDialog.cs:
public class SetupDialog : ComponentDialog
{
public SetupDialog()
: base(nameof(SetupDialog))
{
AddDialog(new TextPrompt(nameof(TextPrompt)));
}
public override Task<DialogTurnResult> ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default)
{
if (!int.TryParse(outerDc.Context.Activity.Text, out var recursions))
{
return outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text($"{outerDc.Context.Activity.Text} konnte nicht in eine Zahl konvertiert werden.")
});
}
return outerDc.EndDialogAsync(recursions);
}
public override Task<DialogTurnResult> BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default)
{
return base.BeginDialogAsync(outerDc, options, cancellationToken);
}
protected override Task<DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default)
{
return innerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
{
Prompt = MessageFactory.Text("Wie viel rekursionen sollen erstellt werden?")
});
}
}
DialogBot.cs:
public class DialogBot<T> : ActivityHandler
where T : Dialog
{
protected readonly DialogSet Dialogs;
protected readonly BotState ConversationState;
protected readonly BotState UserState;
protected readonly ILogger Logger;
public DialogBot(ConversationState conversationState, UserState userState, IEnumerable<Dialog> dialogs, ILogger<DialogBot<T>> logger)
{
ConversationState = conversationState;
UserState = userState;
Logger = logger;
Dialogs = new DialogSet(conversationState.CreateProperty<DialogState>(nameof(DialogState)));
foreach(var dialog in dialogs)
{
Dialogs.Add(dialog);
}
}
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 ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Running dialog with Message Activity.");
var dc = await Dialogs.CreateContextAsync(turnContext, cancellationToken).ConfigureAwait(false);
if (dc.ActiveDialog != null)
{
await dc.ContinueDialogAsync();
}
else
{
// Run the Dialog with the new message Activity.
await dc.BeginDialogAsync(typeof(T).Name, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
}
}
}
Inside the IEnumerable are both (RootDialog & SetupDialog) to get both dialogs into to the BotDialogContext and DialogSet
I am using NLP Dispatch , through which i have a merged luis and QnA model, I am able to normally call and add my own logic inside top scoring intents block. Now i have a dialog class which i want to be called whenever a top scoring intent is being detected
namespace Microsoft.BotBuilderSamples
{
public class DispatchBot : ActivityHandler
{
private ILogger<DispatchBot> _logger;
private IBotServices _botServices;
public DispatchBot(IBotServices botServices, ILogger<DispatchBot> logger)
{
_logger = logger;
_botServices = botServices;
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
// First, we use the dispatch model to determine which cognitive service (LUIS or QnA) to use.
var recognizerResult = await _botServices.Dispatch.RecognizeAsync(turnContext, cancellationToken);
// Top intent tell us which cognitive service to use.
var topIntent = recognizerResult.GetTopScoringIntent();
// Next, we call the dispatcher with the top intent.
await DispatchToTopIntentAsync(turnContext, topIntent.intent, recognizerResult, cancellationToken);
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
const string WelcomeText = "I am here to make your bot experience much more easier";
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text($"Hi {member.Name}, I am your IT assistant at your service . {WelcomeText}"), cancellationToken);
}
}
}
private async Task DispatchToTopIntentAsync(ITurnContext<IMessageActivity> turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken)
{
switch (intent)
{
case "l_mts-bot-809f":
await ProcessHomeAutomationAsync(turnContext, recognizerResult.Properties["luisResult"] as LuisResult, cancellationToken);
break;
case "q_mts-bot":
await ProcessSampleQnAAsync(turnContext, cancellationToken);
break;
default:
_logger.LogInformation($"Dispatch unrecognized intent: {intent}.");
await turnContext.SendActivityAsync(MessageFactory.Text($"Dispatch unrecognized intent: {intent}."), cancellationToken);
break;
}
}
private Activity CreateResponse(IActivity activity, Attachment attachment)
{
var response = ((Activity)activity).CreateReply();
response.Attachments = new List<Attachment>() { attachment };
return response;
}
private async Task ProcessHomeAutomationAsync(ITurnContext<IMessageActivity> turnContext, LuisResult luisResult, CancellationToken cancellationToken)
{
_logger.LogInformation("ProcessHomeAutomationAsync");
// Retrieve LUIS result for Process Automation.
var result = luisResult.ConnectedServiceResult;
var topIntent = result.TopScoringIntent.Intent;
var entity = result.Entities;
if (topIntent == "welcome")
{
await turnContext.SendActivityAsync(MessageFactory.Text("Hi,This is your IT assistant"), cancellationToken);
}
if (topIntent == "None")
{
await turnContext.SendActivityAsync(MessageFactory.Text("Sorry I didnt get you!"), cancellationToken);
}
if (topIntent == "DateTenure")
{
// Here i want to call my dialog class
}
}
}
private async Task ProcessSampleQnAAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
_logger.LogInformation("ProcessSampleQnAAsync");
var results = await _botServices.SampleQnA.GetAnswersAsync(turnContext);
if (results.Any())
{
await turnContext.SendActivityAsync(MessageFactory.Text(results.First().Answer), cancellationToken);
}
else
{
await turnContext.SendActivityAsync(MessageFactory.Text("Sorry, could not find an answer in the Q and A system."), cancellationToken);
}
}
}
}
I want as and when my top intent is detected , my custom dialog comes into action and the handling of conversation is then should be handled by my dialog class.
private async Task DispatchToTopIntentAsync(ITurnContext<IMessageActivity> turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken)
{
switch (intent)
{
case "l_mts-bot-809f":
//I MADE CHANGES HERE
await dc.BeginDialogAsync(nameof(DIALOG_CLASS_I_WANT_TO_START));
break;
case "q_mts-bot":
await ProcessSampleQnAAsync(turnContext, cancellationToken);
break;
default:
_logger.LogInformation($"Dispatch unrecognized intent: {intent}.");
await turnContext.SendActivityAsync(MessageFactory.Text($"Dispatch unrecognized intent: {intent}."), cancellationToken);
break;
}
}
Just remember to add the dialog to either your main dialog class like this:
public MainDialog(UserState userState)
: base(nameof(MainDialog))
{
_userState = userState;
AddDialog(new DIALOG_CLASS_I_WANT_TO_START());
InitialDialogId = nameof(aDifferentDialogNotShownHere);
}
or to your startup.cs as a transient:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//all the other configure stuff...
// Register dialogs
services.AddTransient<MainDialog>();
services.AddTransient<DIALOG_CLASS_I_WANT_TO_START>();