Skip to content

Added 43.complex-dialog #384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions samples/43.complex-dialog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Complex dialog sample

This sample creates a complex conversation with dialogs.

## Running the sample
- Clone the repository
```bash
git clone https://github.com/Microsoft/botbuilder-python.git
```
- Activate your desired virtual environment
- Bring up a terminal, navigate to `botbuilder-python\samples\43.complex-dialog` folder
- In the terminal, type `pip install -r requirements.txt`
- In the terminal, type `python app.py`

## Testing the bot using Bot Framework Emulator
[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.

- Install the Bot Framework emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases)

### Connect to bot using Bot Framework Emulator
- Launch Bot Framework Emulator
- File -> Open Bot
- Paste this URL in the emulator window - http://localhost:3978/api/messages


# Further reading

- [Bot Framework Documentation](https://docs.botframework.com)
- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0)
- [Dialogs](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0)
103 changes: 103 additions & 0 deletions samples/43.complex-dialog/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import asyncio
import sys
from datetime import datetime

from flask import Flask, request, Response
from botbuilder.core import (
BotFrameworkAdapter,
BotFrameworkAdapterSettings,
ConversationState,
MemoryStorage,
TurnContext,
UserState,
)
from botbuilder.schema import Activity, ActivityTypes

from bots import DialogAndWelcomeBot

# Create the loop and Flask app
from dialogs import MainDialog

LOOP = asyncio.get_event_loop()
APP = Flask(__name__, instance_relative_config=True)
APP.config.from_object("config.DefaultConfig")

# Create adapter.
# See https://aka.ms/about-bot-adapter to learn more about how bots work.
SETTINGS = BotFrameworkAdapterSettings(APP.config["APP_ID"], APP.config["APP_PASSWORD"])
ADAPTER = BotFrameworkAdapter(SETTINGS)


# Catch-all for errors.
async def on_error(context: TurnContext, error: Exception):
# This check writes out errors to console log .vs. app insights.
# NOTE: In production environment, you should consider logging this to Azure
# application insights.
print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)

# Send a message to the user
await context.send_activity("The bot encountered an error or bug.")
await context.send_activity("To continue to run this bot, please fix the bot source code.")
# Send a trace activity if we're talking to the Bot Framework Emulator
if context.activity.channel_id == 'emulator':
# Create a trace activity that contains the error object
trace_activity = Activity(
label="TurnError",
name="on_turn_error Trace",
timestamp=datetime.utcnow(),
type=ActivityTypes.trace,
value=f"{error}",
value_type="https://www.botframework.com/schemas/error"
)
# Send a trace activity, which will be displayed in Bot Framework Emulator
await context.send_activity(trace_activity)

# Clear out state
await CONVERSATION_STATE.delete(context)

# Set the error handler on the Adapter.
# In this case, we want an unbound function, so MethodType is not needed.
ADAPTER.on_turn_error = on_error

# Create MemoryStorage and state
MEMORY = MemoryStorage()
USER_STATE = UserState(MEMORY)
CONVERSATION_STATE = ConversationState(MEMORY)

# Create Dialog and Bot
DIALOG = MainDialog(USER_STATE)
BOT = DialogAndWelcomeBot(CONVERSATION_STATE, USER_STATE, DIALOG)


# Listen for incoming requests on /api/messages.
@APP.route("/api/messages", methods=["POST"])
def messages():
# Main bot message handler.
if "application/json" in request.headers["Content-Type"]:
body = request.json
else:
return Response(status=415)

activity = Activity().deserialize(body)
auth_header = (
request.headers["Authorization"] if "Authorization" in request.headers else ""
)

try:
task = LOOP.create_task(
ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
)
LOOP.run_until_complete(task)
return Response(status=201)
except Exception as exception:
raise exception


if __name__ == "__main__":
try:
APP.run(debug=False, port=APP.config["PORT"]) # nosec debug
except Exception as exception:
raise exception
7 changes: 7 additions & 0 deletions samples/43.complex-dialog/bots/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from .dialog_bot import DialogBot
from .dialog_and_welcome_bot import DialogAndWelcomeBot

__all__ = ["DialogBot", "DialogAndWelcomeBot"]
37 changes: 37 additions & 0 deletions samples/43.complex-dialog/bots/dialog_and_welcome_bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from typing import List
from botbuilder.core import (
ConversationState,
MessageFactory,
UserState,
TurnContext,
)
from botbuilder.dialogs import Dialog
from botbuilder.schema import ChannelAccount

from .dialog_bot import DialogBot


class DialogAndWelcomeBot(DialogBot):
def __init__(
self,
conversation_state: ConversationState,
user_state: UserState,
dialog: Dialog,
):
super(DialogAndWelcomeBot, self).__init__(
conversation_state, user_state, dialog
)

async def on_members_added_activity(
self, members_added: List[ChannelAccount], turn_context: TurnContext
):
for member in members_added:
# Greet anyone that was not the target (recipient) of this message.
if member.id != turn_context.activity.recipient.id:
await turn_context.send_activity(MessageFactory.text(
f"Welcome to Complex Dialog Bot {member.name}. This bot provides a complex conversation, with "
f"multiple dialogs. Type anything to get started. ")
)
41 changes: 41 additions & 0 deletions samples/43.complex-dialog/bots/dialog_bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from botbuilder.core import ActivityHandler, ConversationState, UserState, TurnContext
from botbuilder.dialogs import Dialog
from helpers.dialog_helper import DialogHelper


class DialogBot(ActivityHandler):
def __init__(
self,
conversation_state: ConversationState,
user_state: UserState,
dialog: Dialog,
):
if conversation_state is None:
raise Exception(
"[DialogBot]: Missing parameter. conversation_state is required"
)
if user_state is None:
raise Exception("[DialogBot]: Missing parameter. user_state is required")
if dialog is None:
raise Exception("[DialogBot]: Missing parameter. dialog is required")

self.conversation_state = conversation_state
self.user_state = user_state
self.dialog = dialog

async def on_turn(self, turn_context: TurnContext):
await super().on_turn(turn_context)

# Save any state changes that might have occurred during the turn.
await self.conversation_state.save_changes(turn_context, False)
await self.user_state.save_changes(turn_context, False)

async def on_message_activity(self, turn_context: TurnContext):
await DialogHelper.run_dialog(
self.dialog,
turn_context,
self.conversation_state.create_property("DialogState"),
)
15 changes: 15 additions & 0 deletions samples/43.complex-dialog/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env python3
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import os

""" Bot Configuration """


class DefaultConfig:
""" Bot Configuration """

PORT = 3978
APP_ID = os.environ.get("MicrosoftAppId", "")
APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "")
6 changes: 6 additions & 0 deletions samples/43.complex-dialog/data_models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from .user_profile import UserProfile

__all__ = ["UserProfile"]
11 changes: 11 additions & 0 deletions samples/43.complex-dialog/data_models/user_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from typing import List


class UserProfile:
def __init__(self, name: str = None, age: int = 0, companies_to_review: List[str] = None):
self.name: str = name
self.age: int = age
self.companies_to_review: List[str] = companies_to_review
8 changes: 8 additions & 0 deletions samples/43.complex-dialog/dialogs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from .main_dialog import MainDialog
from .review_selection_dialog import ReviewSelectionDialog
from .top_level_dialog import TopLevelDialog

__all__ = ["MainDialog", "ReviewSelectionDialog", "TopLevelDialog"]
52 changes: 52 additions & 0 deletions samples/43.complex-dialog/dialogs/main_dialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from botbuilder.dialogs import (
ComponentDialog,
WaterfallDialog,
WaterfallStepContext,
DialogTurnResult,
)
from botbuilder.core import MessageFactory, UserState

from data_models import UserProfile
from dialogs.top_level_dialog import TopLevelDialog


class MainDialog(ComponentDialog):
def __init__(
self, user_state: UserState
):
super(MainDialog, self).__init__(MainDialog.__name__)

self.user_state = user_state

self.add_dialog(TopLevelDialog(TopLevelDialog.__name__))
self.add_dialog(
WaterfallDialog(
"WFDialog", [
self.initial_step,
self.final_step
]
)
)

self.initial_dialog_id = "WFDialog"

async def initial_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
return await step_context.begin_dialog(TopLevelDialog.__name__)

async def final_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
user_info: UserProfile = step_context.result

companies = "no companies" if len(user_info.companies_to_review) == 0 else " and ".join(user_info.companies_to_review)
status = f"You are signed up to review {companies}."

await step_context.context.send_activity(MessageFactory.text(status))

# store the UserProfile
accessor = self.user_state.create_property("UserProfile")
await accessor.set(step_context.context, user_info)

return await step_context.end_dialog()

Loading