private static (object value, string error) Evaluator(Expression expression, IMemory state, Options options) { TimexProperty parsed = null; string result = null; string error = null; var(validHour, validMinute, validSecond) = (0, 0, 0); var formatRegex = new Regex("TXX:[0-5][0-9]:[0-5][0-9]"); var currentUtcTime = DateTime.UtcNow; var convertedDateTime = currentUtcTime; IReadOnlyList <object> args; (args, error) = FunctionUtils.EvaluateChildren(expression, state, options); if (error == null) { if (!formatRegex.IsMatch(args[0] as string)) { error = $"{args[0]} must be a timex string which only contains minutes and seconds, for example: 'TXX:15:28'"; } } if (error == null) { if (args.Count == 2 && args[1] is string timezone) { object convertedTimeZone = null; (convertedTimeZone, error) = FunctionUtils.ConvertTimeZoneFormat(timezone); if (error == null) { convertedDateTime = TimeZoneInfo.ConvertTimeFromUtc(currentUtcTime, (TimeZoneInfo)convertedTimeZone); } } else { convertedDateTime = currentUtcTime.ToLocalTime(); } } if (error == null) { (parsed, error) = FunctionUtils.ParseTimexProperty((args[0] as string).Replace("XX", "00")); } if (error == null) { var(hour, minute, second) = (convertedDateTime.Hour, convertedDateTime.Minute, convertedDateTime.Second); if (parsed.Minute > minute || (parsed.Minute == minute && parsed.Second >= second)) { validHour = hour; } else { validHour = hour + 1; } validMinute = parsed.Minute ?? 0; validSecond = parsed.Second ?? 0; result = TimexProperty.FromTime(new Time(validHour, validMinute, validSecond)).TimexValue; } return(result, error); }
public void DataTypes_Timex_FromTime() { Assert.AreEqual("T23:59:30", TimexProperty.FromTime(new Time(23, 59, 30)).TimexValue); }
public BookTable() : base("BookTable") { var promptOptions = new ChoicePromptOptions { Choices = new List <Choice> { new Choice { Value = "Seattle" }, new Choice { Value = "Bellevue" }, new Choice { Value = "Renton" }, } }; //Dialogs.Add("textPrompt", new TextPrompt()); Dialogs.Add("choicePrompt", new ChoicePrompt(Culture.English) { Style = Microsoft.Bot.Builder.Prompts.ListStyle.Auto }); Dialogs.Add("numberPrompt", new NumberPrompt <int>(Culture.English)); Dialogs.Add("timexPrompt", new TimexPrompt(Culture.English, TimexValidator)); Dialogs.Add("confirmationPrompt", new ConfirmPrompt(Culture.English)); Dialogs.Add("BookTable", new WaterfallStep[] { async(dc, args, next) => { dc.ActiveDialog.State = new Dictionary <string, object>(); // await dc.Prompt("textPrompt", "Sure. I can help with that. What City?"); await dc.Prompt("choicePrompt", "Which of our locations would you like?", promptOptions); }, async(dc, args, next) => { var choiceResult = (FoundChoice)args["Value"]; dc.ActiveDialog.State["bookingLocation"] = choiceResult.Value; await dc.Prompt("timexPrompt", "When would you like to arrive? (We open at 4PM.)", new PromptOptions { RetryPromptString = "We only accept reservations for the next 2 weeks and in the evenings between 4PM - 8PM" }); }, async(dc, args, next) => { var timexResult = (TimexResult)args; var timexResolution = timexResult.Resolutions.First(); var timexProperty = new TimexProperty(timexResolution.ToString()); var bookingDateTime = $"{timexProperty.ToNaturalLanguage(DateTime.Now)}"; dc.ActiveDialog.State["bookingDateTime"] = bookingDateTime; await dc.Prompt("numberPrompt", "How many in your party?"); }, async(dc, args, next) => { dc.ActiveDialog.State["bookingGuestCount"] = args["Value"]; var dialogState = dc.ActiveDialog.State; await dc.Prompt("confirmationPrompt", $"Thanks, Should I go ahead and book a table for {dialogState["bookingGuestCount"].ToString()} guests at our {dialogState["bookingLocation"].ToString()} location for {dialogState["bookingDateTime"].ToString()} ?"); }, async(dc, args, next) => { var dialogState = dc.ActiveDialog.State; // TODO: Verify user said yes to confirmation prompt // TODO: book the table! await dc.Context.SendActivity($"Thanks, I have {dialogState["bookingGuestCount"].ToString()} guests booked for our {dialogState["bookingLocation"].ToString()} location for {dialogState["bookingDateTime"].ToString()}."); } } ); }
public void DataTypes_Convert_Last5Minutes() { // date + time + duration var timex = new TimexProperty("(2017-09-08T21:19:29,2017-09-08T21:24:29,PT5M)"); // TODO }
public void DataTypes_Timex_FromDateTime() { Assert.AreEqual("2017-12-05T23:57:35", TimexProperty.FromDateTime(new System.DateTime(2017, 12, 5, 23, 57, 35)).TimexValue); }
private async Task <DialogTurnResult> AfterGetNewEventTimePrompt(WaterfallStepContext sc, CancellationToken cancellationToken = default(CancellationToken)) { try { var state = await Accessor.GetAsync(sc.Context); if (state.UpdateMeetingInfor.NewStartDate.Any() || state.UpdateMeetingInfor.NewStartTime.Any() || state.UpdateMeetingInfor.MoveTimeSpan != 0) { var originalEvent = state.ShowMeetingInfor.FocusedEvents[0]; var originalStartDateTime = TimeConverter.ConvertUtcToUserTime(originalEvent.StartTime, state.GetUserTimeZone()); var userNow = TimeConverter.ConvertUtcToUserTime(DateTime.UtcNow, state.GetUserTimeZone()); if (state.UpdateMeetingInfor.NewStartDate.Any() || state.UpdateMeetingInfor.NewStartTime.Any()) { var newStartDate = state.UpdateMeetingInfor.NewStartDate.Any() ? state.UpdateMeetingInfor.NewStartDate.Last() : originalStartDateTime; var newStartTime = new List <DateTime>(); if (state.UpdateMeetingInfor.NewStartTime.Any()) { foreach (var time in state.UpdateMeetingInfor.NewStartTime) { var newStartDateTime = new DateTime( newStartDate.Year, newStartDate.Month, newStartDate.Day, time.Hour, time.Minute, time.Second); if (state.UpdateMeetingInfor.NewStartDateTime == null) { state.UpdateMeetingInfor.NewStartDateTime = newStartDateTime; } if (newStartDateTime >= userNow) { state.UpdateMeetingInfor.NewStartDateTime = newStartDateTime; break; } } } } else if (state.UpdateMeetingInfor.MoveTimeSpan != 0) { state.UpdateMeetingInfor.NewStartDateTime = originalStartDateTime.AddSeconds(state.UpdateMeetingInfor.MoveTimeSpan); } else { return(await sc.BeginDialogAsync(Actions.GetNewStartTime, new UpdateDateTimeDialogOptions(UpdateDateTimeDialogOptions.UpdateReason.NotFound))); } state.UpdateMeetingInfor.NewStartDateTime = TimeZoneInfo.ConvertTimeToUtc(state.UpdateMeetingInfor.NewStartDateTime.Value, state.GetUserTimeZone()); return(await sc.EndDialogAsync()); } else if (sc.Result != null) { IList <DateTimeResolution> dateTimeResolutions = sc.Result as List <DateTimeResolution>; DateTime?newStartTime = null; foreach (var resolution in dateTimeResolutions) { var utcNow = DateTime.UtcNow; var dateTimeConvertTypeString = resolution.Timex; var dateTimeConvertType = new TimexProperty(dateTimeConvertTypeString); var dateTimeValue = DateTime.Parse(resolution.Value); if (dateTimeValue == null) { continue; } var originalStartDateTime = TimeConverter.ConvertUtcToUserTime(state.ShowMeetingInfor.FocusedEvents[0].StartTime, state.GetUserTimeZone()); if (dateTimeConvertType.Types.Contains(Constants.TimexTypes.Date) && !dateTimeConvertType.Types.Contains(Constants.TimexTypes.DateTime)) { dateTimeValue = new DateTime( dateTimeValue.Year, dateTimeValue.Month, dateTimeValue.Day, originalStartDateTime.Hour, originalStartDateTime.Minute, originalStartDateTime.Second); } else if (dateTimeConvertType.Types.Contains(Constants.TimexTypes.Time) && !dateTimeConvertType.Types.Contains(Constants.TimexTypes.DateTime)) { dateTimeValue = new DateTime( originalStartDateTime.Year, originalStartDateTime.Month, originalStartDateTime.Day, dateTimeValue.Hour, dateTimeValue.Minute, dateTimeValue.Second); } dateTimeValue = TimeZoneInfo.ConvertTimeToUtc(dateTimeValue, state.GetUserTimeZone()); if (newStartTime == null) { newStartTime = dateTimeValue; } if (dateTimeValue >= utcNow) { newStartTime = dateTimeValue; break; } } if (newStartTime != null) { state.UpdateMeetingInfor.NewStartDateTime = newStartTime; return(await sc.EndDialogAsync()); } else { return(await sc.BeginDialogAsync(Actions.GetNewStartTime, new UpdateDateTimeDialogOptions(UpdateDateTimeDialogOptions.UpdateReason.NotADateTime))); } } else { return(await sc.BeginDialogAsync(Actions.GetNewStartTime, new UpdateDateTimeDialogOptions(UpdateDateTimeDialogOptions.UpdateReason.NotADateTime))); } } catch (Exception ex) { await HandleDialogExceptions(sc, ex); return(new DialogTurnResult(DialogTurnStatus.Cancelled, CommonUtil.DialogTurnResultCancelAllDialogs)); } }
public async Task <DialogTurnResult> CallReadEventDialog(WaterfallStepContext sc, CancellationToken cancellationToken = default(CancellationToken)) { try { var state = await Accessor.GetAsync(sc.Context); var luisResult = state.LuisResult; var topIntent = luisResult?.TopIntent().intent; var generalLuisResult = state.GeneralLuisResult; var generalTopIntent = generalLuisResult?.TopIntent().intent; if (topIntent == null) { state.Clear(); return(await sc.CancelAllDialogsAsync()); } if ((generalTopIntent == General.Intent.Next || topIntent == CalendarLU.Intent.ShowNextCalendar) && state.SummaryEvents != null) { if ((state.ShowEventIndex + 1) * state.PageSize < state.SummaryEvents.Count) { state.ShowEventIndex++; } else { await sc.Context.SendActivityAsync(ResponseManager.GetResponse(SummaryResponses.CalendarNoMoreEvent)); } return(await sc.ReplaceDialogAsync(Actions.ShowEventsSummary, sc.Options)); } else if ((generalTopIntent == General.Intent.Previous || topIntent == CalendarLU.Intent.ShowPreviousCalendar) && state.SummaryEvents != null) { if (state.ShowEventIndex > 0) { state.ShowEventIndex--; } else { await sc.Context.SendActivityAsync(ResponseManager.GetResponse(SummaryResponses.CalendarNoPreviousEvent)); } return(await sc.ReplaceDialogAsync(Actions.ShowEventsSummary, sc.Options)); } sc.Context.Activity.Properties.TryGetValue("OriginText", out var content); var userInput = content != null?content.ToString() : sc.Context.Activity.Text; var promptRecognizerResult = ConfirmRecognizerHelper.ConfirmYesOrNo(userInput, sc.Context.Activity.Locale); if (promptRecognizerResult.Succeeded && promptRecognizerResult.Value == false) { state.Clear(); return(await sc.CancelAllDialogsAsync()); } else if (promptRecognizerResult.Succeeded && promptRecognizerResult.Value == true) { state.ReadOutEvents = new List <EventModel>() { state.SummaryEvents[0] }; } else if (state.SummaryEvents.Count == 1) { state.Clear(); return(await sc.CancelAllDialogsAsync()); } if (state.SummaryEvents.Count > 1 && (state.ReadOutEvents == null || state.ReadOutEvents.Count <= 0)) { var filteredMeetingList = new List <EventModel>(); // filter meetings with number if (luisResult.Entities.ordinal != null) { var value = luisResult.Entities.ordinal[0]; var num = int.Parse(value.ToString()); var currentList = GetCurrentPageMeetings(state.SummaryEvents, state); if (num > 0 && num <= currentList.Count) { filteredMeetingList.Add(currentList[num - 1]); } } if (filteredMeetingList.Count <= 0 && luisResult.Entities.number != null && (luisResult.Entities.ordinal == null || luisResult.Entities.ordinal.Length == 0)) { var value = luisResult.Entities.number[0]; var num = int.Parse(value.ToString()); var currentList = GetCurrentPageMeetings(state.SummaryEvents, state); if (num > 0 && num <= currentList.Count) { filteredMeetingList.Add(currentList[num - 1]); } } // filter meetings with start time var timeResult = RecognizeDateTime(userInput, sc.Context.Activity.Locale ?? English); if (filteredMeetingList.Count <= 0 && timeResult != null) { foreach (var result in timeResult) { var dateTimeConvertTypeString = result.Timex; var dateTimeConvertType = new TimexProperty(dateTimeConvertTypeString); if (result.Value != null || (dateTimeConvertType.Types.Contains(Constants.TimexTypes.Time) || dateTimeConvertType.Types.Contains(Constants.TimexTypes.DateTime))) { var dateTime = DateTime.Parse(result.Value); if (dateTime != null) { var utcStartTime = TimeZoneInfo.ConvertTimeToUtc(dateTime, state.GetUserTimeZone()); foreach (var meeting in GetCurrentPageMeetings(state.SummaryEvents, state)) { if (meeting.StartTime.TimeOfDay == utcStartTime.TimeOfDay) { filteredMeetingList.Add(meeting); } } } } } } // filter meetings with subject var subject = userInput; if (filteredMeetingList.Count <= 0 && luisResult.Entities.Subject != null) { subject = GetSubjectFromEntity(luisResult.Entities); } foreach (var meeting in GetCurrentPageMeetings(state.SummaryEvents, state)) { if (meeting.Title.ToLower().Contains(subject.ToLower())) { filteredMeetingList.Add(meeting); } } // filter meetings with contact name var contactNameList = new List <string>() { userInput }; if (filteredMeetingList.Count <= 0 && luisResult.Entities.personName != null) { contactNameList = GetAttendeesFromEntity(luisResult.Entities, userInput); } foreach (var meeting in GetCurrentPageMeetings(state.SummaryEvents, state)) { var containsAllContacts = true; foreach (var contactName in contactNameList) { if (!meeting.ContainsAttendee(contactName)) { containsAllContacts = false; break; } } if (containsAllContacts) { filteredMeetingList.Add(meeting); } } if (filteredMeetingList.Count == 1) { state.ReadOutEvents = filteredMeetingList; return(await sc.BeginDialogAsync(Actions.Read, sc.Options)); } else if (filteredMeetingList.Count > 1) { state.SummaryEvents = filteredMeetingList; return(await sc.ReplaceDialogAsync(Actions.ShowEventsSummary, new ShowMeetingsDialogOptions(ShowMeetingReason.ShowFilteredMeetings, sc.Options))); } } if (state.ReadOutEvents != null && state.ReadOutEvents.Count > 0) { return(await sc.BeginDialogAsync(Actions.Read, sc.Options)); } else { state.Clear(); return(await sc.CancelAllDialogsAsync()); } } catch (Exception ex) { await HandleDialogExceptions(sc, ex); return(new DialogTurnResult(DialogTurnStatus.Cancelled, CommonUtil.DialogTurnResultCancelAllDialogs)); } }
private async Task <DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { // If the child dialog ("BookingDialog") was cancelled, the user failed to confirm or if the intent wasn't BookFlight // the Result here will be null. if (stepContext.Result is BookingDetails result) { // Now we have all the booking details call the booking service. // If the call to the booking service was successful tell the user. var timeProperty = new TimexProperty(result.TravelDate); var travelDateMsg = timeProperty.ToNaturalLanguage(DateTime.Now); var messageText = $"I have you booked to {result.Destination} from {result.Origin} on {travelDateMsg}"; var message = MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput); var model = new FlightitineraryModel { Places = new Cards.Place[] { new Cards.Place { Name = result.Origin, Code = result.Origin, Id = 13554, Type = "Airport" }, new Cards.Place { Name = result.Destination, Code = result.Destination, Id = 11235, Type = "Airport" } }, Segments = new Segment[] { new Segment { Id = 1, ArrivalDateTime = DateTime.Parse(result.TravelDate), DepartureDateTime = DateTime.Parse(result.TravelDate), OriginStation = 13554, DestinationStation = 11235 }, new Segment { Id = 2, ArrivalDateTime = DateTime.Parse(result.TravelDate), DepartureDateTime = DateTime.Parse(result.TravelDate), OriginStation = 11235, DestinationStation = 13554 } }, Query = new Query { DestinationPlace = "11235", OriginPlace = "13554", InboundDate = result.TravelDate, OutboundDate = result.TravelDate, LocationSchema = "Default", CabinClass = "Economy", GroupPricing = false, Country = "GB", Currency = "GBP", Locale = "en-gb", Adults = 3, Children = 0, Infants = 0, }, BookingOptions = new Bookingoption[] { new Bookingoption { BookingItems = new Bookingitem[] { new Bookingitem { Price = new Random().Next(200, 5000), SegmentIds = new int[] { 1, 2 } } } } } }; var confirmationCard = CreateBookingConfimrationCardAttachment(model); message.Attachments.Add(confirmationCard); await stepContext.Context.SendActivityAsync(message, cancellationToken); } // Restart the main dialog with a different message the second time around var promptMessage = "What else can I do for you?"; return(await stepContext.ReplaceDialogAsync(InitialDialogId, promptMessage, cancellationToken)); }
public async Task <DialogTurnResult> AfterGetNewEventTime(WaterfallStepContext sc, CancellationToken cancellationToken = default(CancellationToken)) { try { var state = await Accessor.GetAsync(sc.Context); if (state.StartDate.Any() || state.StartTime.Any() || state.MoveTimeSpan != 0) { var originalEvent = state.Events[0]; var originalStartDateTime = TimeConverter.ConvertUtcToUserTime(originalEvent.StartTime, state.GetUserTimeZone()); var userNow = TimeConverter.ConvertUtcToUserTime(DateTime.UtcNow, state.GetUserTimeZone()); if (state.StartDate.Any() || state.StartTime.Any()) { var newStartDate = state.StartDate.Any() ? state.StartDate.Last() : originalStartDateTime; var newStartTime = new List <DateTime>(); if (state.StartTime.Any()) { foreach (var time in state.StartTime) { var newStartDateTime = new DateTime( newStartDate.Year, newStartDate.Month, newStartDate.Day, time.Hour, time.Minute, time.Second); if (state.NewStartDateTime == null) { state.NewStartDateTime = newStartDateTime; } if (newStartDateTime >= userNow) { state.NewStartDateTime = newStartDateTime; break; } } } } else if (state.MoveTimeSpan != 0) { state.NewStartDateTime = originalStartDateTime.AddSeconds(state.MoveTimeSpan); } else { return(await sc.BeginDialogAsync(Actions.UpdateNewStartTime, new UpdateDateTimeDialogOptions(UpdateDateTimeDialogOptions.UpdateReason.NotFound))); } state.NewStartDateTime = TimeZoneInfo.ConvertTimeToUtc(state.NewStartDateTime.Value, state.GetUserTimeZone()); return(await sc.ContinueDialogAsync()); } else if (sc.Result != null) { IList <DateTimeResolution> dateTimeResolutions = sc.Result as List <DateTimeResolution>; DateTime?newStartTime = null; foreach (var resolution in dateTimeResolutions) { var utcNow = DateTime.UtcNow; var dateTimeConvertTypeString = resolution.Timex; var dateTimeConvertType = new TimexProperty(dateTimeConvertTypeString); var dateTimeValue = DateTime.Parse(resolution.Value); if (dateTimeValue == null) { continue; } var isRelativeTime = IsRelativeTime(sc.Context.Activity.Text, resolution.Value, dateTimeConvertTypeString); if (isRelativeTime) { dateTimeValue = DateTime.SpecifyKind(dateTimeValue, DateTimeKind.Local); } dateTimeValue = isRelativeTime ? TimeZoneInfo.ConvertTime(dateTimeValue, TimeZoneInfo.Local, state.GetUserTimeZone()) : dateTimeValue; var originalStartDateTime = TimeConverter.ConvertUtcToUserTime(state.Events[0].StartTime, state.GetUserTimeZone()); if (dateTimeConvertType.Types.Contains(Constants.TimexTypes.Date) && !dateTimeConvertType.Types.Contains(Constants.TimexTypes.DateTime)) { dateTimeValue = new DateTime( dateTimeValue.Year, dateTimeValue.Month, dateTimeValue.Day, originalStartDateTime.Hour, originalStartDateTime.Minute, originalStartDateTime.Second); } else if (dateTimeConvertType.Types.Contains(Constants.TimexTypes.Time) && !dateTimeConvertType.Types.Contains(Constants.TimexTypes.DateTime)) { dateTimeValue = new DateTime( originalStartDateTime.Year, originalStartDateTime.Month, originalStartDateTime.Day, dateTimeValue.Hour, dateTimeValue.Minute, dateTimeValue.Second); } dateTimeValue = TimeZoneInfo.ConvertTimeToUtc(dateTimeValue, state.GetUserTimeZone()); if (newStartTime == null) { newStartTime = dateTimeValue; } if (dateTimeValue >= utcNow) { newStartTime = dateTimeValue; break; } } if (newStartTime != null) { state.NewStartDateTime = newStartTime; return(await sc.ContinueDialogAsync()); } else { return(await sc.BeginDialogAsync(Actions.UpdateNewStartTime, new UpdateDateTimeDialogOptions(UpdateDateTimeDialogOptions.UpdateReason.NotADateTime))); } } else { return(await sc.BeginDialogAsync(Actions.UpdateNewStartTime, new UpdateDateTimeDialogOptions(UpdateDateTimeDialogOptions.UpdateReason.NotADateTime))); } } catch { await HandleDialogExceptions(sc); throw; } }
public BookTable() : base("BookTable") { var promptOptions = new ChoicePromptOptions { Choices = new List <Choice> { new Choice { Value = "Seattle" }, new Choice { Value = "Bellevue" }, new Choice { Value = "Renton" }, } }; //Dialogs.Add("textPrompt", new TextPrompt()); Dialogs.Add("choicePrompt", new ChoicePrompt(Culture.English) { Style = Microsoft.Bot.Builder.Prompts.ListStyle.Auto }); Dialogs.Add("numberPrompt", new NumberPrompt <int>(Culture.English)); Dialogs.Add("timexPrompt", new TimexPrompt(Culture.English, TimexValidator)); Dialogs.Add("confirmationPrompt", new ConfirmPrompt(Culture.English)); Dialogs.Add("BookTable", new WaterfallStep[] { async(dc, args, next) => { dc.ActiveDialog.State = new Dictionary <string, object>(); IDictionary <string, object> state = dc.ActiveDialog.State; // add any LUIS entities to active dialog state. if (args.ContainsKey("luisResult")) { cafeLUISModel lResult = (cafeLUISModel)args["luisResult"]; updateContextWithLUIS(lResult, ref state); } // prompt if we do not already have cafelocation if (state.ContainsKey("cafeLocation")) { state["bookingLocation"] = state["cafeLocation"]; await next(); } else { await dc.Prompt("choicePrompt", "Which of our locations would you like?", promptOptions); } }, async(dc, args, next) => { var state = dc.ActiveDialog.State; if (!state.ContainsKey("cafeLocation")) { var choiceResult = (FoundChoice)args["Value"]; state["bookingLocation"] = choiceResult.Value; } bool promptForDateTime = true; if (state.ContainsKey("datetime")) { // validate timex var inputdatetime = new string[] { (string)state["datetime"] }; var results = evaluateTimeX((string[])inputdatetime); if (results.Count != 0) { var timexResolution = results.First().TimexValue; var timexProperty = new TimexProperty(timexResolution.ToString()); var bookingDateTime = $"{timexProperty.ToNaturalLanguage(DateTime.Now)}"; state["bookingDateTime"] = bookingDateTime; promptForDateTime = false; } } // prompt if we do not already have date and time if (promptForDateTime) { await dc.Prompt("timexPrompt", "When would you like to arrive? (We open at 4PM.)", new PromptOptions { RetryPromptString = "Please pick a date in the future and a time in the evening." }); } else { await next(); } }, async(dc, args, next) => { var state = dc.ActiveDialog.State; if (!state.ContainsKey("datetime")) { var timexResult = (TimexResult)args; var timexResolution = timexResult.Resolutions.First(); var timexProperty = new TimexProperty(timexResolution.ToString()); var bookingDateTime = $"{timexProperty.ToNaturalLanguage(DateTime.Now)}"; state["bookingDateTime"] = bookingDateTime; } // prompt if we already do not have party size if (state.ContainsKey("partySize")) { state["bookingGuestCount"] = state["partySize"]; await next(); } else { await dc.Prompt("numberPrompt", "How many in your party?"); } }, async(dc, args, next) => { var state = dc.ActiveDialog.State; if (!state.ContainsKey("partySize")) { state["bookingGuestCount"] = args["Value"]; } await dc.Prompt("confirmationPrompt", $"Thanks, Should I go ahead and book a table for {state["bookingGuestCount"].ToString()} guests at our {state["bookingLocation"].ToString()} location for {state["bookingDateTime"].ToString()} ?"); }, async(dc, args, next) => { var dialogState = dc.ActiveDialog.State; // TODO: Verify user said yes to confirmation prompt // TODO: book the table! await dc.Context.SendActivity($"Thanks, I have {dialogState["bookingGuestCount"].ToString()} guests booked for our {dialogState["bookingLocation"].ToString()} location for {dialogState["bookingDateTime"].ToString()}."); } } ); }
private async Task <DialogTurnResult> InitialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { var timex = (string)stepContext.Options; var time = DateRange(timex); var promptMessage = MessageFactory.Text(PromptMsgText, PromptMsgText, InputHints.ExpectingInput); var repromptMessage = MessageFactory.Text(RepromptMsgText, RepromptMsgText, InputHints.ExpectingInput); // We have a Date we just need to check it is unambiguous. var timexProperty = new TimexProperty(time); if (!IsDateRange(time)) { var getFeedback = stepContext.Context.Activity.CreateReply(); var feedbackChoices = new HeroCard { // Text = "We could not identify the valid date . Please select any of the below.", Buttons = new List <CardAction> { new CardAction() { Title = "Next week", Type = ActionTypes.ImBack, Value = "next week" }, new CardAction() { Title = "Next weekend", Type = ActionTypes.ImBack, Value = "next weekend" }, new CardAction() { Title = "Tomorrow", Type = ActionTypes.ImBack, Value = "tomorrow" }, new CardAction() { Title = "Today", Type = ActionTypes.ImBack, Value = "today" }, new CardAction() { Title = "Last week", Type = ActionTypes.ImBack, Value = "last week" }, new CardAction() { Title = "Last weekend", Type = ActionTypes.ImBack, Value = "last weekend" }, new CardAction() { Title = "Yesterday", Type = ActionTypes.ImBack, Value = "yesterday" } }, }; // Add the card to our reply to user. getFeedback.Attachments = new List <Attachment>() { feedbackChoices.ToAttachment() }; await stepContext.Context.SendActivityAsync(getFeedback, cancellationToken); return(await stepContext.PromptAsync(nameof(DateTimePrompt), new PromptOptions { Prompt = repromptMessage, }, cancellationToken)); } return(await stepContext.NextAsync(new List <DateTimeResolution> { new DateTimeResolution { Timex = time } }, cancellationToken)); }
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 static string ConvertTimexToString(TimexProperty timex) { return(TimexConvertEnglish.ConvertTimexToString(timex)); }
public void DataTypes_Convert_WednesdayToSaturday() { // date + duration var timex = new TimexProperty("(XXXX-WXX-3,XXXX-WXX-6,P3D)"); // TODO }
private static (object value, string error) Evaluator(Expression expression, IMemory state, Options options) { TimexProperty parsed = null; string result = null; string error = null; var(validYear, validMonth, validDay) = (0, 0, 0); var currentUtcTime = DateTime.UtcNow; var convertedDateTime = currentUtcTime; IReadOnlyList <object> args; (args, error) = FunctionUtils.EvaluateChildren(expression, state, options); if (error == null) { (parsed, error) = FunctionUtils.ParseTimexProperty(args[0]); } if (error == null) { if (parsed.Year != null || parsed.Month == null || parsed.DayOfMonth == null) { error = $"{args[0]} must be a timex string which only contains month and day-of-month, for example: 'XXXX-10-31'."; } } if (error == null) { if (args.Count == 2 && args[1] is string timezone) { object convertedTimeZone = null; (convertedTimeZone, error) = FunctionUtils.ConvertTimeZoneFormat(timezone); if (error == null) { convertedDateTime = TimeZoneInfo.ConvertTimeFromUtc(currentUtcTime, (TimeZoneInfo)convertedTimeZone); } } else { convertedDateTime = currentUtcTime.ToLocalTime(); } } if (error == null) { var(year, month, day) = (convertedDateTime.Year, convertedDateTime.Month, convertedDateTime.Day); if (parsed.Month <= month || (parsed.Month == month && parsed.DayOfMonth < day)) { validYear = year; } else { validYear = year - 1; } validMonth = parsed.Month ?? 0; validDay = parsed.DayOfMonth ?? 0; if (validMonth == 2 && validDay == 29) { while (!DateTime.IsLeapYear(validYear)) { validYear -= 1; } } result = TimexProperty.FromDate(new DateTime(validYear, validMonth, validDay)).TimexValue; } return(result, error); }
// </AttendeesValidatorSnippet> // <StartValidatorSnippet> private static bool TimexHasDateAndTime(TimexProperty timex) { return(timex.Now ?? false || (timex.Types.Contains(TimexTypes.DateTime) && timex.Types.Contains(TimexTypes.Definite))); }
public static bool IsTimeRange(string timex) { TimexProperty timexProperty = new TimexProperty(timex); return(timexProperty.Types.Contains(Constants.TimexTypes.TimeRange) || timexProperty.Types.Contains(Constants.TimexTypes.DateTimeRange)); }
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)); } List <DateTime> recommendedSlots; try { var api = new CarwashService(step, _telemetryClient, 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)); }
private static bool IsAmbiguous(string timex) { var timexProperty = new TimexProperty(timex); return(!timexProperty.Types.Contains(Constants.TimexTypes.Definite)); }
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, _telemetryClient, 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)); }
protected async Task DigestLuisResult(DialogContext dc, ReservationLuis luisResult) { try { var state = await ConversationStateAccessor.GetAsync(dc.Context); // Extract entities and store in state here. if (luisResult != null) { var entities = luisResult.Entities; // Extract the cuisines out (already normalized to canonical form) and put in State thus slot-filling for the dialog. if (entities.cuisine != null) { foreach (var cuisine in entities.cuisine) { var type = cuisine.First <string>(); state.Booking.Category = type; } } if (entities.datetime != null) { var results = DateTimeRecognizer.RecognizeDateTime(dc.Context.Activity.Text, CultureInfo.CurrentUICulture.ToString()); if (results.Count > 0) { // We only care about presence of one DateTime var result = results.First(); // The resolution could include two example values: one for AM and one for PM. var distinctTimexExpressions = new HashSet <string>(); var values = (List <Dictionary <string, string> >)result.Resolution["values"]; foreach (var value in values) { // Each result includes a TIMEX expression that captures the inherent date but not time ambiguity. // We are interested in the distinct set of TIMEX expressions. if (value.TryGetValue("timex", out var timex)) { distinctTimexExpressions.Add(timex); } } // Now we have the timex properties let's see if we have a definite date and time // If so we slot-fill this and move on, if we don't we'll ignore for now meaning the user will be prompted var timexProperty = new TimexProperty(distinctTimexExpressions.First()); if (timexProperty.Types.Contains(Constants.TimexTypes.Date) && timexProperty.Types.Contains(Constants.TimexTypes.Definite)) { // We have definite date (no ambiguity) state.Booking.ReservationDate = new DateTime(timexProperty.Year.Value, timexProperty.Month.Value, timexProperty.DayOfMonth.Value); // Timex doesn't capture time ambiguity (e.g. 4 rather than 4pm) if (timexProperty.Types.Contains(Constants.TimexTypes.Time)) { // If we have multiple TimeX if (distinctTimexExpressions.Count == 1) { // We have definite time (no ambiguity) state.Booking.ReservationTime = DateTime.Parse($"{timexProperty.Hour.Value}:{timexProperty.Minute.Value}:{timexProperty.Second.Value}"); } else { // We don't have a distinct time so add the TimeEx expressions to enable disambiguation later and prepare the natural language versions foreach (var timex in distinctTimexExpressions) { var property = new TimexProperty(timex); state.AmbiguousTimexExpressions.Add(timex, property.ToNaturalLanguage(DateTime.Now)); } } } } else if (timexProperty.Types.Contains(Constants.TimexTypes.Time)) { // We might have a time but no date (e.g. book a table for 4pm) // If we have multiple timex (and time) this means we have a AM and PM component (e.g. ambiguous - book a table at 9) if (distinctTimexExpressions.Count == 1) { state.Booking.ReservationTime = DateTime.Parse($"{timexProperty.Hour.Value}:{timexProperty.Minute.Value}:{timexProperty.Second.Value}"); } } else { // We don't have a distinct time so add the TimeEx expressions to enable disambiguation later and prepare the natural language versions foreach (var timex in distinctTimexExpressions) { var property = new TimexProperty(timex); state.AmbiguousTimexExpressions.Add(timex, property.ToNaturalLanguage(DateTime.Now)); } } } } if (entities.geographyV2 != null) { state.Booking.Location = entities.geographyV2.First().Location; } // Establishing attendee count can be problematic as the number entity can be picked up for poorly qualified // times, e.g. book a restaurant tomorrow at 2 for 4 people so we rely on a composite entity if (entities.attendees != null) { var attendeesComposite = entities.attendees.First(); if (attendeesComposite != null) { int.TryParse(attendeesComposite.number.First().ToString(), out var attendeeCount); if (attendeeCount > 0) { state.Booking.AttendeeCount = attendeeCount; } } } } } catch { // put log here } }
public static string TimexDurationToFrench(TimexProperty timex) { if (timex.Years != null) { if (timex.Years == 1) { return($"{timex.Years} an"); } return($"{timex.Years} ans"); } if (timex.Months != null) { return($"{timex.Months} mois"); } if (timex.Weeks != null) { if (timex.Weeks == 1) { return($"{timex.Weeks} semaine"); } return($"{timex.Weeks} semaines"); } if (timex.Days != null) { if (timex.Days == 1) { return($"{timex.Days} jour"); } return($"{timex.Days} jours"); } if (timex.Hours != null) { if (timex.Hours == 1) { return($"{timex.Hours} heure"); } return($"{timex.Hours} heures"); } if (timex.Minutes != null) { if (timex.Minutes == 1) { return($"{timex.Minutes} minute"); } return($"{timex.Minutes} minutes"); } if (timex.Seconds != null) { if (timex.Seconds == 1) { return($"{timex.Seconds} seconde"); } return($"{timex.Seconds} secondes"); } return(string.Empty); }