async def test_should_recognize_and_use_custom_locale_dict(self, ): async def exec_test(turn_context: TurnContext): dialog_context = await dialogs.create_context(turn_context) results: DialogTurnResult = await dialog_context.continue_dialog() if results.status == DialogTurnStatus.Empty: options = PromptOptions(prompt=Activity( type=ActivityTypes.message, text="Please confirm.")) await dialog_context.prompt("prompt", options) elif results.status == DialogTurnStatus.Complete: selected_choice = results.result await turn_context.send_activity(selected_choice.value) await convo_state.save_changes(turn_context) async def validator(prompt: PromptValidatorContext) -> bool: assert prompt if not prompt.recognized.succeeded: await prompt.context.send_activity("Bad input.") return prompt.recognized.succeeded adapter = TestAdapter(exec_test) convo_state = ConversationState(MemoryStorage()) dialog_state = convo_state.create_property("dialogState") dialogs = DialogSet(dialog_state) culture = PromptCultureModel( locale="custom-locale", no_in_language="customNo", yes_in_language="customYes", separator="customSeparator", inline_or="customInlineOr", inline_or_more="customInlineOrMore", ) custom_dict = { culture.locale: ( Choice(culture.yes_in_language), Choice(culture.no_in_language), ChoiceFactoryOptions(culture.separator, culture.inline_or, culture.inline_or_more, True), ) } confirm_prompt = ConfirmPrompt("prompt", validator, choice_defaults=custom_dict) dialogs.add(confirm_prompt) step1 = await adapter.send( Activity(type=ActivityTypes.message, text="Hello", locale=culture.locale)) await step1.assert_reply( "Please confirm. (1) customYescustomInlineOr(2) customNo")
def test_should_render_unincluded_numbers_choices_as_a_list(self): activity = ChoiceFactory.list( ChoiceFactoryTest.color_choices, "select from:", options=ChoiceFactoryOptions(include_numbers=False), ) self.assertEqual("select from:\n\n - red\n - green\n - blue", activity.text)
async def test_confirm_prompt_should_default_to_english_locale(self): async def exec_test(turn_context: TurnContext): dialog_context = await dialogs.create_context(turn_context) results: DialogTurnResult = await dialog_context.continue_dialog() if results.status == DialogTurnStatus.Empty: options = PromptOptions( prompt=Activity(type=ActivityTypes.message, text="Please confirm."), retry_prompt=Activity( type=ActivityTypes.message, text= "Please confirm, say 'yes' or 'no' or something like that.", ), ) await dialog_context.prompt("ConfirmPrompt", options) elif results.status == DialogTurnStatus.Complete: message_text = "Confirmed" if results.result else "Not confirmed" await turn_context.send_activity( MessageFactory.text(message_text)) await convo_state.save_changes(turn_context) locales = [None, "", "not-supported"] for locale in locales: # Initialize TestAdapter. adapter = TestAdapter(exec_test) # Create new ConversationState with MemoryStorage and register the state as middleware. convo_state = ConversationState(MemoryStorage()) # Create a DialogState property, DialogSet, and ChoicePrompt. dialog_state = convo_state.create_property("dialogState") dialogs = DialogSet(dialog_state) confirm_prompt = ConfirmPrompt("ConfirmPrompt") confirm_prompt.choice_options = ChoiceFactoryOptions( include_numbers=True) dialogs.add(confirm_prompt) step1 = await adapter.send( Activity(type=ActivityTypes.message, text="Hello", locale=locale)) step2 = await step1.assert_reply( "Please confirm. (1) Yes or (2) No") step3 = await step2.send("lala") step4 = await step3.assert_reply( "Please confirm, say 'yes' or 'no' or something like that. (1) Yes or (2) No" ) step5 = await step4.send("2") await step5.assert_reply("Not confirmed")
class ChoicePrompt(Prompt): """ Prompts a user to select from a list of choices. By default the prompt will return to the calling dialog a `FoundChoice` object containing the choice that was selected. """ _default_choice_options: Dict[str, ChoiceFactoryOptions] = { Culture.Spanish: ChoiceFactoryOptions(inline_separator=', ', inline_or=' o ', include_numbers=True), Culture.Dutch: ChoiceFactoryOptions(inline_separator=', ', inline_or=' of ', include_numbers=True), Culture.English: ChoiceFactoryOptions(inline_separator=', ', inline_or=' or ', include_numbers=True), Culture.French: ChoiceFactoryOptions(inline_separator=', ', inline_or=' ou ', include_numbers=True), 'de-de': ChoiceFactoryOptions(inline_separator=', ', inline_or=' oder ', include_numbers=True), Culture.Japanese: ChoiceFactoryOptions(inline_separator='、 ', inline_or=' または ', include_numbers=True), Culture.Portuguese: ChoiceFactoryOptions(inline_separator=', ', inline_or=' ou ', include_numbers=True), Culture.Chinese: ChoiceFactoryOptions(inline_separator=', ', inline_or=' 要么 ', include_numbers=True), } def __init__(self, dialog_id: str, validator: Callable[[PromptValidatorContext], bool] = None, default_locale: str = None): super().__init__(dialog_id, validator) self.style = ListStyle.auto self.default_locale = default_locale self.choice_options: ChoiceFactoryOptions = None self.recognizer_options: FindChoicesOptions = None async def on_prompt(self, turn_context: TurnContext, state: Dict[str, object], options: PromptOptions, is_retry: bool): if not turn_context: raise TypeError( 'ChoicePrompt.on_prompt(): turn_context cannot be None.') if not options: raise TypeError( 'ChoicePrompt.on_prompt(): options cannot be None.') # Determine culture culture: Union[ str, None] = turn_context.activity.locale if turn_context.activity.locale else self.default_locale if (not culture or culture not in ChoicePrompt._default_choice_options): culture = Culture.English # Format prompt to send choices: List[Choice] = options.choices if options.choices else [] channel_id: str = turn_context.activity.channel_id choice_options: ChoiceFactoryOptions = self.choice_options if self.choice_options else ChoicePrompt._default_choice_options[ culture] choice_style = options.style if options.style else self.style if is_retry and options.retry_prompt is not None: prompt = self.append_choices(options.retry_prompt, channel_id, choices, choice_style, choice_options) else: prompt = self.append_choices(options.prompt, channel_id, choices, choice_style, choice_options) # Send prompt await turn_context.send_activity(prompt) async def on_recognize(self, turn_context: TurnContext, state: Dict[str, object], options: PromptOptions) -> PromptRecognizerResult: if not turn_context: raise TypeError( 'ChoicePrompt.on_recognize(): turn_context cannot be None.') choices: List[Choice] = options.choices if ( options and options.choices) else [] result: PromptRecognizerResult = PromptRecognizerResult() if turn_context.activity.type == ActivityTypes.message: activity: Activity = turn_context.activity utterance: str = activity.text opt: FindChoicesOptions = self.recognizer_options if self.recognizer_options else FindChoicesOptions( ) opt.locale = activity.locale if activity.locale else ( self.default_locale or Culture.English) results = ChoiceRecognizers.recognize_choices( utterance, choices, opt) if results is not None and len(results) > 0: result.succeeded = True result.value = results[0].resolution return result
class ConfirmPrompt(Prompt): # TODO: Fix to reference recognizer to use proper constants choice_defaults: Dict[str, object] = { "Spanish": ( Choice("Si"), Choice("No"), ChoiceFactoryOptions(", ", " o ", ", o ", True), ), "Dutch": ( Choice("Ja"), Choice("Nee"), ChoiceFactoryOptions(", ", " of ", ", of ", True), ), "English": ( Choice("Yes"), Choice("No"), ChoiceFactoryOptions(", ", " or ", ", or ", True), ), "French": ( Choice("Oui"), Choice("Non"), ChoiceFactoryOptions(", ", " ou ", ", ou ", True), ), "German": ( Choice("Ja"), Choice("Nein"), ChoiceFactoryOptions(", ", " oder ", ", oder ", True), ), "Japanese": ( Choice("はい"), Choice("いいえ"), ChoiceFactoryOptions("、 ", " または ", "、 または ", True), ), "Portuguese": ( Choice("Sim"), Choice("Não"), ChoiceFactoryOptions(", ", " ou ", ", ou ", True), ), "Chinese": ( Choice("是的"), Choice("不"), ChoiceFactoryOptions(", ", " 要么 ", ", 要么 ", True), ), } # TODO: PromptValidator def __init__(self, dialog_id: str, validator: object = None, default_locale: str = None): super(ConfirmPrompt, self).__init__(dialog_id, validator) if dialog_id is None: raise TypeError("ConfirmPrompt(): dialog_id cannot be None.") # TODO: Port ListStyle self.style = ListStyle.auto # TODO: Import defaultLocale self.default_locale = default_locale self.choice_options = None self.confirm_choices = None async def on_prompt( self, turn_context: TurnContext, state: Dict[str, object], options: PromptOptions, is_retry: bool, ): if not turn_context: raise TypeError( "ConfirmPrompt.on_prompt(): turn_context cannot be None.") if not options: raise TypeError( "ConfirmPrompt.on_prompt(): options cannot be None.") # Format prompt to send channel_id = turn_context.activity.channel_id culture = self.determine_culture(turn_context.activity) defaults = self.choice_defaults[culture] choice_opts = (self.choice_options if self.choice_options is not None else defaults[2]) confirms = (self.confirm_choices if self.confirm_choices is not None else (defaults[0], defaults[1])) choices = {confirms[0], confirms[1]} if is_retry and options.retry_prompt is not None: prompt = self.append_choices(options.retry_prompt, channel_id, choices, self.style, choice_opts) else: prompt = self.append_choices(options.prompt, channel_id, choices, self.style, choice_opts) await turn_context.send_activity(prompt) async def on_recognize( self, turn_context: TurnContext, state: Dict[str, object], options: PromptOptions, ) -> PromptRecognizerResult: # pylint: disable=undefined-variable if not turn_context: raise TypeError( "ConfirmPrompt.on_prompt(): turn_context cannot be None.") result = PromptRecognizerResult() if turn_context.activity.type == ActivityTypes.message: # Recognize utterance message = turn_context.activity culture = self.determine_culture(turn_context.activity) # TODO: Port ChoiceRecognizer results = ChoiceRecognizer.recognize_boolean(message.text, culture) if results.Count > 0: first = results[0] if "value" in first.Resolution: result.succeeded = True result.value = str(first.Resolution["value"]) else: # First check whether the prompt was sent to the user with numbers # if it was we should recognize numbers defaults = self.choice_defaults[culture] opts = (self.choice_options if self.choice_options is not None else defaults[2]) # This logic reflects the fact that IncludeNumbers is nullable and True is the default set in # Inline style if opts.include_numbers.has_value or opts.include_numbers.value: # The text may be a number in which case we will interpret that as a choice. confirm_choices = (self.confirm_choices if self.confirm_choices is not None else (defaults[0], defaults[1])) choices = {confirm_choices[0], confirm_choices[1]} # TODO: Port ChoiceRecognizer second_attempt_results = ChoiceRecognizers.recognize_choices( message.text, choices) if second_attempt_results: result.succeeded = True result.value = second_attempt_results[ 0].resolution.index == 0 return result def determine_culture(self, activity: Activity) -> str: culture = (activity.locale if activity.locale is not None else self.default_locale) if not culture or culture not in self.choice_defaults: culture = ( "English" ) # TODO: Fix to reference recognizer to use proper constants return culture
def test_inline_separator_round_trips(self) -> None: choice_factor_options = ChoiceFactoryOptions() expected = ", " choice_factor_options.inline_separator = expected self.assertEqual(expected, choice_factor_options.inline_separator)
def test_include_numbers_round_trips(self) -> None: choice_factor_options = ChoiceFactoryOptions() expected = True choice_factor_options.include_numbers = expected self.assertEqual(expected, choice_factor_options.include_numbers)
def test_inline_or_more_round_trips(self) -> None: choice_factor_options = ChoiceFactoryOptions() expected = ", or " choice_factor_options.inline_or_more = expected self.assertEqual(expected, choice_factor_options.inline_or_more)
class ConfirmPrompt(Prompt): _default_choice_options: Dict[str, object] = { c.locale: ( Choice(c.yes_in_language), Choice(c.no_in_language), ChoiceFactoryOptions(c.separator, c.inline_or, c.inline_or_more, True), ) for c in PromptCultureModels.get_supported_cultures() } # TODO: PromptValidator def __init__( self, dialog_id: str, validator: object = None, default_locale: str = None, choice_defaults: Dict[str, object] = None, ): super().__init__(dialog_id, validator) if dialog_id is None: raise TypeError("ConfirmPrompt(): dialog_id cannot be None.") # TODO: Port ListStyle self.style = ListStyle.auto # TODO: Import defaultLocale self.default_locale = default_locale self.choice_options = None self.confirm_choices = None if choice_defaults is not None: self._default_choice_options = choice_defaults async def on_prompt( self, turn_context: TurnContext, state: Dict[str, object], options: PromptOptions, is_retry: bool, ): if not turn_context: raise TypeError( "ConfirmPrompt.on_prompt(): turn_context cannot be None.") if not options: raise TypeError( "ConfirmPrompt.on_prompt(): options cannot be None.") # Format prompt to send channel_id = turn_context.activity.channel_id culture = self._determine_culture(turn_context.activity) defaults = self._default_choice_options[culture] choice_opts = (self.choice_options if self.choice_options is not None else defaults[2]) confirms = (self.confirm_choices if self.confirm_choices is not None else (defaults[0], defaults[1])) choices = [confirms[0], confirms[1]] if is_retry and options.retry_prompt is not None: prompt = self.append_choices(options.retry_prompt, channel_id, choices, self.style, choice_opts) else: prompt = self.append_choices(options.prompt, channel_id, choices, self.style, choice_opts) await turn_context.send_activity(prompt) async def on_recognize( self, turn_context: TurnContext, state: Dict[str, object], options: PromptOptions, ) -> PromptRecognizerResult: if not turn_context: raise TypeError( "ConfirmPrompt.on_prompt(): turn_context cannot be None.") result = PromptRecognizerResult() if turn_context.activity.type == ActivityTypes.message: # Recognize utterance utterance = turn_context.activity.text if not utterance: return result culture = self._determine_culture(turn_context.activity) results = recognize_boolean(utterance, culture) if results: first = results[0] if "value" in first.resolution: result.succeeded = True result.value = first.resolution["value"] else: # First check whether the prompt was sent to the user with numbers # if it was we should recognize numbers defaults = self._default_choice_options[culture] opts = (self.choice_options if self.choice_options is not None else defaults[2]) # This logic reflects the fact that IncludeNumbers is nullable and True is the default set in # Inline style if opts.include_numbers is None or opts.include_numbers: # The text may be a number in which case we will interpret that as a choice. confirm_choices = (self.confirm_choices if self.confirm_choices is not None else (defaults[0], defaults[1])) choices = {confirm_choices[0], confirm_choices[1]} second_attempt_results = ChoiceRecognizers.recognize_choices( utterance, choices) if second_attempt_results: result.succeeded = True result.value = second_attempt_results[ 0].resolution.index == 0 return result def _determine_culture(self, activity: Activity) -> str: culture = (PromptCultureModels.map_to_nearest_language(activity.locale) or self.default_locale or PromptCultureModels.English.locale) if not culture or not self._default_choice_options.get(culture): culture = PromptCultureModels.English.locale return culture
class ChoicePrompt(Prompt): """ Prompts a user to select from a list of choices. By default the prompt will return to the calling dialog a `FoundChoice` object containing the choice that was selected. """ _default_choice_options: Dict[str, ChoiceFactoryOptions] = { c.locale: ChoiceFactoryOptions( inline_separator=c.separator, inline_or=c.inline_or_more, inline_or_more=c.inline_or_more, include_numbers=True, ) for c in PromptCultureModels.get_supported_cultures() } def __init__( self, dialog_id: str, validator: Callable[[PromptValidatorContext], bool] = None, default_locale: str = None, choice_defaults: Dict[str, ChoiceFactoryOptions] = None, ): """ :param dialog_id: Unique ID of the dialog within its parent `DialogSet`. :param validator: (Optional) validator that will be called each time the user responds to the prompt. If the validator replies with a message no additional retry prompt will be sent. :param default_locale: (Optional) locale to use if `dc.context.activity.locale` not specified. Defaults to a value of `en-us`. :param choice_defaults: (Optional) Overrides the dictionary of Bot Framework SDK-supported _default_choice_options. As type Dict[str, ChoiceFactoryOptions], the key is a string of the locale, such as "en-us". * Must be passed in to each ConfirmPrompt that needs the custom choice defaults. """ super().__init__(dialog_id, validator) self.style = ListStyle.auto self.default_locale = default_locale self.choice_options: ChoiceFactoryOptions = None self.recognizer_options: FindChoicesOptions = None if choice_defaults is not None: self._default_choice_options = choice_defaults async def on_prompt( self, turn_context: TurnContext, state: Dict[str, object], options: PromptOptions, is_retry: bool, ): if not turn_context: raise TypeError( "ChoicePrompt.on_prompt(): turn_context cannot be None.") if not options: raise TypeError( "ChoicePrompt.on_prompt(): options cannot be None.") # Determine culture culture = self._determine_culture(turn_context.activity) # Format prompt to send choices: List[Choice] = options.choices if options.choices else [] channel_id: str = turn_context.activity.channel_id choice_options: ChoiceFactoryOptions = ( self.choice_options if self.choice_options else self._default_choice_options[culture]) choice_style = (0 if options.style == 0 else options.style if options.style else self.style) if is_retry and options.retry_prompt is not None: prompt = self.append_choices(options.retry_prompt, channel_id, choices, choice_style, choice_options) else: prompt = self.append_choices(options.prompt, channel_id, choices, choice_style, choice_options) # Send prompt await turn_context.send_activity(prompt) async def on_recognize( self, turn_context: TurnContext, state: Dict[str, object], options: PromptOptions, ) -> PromptRecognizerResult: if not turn_context: raise TypeError( "ChoicePrompt.on_recognize(): turn_context cannot be None.") choices: List[Choice] = options.choices if ( options and options.choices) else [] result: PromptRecognizerResult = PromptRecognizerResult() if turn_context.activity.type == ActivityTypes.message: activity: Activity = turn_context.activity utterance: str = activity.text if not utterance: return result opt: FindChoicesOptions = self.recognizer_options if self.recognizer_options else FindChoicesOptions( ) opt.locale = self._determine_culture(turn_context.activity, opt) results = ChoiceRecognizers.recognize_choices( utterance, choices, opt) if results is not None and results: result.succeeded = True result.value = results[0].resolution return result def _determine_culture( self, activity: Activity, opt: FindChoicesOptions = FindChoicesOptions()) -> str: culture = (PromptCultureModels.map_to_nearest_language(activity.locale) or opt.locale or self.default_locale or PromptCultureModels.English.locale) if not culture or not self._default_choice_options.get(culture): culture = PromptCultureModels.English.locale return culture