/// <inheritdoc /> protected override IActivity[] GetActivities(DialogContext context, ReservationServiceBusMessage message, UserProfile userProfile, CancellationToken cancellationToken = default) { Reservation reservation = null; try { var api = new CarwashService(context, cancellationToken); reservation = api.GetReservationAsync(message.ReservationId, cancellationToken).GetAwaiter().GetResult(); } catch (Exception e) { _telemetryClient.TrackException(e); } var greeting = userProfile?.NickName == null ? "Hi!" : $"Hi {userProfile.NickName}!"; var activities = new List <IActivity> { new Activity(type: ActivityTypes.Message, text: greeting), new Activity(type: ActivityTypes.Message, text: reservation.Private ? "Your car is ready! Don't forget to pay!" : "Your car is ready!"), }; if (reservation != null) { activities.Add(new Activity(type: ActivityTypes.Message, text: $"You can find it here: {reservation.Location}")); } return(activities.ToArray()); }
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)); }
/// <summary> /// Load and responds with the next available, free slots. /// </summary> /// <param name="dc">A <see cref="DialogContext"/> provides context for the dialog.</param> /// <param name="options">(Optional) An object storing the input options.</param> /// <param name="cancellationToken" >(Optional) A <see cref="CancellationToken"/> that can be used by other objects /// or threads to receive notice of cancellation.</param> /// <returns>A <see cref="Task"/> representing the operation result of the operation.</returns> public override async Task <DialogTurnResult> BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default(CancellationToken)) { var activities = new List <IActivity>(); try { var api = new CarwashService(dc, cancellationToken); var freeSlots = await api.GetNextFreeSlotsAsync(cancellationToken : cancellationToken); var numberOfSlots = freeSlots.Count(); // Chit-chat switch (numberOfSlots) { case 0: await dc.Context.SendActivityAsync("I couldn't find ANY free slots in the next year! Crazy times... 🤦", cancellationToken : cancellationToken); return(await dc.EndDialogAsync(cancellationToken : cancellationToken)); case 1: activities.Add(new Activity(type: ActivityTypes.Message, text: "Currently there is only one free slot:")); break; default: activities.Add(new Activity(type: ActivityTypes.Message, text: $"Here are the next {numberOfSlots} free slots:")); break; } foreach (var slot in freeSlots) { activities.Add(new Activity(type: ActivityTypes.Message, text: slot)); } activities.Add(new Activity(type: ActivityTypes.Message, text: $"To make a new reservation, just type 'reserve for {freeSlots.First()}'.")); } catch (AuthenticationException) { await dc.Context.SendActivityAsync(AuthDialog.NotAuthenticatedMessage, cancellationToken : cancellationToken); return(await dc.EndDialogAsync(cancellationToken : cancellationToken)); } catch (Exception e) { _telemetryClient.TrackException(e); await dc.Context.SendActivityAsync(e.Message, cancellationToken : cancellationToken); return(await dc.EndDialogAsync(cancellationToken : cancellationToken)); } await dc.Context.SendActivitiesAsync(activities.ToArray(), cancellationToken); return(await dc.EndDialogAsync(cancellationToken : cancellationToken)); }
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)); }
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)); }
/// <summary> /// In this step we check that a token was received and prompt the user as needed. /// </summary> /// <param name="step">A <see cref="WaterfallStepContext"/> provides context for the current waterfall step.</param> /// <param name="cancellationToken" >(Optional) A <see cref="CancellationToken"/> that can be used by other objects /// or threads to receive notice of cancellation.</param> /// <returns>A <see cref="Task"/> representing the operation result of the operation.</returns> private async Task <DialogTurnResult> LoginStepAsync(WaterfallStepContext step, CancellationToken cancellationToken = default(CancellationToken)) { // Get the token from the previous step. Note that we could also have gotten the // token directly from the prompt itself. There is an example of this in the next method. if (!(step.Result is TokenResponse tokenResponse)) { await step.Context.SendActivityAsync("Login was not successful, please try again.", cancellationToken : cancellationToken); return(await step.EndDialogAsync(cancellationToken : cancellationToken)); } await step.Context.SendActivityAsync("You are now logged in.", cancellationToken : cancellationToken); // Call Carwash API and update UserProfile state try { var api = new CarwashService(tokenResponse.Token); var carwashUser = await api.GetMe(cancellationToken); var userProfile = await _userProfileAccessor.GetAsync(step.Context, () => new UserProfile()); userProfile.CarwashUserId = carwashUser.Id; userProfile.NickName = carwashUser.FirstName; await _userProfileAccessor.SetAsync(step.Context, userProfile, cancellationToken); } catch (Exception e) { _telemetryClient.TrackException(e); await step.Context.SendActivityAsync(e.Message, cancellationToken : cancellationToken); } await UpdateUserInfoForProactiveMessages(step.Context, cancellationToken).ConfigureAwait(false); // Display user's active reservations after login return(await step.ReplaceDialogAsync( nameof(FindReservationDialog), new FindReservationDialog.FindReservationDialogOptions { Token = tokenResponse.Token }, cancellationToken : cancellationToken)); }
/// <inheritdoc /> protected override IActivity[] GetActivities(DialogContext context, ReservationServiceBusMessage message, UserProfile userProfile, CancellationToken cancellationToken = default) { Reservation reservation = null; try { var api = new CarwashService(context, _telemetryClient, cancellationToken); reservation = api.GetReservationAsync(message.ReservationId, cancellationToken).GetAwaiter().GetResult(); } catch (Exception e) { _telemetryClient.TrackException(e); } var greeting = userProfile?.NickName == null ? "Hi!" : $"Hi {userProfile.NickName}!"; return(new IActivity[] { new Activity(type: ActivityTypes.Message, text: greeting), new Activity(type: ActivityTypes.Message, text: reservation.CarwashComment), }); }
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)); }
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)); }
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)); }
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)); }
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)); }
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)); }