зеркало из https://github.com/microsoft/midemo.git
Initial Commit - MiDemo Bot (Node.js)
This commit is contained in:
Родитель
0a1ba16a8a
Коммит
ec105a2172
|
@ -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"]
|
||||
}
|
||||
};
|
|
@ -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
|
|
@ -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;
|
|
@ -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;
|
|
@ -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);
|
||||
});
|
||||
});
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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"
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
Двоичный файл не отображается.
|
@ -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?"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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>
|
Загрузка…
Ссылка в новой задаче