diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca26ad9..13155b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
* [#352](https://github.com/alexa-js/alexa-app/pull/352): Allow utterance expansion in custom slot type synonyms - [@daanzu](https://github.com/daanzu).
* [#361](https://github.com/alexa-js/alexa-app/pull/361): Fix object reference for dialogState in in doc - [@Sephtenen](https://github.com/Sephtenen).
* [#364](https://github.com/alexa-js/alexa-app/pull/364): Fix reprompt() to concatenate multiple SSML prompts - [@andrewjhunt](https://github.com/andrewjhunt).
+* [#366](https://github.com/alexa-js/alexa-app/pull/358): Add support for including samples in slots and intents for confirmations - [@fabien88](https://github.com/fabien88).
* Your contribution here.
### 4.2.2 (April 7, 2018)
diff --git a/README.md b/README.md
index 5e5f36c..b313b1e 100644
--- a/README.md
+++ b/README.md
@@ -622,16 +622,27 @@ The alexa-app module makes it easy to define your intent schema and generate man
### Schema Syntax
Pass an object with two properties: slots and utterances.
+A third (optional) property : prompts. To confirm intent on alexa side with dialog delegate.
+slot prompts and intents prompts are working only with askcli command.
```javascript
app.intent("sampleIntent", {
"slots": {
"NAME": "AMAZON.US_FIRST_NAME",
- "AGE": "AMAZON.NUMBER"
+ "AGE": "AMAZON.NUMBER",
+ "CITY": {
+ "type": "AMAZON.US_CITY",
+ "elicitationPrompts": [
+ "Oh I forgot to ask you, in which city do you live ?",
+ ],
+ "samples": ['I live in {-|CITY}', '{in|at|near|} {-|CITY}'],
+ "confirmationPrompts": ['Ok so you live in {CITY} right ?'],
+ }
},
"utterances": [
"my {name is|name's} {NAME} and {I am|I'm} {-|AGE}{ years old|}"
- ]
+ ],
+ "prompts": ['Ok do you confirm your name is {NAME}, your age is {AGE} and that you live in {CITY} ?'],
},
function(request, response) { ... }
);
@@ -747,7 +758,7 @@ WhatsMyColorIntent tell me what my favorite color is
#### Skill Builder Syntax
-If you are using the Skill Builder Beta, the `schemas.skillBuilder()` function will generate a single schema JSON string
+If you are using the Skill Builder Beta, the `schemas.skillBuilder()` function will generate a single schema JSON string
that includes your intents with all of their utterances
```javascript
@@ -1056,4 +1067,4 @@ All named apps can be found in the `alexa.apps` object, keyed by name. The value
Copyright (c) 2016-2017 Matt Kruse
-MIT License, see [LICENSE](LICENSE.md) for details.
+MIT License, see [LICENSE](LICENSE.md) for details.
\ No newline at end of file
diff --git a/index.js b/index.js
index 297d19a..ade1454 100644
--- a/index.js
+++ b/index.js
@@ -312,6 +312,7 @@ alexa.intent = function(name, schema, handler) {
this.dialog = (schema && typeof schema.dialog !== "undefined") ? schema.dialog : {};
this.slots = (schema && typeof schema["slots"] !== "undefined") ? schema["slots"] : null;
this.utterances = (schema && typeof schema["utterances"] !== "undefined") ? schema["utterances"] : null;
+ this.prompts = schema && typeof schema["prompts"] !== "undefined" ? schema["prompts"] : null;
this.isDelegatedDialog = function() {
return this.dialog.type === "delegate";
@@ -684,12 +685,26 @@ alexa.app = function(name) {
if (intent.slots && Object.keys(intent.slots).length > 0) {
intentSchema["slots"] = [];
for (key in intent.slots) {
- // It's unclear whether `samples` is actually used for slots,
- // but the interaction model will not build without an (empty) array
+ const slot = intent.slots[key];
+ const type = slot.type ? slot.type : slot;
+ const samples = [];
+ if (slot.samples) {
+ slot.samples.forEach(function(sample) {
+ var list = AlexaUtterances(
+ sample,
+ intent.slots,
+ self.dictionary,
+ self.exhaustiveUtterances
+ );
+ list.forEach(function(utterance) {
+ samples.push(utterance);
+ });
+ });
+ }
intentSchema.slots.push({
- "name": key,
- "type": intent.slots[key],
- "samples": []
+ name: key,
+ type,
+ samples
});
}
}
@@ -733,9 +748,13 @@ alexa.app = function(name) {
if (intent.slots && Object.keys(intent.slots).length > 0) {
intentSchema["slots"] = [];
for (key in intent.slots) {
+ const slot = intent.slots[key];
+ const type = slot.type ? slot.type : slot;
+ const samples = slot.type ? slot.samples : [];
intentSchema.slots.push({
"name": key,
- "type": intent.slots[key]
+ "type": type,
+ "samples": samples
});
}
}
@@ -750,16 +769,92 @@ alexa.app = function(name) {
},
askcli: function(invocationName) {
var model = skillBuilderSchema();
+ var { prompts, dialog } = skillBuilderDialog();
model.invocationName = invocationName || self.invocationName || self.name;
var schema = {
interactionModel: {
- languageModel: model
+ languageModel: model,
+ dialog,
+ prompts
}
};
return JSON.stringify(schema, null, 3);
}
};
+ var skillBuilderDialog = function() {
+ var schema = {
+ dialog: {
+ intents: []
+ },
+ prompts: []
+ },
+ intentName,
+ intent,
+ key;
+
+ var creatPrompt = function(promptId, variations) {
+ return {
+ id: promptId,
+ variations: variations.map(prompt => ({
+ type: "SSML",
+ value: "" + prompt + ""
+ }))
+ };
+ };
+
+ for (intentName in self.intents) {
+ intent = self.intents[intentName];
+ var intentSchema = {
+ name: intent.name,
+ confirmationRequired: false,
+ prompts: {},
+ slots: []
+ };
+
+ if (intent.prompts) {
+ var intentConfirmPromptId = "Confirm.Intent." + schema.prompts.length;
+ schema.prompts.push(creatPrompt(intentConfirmPromptId, intent.prompts));
+ intentSchema.prompts.confirmation = intentConfirmPromptId;
+ intentSchema.confirmationRequired = true;
+ }
+
+ if (intent.slots && Object.keys(intent.slots).length > 0) {
+ for (key in intent.slots) {
+ var slot = intent.slots[key];
+ var dialogIntentSlot = {
+ name: key,
+ type: slot.type || slot,
+ prompts: {},
+ elicitationRequired: false,
+ confirmationRequired: false
+ };
+ if (slot.elicitationPrompts) {
+ var elicitPromptId = "Elicit.Slot." + schema.prompts.length;
+ schema.prompts.push(
+ creatPrompt(elicitPromptId, slot.elicitationPrompts)
+ );
+ dialogIntentSlot.elicitationRequired = true;
+ dialogIntentSlot.prompts.elicitation = elicitPromptId;
+ }
+ if (slot.confirmationPrompts) {
+ var confirmPromptId = "Confirm.Slot." + schema.prompts.length;
+ schema.prompts.push(
+ creatPrompt(confirmPromptId, slot.confirmationPrompts)
+ );
+ dialogIntentSlot.confirmationRequired = true;
+ dialogIntentSlot.prompts.confirmation = confirmPromptId;
+ }
+
+ intentSchema.slots.push(dialogIntentSlot);
+ }
+ }
+ schema.dialog.intents.push(intentSchema);
+ }
+
+ return schema;
+ };
+
// extract the schema and generate a schema JSON object
this.schema = function() {
return this.schemas.intent();
diff --git a/test/test_alexa_app_schema.js b/test/test_alexa_app_schema.js
index 2ce07e2..fbf23f2 100644
--- a/test/test_alexa_app_schema.js
+++ b/test/test_alexa_app_schema.js
@@ -46,24 +46,30 @@ describe("Alexa", function() {
"intent": "testIntentTwo",
"slots": [{
"name": "MyCustomSlotType",
+ "samples": [],
"type": "CUSTOMTYPE"
}, {
"name": "Tubular",
+ "samples": [],
"type": "AMAZON.LITERAL"
}, {
"name": "Radical",
+ "samples": [],
"type": "AMAZON.US_STATE"
}]
}, {
"intent": "testIntent",
"slots": [{
"name": "AirportCode",
+ "samples": [],
"type": "FAACODES"
}, {
"name": "Awesome",
+ "samples": [],
"type": "AMAZON.DATE"
}, {
"name": "Tubular",
+ "samples": [],
"type": "AMAZON.LITERAL"
}]
}]
@@ -122,12 +128,15 @@ describe("Alexa", function() {
"intent": "testIntent",
"slots": [{
"name": "MyCustomSlotType",
+ "samples": [],
"type": "CUSTOMTYPE"
}, {
"name": "Tubular",
+ "samples": [],
"type": "AMAZON.LITERAL"
}, {
"name": "Radical",
+ "samples": [],
"type": "AMAZON.US_STATE"
}]
}]
@@ -165,24 +174,30 @@ describe("Alexa", function() {
"intent": "testIntentTwo",
"slots": [{
"name": "MyCustomSlotType",
+ "samples": [],
"type": "CUSTOMTYPE"
}, {
"name": "Tubular",
+ "samples": [],
"type": "AMAZON.LITERAL"
}, {
"name": "Radical",
+ "samples": [],
"type": "AMAZON.US_STATE"
}]
}, {
"intent": "testIntent",
"slots": [{
"name": "AirportCode",
+ "samples": [],
"type": "FAACODES"
}, {
"name": "Awesome",
+ "samples": [],
"type": "AMAZON.DATE"
}, {
"name": "Tubular",
+ "samples": [],
"type": "AMAZON.LITERAL"
}]
}]
@@ -441,11 +456,15 @@ describe("Alexa", function() {
var subject = JSON.parse(testApp.schemas.askcli());
expect(subject).to.eql({
"interactionModel": {
+ "dialog": {
+ "intents": []
+ },
"languageModel": {
"invocationName": "testApp",
"intents": [],
"types": []
- }
+ },
+ "prompts": []
}
});
})
@@ -460,11 +479,15 @@ describe("Alexa", function() {
var subject = JSON.parse(testApp.schemas.askcli());
expect(subject).to.eql({
"interactionModel": {
+ "dialog": {
+ "intents": []
+ },
"languageModel": {
"invocationName": "my cool skill",
"intents": [],
"types": []
- }
+ },
+ "prompts": []
}
});
})
@@ -474,11 +497,15 @@ describe("Alexa", function() {
var subject = JSON.parse(testApp.schemas.askcli("my okay skill"));
expect(subject).to.eql({
"interactionModel": {
+ "dialog": {
+ "intents": []
+ },
"languageModel": {
"invocationName": "my okay skill",
"intents": [],
"types": []
- }
+ },
+ "prompts": []
}
});
})
@@ -494,6 +521,16 @@ describe("Alexa", function() {
var subject = JSON.parse(testApp.schemas.askcli());
expect(subject).to.eql({
"interactionModel": {
+ "dialog": {
+ "intents": [
+ {
+ "confirmationRequired": false,
+ "name": "AMAZON.PauseIntent",
+ "prompts": {},
+ "slots": [],
+ }
+ ]
+ },
"languageModel": {
"invocationName": "testApp",
"intents": [{
@@ -501,7 +538,8 @@ describe("Alexa", function() {
"samples": []
}],
"types": []
- }
+ },
+ "prompts": []
}
});
});
@@ -518,6 +556,16 @@ describe("Alexa", function() {
var subject = JSON.parse(testApp.schemas.askcli());
expect(subject).to.eql({
"interactionModel": {
+ "dialog": {
+ "intents": [
+ {
+ "confirmationRequired": false,
+ "name": "AMAZON.PauseIntent",
+ "prompts": {},
+ "slots": []
+ }
+ ]
+ },
"languageModel": {
"invocationName": "testApp",
"intents": [{
@@ -525,7 +573,8 @@ describe("Alexa", function() {
"samples": []
}],
"types": []
- }
+ },
+ "prompts": []
}
});
});
@@ -545,6 +594,31 @@ describe("Alexa", function() {
var subject = JSON.parse(testApp.schemas.askcli());
expect(subject).to.eql({
"interactionModel": {
+ "dialog": {
+ "intents": [
+ {
+ "confirmationRequired": false,
+ "name": "testIntent",
+ "prompts": {},
+ "slots": [
+ {
+ "confirmationRequired": false,
+ "elicitationRequired": false,
+ "name": "Tubular",
+ "prompts": {},
+ "type": "AMAZON.LITERAL"
+ },
+ {
+ "confirmationRequired": false,
+ "elicitationRequired": false,
+ "name": "Radical",
+ "prompts": {},
+ "type": "AMAZON.US_STATE"
+ }
+ ]
+ }
+ ]
+ },
"languageModel": {
"invocationName": "testApp",
"intents": [{
@@ -561,7 +635,8 @@ describe("Alexa", function() {
}]
}],
"types": []
- }
+ },
+ "prompts": []
}
});
});
@@ -578,6 +653,16 @@ describe("Alexa", function() {
var subject = JSON.parse(testApp.schemas.askcli());
expect(subject).to.eql({
"interactionModel": {
+ "dialog": {
+ "intents": [
+ {
+ "confirmationRequired": false,
+ "name": "testIntent",
+ "prompts": {},
+ "slots": []
+ }
+ ]
+ },
"languageModel": {
"invocationName": "testApp",
"intents": [{
@@ -588,7 +673,8 @@ describe("Alexa", function() {
]
}],
"types": []
- }
+ },
+ "prompts": []
}
});
});
@@ -601,17 +687,28 @@ describe("Alexa", function() {
testApp.intent("testIntentTwo", {
"slots": {
"MyCustomSlotType": "CUSTOMTYPE",
- "Tubular": "AMAZON.LITERAL",
- "Radical": "AMAZON.US_STATE"
+ "Tubular": {
+ "type": "AMAZON.LITERAL",
+ "samples": ["{-|Tubular}"],
+ "elicitationPrompts": ["which tubular do you use ?"],
+ "confirmationPrompts": ["{Tubular} are you sure ?"]
+ },
+ "Radical": "AMAZON.US_STATE",
},
});
testApp.intent("testIntent", {
"slots": {
"AirportCode": "FAACODES",
- "Awesome": "AMAZON.DATE",
+ "Awesome": {
+ "type":"AMAZON.DATE",
+ "samples":["I {like to|} do awesome {things|stuff} on {-|Awesome}", "{-|Awesome}"],
+ "elicitationPrompts": ["When do you do awesome things ?"],
+ "confirmationPrompts": ["I never though you could do awesome things that date of :{Awesome} ! Are you sure ?"]
+ },
"Tubular": "AMAZON.LITERAL"
},
+ prompts: ['are you sure about {AirportCode} and {Tubular} ?']
});
});
@@ -619,6 +716,79 @@ describe("Alexa", function() {
var subject = JSON.parse(testApp.schemas.askcli());
expect(subject).to.eql({
"interactionModel": {
+ "dialog": {
+ "intents": [
+ {
+ "confirmationRequired": false,
+ "name": "AMAZON.PauseIntent",
+ "prompts": {},
+ "slots": [],
+ },
+ {
+ "confirmationRequired": false,
+ "name": "testIntentTwo",
+ "prompts": {},
+ "slots": [
+ {
+ "confirmationRequired": false,
+ "elicitationRequired": false,
+ "name": "MyCustomSlotType",
+ "prompts": {},
+ "type": "CUSTOMTYPE",
+ },
+ {
+ "confirmationRequired": true,
+ "elicitationRequired": true,
+ "name": "Tubular",
+ "prompts": {
+ "confirmation": "Confirm.Slot.1",
+ "elicitation": "Elicit.Slot.0",
+ },
+ "type": "AMAZON.LITERAL",
+
+ },
+ {
+ "confirmationRequired": false,
+ "elicitationRequired": false,
+ "name": "Radical",
+ "prompts": {},
+ "type": "AMAZON.US_STATE",
+ }
+ ]
+ },
+ {
+ "confirmationRequired": true,
+ "name": "testIntent",
+ "prompts": { "confirmation": "Confirm.Intent.2" },
+ "slots": [
+ {
+ "confirmationRequired": false,
+ "elicitationRequired": false,
+ "name": "AirportCode",
+ "prompts": {},
+ "type": "FAACODES",
+ },
+ {
+ "confirmationRequired": true,
+ "elicitationRequired": true,
+ "name": "Awesome",
+ "prompts": {
+ "confirmation": "Confirm.Slot.4",
+ "elicitation": "Elicit.Slot.3",
+ },
+ "type": "AMAZON.DATE",
+ },
+ {
+ "confirmationRequired": false,
+ "elicitationRequired": false,
+ "name": "Tubular",
+ "prompts": {},
+ "type": "AMAZON.LITERAL",
+ }
+ ]
+ }
+ ]
+ },
"languageModel": {
"invocationName": "testApp",
"intents": [{
@@ -634,7 +804,9 @@ describe("Alexa", function() {
}, {
"name": "Tubular",
"type": "AMAZON.LITERAL",
- "samples": []
+ "samples": [
+ "{Tubular}"
+ ],
}, {
"name": "Radical",
"type": "AMAZON.US_STATE",
@@ -650,7 +822,13 @@ describe("Alexa", function() {
}, {
"name": "Awesome",
"type": "AMAZON.DATE",
- "samples": []
+ "samples": [
+ "I like to do awesome things on {Awesome}",
+ "I do awesome things on {Awesome}",
+ "I like to do awesome stuff on {Awesome}",
+ "I do awesome stuff on {Awesome}",
+ "{Awesome}",
+ ],
}, {
"name": "Tubular",
"type": "AMAZON.LITERAL",
@@ -658,7 +836,56 @@ describe("Alexa", function() {
}]
}],
"types": []
- }
+ },
+ "prompts": [
+ {
+ "id": "Elicit.Slot.0",
+ "variations": [
+ {
+ "type": "SSML",
+ "value": "which tubular do you use ?",
+ }
+ ]
+ },
+ {
+ "id": "Confirm.Slot.1",
+ "variations": [
+ {
+ "type": "SSML",
+ "value": "{Tubular} are you sure ?",
+ }
+ ]
+ },
+ {
+ "id": "Confirm.Intent.2",
+ "variations": [
+ {
+ "type": "SSML",
+ "value": "are you sure about {AirportCode} and {Tubular} ?"
+ }
+ ]
+ },
+ {
+ "id": "Elicit.Slot.3",
+ "variations": [
+ {
+ "type": "SSML",
+ "value": "When do you do awesome things ?",
+ }
+ ]
+ },
+ {
+ "id": "Confirm.Slot.4",
+ "variations": [
+ {
+ "type": "SSML",
+ "value": "I never though you could do awesome things that date of :{Awesome} ! Are you sure ?",
+ }
+ ]
+ }
+ ]
+
+
}
});
});
@@ -679,6 +906,9 @@ describe("Alexa", function() {
var subject = JSON.parse(testApp.schemas.askcli());
expect(subject).to.eql({
"interactionModel": {
+ "dialog": {
+ "intents": []
+ },
"languageModel": {
"invocationName": "testApp",
"intents": [],
@@ -698,7 +928,8 @@ describe("Alexa", function() {
}
}]
}]
- }
+ },
+ "prompts": []
}
});
});
@@ -719,6 +950,9 @@ describe("Alexa", function() {
var subject = JSON.parse(testApp.schemas.askcli());
expect(subject).to.eql({
"interactionModel": {
+ "dialog": {
+ "intents": []
+ },
"languageModel": {
"invocationName": "testApp",
"intents": [],
@@ -753,7 +987,8 @@ describe("Alexa", function() {
}
}]
}]
- }
+ },
+ "prompts": []
}
});
});