Beispiel #1
0
        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));
            }
        }
Beispiel #3
0
        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);
        }
Beispiel #4
0
        public ReservationResult UpdateProperties(OnTurnProperty onTurnProperty)
        {
            var returnResult = new ReservationResult(this);

            return(Validate(onTurnProperty, returnResult));
        }
Beispiel #5
0
        public static ReservationResult FromOnTurnProperty(OnTurnProperty onTurnProperty)
        {
            var returnResult = new ReservationResult(new ReservationProperty());

            return(Validate(onTurnProperty, returnResult));
        }