8000 Merge pull request #384 from microsoft/trboehre-43.complex-dialog · snifhex/botbuilder-python@3b74bb2 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3b74bb2

Browse files
authored
Merge pull request microsoft#384 from microsoft/trboehre-43.complex-dialog
Added 43.complex-dialog
2 parents 8646e74 + 3639c76 commit 3b74bb2

File tree

15 files changed

+516
-0
lines changed

15 files changed

+516
-0
lines changed

samples/43.complex-dialog/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Complex dialog sample
2+
3+
This sample creates a complex conversation with dialogs.
4+
5+
## Running the sample
6+
- Clone the repository
7+
```bash
8+
git clone https://github.com/Microsoft/botbuilder-python.git
9+
```
10+
- Activate your desired virtual environment
11+
- Bring up a terminal, navigate to `botbuilder-python\samples\43.complex-dialog` folder
12+
- In the terminal, type `pip install -r requirements.txt`
13+
- In the terminal, type `python app.py`
14+
15+
## Testing the bot using Bot Framework Emulator
16+
[Microsoft Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel.
17+
18+
- Install the Bot Framework emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases)
19+
20+
### Connect to bot using Bot Framework Emulator
21+
- Launch Bot Framework Emulator
22+
- File -> Open Bot
23+
- Paste this URL in the emulator window - http://localhost:3978/api/messages
24+
25+
26+
# Further reading
27+
28+
- [Bot Framework Documentation](https://docs.botframework.com)
29+
- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0)
30+
- [Dialogs](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0)

samples/43.complex-dialog/app.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import asyncio
5+
import sys
6+
from datetime import datetime
7+
8+
from flask import Flask, request, Response
9+
from botbuilder.core import (
10+
BotFrameworkAdapter,
11+
BotFrameworkAdapterSettings,
12+
ConversationState,
13+
MemoryStorage,
14+
TurnContext,
15+
UserState,
16+
)
17+
from botbuilder.schema import Activity, ActivityTypes
18+
19+
from bots import DialogAndWelcomeBot
20+
21+
# Create the loop and Flask app
22+
from dialogs import MainDialog
23+
24+
LOOP = asyncio.get_event_loop()
25+
APP = Flask(__name__, instance_relative_config=True)
26+
APP.config.from_object("config.DefaultConfig")
27+
28+
# Create adapter.
29+
# See https://aka.ms/about-bot-adapter to learn more about how bots work.
30+
SETTINGS = BotFrameworkAdapterSettings(APP.config["APP_ID"], APP.config["APP_PASSWORD"])
31+
ADAPTER = BotFrameworkAdapter(SETTINGS)
32+
33+
34+
# Catch-all for errors.
35+
async def on_error(context: TurnContext, error: Exception):
36+
# This check writes out errors to console log .vs. app insights.
37+
# NOTE: In production environment, you should consider logging this to Azure
38+
# application insights.
39+
print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
40+
41+
# Send a message to the user
42+
await context.send_activity("The bot encountered an error or bug.")
43+
await context.send_activity("To continue to run this bot, please fix the bot source code.")
44+
# Send a trace activity if we're talking to the Bot Framework Emulator
45+
if context.activity.channel_id == 'emulator':
46+
# Create a trace activity that contains the error object
47+
trace_activity = Activity(
48+
label="TurnError",
49+
name="on_turn_error Trace",
50+
timestamp=datetime.utcnow(),
51+
type=ActivityTypes.trace,
52+
value=f"{error}",
53+
value_type="https://www.botframework.com/schemas/error"
54+
)
55+
# Send a trace activity, which will be displayed in Bot Framework Emulator
56+
await context.send_activity(trace_activity)
57+
58+
# Clear out state
59+
await CONVERSATION_STATE.delete(context)
60+
61+
# Set the error handler on the Adapter.
62+
# In this case, we want an unbound function, so MethodType is not needed.
63+
ADAPTER.on_turn_error = on_error
64+
65+
# Create MemoryStorage and state
66+
MEMORY = MemoryStorage()
67+
USER_STATE = UserState(MEMORY)
68+
CONVERSATION_STATE = ConversationState(MEMORY)
69+
70+
# Create Dialog and Bot
71+
DIALOG = MainDialog(USER_STATE)
72+
BOT = DialogAndWelcomeBot(CONVERSATION_STATE, USER_STATE, DIALOG)
73+
74+
75+
# Listen for incoming requests on /api/messages.
76+
@APP.route("/api/messages", methods=["POST"])
77+
def messages():
78+
# Main bot message handler.
79+
if "application/json" in request.headers["Content-Type"]:
80+
body = request.json
81+
else:
82+
return Response(status=415)
83+
84+
activity = Activity().deserialize(body)
85+
auth_header = (
86+
request.headers["Authorization"] if "Authorization" in request.headers else ""
87+
)
88+
89+
try:
90+
task = LOOP.create_task(
91+
ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
92+
)
93+
LOOP.run_until_complete(task)
94+
return Response(status=201)
95+
except Exception as exception:
96+
raise exception
97+
98+
99+
if __name__ == "__main__":
100+
try:
101+
APP.run(debug=False, port=APP.config["PORT"]) # nosec debug
102+
except Exception as exception:
103+
raise exception
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from .dialog_bot import DialogBot
5+
from .dialog_and_welcome_bot import DialogAndWelcomeBot
6+
7+
__all__ = ["DialogBot", "DialogAndWelcomeBot"]
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from typing import List
5+
from botbuilder.core import (
6+
ConversationState,
7+
MessageFactory,
8+
UserState,
9+
TurnContext,
10+
)
11+
from botbuilder.dialogs import Dialog
12+
from botbuilder.schema import ChannelAccount
13+
14+
from .dialog_bot import DialogBot
15+
16+
17+
class DialogAndWelcomeBot(DialogBot):
18+
def __init__(
19+
self,
20+
conversation_state: ConversationState,
21+
user_state: UserState,
22+
dialog: Dialog,
23+
):
24+
super(DialogAndWelcomeBot, self).__init__(
25+
conversation_state, user_state, dialog
26+
)
27+
28+
async def on_members_added_activity(
29+
self, members_added: List[ChannelAccount], turn_context: TurnContext
30+
):
31+
for member in members_added:
32+
# Greet anyone that was not the target (recipient) of this message.
33+
if member.id != turn_context.activity.recipient.id:
34+
await turn_context.send_activity(MessageFactory.text(
35+
f"Welcome to Complex Dialog Bot {member.name}. This bot provides a complex conversation, with "
36+
f"multiple dialogs. Type anything to get started. ")
37+
)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from botbuilder.core import ActivityHandler, ConversationState, UserState, TurnContext
5+
from botbuilder.dialogs import Dialog
6+
from helpers.dialog_helper import DialogHelper
7+
8+
9+
class DialogBot(ActivityHandler):
10+
def __init__(
11+
self,
12+
conversation_state: ConversationState,
13+
user_state: UserState,
14+
dialog: Dialog,
15+
):
16+
if conversation_state is None:
17+
raise Exception(
18+
"[DialogBot]: Missing parameter. conversation_state is required"
19+
)
20+
if user_state is None:
21+
raise Exception("[DialogBot]: Missing parameter. user_state is required")
22+
if dialog is None:
23+
raise Exception("[DialogBot]: Missing parameter. dialog is required")
24+
25+
self.conversation_state = conversation_state
26+
self.user_state = user_state
27+
self.dialog = dialog
28+
29+
async def on_turn(self, turn_context: TurnContext):
30+
await super().on_turn(turn_context)
31+
32+
# Save any state changes that might have occurred during the turn.
33+
await self.conversation_state.save_changes(turn_context, False)
34+
await self.user_state.save_changes(turn_context, False)
35+
36+
async def on_message_activity(self, turn_context: TurnContext):
37+
await DialogHelper.run_dialog(
38+
self.dialog,
39+
turn_context,
40+
self.conversation_state.create_property("DialogState"),
41+
)

samples/43.complex-dialog/config.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License.
4+
5+
import os
6+
7+
""" Bot Configuration """
8+
9+
10+
class DefaultConfig:
11+
""" Bot Configuration """
12+
13+
PORT = 3978
14+
APP_ID = os.environ.get("MicrosoftAppId", "")
15+
APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "")
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from .user_profile import UserProfile
5+
6+
__all__ = ["UserProfile"]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from typing import List
5+
6+
7+
class UserProfile:
8+
def __init__(self, name: str = None, age: int = 0, companies_to_review: List[str] = None):
9+
self.name: str = name
10+
self.age: int = age
11+
self.companies_to_review: List[str] = companies_to_review
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from .main_dialog import MainDialog
5+
from .review_selection_dialog import ReviewSelectionDialog
6+
from .top_level_dialog import TopLevelDialog
7+
8+
__all__ = ["MainDialog", "ReviewSelectionDialog", "TopLevelDialog"]
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from botbuilder.dialogs import (
5+
ComponentDialog,
6+
WaterfallDialog,
7+
WaterfallStepContext,
8+
DialogTurnResult,
9+
)
10+
from botbuilder.core import MessageFactory, UserState
11+
12+
from data_models import UserProfile
13+
from dialogs.top_level_dialog import TopLevelDialog
14+
15+
16+
class MainDialog(ComponentDialog):
17+
def __init__(
18+
self, user_state: UserState
19+
):
20+
super(MainDialog, self).__init__(MainDialog.__name__)
21+
22+
self.user_state = user_state
23+
24+
self.add_dialog(TopLevelDialog(TopLevelDialog.__name__))
25+
self.add_dialog(
26+
WaterfallDialog(
27+
"WFDialog", [
28+
self.initial_step,
29+
self.final_step
30+
]
31+
)
32+
)
33+
34+
self.initial_dialog_id = "WFDialog"
35+
36+
async def initial_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
37+
return await step_context.begin_dialog(TopLevelDialog.__name__)
38+
39+
async def final_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
40+
user_info: UserProfile = step_context.result
41+
42+
companies = "no companies" if len(user_info.companies_to_review) == 0 else " and ".join(user_info.companies_to_review)
43+
status = f"You are signed up to review {companies}."
44+
45+
await step_context.context.send_activity(MessageFactory.text(status))
46+
47+
# store the UserProfile
48+
accessor = self.user_state.create_property("UserProfile")
49+
await accessor.set(step_context.context, user_info)
50+
51+
return await step_context.end_dialog()
52+

0 commit comments

Comments
 (0)
0