It's your lucky day. This is how I would do this with multiple MODES
in Node.js.
Disclaimer: I am providing this lengthy response because I am desperate for reputation points and would like to become
a developer evangelist. ;)
Given the Node.js SDK and it's many features this is the format I would use.
// To zip and upload to lambda
// cd Desktop/StudentSkill
// sudo rm -r foo.zip
// zip -r foo.zip .s
'use strict';
var Alexa = require("alexa-sdk");
var appId = 'YOUR-AMAZON-ALEXA-SKILL-ID';
exports.handler = function(event, context, callback) {
var alexa = Alexa.handler(event, context);
alexa.appId = appId;
alexa.registerHandlers(newSessionHandlers, studentSkillSessionHandlers, specificClassSessionHandlers, functionHandlers);
alexa.execute();
};
var states = {
STUDENTMODE: '_STUDENTMODE',
CLASSMODE: '_CLASSMODE',
};
//Variables
var myStudents = {'josh':{'math':'A Plus','english':'A Plus','gym':'C'},'sam':{'math':'A Plus','english':'A minus','gym':'B Plus'}}; //add more classes.
//Could add an intent to create new students.
//var newStudent = {name:{'math':null,'english':null,'gym':null}};
//Could also add an intent to set grades
var newSessionHandlers = {
'NewSession': function() {
this.handler.state = states.STUDENTMODE;
var message = `Welcome to the School Skill, You may add students, edit grades, and review grades. For a list of uses say information. `; //this skill only contains reviewing grades.
var reprompt = ` try saying, grades for josh. `;
this.emit(':ask',message,reprompt);
},
'Unhandled': function() {
console.log("UNHANDLED");
this.emit('NewSession');
}
};
/////////////////////////////////
var studentSkillSessionHandlers = Alexa.CreateStateHandler(states.STUDENTMODE, {//Your location
'NewSession': function () {
this.emit('NewSession'); // Uses the handler in newSessionHandlers
},
//Primary Intents
'GetGradeIntent': function () { // Sampe Utterance: Tell me the marks of {student} or Grades for {student}
this.handler.state = states.CLASSMODE; //Change mode to accept a class, the intent handler getClassIntent is only available in CLASSMODE
this.attributes['CURRENTSTUDENT'] = this.event.request.intent.slots.student.value;
var message = ` which of `+this.attributes['CURRENTSTUDENT']+`'s classes would you like the grade for, name a class or say all. `;
var reprompt = message;
this.emit(':ask',message,reprompt);
},
//Help Intents
"InformationIntent": function() {
console.log("INFORMATION");
var message = ` Try saying, Tell me the marks of josh. `;
this.emit(':ask', message, message);
},
"AMAZON.StopIntent": function() {
console.log("STOPINTENT");
this.emit(':tell', "Goodbye!");
},
"AMAZON.CancelIntent": function() {
console.log("CANCELINTENT");
this.emit(':tell', "Goodbye!");
},
'AMAZON.HelpIntent': function() {
var message = helpMessage;
this.emit(':ask', message, message);
},
//Unhandled
'Unhandled': function() {
console.log("UNHANDLED");
var reprompt = ` That was not an appropriate response. which student would you like grades for. Say, grades for josh. `;
this.emit(':ask', reprompt, reprompt);
}
});
////////////////////////////
/////////////////////////////////
var specificClassSessionHandlers = Alexa.CreateStateHandler(states.CLASSMODE, {//Your location
'NewSession': function () {
this.emit('NewSession'); // Uses the handler in newSessionHandlers
},
//Primary Intents
'GetClassIntent': function () { // {className} class. ex: gym class, math class, english class. It helps to have a word that's not a slot. but amazon may pick it up correctly if you just say {className}
this.attributes['CLASSNAME'] = this.event.request.intent.slots.className.value;
var message = ``;
var reprompt = ``;
if(this.attributes['CLASSNAME'] != undefined){
message = ` I didn't get that class name. would you please repeat it. `;
reprompt = message;
}else{
grade = myStudents[this.attributes['CURRENTSTUDENT']][this.attributes['CLASSNAME']];
if(grade != undefined){
this.handler.state = states.STUDENTMODE; //Answer was present. return to student mode.
message = this.attributes['CURRENTSTUDENT']+`'s `+[this.attributes['CLASSNAME']+` grade is `+aAn(grade)+` `+grade+`. What else would you like to know?`; //Josh's math grade is an A plus.
reprompt = `what else would you like to know?`;
}else{
message = this.attributes['CURRENTSTUDENT']+` does not appear to have a grade for `+[this.attributes['CLASSNAME']+` please try again with a different class or say back.`;
reprompt = `please try again with a different class or say back.`;
}
}
var message = this.attributes['FROM'] + ' .. '+ ProFirstCity;
var reprompt = ProFirstReprompt;
this.emit(':ask',message,reprompt);
},
"AllIntent": function() {// Utterance: All, All Classes
message = ``;
//Not going to code.
//Pseudo code
// for each in json object myStudents[this.attributes['CURRENTSTUDENT']] append to message class name and grade.
this.emit(':ask', message, message);
},
"BackIntent": function() {// Utterance: Back, go back
var message = ` Who's grade would you like to know. try saying, grades for josh. `;
this.emit(':ask', message, message);
},
//Help Intents
"InformationIntent": function() {
console.log("INFORMATION");
var message = ` You've been asked for which of `+this.attributes['CURRENTSTUDENT']+`'s classes you'd his grade. Please name a class or say back. `;
this.emit(':ask', message, 'Name a class or say back.');
},
"AMAZON.StopIntent": function() {
console.log("STOPINTENT");
this.emit(':tell', "Goodbye!");
},
"AMAZON.CancelIntent": function() {
console.log("CANCELINTENT");
this.emit(':tell', "Goodbye!");
},
'AMAZON.HelpIntent': function() {
var message = helpMessage;
this.emit(':ask', message, message);
},
//Unhandled
'Unhandled': function() {
console.log("UNHANDLED");
var reprompt = ' That was not an appropriate response. Name a class or say back.';
this.emit(':ask', reprompt, reprompt);
}
});
////////////////////////////////////////////////////////////
var functionHandlers = {//NOT USED IN THIS APP //Note tied to a specific mode.
};
//#############################HELPERS VVVV#########################
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function clone(a) {
return JSON.parse(JSON.stringify(a));
}
function responseRandomizer(responseType){
let len = responseType.length;
let index = getRandomInt(0,len-1);
return responseType[index];
}
var vowels = {}
function aAn(word){
if(word != ''){
let first = word[0];
if(/[aAeEiIoOuU]/.test(first)){
return 'an';
}else{
return 'a';
}
}else{
return '';
}
}
Please note: this code was adapted from a live skill but has not been tested on it's own.
To start off requesting a follow up question you need to understand the different stateHandlers
. When you invoke a new skill it goes to the newSessionHandlers
from there you can run some sort of setup code and then change the MODE
to a lobby to capture the main intents for the skill. I've named this lobby STUDENTMODE
. Inside STUDENTMODE
you can ask for the grades of a student and you could theoretically create a new student or add a class or what not. If you use the existing intent GetGradeIntent
and supply it an apropriate name it will save the name of the student in the session state and change the mode to CLASSMODE
which only accepts the Intents ClassNameIntent and BackIntent. If you try to invoke some other intent you will be reprompted for the name of a class by the UnhandledIntent
. Upon provideing an appropriate class or saying "all" you will be provided a response and the mode will be changed back to STUDENTMODE
. this plops you back in the lobby where you can ask questions about other students. Voila!
This process of changing modes is much better than Multi Part Intent Schema's like "Tell me grades for {studentName} in {mathClass}". While this can certainly work one reason modes are better is that they allow you to properly handle errors if one of the input values is incorrect, like student name or class name. You can easily ask for a single piece of information instead of asking the user to restate the entire multi part intent. It also allows you to handle getting multiple pieces of information one small piece at a time with ample instructions as apposed to allowing Alexa to continue to ask reprompt questions until all the required slots are filled.
One items I didn't cover.
Where are you storing your students? I have them hard coded into the lambda function. You could connect to amazon's dynamodb and store your session states there so they are available on the next session. that's actually as simple as adding.
alexa.dynamoDBTableName = 'NAME-OF-YOUR-DynamoDB-TABLE'; //You literally dont need any other code it just does the saving and loading??!! WHAT?
to your function here.
exports.handler = function(event, context, callback) {
var alexa = Alexa.handler(event, context);
alexa.appId = appId;
alexa.dynamoDBTableName = 'NAME-OF-YOUR-DynamoDB-TABLE'; //You literally don't need any other code it just does the saving and loading??!! WHAT?
alexa.registerHandlers(newSessionHandlers, studentSkillSessionHandlers, specificClassSessionHandlers, functionHandlers);
alexa.execute();
};
You'll need to create a dynamoDB data table and an IAm permission to allow your lambda function to access it. Then, magically, Alexa will create a single row in your data table for each unique user. A single teacher could easily add the students in their class. However, if you're looking for each teacher in a school to access one master database this is likely not the correct approach. There are likely other tutorials on how to connect Alexa to a single data table across multiple users.
I believe the core concern of your question was answered by the
different MODES
where you can block out unwanted intents. If you
found this response helpful I take payment in trident layers and
reputation. Thanks!