Initial Commit - MiDemo Bot (Node.js)

This commit is contained in:
Fernando Flechas 2020-07-10 23:51:14 -05:00
Родитель 0a1ba16a8a
Коммит ec105a2172
32 изменённых файлов: 8865 добавлений и 0 удалений

14
bot-js/.eslintrc.js Normal file
Просмотреть файл

@ -0,0 +1,14 @@
module.exports = {
"extends": "standard",
"rules": {
"semi": [2, "always"],
"indent": [2, 4],
"no-return-await": 0,
"space-before-function-paren": [2, {
"named": "never",
"anonymous": "never",
"asyncArrow": "always"
}],
"template-curly-spacing": [2, "always"]
}
};

66
bot-js/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,66 @@
# Bot
lib
.env
*.bot
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next

78
bot-js/README.md Normal file
Просмотреть файл

@ -0,0 +1,78 @@
# midemo
Azure Demos
This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to:
- Use [LUIS](https://www.luis.ai) to implement core AI capabilities
- Implement a multi-turn conversation using Dialogs
- Handle user interruptions for such things as `Help` or `Cancel`
- Prompt for and validate requests for information from the user
## Prerequisites
This sample **requires** prerequisites in order to run.
### Overview
This bot uses [LUIS](https://www.luis.ai), an AI based cognitive service, to implement language understanding.
- [Node.js](https://nodejs.org) version 10.14.1 or higher
```bash
# determine node version
node --version
```
### Create a LUIS Application to enable language understanding
LUIS language model setup, training, and application configuration steps can be found [here](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-luis?view=azure-bot-service-4.0).
# To run the bot
- Install modules
```bash
npm install
```
- Setup LUIS
The prerequisite outlined above contain the steps necessary to provision a language understanding model on www.luis.ai. Refer to _Create a LUIS Application to enable language understanding_ above for directions to setup and configure LUIS.
- Start the bot
```bash
npm start
```
## 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 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
To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions.
## 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)
- [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)
- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/)
- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0)
- [Restify](https://www.npmjs.com/package/restify)
- [dotenv](https://www.npmjs.com/package/dotenv)

Просмотреть файл

@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { CardFactory } = require('botbuilder-core');
const { DialogBot } = require('./dialogBot');
const WelcomeCard = require('../resources/welcomeCard.json');
class DialogAndWelcomeBot extends DialogBot {
constructor(conversationState, userState, dialog) {
super(conversationState, userState, dialog);
this.onMembersAdded(async(context, next) => {
const membersAdded = context.activity.membersAdded;
for (let cnt = 0; cnt < membersAdded.length; cnt++) {
if (membersAdded[cnt].id !== context.activity.recipient.id) {
const welcomeCard = CardFactory.adaptiveCard(WelcomeCard);
await context.sendActivity({ attachments: [welcomeCard] });
await dialog.run(context, conversationState.createProperty('DialogState'));
}
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
this.onTokenResponseEvent(async(context, next) => {
// Run the Dialog with the new Token Response Event Activity.
await this.dialog.run(context, this.dialogState);
// By calling next() you ensure that the next BotHandler is run.
await next();
});
this.onInvoke(async(context, next) => {
await this.dialog.run(context, this.dialogState);
await next();
});
}
}
module.exports.DialogAndWelcomeBot = DialogAndWelcomeBot;

42
bot-js/bots/dialogBot.js Normal file
Просмотреть файл

@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { TeamsActivityHandler } = require('../handlers/teamsActivityHandler');
class DialogBot extends TeamsActivityHandler {
/**
*
* @param {ConversationState} conversationState
* @param {UserState} userState
* @param {Dialog} dialog
*/
constructor(conversationState, userState, dialog) {
super();
if (!conversationState) throw new Error('[DialogBot]: Missing parameter. conversationState is required');
if (!userState) throw new Error('[DialogBot]: Missing parameter. userState is required');
if (!dialog) throw new Error('[DialogBot]: Missing parameter. dialog is required');
this.conversationState = conversationState;
this.userState = userState;
this.dialog = dialog;
this.dialogState = this.conversationState.createProperty('DialogState');
this.onMessage(async(context, next) => {
await this.dialog.run(context, this.dialogState);
// By calling next() you ensure that the next BotHandler is run.
await next();
});
this.onDialog(async(context, next) => {
// Save any state changes. The load happened during the execution of the Dialog.
await this.conversationState.saveChanges(context, false);
await this.userState.saveChanges(context, false);
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
}
module.exports.DialogBot = DialogBot;

Просмотреть файл

@ -0,0 +1,109 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { Client } = require('@microsoft/microsoft-graph-client');
/**
* This class is a wrapper for the Microsoft Graph API.
* See: https://developer.microsoft.com/en-us/graph for more information.
*/
class SimpleGraphClient {
constructor(token) {
if (!token || !token.trim()) {
throw new Error('SimpleGraphClient: Invalid token received.');
}
this._token = token;
// Get an Authenticated Microsoft Graph client using the token issued to the user.
this.graphClient = Client.init({
authProvider: (done) => {
done(null, this._token); // First parameter takes an error if you can't get an access token.
}
});
}
/**
* Sends an email on the user's behalf.
* @param {string} toAddress Email address of the email's recipient.
* @param {string} subject Subject of the email to be sent to the recipient.
* @param {string} content Email message to be sent to the recipient.
*/
async sendMail(toAddress, subject, content) {
if (!toAddress || !toAddress.trim()) {
throw new Error('SimpleGraphClient.sendMail(): Invalid `toAddress` parameter received.');
}
if (!subject || !subject.trim()) {
throw new Error('SimpleGraphClient.sendMail(): Invalid `subject` parameter received.');
}
if (!content || !content.trim()) {
throw new Error('SimpleGraphClient.sendMail(): Invalid `content` parameter received.');
}
// Create the email.
const mail = {
body: {
content: content, // `Hi there! I had this message sent from a bot. - Your friend, ${ graphData.displayName }!`,
contentType: 'Text'
},
subject: subject, // `Message from a bot!`,
toRecipients: [{
emailAddress: {
address: toAddress
}
}]
};
// Send the message.
return await this.graphClient
.api('/me/sendMail')
.post({ message: mail }, (error, res) => {
if (error) {
throw error;
} else {
return res;
}
});
}
/**
* Gets recent mail the user has received within the last hour and displays up to 5 of the emails in the bot.
*/
async getRecentMail() {
return await this.graphClient
.api('/me/messages')
.version('beta')
.top(5)
.get().then((res) => {
return res;
});
}
/**
* Collects information about the user in the bot.
*/
async getMe() {
return await this.graphClient
.api('/me')
.get().then((res) => {
return res;
}).catch(err => {
return err.message;
});
}
/**
* Collects the user's manager in the bot.
*/
async getManager() {
return await this.graphClient
.api('/me/manager')
.version('beta')
.select('displayName')
.get().then((res) => {
return res;
});
}
}
exports.SimpleGraphClient = SimpleGraphClient;

Просмотреть файл

@ -0,0 +1,339 @@
{
"luis_schema_version": "3.2.0",
"versionId": "0.1",
"name": "FlightBooking",
"desc": "Luis Model for CoreBot",
"culture": "en-us",
"tokenizerVersion": "1.0.0",
"intents": [
{
"name": "BookFlight"
},
{
"name": "Cancel"
},
{
"name": "GetWeather"
},
{
"name": "None"
}
],
"entities": [],
"composites": [
{
"name": "From",
"children": [
"Airport"
],
"roles": []
},
{
"name": "To",
"children": [
"Airport"
],
"roles": []
}
],
"closedLists": [
{
"name": "Airport",
"subLists": [
{
"canonicalForm": "Paris",
"list": [
"paris",
"cdg"
]
},
{
"canonicalForm": "London",
"list": [
"london",
"lhr"
]
},
{
"canonicalForm": "Berlin",
"list": [
"berlin",
"txl"
]
},
{
"canonicalForm": "New York",
"list": [
"new york",
"jfk"
]
},
{
"canonicalForm": "Seattle",
"list": [
"seattle",
"sea"
]
}
],
"roles": []
}
],
"patternAnyEntities": [],
"regex_entities": [],
"prebuiltEntities": [
{
"name": "datetimeV2",
"roles": []
}
],
"model_features": [],
"regex_features": [],
"patterns": [],
"utterances": [
{
"text": "book a flight",
"intent": "BookFlight",
"entities": []
},
{
"text": "book a flight from new york",
"intent": "BookFlight",
"entities": [
{
"entity": "From",
"startPos": 19,
"endPos": 26
}
]
},
{
"text": "book a flight from seattle",
"intent": "BookFlight",
"entities": [
{
"entity": "From",
"startPos": 19,
"endPos": 25
}
]
},
{
"text": "book a hotel in new york",
"intent": "None",
"entities": []
},
{
"text": "book a restaurant",
"intent": "None",
"entities": []
},
{
"text": "book flight from london to paris on feb 14th",
"intent": "BookFlight",
"entities": [
{
"entity": "From",
"startPos": 17,
"endPos": 22
},
{
"entity": "To",
"startPos": 27,
"endPos": 31
}
]
},
{
"text": "book flight to berlin on feb 14th",
"intent": "BookFlight",
"entities": [
{
"entity": "To",
"startPos": 15,
"endPos": 20
}
]
},
{
"text": "book me a flight from london to paris",
"intent": "BookFlight",
"entities": [
{
"entity": "From",
"startPos": 22,
"endPos": 27
},
{
"entity": "To",
"startPos": 32,
"endPos": 36
}
]
},
{
"text": "bye",
"intent": "Cancel",
"entities": []
},
{
"text": "cancel booking",
"intent": "Cancel",
"entities": []
},
{
"text": "exit",
"intent": "Cancel",
"entities": []
},
{
"text": "find an airport near me",
"intent": "None",
"entities": []
},
{
"text": "flight to paris",
"intent": "BookFlight",
"entities": [
{
"entity": "To",
"startPos": 10,
"endPos": 14
}
]
},
{
"text": "flight to paris from london on feb 14th",
"intent": "BookFlight",
"entities": [
{
"entity": "To",
"startPos": 10,
"endPos": 14
},
{
"entity": "From",
"startPos": 21,
"endPos": 26
}
]
},
{
"text": "fly from berlin to paris on may 5th",
"intent": "BookFlight",
"entities": [
{
"entity": "From",
"startPos": 9,
"endPos": 14
},
{
"entity": "To",
"startPos": 19,
"endPos": 23
}
]
},
{
"text": "go to paris",
"intent": "BookFlight",
"entities": [
{
"entity": "To",
"startPos": 6,
"endPos": 10
}
]
},
{
"text": "going from paris to berlin",
"intent": "BookFlight",
"entities": [
{
"entity": "From",
"startPos": 11,
"endPos": 15
},
{
"entity": "To",
"startPos": 20,
"endPos": 25
}
]
},
{
"text": "i'd like to rent a car",
"intent": "None",
"entities": []
},
{
"text": "ignore",
"intent": "Cancel",
"entities": []
},
{
"text": "travel from new york to paris",
"intent": "BookFlight",
"entities": [
{
"entity": "From",
"startPos": 12,
"endPos": 19
},
{
"entity": "To",
"startPos": 24,
"endPos": 28
}
]
},
{
"text": "travel to new york",
"intent": "BookFlight",
"entities": [
{
"entity": "To",
"startPos": 10,
"endPos": 17
}
]
},
{
"text": "travel to paris",
"intent": "BookFlight",
"entities": [
{
"entity": "To",
"startPos": 10,
"endPos": 14
}
]
},
{
"text": "what's the forecast for this friday?",
"intent": "GetWeather",
"entities": []
},
{
"text": "what's the weather like for tomorrow",
"intent": "GetWeather",
"entities": []
},
{
"text": "what's the weather like in new york",
"intent": "GetWeather",
"entities": []
},
{
"text": "what's the weather like?",
"intent": "GetWeather",
"entities": []
},
{
"text": "winter is coming",
"intent": "None",
"entities": []
}
],
"settings": []
}

Просмотреть файл

@ -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": ""
}
}
}

Просмотреть файл

@ -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": ""
}
}
}

Просмотреть файл

@ -0,0 +1,183 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"groupLocation": {
"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": {
"type": "string",
"metadata": {
"description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1."
}
},
"newAppServicePlanName": {
"type": "string",
"metadata": {
"description": "The name of the App Service Plan."
}
},
"newAppServicePlanSku": {
"type": "object",
"defaultValue": {
"name": "S1",
"tier": "Standard",
"size": "S1",
"family": "S",
"capacity": 1
},
"metadata": {
"description": "The SKU of the App Service Plan. Defaults to Standard values."
}
},
"newAppServicePlanLocation": {
"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')]"
},
"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 App Service Plan",
"type": "Microsoft.Web/serverfarms",
"name": "[variables('appServicePlanName')]",
"apiVersion": "2018-02-01",
"location": "[variables('resourcesLocation')]",
"sku": "[parameters('newAppServicePlanSku')]",
"properties": {
"name": "[variables('appServicePlanName')]"
}
},
{
"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": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "10.14.1"
},
{
"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": {}
}
}
}
]
}

Просмотреть файл

@ -0,0 +1,154 @@
{
"$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": "F0",
"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": "S1",
"tier": "Standard",
"size": "S1",
"family": "S",
"capacity": 1
},
"metadata": {
"description": "The SKU of the App Service Plan. Defaults to Standard values."
}
},
"appServicePlanLocation": {
"type": "string",
"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'))]",
"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 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')]",
"properties": {
"name": "[variables('servicePlanName')]"
}
},
{
"comments": "Create a Web App using an App Service Plan",
"type": "Microsoft.Web/sites",
"apiVersion": "2015-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": {
"appSettings": [
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "10.14.1"
},
{
"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'))]"
]
}
]
}

Просмотреть файл

@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { LuisRecognizer } = require('botbuilder-ai');
class BotLuisRecognizer {
constructor(config, options) {
const luisIsConfigured = config && config.applicationId && config.endpointKey && config.endpoint;
if (luisIsConfigured) {
this.recognizer = new LuisRecognizer(config, options, true);
}
}
get isConfigured() {
return (this.recognizer !== undefined);
}
/**
* Returns an object with preformatted LUIS results for the bot's dialogs to consume.
* @param {TurnContext} context
*/
async executeLuisQuery(context) {
return await this.recognizer.recognize(context);
}
getFromEntities(result) {
let fromValue, fromAirportValue;
if (result.entities.$instance.From) {
fromValue = result.entities.$instance.From[0].text;
}
if (fromValue && result.entities.From[0].Airport) {
fromAirportValue = result.entities.From[0].Airport[0][0];
}
return { from: fromValue, airport: fromAirportValue };
}
getToEntities(result) {
let toValue, toAirportValue;
if (result.entities.$instance.To) {
toValue = result.entities.$instance.To[0].text;
}
if (toValue && result.entities.To[0].Airport) {
toAirportValue = result.entities.To[0].Airport[0][0];
}
return { to: toValue, airport: toAirportValue };
}
/**
* This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the Time part.
* TIMEX is a format that represents DateTime expressions that include some ambiguity. e.g. missing a Year.
*/
getTravelDate(result) {
const datetimeEntity = result.entities['datetime'];
if (!datetimeEntity || !datetimeEntity[0]) return undefined;
const timex = datetimeEntity[0]['timex'];
if (!timex || !timex[0]) return undefined;
const datetime = timex[0].split('T')[0];
return datetime;
}
}
module.exports.BotLuisRecognizer = BotLuisRecognizer;

Просмотреть файл

@ -0,0 +1,110 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { TimexProperty } = require('@microsoft/recognizers-text-data-types-timex-expression');
const { InputHints, MessageFactory } = require('botbuilder');
const { ConfirmPrompt, TextPrompt, WaterfallDialog } = require('botbuilder-dialogs');
const { CancelAndHelpDialog } = require('./cancelAndHelpDialog');
const { DateResolverDialog } = require('./dateResolverDialog');
const CONFIRM_PROMPT = 'confirmPrompt';
const DATE_RESOLVER_DIALOG = 'dateResolverDialog';
const TEXT_PROMPT = 'textPrompt';
const WATERFALL_DIALOG = 'waterfallDialog';
class BookingDialog extends CancelAndHelpDialog {
constructor(id) {
super(id || 'bookingDialog');
this.addDialog(new TextPrompt(TEXT_PROMPT))
.addDialog(new ConfirmPrompt(CONFIRM_PROMPT))
.addDialog(new DateResolverDialog(DATE_RESOLVER_DIALOG))
.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.destinationStep.bind(this),
this.originStep.bind(this),
this.travelDateStep.bind(this),
this.confirmStep.bind(this),
this.finalStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
/**
* If a destination city has not been provided, prompt for one.
*/
async destinationStep(stepContext) {
const bookingDetails = stepContext.options;
if (!bookingDetails.destination) {
const messageText = 'To what city would you like to travel?';
const msg = MessageFactory.text(messageText, messageText, InputHints.ExpectingInput);
return await stepContext.prompt(TEXT_PROMPT, { prompt: msg });
}
return await stepContext.next(bookingDetails.destination);
}
/**
* If an origin city has not been provided, prompt for one.
*/
async originStep(stepContext) {
const bookingDetails = stepContext.options;
// Capture the response to the previous step's prompt
bookingDetails.destination = stepContext.result;
if (!bookingDetails.origin) {
const messageText = 'From what city will you be travelling?';
const msg = MessageFactory.text(messageText, 'From what city will you be travelling?', InputHints.ExpectingInput);
return await stepContext.prompt(TEXT_PROMPT, { prompt: msg });
}
return await stepContext.next(bookingDetails.origin);
}
/**
* If a travel date has not been provided, prompt for one.
* This will use the DATE_RESOLVER_DIALOG.
*/
async travelDateStep(stepContext) {
const bookingDetails = stepContext.options;
// Capture the results of the previous step
bookingDetails.origin = stepContext.result;
if (!bookingDetails.travelDate || this.isAmbiguous(bookingDetails.travelDate)) {
return await stepContext.beginDialog(DATE_RESOLVER_DIALOG, { date: bookingDetails.travelDate });
}
return await stepContext.next(bookingDetails.travelDate);
}
/**
* Confirm the information the user has provided.
*/
async confirmStep(stepContext) {
const bookingDetails = stepContext.options;
// Capture the results of the previous step
bookingDetails.travelDate = stepContext.result;
const messageText = `Please confirm, I have you traveling to: ${ bookingDetails.destination } from: ${ bookingDetails.origin } on: ${ bookingDetails.travelDate }. Is this correct?`;
const msg = MessageFactory.text(messageText, messageText, InputHints.ExpectingInput);
// Offer a YES/NO prompt.
return await stepContext.prompt(CONFIRM_PROMPT, { prompt: msg });
}
/**
* Complete the interaction and end the dialog.
*/
async finalStep(stepContext) {
if (stepContext.result === true) {
const bookingDetails = stepContext.options;
return await stepContext.endDialog(bookingDetails);
}
return await stepContext.endDialog();
}
isAmbiguous(timex) {
const timexPropery = new TimexProperty(timex);
return !timexPropery.types.has('definite');
}
}
module.exports.BookingDialog = BookingDialog;

Просмотреть файл

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { InputHints } = require('botbuilder');
const { ComponentDialog, DialogTurnStatus } = require('botbuilder-dialogs');
/**
* This base class watches for common phrases like "help" and "cancel" and takes action on them
* BEFORE they reach the normal bot logic.
*/
class CancelAndHelpDialog extends ComponentDialog {
async onContinueDialog(innerDc) {
const result = await this.interrupt(innerDc);
if (result) {
return result;
}
return await super.onContinueDialog(innerDc);
}
async interrupt(innerDc) {
if (innerDc.context.activity.text) {
const text = innerDc.context.activity.text.toLowerCase();
switch (text) {
case 'ayuda': // 'help'
case '?':
const helpMessageText = 'Ayuda...';
await innerDc.context.sendActivity(helpMessageText, helpMessageText, InputHints.ExpectingInput);
return { status: DialogTurnStatus.waiting };
case 'cancelar': // 'cancel'
const cancelMessageText = 'Cancelando...';
await innerDc.context.sendActivity(cancelMessageText, cancelMessageText, InputHints.IgnoringInput);
return await innerDc.cancelAllDialogs();
case 'salir': // 'quit'
const logoutMessageText = 'Saliendo...';
const botAdapter = innerDc.context.adapter;
await botAdapter.signOutUser(innerDc.context, process.env.ConnectionName);
await innerDc.context.sendActivity(logoutMessageText, logoutMessageText, InputHints.IgnoringInput);
return await innerDc.cancelAllDialogs();
}
}
}
}
module.exports.CancelAndHelpDialog = CancelAndHelpDialog;

Просмотреть файл

@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { ActivityTypes, InputHints } = require('botbuilder');
const { ChoicePrompt, OAuthPrompt, TextPrompt, WaterfallDialog } = require('botbuilder-dialogs');
const { ChoiceFactory } = require('botbuilder-choices');
const fs = require('fs');
const path = require('path');
const { SimpleGraphClient } = require('../clients/simple-graph-client');
const { CancelAndHelpDialog } = require('./cancelAndHelpDialog');
const CHOICE_PROMPT = 'ChoicePrompt';
const TEXT_PROMPT = 'TextPrompt';
const OAUTH_PROMPT = 'oAuthPrompt';
const WATERFALL_DIALOG = 'waterfallDialog';
class CertificateDialog extends CancelAndHelpDialog {
constructor(id) {
super(id || 'certificateDialog');
this.addDialog(new ChoicePrompt(CHOICE_PROMPT));
this.addDialog(new TextPrompt(TEXT_PROMPT));
this.addDialog(new OAuthPrompt(OAUTH_PROMPT, {
connectionName: process.env.ConnectionName,
text: 'Por favor ingrese a continuación con sus credenciales y escriba el token generado',
title: 'Ingresar y generar token',
timeout: 300000
}));
this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.promptStep.bind(this),
this.loginStep.bind(this),
this.firstStep.bind(this),
this.finalStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
async promptStep(stepContext) {
return stepContext.beginDialog(OAUTH_PROMPT);
}
async loginStep(stepContext) {
// Get the token from the previous step. Note that we could also have gotten the
// token directly from the prompt itself. There is an example of this in the next method.
const tokenResponse = stepContext.result;
if (tokenResponse) {
const client = new SimpleGraphClient(tokenResponse.token);
const me = await client.getMe();
await stepContext.context.sendActivity('Ingreso exitoso, bienvenida(o) ' + me.displayName);
return await stepContext.next();
} else {
await stepContext.context.sendActivity('No pudo ingresar exitosamente, intente nuevamente.');
return await stepContext.endDialog();
}
}
async firstStep(stepContext) {
// const getChoiceMessageText = ChoiceFactory.forChannel(stepContext.context, ['Nómina', 'Ingresos'], '¿Qué certificado requiere?', '¿Qué certificado requiere?');
return await stepContext.prompt(CHOICE_PROMPT, { prompt: '¿Qué certificado requiere?', choices: ChoiceFactory.toChoices(['Nomina', 'Ingresos']) });
}
/**
* Complete the interaction and end the dialog.
*/
async finalStep(stepContext) {
const cert = stepContext.result.value;
let msg = 'Sólamente puedo enviar certificados de nómina o de ingresos';
if (cert.toLowerCase().includes('nomi') || cert.toLowerCase().includes('ingre')) {
msg = 'Enviando certificado de ' + cert;
const reply = { type: ActivityTypes.Message };
reply.text = msg;
reply.attachments = [this.getInlineAttachment(cert.toLowerCase())];
await stepContext.context.sendActivity(reply);
} else {
await stepContext.context.sendActivity(msg, msg, InputHints.IgnoringInput);
}
return await stepContext.endDialog();
}
getInlineAttachment(cert) {
const file = fs.readFileSync(path.join(__dirname, '../resources/cert-' + cert + '.pdf'));
const base64File = Buffer.from(file).toString('base64');
return {
name: 'cert-' + cert + '.pdf',
contentType: 'application/pdf',
contentUrl: 'data:application/pdf;base64,' + base64File
};
}
}
module.exports.CertificateDialog = CertificateDialog;

Просмотреть файл

@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { InputHints, MessageFactory } = require('botbuilder');
const { DateTimePrompt, WaterfallDialog } = require('botbuilder-dialogs');
const { CancelAndHelpDialog } = require('./cancelAndHelpDialog');
const { TimexProperty } = require('@microsoft/recognizers-text-data-types-timex-expression');
const DATETIME_PROMPT = 'datetimePrompt';
const WATERFALL_DIALOG = 'waterfallDialog';
class DateResolverDialog extends CancelAndHelpDialog {
constructor(id) {
super(id || 'dateResolverDialog');
this.addDialog(new DateTimePrompt(DATETIME_PROMPT, this.dateTimePromptValidator.bind(this)))
.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.initialStep.bind(this),
this.finalStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
async initialStep(stepContext) {
const timex = stepContext.options.date;
const promptMessageText = 'On what date would you like to travel?';
const promptMessage = MessageFactory.text(promptMessageText, promptMessageText, InputHints.ExpectingInput);
const repromptMessageText = "I'm sorry, for best results, please enter your travel date including the month, day and year.";
const repromptMessage = MessageFactory.text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput);
if (!timex) {
// We were not given any date at all so prompt the user.
return await stepContext.prompt(DATETIME_PROMPT,
{
prompt: promptMessage,
retryPrompt: repromptMessage
});
}
// We have a Date we just need to check it is unambiguous.
const timexProperty = new TimexProperty(timex);
if (!timexProperty.types.has('definite')) {
// This is essentially a "reprompt" of the data we were given up front.
return await stepContext.prompt(DATETIME_PROMPT, { prompt: repromptMessage });
}
return await stepContext.next([{ timex: timex }]);
}
async finalStep(stepContext) {
const timex = stepContext.result[0].timex;
return await stepContext.endDialog(timex);
}
async dateTimePromptValidator(promptContext) {
if (promptContext.recognized.succeeded) {
// This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the Time part.
// TIMEX is a format that represents DateTime expressions that include some ambiguity. e.g. missing a Year.
const timex = promptContext.recognized.value[0].timex.split('T')[0];
// If this is a definite Date including year, month and day we are good otherwise reprompt.
// A better solution might be to let the user know what part is actually missing.
return new TimexProperty(timex).types.has('definite');
}
return false;
}
}
module.exports.DateResolverDialog = DateResolverDialog;

Просмотреть файл

@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { InputHints, MessageFactory } = require('botbuilder');
const { TextPrompt, WaterfallDialog } = require('botbuilder-dialogs');
const { CancelAndHelpDialog } = require('./cancelAndHelpDialog');
const request = require('request-promise-native');
const TEXT_PROMPT = 'textPrompt';
const WATERFALL_DIALOG = 'waterfallDialog';
class ForexDialog extends CancelAndHelpDialog {
constructor(id) {
super(id || 'translateDialog');
this.addDialog(new TextPrompt(TEXT_PROMPT))
.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
// this.initialStep.bind(this),
this.finalStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
async initialStep(stepContext) {
const messageText = '¿Que texto quiere traducir?';
const msg = MessageFactory.text(messageText, messageText, InputHints.ExpectingInput);
return await stepContext.prompt(TEXT_PROMPT, { prompt: msg });
}
/**
* Complete the interaction and end the dialog.
*/
async finalStep(stepContext) {
const msg = await this.getForex(stepContext);
await stepContext.context.sendActivity(msg, msg, InputHints.IgnoringInput);
return await stepContext.endDialog();
}
async getForex(stepContext) {
return new Promise((resolve, reject) => {
const forex = "USD_COP";
const extraConfig = stepContext.options;
const optsMsg = {
method: 'GET',
uri: extraConfig.ForexEndpoint + '/api/v7/convert?q=' + forex + '&compact=ultra&apiKey=' + extraConfig.ForexKey,
json: true
};
request(optsMsg)
.then(function(data) {
const rate = Math.round(data[forex] * 100) / 100;
resolve('La tasa de cambio del dìa es COP ' + rate.toLocaleString() + ' por USD\n\nObtenido de: ' + extraConfig.ForexUrl);
resolve(rate);
})
.catch(function(err) {
console.log(err);
resolve('No fue posible obtener la tasa de cambio, por favor intente nuevamente.');
});
});
}
}
module.exports.ForexDialog = ForexDialog;

Просмотреть файл

@ -0,0 +1,124 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { InputHints } = require('botbuilder');
const { ChoiceFactory } = require('botbuilder-choices');
const { ChoicePrompt, NumberPrompt, WaterfallDialog } = require('botbuilder-dialogs');
const { CancelAndHelpDialog } = require('./cancelAndHelpDialog');
const request = require('request-promise-native');
const CHOICE_PROMPT = 'ChoicePrompt';
const NUMBER_PROMPT = 'numberPrompt';
const WATERFALL_DIALOG = 'waterfallDialog';
class MachineDialog extends CancelAndHelpDialog {
constructor(id) {
super(id || 'machineDialog');
this.addDialog(new ChoicePrompt(CHOICE_PROMPT))
.addDialog(new NumberPrompt(NUMBER_PROMPT, this.numberValidator))
.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.firstStep.bind(this),
this.secondStep.bind(this),
this.thirdStep.bind(this),
this.finalStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
async numberValidator(stepContext) {
if (!stepContext.recognized.succeeded) return false;
const question = stepContext.options.prompt;
const response = stepContext.recognized.value;
if (question.includes('edad')) {
if (response >= 16 && response <= 90) return true;
} else if (question.includes('estudio')) {
if (response >= 0 && response <= 25) return true;
}
return false;
}
async firstStep(stepContext) {
const promptOptions = { prompt: 'Indiqueme su edad en años:', retryPrompt: 'Solamente analizo rango de edad entre 16 y 90 años, por favor intente nuevamente.' };
return await stepContext.prompt(NUMBER_PROMPT, promptOptions);
}
async secondStep(stepContext) {
stepContext.values.age = stepContext.result;
const promptOptions = { prompt: 'Indiqueme sus años de estudio (incluyendo Colegio):', retryPrompt: 'Solamente analizo rango de estudio entre 0 y 25 años, por favor intente nuevamente.' };
return await stepContext.prompt(NUMBER_PROMPT, promptOptions);
}
async thirdStep(stepContext) {
stepContext.values.education = stepContext.result;
return await stepContext.prompt(CHOICE_PROMPT, { prompt: 'Indiqueme su género:', choices: ChoiceFactory.toChoices(['Mujer', 'Hombre', 'N/D']) });
}
/**
* Complete the interaction and end the dialog.
*/
async finalStep(stepContext) {
const result = stepContext.result.value.toLowerCase();
const sex = (result.includes('muj') ? 'Female' : (result.includes('homb') ? 'Male' : ''));
stepContext.values.sex = sex;
const msg = await this.getMLResult(stepContext);
await stepContext.context.sendActivity(msg, msg, InputHints.IgnoringInput);
return await stepContext.endDialog();
}
async getMLResult(stepContext) {
return new Promise((resolve, reject) => {
const extraConfig = stepContext.options;
const values = stepContext.values;
const body = {
"Inputs": {
"Entrada": [{
"age": values.age,
"workclass": "",
"fnlwgt": "",
"education": "",
"education-num": values.education,
"marital-status": "",
"occupation": "",
"relationship": "",
"race": "",
"sex": values.sex,
"capital-gain": "",
"capital-loss": "",
"hours-per-week": "",
"native-country": "-States",
"income": ""
}]
},
"GlobalParameters": {}
};
const optsMsg = {
method: 'POST',
uri: extraConfig.MLApiUrl,
headers: {
'Authorization': 'Bearer ' + extraConfig.MLApiKey
},
body,
json: true
};
request(optsMsg)
.then(function(data) {
const result = data.Results.Salida;
if (result) {
result.forEach(score => {
resolve(Math.round(score['Scored Probabilities'] * 100) + '% probable que gane mas de USD 50k/año');
});
} else {
resolve('No fue posible realizar la traducción, por favor intente nuevamente.');
}
})
.catch(function(err) {
resolve('No fue posible realizar la traducción, por favor intente nuevamente.');
});
});
};
}
module.exports.MachineDialog = MachineDialog;

Просмотреть файл

@ -0,0 +1,242 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { TimexProperty } = require('@microsoft/recognizers-text-data-types-timex-expression');
const { MessageFactory, InputHints } = require('botbuilder');
const { ChoiceFactory } = require('botbuilder-choices');
const { CardFactory } = require('botbuilder-core');
const { LuisRecognizer, QnAMaker } = require('botbuilder-ai');
const { ChoicePrompt, ComponentDialog, DialogSet, DialogTurnStatus, TextPrompt, WaterfallDialog } = require('botbuilder-dialogs');
const InitialCard = require('../resources/initialCard.json');
const MAIN_WATERFALL_DIALOG = 'mainWaterfallDialog';
const TEXT_PROMPT = 'TextPrompt';
const CHOICE_PROMPT = 'ChoicePrompt';
class MainDialog extends ComponentDialog {
constructor(luisRecognizer, qnaConfig, extraConfig, tableService, dialogs) {
super('MainDialog');
if (!qnaConfig) throw new Error('[QnaMakerBot]: Missing parameter. configuration is required');
if (!luisRecognizer) throw new Error('[MainDialog]: Missing parameter \'luisRecognizer\' is required');
this.luisRecognizer = luisRecognizer;
this.qnaMaker = new QnAMaker(qnaConfig, {});
this.extraConfig = extraConfig;
this.tableService = tableService;
// Define the main dialog and its related components.
this.addDialog(new TextPrompt(TEXT_PROMPT));
this.addDialog(new ChoicePrompt(CHOICE_PROMPT));
dialogs.forEach(dialog => {
this.addDialog(dialog);
});
this.addDialog(new WaterfallDialog(MAIN_WATERFALL_DIALOG, [
this.introStep.bind(this),
this.actStep.bind(this),
this.finalStep.bind(this)
]));
this.initialDialogId = MAIN_WATERFALL_DIALOG;
}
/**
* The run method handles the incoming activity (in the form of a TurnContext) and passes it through the dialog system.
* If no dialog is active, it will start the default dialog.
* @param {*} turnContext
* @param {*} accessor
*/
async run(turnContext, accessor) {
const dialogSet = new DialogSet(accessor);
dialogSet.add(this);
const dialogContext = await dialogSet.createContext(turnContext);
const results = await dialogContext.continueDialog();
if (results.status === DialogTurnStatus.empty) {
await dialogContext.beginDialog(this.id);
}
}
/**
* First step in the waterfall dialog. Prompts the user for a command.
* Currently, this expects a booking request, like "book me a flight from Paris to Berlin on march 22"
* Note that the sample LUIS model will only recognize Paris, Berlin, New York and London as airport cities.
*/
async introStep(stepContext) {
if (!this.luisRecognizer.isConfigured) {
const messageText = 'NOTE: LUIS is not configured. To enable all capabilities, add `LuisAppId`, `LuisAPIKey` and `LuisAPIHostName` to the .env file.';
await stepContext.context.sendActivity(messageText, null, InputHints.IgnoringInput);
return await stepContext.next();
}
const messageText = stepContext.options.restartMsg ? stepContext.options.restartMsg : '¿Qué puedo hacer para ayudarle el día de hoy?';
const promptMessage = MessageFactory.text(messageText, messageText, InputHints.ExpectingInput);
if (stepContext.options.restartMsg) {
return await stepContext.prompt('TextPrompt', { prompt: promptMessage });
} else {
return await stepContext.next();
}
}
/**
* Second step in the waterfall. This will use LUIS to attempt to extract the origin, destination and travel dates.
* Then, it hands off to the bookingDialog child dialog to collect any remaining details.
*/
async actStep(stepContext) {
const bookingDetails = {};
let question = '';
let emotion = 0.5;
const luisResult = await this.luisRecognizer.executeLuisQuery(stepContext.context);
const intent = LuisRecognizer.topIntent(luisResult).toLowerCase();
if (luisResult) {
question = luisResult.text;
if (luisResult.sentiment) { emotion = luisResult.sentiment.score; }
if (luisResult.alteredText !== undefined) { question = luisResult.alteredText; }
}
let getEmotionMessageText = "";
if (emotion < 0.4) {
getEmotionMessageText = "Detecto un sentimiento negativo!";
} else if (emotion > 0.6) {
getEmotionMessageText = ""; //Detecto un sentimiento positivo!
}
if (getEmotionMessageText !== "") {
await stepContext.context.sendActivity(getEmotionMessageText, getEmotionMessageText, InputHints.IgnoringInput);
}
switch (intent) {
case 'bookflight':
// Extract the values for the composite entities from the LUIS result.
const fromEntities = this.luisRecognizer.getFromEntities(luisResult);
const toEntities = this.luisRecognizer.getToEntities(luisResult);
// Show a warning for Origin and Destination if we can't resolve them.
await this.showWarningForUnsupportedCities(stepContext.context, fromEntities, toEntities);
// Initialize BookingDetails with any entities we may have found in the response.
bookingDetails.destination = toEntities.airport;
bookingDetails.origin = fromEntities.airport;
bookingDetails.travelDate = this.luisRecognizer.getTravelDate(luisResult);
// Run the BookingDialog passing in whatever details we have from the LUIS call, it will fill out the remainder.
return await stepContext.beginDialog('bookingDialog', bookingDetails);
case 'initial':
// directline, msteams, telegram, facebook, slack
const activity = stepContext.context.activity;
const channelId = activity.channelId;
const fromId = activity.from.id;
if (channelId === 'directline' && fromId !== 'miDemoBot.co') {
const initialCard = CardFactory.adaptiveCard(InitialCard);
return await stepContext.context.sendActivity({ attachments: [initialCard] });
} else {
const getInitialMessageText = ChoiceFactory.forChannel(stepContext.context, ['QnA', 'IoT', 'Traducir', 'Imagen', 'Salario', 'TRM', 'Certificado'], '¿Qué puedo hacer para ayudarle el día de hoy?', '¿Qué puedo hacer para ayudarle el día de hoy?');
return await stepContext.context.sendActivity(getInitialMessageText, getInitialMessageText, InputHints.IgnoringInput);
}
case 'certificate':
return await stepContext.beginDialog('certificateDialog', this.extraConfig);
case 'forex':
return await stepContext.beginDialog('forexDialog', this.extraConfig);
case 'general':
const qnaResults = await this.qnaMaker.getAnswers(stepContext.context);
let getQnAMessageText = 'Lo siento, no entendi su pregunta "' + question + '", puede intentarlo nuevamente?';
// If an answer was received from QnA Maker, send the answer back to the user.
if (qnaResults[0]) {
getQnAMessageText = qnaResults[0].answer;
}
await stepContext.context.sendActivity(getQnAMessageText, getQnAMessageText, InputHints.IgnoringInput);
break;
case 'machine':
return await stepContext.beginDialog('machineDialog', this.extraConfig);
case 'sensor':
return await stepContext.beginDialog('sensorDialog', { question, tableService: this.tableService });
case 'translate':
return await stepContext.beginDialog('translateDialog', this.extraConfig);
case 'vision':
return await stepContext.beginDialog('visionDialog', this.extraConfig);
case 'none':
if (stepContext.context.activity.attachments && stepContext.context.activity.attachments.length > 0) {
return await stepContext.beginDialog('visionDialog', this.extraConfig);
}
if (question !== '') {
const getNoneMessageText = 'Lo siento, no entendi su pregunta "' + question + '", puede intentarlo nuevamente?';
await stepContext.context.sendActivity(getNoneMessageText, getNoneMessageText, InputHints.IgnoringInput);
}
break;
default:
// Catch all for unhandled intents
const didntUnderstandMessageText = 'Lo siento, no entendi su pregunta "' + question + '", puede intentarlo nuevamente? (intent: ' + intent + ')';
await stepContext.context.sendActivity(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IgnoringInput);
}
return await stepContext.next();
}
/**
* Shows a warning if the requested From or To cities are recognized as entities but they are not in the Airport entity list.
* In some cases LUIS will recognize the From and To composite entities as a valid cities but the From and To Airport values
* will be empty if those entity values can't be mapped to a canonical item in the Airport.
*/
async showWarningForUnsupportedCities(context, fromEntities, toEntities) {
const unsupportedCities = [];
if (fromEntities.from && !fromEntities.airport) {
unsupportedCities.push(fromEntities.from);
}
if (toEntities.to && !toEntities.airport) {
unsupportedCities.push(toEntities.to);
}
if (unsupportedCities.length) {
const messageText = `Sorry but the following airports are not supported: ${ unsupportedCities.join(', ') }`;
await context.sendActivity(messageText, messageText, InputHints.IgnoringInput);
}
}
async filesAttached(context) {
const unsupportedCities = [];
if (fromEntities.from && !fromEntities.airport) {
unsupportedCities.push(fromEntities.from);
}
if (toEntities.to && !toEntities.airport) {
unsupportedCities.push(toEntities.to);
}
if (unsupportedCities.length) {
const messageText = `Sorry but the following airports are not supported: ${ unsupportedCities.join(', ') }`;
await context.sendActivity(messageText, messageText, InputHints.IgnoringInput);
}
}
/**
* This is the final step in the main waterfall dialog.
* It wraps up the sample "book a flight" interaction with a simple confirmation.
*/
async finalStep(stepContext) {
// If the child dialog ("bookingDialog") was cancelled or the user failed to confirm, the Result here will be null.
if (stepContext.result) {
const result = stepContext.result;
// Now we have all the booking details.
// This is where calls to the booking AOU service or database would go.
// If the call to the booking service was successful tell the user.
const timeProperty = new TimexProperty(result.travelDate);
const travelDateMsg = timeProperty.toNaturalLanguage(new Date(Date.now()));
const msg = `I have you booked to ${ result.destination } from ${ result.origin } on ${ travelDateMsg }.`;
await stepContext.context.sendActivity(msg, msg, InputHints.IgnoringInput);
}
// Restart the main dialog with a different message the second time around
// return await stepContext.replaceDialog(this.initialDialogId, { restartMsg: 'What else can I do for you?' });
return await stepContext.next();
}
}
module.exports.MainDialog = MainDialog;

Просмотреть файл

@ -0,0 +1,102 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { InputHints } = require('botbuilder');
const { TextPrompt, WaterfallDialog } = require('botbuilder-dialogs');
const { CardFactory } = require('botbuilder-core');
const { ChoiceFactory } = require('botbuilder-choices');
const { CancelAndHelpDialog } = require('./cancelAndHelpDialog');
const request = require('request-promise-native');
const SensorCard = require('../resources/sensorCard.json');
const TEXT_PROMPT = 'textPrompt';
const WATERFALL_DIALOG = 'waterfallDialog';
class SensorDialog extends CancelAndHelpDialog {
constructor(id) {
super(id || 'sensorDialog');
this.addDialog(new TextPrompt(TEXT_PROMPT))
.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.initialStep.bind(this),
this.finalStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
async initialStep(stepContext) {
const question = stepContext.options.question;
if (question !== '') {
const sensor = question.toLowerCase();
let getSensorMessageText = '',
tableMsg = '',
tableKey = '',
tableRow = '',
tableSuf = '';
if (sensor.includes('temp')) {
tableMsg = 'La temperatura actual es ';
tableKey = 'iot-devkit';
tableRow = 'temp';
tableSuf = '°';
} else if (sensor.includes('hume')) {
tableMsg = 'La humedad actual es ';
tableKey = 'iot-devkit';
tableRow = 'humi';
tableSuf = '%';
} else if (sensor.includes('pers')) {
tableMsg = 'La cantidad de personas es ';
tableKey = 'rpi-demo';
tableRow = 'people';
} else if (sensor.includes('muje')) {
tableMsg = 'La cantidad de mujeres es ';
tableKey = 'rpi-demo';
tableRow = 'male';
} else if (sensor.includes('homb')) {
tableMsg = 'La cantidad de hombres es ';
tableKey = 'rpi-demo';
tableRow = 'female';
} else {
const activity = stepContext.context.activity;
const channelId = activity.channelId;
const fromId = activity.from.id;
if (channelId === 'directline' && fromId !== 'miDemoBot.co') {
const sensorCard = CardFactory.adaptiveCard(SensorCard);
return await stepContext.context.sendActivity({ attachments: [sensorCard] });
} else {
const getInitialMessageText = ChoiceFactory.forChannel(stepContext.context, ['Temperatura', 'Humedad', 'Personas', 'Hombres', 'Mujeres'], 'Seleccione un sensor:', 'Seleccione un sensor:');
return await stepContext.context.sendActivity(getInitialMessageText, getInitialMessageText, InputHints.IgnoringInput);
}
}
if (tableKey !== '') {
const tableService = stepContext.options.tableService;
getSensorMessageText = await this.getTableStorageResult(tableService, tableKey, tableRow, tableMsg, tableSuf);
}
return await stepContext.context.sendActivity(getSensorMessageText, getSensorMessageText, InputHints.IgnoringInput);
}
}
/**
* Complete the interaction and end the dialog.
*/
async finalStep(stepContext) {
// const msg = await this.getTranslation(stepContext);
// await stepContext.context.sendActivity(msg, msg, InputHints.IgnoringInput);
return await stepContext.endDialog();
}
async getTableStorageResult(tableService, tableKey, tableRow, tableMsg, tableSuf) {
return new Promise((resolve, reject) => {
let storageResult = 'De momento no estoy monitoreando ningun sensor';
tableService.retrieveEntity('iothub', tableKey, 'state', function(error, result) {
if (!error) { storageResult = tableMsg + result[tableRow]._ + tableSuf; }
resolve(storageResult);
});
});
};
}
module.exports.SensorDialog = SensorDialog;

Просмотреть файл

@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { InputHints, MessageFactory } = require('botbuilder');
const { TextPrompt, WaterfallDialog } = require('botbuilder-dialogs');
const { CancelAndHelpDialog } = require('./cancelAndHelpDialog');
const request = require('request-promise-native');
const TEXT_PROMPT = 'textPrompt';
const WATERFALL_DIALOG = 'waterfallDialog';
class TranslateDialog extends CancelAndHelpDialog {
constructor(id) {
super(id || 'translateDialog');
this.addDialog(new TextPrompt(TEXT_PROMPT))
.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.initialStep.bind(this),
this.finalStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
async initialStep(stepContext) {
const messageText = '¿Que texto quiere traducir?';
const msg = MessageFactory.text(messageText, messageText, InputHints.ExpectingInput);
return await stepContext.prompt(TEXT_PROMPT, { prompt: msg });
}
/**
* Complete the interaction and end the dialog.
*/
async finalStep(stepContext) {
const msg = await this.getTranslation(stepContext);
await stepContext.context.sendActivity(msg, msg, InputHints.IgnoringInput);
return await stepContext.endDialog();
}
async getTranslation(stepContext) {
return new Promise((resolve, reject) => {
const extraConfig = stepContext.options;
const optsMsg = {
method: 'POST',
uri: extraConfig.TranslateEndpoint + "/translate?api-version=3.0&to=es&to=en&to=pt&to=fr&to=it&to=ja",
headers: {
'Ocp-Apim-Subscription-Key': extraConfig.TranslateKey
},
body: [{
'text': stepContext.result
}],
json: true
};
request(optsMsg)
.then(function(data) {
data.forEach(result => {
let language = '';
result.translations.forEach(t => {
language += t.to + ' - ' + t.text + '\n\n';
});
let lang = '';
if (result.detectedLanguage.language === 'es') {
lang = 'Español';
} else if (result.detectedLanguage.language === 'en') {
lang = 'Ingles';
} else if (result.detectedLanguage.language === 'pt') {
lang = 'Portuges';
} else if (result.detectedLanguage.language === 'fr') {
lang = 'Frances';
} else if (result.detectedLanguage.language === 'it') {
lang = 'Italiano';
} else {
lang = result.detectedLanguage.language;
}
language = 'Traducido del ' + lang + '\n\nTraducciones\n\n' + language;
resolve(language);
});
})
.catch(function(err) {
resolve('No fue posible realizar la traducción, por favor intente nuevamente.');
});
});
};
}
module.exports.TranslateDialog = TranslateDialog;

Просмотреть файл

@ -0,0 +1,322 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { ActivityTypes, InputHints } = require('botbuilder');
const { AttachmentPrompt, WaterfallDialog } = require('botbuilder-dialogs');
const { CancelAndHelpDialog } = require('./cancelAndHelpDialog');
const request = require('request-promise-native');
const ATTACHMENT_PROMPT = 'attachmentPrompt';
const WATERFALL_DIALOG = 'waterfallDialog';
class VisionDialog extends CancelAndHelpDialog {
constructor(id) {
super(id || 'visionDialog');
this.addDialog(new AttachmentPrompt(ATTACHMENT_PROMPT))
.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.firstStep.bind(this),
this.finalStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
async firstStep(stepContext) {
if (stepContext.context.activity.attachments && stepContext.context.activity.attachments.length > 0) {
return await stepContext.next();
}
const promptOptions = { prompt: 'Por favor adjunte la imagen' };
return await stepContext.prompt(ATTACHMENT_PROMPT, promptOptions);
}
/**
* Complete the interaction and end the dialog.
*/
async finalStep(stepContext) {
const attachments = stepContext.context.activity.attachments;
for await (const file of attachments) {
let err = "";
let msg = 'Por favor adjunta una imagen (enviaste: ' + file.contentType + ')';
let resp = { category: '', caption: '', tags: '' };
if (file.contentType.includes('image')) {
const reply = { type: ActivityTypes.Message };
reply.text = 'Analizando la imagen usando: Computer Vision API...';
reply.attachments = [file];
await stepContext.context.sendActivity(reply);
resp = await this.getComputerVisionResult(stepContext, file);
if (resp === "InvalidImageUrl") {
err = resp;
resp = { category: '-', caption: '-', tags: '-' };
}
msg = 'Categoría: ' + resp.category + '\n\nDescripción: ' + resp.caption + '\n\nEtiquetas: ' + resp.tags;
}
await stepContext.context.sendActivity(msg, msg, InputHints.IgnoringInput);
if (err === "InvalidImageUrl") {
break;
}
if (resp.category.includes('gente') || resp.tags.includes('persona')) {
let msg2 = 'Analizando la imagen usando: Face API...';
await stepContext.context.sendActivity(msg2, msg2, InputHints.IgnoringInput);
const resp2 = await this.getFaceDetectResult(stepContext, file);
msg = 'Género: ' + resp2.gender + '\n\nEdad: ' + resp2.age + '\n\nEmoción: ' + resp2.emotion + '\n\nId: ' + resp2.id;
await stepContext.context.sendActivity(msg, msg, InputHints.IgnoringInput);
msg2 = 'Identificando a la persona usando: Face API...';
await stepContext.context.sendActivity(msg2, msg2, InputHints.IgnoringInput);
msg = await this.getFaceIdentifyResult(stepContext, resp2.id);
await stepContext.context.sendActivity(msg, msg, InputHints.IgnoringInput);
} else if (resp.category.includes('planta') || resp.tags.includes('fruta') || resp.tags.includes('comida')) {
let msg2 = 'Analizando la imagen usando: Custom Vision API (Cafe)...';
await stepContext.context.sendActivity(msg2, msg2, InputHints.IgnoringInput);
const resp2 = await this.getCustomVisionResult(stepContext, file);
msg = 'Café: ' + resp2.cofee.toLocaleString() + '%\n\nVariedad: ' + (resp2.arabica > resp2.robusta ? 'Arabica ' + resp2.arabica.toLocaleString() + '% (Robusta ' + resp2.robusta.toLocaleString() + '%)' : 'Robusta ' + resp2.robusta.toLocaleString() + '% (Arabica ' + resp2.arabica.toLocaleString() + '%)');
msg = msg + '\n\nEstado: ' + (resp2.tree > resp2.toasted ? 'Arbol ' + resp2.tree.toLocaleString() + '% (Tostado ' + resp2.toasted.toLocaleString() + '%)' : 'Tostado ' + resp2.toasted.toLocaleString() + '% (Arbol ' + resp2.tree.toLocaleString() + '%)');
await stepContext.context.sendActivity(msg, msg, InputHints.IgnoringInput);
} else if (resp.tags.includes('alimentos')) {
let msg2 = 'Analizando la imagen usando: Custom Vision API (Queso)...';
await stepContext.context.sendActivity(msg2, msg2, InputHints.IgnoringInput);
const resp2 = await this.getCustomVision2Result(stepContext, file);
msg = 'Queso: ' + resp2.cheese.toLocaleString() + '%\n\nMarca: ' + (resp2.brand1 > resp2.brand2 ? 'Alpina ' + resp2.brand1.toLocaleString() + '% (Colanta ' + resp2.brand2.toLocaleString() + '%)' : 'Colanta ' + resp2.brand2.toLocaleString() + '% (Alpina ' + resp2.brand1.toLocaleString() + '%)');
msg = msg + '\n\nTipo:\n\nCampesino - ' + resp2.type1.toLocaleString() + '% \n\nMozarella - ' + resp2.type2.toLocaleString() + '% \n\nParmesano - ' + resp2.type3.toLocaleString() + '% \n\nQuesito - ' + resp2.type4.toLocaleString() + '% \n\nSabana - ' + resp2.type5.toLocaleString() + '%';
await stepContext.context.sendActivity(msg, msg, InputHints.IgnoringInput);
}
}
return await stepContext.endDialog();
}
async getComputerVisionResult(stepContext, file) {
return new Promise((resolve, reject) => {
const extraConfig = stepContext.options;
const params = 'visualFeatures=Categories,Description&language=es';
const body = {
'url': file.contentUrl // 'https://sc01.alicdn.com/kf/UTB82NebfFfJXKJkSamHq6zLyVXa3/Brazil-Nice-Quality-Bulk-Roasted-Arabica-Coffee.jpg_350x350.jpg'
};
const optsMsg = {
method: 'POST',
uri: extraConfig.ComputerVisionEndpoint + '/vision/v2.1/analyze' + '?' + params,
headers: {
'Ocp-Apim-Subscription-Key': extraConfig.ComputerVisionApiKey
},
body,
json: true
};
request(optsMsg)
.then(function(data) {
let category = '-';
let tags = '-';
let caption = '-';
if (data.categories) {
category = data.categories[0].name;
}
if (data.description) {
if (data.description.tags) {
tags = data.description.tags;
}
if (data.description.captions) {
if (data.description.captions.length > 0) {
caption = data.description.captions[0].text;
}
}
}
resolve({ category, caption, tags });
})
.catch(function(err) {
resolve(err.error.code);
// resolve('No fue posible realizar la traducción, por favor intente nuevamente.');
});
});
};
async getCustomVisionResult(stepContext, file) {
return new Promise((resolve, reject) => {
const extraConfig = stepContext.options;
const body = {
'url': file.contentUrl // 'https://sc01.alicdn.com/kf/UTB82NebfFfJXKJkSamHq6zLyVXa3/Brazil-Nice-Quality-Bulk-Roasted-Arabica-Coffee.jpg_350x350.jpg'
};
const optsMsg = {
method: 'POST',
uri: extraConfig.CustomVisionEndpoint + '/classify/iterations/cafe/url',
headers: {
'Prediction-Key': extraConfig.CustomVisionApiKey
},
body,
json: true
};
request(optsMsg)
.then(function(data) {
let cofee = 0
let tree = 0
let toasted = 0
let arabica = 0
let robusta = 0
for (const prediction of data.predictions) {
const prob = Math.round(prediction.probability * 10000) / 100;
if (prediction.tagName === "cafe") {
cofee = prob;
} else if (prediction.tagName === "arbol") {
tree = prob;
} else if (prediction.tagName === "tostado") {
toasted = prob;
} else if (prediction.tagName === "arabica") {
arabica = prob;
} else if (prediction.tagName === "robusta") {
robusta = prob;
}
}
resolve({ cofee, tree, toasted, arabica, robusta });
})
.catch(function(err) {
resolve(err.message);
// resolve('No fue posible realizar la traducción, por favor intente nuevamente.');
});
});
};
async getCustomVision2Result(stepContext, file) {
return new Promise((resolve, reject) => {
const extraConfig = stepContext.options;
const body = {
'url': file.contentUrl // 'https://sc01.alicdn.com/kf/UTB82NebfFfJXKJkSamHq6zLyVXa3/Brazil-Nice-Quality-Bulk-Roasted-Arabica-Coffee.jpg_350x350.jpg'
};
const optsMsg = {
method: 'POST',
uri: extraConfig.CustomVisionEndpoint2 + '/classify/iterations/queso/url',
headers: {
'Prediction-Key': extraConfig.CustomVisionApiKey
},
body,
json: true
};
request(optsMsg)
.then(function(data) {
let cheese = 0
let brand1 = 0
let brand2 = 0
let type1 = 0
let type2 = 0
let type3 = 0
let type4 = 0
let type5 = 0
for (const prediction of data.predictions) {
const prob = Math.round(prediction.probability * 10000) / 100;
if (prediction.tagName === "queso") {
cheese = prob;
} else if (prediction.tagName === "alpina") {
brand1 = prob;
} else if (prediction.tagName === "colanta") {
brand2 = prob;
} else if (prediction.tagName === "campesino") {
type1 = prob;
} else if (prediction.tagName === "mozarella") {
type2 = prob;
} else if (prediction.tagName === "parmesano") {
type3 = prob;
} else if (prediction.tagName === "quesito") {
type4 = prob;
} else if (prediction.tagName === "parmesano") {
type5 = prob;
}
}
resolve({ cheese, brand1, brand2, type1, type2, type3, type4, type5 });
})
.catch(function(err) {
resolve(err.message);
// resolve('No fue posible realizar la traducción, por favor intente nuevamente.');
});
});
};
async getFaceDetectResult(stepContext, file) {
return new Promise((resolve, reject) => {
const extraConfig = stepContext.options;
const params = 'returnFaceId=true&returnFaceAttributes=age,gender,emotion';
const body = {
'url': file.contentUrl
// 'https://www.marieclaire.com.mx/wp-content/uploads/2018/06/Artista-digital-causa-furor-al-combinar-las-caras-m%C3%A1s-famosas-de-Hollywood-1024x552.jpg' //
};
const optsMsg = {
method: 'POST',
uri: extraConfig.FaceEndpoint + '/detect?' + params,
headers: {
'Ocp-Apim-Subscription-Key': extraConfig.FaceApiKey
},
body,
json: true
};
request(optsMsg)
.then(function(data) {
let id = '-';
let gender = '-';
let age = '-';
let emotion = '-';
const attribs = data[0].faceAttributes;
if (attribs) {
id = data[0].faceId;
gender = (attribs.gender === 'male' ? 'hombre' : 'mujer');
age = attribs.age;
if (attribs.emotion.surprise > 0.5) {
emotion = 'negativa';
} else if (attribs.emotion.disgust > 0.5) {
emotion = 'negativa';
} else if (attribs.emotion.neutral > 0.5) {
emotion = 'neutral';
} else if (attribs.emotion.contempt > 0.5) {
emotion = 'negativa';
} else if (attribs.emotion.fear > 0.5) {
emotion = 'negativa';
} else if (attribs.emotion.happiness > 0.5) {
emotion = 'positiva';
} else if (attribs.emotion.sadness > 0.5) {
emotion = 'negativa';
} else if (attribs.emotion.anger > 0.5) {
emotion = 'negativa';
}
}
resolve({ id, gender, age, emotion });
})
.catch(function(err) {
resolve(err.message);
// resolve('No fue posible realizar la traducción, por favor intente nuevamente.');
});
});
};
async getFaceIdentifyResult(stepContext, id) {
return new Promise((resolve, reject) => {
const extraConfig = stepContext.options;
const body = {
'personGroupId': extraConfig.FaceGroupId,
'faceIds': [id],
};
const optsMsg = {
method: 'POST',
uri: extraConfig.FaceEndpoint + '/identify',
headers: {
'Ocp-Apim-Subscription-Key': extraConfig.FaceApiKey
},
body,
json: true
};
request(optsMsg)
.then(function(data) {
let resp = 'Persona no identificada';
const personId = extraConfig.FacePersonId;
const faceId = data[0];
if (faceId && faceId.candidates.length > 0) {
const canditate = faceId.candidates[0];
if (canditate.personId === personId && canditate.confidence > 0.5) {
resp = 'Persona Identificada: Fernando'
}
}
resolve(resp);
})
.catch(function(err) {
resolve(err.message);
});
});
};
}
module.exports.VisionDialog = VisionDialog;

Просмотреть файл

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { ActivityHandler, ActivityTypes } = require('botbuilder');
class TeamsActivityHandler extends ActivityHandler {
constructor() {
super();
this.onUnrecognizedActivityType(async(context, next) => {
const runDialogs = async() => {
await this.handle(context, 'Dialog', async() => {
// noop
});
};
switch (context.activity.type) {
case ActivityTypes.Invoke:
await this.handle(context, 'Invoke', runDialogs);
break;
default:
await next();
}
});
}
onInvoke(handler) {
return this.on('Invoke', handler);
}
}
module.exports.TeamsActivityHandler = TeamsActivityHandler;

163
bot-js/index.js Normal file
Просмотреть файл

@ -0,0 +1,163 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// index.js is used to setup and configure your bot
// Import required packages
const path = require('path');
const restify = require('restify');
const azureStorage = require('azure-storage');
// Import required bot services. See https://aka.ms/bot-services to learn more about the different parts of a bot.
const { BotFrameworkAdapter, ConversationState, InputHints, MemoryStorage, UserState } = require('botbuilder');
const { BotLuisRecognizer } = require('./dialogs/BotLuisRecognizer');
// This bot's main dialog.
const { DialogAndWelcomeBot } = require('./bots/dialogAndWelcomeBot');
const { MainDialog } = require('./dialogs/mainDialog');
// the bot's booking dialog
const { BookingDialog } = require('./dialogs/bookingDialog');
const BOOKING_DIALOG = 'bookingDialog';
// Certificate dialog
const { CertificateDialog } = require('./dialogs/certificateDialog');
const CERTIFICATE_DIALOG = 'certificateDialog';
// Sensor dialog
const { SensorDialog } = require('./dialogs/sensorDialog');
const SENSOR_DIALOG = 'sensorDialog';
// Forex dialog
const { ForexDialog } = require('./dialogs/forexDialog');
const FOREX_DIALOG = 'forexDialog';
// Translation dialog
const { TranslateDialog } = require('./dialogs/translateDialog');
const TRANSLATE_DIALOG = 'translateDialog';
// Machine Learning dialog
const { MachineDialog } = require('./dialogs/machineDialog');
const MACHINE_DIALOG = 'machineDialog';
// Computer Vision dialog
const { VisionDialog } = require('./dialogs/visionDialog');
const VISION_DIALOG = 'visionDialog';
// Note: Ensure you have a .env file and include LuisAppId, LuisAPIKey and LuisAPIHostName.
const ENV_FILE = path.join(__dirname, '.env');
require('dotenv').config({ path: ENV_FILE });
// Create adapter.
// See https://aka.ms/about-bot-adapter to learn more about adapters.
const adapter = new BotFrameworkAdapter({
appId: process.env.MicrosoftAppId,
appPassword: process.env.MicrosoftAppPassword
});
// Catch-all for errors.
adapter.onTurnError = async(context, error) => {
// This check writes out errors to console log
// NOTE: In production environment, you should consider logging this to Azure
// application insights.
console.error(`\n [onTurnError]: ${ error }`);
// Send a message to the user
const onTurnErrorMessage = 'Disculpas, parece que algo no ha salido bien!';
await context.sendActivity(onTurnErrorMessage, onTurnErrorMessage, InputHints.ExpectingInput);
// Clear out state
await conversationState.delete(context);
};
// Define a state store for your bot. See https://aka.ms/about-bot-state to learn more about using MemoryStorage.
// A bot requires a state store to persist the dialog and user state between messages.
// For local development, in-memory storage is used.
// CAUTION: The Memory Storage used here is for local bot debugging only. When the bot
// is restarted, anything stored in memory will be gone.
const memoryStorage = new MemoryStorage();
const conversationState = new ConversationState(memoryStorage);
const userState = new UserState(memoryStorage);
// If configured, pass in the BotLuisRecognizer. (Defining it externally allows it to be mocked for tests)
const {
BingAPIKey,
ComputerVisionEndpoint,
ComputerVisionApiKey,
CustomVisionEndpoint,
CustomVisionEndpoint2,
CustomVisionApiKey,
FaceEndpoint,
FaceApiKey,
FaceGroupId,
FacePersonId,
ForexEndpoint,
ForexKey,
ForexUrl,
IoTStorageAccount,
IoTStorageAccessKey,
IoTStorageConnectionString,
LuisAppId,
LuisAPIKey,
LuisAPIHostName,
MLApiKey,
MLApiUrl,
QnAKnowledgebaseId,
QnAAuthKey,
QnAEndpointHostName,
TranslateEndpoint,
TranslateKey
} = process.env;
const luisConfig = { applicationId: LuisAppId, endpointKey: LuisAPIKey, endpoint: LuisAPIHostName };
const luisOption = { bingSpellCheckSubscriptionKey: BingAPIKey, spellCheck: true };
const luisRecognizer = new BotLuisRecognizer(luisConfig, luisOption);
const tableService = azureStorage.createTableService(IoTStorageAccount, IoTStorageAccessKey, IoTStorageConnectionString);
// Map knowledge base endpoint values from .env file into the required format for `QnAMaker`.
const qnaConfig = { knowledgeBaseId: QnAKnowledgebaseId, endpointKey: QnAAuthKey, host: QnAEndpointHostName };
const extraConfig = {
ComputerVisionEndpoint,
ComputerVisionApiKey,
CustomVisionEndpoint,
CustomVisionEndpoint2,
CustomVisionApiKey,
FaceEndpoint,
FaceApiKey,
FaceGroupId,
FacePersonId,
ForexEndpoint,
ForexKey,
ForexUrl,
MLApiKey,
MLApiUrl,
TranslateEndpoint,
TranslateKey
}
// Create the main dialog.
const bookingDialog = new BookingDialog(BOOKING_DIALOG);
const certificateDialog = new CertificateDialog(CERTIFICATE_DIALOG);
const machineDialog = new MachineDialog(MACHINE_DIALOG);
const sensorDialog = new SensorDialog(SENSOR_DIALOG);
const forexDialog = new ForexDialog(FOREX_DIALOG);
const translateDialog = new TranslateDialog(TRANSLATE_DIALOG);
const visionDialog = new VisionDialog(VISION_DIALOG);
const dialogs = [bookingDialog, certificateDialog, forexDialog, machineDialog, sensorDialog, translateDialog, visionDialog];
const dialog = new MainDialog(luisRecognizer, qnaConfig, extraConfig, tableService, dialogs);
const bot = new DialogAndWelcomeBot(conversationState, userState, dialog);
// Create HTTP server
const server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log(`\n${ server.name } listening to ${ server.url }`);
});
// Listen for incoming activities and route them to your bot main dialog.
server.post('/api/messages', (req, res) => {
// Route received a request to adapter for processing
adapter.processActivity(req, res, async(turnContext) => {
// route to bot activity handler.
await bot.run(turnContext);
});
});

5974
bot-js/package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

39
bot-js/package.json Normal file
Просмотреть файл

@ -0,0 +1,39 @@
{
"name": "midemo",
"version": "1.0.0",
"description": "Azure Demos",
"author": "Generated using Microsoft Bot Builder Yeoman generator v4.5.0",
"license": "MIT",
"main": "index.js",
"scripts": {
"start": "node ./index.js",
"watch": "nodemon ./index.js",
"lint": "eslint .",
"test": "nodemon ./index.js"
},
"repository": {
"type": "git",
"url": "https://github.com"
},
"dependencies": {
"@microsoft/microsoft-graph-client": "^2.0.0",
"@microsoft/recognizers-text-data-types-timex-expression": "1.1.4",
"azure-storage": "^2.10.3",
"botbuilder": "~4.5.1",
"botbuilder-ai": "~4.5.1",
"botbuilder-choices": "^4.0.0-preview1.2",
"botbuilder-dialogs": "~4.5.1",
"dotenv": "~8.0.0",
"request-promise-native": "^1.0.7",
"restify": "~8.3.3"
},
"devDependencies": {
"eslint": "^6.0.1",
"eslint-config-standard": "^13.0.1",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^9.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"nodemon": "^1.19.2"
}
}

Двоичные данные
bot-js/resources/cert-ingresos.pdf Normal file

Двоичный файл не отображается.

Двоичные данные
bot-js/resources/cert-nomina.pdf Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,48 @@
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [{
"type": "TextBlock",
"isSubtle": true,
"text": "¿Qué puedo hacer para ayudarle el día de hoy?",
"wrap": true,
"maxLines": 0
}],
"actions": [{
"type": "Action.Submit",
"title": "Preguntas frecuentes",
"data": "¿Qué puedo preguntar?"
},
{
"type": "Action.Submit",
"title": "Monitorear sensores",
"data": "¿Qué sensores puedo monitorear?"
},
{
"type": "Action.Submit",
"title": "Traducir un texto",
"data": "¿Puede traducir un texto?"
},
{
"type": "Action.Submit",
"title": "Analizar una imagen",
"data": "¿Puede analizar una imagen?"
},
{
"type": "Action.Submit",
"title": "Predecir mi salario",
"data": "¿Puede predecir mi salario?"
},
{
"type": "Action.Submit",
"title": "Obtener tasa de cambio",
"data": "¿Puede indicarme la tasa de cambio del día?"
},
{
"type": "Action.Submit",
"title": "Solicitar certificado",
"data": "¿Puedo solicitar mi certificado?"
}
]
}

Просмотреть файл

@ -0,0 +1,38 @@
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [{
"type": "TextBlock",
"isSubtle": true,
"text": "Seleccione un sensor:",
"wrap": true,
"maxLines": 0
}],
"actions": [{
"type": "Action.Submit",
"title": "Temperatura",
"data": "Temperatura"
},
{
"type": "Action.Submit",
"title": "Humedad",
"data": "Humedad"
},
{
"type": "Action.Submit",
"title": "Personas",
"data": "Personas"
},
{
"type": "Action.Submit",
"title": "Mujeres",
"data": "Mujeres"
},
{
"type": "Action.Submit",
"title": "Hombres",
"data": "Hombres"
}
]
}

Просмотреть файл

@ -0,0 +1,75 @@
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [{
"type": "ColumnSet",
"columns": [{
"type": "Column",
"items": [{
"type": "Image",
"url": "https://www.midemo.co/img/favicon.png"
}],
"width": "auto"
},
{
"type": "Column",
"items": [{
"type": "TextBlock",
"size": "medium",
"weight": "bolder",
"text": "Bienvenidos al Bot de MiDemo.co",
"wrap": true,
"maxLines": 0
}],
"width": "stretch",
"verticalContentAlignment": "Center"
}
]
},
{
"type": "TextBlock",
"isSubtle": true,
"text": "¿Qué puedo hacer para ayudarle el día de hoy?",
"size": "medium",
"wrap": true,
"maxLines": 0
}
],
"actions": [{
"type": "Action.Submit",
"title": "Preguntas frecuentes",
"data": "¿Qué puedo preguntar?"
},
{
"type": "Action.Submit",
"title": "Monitorear sensores",
"data": "¿Qué sensores puedo monitorear?"
},
{
"type": "Action.Submit",
"title": "Traducir un texto",
"data": "¿Puede traducir un texto?"
},
{
"type": "Action.Submit",
"title": "Analizar una imagen",
"data": "¿Puede analizar una imagen?"
},
{
"type": "Action.Submit",
"title": "Predecir mi salario",
"data": "¿Puede predecir mi salario?"
},
{
"type": "Action.Submit",
"title": "Obtener tasa de cambio",
"data": "¿Puede indicarme la tasa de cambio del día?"
},
{
"type": "Action.Submit",
"title": "Solicitar certificado",
"data": "¿Puedo solicitar mi certificado?"
}
]
}

61
bot-js/web.config Normal file
Просмотреть файл

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This configuration file is required if iisnode is used to run node processes behind
IIS or IIS Express. For more information, visit:
https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config
-->
<configuration>
<system.webServer>
<!-- Visit http://blogs.msdn.com/b/windowsazure/archive/2013/11/14/introduction-to-websockets-on-windows-azure-web-sites.aspx for more information on WebSocket support -->
<webSocket enabled="false" />
<handlers>
<!-- Indicates that the server.js file is a node.js site to be handled by the iisnode module -->
<add name="iisnode" path="index.js" verb="*" modules="iisnode"/>
</handlers>
<rewrite>
<rules>
<!-- Do not interfere with requests for node-inspector debugging -->
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^index.js\/debug[\/]?" />
</rule>
<!-- First we consider whether the incoming URL matches a physical file in the /public folder -->
<rule name="StaticContent">
<action type="Rewrite" url="public{REQUEST_URI}"/>
</rule>
<!-- All other URLs are mapped to the node.js site entry point -->
<rule name="DynamicContent">
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
</conditions>
<action type="Rewrite" url="index.js"/>
</rule>
</rules>
</rewrite>
<!-- 'bin' directory has no special meaning in node.js and apps can be placed in it -->
<security>
<requestFiltering>
<hiddenSegments>
<remove segment="bin"/>
</hiddenSegments>
</requestFiltering>
</security>
<!-- Make sure error responses are left untouched -->
<httpErrors existingResponse="PassThrough" />
<!--
You can control how Node is hosted within IIS using the following options:
* watchedFiles: semi-colon separated list of files that will be watched for changes to restart the server
* node_env: will be propagated to node as NODE_ENV environment variable
* debuggingEnabled - controls whether the built-in debugger is enabled
See https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config for a full list of options
-->
<!--<iisnode watchedFiles="web.config;*.js"/>-->
</system.webServer>
</configuration>