Exemple #1
0
        private async Task <DialogTurnResult> PromptForConfirmationStepAsync(WaterfallStepContext step, CancellationToken cancellationToken)
        {
            var state = await _stateAccessor.GetAsync(step.Context, cancellationToken : cancellationToken);

            Reservation reservation;

            try
            {
                var api = new CarwashService(step, _telemetryClient, cancellationToken);

                reservation = await api.GetReservationAsync(state.ReservationId, cancellationToken);
            }
            catch (AuthenticationException)
            {
                await step.Context.SendActivityAsync(AuthDialog.NotAuthenticatedMessage, cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }
            catch (Exception e)
            {
                _telemetryClient.TrackException(e);
                await step.Context.SendActivityAsync(e.Message, cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }

            return(await step.PromptAsync(
                       ConfirmationPromptName,
                       new PromptOptions
            {
                Prompt = MessageFactory.Text($"Are you sure you want to cancel your reservation on {reservation.StartDate.ToNaturalLanguage()} for {reservation.VehiclePlateNumber}?"),
            },
                       cancellationToken));
        }
Exemple #2
0
        private async Task <DialogTurnResult> PromptForBuildingStepAsync(WaterfallStepContext step, CancellationToken cancellationToken)
        {
            var state = await _stateAccessor.GetAsync(step.Context, cancellationToken : cancellationToken);

            if (string.IsNullOrWhiteSpace(state.ReservationId))
            {
                state.ReservationId = step.Result as string;
                await _stateAccessor.SetAsync(step.Context, state, cancellationToken);
            }

            Reservation reservation;

            try
            {
                var api = new CarwashService(step, _telemetryClient, cancellationToken);

                reservation = await api.GetReservationAsync(state.ReservationId, cancellationToken);
            }
            catch (AuthenticationException)
            {
                await step.Context.SendActivityAsync(AuthDialog.NotAuthenticatedMessage, cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }
            catch (Exception e)
            {
                _telemetryClient.TrackException(e);
                await step.Context.SendActivityAsync("I am not able to access your reservations right now.", cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }

            if (!string.IsNullOrWhiteSpace(reservation.Location))
            {
                var location = reservation.Location.Split('/');
                state.Building = location[0];
                state.Floor    = location[1];
                state.Seat     = location[2];

                return(await step.NextAsync(cancellationToken : cancellationToken));
            }

            return(await step.PromptAsync(
                       BuildingPromptName,
                       new PromptOptions
            {
                Prompt = MessageFactory.Text("In which building did you park the car?"),
                Choices = new List <Choice> {
                    new Choice("M"), new Choice("S1"), new Choice("GS"), new Choice("HX")
                },
            },
                       cancellationToken));
        }
Exemple #3
0
        private async Task <DialogTurnResult> DisplayCancellationConfirmationStepAsync(WaterfallStepContext step, CancellationToken cancellationToken)
        {
            var state = await _stateAccessor.GetAsync(step.Context, cancellationToken : cancellationToken);

            if (!(step.Result is bool confirmed && confirmed))
            {
                await step.Context.SendActivityAsync("Fine, I won't cancel that.", cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }

            try
            {
                var api = new CarwashService(step, _telemetryClient, cancellationToken);

                // Cancel the reservation
                await api.CancelReservationAsync(state.ReservationId, cancellationToken);
            }
            catch (AuthenticationException)
            {
                await step.Context.SendActivityAsync(AuthDialog.NotAuthenticatedMessage, cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }
            catch (Exception e)
            {
                _telemetryClient.TrackException(e);
                await step.Context.SendActivityAsync(e.Message, cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }

            await step.Context.SendActivityAsync("Fine, I have cancelled your reservation.", cancellationToken : cancellationToken);

            _telemetryClient.TrackEvent(
                "Reservation cancelled from chat bot.",
                new Dictionary <string, string>
            {
                { "Reservation ID", state.ReservationId },
            },
                new Dictionary <string, double>
            {
                { "Reservation cancellation", 1 },
            });

            return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
        }
Exemple #4
0
        private async Task <DialogTurnResult> SelectReservationStepAsync(WaterfallStepContext step, CancellationToken cancellationToken)
        {
            var state = await _stateAccessor.GetAsync(step.Context, cancellationToken : cancellationToken);

            var options = step.Options as CancelReservationDialogOptions ?? new CancelReservationDialogOptions();

            List <Reservation> reservations;

            try
            {
                var api = new CarwashService(step, _telemetryClient, cancellationToken);

                reservations = (await api.GetMyActiveReservationsAsync(cancellationToken))
                               .Where(r => r.State == State.SubmittedNotActual || r.State == State.ReminderSentWaitingForKey)
                               .OrderBy(r => r.StartDate)
                               .ToList();
            }
            catch (AuthenticationException)
            {
                await step.Context.SendActivityAsync(AuthDialog.NotAuthenticatedMessage, cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }
            catch (Exception e)
            {
                _telemetryClient.TrackException(e);
                await step.Context.SendActivityAsync(e.Message, cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }

            if (reservations.Count == 0)
            {
                await step.Context.SendActivityAsync("You do not have any active reservations which could be cancelled.", cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }

            if (reservations.Any(r => r.Id == options.ReservationId))
            {
                state.ReservationId = options.ReservationId;
                await _stateAccessor.SetAsync(step.Context, state, cancellationToken);

                return(await step.NextAsync(cancellationToken : cancellationToken));
            }

            if (reservations.Count == 1)
            {
                state.ReservationId = reservations[0].Id;
                await _stateAccessor.SetAsync(step.Context, state, cancellationToken);

                return(await step.NextAsync(cancellationToken : cancellationToken));
            }

            /*
             * var activity = MessageFactory.Carousel(new ReservationCarousel(reservations).ToAttachmentList());
             * await step.Context.SendActivityAsync("Please choose one reservation!", cancellationToken: cancellationToken);
             * await step.Context.SendActivityAsync(activity, cancellationToken);
             * return await step.NextAsync(cancellationToken: cancellationToken);
             */

            await step.Context.SendActivityAsync("I'm not sure which one. These are your active reservations, please choose one:", cancellationToken : cancellationToken);

            return(await step.ReplaceDialogAsync(
                       nameof(FindReservationDialog),
                       new FindReservationDialog.FindReservationDialogOptions {
                DisableChitChat = true
            },
                       cancellationToken));
        }
Exemple #5
0
        private async Task <DialogTurnResult> DisplayDropoffConfirmationStepAsync(WaterfallStepContext step, CancellationToken cancellationToken)
        {
            var state = await _stateAccessor.GetAsync(step.Context, cancellationToken : cancellationToken);

            if (string.IsNullOrWhiteSpace(state.Seat) && step.Result is string seat && seat.ToLower() != "skip")
            {
                state.Seat = seat;
                await _stateAccessor.SetAsync(step.Context, state, cancellationToken);
            }

            Reservation reservation;

            try
            {
                var api = new CarwashService(step, _telemetryClient, cancellationToken);

                // Confirm key drop-off and location
                await api.ConfirmDropoffAsync(state.ReservationId, state.Location, cancellationToken);

                // Get reservation object for displaying
                reservation = await api.GetReservationAsync(state.ReservationId, cancellationToken);
            }
            catch (AuthenticationException)
            {
                await step.Context.SendActivityAsync(AuthDialog.NotAuthenticatedMessage, cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }
            catch (Exception e)
            {
                _telemetryClient.TrackException(e);
                await step.Context.SendActivityAsync(e.Message, cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }

            await step.Context.SendActivityAsync("OK, I have confirmed that you've dropped off the key.", cancellationToken : cancellationToken);

            await step.Context.SendActivityAsync("Thanks! 👌", cancellationToken : cancellationToken);

            await step.Context.SendActivityAsync("Here is the current state of your reservation. I'll let you know when it changes.", cancellationToken : cancellationToken);

            var response = step.Context.Activity.CreateReply();

            response.Attachments = new ReservationCard(reservation).ToAttachmentList();

            await step.Context.SendActivityAsync(response, cancellationToken).ConfigureAwait(false);

            _telemetryClient.TrackEvent(
                "Drop-off confirmed from chat bot.",
                new Dictionary <string, string>
            {
                { "Reservation ID", reservation.Id },
            },
                new Dictionary <string, double>
            {
                { "Drop-off confirmation", 1 },
            });

            return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
        }
Exemple #6
0
        private async Task <DialogTurnResult> InitializeStateStepAsync(WaterfallStepContext step, CancellationToken cancellationToken)
        {
            var state = await _stateAccessor.GetAsync(step.Context, () => null, cancellationToken);

            if (state == null)
            {
                if (step.Options is NewReservationState o)
                {
                    state = o;
                }
                else
                {
                    state = new NewReservationState();
                }

                await _stateAccessor.SetAsync(step.Context, state, cancellationToken);
            }

            // Load LUIS entities
            var options = step.Options as NewReservationDialogOptions ?? new NewReservationDialogOptions();

            foreach (var entity in options.LuisEntities)
            {
                switch (entity.Type)
                {
                case LuisEntityType.Service:
                    var service = (ServiceModel)entity;
                    state.Services.Add(service.Service);
                    break;

                case LuisEntityType.DateTime:
                    var dateTime = (DateTimeModel)entity;
                    state.Timex = dateTime.Timex;

                    if (dateTime.Timex.Year != null &&
                        dateTime.Timex.Month != null &&
                        dateTime.Timex.DayOfMonth != null)
                    {
                        var hour = dateTime.Timex.Hour ?? 0;
                        state.StartDate = new DateTime(
                            dateTime.Timex.Year.Value,
                            dateTime.Timex.Month.Value,
                            dateTime.Timex.DayOfMonth.Value,
                            hour,
                            0,
                            0);
                    }

                    break;

                case LuisEntityType.Comment:
                    state.Comment = entity.Text;
                    break;

                case LuisEntityType.Private:
                    state.Private = true;
                    break;

                case LuisEntityType.VehiclePlateNumber:
                    state.VehiclePlateNumber = entity.Text;
                    break;
                }
            }

            // Load last reservation settings
            if (string.IsNullOrWhiteSpace(state.VehiclePlateNumber))
            {
                try
                {
                    var api = new CarwashService(step, cancellationToken);

                    state.LastSettings = await api.GetLastSettingsAsync(cancellationToken);
                }
                catch (AuthenticationException)
                {
                    await step.Context.SendActivityAsync(AuthDialog.NotAuthenticatedMessage, cancellationToken : cancellationToken);

                    return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
                }
                catch (Exception e)
                {
                    _telemetryClient.TrackException(e);
                    await step.Context.SendActivityAsync(e.Message, cancellationToken : cancellationToken);

                    return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
                }
            }

            await _stateAccessor.SetAsync(step.Context, state, cancellationToken);

            return(await step.NextAsync(cancellationToken : cancellationToken));
        }
Exemple #7
0
        private async Task <DialogTurnResult> DisplayReservationStepAsync(WaterfallStepContext step, CancellationToken cancellationToken)
        {
            var state = await _stateAccessor.GetAsync(step.Context, cancellationToken : cancellationToken);

            if (string.IsNullOrWhiteSpace(state.Comment) && step.Result is string comment && !new[] { "skip", "nope", "thanks, no", "no" }.Contains(comment.ToLower()))
            {
                state.Comment = comment;
                await _stateAccessor.SetAsync(step.Context, state, cancellationToken);
            }

            Reservation reservation = new Reservation
            {
                VehiclePlateNumber = state.VehiclePlateNumber,
                Services           = state.Services,
                Private            = state.Private,
                StartDate          = state.StartDate.Value,
                Comment            = state.Comment,
            };

            try
            {
                var api = new CarwashService(step, cancellationToken);

                // Submit reservation
                reservation = await api.SubmitReservationAsync(reservation, cancellationToken);
            }
            catch (AuthenticationException)
            {
                await step.Context.SendActivityAsync(AuthDialog.NotAuthenticatedMessage, cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }
            catch (Exception e)
            {
                _telemetryClient.TrackException(e);
                await step.Context.SendActivityAsync(e.Message, cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }

            await step.Context.SendActivityAsync($"OK, I have reserved a car wash for {reservation.StartDate.ToNaturalLanguage()}.", cancellationToken : cancellationToken);

            await step.Context.SendActivityAsync("🚗", cancellationToken : cancellationToken);

            await step.Context.SendActivityAsync("Here is the current state of your reservation. I'll let you know when you will need to drop off the keys.", cancellationToken : cancellationToken);

            var response = step.Context.Activity.CreateReply();

            response.Attachments = new ReservationCard(reservation).ToAttachmentList();

            await step.Context.SendActivityAsync(response, cancellationToken).ConfigureAwait(false);

            _telemetryClient.TrackEvent(
                "New reservation from chat bot.",
                new Dictionary <string, string>
            {
                { "Reservation ID", reservation.Id },
            },
                new Dictionary <string, double>
            {
                { "New reservation", 1 },
            });

            return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
        }
Exemple #8
0
        private async Task <DialogTurnResult> PromptForSlotStepAsync(WaterfallStepContext step, CancellationToken cancellationToken)
        {
            var state = await _stateAccessor.GetAsync(step.Context, cancellationToken : cancellationToken);

            if (state.StartDate == null && step.Result is IList <DateTimeResolution> resolution)
            {
                var timex = new TimexProperty(resolution[0].Timex);
                state.Timex = timex;
                var hour = timex.Hour ?? 0;
                state.StartDate = new DateTime(
                    timex.Year.Value,
                    timex.Month.Value,
                    timex.DayOfMonth.Value,
                    hour,
                    0,
                    0);
                await _stateAccessor.SetAsync(step.Context, state, cancellationToken);
            }

            var reservationCapacity = new List <CarwashService.ReservationCapacity>();

            try
            {
                var api = new CarwashService(step, cancellationToken);

                reservationCapacity = (List <CarwashService.ReservationCapacity>) await api.GetReservationCapacityAsync(state.StartDate.Value, cancellationToken);
            }
            catch (AuthenticationException)
            {
                await step.Context.SendActivityAsync(AuthDialog.NotAuthenticatedMessage, cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }
            catch (Exception e)
            {
                _telemetryClient.TrackException(e);
                await step.Context.SendActivityAsync(e.Message, cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }

            // Check whether we already know the slot.
            if (Slots.Any(s => s.StartTime == state.StartDate?.Hour))
            {
                // Check if slot is available.
                if (!reservationCapacity.Any(c => c.StartTime == state.StartDate && c.FreeCapacity > 0))
                {
                    await step.Context.SendActivityAsync("Sorry, this slot is already full.", cancellationToken : cancellationToken);
                }
                else
                {
                    return(await step.NextAsync(cancellationToken : cancellationToken));
                }
            }
            else if (!string.IsNullOrEmpty(state.Timex?.PartOfDay))
            {
                // Check whether we can find out the slot from timex.
                Slot slot = null;
                switch (state.Timex.PartOfDay)
                {
                case "MO":
                    slot = Slots[0];
                    break;

                case "AF":
                    slot = Slots[1];
                    break;

                case "EV":
                    slot = Slots[2];
                    break;
                }

                if (slot != null)
                {
                    state.StartDate = new DateTime(
                        state.StartDate.Value.Year,
                        state.StartDate.Value.Month,
                        state.StartDate.Value.Day,
                        slot.StartTime,
                        0,
                        0);

                    await _stateAccessor.SetAsync(step.Context, state, cancellationToken);

                    return(await step.NextAsync(cancellationToken : cancellationToken));
                }
            }

            var choices = new List <Choice>();

            state.SlotChoices = new List <DateTime>();
            foreach (var slot in reservationCapacity)
            {
                if (slot.FreeCapacity < 1)
                {
                    continue;
                }

                choices.Add(new Choice($"{slot.StartTime.ToNaturalLanguage()} ({slot.StartTime.Hour}-{Slots.Single(s => s.StartTime == slot.StartTime.Hour).EndTime})"));

                // Save recommendations to state
                state.SlotChoices.Add(slot.StartTime);
            }

            await _stateAccessor.SetAsync(step.Context, state, cancellationToken);

            return(await step.PromptAsync(
                       SlotPromptName,
                       new PromptOptions
            {
                Prompt = MessageFactory.Text("Please choose one of these slots:"),
                Choices = choices,
            },
                       cancellationToken));
        }
Exemple #9
0
        private async Task <DialogTurnResult> RecommendSlotsStepAsync(WaterfallStepContext step, CancellationToken cancellationToken)
        {
            var state = await _stateAccessor.GetAsync(step.Context, cancellationToken : cancellationToken);

            if (state.Services.Count == 0)
            {
                state.Services = ParseServiceSelectionCardResponse(step.Context.Activity);
                await _stateAccessor.SetAsync(step.Context, state, cancellationToken);
            }

            // Check whether we already know something about the date.
            if (state.StartDate != null)
            {
                return(await step.NextAsync(cancellationToken : cancellationToken));
            }

            var recommendedSlots = new List <DateTime>();

            try
            {
                var api = new CarwashService(step, cancellationToken);

                var notAvailable = await api.GetNotAvailableDatesAndTimesAsync(cancellationToken);

                recommendedSlots = GetRecommendedSlots(notAvailable);
            }
            catch (AuthenticationException)
            {
                await step.Context.SendActivityAsync(AuthDialog.NotAuthenticatedMessage, cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }
            catch (Exception e)
            {
                _telemetryClient.TrackException(e);
                await step.Context.SendActivityAsync(e.Message, cancellationToken : cancellationToken);

                return(await step.ClearStateAndEndDialogAsync(_stateAccessor, cancellationToken : cancellationToken));
            }

            // Save recommendations to state
            state.RecommendedSlots = recommendedSlots;
            await _stateAccessor.SetAsync(step.Context, state, cancellationToken);

            var choices = new List <Choice>();

            foreach (var slot in recommendedSlots)
            {
                var timex = TimexProperty.FromDateTime(slot);
                choices.Add(new Choice(timex.ToNaturalLanguage(DateTime.Now)));
            }

            return(await step.PromptAsync(
                       RecommendedSlotsPromptName,
                       new PromptOptions
            {
                Prompt = MessageFactory.Text("Can I recommend you one of these slots? If you want to choose something else, just type skip."),
                Choices = choices,
            },
                       cancellationToken));
        }