private void UpdateContactSelectionPromptOptions(PromptOptions options, PhoneSkillState state) { var templateId = OutgoingCallResponses.ContactSelection; var tokens = new Dictionary <string, object> { { "contactName", state.ContactResult.SearchQuery }, }; options.Choices = new List <Choice>(); var searchQueryPreProcessed = contactFilter.PreProcess(state.ContactResult.SearchQuery); for (var i = 0; i < state.ContactResult.Matches.Count; ++i) { var item = state.ContactResult.Matches[i].Name; var synonyms = new List <string> { item, (i + 1).ToString(), }; var choice = new Choice() { Value = item, Synonyms = synonyms, }; options.Choices.Add(choice); if (!contactFilter.PreProcess(item).Contains(searchQueryPreProcessed, StringComparison.OrdinalIgnoreCase)) { templateId = OutgoingCallResponses.ContactSelectionWithoutName; tokens.Remove("contactName"); } } options.Prompt = TemplateManager.GenerateActivity(templateId, tokens); }
/// <summary> /// If there is a requested phone number type, filter the phone numbers of each contact candidate by that type. /// Exception: if this would remove all phone numbers of a candidate, keep them all, so the user can choose a different one. /// </summary> /// <param name="state">The current state. This will be modified.</param> /// <param name="isFiltered">Whether filtering was actually performed before this method was called.</param> /// <returns>Whether filtering was actually performed, either by this method or before.</returns> private (bool, bool) FilterPhoneNumbersByType(PhoneSkillState state, bool isFiltered) { var hasPhoneNumberOfRequestedType = false; for (int i = 0; i < state.ContactResult.Matches.Count; ++i) { var candidate = state.ContactResult.Matches[i]; if (state.ContactResult.RequestedPhoneNumberType.Any()) { var phoneNumbersOfCorrectType = new List <PhoneNumber>(); foreach (var phoneNumber in candidate.PhoneNumbers) { if (DoPhoneNumberTypesMatch(state.ContactResult.RequestedPhoneNumberType, phoneNumber.Type)) { phoneNumbersOfCorrectType.Add(phoneNumber); hasPhoneNumberOfRequestedType = true; } } if (phoneNumbersOfCorrectType.Any()) { isFiltered = isFiltered || phoneNumbersOfCorrectType.Count != candidate.PhoneNumbers.Count; candidate = (ContactCandidate)candidate.Clone(); candidate.PhoneNumbers = phoneNumbersOfCorrectType; state.ContactResult.Matches[i] = candidate; } } } return(isFiltered, hasPhoneNumberOfRequestedType); }
private IContactProvider GetContactProvider(PhoneSkillState state) { if (state.SourceOfContacts == null) { // TODO Better error message to tell the bot developer where to specify the source. throw new Exception("Cannot retrieve contact list because no contact source specified."); } return(ServiceManager.GetContactProvider(state.Token, state.SourceOfContacts.Value)); }
/// <summary> /// Override the entities on the state with the ones from the given LUIS result. /// </summary> /// <param name="state">The current state. This will be modified.</param> /// <param name="contactSelectionResult">The LUIS result.</param> public void OverrideEntities(PhoneSkillState state, ContactSelectionLuis contactSelectionResult) { state.LuisResult.Entities.contactName = contactSelectionResult.Entities.contactName; state.LuisResult.Entities._instance.contactName = contactSelectionResult.Entities._instance.contactName; state.LuisResult.Entities.phoneNumber = new string[0]; state.LuisResult.Entities._instance.phoneNumber = new InstanceData[0]; state.LuisResult.Entities.phoneNumberSpelledOut = new string[0]; state.LuisResult.Entities._instance.phoneNumberSpelledOut = new InstanceData[0]; }
/// <summary> /// Filters the user's contact list repeatedly based on the user's input to determine the right contact and phone number to call. /// </summary> /// <param name="state">The current conversation state. This will be modified.</param> /// <param name="contactProvider">The provider for the user's contact list. This may be null if the contact list is not to be used.</param> /// <returns>The first boolean indicates whether filtering was actually performed. (In some cases, no filtering is necessary.) /// The second boolean indicates whether any of the contacts has a phone number whose type matches the requested type.</returns> public async Task <(bool, bool)> FilterAsync(PhoneSkillState state, IContactProvider contactProvider) { var isFiltered = false; var searchQuery = string.Empty; if (state.LuisResult.Entities.contactName != null) { searchQuery = string.Join(" ", state.LuisResult.Entities.contactName); } if (searchQuery.Any() && !(searchQuery == state.ContactResult.SearchQuery && state.ContactResult.Matches.Any())) { IList <ContactCandidate> contacts; if (state.ContactResult.Matches.Any()) { contacts = state.ContactResult.Matches; } else if (contactProvider != null) { contacts = await contactProvider.GetContactsAsync(); } else { contacts = new List <ContactCandidate>(); } if (contacts.Any()) { // TODO Adjust max number of returned contacts? var matcher = new EnContactMatcher <ContactCandidate>(contacts, ExtractContactFields); var matches = matcher.FindByName(searchQuery); if (!state.ContactResult.Matches.Any() || matches.Any()) { isFiltered = isFiltered || matches.Count != state.ContactResult.Matches.Count; state.ContactResult.SearchQuery = searchQuery; state.ContactResult.Matches = matches; } } } SetRequestedPhoneNumberType(state); var hasPhoneNumberOfRequestedType = false; (isFiltered, hasPhoneNumberOfRequestedType) = FilterPhoneNumbersByType(state, isFiltered); SetPhoneNumber(state); return(isFiltered, hasPhoneNumberOfRequestedType); }
private void SetPhoneNumber(PhoneSkillState state) { if (!state.PhoneNumber.Any()) { var entities = state.LuisResult.Entities; if (state.ContactResult.Matches.Count == 1 && state.ContactResult.Matches[0].PhoneNumbers.Count == 1) { state.PhoneNumber = state.ContactResult.Matches[0].PhoneNumbers[0].Number; } else if (entities.phoneNumber != null && entities.phoneNumber.Any()) { state.PhoneNumber = string.Join(" ", entities.phoneNumber); } } }
/// <summary> /// Override the entities on the state with the ones from the given LUIS result. /// </summary> /// <param name="state">The current state. This will be modified.</param> /// <param name="phoneNumberSelectionResult">The LUIS result.</param> public void OverrideEntities(PhoneSkillState state, PhoneNumberSelectionLuis phoneNumberSelectionResult) { state.LuisResult.Entities.phoneNumber = new string[0]; state.LuisResult.Entities._instance.phoneNumber = new InstanceData[0]; state.LuisResult.Entities.phoneNumberSpelledOut = new string[0]; state.LuisResult.Entities._instance.phoneNumberSpelledOut = new InstanceData[0]; if (state.LuisResult.Entities.phoneNumberType == null || !state.LuisResult.Entities.phoneNumberType.Any() || (phoneNumberSelectionResult.Entities.phoneNumberType != null && phoneNumberSelectionResult.Entities.phoneNumberType.Any())) { state.LuisResult.Entities.phoneNumberType = phoneNumberSelectionResult.Entities.phoneNumberType; state.LuisResult.Entities._instance.phoneNumberType = phoneNumberSelectionResult.Entities._instance.phoneNumberType; } }
/// <summary> /// Override the entities on the state with the ones from the given LUIS result. /// </summary> /// <param name="state">The current state. This will be modified.</param> /// <param name="phoneResult">The LUIS result.</param> public void OverrideEntities(PhoneSkillState state, PhoneLuis phoneResult) { state.LuisResult.Entities.contactName = phoneResult.Entities.contactName; state.LuisResult.Entities._instance.contactName = phoneResult.Entities._instance.contactName; state.LuisResult.Entities.phoneNumber = phoneResult.Entities.phoneNumber; state.LuisResult.Entities._instance.phoneNumber = phoneResult.Entities._instance.phoneNumber; state.LuisResult.Entities.phoneNumberSpelledOut = phoneResult.Entities.phoneNumberSpelledOut; state.LuisResult.Entities._instance.phoneNumberSpelledOut = phoneResult.Entities._instance.phoneNumberSpelledOut; if (state.LuisResult.Entities.phoneNumberType == null || !state.LuisResult.Entities.phoneNumberType.Any() || (phoneResult.Entities.phoneNumberType != null && phoneResult.Entities.phoneNumberType.Any())) { state.LuisResult.Entities.phoneNumberType = phoneResult.Entities.phoneNumberType; state.LuisResult.Entities._instance.phoneNumberType = phoneResult.Entities._instance.phoneNumberType; } }
private void SetRequestedPhoneNumberType(PhoneSkillState state) { if (!state.ContactResult.RequestedPhoneNumberType.Any()) { var entities = state.LuisResult.Entities; if (entities.phoneNumberType != null && entities.phoneNumberType.Length > 0 && entities._instance.phoneNumberType != null && entities._instance.phoneNumberType.Length > 0) { var instanceData = entities._instance.phoneNumberType[0]; var resolvedValues = entities.phoneNumberType[0]; foreach (var value in resolvedValues) { state.ContactResult.RequestedPhoneNumberType = ParsePhoneNumberType(value, instanceData); break; } } } }
private void UpdatePhoneNumberSelectionPromptOptions(PromptOptions options, PhoneSkillState state) { var templateId = OutgoingCallResponses.PhoneNumberSelection; var tokens = new Dictionary <string, object> { { "contact", state.ContactResult.Matches[0].Name }, }; options.Choices = new List <Choice>(); var phoneNumberList = state.ContactResult.Matches[0].PhoneNumbers; var phoneNumberTypes = new HashSet <PhoneNumberType>(); for (var i = 0; i < phoneNumberList.Count; ++i) { var phoneNumber = phoneNumberList[i]; var speakableType = $"{GetSpeakablePhoneNumberType(phoneNumber.Type)}: {phoneNumber.Number}"; var synonyms = new List <string> { speakableType, phoneNumber.Type.FreeForm, phoneNumber.Number, (i + 1).ToString(), }; var choice = new Choice() { Value = speakableType, Synonyms = synonyms, }; options.Choices.Add(choice); phoneNumberTypes.Add(phoneNumber.Type); } if (state.ContactResult.RequestedPhoneNumberType.Any() && phoneNumberTypes.Count == 1) { templateId = OutgoingCallResponses.PhoneNumberSelectionWithPhoneNumberType; tokens["phoneNumberType"] = GetSpeakablePhoneNumberType(phoneNumberTypes.First()); } options.Prompt = TemplateManager.GenerateActivity(templateId, tokens); }
/// <summary> /// Remove and return contact candidates that don't have any phone number. /// </summary> /// <param name="state">The current state. This will be modified.</param> /// <returns>The removed candidates.</returns> public List <ContactCandidate> RemoveContactsWithNoPhoneNumber(PhoneSkillState state) { var newCandidates = new List <ContactCandidate>(); var removedCandidates = new List <ContactCandidate>(); foreach (var candidate in state.ContactResult.Matches) { if (candidate.PhoneNumbers.Any()) { newCandidates.Add(candidate); } else { removedCandidates.Add(candidate); } } if (newCandidates.Count != state.ContactResult.Matches.Count) { state.ContactResult.Matches = newCandidates; } return(removedCandidates); }
private async Task <bool> CheckRecipientAndExplainFailureToUserAsync(ITurnContext context, PhoneSkillState state, CancellationToken cancellationToken) { if (contactFilter.HasRecipient(state)) { var contactsWithNoPhoneNumber = contactFilter.RemoveContactsWithNoPhoneNumber(state); if (contactFilter.HasRecipient(state)) { return(true); } if (contactsWithNoPhoneNumber.Count == 1) { var tokens = new Dictionary <string, object>() { { "contact", contactsWithNoPhoneNumber[0].Name }, }; var response = TemplateManager.GenerateActivity(OutgoingCallResponses.ContactHasNoPhoneNumber, tokens); await context.SendActivityAsync(response, cancellationToken); } else { var tokens = new Dictionary <string, object>() { { "contactName", state.ContactResult.SearchQuery }, }; var response = TemplateManager.GenerateActivity(OutgoingCallResponses.ContactsHaveNoPhoneNumber, tokens); await context.SendActivityAsync(response, cancellationToken); } state.ContactResult.SearchQuery = string.Empty; return(false); } if (state.ContactResult.SearchQuery.Any()) { var tokens = new Dictionary <string, object>() { { "contactName", state.ContactResult.SearchQuery }, }; var response = TemplateManager.GenerateActivity(OutgoingCallResponses.ContactNotFound, tokens); await context.SendActivityAsync(response, cancellationToken); } return(false); }
/// <summary> /// Returns whether the contact has been completely disambiguated. /// </summary> /// <param name="state">The current state.</param> /// <returns>Whether the contact has been completely disambiguated.</returns> public bool IsContactDisambiguated(PhoneSkillState state) { return(state.ContactResult.Matches.Count == 1 || state.PhoneNumber.Any()); }
/// <summary> /// Returns whether a recipient has been specified. /// </summary> /// <param name="state">The current state.</param> /// <returns>Whether a recipient has been specified.</returns> public bool HasRecipient(PhoneSkillState state) { return(state.ContactResult.Matches.Any() || state.PhoneNumber.Any()); }
/// <summary> /// Returns whether the contact's phone number has been completely disambiguated. /// </summary> /// <param name="state">The current state.</param> /// <returns>Whether the contact's phone number has been completely disambiguated.</returns> public bool IsPhoneNumberDisambiguated(PhoneSkillState state) { return((state.ContactResult.Matches.Count == 1 && state.ContactResult.Matches[0].PhoneNumbers.Count == 1) || state.PhoneNumber.Any()); }