diff --git a/samples/44.prompt-users-for-input/LICENSE b/samples/44.prompt-users-for-input/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/44.prompt-users-for-input/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/samples/44.prompt-users-for-input/README.md b/samples/44.prompt-users-for-input/README.md new file mode 100644 index 000000000..1a7f0a8e7 --- /dev/null +++ b/samples/44.prompt-users-for-input/README.md @@ -0,0 +1,90 @@ +# Prompt users for input + +Bot Framework v4 Prompt Users for Input bot sample + +This bot has been created using [Bot Framework](https://dev.botframework.com). The bot maintains conversation state to track and direct the conversation and ask the user questions. The bot maintains user state to track the user's answers. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +## To try this sample locally +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\bot-promptusersforinput-sample.jar` + +- Test the bot using Bot Framework Emulator + + [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 version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + + - Connect to the bot using Bot Framework Emulator + + - Launch Bot Framework Emulator + - File -> Open Bot + - Enter a Bot URL of `http://localhost:3978/api/messages` + +## Deploy the bot to Azure + +As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. + +### 1. Login to Azure +From a command (or PowerShell) prompt in the root of the bot folder, execute: +`az login` + +### 2. Set the subscription +`az account set --subscription ""` + +If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. + +### 3. Create an App registration +`az ad app create --display-name "" --password "" --available-to-other-tenants` + +Replace `` and `` with your own values. + +`` is the unique name of your bot. +`` is a minimum 16 character password for your bot. + +Record the `appid` from the returned JSON + +### 4. Create the Azure resources +Replace the values for ``, ``, ``, and `` in the following commands: + +#### To a new Resource Group +`az deployment create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` + +#### To an existing Resource Group +`az group deployment create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` + +### 5. Update app id and password +In src/main/resources/application.properties update + - `MicrosoftAppPassword` with the botsecret value + - `MicrosoftAppId` with the appid from the first step + +### 6. Deploy the code +- Execute `mvn clean package` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` + +If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. + +After the bot is deployed, you only need to execute #6 if you make changes to the bot. + + +## 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/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) +- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) +- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) +- [Spring Boot](https://spring.io/projects/spring-boot) +- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) diff --git a/samples/44.prompt-users-for-input/deploymentTemplates/new-rg-parameters.json b/samples/44.prompt-users-for-input/deploymentTemplates/new-rg-parameters.json new file mode 100644 index 000000000..ead339093 --- /dev/null +++ b/samples/44.prompt-users-for-input/deploymentTemplates/new-rg-parameters.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "value": "" + }, + "groupName": { + "value": "" + }, + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "newAppServicePlanLocation": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/44.prompt-users-for-input/deploymentTemplates/preexisting-rg-parameters.json b/samples/44.prompt-users-for-input/deploymentTemplates/preexisting-rg-parameters.json new file mode 100644 index 000000000..b6f5114fc --- /dev/null +++ b/samples/44.prompt-users-for-input/deploymentTemplates/preexisting-rg-parameters.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "appServicePlanLocation": { + "value": "" + }, + "existingAppServicePlan": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json b/samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..dcd6260a5 --- /dev/null +++ b/samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,191 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "F0", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "resourcesLocation": "[deployment().location]", + "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[variables('effectiveGroupLocation')]", + "properties": { + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new App Service Plan", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('effectivePlanLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "name": "[variables('appServicePlanName')]", + "reserved":true + } + }, + { + "comments": "Create a Web App using the new App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('appServicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} \ No newline at end of file diff --git a/samples/44.prompt-users-for-input/deploymentTemplates/template-with-preexisting-rg.json b/samples/44.prompt-users-for-input/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..b790d2bdc --- /dev/null +++ b/samples/44.prompt-users-for-input/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,158 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", + "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "name": "[variables('servicePlanName')]", + "reserved":true + } + }, + { + "comments": "Create a Web App using an App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2016-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('servicePlanName')]", + "siteConfig": { + "linuxFxVersion": "JAVA|8-jre8", + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} \ No newline at end of file diff --git a/samples/44.prompt-users-for-input/pom.xml b/samples/44.prompt-users-for-input/pom.xml new file mode 100644 index 000000000..0174fbb77 --- /dev/null +++ b/samples/44.prompt-users-for-input/pom.xml @@ -0,0 +1,244 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-promptusersforinput + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains the Prompt Users for Input sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.promptusersforinput.Application + + + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + junit + junit + 4.13.1 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview8 + compile + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview8 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.promptusersforinput.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/Application.java b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/Application.java new file mode 100644 index 000000000..bd12d9ab2 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/Application.java @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.promptusersforinput; + +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; + +/** + * This is the starting point of the Sprint Boot Bot application. + * + * This class also provides overrides for dependency injections. A class that + * extends the {@link com.microsoft.bot.builder.Bot} interface should be + * annotated with @Component. + * + * @see DialogBot + */ +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +public class Application extends BotDependencyConfiguration { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/ConversationFlow.java b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/ConversationFlow.java new file mode 100644 index 000000000..cf9f51702 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/ConversationFlow.java @@ -0,0 +1,32 @@ +package com.microsoft.bot.sample.promptusersforinput; + +public class ConversationFlow { + + private Question lastQuestionAsked = Question.None; + + /** + * Identifies the last question asked. + */ + public enum Question { + Name, + Age, + Date, + None //Our last action did not involved a question. + } + + /** + * Gets the last question asked. + * @return The last question asked. + */ + public Question getLastQuestionAsked() { + return lastQuestionAsked; + } + + /** + * Sets the last question asked. + * @param withLastQuestionAsked the last question asked. + */ + public void setLastQuestionAsked(Question withLastQuestionAsked) { + this.lastQuestionAsked = withLastQuestionAsked; + } +} diff --git a/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/CustomPromptBot.java b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/CustomPromptBot.java new file mode 100644 index 000000000..08320dce6 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/CustomPromptBot.java @@ -0,0 +1,223 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.promptusersforinput; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.dialogs.prompts.PromptCultureModels; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.datetime.DateTimeRecognizer; +import com.microsoft.recognizers.text.number.NumberRecognizer; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Triple; +import org.springframework.stereotype.Component; + +/** + * This Bot implementation can run any type of Dialog. The use of type + * parameterization is to allows multiple different bots to be run at different + * endpoints within the same project. This can be achieved by defining distinct + * Controller types each with dependency on distinct Bot types. + */ +@Component +public class CustomPromptBot extends ActivityHandler { + + private final BotState userState; + private final BotState conversationState; + + public CustomPromptBot(ConversationState conversationState, UserState userState) { + this.conversationState = conversationState; + this.userState = userState; + } + + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + + StatePropertyAccessor conversationStateAccessors = + conversationState.createProperty("ConversationFlow"); + + StatePropertyAccessor userStateAccessors = userState.createProperty("UserProfile"); + return userStateAccessors.get(turnContext, () -> new UserProfile()).thenCompose(profile -> { + return conversationStateAccessors.get(turnContext, () -> new ConversationFlow()).thenCompose(flow -> { + return fillOutUserProfile(flow, profile, turnContext); + }); + }) + .thenCompose(result -> conversationState.saveChanges(turnContext)) + .thenCompose(result -> userState.saveChanges(turnContext)); + } + + private static CompletableFuture fillOutUserProfile(ConversationFlow flow, + UserProfile profile, + TurnContext turnContext) { + String input = ""; + if (StringUtils.isNotBlank(turnContext.getActivity().getText())) { + input = turnContext.getActivity().getText().trim(); + } + + switch (flow.getLastQuestionAsked()) { + case None: + return turnContext.sendActivity("Let's get started. What is your name?", null, null) + .thenRun(() -> {flow.setLastQuestionAsked(ConversationFlow.Question.Name);}); + case Name: + Triple nameValidationResult = validateName(input); + if (nameValidationResult.getLeft()) { + profile.name = nameValidationResult.getMiddle(); + return turnContext.sendActivity(String.format("Hi %s.", profile.name), null, null) + .thenCompose(result -> turnContext.sendActivity("How old are you?", null, null)) + .thenRun(() -> { flow.setLastQuestionAsked(ConversationFlow.Question.Age); }); + } else { + if (StringUtils.isNotBlank(nameValidationResult.getRight())) { + return turnContext.sendActivity(nameValidationResult.getRight(), null, null) + .thenApply(result -> null); + } else { + return turnContext.sendActivity("I'm sorry, I didn't understand that.", null, null) + .thenApply(result -> null); + } + } + case Age: + Triple ageValidationResult = ValidateAge(input); + if (ageValidationResult.getLeft()) { + profile.age = ageValidationResult.getMiddle(); + return turnContext.sendActivity(String.format("I have your age as %d.", profile.age), null, null) + .thenCompose(result -> turnContext.sendActivity("When is your flight?", null, null)) + .thenRun(() -> { flow.setLastQuestionAsked(ConversationFlow.Question.Date); }); + } else { + if (StringUtils.isNotBlank(ageValidationResult.getRight())) { + return turnContext.sendActivity(ageValidationResult.getRight(), null, null) + .thenApply(result -> null); + } else { + return turnContext.sendActivity("I'm sorry, I didn't understand that.", null, null) + .thenApply(result -> null); + } + } + + case Date: + Triple dateValidationResult = ValidateDate(input); + AtomicReference profileReference = new AtomicReference(profile); + if (dateValidationResult.getLeft()) { + profile.date = dateValidationResult.getMiddle(); + return turnContext.sendActivity( + String.format("Your cab ride to the airport is scheduled for %s.", + profileReference.get().date)) + .thenCompose(result -> turnContext.sendActivity( + String.format("Thanks for completing the booking %s.", profileReference.get().name))) + .thenCompose(result -> turnContext.sendActivity("Type anything to run the bot again.")) + .thenRun(() -> { + flow.setLastQuestionAsked(ConversationFlow.Question.None); + profileReference.set(new UserProfile()); + }); + } else { + if (StringUtils.isNotBlank(dateValidationResult.getRight())) { + return turnContext.sendActivity(dateValidationResult.getRight(), null, null) + .thenApply(result -> null); + } else { + return turnContext.sendActivity("I'm sorry, I didn't understand that.", null, null) + .thenApply(result -> null); } + } + default: + return CompletableFuture.completedFuture(null); + } + } + + private static Triple validateName(String input) { + String name = null; + String message = null; + + if (StringUtils.isEmpty(input)) { + message = "Please enter a name that contains at least one character."; + } else { + name = input.trim(); + } + + return Triple.of(StringUtils.isBlank(message), name, message); + } + + private static Triple ValidateAge(String input) { + int age = 0; + String message = null; + + // Try to recognize the input as a number. This works for responses such as "twelve" as well as "12". + try { + // Attempt to convert the Recognizer result to an integer. This works for "a dozen", "twelve", "12", and so on. + // The recognizer returns a list of potential recognition results, if any. + + List results = NumberRecognizer.recognizeNumber(input, PromptCultureModels.ENGLISH_CULTURE); + for (ModelResult result : results) { + // The result resolution is a dictionary, where the "value" entry contains the processed String. + Object value = result.resolution.get("value"); + if (value != null) { + age = Integer.parseInt((String) value); + if (age >= 18 && age <= 120) { + return Triple.of(true, age, ""); + } + } + } + + message = "Please enter an age between 18 and 120."; + } + catch (Throwable th) { + message = "I'm sorry, I could not interpret that as an age. Please enter an age between 18 and 120."; + } + + return Triple.of(StringUtils.isBlank(message), age, message); + } + + private static Triple ValidateDate(String input) { + String date = null; + String message = null; + + // Try to recognize the input as a date-time. This works for responses such as "11/14/2018", "9pm", "tomorrow", "Sunday at 5pm", and so on. + // The recognizer returns a list of potential recognition results, if any. + try { + List results = DateTimeRecognizer.recognizeDateTime(input, PromptCultureModels.ENGLISH_CULTURE); + + // Check whether any of the recognized date-times are appropriate, + // and if so, return the first appropriate date-time. We're checking for a value at least an hour in the future. + LocalDateTime earliest = LocalDateTime.now().plus(1, ChronoUnit.HOURS); + + for (ModelResult result : results) { + // The result resolution is a dictionary, where the "values" entry contains the processed input. + List> resolutions = (List>) result.resolution.get("values"); + + for (Map resolution : resolutions) { + // The processed input contains a "value" entry if it is a date-time value, or "start" and + // "end" entries if it is a date-time range. + String dateString = (String) resolution.get("value"); + if (StringUtils.isBlank(dateString)) { + dateString = (String) resolution.get("start"); + } + if (StringUtils.isNotBlank(dateString)){ + DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + LocalDateTime candidate = LocalDateTime.from(f.parse(dateString)); + if (earliest.isBefore(candidate)) { + DateTimeFormatter dateformat = DateTimeFormatter.ofPattern("MM-dd-yyyy"); + date = candidate.format(dateformat); + return Triple.of(true, date, message); + } + } + + } + } + + message = "I'm sorry, please enter a date at least an hour out."; + } + catch (Throwable th) { + message = "I'm sorry, I could not interpret that as an appropriate date. Please enter a date at least an hour out."; + } + + return Triple.of(false, date, message); + } +} diff --git a/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/UserProfile.java b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/UserProfile.java new file mode 100644 index 000000000..2f5df5b91 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/UserProfile.java @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.promptusersforinput; + +public class UserProfile { + public String name; + public Integer age; + public String date; +} diff --git a/samples/44.prompt-users-for-input/src/main/resources/application.properties b/samples/44.prompt-users-for-input/src/main/resources/application.properties new file mode 100644 index 000000000..d7d0ee864 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/resources/application.properties @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 diff --git a/samples/44.prompt-users-for-input/src/main/resources/log4j2.json b/samples/44.prompt-users-for-input/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/44.prompt-users-for-input/src/main/webapp/META-INF/MANIFEST.MF b/samples/44.prompt-users-for-input/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/44.prompt-users-for-input/src/main/webapp/WEB-INF/web.xml b/samples/44.prompt-users-for-input/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/44.prompt-users-for-input/src/main/webapp/index.html b/samples/44.prompt-users-for-input/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + EchoBot + + + + + +
+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/44.prompt-users-for-input/src/test/java/com/microsoft/bot/sample/promptusersforinput/ApplicationTest.java b/samples/44.prompt-users-for-input/src/test/java/com/microsoft/bot/sample/promptusersforinput/ApplicationTest.java new file mode 100644 index 000000000..df4f92a57 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/test/java/com/microsoft/bot/sample/promptusersforinput/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.promptusersforinput; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +}