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
Beispiel #6
0
 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)
Beispiel #7
0
 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)
Beispiel #8
0
 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