From 7ad4d0fadf3616557350f1c60671688f7f60f91c Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 11 Feb 2021 10:20:45 -0600 Subject: [PATCH] Sample 19.custom-dialogs --- samples/19.custom-dialogs/LICENSE | 21 + samples/19.custom-dialogs/README.md | 102 +++++ .../template-with-new-rg.json | 291 ++++++++++++ .../template-with-preexisting-rg.json | 259 +++++++++++ samples/19.custom-dialogs/pom.xml | 244 ++++++++++ .../bot/sample/customdialogs/Application.java | 86 ++++ .../bot/sample/customdialogs/DialogBot.java | 53 +++ .../bot/sample/customdialogs/RootDialog.java | 132 ++++++ .../bot/sample/customdialogs/SlotDetails.java | 75 ++++ .../customdialogs/SlotFillingDialog.java | 170 +++++++ .../src/main/resources/application.properties | 3 + .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 418 ++++++++++++++++++ .../sample/customdialogs/ApplicationTest.java | 19 + 16 files changed, 1906 insertions(+) create mode 100644 samples/19.custom-dialogs/LICENSE create mode 100644 samples/19.custom-dialogs/README.md create mode 100644 samples/19.custom-dialogs/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/19.custom-dialogs/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/19.custom-dialogs/pom.xml create mode 100644 samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/Application.java create mode 100644 samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/DialogBot.java create mode 100644 samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/RootDialog.java create mode 100644 samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotDetails.java create mode 100644 samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotFillingDialog.java create mode 100644 samples/19.custom-dialogs/src/main/resources/application.properties create mode 100644 samples/19.custom-dialogs/src/main/resources/log4j2.json create mode 100644 samples/19.custom-dialogs/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/19.custom-dialogs/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/19.custom-dialogs/src/main/webapp/index.html create mode 100644 samples/19.custom-dialogs/src/test/java/com/microsoft/bot/sample/customdialogs/ApplicationTest.java diff --git a/samples/19.custom-dialogs/LICENSE b/samples/19.custom-dialogs/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/19.custom-dialogs/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/19.custom-dialogs/README.md b/samples/19.custom-dialogs/README.md new file mode 100644 index 000000000..de0766982 --- /dev/null +++ b/samples/19.custom-dialogs/README.md @@ -0,0 +1,102 @@ +# Custom Dialogs + +Bot Framework v4 custom dialogs bot sample + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to sub-class the `Dialog` class to create different bot control mechanism like simple slot filling. + +BotFramework provides a built-in base class called `Dialog`. By subclassing `Dialog`, developers can create new ways to define and control dialog flows used by the bot. + +## 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 +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\bot-customdialogs-sample.jar` + +## Testing 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 latest Bot Framework Emulator 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` + +## Interacting with the bot + +BotFramework provides a built-in base class called `Dialog`. By subclassing Dialog, developers +can create new ways to define and control dialog flows used by the bot. By adhering to the +features of this class, developers will create custom dialogs that can be used side-by-side +with other dialog types, as well as built-in or custom prompts. + +This example demonstrates a custom Dialog class called `SlotFillingDialog`, which takes a +series of "slots" which define a value the bot needs to collect from the user, as well +as the prompt it should use. The bot will iterate through all of the slots until they are +all full, at which point the dialog completes. + +## 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 sub create --name "multiTurnPromptBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="multiTurnPromptBotPlan" newWebAppName="multiTurnPromptBot" groupLocation="westus" newAppServicePlanLocation="westus"` + +#### To an existing Resource Group +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="multiTurnPromptBot" newAppServicePlanName="multiTurnPromptBotPlan" appServicePlanLocation="westus" --name "multiTurnPromptBot"` + +### 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) +- [Dialog class reference](https://docs.microsoft.com/en-us/javascript/api/botbuilder-dialogs/dialog) +- [Manage complex conversation flows with dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-dialog-manage-complex-conversation-flow?view=azure-bot-service-4.0) +- [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) diff --git a/samples/19.custom-dialogs/deploymentTemplates/template-with-new-rg.json b/samples/19.custom-dialogs/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/19.custom-dialogs/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$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": "S1", + "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": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "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 Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "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/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "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": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/19.custom-dialogs/deploymentTemplates/template-with-preexisting-rg.json b/samples/19.custom-dialogs/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/19.custom-dialogs/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$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'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[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 Linux 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": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "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/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "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'))]" + ] + } + ] +} diff --git a/samples/19.custom-dialogs/pom.xml b/samples/19.custom-dialogs/pom.xml new file mode 100644 index 000000000..725f8f839 --- /dev/null +++ b/samples/19.custom-dialogs/pom.xml @@ -0,0 +1,244 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-customdialogs + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains the Custom Dialogs 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.customdialogs.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.customdialogs.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${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/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/Application.java b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/Application.java new file mode 100644 index 000000000..4997b04e0 --- /dev/null +++ b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/Application.java @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.customdialogs; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; +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.Bean; +import org.springframework.context.annotation.Import; + +// +// This is the starting point of the Sprint Boot Bot application. +// +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + ConversationState conversationState, + UserState userState, + Dialog dialog + ) { + return new DialogBot(conversationState, userState, dialog); + } + + /** + * Returns the starting Dialog for this application. + * + *

+ * The @Component annotation could be used on the Dialog class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Dialog implementation for this application. + */ + @Bean + public Dialog getRootDialog(UserState userState) { + return new RootDialog(userState); + } + + /** + * 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/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/DialogBot.java b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/DialogBot.java new file mode 100644 index 000000000..ce884a210 --- /dev/null +++ b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/DialogBot.java @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.customdialogs; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import java.util.concurrent.CompletableFuture; + +/** + * This IBot 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 IBot + * types, this way ASP Dependency Injection can glue everything together without ambiguity. The + * ConversationState is used by the Dialog system. The UserState isn't, however, it might have + * been used in a Dialog implementation, and the requirement is that all BotState objects are + * saved at the end of a turn. + */ +public class DialogBot extends ActivityHandler { + protected Dialog dialog; + protected BotState conversationState; + protected BotState userState; + + public DialogBot( + ConversationState withConversationState, + UserState withUserState, + Dialog withDialog + ) { + dialog = withDialog; + conversationState = withConversationState; + userState = withUserState; + } + + @Override + public CompletableFuture onTurn( + TurnContext turnContext + ) { + return super.onTurn(turnContext) + .thenCompose(result -> conversationState.saveChanges(turnContext)) + .thenCompose(result -> userState.saveChanges(turnContext)); + } + + @Override + protected CompletableFuture onMessageActivity( + TurnContext turnContext + ) { + return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); + } +} diff --git a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/RootDialog.java b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/RootDialog.java new file mode 100644 index 000000000..bfaf3b445 --- /dev/null +++ b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/RootDialog.java @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.customdialogs; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.NumberPrompt; +import com.microsoft.bot.dialogs.prompts.PromptValidatorContext; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class RootDialog extends ComponentDialog { + + private StatePropertyAccessor>> userStateAccessor; + + public RootDialog(UserState withUserState) { + super("root"); + + userStateAccessor = withUserState.createProperty("result"); + + // Rather than explicitly coding a Waterfall we have only to declare what properties we + // want collected. + // In this example we will want two text prompts to run, one for the first name and one + // for the last. + List fullname_slots = Arrays.asList( + new SlotDetails("first", "text", "Please enter your first name."), + new SlotDetails("last", "text", "Please enter your last name.") + ); + + // This defines an address dialog that collects street, city and zip properties. + List address_slots = Arrays.asList( + new SlotDetails("street", "text", "Please enter the street."), + new SlotDetails("city", "text", "Please enter the city."), + new SlotDetails("zip", "text", "Please enter the zip.") + ); + + // Dialogs can be nested and the slot filling dialog makes use of that. In this example + // some of the child dialogs are slot filling dialogs themselves. + List slots = Arrays.asList( + new SlotDetails("fullname", "fullname"), + new SlotDetails("age", "number", "Please enter your age."), + new SlotDetails( + "shoesize", "shoesize", "Please enter your shoe size.", + "You must enter a size between 0 and 16. Half sizes are acceptable." + ), + new SlotDetails("address", "address") + ); + + // Add the various dialogs that will be used to the DialogSet. + addDialog(new SlotFillingDialog("address", address_slots)); + addDialog(new SlotFillingDialog("fullname", fullname_slots)); + addDialog(new TextPrompt("text")); + addDialog(new NumberPrompt<>("number", Integer.class)); + addDialog(new NumberPrompt("shoesize", this::shoeSize, Float.class)); + addDialog(new SlotFillingDialog("slot-dialog", slots)); + + // Defines a simple two step Waterfall to test the slot dialog. + WaterfallStep[] waterfallSteps = { + this::startDialog, + this::processResult + }; + addDialog(new WaterfallDialog("waterfall", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("waterfall"); + } + + private CompletableFuture startDialog(WaterfallStepContext stepContext) { + // Start the child dialog. This will run the top slot dialog than will complete when + // all the properties are gathered. + return stepContext.beginDialog("slot-dialog"); + } + + private CompletableFuture processResult(WaterfallStepContext stepContext) { + Map result = stepContext.getResult() instanceof Map + ? (Map) stepContext.getResult() + : null; + + // To demonstrate that the slot dialog collected all the properties we will echo them back to the user. + if (result != null && result.size() > 0) { + // Now the waterfall is complete, save the data we have gathered into UserState. + return userStateAccessor.get(stepContext.getContext(), HashMap::new) + .thenApply(obj -> { + Map fullname = (Map) result.get("fullname"); + Float shoesize = (Float) result.get("shoesize"); + Map address = (Map) result.get("address"); + + Map data = new HashMap<>(); + data.put("fullname", String.format("%s %s", fullname.get("first"), fullname.get("last"))); + data.put("shoesize", Float.toString(shoesize)); + data.put("address", String.format("%s, %s, %s", address.get("street"), address.get("city"), address.get("zip"))); + + obj.put("data", data); + return obj; + }) + .thenCompose(obj -> stepContext.getContext().sendActivities( + MessageFactory.text(obj.get("data").get("fullname")), + MessageFactory.text(obj.get("data").get("shoesize")), + MessageFactory.text(obj.get("data").get("address")) + )) + .thenCompose(resourceResponse -> stepContext.endDialog()); + } + + // Remember to call EndAsync to indicate to the runtime that this is the end of our waterfall. + return stepContext.endDialog(); + } + + private CompletableFuture shoeSize(PromptValidatorContext promptContext) { + Float shoesize = promptContext.getRecognized().getValue(); + + // show sizes can range from 0 to 16 + if (shoesize >= 0 && shoesize < 16) { + // we only accept round numbers or half sizes + if (Math.floor(shoesize) == shoesize || Math.floor(shoesize * 2) == shoesize * 2) { + return CompletableFuture.completedFuture(true); + } + } + + return CompletableFuture.completedFuture(false); + } +} diff --git a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotDetails.java b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotDetails.java new file mode 100644 index 000000000..6531a9741 --- /dev/null +++ b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotDetails.java @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.customdialogs; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.prompts.PromptOptions; + +/** + * A list of SlotDetails defines the behavior of our SlotFillingDialog. This class represents a + * description of a single 'slot'. It contains the name of the property we want to gather and the id + * of the corresponding dialog that should be used to gather that property. The id is that used when + * the dialog was added to the current DialogSet. Typically this id is that of a prompt but it could + * also be the id of another slot dialog. + */ +public class SlotDetails { + + private String name; + private String dialogId; + private PromptOptions options; + + public SlotDetails( + String withName, String withDialogId + ) { + this(withName, withDialogId, null, null); + } + + public SlotDetails( + String withName, String withDialogId, String withPrompt + ) { + this(withName, withDialogId, withPrompt, null); + } + + public SlotDetails( + String withName, String withDialogId, String withPrompt, String withRetryPrompt + ) { + name = withName; + dialogId = withDialogId; + options = new PromptOptions(); + options.setPrompt(MessageFactory.text(withPrompt)); + options.setRetryPrompt(MessageFactory.text(withRetryPrompt)); + } + + public SlotDetails( + String withName, String withDialogId, PromptOptions withPromptOptions + ) { + name = withName; + dialogId = withDialogId; + options = withPromptOptions; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDialogId() { + return dialogId; + } + + public void setDialogId(String withDialogId) { + dialogId = withDialogId; + } + + public PromptOptions getOptions() { + return options; + } + + public void setOptions(PromptOptions withOptions) { + options = withOptions; + } +} diff --git a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotFillingDialog.java b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotFillingDialog.java new file mode 100644 index 000000000..a63e48371 --- /dev/null +++ b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotFillingDialog.java @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.customdialogs; + +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogInstance; +import com.microsoft.bot.dialogs.DialogReason; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.schema.ActivityTypes; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** + * This is an example of implementing a custom Dialog class. This is similar to the Waterfall dialog + * in the framework; however, it is based on a Dictionary rather than a sequential set of functions. + * The dialog is defined by a list of 'slots', each slot represents a property we want to gather and + * the dialog we will be using to collect it. Often the property is simply an atomic piece of data + * such as a number or a date. But sometimes the property is itself a complex object, in which case + * we can use the slot dialog to collect that compound property. + */ +public class SlotFillingDialog extends Dialog { + + // Custom dialogs might define their own custom state. + // Similarly to the Waterfall dialog we will have a set of values in the ConversationState. + // However, rather than persisting an index we will persist the last property we prompted for. + // This way when we resume this code following a prompt we will have remembered what property + // we were filling. + private static final String SLOT_NAME = "slot"; + private static final String PERSISTED_VALUES = "values"; + + // The list of slots defines the properties to collect and the dialogs to use to collect them. + private List slots; + + public SlotFillingDialog(String withDialogId, List withSlots) { + super(withDialogId); + if (withSlots == null) { + throw new IllegalArgumentException("slot details are required"); + } + slots = withSlots; + } + + /** + * Called when the dialog is started and pushed onto the dialog stack. + * + * @param dc The {@link DialogContext} for the current turn of conversation. + * @param options Initial information to pass to the dialog. + * @return If the task is successful, the result indicates whether the dialog is still active + * after the turn has been processed by the dialog. + */ + @Override + public CompletableFuture beginDialog(DialogContext dc, Object options) { + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException("DialogContext")); + } + + // Don't do anything for non-message activities. + if (!dc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { + return dc.endDialog(new HashMap()); + } + + // Run prompt + return runPrompt(dc); + } + + /** + * Called when the dialog is _continued_, where it is the active dialog and the user replies + * with a new activity. + * + *

If this method is *not* overridden, the dialog automatically ends when the user + * replies.

+ * + * @param dc The {@link DialogContext} for the current turn of conversation. + * @return If the task is successful, the result indicates whether the dialog is still active + * after the turn has been processed by the dialog. The result may also contain a return value. + */ + @Override + public CompletableFuture continueDialog( + DialogContext dc + ) { + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException("DialogContext")); + } + + // Don't do anything for non-message activities. + if (!dc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { + return CompletableFuture.completedFuture(END_OF_TURN); + } + + // Run next step with the message text as the result. + return runPrompt(dc); + } + + /** + * Called when a child dialog completed this turn, returning control to this dialog. + * + *

Generally, the child dialog was started with a call to + * {@link #beginDialog(DialogContext, Object)} However, if the {@link + * DialogContext#replaceDialog(String, Object)} method is called, the logical child dialog may + * be different than the original.

+ * + *

If this method is *not* overridden, the dialog automatically ends when the user + * replies.

+ * + * @param dc The dialog context for the current turn of the conversation. + * @param reason Reason why the dialog resumed. + * @param result Optional, value returned from the dialog that was called. The type of the value + * returned is dependent on the child dialog. + * @return If the task is successful, the result indicates whether this dialog is still active + * after this dialog turn has been processed. + */ + @Override + public CompletableFuture resumeDialog( + DialogContext dc, DialogReason reason, Object result + ) { + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException("DialogContext")); + } + + // Update the state with the result from the child prompt. + String slotName = (String) dc.getActiveDialog().getState().get(SLOT_NAME); + Map values = getPersistedValues(dc.getActiveDialog()); + values.put(slotName, result); + + // Run prompt + return runPrompt(dc); + } + + // This helper function contains the core logic of this dialog. The main idea is to compare + // the state we have gathered with the list of slots we have been asked to fill. When we find + // an empty slot we execute the corresponding prompt. + private CompletableFuture runPrompt(DialogContext dc) { + Map state = getPersistedValues(dc.getActiveDialog()); + + // Run through the list of slots until we find one that hasn't been filled yet. + Optional optionalSlot = slots.stream() + .filter(item -> !state.containsKey(item.getName())) + .findFirst(); + + if (!optionalSlot.isPresent()) { + // No more slots to fill so end the dialog. + return dc.endDialog(state); + } else { + // If we have an unfilled slot we will try to fill it + SlotDetails unfilledSlot = optionalSlot.get(); + + // The name of the slot we will be prompting to fill. + dc.getActiveDialog().getState().put(SLOT_NAME, unfilledSlot.getName()); + + // Run the child dialog + return dc.beginDialog(unfilledSlot.getDialogId(), unfilledSlot.getOptions()); + } + } + + // Helper function to deal with the persisted values we collect in this dialog. + private Map getPersistedValues(DialogInstance dialogInstance) { + Map state = (Map) dialogInstance.getState() + .get(PERSISTED_VALUES); + if (state == null) { + state = new HashMap(); + dialogInstance.getState().put(PERSISTED_VALUES, state); + } + return state; + } +} diff --git a/samples/19.custom-dialogs/src/main/resources/application.properties b/samples/19.custom-dialogs/src/main/resources/application.properties new file mode 100644 index 000000000..d7d0ee864 --- /dev/null +++ b/samples/19.custom-dialogs/src/main/resources/application.properties @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 diff --git a/samples/19.custom-dialogs/src/main/resources/log4j2.json b/samples/19.custom-dialogs/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/19.custom-dialogs/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/19.custom-dialogs/src/main/webapp/META-INF/MANIFEST.MF b/samples/19.custom-dialogs/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/19.custom-dialogs/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/19.custom-dialogs/src/main/webapp/WEB-INF/web.xml b/samples/19.custom-dialogs/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/19.custom-dialogs/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/19.custom-dialogs/src/main/webapp/index.html b/samples/19.custom-dialogs/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/19.custom-dialogs/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/19.custom-dialogs/src/test/java/com/microsoft/bot/sample/customdialogs/ApplicationTest.java b/samples/19.custom-dialogs/src/test/java/com/microsoft/bot/sample/customdialogs/ApplicationTest.java new file mode 100644 index 000000000..89c3838fd --- /dev/null +++ b/samples/19.custom-dialogs/src/test/java/com/microsoft/bot/sample/customdialogs/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.customdialogs; + +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() { + } + +}