Alexa Developer Console replying with "There was a problem with the requested skill's response" - alexa

I have been trying to resolve this issue for the past couple of hours with no success. Here is my code:
Lambda code:
/*eslint-disable func-names */
/* eslint quote-props: ["error", "consistent"]*/
// There are three sections, Text Strings, Skill Code, and Helper Function(s).
// You can copy and paste the contents as the code for a new Lambda function, using the alexa-skill-kit-sdk-factskill template.
// This code includes helper functions for compatibility with versions of the SDK prior to 1.0.9, which includes the dialog directives.
// 1. Text strings =====================================================================================================
// Modify these strings and messages to change the behavior of your Lambda function
let speechOutput;
let reprompt;
let welcomeOutput = "Welcome to Tanpura! You can ask me to play at any pitch. How can I help you today?";
let welcomeReprompt = "sample re-prompt text";
// 2. Skill Code =======================================================================================================
"use strict";
const Alexa = require('alexa-sdk');
const APP_ID = undefined; // TODO replace with your app ID (OPTIONAL).
speechOutput = '';
const handlers = {
'LaunchRequest': function () {
this.emit(':ask', welcomeOutput, welcomeReprompt);
},
'AMAZON.HelpIntent': function () {
speechOutput = 'You can ask me to play at any pitch, such as C, C sharp, D, D sharp, and so on. How can I help you?';
reprompt = '';
this.emit(':ask', speechOutput, reprompt);
},
'AMAZON.CancelIntent': function () {
speechOutput = 'Would you like me to play another pitch?';
this.emit(':tell', speechOutput);
},
'AMAZON.StopIntent': function () {
speechOutput = 'I hope I helped you. Goodbye!';
this.emit(':tell', speechOutput);
},
'SessionEndedRequest': function () {
speechOutput = '';
//this.emit(':saveState',true);//uncomment to save attributes to db on session end
this.emit(':tell', speechOutput);
},
'AMAZON.FallbackIntent': function () {
speechOutput = '';
//any intent slot variables are listed here for convenience
//Your custom intent handling goes here
speechOutput = "I currently do not support your request. However, you can ask me to play at any pitch, and I will do so. How can I help?";
this.emit(":ask", speechOutput, speechOutput);
},
'AMAZON.NavigateHomeIntent': function () {
speechOutput = '';
//any intent slot variables are listed here for convenience
//Your custom intent handling goes here
speechOutput = "Welcome to Tanpura! You can ask me to play at any pitch. How can I help you today?";
this.emit(":ask", speechOutput, speechOutput);
},
'PlayNoteIntent': function () {
speechOutput = '';
//any intent slot variables are listed here for convenience
let noteSlot = resolveCanonical(this.event.request.intent.slots.note);
console.log("User selected pitch: " + noteSlot);
let accidentalSlot = resolveCanonical(this.event.request.intent.slots.accidental);
console.log("User selected accidental: " + accidentalSlot);
var notes = {
'a': {
'flat': 'https://s3.amazonaws.com/tanpuranew/tanpura_Gsharp.mp3',
'natural': 'https://s3.amazonaws.com/tanpuranew/tanpura_A.mp3',
'sharp': 'https://s3.amazonaws.com/tanpuranew/tanpura_Asharp.mp3'
},
'b': {
'flat': 'https://s3.amazonaws.com/tanpuranew/tanpura_Asharp.mp3',
'natural': 'https://s3.amazonaws.com/tanpuranew/tanpura_B.mp3',
'sharp': 'https://s3.amazonaws.com/tanpuranew/tanpura_C.mp3'
},
'c': {
'flat': 'https://s3.amazonaws.com/tanpuranew/tanpura_B.mp3',
'natural': 'https://s3.amazonaws.com/tanpuranew/tanpura_C.mp3',
'sharp': 'https://s3.amazonaws.com/tanpuranew/tanpura_Csharp.mp3'
},
'd': {
'flat': 'https://s3.amazonaws.com/tanpuranew/tanpura_Csharp.mp3',
'natural': 'https://s3.amazonaws.com/tanpuranew/tanpura_D.mp3',
'sharp': 'https://s3.amazonaws.com/tanpuranew/tanpura_Dsharp.mp3'
},
'e': {
'flat': 'https://s3.amazonaws.com/tanpuranew/tanpura_Dsharp.mp3',
'natural': 'https://s3.amazonaws.com/tanpuranew/tanpura_E.mp3',
'sharp': 'https://s3.amazonaws.com/tanpuranew/tanpura_F.mp3'
},
'f': {
'flat': 'https://s3.amazonaws.com/tanpuranew/tanpura_E.mp3',
'natural': 'https://s3.amazonaws.com/tanpuranew/tanpura_F.mp3',
'sharp': 'https://s3.amazonaws.com/tanpuranew/tanpura_Fsharp.mp3'
},
'g': {
'flat': 'https://s3.amazonaws.com/tanpuranew/tanpura_Fsharp.mp3',
'natural': 'https://s3.amazonaws.com/tanpuranew/tanpura_G.mp3',
'sharp': 'https://s3.amazonaws.com/tanpuranew/tanpura_Gsharp.mp3'
}
}
var note = noteSlot.toLowerCase();
var speechReprompt = "";
if (noteSlot && notes[note]){
var audio = '';
var accidental = 'natural';
if(accidentalSlot && accidental.indexOf(accidentalSlot) > -1){
accidental = accidentalSlot;
}
var audioSrc = notes[note][accidental];
speechOutput = "Ok. I will play " + noteSlot + accidental + <audio src="' + audioSrc + '" />;
speechReprompt = "Would you like me to continue playing?";
}
else{
speechOutput = "The note you have requested is not supported yet.";
speechReprompt = "However, Tanpura does support A, B, C, D, E, F, G, and all of the accidentals in between.";
}
//Your custom intent handling goes here
this.emit(":ask", speechOutput, speechOutput);
},
'Unhandled': function () {
speechOutput = "Tanpura didn't quite understand what you wanted. Please try rephrasing your request.";
this.emit(':ask', speechOutput, speechOutput);
}
};
exports.handler = (event, context) => {
const alexa = Alexa.handler(event, context);
alexa.appId = APP_ID;
// To enable string internationalization (i18n) features, set a resources object.
//alexa.resources = languageStrings;
alexa.registerHandlers(handlers);
//alexa.dynamoDBTableName='DYNAMODB_TABLE_NAME';//uncomment this line to save attributes to DB
alexa.execute();
};
// END of Intent Handlers {} ========================================================================================
// 3. Helper Function =================================================================================================
function resolveCanonical(slot){
//this function looks at the entity resolution part of request and returns the slot value if a synonyms is provided
let canonical;
try{
canonical = slot.resolutions.resolutionsPerAuthority[0].values[0].value.name;
}catch(err){
console.log(err.message);
canonical = slot.value;
};
return canonical;
};
function delegateSlotCollection(){
console.log("in delegateSlotCollection");
console.log("current dialogState: "+this.event.request.dialogState);
if (this.event.request.dialogState === "STARTED") {
console.log("in Beginning");
let updatedIntent= null;
// updatedIntent=this.event.request.intent;
//optionally pre-fill slots: update the intent object with slot values for which
//you have defaults, then return Dialog.Delegate with this updated intent
// in the updatedIntent property
//this.emit(":delegate", updatedIntent); //uncomment this is using ASK SDK 1.0.9 or newer
//this code is necessary if using ASK SDK versions prior to 1.0.9
if(this.isOverridden()) {
return;
}
this.handler.response = buildSpeechletResponse({
sessionAttributes: this.attributes,
directives: getDialogDirectives('Dialog.Delegate', updatedIntent, null),
shouldEndSession: false
});
this.emit(':responseReady', updatedIntent);
} else if (this.event.request.dialogState !== "COMPLETED") {
console.log("in not completed");
// return a Dialog.Delegate directive with no updatedIntent property.
//this.emit(":delegate"); //uncomment this is using ASK SDK 1.0.9 or newer
//this code necessary is using ASK SDK versions prior to 1.0.9
if(this.isOverridden()) {
return;
}
this.handler.response = buildSpeechletResponse({
sessionAttributes: this.attributes,
directives: getDialogDirectives('Dialog.Delegate', null, null),
shouldEndSession: false
});
this.emit(':responseReady');
} else {
console.log("in completed");
console.log("returning: "+ JSON.stringify(this.event.request.intent));
// Dialog is now complete and all required slots should be filled,
// so call your normal intent handler.
return this.event.request.intent;
}
}
function randomPhrase(array) {
// the argument is an array [] of words or phrases
let i = 0;
i = Math.floor(Math.random() * array.length);
return(array[i]);
}
function isSlotValid(request, slotName){
let slot = request.intent.slots[slotName];
//console.log("request = "+JSON.stringify(request)); //uncomment if you want to see the request
let slotValue;
//if we have a slot, get the text and store it into speechOutput
if (slot && slot.value) {
//we have a value in the slot
slotValue = slot.value.toLowerCase();
return slotValue;
} else {
//we didn't get a value in the slot.
return false;
}
}
//These functions are here to allow dialog directives to work with SDK versions prior to 1.0.9
//will be removed once Lambda templates are updated with the latest SDK
function createSpeechObject(optionsParam) {
if (optionsParam && optionsParam.type === 'SSML') {
return {
type: optionsParam.type,
ssml: optionsParam['speech']
};
} else {
return {
type: optionsParam.type || 'PlainText',
text: optionsParam['speech'] || optionsParam
};
}
}
function buildSpeechletResponse(options) {
let alexaResponse = {
shouldEndSession: options.shouldEndSession
};
if (options.output) {
alexaResponse.outputSpeech = createSpeechObject(options.output);
}
if (options.reprompt) {
alexaResponse.reprompt = {
outputSpeech: createSpeechObject(options.reprompt)
};
}
if (options.directives) {
alexaResponse.directives = options.directives;
}
if (options.cardTitle && options.cardContent) {
alexaResponse.card = {
type: 'Simple',
title: options.cardTitle,
content: options.cardContent
};
if(options.cardImage && (options.cardImage.smallImageUrl || options.cardImage.largeImageUrl)) {
alexaResponse.card.type = 'Standard';
alexaResponse.card['image'] = {};
delete alexaResponse.card.content;
alexaResponse.card.text = options.cardContent;
if(options.cardImage.smallImageUrl) {
alexaResponse.card.image['smallImageUrl'] = options.cardImage.smallImageUrl;
}
if(options.cardImage.largeImageUrl) {
alexaResponse.card.image['largeImageUrl'] = options.cardImage.largeImageUrl;
}
}
} else if (options.cardType === 'LinkAccount') {
alexaResponse.card = {
type: 'LinkAccount'
};
} else if (options.cardType === 'AskForPermissionsConsent') {
alexaResponse.card = {
type: 'AskForPermissionsConsent',
permissions: options.permissions
};
}
let returnResult = {
version: '1.0',
response: alexaResponse
};
if (options.sessionAttributes) {
returnResult.sessionAttributes = options.sessionAttributes;
}
return returnResult;
}
function getDialogDirectives(dialogType, updatedIntent, slotName) {
let directive = {
type: dialogType
};
if (dialogType === 'Dialog.ElicitSlot') {
directive.slotToElicit = slotName;
} else if (dialogType === 'Dialog.ConfirmSlot') {
directive.slotToConfirm = slotName;
}
if (updatedIntent) {
directive.updatedIntent = updatedIntent;
}
return [directive];
}
Alexa Developer JSON Input:
{
"version": "1.0",
"session": {
"new": true,
"sessionId": "amzn1.echo-api.session.1456bfda-a9d3-457f-88a7-bc5387d774db",
"application": {
"applicationId": "amzn1.ask.skill.0c237132-8815-4025-a23f-ca6df688bcd2"
},
"user": {
"userId": "amzn1.ask.account.AGD7V7GZTLU4DQH623OMU5MUBR2FGWXKDVW2OPNYYRWKIYJCHQBCSKVNQHEPOEXQWO33Q4OTJ6LSIRLYT3TN33OAK3W7LLNNYPU5S3MVKPMPNH2XDWYJ7DBWCFZRXY4STCPFKVL2FADYZE4TXS53Z5AXBPN6344R6VG6GD365TFQTCPPKABC5IKM46UZXUX3BPR4TQ4KEYO6LTA"
}
},
"context": {
"System": {
"application": {
"applicationId": "amzn1.ask.skill.0c237132-8815-4025-a23f-ca6df688bcd2"
},
"user": {
"userId": "amzn1.ask.account.AGD7V7GZTLU4DQH623OMU5MUBR2FGWXKDVW2OPNYYRWKIYJCHQBCSKVNQHEPOEXQWO33Q4OTJ6LSIRLYT3TN33OAK3W7LLNNYPU5S3MVKPMPNH2XDWYJ7DBWCFZRXY4STCPFKVL2FADYZE4TXS53Z5AXBPN6344R6VG6GD365TFQTCPPKABC5IKM46UZXUX3BPR4TQ4KEYO6LTA"
},
"device": {
"deviceId": "amzn1.ask.device.AFHBRIBVUWYIR2ESXPKWP3G3PHYK4W5VW4NF55KH5ZXD27WMSPBPU7YLJQJWM2YQDZBH7VWGXCLNQKESUNWWGI6CJUWUUSWUKVBZWZC5LBNXMCDY2IOZAZUYWHYXT5VLLA7XC3OP2WY7RXE2LPRHM5E4BIMR662M5MZKJH4WRPUFS3HVIFRDK",
"supportedInterfaces": {}
},
"apiEndpoint": "https://api.amazonalexa.com",
"apiAccessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJhdWQiOiJodHRwczovL2FwaS5hbWF6b25hbGV4YS5jb20iLCJpc3MiOiJBbGV4YVNraWxsS2l0Iiwic3ViIjoiYW16bjEuYXNrLnNraWxsLjBjMjM3MTMyLTg4MTUtNDAyNS1hMjNmLWNhNmRmNjg4YmNkMiIsImV4cCI6MTUzOTU1MzE2NywiaWF0IjoxNTM5NTQ5NTY3LCJuYmYiOjE1Mzk1NDk1NjcsInByaXZhdGVDbGFpbXMiOnsiY29uc2VudFRva2VuIjpudWxsLCJkZXZpY2VJZCI6ImFtem4xLmFzay5kZXZpY2UuQUZIQlJJQlZVV1lJUjJFU1hQS1dQM0czUEhZSzRXNVZXNE5GNTVLSDVaWEQyN1dNU1BCUFU3WUxKUUpXTTJZUURaQkg3VldHWENMTlFLRVNVTldXR0k2Q0pVV1VVU1dVS1ZCWldaQzVMQk5YTUNEWTJJT1pBWlVZV0hZWFQ1VkxMQTdYQzNPUDJXWTdSWEUyTFBSSE01RTRCSU1SNjYyTTVNWktKSDRXUlBVRlMzSFZJRlJESyIsInVzZXJJZCI6ImFtem4xLmFzay5hY2NvdW50LkFHRDdWN0daVExVNERRSDYyM09NVTVNVUJSMkZHV1hLRFZXMk9QTllZUldLSVlKQ0hRQkNTS1ZOUUhFUE9FWFFXTzMzUTRPVEo2TFNJUkxZVDNUTjMzT0FLM1c3TExOTllQVTVTM01WS1BNUE5IMlhEV1lKN0RCV0NGWlJYWTRTVENQRktWTDJGQURZWkU0VFhTNTNaNUFYQlBONjM0NFI2Vkc2R0QzNjVURlFUQ1BQS0FCQzVJS000NlVaWFVYM0JQUjRUUTRLRVlPNkxUQSJ9fQ.KAHvIOOUP4k-73lNMxRnOToYjrUbeHuLRDQGzMFi9dVEiwc2QpvpMZpLNpG5rCtoqB2-OfC48KbK5u67nW6X9QO6DSoNTBfPKUatIHB6pUWbArdv-FliUO69SQMomjLtLzC86_jnZ8TqvNavjb5I5hOGnmCe5Fv2IY5HgBw0h07Dq3ZT4i_4edcnhX9zYJretTEydF0L3JA7GTithgtAGFxbBqbiDTKRMlaGUGBWAkZkHy8FPWsAmvfTwRaNL7F3LAEbGH2QJlyoPQR7jYij7CsnlRAEv-3Ur1kFaMEdhDNA9fcn2JI4TVf1umy0fL66dHWq3omk2p5I4FyrJ3a8SQ"
}
},
"request": {
"type": "IntentRequest",
"requestId": "amzn1.echo-api.request.80cc2899-2fa2-4828-99ba-1c25d8cce05b",
"timestamp": "2018-10-14T20:39:27Z",
"locale": "en-US",
"intent": {
"name": "PlayNoteIntent",
"confirmationStatus": "NONE",
"slots": {
"note": {
"name": "note",
"value": "an",
"resolutions": {
"resolutionsPerAuthority": [
{
"authority": "amzn1.er-authority.echo-sdk.amzn1.ask.skill.0c237132-8815-4025-a23f-ca6df688bcd2.Note",
"status": {
"code": "ER_SUCCESS_NO_MATCH"
}
}
]
},
"confirmationStatus": "NONE",
"source": "USER"
},
"accidental": {
"name": "accidental",
"value": "e",
"resolutions": {
"resolutionsPerAuthority": [
{
"authority": "amzn1.er-authority.echo-sdk.amzn1.ask.skill.0c237132-8815-4025-a23f-ca6df688bcd2.accidental",
"status": {
"code": "ER_SUCCESS_NO_MATCH"
}
}
]
},
"confirmationStatus": "NONE",
"source": "USER"
}
}
}
}
}
And finally the Skill's JSON:
{
"interactionModel": {
"languageModel": {
"invocationName": "tanpura",
"intents": [
{
"name": "AMAZON.FallbackIntent",
"samples": []
},
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "PlayNoteIntent",
"slots": [
{
"name": "note",
"type": "Note"
},
{
"name": "accidental",
"type": "accidental"
}
],
"samples": [
"for an {note} {accidental}",
"for a {note} {accidental}",
"play {note} {accidental}",
"Start an {note} {accidental}",
"Give me an {note} {accidental}",
"Make an {note} {accidental}",
"Put on an {note} {accidental}",
"Sing at an {note} {accidental}",
"Sing at a {note} {accidental}",
"Create an {note} {accidental}",
"Lets hear an {note} {accidental}",
"Play an {note} {accidental}",
"Lets hear a {note} {accidental}",
"Sing at {note} {accidental}",
"Create a {note} {accidental}",
"Make a {note} {accidental}",
"Put on a {note} {accidental}",
"Initiate a {note} {accidental}",
"Give me a {note} {accidental}",
"Start a {note} {accidental}",
"Play a {note} {accidental}"
]
}
],
"types": [
{
"name": "Note",
"values": [
{
"name": {
"value": "B"
}
},
{
"name": {
"value": "A#"
}
},
{
"name": {
"value": "A"
}
},
{
"name": {
"value": "G#"
}
},
{
"name": {
"value": "G"
}
},
{
"name": {
"value": "F"
}
},
{
"name": {
"value": "E"
}
},
{
"name": {
"value": "D#"
}
},
{
"name": {
"value": "D"
}
},
{
"name": {
"value": "C#"
}
},
{
"name": {
"value": "C"
}
}
]
},
{
"name": "accidental",
"values": [
{
"name": {
"value": "natural"
}
},
{
"name": {
"value": "flat"
}
},
{
"name": {
"value": "sharp"
}
}
]
}
]
}
}
}
The code was working initially, but after I made a couple of edits to the Lambda code, I was getting the same reply again and again, which made no sense. I think that the problem may lie in the fact that I added an extra value for the accidental value, and I added a natural value before rebuilding my Lambda code in the skillinator.io site. Any help would be much appreciated as I have been struggling with this code all day.

"There was a problem with the requested skill's response” means that there is something wrong with the response json. It might be null or invalid.
In your case, this line throws an error because your string concatenation is not right.
speechOutput = "Ok. I will play " + noteSlot + accidental + <audio src="' + audioSrc + '" />;
Change it to :
speechOutput = "Ok. I will play " + noteSlot + accidental + "<audio src=\"" + audioSrc + "\" />";

Related

Alexa Skill : How to invoke custom intent from a template?

I'm trying to create an alexa skill with the help of 'Survey' template, which uses personalization + voice recognition based auth.
personalization + voice recognition based auth works fine. I have added a new intent 'Introduce' which needs to be triggered based on utterance - 'introduce' but that isn't working as expected.
Alexa open my bot
-> (welcome note from alexa)
let's begin (invokes StartMyStandupIntentHandler intent and auth based on voice id)
-> Hello How can i help you?
introduce
-> doesn't invoke IntroduceHandler but i have IntentReflectorHandler that says : You just triggered the introduce intent. You're hearing this response because introduce does not have an intent handler yet.
index.js:
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
handle(handlerInput) {
const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
const skillName = requestAttributes.t('SKILL_NAME');
const name = personalization.getPersonalizedPrompt(handlerInput);
var speakOutput = ""
if (name && name.length > 0) {
speakOutput = requestAttributes.t('GREETING_PERSONALIZED', skillName);
} else {
speakOutput = requestAttributes.t('GREETING', skillName);
}
const repromptOutput = requestAttributes.t('GREETING_REPROMPT');
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(repromptOutput)
.getResponse();
},
};
const StartMyStandupIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'StartMyStandupIntent';
},
async handle(handlerInput) {
const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
let speakOutput;
const name = personalization.getPersonalizedPrompt(handlerInput);
let response = handlerInput.responseBuilder;
if (name && name.length > 0) {
speakOutput = 'Hello '+ name +'! How can i help you?';
const upsServiceClient = handlerInput.serviceClientFactory.getUpsServiceClient();
let profileName
let profileEmail
try {
profileName = await upsServiceClient.getPersonsProfileGivenName();
profileEmail = await upsServiceClient.getProfileEmail();
} catch (error) {
return handleError(error, handlerInput)
}
const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
sessionAttributes.userEmail = profileEmail;
sessionAttributes.userName = profileName;
handlerInput.attributesManager.setSessionAttributes(sessionAttributes);
} else {
speakOutput = requestAttributes.t('PERSONALIZED_FALLBACK')
}
return response
.speak(speakOutput)
.withShouldEndSession(false)
.getResponse()
},
};
const Introduce_Handler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'Introduce'
},
handle(handlerInput) {
const responseBuilder = handlerInput.responseBuilder;
let say = 'Hi everyone, As the famous saying goes, \'The human voice is the most perfect instrument of all\', . ';
say += 'A very Warm greetings to all. ';
return responseBuilder
.speak(say)
.withShouldEndSession(false)
.getResponse();
},
};
/**
* Voice consent request - response is handled via Skill Connections.
* Hence we need to handle async response from the Voice Consent.
* The user could have accepted or rejected or skipped the voice consent request.
* Create your custom callBackFunction to handle accepted flow - in this case its handling identifying the person
* The rejected/skipped default handling is taken care by the library.
*
* #params handlerInput - the handlerInput received from the IntentRequest
* #returns
**/
async function handleCallBackForVoiceConsentAccepted(handlerInput) {
const upsServiceClient = handlerInput.serviceClientFactory.getUpsServiceClient();
let profileName = await upsServiceClient.getProfileEmail();
let profileEmail = await upsServiceClient.getPersonsProfileGivenName();
const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
sessionAttributes.userEmail = profileEmail;
sessionAttributes.userName = profileName;
handlerInput.attributesManager.setSessionAttributes(sessionAttributes);
// this is done because currently intent chaining is not supported from any
// Skill Connections requests, such as SessionResumedRequest.
const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
const name = personalization.getPersonalizedPrompt(handlerInput);
let speakOutput = 'Hello '+ name +'! How can i help you?';
//let repromptOutput = requestAttributes.t('ABOUT_REPROMPT');
let response = handlerInput.responseBuilder;
return response
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse()
}
Interaction model json:
{
"interactionModel": {
"languageModel": {
"invocationName": "my bot",
"modelConfiguration": {
"fallbackIntentSensitivity": {
"level": "LOW"
}
},
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "GetCodeIntent",
"slots": [
{
"name": "MeetingCode",
"type": "AMAZON.NUMBER"
}
],
"samples": [
"My code is {MeetingCode}",
"The code is {MeetingCode}",
"{MeetingCode}"
]
},
{
"name": "GetReportIntent",
"slots": [
{
"name": "questionYesterday",
"type": "AMAZON.SearchQuery",
"samples": [
"{questionYesterday}"
]
},
{
"name": "questionToday",
"type": "AMAZON.SearchQuery",
"samples": [
"{questionToday}"
]
},
{
"name": "questionBlocking",
"type": "AMAZON.SearchQuery",
"samples": [
"{questionBlocking}"
]
}
],
"samples": [
"{questionToday} today",
"{questionYesterday} yesterday",
"yesterday {questionYesterday}",
"today {questionToday}"
]
},
{
"name": "AMAZON.FallbackIntent",
"samples": []
},
{
"name": "AMAZON.YesIntent",
"samples": []
},
{
"name": "AMAZON.NoIntent",
"samples": []
},
{
"name": "ResetPinIntent",
"slots": [],
"samples": [
"where do i get a pin",
"what is my pin",
"how do i get a pin",
"i need a new pin",
"i forgot my pin"
]
},
{
"name": "StartMyStandupIntent",
"slots": [],
"samples": [
"yes let's get started",
"yes let's begin",
"let's begin",
"let's get started"
]
},
{
"name": "Introduce",
"slots": [],
"samples": [
"introduce yourself",
"introduce",
"intro"
]
}
],
"types": []
},
"dialog": {
"intents": [
{
"name": "GetReportIntent",
"delegationStrategy": "ALWAYS",
"confirmationRequired": false,
"prompts": {},
"slots": [
{
"name": "questionYesterday",
"type": "AMAZON.SearchQuery",
"confirmationRequired": false,
"elicitationRequired": true,
"prompts": {
"elicitation": "Elicit.Slot.420907304064.1434077833163"
}
},
{
"name": "questionToday",
"type": "AMAZON.SearchQuery",
"confirmationRequired": false,
"elicitationRequired": true,
"prompts": {
"elicitation": "Elicit.Slot.173201382582.539843571833"
}
},
{
"name": "questionBlocking",
"type": "AMAZON.SearchQuery",
"confirmationRequired": false,
"elicitationRequired": true,
"prompts": {
"elicitation": "Elicit.Slot.173201382582.1204298947985"
}
}
]
}
],
"delegationStrategy": "ALWAYS"
},
"prompts": [
{
"id": "Elicit.Slot.288779318596.409557698368",
"variations": [
{
"type": "PlainText",
"value": "Alright, first question. What did you do yesterday?"
}
]
},
{
"id": "Elicit.Slot.288779318596.1420775370020",
"variations": [
{
"type": "PlainText",
"value": "Got it. What will you do today?"
}
]
},
{
"id": "Elicit.Slot.288779318596.88143460540",
"variations": [
{
"type": "PlainText",
"value": "Okay, last question. Is there anything blocking your progress?"
}
]
},
{
"id": "Elicit.Slot.420907304064.1434077833163",
"variations": [
{
"type": "PlainText",
"value": "What did you work on yesterday?"
}
]
},
{
"id": "Elicit.Slot.173201382582.539843571833",
"variations": [
{
"type": "PlainText",
"value": "What will you work on today?"
}
]
},
{
"id": "Elicit.Slot.173201382582.1204298947985",
"variations": [
{
"type": "PlainText",
"value": "What if anything is blocking your progress?"
}
]
}
]
}
}
I suspect you missed adding your intent handler to the Skill object. See this snipet from the Alexa documentation
let skill;
exports.handler = async function (event, context) {
console.log(`REQUEST++++${JSON.stringify(event)}`);
if (!skill) {
skill = Alexa.SkillBuilders.custom()
.addRequestHandlers(
LaunchRequestHandler,
StartMyStandupIntentHandler,
HelpIntentHandler,
CancelAndStopIntentHandler,
SessionEndedRequestHandler,
)
.addErrorHandlers(ErrorHandler)
.create();
}
const response = await skill.invoke(event, context);
console.log(`RESPONSE++++${JSON.stringify(response)}`);
return response;
};
You need to add the Introduce_Handler to the addRequestHandlers method call. Also, make sure to add it before the intent reflector handler. ASK will prioritize which handler is used based on the order they are added to the skill object. Your code will probably look something like this:
.addRequestHandlers(
LaunchRequestHandler,
AskWeatherIntentHandler,
Introduce_Handler,
HelpIntentHandler,
CancelAndStopIntentHandler,
SessionEndedRequestHandler,
IntentReflectorHandler
)

How do I sort this array by date?

I'm trying to sort the dates from this external API in my latestResults array by latest on top to oldest on bottom but can't seem to figure out how.
Right now they're displayed with the oldest date first and it's working fine, but it's in the wrong order for me.
I tried using result in latestResults.reverse() but that just reverses the 7 items currently in the array.
HTML:
<div v-for="result in latestResults" v-bind:key="result.latestResults">
<small">{{ result.utcDate }}</small>
</div>
Script:
<script>
import api from '../api'
export default {
data () {
return {
latestResults: [],
limit: 7,
busy: false,
loader: false,
}
},
methods: {
loadMore() {
this.loader = true;
this.busy = true;
api.get('competitions/PL/matches?status=FINISHED')
.then(response => { const append = response.data.matches.slice(
this.latestResults.length,
this.latestResults.length + this.limit,
this.latestResults.sort((b, a) => {
return new Date(b.utcDate) - new Date(a.utcDate);
})
);
setTimeout(() => {
this.latestResults = this.latestResults.concat(append);
this.busy = false;
this.loader = false;
}, 500);
});
}
},
created() {
this.loadMore();
}
}
</script>
The JSON where I'm getting matches like this that has utcDate:
{
"count": 205,
"filters": {
"status": [
"FINISHED"
]
},
"competition": {
"id": 2021,
"area": {
"id": 2072,
"name": "England"
},
"name": "Premier League",
"code": "PL",
"plan": "TIER_ONE",
"lastUpdated": "2021-02-01T16:20:10Z"
},
"matches": [
{
"id": 303759,
"season": {
"id": 619,
"startDate": "2020-09-12",
"endDate": "2021-05-23",
"currentMatchday": 22
},
"utcDate": "2020-09-12T11:30:00Z",
"status": "FINISHED",
"matchday": 1,
"stage": "REGULAR_SEASON",
"group": "Regular Season",
"lastUpdated": "2020-09-13T00:08:13Z",
"odds": {
"msg": "Activate Odds-Package in User-Panel to retrieve odds."
},
},

Alexa Trivia Skill - Quiz

I was trying to make a Trivia Quiz. In general it is working fine, but the quiz doesn't recognize the answer and doesn't count the score correctly.
I don't know where to find the mistake in the code and how to change it.
I used this instruction and code for the quiz:
https://github.com/alexa/skill-sample-nodejs-trivia
I would be happy, if someone could help me.
For what you say, it may be both a problem of the Interaction Model and the Backend logic. Share the code, if you can :)
This is the code of the interaction model:
{
"interactionModel": {
"languageModel": {
"invocationName": "teach me",
"intents": [
{
"name": "AnswerIntent",
"slots": [
{
"name": "Answer",
"type": "AMAZON.NUMBER"
}
],
"samples": [
"die Antwort ist {Answer}",
"meine Antwort ist {Answer}",
"ist die Antwort {Answer}",
"{Answer} ist meine Antwort",
"{Answer}"
]
},
{
"name": "DontKnowIntent",
"slots": [],
"samples": [
"weiß ich nicht",
"weiß nicht",
"überspringen",
"das weiß ich nicht",
"wer weiß",
"diese Frage weiß ich nicht",
"die weiß ich nicht",
"keine Ahnung"
]
},
{
"name": "AMAZON.StartOverIntent",
"samples": [
"Spiel starten",
"neues Spiel",
"starten",
"neues Spiel starten"
]
},
{
"name": "AMAZON.RepeatIntent",
"samples": [
"bitte wiederholen",
"noch einmal",
"wiederholen"
]
},
{
"name": "AMAZON.HelpIntent",
"samples": [
"Ich brauche Hilfe",
"Hilfe"
]
},
{
"name": "AMAZON.YesIntent",
"samples": []
},
{
"name": "AMAZON.NoIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
}
],
"types": []
}
}
}
And I think the problem in the backend could be in the following part of the code:
function isAnswerSlotValid(intent) {
const answerSlotFilled = intent
&& intent.slots
&& intent.slots.Answer
&& intent.slots.Answer.value;
const answerSlotIsInt = answerSlotFilled
&& !Number.isNaN(parseInt(intent.slots.Answer.value, 10));
return answerSlotIsInt
&& parseInt(intent.slots.Answer.value, 10) < (ANSWER_COUNT + 1)
&& parseInt(intent.slots.Answer.value, 10) > 0;
}
function handleUserGuess(userGaveUp, handlerInput) {
const { requestEnvelope, attributesManager, responseBuilder } = handlerInput;
const { intent } = requestEnvelope.request;
const answerSlotValid = isAnswerSlotValid(intent);
let speechOutput = '';
let speechOutputAnalysis = '';
let aplFirstPageSpeechOutput = '';
let aplSecondPageSpeechOutput = '';
const sessionAttributes = attributesManager.getSessionAttributes();
const gameQuestions = sessionAttributes.questions;
let correctAnswerIndex = parseInt(sessionAttributes.correctAnswerIndex, 10);
let currentScore = parseInt(sessionAttributes.score, 10);
let currentQuestionIndex = parseInt(sessionAttributes.currentQuestionIndex, 10);
const { correctAnswerText } = sessionAttributes;
const requestAttributes = attributesManager.getRequestAttributes();
const translatedQuestions = requestAttributes.t('QUESTIONS');
if (answerSlotValid
&& parseInt(intent.slots.Answer.value, 10) ===
sessionAttributes.correctAnswerIndex) {
currentScore += 1;
speechOutputAnalysis = requestAttributes.t('ANSWER_CORRECT_MESSAGE');
} else {
if (!userGaveUp) {
speechOutputAnalysis = requestAttributes.t('ANSWER_WRONG_MESSAGE');
}
speechOutputAnalysis += requestAttributes.t(
'CORRECT_ANSWER_MESSAGE',
correctAnswerIndex,
correctAnswerText
);
}
If you need more code of the backend logic, let me know
Thank you for your help :)

Response duplicated but the count shows as 1

Using Dynamoose ORM with Serverless. I have a scenario where I'm finding user information based on recommendation.
The response is as follows
{
"data": {
"results": [
{
"specialTip": "Hello World",
"recommendation": "Huli ka!",
"poi": {
"uuid": "poi_555",
"name": "Bukit Panjang",
"images": [
{
"url": "facebook.com",
"libraryUuid": "2222",
"uuid": "9999"
}
]
},
"uuid": "i_8253578c-600d-4dfd-bd40-ce5b9bb89067",
"headline": "Awesome",
"dataset": "attractions",
"insiderUUID": "i_c932e85b-0aee-4462-b930-962f555b64bd",
"insiderInfo": [
{
"gender": "m",
"funFacts": [
{
"type": "knock knock!",
"answer": "Who's there?"
}
],
"profileImage": "newImage.jpg",
"shortDescription": "Samething",
"fullDescription": "Whatever Description",
"interests": [
"HELLO",
"WORLD"
],
"tribes": [
"HELLO",
"WORLD"
],
"uuid": "i_c932e85b-0aee-4462-b930-962f555b64bd",
"personalities": [
"HELLO",
"WORLD"
],
"travelledCities": [
"HELLO",
"WORLD"
]
}
]
},
{
"specialTip": "Hello World",
"recommendation": "Huli ka!",
"poi": {
"uuid": "poi_555",
"name": "Bukit Panjang",
"images": [
{
"url": "facebook.com",
"libraryUuid": "2222",
"uuid": "9999"
}
]
},
"uuid": "i_8253578c-600d-4dfd-bd40-ce5b9bb89067",
"headline": "Awesome",
"dataset": "attractions",
"insiderUUID": "i_c932e85b-0aee-4462-b930-962f555b64bd",
"insiderInfo": [
{
"gender": "m",
"funFacts": [
{
"type": "knock knock!",
"answer": "Who's there?"
}
],
"profileImage": "newImage.jpg",
"shortDescription": "Samething",
"fullDescription": "Whatever Description",
"interests": [
"HELLO",
"WORLD"
],
"tribes": [
"HELLO",
"WORLD"
],
"uuid": "i_c932e85b-0aee-4462-b930-962f555b64bd",
"personalities": [
"HELLO",
"WORLD"
],
"travelledCities": [
"HELLO",
"WORLD"
]
}
]
}
],
"count": 1
},
"statusCode": 200
}
Not sure where I'm going wrong as the items in the response seems to be duplicated but the count is 1.
Here is the code
module.exports.index = (_event, _context, callback) => {
Recommendation.scan().exec((_err, recommendations) => {
if (recommendations.count == 0) {
return;
}
let results = [];
recommendations.forEach((recommendation) => {
Insider.query({uuid: recommendation.insiderUUID}).exec((_err, insider) => {
if (insider.count == 0) {
return;
}
recommendation.insiderInfo = insider;
results.push(recommendation);
});
});
const response = {
data: {
results: results,
count: results.count
},
statusCode: 200
};
callback(null, response);
});
};
EDIT: My previous code ignored the fact that your "Insider" query is asynchronous. This new code handles that and matches your edit.
const async = require('async'); // install async with 'npm install --save async'
[...]
module.exports.index = (_event, _context, callback) => {
Recommendation.scan().exec((_err, recommendations) => {
if (_err) {
console.log(_err);
return callback(_err);
}
if (recommendations.count == 0) {
const response = {
data: {
results: [],
count: 0
},
statusCode: 200
};
return callback(null, response);
}
let results = [];
async.each(recommendations, (recommendation, cb) => { // We need to handle each recommendation asynchronously...
Insider.query({uuid: recommendation.insiderUUID}).exec((_err, insider) => { // because this is asynchronous
if (_err) {
console.log(_err);
return callback(_err);
}
if (insider.count == 0) {
return cb(null);
}
recommendation.insiderInfo = insider;
results.push(recommendation);
return cb(null);
});
}, (err) => { // Once all items are handled, this is called
if (err) {
console.log(err);
return callback(err);
}
const response = { // We prepare our response
data: {
results: results, // Results may be in a different order than in the initial `recommendations` array
count: results.count
},
statusCode: 200
};
callback(null, response); // We call our main callback only once
});
});
};
Initial (partly incorrect) answer, for reference.
You are pushing the result of your mapping into the object that you are currently mapping and callback is called more than once here. That's a pretty good amount of unexpected behavior material.
Try the following:
let results = [];
recommendations.forEach((recommendation) => {
Insider.query({uuid: recommendation.insiderUUID}).exec((_err, insider) => {
if (insider.count == 0) {
return;
}
recommendation.insiderInfo = insider;
results.push(recommendation);
});
});
let response = {
data: {
results: results,
count: results.count
},
statusCode: 200
};
callback(null, response);

Ace editor autocomplete uploads extra-records

I added ui-ace editor to the my application with angular. Instead of requesting words every time, I get a json 1 time, during application initiation.
Example of json-file:
[
{
"Word": "Do {int} + {int}",
"Meta": "Implemented"
},
{
"Word": "Line3",
"Meta": "Not-implemented"
},
{
"Word": "Line2",
"Meta": "Not-implemented"
},
{
"Word": "Line4",
"Meta": "Not-implemented"
},
{
"Word": "444",
"Meta": "Not-implemented"
},
{
"Word": "E1",
"Meta": "Not-implemented"
},
{
"Word": "E2",
"Meta": "Not-implemented"
},
{
"Word": "E1Try",
"Meta": "Not-implemented"
},
{
"Word": "E3",
"Meta": "Not-implemented"
},
{
"Word": "E4444",
"Meta": "Not-implemented"
}
]
The issue is that some of words are listed in autocomplete more than ones, take a look on a screenshot: http://take.ms/N8BFZ .
Here's how I load ace-editor, where ctrl.listStepLines is an object which contains json-response from API:
$scope.aceLoaded = function(_editor){
// Editor part
var _session = _editor.getSession();
var _renderer = _editor.renderer;
_editor.$blockScrolling = Infinity;
_editor.setOptions({
minLines: 10,
maxLines: 40,
wrap: true,
firstLineNumber: 1,
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true
})
var langTools = ace.require("ace/ext/language_tools");
var rhymeCompleter = {
getCompletions: function (editor, session, pos, prefix, callback) {
if (prefix.length === 0) { callback(null, []); return }
callback(null, ctrl.listStepLines.map(function (ea) {
return { name: ea.Word, value: ea.Word, meta: ea.Meta }
}));
}
}
langTools.addCompleter(rhymeCompleter);
};
The issue was that angularjs loaded my function a lot of times and ace editor had 14 similar completers. I refactored my code and create a separate function for completer adding which is called only one time.
ctrl.addAutoCompleter();
function init() {
ctrl.addAutoCompleter = function () {
var langTools = ace.require("ace/ext/language_tools");
var stepLineCompleter = {
getCompletions: function (_editor, session, pos, prefix, callback) {
if (prefix.length === 0) { callback(null, []); return }
callback(null, ctrl.listStepLines.map(function (ea) {
return { name: ea.Word, value: ea.Word, meta: ea.Meta }
}));
}
}
langTools.addCompleter(stepLineCompleter);
}
};

Resources