diff --git a/samples/08.suggested-actions/README.md b/samples/08.suggested-actions/README.md new file mode 100644 index 000000000..4e0e76ebb --- /dev/null +++ b/samples/08.suggested-actions/README.md @@ -0,0 +1,28 @@ +# suggested actions + +Bot Framework v4 using adaptive cards bot sample + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to use suggested actions. Suggested actions enable your bot to present buttons that the user can tap to provide input. + +## Running the sample +- Clone the repository +```bash +git clone https://github.com/Microsoft/botbuilder-python.git +``` +- Bring up a terminal, navigate to `botbuilder-python\samples\08.suggested-actions` 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 + +## Suggested actions + +Suggested actions enable your bot to present buttons that the user can tap to provide input. Suggested actions appear close to the composer and enhance user experience. diff --git a/samples/08.suggested-actions/app.py b/samples/08.suggested-actions/app.py new file mode 100644 index 000000000..4e9403486 --- /dev/null +++ b/samples/08.suggested-actions/app.py @@ -0,0 +1,84 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import asyncio +import sys +from datetime import datetime +from types import MethodType + +from flask import Flask, request, Response +from botbuilder.core import BotFrameworkAdapterSettings, BotFrameworkAdapter, TurnContext +from botbuilder.schema import Activity, ActivityTypes + +from bots import SuggestActionsBot + +# Create the loop and Flask app +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(self, 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) + +ADAPTER.on_turn_error = MethodType(on_error, ADAPTER) + +# Create Bot +BOT = SuggestActionsBot() + + +# 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 diff --git a/samples/08.suggested-actions/bots/__init__.py b/samples/08.suggested-actions/bots/__init__.py new file mode 100644 index 000000000..cbf771a32 --- /dev/null +++ b/samples/08.suggested-actions/bots/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .suggested_actions_bot import SuggestActionsBot + +__all__ = ["SuggestActionsBot"] diff --git a/samples/08.suggested-actions/bots/suggested_actions_bot.py b/samples/08.suggested-actions/bots/suggested_actions_bot.py new file mode 100644 index 000000000..5bee547be --- /dev/null +++ b/samples/08.suggested-actions/bots/suggested_actions_bot.py @@ -0,0 +1,88 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.core import ActivityHandler, MessageFactory, TurnContext +from botbuilder.schema import ChannelAccount, CardAction, ActionTypes, SuggestedActions + +""" +This bot will respond to the user's input with suggested actions. +Suggested actions enable your bot to present buttons that the user +can tap to provide input. +""" + + +class SuggestActionsBot(ActivityHandler): + async def on_members_added_activity(self, members_added: [ChannelAccount], turn_context: TurnContext): + """ + Send a welcome message to the user and tell them what actions they may perform to use this bot + """ + + return await self._send_welcome_message(turn_context) + + async def on_message_activity(self, turn_context: TurnContext): + """ + Respond to the users choice and display the suggested actions again. + """ + + text = turn_context.activity.text.lower() + response_text = self._process_input(text) + + await turn_context.send_activity(MessageFactory.text(response_text)) + + return await self._send_suggested_actions(turn_context) + + async def _send_welcome_message(self, turn_context: TurnContext): + for member in turn_context.activity.members_added: + if member.id != turn_context.activity.recipient.id: + await turn_context.send_activity(MessageFactory.text( + f"Welcome to SuggestedActionsBot {member.name}. This bot will introduce you to suggestedActions. " + f"Please answer the question: " + )) + + await self._send_suggested_actions(turn_context) + + def _process_input(self, text: str): + color_text = "is the best color, I agree." + + if text == "red": + return f"Red {color_text}" + + if text == "yellow": + return f"Yellow {color_text}" + + if text == "blue": + return f"Blue {color_text}" + + return "Please select a color from the suggested action choices" + + async def _send_suggested_actions(self, turn_context: TurnContext): + """ + Creates and sends an activity with suggested actions to the user. When the user + clicks one of the buttons the text value from the "CardAction" will be displayed + in the channel just as if the user entered the text. There are multiple + "ActionTypes" that may be used for different situations. + """ + + reply = MessageFactory.text("What is your favorite color?") + + reply.suggested_actions = SuggestedActions( + actions=[ + CardAction( + title="Red", + type=ActionTypes.im_back, + value="Read" + ), + CardAction( + title="Yellow", + type=ActionTypes.im_back, + value="Yellow" + ), + CardAction( + title="Blue", + type=ActionTypes.im_back, + value="Blue" + ) + ] + ) + + return await turn_context.send_activity(reply) diff --git a/samples/08.suggested-actions/config.py b/samples/08.suggested-actions/config.py new file mode 100644 index 000000000..e007d0fa9 --- /dev/null +++ b/samples/08.suggested-actions/config.py @@ -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", "") diff --git a/samples/08.suggested-actions/requirements.txt b/samples/08.suggested-actions/requirements.txt new file mode 100644 index 000000000..7e54b62ec --- /dev/null +++ b/samples/08.suggested-actions/requirements.txt @@ -0,0 +1,2 @@ +botbuilder-core>=4.4.0b1 +flask>=1.0.3