microsoft-agents-hosting-dialogs 0.10.0.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- microsoft_agents/hosting/dialogs/__init__.py +76 -0
- microsoft_agents/hosting/dialogs/_component_registration.py +30 -0
- microsoft_agents/hosting/dialogs/_telemetry_client.py +78 -0
- microsoft_agents/hosting/dialogs/choices/__init__.py +38 -0
- microsoft_agents/hosting/dialogs/choices/channel.py +121 -0
- microsoft_agents/hosting/dialogs/choices/choice_factory.py +262 -0
- microsoft_agents/hosting/dialogs/choices/choice_recognizer.py +148 -0
- microsoft_agents/hosting/dialogs/choices/find.py +242 -0
- microsoft_agents/hosting/dialogs/choices/models/__init__.py +23 -0
- microsoft_agents/hosting/dialogs/choices/models/choice.py +14 -0
- microsoft_agents/hosting/dialogs/choices/models/choice_factory_options.py +13 -0
- microsoft_agents/hosting/dialogs/choices/models/find_choices_options.py +28 -0
- microsoft_agents/hosting/dialogs/choices/models/find_values_options.py +31 -0
- microsoft_agents/hosting/dialogs/choices/models/found_choice.py +22 -0
- microsoft_agents/hosting/dialogs/choices/models/found_value.py +20 -0
- microsoft_agents/hosting/dialogs/choices/models/list_style.py +15 -0
- microsoft_agents/hosting/dialogs/choices/models/model_result.py +16 -0
- microsoft_agents/hosting/dialogs/choices/models/sorted_value.py +16 -0
- microsoft_agents/hosting/dialogs/choices/models/token.py +20 -0
- microsoft_agents/hosting/dialogs/choices/tokenizer.py +92 -0
- microsoft_agents/hosting/dialogs/component_dialog.py +284 -0
- microsoft_agents/hosting/dialogs/dialog.py +198 -0
- microsoft_agents/hosting/dialogs/dialog_component_registration.py +52 -0
- microsoft_agents/hosting/dialogs/dialog_container.py +31 -0
- microsoft_agents/hosting/dialogs/dialog_context.py +426 -0
- microsoft_agents/hosting/dialogs/dialog_extensions.py +201 -0
- microsoft_agents/hosting/dialogs/dialog_manager.py +189 -0
- microsoft_agents/hosting/dialogs/dialog_manager_result.py +17 -0
- microsoft_agents/hosting/dialogs/dialog_set.py +174 -0
- microsoft_agents/hosting/dialogs/dialog_state.py +20 -0
- microsoft_agents/hosting/dialogs/memory/__init__.py +24 -0
- microsoft_agents/hosting/dialogs/memory/component_memory_scopes_base.py +14 -0
- microsoft_agents/hosting/dialogs/memory/component_path_resolvers_base.py +15 -0
- microsoft_agents/hosting/dialogs/memory/dialog_path.py +33 -0
- microsoft_agents/hosting/dialogs/memory/dialog_state_manager.py +563 -0
- microsoft_agents/hosting/dialogs/memory/dialog_state_manager_configuration.py +11 -0
- microsoft_agents/hosting/dialogs/memory/path_resolver_base.py +8 -0
- microsoft_agents/hosting/dialogs/memory/path_resolvers/__init__.py +19 -0
- microsoft_agents/hosting/dialogs/memory/path_resolvers/alias_path_resolver.py +53 -0
- microsoft_agents/hosting/dialogs/memory/path_resolvers/at_at_path_resolver.py +9 -0
- microsoft_agents/hosting/dialogs/memory/path_resolvers/at_path_resolver.py +44 -0
- microsoft_agents/hosting/dialogs/memory/path_resolvers/dollar_path_resolver.py +9 -0
- microsoft_agents/hosting/dialogs/memory/path_resolvers/hash_path_resolver.py +9 -0
- microsoft_agents/hosting/dialogs/memory/path_resolvers/percent_path_resolver.py +9 -0
- microsoft_agents/hosting/dialogs/memory/scope_path.py +38 -0
- microsoft_agents/hosting/dialogs/memory/scopes/__init__.py +31 -0
- microsoft_agents/hosting/dialogs/memory/scopes/bot_state_memory_scope.py +66 -0
- microsoft_agents/hosting/dialogs/memory/scopes/class_memory_scope.py +64 -0
- microsoft_agents/hosting/dialogs/memory/scopes/conversation_memory_scope.py +12 -0
- microsoft_agents/hosting/dialogs/memory/scopes/dialog_class_memory_scope.py +52 -0
- microsoft_agents/hosting/dialogs/memory/scopes/dialog_context_memory_scope.py +68 -0
- microsoft_agents/hosting/dialogs/memory/scopes/dialog_memory_scope.py +75 -0
- microsoft_agents/hosting/dialogs/memory/scopes/memory_scope.py +91 -0
- microsoft_agents/hosting/dialogs/memory/scopes/settings_memory_scope.py +38 -0
- microsoft_agents/hosting/dialogs/memory/scopes/this_memory_scope.py +36 -0
- microsoft_agents/hosting/dialogs/memory/scopes/turn_memory_scope.py +86 -0
- microsoft_agents/hosting/dialogs/memory/scopes/user_memory_scope.py +12 -0
- microsoft_agents/hosting/dialogs/models/__init__.py +15 -0
- microsoft_agents/hosting/dialogs/models/dialog_event.py +13 -0
- microsoft_agents/hosting/dialogs/models/dialog_events.py +12 -0
- microsoft_agents/hosting/dialogs/models/dialog_instance.py +28 -0
- microsoft_agents/hosting/dialogs/models/dialog_reason.py +34 -0
- microsoft_agents/hosting/dialogs/models/dialog_turn_result.py +17 -0
- microsoft_agents/hosting/dialogs/models/dialog_turn_status.py +26 -0
- microsoft_agents/hosting/dialogs/object_path.py +315 -0
- microsoft_agents/hosting/dialogs/persisted_state.py +22 -0
- microsoft_agents/hosting/dialogs/persisted_state_keys.py +8 -0
- microsoft_agents/hosting/dialogs/prompts/__init__.py +41 -0
- microsoft_agents/hosting/dialogs/prompts/activity_prompt.py +203 -0
- microsoft_agents/hosting/dialogs/prompts/attachment_prompt.py +87 -0
- microsoft_agents/hosting/dialogs/prompts/choice_prompt.py +156 -0
- microsoft_agents/hosting/dialogs/prompts/confirm_prompt.py +161 -0
- microsoft_agents/hosting/dialogs/prompts/datetime_prompt.py +90 -0
- microsoft_agents/hosting/dialogs/prompts/datetime_resolution.py +16 -0
- microsoft_agents/hosting/dialogs/prompts/number_prompt.py +81 -0
- microsoft_agents/hosting/dialogs/prompts/oauth_prompt.py +569 -0
- microsoft_agents/hosting/dialogs/prompts/oauth_prompt_settings.py +43 -0
- microsoft_agents/hosting/dialogs/prompts/prompt.py +224 -0
- microsoft_agents/hosting/dialogs/prompts/prompt_culture_models.py +222 -0
- microsoft_agents/hosting/dialogs/prompts/prompt_options.py +42 -0
- microsoft_agents/hosting/dialogs/prompts/prompt_recognizer_result.py +11 -0
- microsoft_agents/hosting/dialogs/prompts/prompt_validator.py +0 -0
- microsoft_agents/hosting/dialogs/prompts/prompt_validator_context.py +44 -0
- microsoft_agents/hosting/dialogs/prompts/text_prompt.py +82 -0
- microsoft_agents/hosting/dialogs/waterfall_dialog.py +266 -0
- microsoft_agents/hosting/dialogs/waterfall_step_context.py +109 -0
- microsoft_agents_hosting_dialogs-0.10.0.dev2.dist-info/METADATA +87 -0
- microsoft_agents_hosting_dialogs-0.10.0.dev2.dist-info/RECORD +91 -0
- microsoft_agents_hosting_dialogs-0.10.0.dev2.dist-info/WHEEL +5 -0
- microsoft_agents_hosting_dialogs-0.10.0.dev2.dist-info/licenses/LICENSE +21 -0
- microsoft_agents_hosting_dialogs-0.10.0.dev2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
# --------------------------------------------------------------------------
|
|
3
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
4
|
+
# Licensed under the MIT License. See License.txt in the project root for
|
|
5
|
+
# license information.
|
|
6
|
+
# --------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
__version__ = "0.0.0"
|
|
9
|
+
|
|
10
|
+
from .component_dialog import ComponentDialog
|
|
11
|
+
from .dialog_container import DialogContainer
|
|
12
|
+
from .dialog_context import DialogContext
|
|
13
|
+
from .models.dialog_event import DialogEvent
|
|
14
|
+
from .models.dialog_events import DialogEvents
|
|
15
|
+
from .models.dialog_instance import DialogInstance
|
|
16
|
+
from .models.dialog_reason import DialogReason
|
|
17
|
+
from .dialog_set import DialogSet
|
|
18
|
+
from .dialog_state import DialogState
|
|
19
|
+
from .models.dialog_turn_result import DialogTurnResult
|
|
20
|
+
from .models.dialog_turn_status import DialogTurnStatus
|
|
21
|
+
from .dialog_manager import DialogManager
|
|
22
|
+
from .dialog_manager_result import DialogManagerResult
|
|
23
|
+
from .dialog import Dialog
|
|
24
|
+
from .dialog_component_registration import DialogsComponentRegistration
|
|
25
|
+
from .persisted_state_keys import PersistedStateKeys
|
|
26
|
+
from .persisted_state import PersistedState
|
|
27
|
+
from .waterfall_dialog import WaterfallDialog
|
|
28
|
+
from .waterfall_step_context import WaterfallStepContext
|
|
29
|
+
from .dialog_extensions import DialogExtensions
|
|
30
|
+
from .prompts import *
|
|
31
|
+
from .choices import *
|
|
32
|
+
from .object_path import ObjectPath
|
|
33
|
+
from .models import (
|
|
34
|
+
DialogEvent,
|
|
35
|
+
DialogEvents,
|
|
36
|
+
DialogInstance,
|
|
37
|
+
DialogReason,
|
|
38
|
+
DialogTurnResult,
|
|
39
|
+
DialogTurnStatus,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"ComponentDialog",
|
|
44
|
+
"DialogContainer",
|
|
45
|
+
"DialogContext",
|
|
46
|
+
"DialogEvent",
|
|
47
|
+
"DialogEvents",
|
|
48
|
+
"DialogInstance",
|
|
49
|
+
"DialogReason",
|
|
50
|
+
"DialogSet",
|
|
51
|
+
"DialogState",
|
|
52
|
+
"DialogTurnResult",
|
|
53
|
+
"DialogTurnStatus",
|
|
54
|
+
"DialogManager",
|
|
55
|
+
"DialogManagerResult",
|
|
56
|
+
"Dialog",
|
|
57
|
+
"DialogsComponentRegistration",
|
|
58
|
+
"WaterfallDialog",
|
|
59
|
+
"WaterfallStepContext",
|
|
60
|
+
"ConfirmPrompt",
|
|
61
|
+
"DateTimePrompt",
|
|
62
|
+
"DateTimeResolution",
|
|
63
|
+
"NumberPrompt",
|
|
64
|
+
"OAuthPrompt",
|
|
65
|
+
"OAuthPromptSettings",
|
|
66
|
+
"PersistedStateKeys",
|
|
67
|
+
"PersistedState",
|
|
68
|
+
"PromptRecognizerResult",
|
|
69
|
+
"PromptValidatorContext",
|
|
70
|
+
"Prompt",
|
|
71
|
+
"PromptOptions",
|
|
72
|
+
"TextPrompt",
|
|
73
|
+
"DialogExtensions",
|
|
74
|
+
"ObjectPath",
|
|
75
|
+
"__version__",
|
|
76
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ComponentRegistration:
|
|
8
|
+
"""
|
|
9
|
+
Simple component registration that allows dialogs and other components
|
|
10
|
+
to register memory scopes and path resolvers.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
_components: List = []
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def add(cls, component: object) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Register a component. Duplicate types are ignored.
|
|
19
|
+
:param component: The component instance to register.
|
|
20
|
+
"""
|
|
21
|
+
if not any(type(c) == type(component) for c in cls._components):
|
|
22
|
+
cls._components.append(component)
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def get_components(cls) -> List:
|
|
26
|
+
"""
|
|
27
|
+
Gets all registered components.
|
|
28
|
+
:return: List of registered component instances.
|
|
29
|
+
"""
|
|
30
|
+
return list(cls._components)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AgentTelemetryClient:
|
|
6
|
+
"""
|
|
7
|
+
Interface for telemetry logging. Override to send telemetry to a custom sink.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def track_event(
|
|
11
|
+
self,
|
|
12
|
+
name: str,
|
|
13
|
+
properties: dict[str, str] | None = None,
|
|
14
|
+
metrics: dict[str, float] | None = None,
|
|
15
|
+
) -> None:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
def track_exception(
|
|
19
|
+
self,
|
|
20
|
+
exception: Exception,
|
|
21
|
+
properties: dict[str, str] | None = None,
|
|
22
|
+
measurements: dict[str, float] | None = None,
|
|
23
|
+
) -> None:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def track_dependency(
|
|
27
|
+
self,
|
|
28
|
+
name: str,
|
|
29
|
+
data: str | None = None,
|
|
30
|
+
type_name: str | None = None,
|
|
31
|
+
target: str | None = None,
|
|
32
|
+
duration: int | None = None,
|
|
33
|
+
success: bool = True,
|
|
34
|
+
result_code: str | None = None,
|
|
35
|
+
properties: dict[str, str] | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
def flush(self) -> None:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class NullTelemetryClient(AgentTelemetryClient):
|
|
44
|
+
"""
|
|
45
|
+
No-op telemetry client. All calls are silently discarded.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def track_event(
|
|
49
|
+
self,
|
|
50
|
+
name: str,
|
|
51
|
+
properties: dict[str, str] | None = None,
|
|
52
|
+
metrics: dict[str, float] | None = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
def track_exception(
|
|
57
|
+
self,
|
|
58
|
+
exception: Exception,
|
|
59
|
+
properties: dict[str, str] | None = None,
|
|
60
|
+
measurements: dict[str, float] | None = None,
|
|
61
|
+
) -> None:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
def track_dependency(
|
|
65
|
+
self,
|
|
66
|
+
name: str,
|
|
67
|
+
data: str | None = None,
|
|
68
|
+
type_name: str | None = None,
|
|
69
|
+
target: str | None = None,
|
|
70
|
+
duration: int | None = None,
|
|
71
|
+
success: bool = True,
|
|
72
|
+
result_code: str | None = None,
|
|
73
|
+
properties: dict[str, str] | None = None,
|
|
74
|
+
) -> None:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
def flush(self) -> None:
|
|
78
|
+
pass
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
# --------------------------------------------------------------------------
|
|
3
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
4
|
+
# Licensed under the MIT License. See License.txt in the project root for
|
|
5
|
+
# license information.
|
|
6
|
+
# --------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
from .channel import Channel
|
|
9
|
+
from .models.choice import Choice
|
|
10
|
+
from .models.choice_factory_options import ChoiceFactoryOptions
|
|
11
|
+
from .choice_factory import ChoiceFactory
|
|
12
|
+
from .choice_recognizer import ChoiceRecognizers
|
|
13
|
+
from .find import Find
|
|
14
|
+
from .models.find_choices_options import FindChoicesOptions, FindValuesOptions
|
|
15
|
+
from .models.found_choice import FoundChoice
|
|
16
|
+
from .models.found_value import FoundValue
|
|
17
|
+
from .models.list_style import ListStyle
|
|
18
|
+
from .models.model_result import ModelResult
|
|
19
|
+
from .models.sorted_value import SortedValue
|
|
20
|
+
from .models.token import Token
|
|
21
|
+
from .tokenizer import Tokenizer
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"Channel",
|
|
25
|
+
"Choice",
|
|
26
|
+
"ChoiceFactory",
|
|
27
|
+
"ChoiceFactoryOptions",
|
|
28
|
+
"ChoiceRecognizers",
|
|
29
|
+
"Find",
|
|
30
|
+
"FindChoicesOptions",
|
|
31
|
+
"FindValuesOptions",
|
|
32
|
+
"FoundChoice",
|
|
33
|
+
"ListStyle",
|
|
34
|
+
"ModelResult",
|
|
35
|
+
"SortedValue",
|
|
36
|
+
"Token",
|
|
37
|
+
"Tokenizer",
|
|
38
|
+
]
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from microsoft_agents.hosting.core import TurnContext
|
|
5
|
+
from microsoft_agents.activity import Channels
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Channel:
|
|
9
|
+
"""
|
|
10
|
+
Methods for determining channel-specific functionality.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def supports_suggested_actions(channel_id: str, button_cnt: int = 100) -> bool:
|
|
15
|
+
"""Determine if a number of Suggested Actions are supported by a Channel.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
channel_id (str): The Channel to check the if Suggested Actions are supported in.
|
|
19
|
+
button_cnt (int, optional): Defaults to 100. The number of Suggested Actions to check for the Channel.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
bool: True if the Channel supports the button_cnt total Suggested Actions, False if the Channel does not
|
|
23
|
+
support that number of Suggested Actions.
|
|
24
|
+
"""
|
|
25
|
+
if isinstance(channel_id, Channels):
|
|
26
|
+
channel_id = channel_id.value
|
|
27
|
+
|
|
28
|
+
max_actions = {
|
|
29
|
+
# https://developers.facebook.com/docs/messenger-platform/send-messages/quick-replies
|
|
30
|
+
Channels.facebook.value: 10,
|
|
31
|
+
Channels.skype.value: 10,
|
|
32
|
+
# https://developers.line.biz/en/reference/messaging-api/#items-object
|
|
33
|
+
Channels.line.value: 13,
|
|
34
|
+
# https://dev.kik.com/#/docs/messaging#text-response-object
|
|
35
|
+
Channels.kik.value: 20,
|
|
36
|
+
Channels.telegram.value: 100,
|
|
37
|
+
Channels.emulator.value: 100,
|
|
38
|
+
Channels.direct_line.value: 100,
|
|
39
|
+
Channels.direct_line_speech.value: 100,
|
|
40
|
+
Channels.webchat.value: 100,
|
|
41
|
+
}
|
|
42
|
+
return (
|
|
43
|
+
button_cnt <= max_actions[channel_id]
|
|
44
|
+
if channel_id in max_actions
|
|
45
|
+
else False
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def supports_card_actions(channel_id: str, button_cnt: int = 100) -> bool:
|
|
50
|
+
"""Determine if a number of Card Actions are supported by a Channel.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
channel_id (str): The Channel to check if the Card Actions are supported in.
|
|
54
|
+
button_cnt (int, optional): Defaults to 100. The number of Card Actions to check for the Channel.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
bool: True if the Channel supports the button_cnt total Card Actions, False if the Channel does not support
|
|
58
|
+
that number of Card Actions.
|
|
59
|
+
"""
|
|
60
|
+
if isinstance(channel_id, Channels):
|
|
61
|
+
channel_id = channel_id.value
|
|
62
|
+
|
|
63
|
+
max_actions = {
|
|
64
|
+
Channels.facebook.value: 3,
|
|
65
|
+
Channels.skype.value: 3,
|
|
66
|
+
Channels.ms_teams.value: 3,
|
|
67
|
+
Channels.line.value: 99,
|
|
68
|
+
Channels.slack.value: 100,
|
|
69
|
+
Channels.telegram.value: 100,
|
|
70
|
+
Channels.emulator.value: 100,
|
|
71
|
+
Channels.direct_line.value: 100,
|
|
72
|
+
Channels.direct_line_speech.value: 100,
|
|
73
|
+
Channels.webchat.value: 100,
|
|
74
|
+
}
|
|
75
|
+
return (
|
|
76
|
+
button_cnt <= max_actions[channel_id]
|
|
77
|
+
if channel_id in max_actions
|
|
78
|
+
else False
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def has_message_feed(_: str) -> bool:
|
|
83
|
+
"""Determine if a Channel has a Message Feed.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
channel_id (str): The Channel to check for Message Feed.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
bool: True if the Channel has a Message Feed, False if it does not.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def get_channel_id(turn_context: TurnContext) -> str:
|
|
96
|
+
"""Get the channel ID from the TurnContext's activity.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
turn_context (TurnContext): The current turn context.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
str: The channel ID, or an empty string if not set.
|
|
103
|
+
"""
|
|
104
|
+
if turn_context.activity and turn_context.activity.channel_id:
|
|
105
|
+
return turn_context.activity.channel_id
|
|
106
|
+
return ""
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def max_action_title_length( # pylint: disable=unused-argument
|
|
110
|
+
channel_id: str,
|
|
111
|
+
) -> int:
|
|
112
|
+
"""Maximum length allowed for Action Titles.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
channel_id (str): The Channel to determine Maximum Action Title Length.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
int: The total number of characters allowed for an Action Title on a specific Channel.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
return 20
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from collections.abc import Iterable
|
|
5
|
+
|
|
6
|
+
from microsoft_agents.hosting.core import CardFactory, MessageFactory
|
|
7
|
+
from microsoft_agents.activity import (
|
|
8
|
+
ActionTypes,
|
|
9
|
+
Activity,
|
|
10
|
+
CardAction,
|
|
11
|
+
HeroCard,
|
|
12
|
+
InputHints,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from . import Channel, Choice, ChoiceFactoryOptions
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ChoiceFactory:
|
|
19
|
+
"""
|
|
20
|
+
Assists with formatting a message activity that contains a list of choices.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def for_channel(
|
|
25
|
+
channel_id: str,
|
|
26
|
+
choices: Iterable[str | Choice],
|
|
27
|
+
text: str | None = None,
|
|
28
|
+
speak: str | None = None,
|
|
29
|
+
options: ChoiceFactoryOptions | None = None,
|
|
30
|
+
) -> Activity:
|
|
31
|
+
"""
|
|
32
|
+
Creates a message activity that includes a list of choices formatted based on the
|
|
33
|
+
capabilities of a given channel.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
channel_id: A channel ID.
|
|
38
|
+
choices: List of choices to render
|
|
39
|
+
text: (Optional) Text of the message to send.
|
|
40
|
+
speak (Optional) SSML. Text to be spoken by your bot on a speech-enabled channel.
|
|
41
|
+
"""
|
|
42
|
+
if channel_id is None:
|
|
43
|
+
channel_id = ""
|
|
44
|
+
|
|
45
|
+
choice_list: list[Choice] = ChoiceFactory._to_choices(choices)
|
|
46
|
+
|
|
47
|
+
# Find maximum title length
|
|
48
|
+
max_title_length = 0
|
|
49
|
+
for choice in choice_list:
|
|
50
|
+
if choice.action is not None and choice.action.title not in (None, ""):
|
|
51
|
+
size = len(choice.action.title)
|
|
52
|
+
else:
|
|
53
|
+
size = len(choice.value) if choice.value is not None else 0
|
|
54
|
+
|
|
55
|
+
max_title_length = max(max_title_length, size)
|
|
56
|
+
|
|
57
|
+
# Determine list style
|
|
58
|
+
supports_suggested_actions = Channel.supports_suggested_actions(
|
|
59
|
+
channel_id, len(choice_list)
|
|
60
|
+
)
|
|
61
|
+
supports_card_actions = Channel.supports_card_actions(
|
|
62
|
+
channel_id, len(choice_list)
|
|
63
|
+
)
|
|
64
|
+
max_action_title_length = Channel.max_action_title_length(channel_id)
|
|
65
|
+
long_titles = max_title_length > max_action_title_length
|
|
66
|
+
|
|
67
|
+
if not long_titles and not supports_suggested_actions and supports_card_actions:
|
|
68
|
+
# SuggestedActions is the preferred approach, but for channels that don't
|
|
69
|
+
# support them (e.g. Teams, Cortana) we should use a HeroCard with CardActions
|
|
70
|
+
return ChoiceFactory.hero_card(choice_list, text, speak)
|
|
71
|
+
if not long_titles and supports_suggested_actions:
|
|
72
|
+
# We always prefer showing choices using suggested actions. If the titles are too long, however,
|
|
73
|
+
# we'll have to show them as a text list.
|
|
74
|
+
return ChoiceFactory.suggested_action(choice_list, text, speak)
|
|
75
|
+
if not long_titles and len(choice_list) <= 3:
|
|
76
|
+
# If the titles are short and there are 3 or less choices we'll use an inline list.
|
|
77
|
+
return ChoiceFactory.inline(choice_list, text, speak, options)
|
|
78
|
+
# Show a numbered list.
|
|
79
|
+
return ChoiceFactory.list_style(choice_list, text, speak, options)
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def inline(
|
|
83
|
+
choices: Iterable[str | Choice],
|
|
84
|
+
text: str | None = None,
|
|
85
|
+
speak: str | None = None,
|
|
86
|
+
options: ChoiceFactoryOptions | None = None,
|
|
87
|
+
) -> Activity:
|
|
88
|
+
"""
|
|
89
|
+
Creates a message activity that includes a list of choices formatted as an inline list.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
choices: The list of choices to render.
|
|
94
|
+
text: (Optional) The text of the message to send.
|
|
95
|
+
speak: (Optional) SSML. Text to be spoken by your bot on a speech-enabled channel.
|
|
96
|
+
options: (Optional) The formatting options to use to tweak rendering of list.
|
|
97
|
+
"""
|
|
98
|
+
choice_list = ChoiceFactory._to_choices(choices)
|
|
99
|
+
|
|
100
|
+
if options is None:
|
|
101
|
+
options = ChoiceFactoryOptions()
|
|
102
|
+
|
|
103
|
+
opt = ChoiceFactoryOptions(
|
|
104
|
+
inline_separator=options.inline_separator or ", ",
|
|
105
|
+
inline_or=options.inline_or or " or ",
|
|
106
|
+
inline_or_more=options.inline_or_more or ", or ",
|
|
107
|
+
include_numbers=(
|
|
108
|
+
options.include_numbers if options.include_numbers is not None else True
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Format list of choices
|
|
113
|
+
connector = ""
|
|
114
|
+
txt_builder: list[str] = []
|
|
115
|
+
if text is not None:
|
|
116
|
+
txt_builder.append(text)
|
|
117
|
+
txt_builder.append(" ")
|
|
118
|
+
|
|
119
|
+
for index, choice in enumerate(choice_list):
|
|
120
|
+
title = (
|
|
121
|
+
choice.action.title
|
|
122
|
+
if (choice.action is not None and choice.action.title is not None)
|
|
123
|
+
else choice.value
|
|
124
|
+
)
|
|
125
|
+
txt_builder.append(connector)
|
|
126
|
+
if opt.include_numbers is True:
|
|
127
|
+
txt_builder.append("(")
|
|
128
|
+
txt_builder.append(f"{index + 1}")
|
|
129
|
+
txt_builder.append(") ")
|
|
130
|
+
|
|
131
|
+
txt_builder.append(title or "title")
|
|
132
|
+
if index == (len(choice_list) - 2):
|
|
133
|
+
connector = opt.inline_or if index == 0 else opt.inline_or_more
|
|
134
|
+
connector = connector or ""
|
|
135
|
+
else:
|
|
136
|
+
connector = opt.inline_separator or ""
|
|
137
|
+
|
|
138
|
+
# Return activity with choices as an inline list.
|
|
139
|
+
return MessageFactory.text(
|
|
140
|
+
"".join(txt_builder), speak, InputHints.expecting_input
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def list_style(
|
|
145
|
+
choices: Iterable[str | Choice],
|
|
146
|
+
text: str | None = None,
|
|
147
|
+
speak: str | None = None,
|
|
148
|
+
options: ChoiceFactoryOptions | None = None,
|
|
149
|
+
) -> Activity:
|
|
150
|
+
"""
|
|
151
|
+
Creates a message activity that includes a list of choices formatted as a numbered or bulleted list.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
|
|
156
|
+
choices: The list of choices to render.
|
|
157
|
+
|
|
158
|
+
text: (Optional) The text of the message to send.
|
|
159
|
+
|
|
160
|
+
speak: (Optional) SSML. Text to be spoken by your bot on a speech-enabled channel.
|
|
161
|
+
|
|
162
|
+
options: (Optional) The formatting options to use to tweak rendering of list.
|
|
163
|
+
"""
|
|
164
|
+
choices = ChoiceFactory._to_choices(choices)
|
|
165
|
+
if options is None:
|
|
166
|
+
options = ChoiceFactoryOptions()
|
|
167
|
+
|
|
168
|
+
if options.include_numbers is None:
|
|
169
|
+
include_numbers = True
|
|
170
|
+
else:
|
|
171
|
+
include_numbers = options.include_numbers
|
|
172
|
+
|
|
173
|
+
# Format list of choices
|
|
174
|
+
connector = ""
|
|
175
|
+
txt_builder: list[str] = []
|
|
176
|
+
if text:
|
|
177
|
+
txt_builder.append(text)
|
|
178
|
+
txt_builder.append("\n\n ")
|
|
179
|
+
|
|
180
|
+
for index, choice in enumerate(choices):
|
|
181
|
+
title = (
|
|
182
|
+
choice.action.title
|
|
183
|
+
if choice.action is not None and choice.action.title is not None
|
|
184
|
+
else choice.value
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
txt_builder.append(connector)
|
|
188
|
+
if include_numbers:
|
|
189
|
+
txt_builder.append(f"{index + 1}")
|
|
190
|
+
txt_builder.append(". ")
|
|
191
|
+
else:
|
|
192
|
+
txt_builder.append("- ")
|
|
193
|
+
|
|
194
|
+
txt_builder.append(title)
|
|
195
|
+
connector = "\n "
|
|
196
|
+
|
|
197
|
+
# Return activity with choices as a numbered list.
|
|
198
|
+
txt = "".join(txt_builder)
|
|
199
|
+
return MessageFactory.text(txt, speak, InputHints.expecting_input)
|
|
200
|
+
|
|
201
|
+
@staticmethod
|
|
202
|
+
def suggested_action(
|
|
203
|
+
choices: Iterable[Choice], text: str | None = None, speak: str | None = None
|
|
204
|
+
) -> Activity:
|
|
205
|
+
"""
|
|
206
|
+
Creates a message activity that includes a list of choices that have been added as suggested actions.
|
|
207
|
+
"""
|
|
208
|
+
# Return activity with choices as suggested actions
|
|
209
|
+
return MessageFactory.suggested_actions(
|
|
210
|
+
ChoiceFactory._extract_actions(choices),
|
|
211
|
+
text,
|
|
212
|
+
speak,
|
|
213
|
+
InputHints.expecting_input,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
@staticmethod
|
|
217
|
+
def hero_card(
|
|
218
|
+
choices: Iterable[Choice | str],
|
|
219
|
+
text: str | None = None,
|
|
220
|
+
speak: str | None = None,
|
|
221
|
+
) -> Activity:
|
|
222
|
+
"""
|
|
223
|
+
Creates a message activity that includes a lsit of coices that have been added as `HeroCard`'s
|
|
224
|
+
"""
|
|
225
|
+
attachment = CardFactory.hero_card(
|
|
226
|
+
HeroCard(text=text or "", buttons=ChoiceFactory._extract_actions(choices))
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Return activity with choices as HeroCard with buttons
|
|
230
|
+
return MessageFactory.attachment(
|
|
231
|
+
attachment, None, speak, InputHints.expecting_input
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
@staticmethod
|
|
235
|
+
def _to_choices(choices: Iterable[str | Choice]) -> list[Choice]:
|
|
236
|
+
"""
|
|
237
|
+
Takes a list of strings and returns them as [`Choice`].
|
|
238
|
+
"""
|
|
239
|
+
if choices is None:
|
|
240
|
+
return []
|
|
241
|
+
return [
|
|
242
|
+
Choice(value=choice) if isinstance(choice, str) else choice
|
|
243
|
+
for choice in choices
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
@staticmethod
|
|
247
|
+
def _extract_actions(choices: Iterable[str | Choice]) -> list[CardAction]:
|
|
248
|
+
if choices is None:
|
|
249
|
+
choices = []
|
|
250
|
+
choices = ChoiceFactory._to_choices(choices)
|
|
251
|
+
card_actions: list[CardAction] = []
|
|
252
|
+
for choice in choices:
|
|
253
|
+
if choice.action is not None:
|
|
254
|
+
card_action = choice.action
|
|
255
|
+
else:
|
|
256
|
+
card_action = CardAction(
|
|
257
|
+
type=ActionTypes.im_back, value=choice.value, title=choice.value
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
card_actions.append(card_action)
|
|
261
|
+
|
|
262
|
+
return card_actions
|