public async override Task <DialogTurnResult> ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) { var turnContext = dc.Context; var step = dc.ActiveDialog.State; // Get reservation property. var newReservation = await _reservationsAccessor.GetAsync(turnContext, () => new ReservationProperty()); // Get on turn property. This has any entities that mainDispatcher, // or Bot might have captured in its LUIS model. var onTurnProperties = await _onTurnAccessor.GetAsync(turnContext, () => new OnTurnProperty("None", new List <EntityProperty>())); // If on turn property has entities.. ReservationResult updateResult = null; if (onTurnProperties.Entities.Count > 0) { // ...update reservation property with on turn property results. updateResult = newReservation.UpdateProperties(onTurnProperties); } // See if updates to reservation resulted in errors, if so, report them to user. if (updateResult != null && updateResult.Status == ReservationStatus.Incomplete && updateResult.Outcome != null && updateResult.Outcome.Count > 0) { await _reservationsAccessor.SetAsync(turnContext, updateResult.NewReservation); // Return and do not continue if there is an error. await turnContext.SendActivityAsync(updateResult.Outcome[0].Message); return(await base.ContinueDialogAsync(dc)); } // Call LUIS and get results. var luisResults = await _botServices.LuisServices[LuisConfiguration].RecognizeAsync(turnContext, cancellationToken); var topLuisIntent = luisResults.GetTopScoringIntent(); var topIntent = topLuisIntent.intent; // If we don't have an intent match from LUIS, go with the intent available via // the on turn property (parent's LUIS model). if (luisResults.Intents.Count <= 0) { // Go with intent in onTurnProperty. topIntent = string.IsNullOrWhiteSpace(onTurnProperties.Intent) ? "None" : onTurnProperties.Intent; } // Update object with LUIS result. updateResult = newReservation.UpdateProperties(OnTurnProperty.FromLuisResults(luisResults)); // See if update reservation resulted in errors, if so, report them to user. if (updateResult != null && updateResult.Status == ReservationStatus.Incomplete && updateResult.Outcome != null && updateResult.Outcome.Count > 0) { // Set reservation property. await _reservationsAccessor.SetAsync(turnContext, updateResult.NewReservation); // Return and do not continue if there is an error. await turnContext.SendActivityAsync(updateResult.Outcome[0].Message); return(await base.ContinueDialogAsync(dc)); } // Did user ask for help or said cancel or continuing the conversation? switch (topIntent) { case ContinuePromptIntent: // User does not want to make any change. updateResult.NewReservation.NeedsChange = false; break; case NoChangeIntent: // User does not want to make any change. updateResult.NewReservation.NeedsChange = false; break; case HelpIntent: // Come back with contextual help. var helpReadOut = updateResult.NewReservation.HelpReadOut(); await turnContext.SendActivityAsync(helpReadOut); break; case CancelIntent: // Start confirmation prompt. var opts = new PromptOptions { Prompt = new Activity { Type = ActivityTypes.Message, Text = "Are you sure you want to cancel?", }, }; return(await dc.PromptAsync(ConfirmCancelPrompt, opts)); case InterruptionsIntent: default: // If we picked up new entity values, do not treat this as an interruption. if (onTurnProperties.Entities.Count != 0 || luisResults.Entities.Count > 1) { break; } // Handle interruption. var onTurnProperty = await _onTurnAccessor.GetAsync(dc.Context); return(await dc.BeginDialogAsync(InterruptionDispatcher, onTurnProperty)); } // Set reservation property based on OnTurn properties. await _reservationsAccessor.SetAsync(turnContext, updateResult.NewReservation); return(await base.ContinueDialogAsync(dc)); }
private async Task <DialogTurnResult> GetAllRequiredPropertiesAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { var context = stepContext.Context; // Get current reservation from accessor. var newReservation = await ReservationsAccessor.GetAsync(stepContext.Context, () => new ReservationProperty()); // Get on turn (includes LUIS entities captured by parent). var onTurnProperty = await OnTurnAccessor.GetAsync(context); ReservationResult reservationResult = null; if (onTurnProperty != null) { if (newReservation != null) { // Update reservation object and gather results. reservationResult = newReservation.UpdateProperties(onTurnProperty); } else { reservationResult = ReservationProperty.FromOnTurnProperty(onTurnProperty); } } // Set the reservation. await ReservationsAccessor.SetAsync(context, reservationResult.NewReservation); // see if updadte reservtion resulted in errors, if so, report them to user. if (reservationResult != null && reservationResult.Status == ReservationStatus.Incomplete && reservationResult.Outcome != null && reservationResult.Outcome.Count > 0) { // Start the prompt with the initial feedback based on update results. var options = new PromptOptions() { Prompt = new Activity(text: reservationResult.Outcome[0].Message), }; return(await stepContext.PromptAsync(GetLocationDateTimePartySizePrompt, options)); } else { if (reservationResult.NewReservation.HaveCompleteReservation()) { await context.SendActivityAsync("Ok. I have a table for " + reservationResult.NewReservation.ConfirmationReadOut()); await context.SendActivityAsync(MessageFactory.SuggestedActions(new List <string> { "Yes", "No" }, "Should I go ahead and book the table ?")); } var options = new PromptOptions() { Prompt = new Activity(text: reservationResult.NewReservation.GetMissingPropertyReadOut()), }; // Start the prompt with the first missing piece of information. return(await stepContext.PromptAsync(GetLocationDateTimePartySizePrompt, options)); } }
public static ReservationResult Validate(OnTurnProperty onTurnProperty, ReservationResult returnResult) { if (onTurnProperty == null || onTurnProperty.Entities.Count == 0) { return(returnResult); } // We only will pull number -> party size, datetimeV2 -> date and time, cafeLocation -> location. var numberEntity = onTurnProperty.Entities.Find(item => item.EntityName.Equals(PartySizeEntity)); var dateTimeEntity = onTurnProperty.Entities.Find(item => item.EntityName.Equals(ReservationProperty.DateTimeEntity)); var locationEntity = onTurnProperty.Entities.Find(item => item.EntityName.Equals(LocationEntity)); var confirmationEntity = onTurnProperty.Entities.Find(item => item.EntityName.Equals(ConfirmationEntity)); if (numberEntity != null) { // We only accept MaxPartySize in a reservation. if (int.Parse(numberEntity.Value as string) > MaxPartySize) { returnResult.Outcome.Add(new ReservationOutcome("Sorry. " + int.Parse(numberEntity.Value as string) + " does not work. I can only accept up to 10 guests in a reservation.", PartySizeEntity)); returnResult.Status = ReservationStatus.Incomplete; } else { returnResult.NewReservation.PartySize = (int)numberEntity.Value; } } if (dateTimeEntity != null) { // Get parsed date time from TIMEX // LUIS returns a timex expression and so get and un-wrap that. // Take the first date time since book table scenario does not have to deal with multiple date times or date time ranges. var timeProp = dateTimeEntity.Value as string; if (timeProp != null) { var today = DateTime.Now; var parsedTimex = new TimexProperty(timeProp); // See if the date meets our constraints. if (parsedTimex.DayOfMonth != null && parsedTimex.Year != null && parsedTimex.Month != null) { var date = DateTimeOffset.Parse($"{parsedTimex.Year}-${parsedTimex.Month}-${parsedTimex.DayOfMonth}"); returnResult.NewReservation.Date = date.UtcDateTime.ToString("o").Split("T")[0]; returnResult.NewReservation.DateLGString = new TimexProperty(returnResult.NewReservation.Date).ToNaturalLanguage(today); var validDate = TimexRangeResolver.Evaluate(dateTimeEntity.Value as string[], reservationDateConstraints); if (validDate != null || (validDate.Count == 0)) { // Validation failed! returnResult.Outcome.Add(new ReservationOutcome( $"Sorry. {returnResult.NewReservation.DateLGString} does not work. " + "I can only make reservations for the next 4 weeks.", ReservationProperty.DateTimeEntity)); returnResult.NewReservation.Date = string.Empty; returnResult.Status = ReservationStatus.Incomplete; } } // See if the time meets our constraints. if (parsedTimex.Hour != null && parsedTimex.Minute != null && parsedTimex.Second != null) { var validtime = TimexRangeResolver.Evaluate(dateTimeEntity.Value as string[], reservationTimeConstraints); returnResult.NewReservation.Time = ((int)parsedTimex.Hour).ToString("D2"); returnResult.NewReservation.Time += ":"; returnResult.NewReservation.Time += ((int)parsedTimex.Minute).ToString("D2"); returnResult.NewReservation.Time += ":"; returnResult.NewReservation.Time += ((int)parsedTimex.Second).ToString("D2"); if (validtime != null || (validtime.Count == 0)) { // Validation failed! returnResult.Outcome.Add(new ReservationOutcome("Sorry, that time does not work. I can only make reservations that are in the daytime (6AM - 6PM)", ReservationProperty.DateTimeEntity)); returnResult.NewReservation.Time = string.Empty; returnResult.Status = ReservationStatus.Incomplete; } } // Get date time LG string if we have both date and time. if (string.IsNullOrWhiteSpace(returnResult.NewReservation.Date) && string.IsNullOrWhiteSpace(returnResult.NewReservation.Time)) { returnResult.NewReservation.DateTimeLGString = new TimexProperty(returnResult.NewReservation.Date + "T" + returnResult.NewReservation.Time).ToNaturalLanguage(today); } } } // Take the first found value. if (locationEntity != null) { var cafeLocation = ((JObject)locationEntity.Value)[0][0]; // Capitalize cafe location. returnResult.NewReservation.Location = char.ToUpper(((string)cafeLocation)[0]) + ((string)cafeLocation).Substring(1); } // Accept confirmation entity if available only if we have a complete reservation if (confirmationEntity != null) { if ((string)((JObject)confirmationEntity.Value)[0][0] == "yes") { returnResult.NewReservation.ReservationConfirmed = true; returnResult.NewReservation.NeedsChange = false; } else { returnResult.NewReservation.NeedsChange = true; returnResult.NewReservation.ReservationConfirmed = false; } } return(returnResult); }
public ReservationResult UpdateProperties(OnTurnProperty onTurnProperty) { var returnResult = new ReservationResult(this); return(Validate(onTurnProperty, returnResult)); }
public static ReservationResult FromOnTurnProperty(OnTurnProperty onTurnProperty) { var returnResult = new ReservationResult(new ReservationProperty()); return(Validate(onTurnProperty, returnResult)); }