diff --git a/README.md b/README.md
index caec0bf..fc36472 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,7 @@ Prerequests for step 3 (run on server):
requires running mongodb service
Database (tellall) with collection (skill_list)
- * Insert dummy skill with : db.skill_list.insert({"skillID" : "amzn1.ask.skill.efbf0564-a732-4ba9-958f-57939138adae", "intents" : [ { "intentName" : "GetFirstQuestion", "questions" : [ "tell me something about projects", "tell me all about projects" ], "answer" : "blablabla bla bla" }, { "intentName" : "GetThirdQuestion", "questions" : [ "Give me third question" ], "answer" : "This is answer to the third question" } ], "invocationName" : "Saburly", "invocationAnswer" : "We are Saburly team one" })
+ * Insert dummy skill with : db.skill_list.insert({"skillID" : "amzn1.ask.skill.efbf0564-a732-4ba9-958f-57939138adae", "intents" : [ { "intentName" : "GetFirstQuestion", "questionExplanation" : "", "questions" : [ "tell me something about projects", "tell me all about projects" ], "answer" : "blablabla bla bla" }, { "intentName" : "GetThirdQuestion", "questionExplanation" : "", "questions" : [ "Give me third question" ], "answer" : "This is answer to the third question" } ], "invocationName" : "Saburly", "invocationAnswer" : "We are Saburly team one" })
*obtain _id and change in web/src/App.js, and also skill_db_id in backend/config.js
*enter web/ dir and run "npm run build"
diff --git a/backend/config/constants.js b/backend/config/constants.js
index 6674956..7c3a7df 100644
--- a/backend/config/constants.js
+++ b/backend/config/constants.js
@@ -17,7 +17,7 @@ constants.apiResultCodes = {
AMAZON_FAIL:2, //amazon api doesn't work
DATABASE_ERROR:3,
NO_SKILL:4,
- INCONSISTEN_STATE:5,
+ INCONSISTENT_STATE:5,
}
constants.HTTPResultCodes = {
@@ -27,10 +27,16 @@ constants.HTTPResultCodes = {
constants.SKILL_ID_LENGTH = 24;
constants.voiceResponseStrings = {
- QUESTION_NOT_FOUND : 'Sorry, I didnt understan',
+ QUESTION_NOT_FOUND : 'Sorry, I didnt understand',
GENERIC_CONTINUE : 'Would you like to continue'
}
+//Timing is given in [ms]
+constants.voiceResponseTimings = {
+ PAUSE_BETWEEN_QUESTIONS : 650,
+ PAUSE_AFTER_WELCOME_MESSAGE : 650,
+}
+
module.exports = constants;
\ No newline at end of file
diff --git a/backend/config/email.js b/backend/config/email.js
new file mode 100644
index 0000000..89e0ddc
--- /dev/null
+++ b/backend/config/email.js
@@ -0,0 +1,14 @@
+var config = {};
+
+config.PORT = 587;
+config.SMTP_HOST = 'smtp.mail.com';
+config.SECURE = false;
+config.AUTH = {
+ user: 'saburly@mail.com',
+ pass: 'KeepSaburly',
+};
+
+config.FROM_EMAIL = 'saburly@mail.com';
+config.SUBJECT = 'Message from Saburly service';
+
+module.exports = config;
diff --git a/backend/controllers/skill.js b/backend/controllers/skill.js
index 6cc36e7..a0c2f5f 100644
--- a/backend/controllers/skill.js
+++ b/backend/controllers/skill.js
@@ -41,6 +41,7 @@ router.put ('/:id', bodyParser.json (), async (req, res, next) => {
.then (() => {
//Ok, done, now update skill on Amazon (if needed)
if (updateOnAmazon) {
+ //We need to update skill on Amazon
amazonHelper
.updateSkill (skill)
.then (amazonResult => {
@@ -51,45 +52,78 @@ router.put ('/:id', bodyParser.json (), async (req, res, next) => {
res.json ({result: constants.apiResultCodes.OK, message: ''});
alexa.updateModel ();
} else {
- res.status(constants.HTTPResultCodes.INTERNAL_SERVER_ERROR).json ({
- result: constants.apiResultCodes.AMAZON_ERROR,
- message: amazonResult,
- });
+ //Update on amazon failed, revert changes in database and send error to user
+ databaseHelper
+ .updateSkill (id, currentSkillState)
+ .then (() => {
+ res
+ .status (
+ constants.HTTPResultCodes.INTERNAL_SERVER_ERROR
+ )
+ .json ({
+ result: constants.apiResultCodes.AMAZON_ERROR,
+ message: amazonResult,
+ });
+ })
+ .catch (() => {
+ //This should never happen, something is seriously wrong, like no database connection
+ res
+ .status (
+ constants.HTTPResultCodes.INTERNAL_SERVER_ERROR
+ )
+ .json ({
+ result: constants.apiResultCodes.INCONSISTENT_STATE,
+ message: '',
+ });
+ });
}
})
.catch (e => {
- res.status(constants.HTTPResultCodes.INTERNAL_SERVER_ERROR).json ({
- result: constants.apiResultCodes.AMAZON_FAIL,
- message: e,
- });
+ //Update on amazon failed, revert changes in database and send error to user
+ databaseHelper
+ .updateSkill (id, currentSkillState)
+ .then (() => {
+ res
+ .status (constants.HTTPResultCodes.INTERNAL_SERVER_ERROR)
+ .json ({
+ result: constants.apiResultCodes.AMAZON_FAIL,
+ message: e,
+ });
+ })
+ .catch (() => {
+ //This should never happen, something is seriously wrong, like no database connection
+ res
+ .status (constants.HTTPResultCodes.INTERNAL_SERVER_ERROR)
+ .json ({
+ result: constants.apiResultCodes.INCONSISTENT_STATE,
+ message: '',
+ });
+ });
});
- }else{
+ } else {
+ //No need to update on Amazon, tell to user it's ok
res.json ({result: constants.apiResultCodes.OK, message: ''});
alexa.updateModel ();
}
})
.catch (() => {
- //Update in database didn't go well, revert changes
- databaseHelper
- .updateSkill (id, currentSkillState)
- .then (() => {
- res.status(constants.HTTPResultCodes.INTERNAL_SERVER_ERROR).json ({
- result: constants.apiResultCodes.DATABASE_ERROR,
- message: '',
- });
- })
- .catch (() => {
- //This should never happen, something is seriously wrong, like no database connection
- res.status(constants.HTTPResultCodes.INTERNAL_SERVER_ERROR).json ({
- result: constants.apiResultCodes.INCONSISTEN_STATE,
- message: '',
- });
- });
+ //Update in database didn't go well, no need to revert since it failed to write in the first place
+ //just send error to user
+ res
+ .status (
+ constants.HTTPResultCodes.INTERNAL_SERVER_ERROR
+ )
+ .json ({
+ result: constants.apiResultCodes.DATABASE_ERROR,
+ message: '',
+ });
});
})
.catch (e => {
//I don't know why, but something went wrong, possibly ID of skill is wrong, doesn't exist in DB
- res.status(constants.HTTPResultCodes.INTERNAL_SERVER_ERROR).json ({result: constants.apiResultCodes.NO_SKILL, message: ''});
+ res
+ .status (constants.HTTPResultCodes.INTERNAL_SERVER_ERROR)
+ .json ({result: constants.apiResultCodes.NO_SKILL, message: ''});
});
});
diff --git a/backend/helpers/amazon.js b/backend/helpers/amazon.js
index d6c554b..fcc781e 100644
--- a/backend/helpers/amazon.js
+++ b/backend/helpers/amazon.js
@@ -37,15 +37,22 @@ var refreshTokens = function () {
request (options, function (error, response, body) {
if (error) {
reject (error);
- }else{
+ } else {
parsedResponse = JSON.parse (body);
- if (parsedResponse.refresh_token){
- databaseHelper.updateTokens(parsedResponse.refresh_token, parsedResponse.access_token, parsedResponse.expires_in).then(()=>{
- resolve();
- }).catch(e=>{
- reject(e);
- });
- }else{
+ if (parsedResponse.refresh_token) {
+ databaseHelper
+ .updateTokens (
+ parsedResponse.refresh_token,
+ parsedResponse.access_token,
+ parsedResponse.expires_in
+ )
+ .then (() => {
+ resolve ();
+ })
+ .catch (e => {
+ reject (e);
+ });
+ } else {
reject (body);
}
}
@@ -56,87 +63,186 @@ var refreshTokens = function () {
var generateInteractionModel = function (skill) {
let result = {};
let allIntents = [];
- let defaultIntents = [
- {
- name: 'AMAZON.CancelIntent',
- samples: [],
- },
- {
- name: 'AMAZON.HelpIntent',
- samples: [],
- },
- {
- name: 'AMAZON.StopIntent',
- samples: [],
- },
- ];
-
- /*
- defaultIntents.map(intent=>{
- allIntents.push(intent);
- });
- */
skill.intents.map (intent => {
allIntents.push ({name: intent.intentName, samples: intent.questions});
});
- //Special Email Intents :
- /*
- allIntents.push ({
- name: 'EmailIntentLaunch',
- slots: [],
- samples: [
- 'I want to send a message',
- 'I would like to send a message',
- 'I would like to leave a message',
- 'Leave a message',
- ],
- });
+ //Special intent for sending message (Dialog)
allIntents.push ({
- name: 'EmailIntent',
+ name: 'SendMessageIntent',
+ samples: [
+ 'I would like to send a message',
+ 'I want to send a message',
+ 'Send message',
+ ],
slots: [
{
name: 'Name',
type: 'AMAZON.US_FIRST_NAME',
+ samples: ['My name is {Name}', 'I am {Name}', '{Name}'],
},
{
name: 'Email',
- type: 'AMAZON.LITERAL',
+ type: 'EmailSlot',
+ samples: ['My email is {Email}', '{Email}'],
},
{
name: 'Message',
- type: 'AMAZON.LITERAL',
+ type: 'MessageSlot',
+ samples: ['{Message}'],
},
- {
- name: 'Data',
- type: 'AMAZON.LITERAL',
- },
- ],
-
- samples: [
- 'My name is {Name}',
- 'I am {Name}',
- '{exampleww at wwdwdw|Data}',
- 'My email is {example at efefegedd|Email}',
- 'Send replay to {example at abcdefg|Email}',
- 'My message is {The quick brown fox jumps over the lazy dog.The quick brown fox jumps over the lazy dog.The quick brown fox jumps over the lazy dog.|Message}',
],
});
- */
+
+ let customSlotTypes = [
+ {
+ name: 'EmailSlot',
+ values: [
+ {
+ id: null,
+ name: {
+ value: 'bla@bla.bla',
+ synonyms: [],
+ },
+ },
+ {
+ id: null,
+ name: {
+ value: 'bla.bla@bla.bla.bla',
+ synonyms: [],
+ },
+ },
+ {
+ id: null,
+ name: {
+ value: 'bla_bla@bla.bla',
+ synonyms: [],
+ },
+ },
+ ],
+ },
+ {
+ name: 'MessageSlot',
+ values: [
+ {
+ id: null,
+ name: {
+ value: 'Quick brown fox jumps over lazy dog',
+ synonyms: [],
+ },
+ },
+ {
+ id: null,
+ name: {
+ value: 'Quick brown fox jumps over lazy dog. Quick brown fox jumps over lazy dog.',
+ synonyms: [],
+ },
+ },
+ {
+ id: null,
+ name: {
+ value: 'Quick brown fox jumps over lazy dog. Quick brown fox jumps over lazy dog. Quick brown fox jumps over lazy dog.',
+ synonyms: [],
+ },
+ },
+ ],
+ },
+ ];
+
+ let dialogPrompts = [
+ {
+ id: 'Elicit.Intent-SendMessageIntent.IntentSlot-Name',
+ variations: [
+ {
+ type: 'PlainText',
+ value: 'What is your name ?',
+ },
+ {
+ type: 'PlainText',
+ value: 'Tell me your name',
+ },
+ ],
+ },
+ {
+ id: 'Elicit.Intent-SendMessageIntent.IntentSlot-Email',
+ variations: [
+ {
+ type: 'PlainText',
+ value: 'What is your email ?',
+ },
+ {
+ type: 'PlainText',
+ value: 'Tell me your email',
+ },
+ ],
+ },
+ {
+ id: 'Elicit.Intent-SendMessageIntent.IntentSlot-Message',
+ variations: [
+ {
+ type: 'PlainText',
+ value: 'What is your message',
+ },
+ ],
+ },
+ ];
+
+ let dialogIntents = [
+ {
+ name: 'SendMessageIntent',
+ confirmationRequired: false,
+ prompts: {},
+ slots: [
+ {
+ name: 'Name',
+ type: 'AMAZON.US_FIRST_NAME',
+ elicitationRequired: true,
+ confirmationRequired: false,
+ prompts: {
+ elicitation: 'Elicit.Intent-SendMessageIntent.IntentSlot-Name',
+ },
+ },
+ {
+ name: 'Email',
+ type: 'EmailSlot',
+ elicitationRequired: true,
+ confirmationRequired: false,
+ prompts: {
+ elicitation: 'Elicit.Intent-SendMessageIntent.IntentSlot-Email',
+ },
+ },
+ {
+ name: 'Message',
+ type: 'MessageSlot',
+ elicitationRequired: true,
+ confirmationRequired: false,
+ prompts: {
+ elicitation: 'Elicit.Intent-SendMessageIntent.IntentSlot-Message',
+ },
+ },
+ ],
+ },
+ ];
result.interactionModel = {};
result.interactionModel.languageModel = {
invocationName: skill.invocationName,
+ types: customSlotTypes,
intents: allIntents,
};
+ result.interactionModel.prompts = dialogPrompts;
+ result.interactionModel.dialog = {};
+ result.interactionModel.dialog.intents = dialogIntents;
+
return JSON.stringify (result);
};
var uploadSkill = function (skill) {
+ let generatedInteractionModel = generateInteractionModel (skill);
return fetch (
`https://api.amazonalexa.com/v0/skills/${skill.skillID}/interactionModel/locales/en-US`,
{
@@ -144,7 +250,7 @@ var uploadSkill = function (skill) {
headers: {
Authorization: config.TOKEN,
},
- body: generateInteractionModel (skill),
+ body: generatedInteractionModel,
}
);
};
diff --git a/backend/helpers/email.js b/backend/helpers/email.js
new file mode 100644
index 0000000..f13a0ce
--- /dev/null
+++ b/backend/helpers/email.js
@@ -0,0 +1,75 @@
+const nodemailer = require ('nodemailer');
+const emailConfig = require('../config/email');
+
+module.exports = {
+ transformEmailFromAlexaResponse: function (email) {
+ //email from alexa response will contain words instead of symbols, like :
+ //at = @
+ //underscore = _
+ //dash = -
+ //dot = .
+ //TODO: This list should be longer
+ let transformedEmail = email
+ .replace (/\s/g, '') //remove all spaces
+ .replace (/at/gi, '@')
+ .replace (/underscore/gi, '_')
+ .replace (/dash/gi, '-')
+ .replace (/dot/gi, '.');
+
+ return transformedEmail;
+ },
+
+ isEmailValid: function (email) {
+ console.log ('Email to validate : ' + email);
+ let validEmailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+
+ return validEmailRegex.test (email);
+ },
+
+ sendEmail: function (name, fromEmail, message, toEmail) {
+ return new Promise ((resolve, reject) => {
+ fromEmail = this.transformEmailFromAlexaResponse(fromEmail);
+ let messageBody =
+ 'Hello. User left you a message on Saburly service using Alexa skill. \r\nMessage : ' +
+ message +
+ '\r\nName : ' +
+ name +
+ '\r\nEmail : ' +
+ fromEmail +
+ '\r\nYour Saburly team';
+
+ let messageBodyHTML =
+ '
Hello. User left you a message on Saburly service using Alexa skill.
Message :
' +
+ message +
+ '
Name : ' +
+ name +
+ '
Email : ' +
+ fromEmail +
+ '
Your Saburly team';
+
+ let transporter = nodemailer.createTransport ({
+ host: emailConfig.SMTP_HOST,
+ port: emailConfig.PORT,
+ secure: emailConfig.SECURE,
+ auth: emailConfig.AUTH,
+ });
+
+ var mailOptions = {
+ from: emailConfig.FROM_EMAIL,
+ replyTo: fromEmail,
+ to: toEmail,
+ subject: emailConfig.SUBJECT,
+ text: messageBody,
+ html: messageBodyHTML,
+ };
+
+ transporter.sendMail (mailOptions, (error, info) => {
+ if (error) {
+ reject (error);
+ } else {
+ resolve (info);
+ }
+ });
+ });
+ },
+};
diff --git a/backend/models/alexa.js b/backend/models/alexa.js
index d95c855..8a31a0d 100644
--- a/backend/models/alexa.js
+++ b/backend/models/alexa.js
@@ -1,16 +1,17 @@
var Alexa = require ('alexa-sdk');
const config = require ('../config/config');
var databaseHelper = require ('../helpers/database');
+var emailHelper = require ('../helpers/email');
const constants = require ('../config/constants');
var handlers = {};
+var destinationEmail;
module.exports = {
run: function (req, res) {
// Build the context manually, because Amazon Lambda is missing
var context = {
succeed: function (result) {
- console.log (result);
res.json (result);
},
fail: function (error) {
@@ -30,15 +31,34 @@ module.exports = {
.getSkill (config.SKILL_DB_ID)
.then (activeSkill => {
handlers = {};
+ destinationEmail = activeSkill.contactEmail;
- //Handler for launch request
+ let listOfPossibleQuestions = '';
+ activeSkill.intents.forEach(intent => {
+ if (intent.questions.length > 0 && intent.intentExplanation) {
+ listOfPossibleQuestions +=
+ intent.intentExplanation +
+ intent.questions[0] +
+ '';
+ }
+ });
+
+ console.log(listOfPossibleQuestions);
+
+ //Handler for launch requestconsole.log()
handlers = {
LaunchRequest: function () {
this.response
- .speak (activeSkill.invocationAnswer)
+ .speak (
+ activeSkill.invocationAnswer +
+ '' +
+ listOfPossibleQuestions
+ )
.listen (constants.voiceResponseStrings.GENERIC_CONTINUE); //Phrase from listen doesn't work !!!
- //TODO : Maybe to ask user does he want to hear possible intents
- //For this functionality, we need explanation text for each intent (Question)
this.emit (':responseReady');
},
};
@@ -53,19 +73,105 @@ module.exports = {
};
});
+ //Handler for sending message
+ handlers.SendMessageIntent = function () {
+ let intent = this.event.request.intent;
+
+ console.log ('Dialog state : ' + this.event.request.dialogState);
+ console.log (intent);
+ //STARTED, IN_PROGRESS
+
+ if (!intent.slots.Name.value) {
+ //Name not defined yet, ask user for name
+ const slotToElicit = 'Name';
+ const speechOutput = 'What is your name';
+ const repromptSpeech = speechOutput;
+ this.emit (
+ ':elicitSlot',
+ slotToElicit,
+ speechOutput,
+ repromptSpeech
+ );
+ } else if (!intent.slots.Email.value) {
+ //Name not defined yet, ask user for email
+ const slotToElicit = 'Email';
+ const speechOutput =
+ 'Ok ' + intent.slots.Name.value + '. What is your email';
+ const repromptSpeech = speechOutput;
+ this.emit (
+ ':elicitSlot',
+ slotToElicit,
+ speechOutput,
+ repromptSpeech
+ );
+ } else if (!intent.slots.Message.value) {
+ intent.slots.Email.value = emailHelper.transformEmailFromAlexaResponse (
+ intent.slots.Email.value
+ );
+ if (!emailHelper.isEmailValid (intent.slots.Email.value)) {
+ //Email is not valid, ask again
+ const slotToElicit = 'Email';
+ const speechOutput =
+ 'Sorry, that was not valid email. What is your email';
+ const repromptSpeech = speechOutput;
+ this.emit (
+ ':elicitSlot',
+ slotToElicit,
+ speechOutput,
+ repromptSpeech
+ );
+ } else {
+ //Email is valid
+ const slotToElicit = 'Message';
+ const speechOutput = 'Great. What is your message';
+ const repromptSpeech = speechOutput;
+ this.emit (
+ ':elicitSlot',
+ slotToElicit,
+ speechOutput,
+ repromptSpeech
+ );
+ }
+ } else {
+ //all slots are filled
+ console.log ('Name : ' + intent.slots.Name.value);
+ console.log ('Email : ' + intent.slots.Email.value);
+ console.log ('Message : ' + intent.slots.Message.value);
+ emailHelper
+ .sendEmail (
+ intent.slots.Name.value,
+ intent.slots.Email.value,
+ intent.slots.Message.value,
+ destinationEmail
+ )
+ .then (info => {
+ console.log (info);
+ this.response.speak (
+ 'Ok. Message sent. Someone will contact you ASAP'
+ );
+ this.emit (':responseReady');
+ })
+ .catch (error => {
+ console.log (error);
+ this.response.speak (
+ 'Sorry, there was a problem with sending message.'
+ );
+ this.emit (':responseReady');
+ });
+ }
+ };
+
//Default handlers for unknown questions and session close
handlers.Unhandled = function () {
this.response
.speak (constants.voiceResponseStrings.QUESTION_NOT_FOUND)
.listen (constants.voiceResponseStrings.GENERIC_CONTINUE);
- }
+ };
- handlers.SessionEndedRequest = function(){
+ handlers.SessionEndedRequest = function () {
//We don't care for now
- }
-
-
+ };
})
.catch (e => {
//Something is wrong, skill is not ready, use catch-all intent to inform user
diff --git a/backend/package.json b/backend/package.json
index b0c0319..3b48add 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -10,6 +10,7 @@
"express": "^4.13.0",
"isomorphic-fetch": "^2.2.1",
"mongodb": "^2.2.33",
+ "nodemailer": "^4.4.1",
"request": "^2.83.0"
},
"author": "Matt Kruse (http://mattkruse.com/)",
diff --git a/web/src/App.js b/web/src/App.js
index 663d0ec..a91c5f6 100644
--- a/web/src/App.js
+++ b/web/src/App.js
@@ -1,4 +1,4 @@
-import React, { Component } from 'react';
+import React, {Component} from 'react';
import './css/App.css';
import './css/popup.css';
import IntentList from './components/IntentList';
@@ -6,90 +6,117 @@ import IntentDetails from './components/IntentDetails';
import LaunchRequest from './components/LaunchRequest';
import Contact from './components/Contact';
import Popup from 'react-popup';
-import {getSkill, updateSkill} from './lib/api'
+import {getSkill, updateSkill} from './lib/api';
import {
- NEW_INTENT_SELECTED_INDEX,
- LAUNCH_REQUEST_SELECTED_INDEX,
- CONTACT_SELECTED_INDEX,
- RESULT_CODES} from './config/constants'
+ NEW_INTENT_SELECTED_INDEX,
+ LAUNCH_REQUEST_SELECTED_INDEX,
+ CONTACT_SELECTED_INDEX,
+ RESULT_CODES,
+} from './config/constants';
class App extends Component {
+ constructor (props) {
+ super (props);
- constructor(props){
- super(props);
+ this.state = {
+ _id: '5a232fb86ce046c749739455',
+ skillID: '',
+ skillName: '',
+ invocationName: 'Saburly',
+ invocationAnswer: 'We are saburly',
+ allIntents: [],
+ selectedIntent: {
+ intentName: '',
+ intentExplanation: '',
+ questions: [''],
+ answer: '',
+ },
+ selectedIndex: NEW_INTENT_SELECTED_INDEX,
+ contactEmail: '',
+ waiting: false,
+ };
- this.state={_id:'5a232fb86ce046c749739455',
- skillID:'',
- skillName:'',
- invocationName:'Saburly',
- invocationAnswer:'We are saburly',
- allIntents:[],
- selectedIntent: {intentName:'',questions:[''],answer:''},
- selectedIndex:NEW_INTENT_SELECTED_INDEX,
- contactEmail:'',
- waiting: false
- };
+ getSkill (this.state._id).then (l => l.json ()).then (result => {
+ if (result === undefined) return;
+ this.setState ({
+ skillID: result.skillID,
+ skillName: result.skillName,
+ invocationName: result.invocationName,
+ invocationAnswer: result.invocationAnswer,
+ allIntents: result.intents,
+ contactEmail: result.contactEmail,
+ });
+ });
- getSkill(this.state._id).then(l=>l.json()).then(result=>{
- if (result===undefined) return;
- this.setState({ skillID:result.skillID,skillName:result.skillName, invocationName: result.invocationName,
- invocationAnswer: result.invocationAnswer,
- allIntents: result.intents, contactEmail: result.contactEmail})
- })
-
- this.handleIntentClick = this.handleIntentClick.bind(this);
- this.handleLaunchRequestClick = this.handleLaunchRequestClick.bind(this);
- this.handleDeleteIntentClick = this.handleDeleteIntentClick.bind(this);
- this.handleSaveIntentClick = this.handleSaveIntentClick.bind(this);
- this.handleAddIntentClick = this.handleAddIntentClick.bind(this);
- this.handleSaveLaunchRequestClick = this.handleSaveLaunchRequestClick.bind(this);
- this.createSkill = this.createSkill.bind(this);
- this.sendSkill = this.sendSkill.bind(this);
- this.handleContactClick = this.handleContactClick.bind(this);
- this.handleSaveEmailClick = this.handleSaveEmailClick.bind(this);
+ this.handleIntentClick = this.handleIntentClick.bind (this);
+ this.handleLaunchRequestClick = this.handleLaunchRequestClick.bind (this);
+ this.handleDeleteIntentClick = this.handleDeleteIntentClick.bind (this);
+ this.handleSaveIntentClick = this.handleSaveIntentClick.bind (this);
+ this.handleAddIntentClick = this.handleAddIntentClick.bind (this);
+ this.handleSaveLaunchRequestClick = this.handleSaveLaunchRequestClick.bind (
+ this
+ );
+ this.createSkill = this.createSkill.bind (this);
+ this.sendSkill = this.sendSkill.bind (this);
+ this.handleContactClick = this.handleContactClick.bind (this);
+ this.handleSaveEmailClick = this.handleSaveEmailClick.bind (this);
}
- render() {
+ render () {
let rightPanel;
switch (this.state.selectedIndex) {
case LAUNCH_REQUEST_SELECTED_INDEX:
- rightPanel = ;
+ rightPanel = (
+
+ );
break;
case CONTACT_SELECTED_INDEX:
- rightPanel = ;
+ rightPanel = (
+
+ );
break;
default:
- rightPanel = ;
+ rightPanel = (
+
+ );
}
- return(
+ return (
-
+
Tell All
-
-
+
+
{rightPanel}
);
}
- createSkill(intents, name, answer, email, updateOnAmazon){
+ createSkill (intents, name, answer, email, updateOnAmazon) {
return {
_id: this.state._id,
skillID: this.state.skillID,
@@ -97,97 +124,181 @@ class App extends Component {
invocationName: name,
invocationAnswer: answer,
contactEmail: email,
- updateOnAmazon: updateOnAmazon
+ updateOnAmazon: updateOnAmazon,
};
}
- handleIntentClick(selectedIntent, index){
- this.setState({selectedIntent:selectedIntent, selectedIndex: index, launchRequest:false});
+ handleIntentClick (selectedIntent, index) {
+ this.setState ({
+ selectedIntent: selectedIntent,
+ selectedIndex: index,
+ launchRequest: false,
+ });
}
- handleLaunchRequestClick(){
- this.setState({selectedIndex: LAUNCH_REQUEST_SELECTED_INDEX});
+ handleLaunchRequestClick () {
+ this.setState ({selectedIndex: LAUNCH_REQUEST_SELECTED_INDEX});
}
- handleContactClick(){
- this.setState({selectedIndex: CONTACT_SELECTED_INDEX})
+ handleContactClick () {
+ this.setState ({selectedIndex: CONTACT_SELECTED_INDEX});
}
- handleSaveLaunchRequestClick(name, answer){
- this.setState({waiting:true, invocationName:name, invocationAnswer: answer});
- this.sendSkill(this.state.allIntents,true,{waiting:false},{waiting:false},name,answer,this.state.contactEmail,true);
+ handleSaveLaunchRequestClick (name, answer) {
+ this.setState ({
+ waiting: true,
+ invocationName: name,
+ invocationAnswer: answer,
+ });
+ this.sendSkill (
+ this.state.allIntents,
+ true,
+ {waiting: false},
+ {waiting: false},
+ name,
+ answer,
+ this.state.contactEmail,
+ true
+ );
}
- handleSaveEmailClick(email){
- this.setState({waiting:true});
- this.sendSkill(this.state.allIntents,true,{contactEmail: email, waiting:false},{waiting:false},this.state.invocationName,this.state.invocationAnswer,email,false);
+ handleSaveEmailClick (email) {
+ this.setState ({waiting: true});
+ this.sendSkill (
+ this.state.allIntents,
+ true,
+ {contactEmail: email, waiting: false},
+ {waiting: false},
+ this.state.invocationName,
+ this.state.invocationAnswer,
+ email,
+ false
+ );
}
- handleDeleteIntentClick(selectedIntent){
+ handleDeleteIntentClick (selectedIntent) {
let id = -1;
//TODO : Change comparsion method ! Same object with different proeprty sorting will not be same string
- this.state.allIntents.map((intent,index)=>{
- if ((id===-1) && (JSON.stringify(selectedIntent)===JSON.stringify(intent)))
- id = index;
+ this.state.allIntents.map ((intent, index) => {
+ if (
+ id === -1 &&
+ JSON.stringify (selectedIntent) === JSON.stringify (intent)
+ )
+ id = index;
});
- if (id!==-1){
- try{
- let newAllIntentsJSON = JSON.stringify(this.state.allIntents);
- let newAllIntents = JSON.parse(newAllIntentsJSON);
- newAllIntents.splice(id,1);
- this.setState({waiting:true});
+ if (id !== -1) {
+ try {
+ let newAllIntentsJSON = JSON.stringify (this.state.allIntents);
+ let newAllIntents = JSON.parse (newAllIntentsJSON);
+ newAllIntents.splice (id, 1);
+ this.setState ({waiting: true});
- let newState = {allIntents: newAllIntents, selectedIntent: {intentName:'', questions:[''],answer:''}, waiting:false};
- this.sendSkill(newAllIntents,true,newState,{waiting:false},this.state.invocationName,this.state.invocationAnswer,this.state.contactEmail,true);
-
- }catch(e){
- console.log("error : " + e);
+ let newState = {
+ allIntents: newAllIntents,
+ selectedIntent: {intentName: '', questions: [''], answer: ''},
+ waiting: false,
+ };
+ this.sendSkill (
+ newAllIntents,
+ true,
+ newState,
+ {waiting: false},
+ this.state.invocationName,
+ this.state.invocationAnswer,
+ this.state.contactEmail,
+ true
+ );
+ } catch (e) {
+ console.log ('error : ' + e);
}
}
}
-
- handleSaveIntentClick(selectedIntent){
- let newAllIntentsJSON = JSON.stringify(this.state.allIntents);
- let newAllIntents = JSON.parse(newAllIntentsJSON);
+ handleSaveIntentClick (selectedIntent) {
+ let newAllIntentsJSON = JSON.stringify (this.state.allIntents);
+ let newAllIntents = JSON.parse (newAllIntentsJSON);
let newState = null;
- if (this.state.selectedIndex === NEW_INTENT_SELECTED_INDEX){
+ if (this.state.selectedIndex === NEW_INTENT_SELECTED_INDEX) {
//new intent
- newAllIntents.push(selectedIntent);
- newState = {allIntents: newAllIntents, selectedIntent: selectedIntent, selectedIndex: newAllIntents.length-1, waiting:false};
- }else{
+ newAllIntents.push (selectedIntent);
+ newState = {
+ allIntents: newAllIntents,
+ selectedIntent: selectedIntent,
+ selectedIndex: newAllIntents.length - 1,
+ waiting: false,
+ };
+ } else {
newAllIntents[this.state.selectedIndex] = selectedIntent;
- newState = {allIntents: newAllIntents, selectedIntent: selectedIntent, waiting: false};
+ newState = {
+ allIntents: newAllIntents,
+ selectedIntent: selectedIntent,
+ waiting: false,
+ };
}
- this.setState({waiting:true});
- this.sendSkill(newAllIntents, true, newState, {waiting:false}, this.state.invocationName,this.state.invocationAnswer,this.state.contactEmail, true);
+ this.setState ({waiting: true});
+ this.sendSkill (
+ newAllIntents,
+ true,
+ newState,
+ {waiting: false},
+ this.state.invocationName,
+ this.state.invocationAnswer,
+ this.state.contactEmail,
+ true
+ );
}
- handleAddIntentClick(){
- this.setState({allIntents: this.state.allIntents, selectedIndex: NEW_INTENT_SELECTED_INDEX,launchRequest:false,selectedIntent: {intentName:'',questions:[''], answer:''}});
+ handleAddIntentClick () {
+ this.setState ({
+ allIntents: this.state.allIntents,
+ selectedIndex: NEW_INTENT_SELECTED_INDEX,
+ launchRequest: false,
+ selectedIntent: {intentName: '', questions: [''], answer: '', intentExplanation:''},
+ });
}
- sendSkill(newAllIntents, showPopUp, resolveState, rejectState, newName, newAnswer, email, updateOnAmazon){
- return new Promise((resolve,reject)=>{
- updateSkill(this.createSkill(newAllIntents,newName,newAnswer,email,updateOnAmazon)).then(l=>l.json()).then(result=>{
- if (result.result !== RESULT_CODES.OK){
- console.log(result.result);
- if (showPopUp) Popup.alert('Model was not saved. Please try again');
- this.setState(rejectState);
- //reject('Error code : ' + jResult.result);
- }else{
- if (showPopUp) Popup.alert('Saved');
- this.setState(resolveState);
- resolve();
- }
- }).catch(e=>{
- console.log('error : ' + e);
- if (showPopUp) Popup.alert('Model was not saved. Please try again');
- this.setState(rejectState);
- //reject(e);
- });
+ sendSkill (
+ newAllIntents,
+ showPopUp,
+ resolveState,
+ rejectState,
+ newName,
+ newAnswer,
+ email,
+ updateOnAmazon
+ ) {
+ return new Promise ((resolve, reject) => {
+ updateSkill (
+ this.createSkill (
+ newAllIntents,
+ newName,
+ newAnswer,
+ email,
+ updateOnAmazon
+ )
+ )
+ .then (l => l.json ())
+ .then (result => {
+ if (result.result !== RESULT_CODES.OK) {
+ console.log (result);
+ if (showPopUp)
+ Popup.alert ('Model was not saved. Please try again');
+ this.setState (rejectState);
+ //reject('Error code : ' + jResult.result);
+ } else {
+ if (showPopUp) Popup.alert ('Saved');
+ this.setState (resolveState);
+ resolve ();
+ }
+ })
+ .catch (e => {
+ console.log ('error : ' + e);
+ if (showPopUp) Popup.alert ('Model was not saved. Please try again');
+ this.setState (rejectState);
+ //reject(e);
+ });
});
}
}
diff --git a/web/src/components/IntentDetails.js b/web/src/components/IntentDetails.js
index e5c6066..8e76100 100644
--- a/web/src/components/IntentDetails.js
+++ b/web/src/components/IntentDetails.js
@@ -1,7 +1,8 @@
import React, { Component } from 'react';
import {Button, SVGIcon, TextField} from 'react-md';
import '../css/components/IntentDetails.css';
-import {QUESTION_MAX_LENGTH, ANSWER_MAX_LENGTH, INTENT_NAME_MAX_LENGTH} from '../config/constants';
+import '../css/Common.css';
+import {QUESTION_MAX_LENGTH, ANSWER_MAX_LENGTH, INTENT_NAME_MAX_LENGTH, INTENT_EXPLANATION_MAX_LENGTH} from '../config/constants';
class IntentDetails extends Component {
constructor(props){
@@ -14,6 +15,7 @@ class IntentDetails extends Component {
this.handleQuestionEdit = this.handleQuestionEdit.bind(this);
this.handleAnswerEdit = this.handleAnswerEdit.bind(this);
this.handleIntentNameEdit = this.handleIntentNameEdit.bind(this);
+ this.handleIntentExplanationEdit = this.handleIntentExplanationEdit.bind(this);
}
componentWillReceiveProps(props){
@@ -24,10 +26,19 @@ class IntentDetails extends Component {
return (
+
In introduction, Alexa will help users to ask her the right questions about your business. For Example, she will say : "To ask us about our services, say : What do you do ? ". What do you do ? is defined in question field. Alexa will use first variation of question in intro.
+
+