question

HazyJay avatar image
HazyJay asked

Newbie: I can't get my second intent functioning, but no errors are showing

Hi All

On Monday I attended an Alexa skills workshop in Cambridge to start learning how to write skills. I'm really interested in it and I'm relishing getting to learn a bit of code at the same time. However, my lack of experience with Alexa, and my lack of code knowledge (I've dabbled, but never learned anything in depth - I am researching node.js though to get familiar with it) is causing me to stumble.

In class we were set to making our own skills, and as I want to do a particular thing - act as an info point for a small festival - Andrea suggested I use the minecraft helper example to learn from. This has worked brilliantly for the single intent 'LineUpIntent' wherein I have put a small selection of the acts and a short description of them and for the limited range of utterances and bands the intent works very well.
BTW, for the purposes of saving time in class I left the values and responses file named as recipes.js and just changed the info provided within to match a handful of the bands.


With all this working I decided rather than have a really long file in recipes.js (as this will eventually contain a lot of bands, and area info) I'd prefer to have a location intent, and an audio intent separately. I created the second one 'locationfinderIntent', made a slot 'location', set values of address, directions, postcode, and location.
I created location.js changed some obvious stuff, and then went to index.js and copied the relevant band list/line up code and changed obvious parts of names. This took a while as I kept getting errors so I had to really think logically about aspects that need changing in the new handler etc. and make those changes.


Now, no errors show in the code. It compiles and deploys ok (earlier, with errors, it wouldn't save), but when I try to use the limited utterances I wrote to invoke the locationfinderIntent Alexa both on my echo dot 3 and in the test console revert to the default not found response in the handler (same response as the line up handler as I've not changed error messages yet) -'Sorry, I can't understand the command. Please say again.'.


To my untrained eye it all seems ok, but the second intent doesn't work because I've clearly missed something simple, or made a common error.

As I say, I am not scared of code, but I am unfamiliar with both Alexa and js, and in these first steps I need a little help. I've gotten as far as I can myself trying to debug so I look forward to being told what a simple/stupid error I have made and how to avoid it in the future :)


BTW, I'm Jay, and I'm pleased to be here. I have a lot of ideas for future skills, and this particular skill I later want to add audio clips to etc. I'll be sticking around for quite a while!


I'm not sure which bits of code you'll want so please ask and I shall deliver. I have the files index.js, package.json, recipes.js, location.js, and util.js

alexa skills kitalexahelpintents
1 comment
10 |5000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

HazyJay avatar image HazyJay commented ·

Any help would be appreciated

0 Likes 0 ·
HazyJay avatar image
HazyJay answered
/* eslint-disable func-names */
/* eslint-disable no-console */

const Alexa = require('ask-sdk-core');
const recipes = require('./recipes');
const location = require('./location');
const i18n = require('i18next');
const sprintf = require('i18next-sprintf-postprocessor');

/* INTENT HANDLERS */
const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
    },
    handle(handlerInput) {
        const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
        const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();

        const item = requestAttributes.t(getRandomItem(Object.keys(recipes.RECIPE_EN_GB)));

        const speakOutput = requestAttributes.t('WELCOME_MESSAGE', requestAttributes.t('SKILL_NAME'), item);
        const repromptOutput = requestAttributes.t('WELCOME_REPROMPT');

        handlerInput.attributesManager.setSessionAttributes(sessionAttributes);

        return handlerInput.responseBuilder
        .speak(speakOutput)
        .reprompt(repromptOutput)
        .getResponse();
    },
};

const LineupIntent = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
        && handlerInput.requestEnvelope.request.intent.name === 'LineupIntent';
    },
    handle(handlerInput) {
        const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
        const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();

        const itemSlot = handlerInput.requestEnvelope.request.intent.slots.Item;
        let itemName;
        if (itemSlot && itemSlot.value) {
            itemName = itemSlot.value.toLowerCase();
        }

        const cardTitle = requestAttributes.t('DISPLAY_CARD_TITLE', requestAttributes.t('SKILL_NAME'), itemName);
        const myRecipes = requestAttributes.t('RECIPES');
        const recipe = myRecipes[itemName];
        let speakOutput = '';

        if (recipe) {
            sessionAttributes.speakOutput = recipe;
            // uncomment the _2_ reprompt lines if you want to repeat the info
            // and prompt for a subsequent action
            sessionAttributes.repromptSpeech = requestAttributes.t('RECIPE_REPEAT_MESSAGE');
            handlerInput.attributesManager.setSessionAttributes(sessionAttributes);

            return handlerInput.responseBuilder
            .speak(sessionAttributes.speakOutput)
            .reprompt(sessionAttributes.repromptSpeech)
            .withSimpleCard(cardTitle, recipe)
            .getResponse();
        }
        const repromptSpeech = requestAttributes.t('RECIPE_NOT_FOUND_REPROMPT');
        if (itemName) {
            speakOutput += requestAttributes.t('RECIPE_NOT_FOUND_WITH_ITEM_NAME', itemName);
        } else {
            speakOutput += requestAttributes.t('RECIPE_NOT_FOUND_WITHOUT_ITEM_NAME');
        }
        speakOutput += repromptSpeech;

        // save outputs to attributes, so we can use it to repeat
        sessionAttributes.speakOutput = speakOutput;
        sessionAttributes.repromptSpeech = repromptSpeech;

        handlerInput.attributesManager.setSessionAttributes(sessionAttributes);

        return handlerInput.responseBuilder
        .speak(sessionAttributes.speakOutput)
        .reprompt(sessionAttributes.repromptSpeech)
        .getResponse();
    },
};

const locationfinderIntent = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
        && handlerInput.requestEnvelope.request.intent.name === 'locationfinder';
    },
    handle(handlerInput) {
        const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
        const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();

        const itemSlot = handlerInput.requestEnvelope.request.intent.slots.Item;
        let itemName;
        if (itemSlot && itemSlot.value) {
            itemName = itemSlot.value.toLowerCase();
        }

        const cardTitle = requestAttributes.t('DISPLAY_CARD_TITLE', requestAttributes.t('SKILL_NAME'), itemName);
        const myRecipes = requestAttributes.t('LOCATION');
        const location = myRecipes[itemName];
        let speakOutput = '';

        if (location) {
            sessionAttributes.speakOutput = location;
            // uncomment the _2_ reprompt lines if you want to repeat the info
            // and prompt for a subsequent action
            sessionAttributes.repromptSpeech = requestAttributes.t('RECIPE_REPEAT_MESSAGE');
            handlerInput.attributesManager.setSessionAttributes(sessionAttributes);

            return handlerInput.responseBuilder
            .speak(sessionAttributes.speakOutput)
            .reprompt(sessionAttributes.repromptSpeech)
            .withSimpleCard(cardTitle, location)
            .getResponse();
        }
        const repromptSpeech = requestAttributes.t('RECIPE_NOT_FOUND_REPROMPT');
        if (itemName) {
            speakOutput += requestAttributes.t('RECIPE_NOT_FOUND_WITH_ITEM_NAME', itemName);
        } else {
            speakOutput += requestAttributes.t('RECIPE_NOT_FOUND_WITHOUT_ITEM_NAME');
        }
        speakOutput += repromptSpeech;

        // save outputs to attributes, so we can use it to repeat
        sessionAttributes.speakOutput = speakOutput;
        sessionAttributes.repromptSpeech = repromptSpeech;

        handlerInput.attributesManager.setSessionAttributes(sessionAttributes);

        return handlerInput.responseBuilder
        .speak(sessionAttributes.speakOutput)
        .reprompt(sessionAttributes.repromptSpeech)
        .getResponse();
    },
};

const HelpHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
        && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent';
    },
    handle(handlerInput) {
        const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
        const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();

        const item = requestAttributes.t(getRandomItem(Object.keys(recipes.RECIPE_EN_US)));

        sessionAttributes.speakOutput = requestAttributes.t('HELP_MESSAGE', item);
        sessionAttributes.repromptSpeech = requestAttributes.t('HELP_REPROMPT', item);

        return handlerInput.responseBuilder
        .speak(sessionAttributes.speakOutput)
        .reprompt(sessionAttributes.repromptSpeech)
        .getResponse();
    },
};

const RepeatHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
        && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.RepeatIntent';
    },
    handle(handlerInput) {
        const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();

        return handlerInput.responseBuilder
        .speak(sessionAttributes.speakOutput)
        .reprompt(sessionAttributes.repromptSpeech)
        .getResponse();
    },
};

const ExitHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
        && (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent'
        || handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent');
    },
    handle(handlerInput) {
        const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
        const speakOutput = requestAttributes.t('STOP_MESSAGE', requestAttributes.t('SKILL_NAME'));

        return handlerInput.responseBuilder
        .speak(speakOutput)
        .getResponse();
    },
};

const SessionEndedRequestHandler = {
    canHandle(handlerInput) {
        console.log('Inside SessionEndedRequestHandler');
        return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
    },
    handle(handlerInput) {
        console.log(`Session ended with reason: ${JSON.stringify(handlerInput.requestEnvelope)}`);
        return handlerInput.responseBuilder.getResponse();
    },
};

const ErrorHandler = {
    canHandle() {
        return true;
    },
    handle(handlerInput, error) {
        console.log(`Error handled: ${error.message}`);

        return handlerInput.responseBuilder
        .speak('Sorry, I can\'t understand the command. Please say again.')
        .reprompt('Sorry, I can\'t understand the command. Please say again.')
        .getResponse();
    },
};

/* Helper Functions */

// Finding the locale of the user
const LocalizationInterceptor = {
    process(handlerInput) {
        const localizationClient = i18n.use(sprintf).init({
            lng: handlerInput.requestEnvelope.request.locale,
            overloadTranslationOptionHandler: sprintf.overloadTranslationOptionHandler,
            resources: languageStrings,
            returnObjects: true,
        });

        const attributes = handlerInput.attributesManager.getRequestAttributes();
        attributes.t = function (...args) {
            return localizationClient.t(...args);
        };
    },
};

// getRandomItem
function getRandomItem(arrayOfItems) {
    // the argument is an array [] of words or phrases
    let i = 0;
    i = Math.floor(Math.random() * arrayOfItems.length);
    return (arrayOfItems[i]);
}

/* LAMBDA SETUP */
const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
    .addRequestHandlers(
        LaunchRequestHandler,
        LineupIntent,
        HelpHandler,
        locationfinderIntent,
        RepeatHandler,
        ExitHandler,
        SessionEndedRequestHandler,
    )
    .addRequestInterceptors(LocalizationInterceptor)
    .addErrorHandlers(ErrorHandler)
    .lambda();

// langauge strings for localization
// TODO: The items below this comment need your attention

const languageStrings = {
    'en': {
        translation: {
            RECIPES: recipes.RECIPE_EN_GB,
            LOCATION: location.LOCATION_EN_GB,
            SKILL_NAME: 'Kozfest Festival',
            WELCOME_MESSAGE: 'Welcome to <phoneme alphabet="ipa" ph="koʒfest">Kozfest</phoneme> Festival asmall psychedelic and space rock festival held in Devon on the last weekend of July. For 2019 we are on a new site nera woollacombe, ask me for Location, or, you can ask a question like, who\'s playing? ... Now, what can I help you with?',
            WELCOME_REPROMPT: 'For instructions on what you can say, please say help me.',
            DISPLAY_CARD_TITLE: '%s - Line up for %s.',
            HELP_MESSAGE: 'You can ask questions such as, who\'s the recipe for a %splaying, or, you can ask about a band that is playing, or, you can say exit...Now, what can I help you with?',
            HELP_REPROMPT: 'You can say things like, tell me about skeleton gong, tell me the line up, or, you can say exit...Now, what can I help you with?',
            STOP_MESSAGE: 'Goodbye! Thanks for using <phoneme alphabet="ipa" ph="koʒfest">Kozfest</phoneme> Festival',
            RECIPE_REPEAT_MESSAGE: 'Try saying repeat.',
            RECIPE_NOT_FOUND_WITH_ITEM_NAME: 'I\'m sorry, I currently do not know about %s. ',
            RECIPE_NOT_FOUND_WITHOUT_ITEM_NAME: 'I\'m sorry, I currently do not know that band. ',
            RECIPE_NOT_FOUND_REPROMPT: 'What else can I help with?',
        },
    },
    'en-GB': {
        translation: {
            RECIPES: recipes.RECIPE_EN_GB,
            LOCATION: location.LOCATION_EN_GB,
            SKILL_NAME: 'Kozfest Festival',
        },
    }
};
10 |5000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Amazon_Bernardo Bezerra avatar image
Amazon_Bernardo Bezerra answered

Hello Jay and welcome to the Alexa Developer Forum community.

It is not clear from your code if you have also added the intents in the interaction model. This would be the en-GB.json file under the /models/ folder.

This page contains information about how to start building a skill. It contains important information that you should read carefully.

You should also take a look at the Alexa official GitHub repository here. It has several code samples that would be very helpful for your experience.

If after this you are still facing some difficulties to develop and deploy your skill correctly, feel free to reach out. And make sure to share your skill ID as well.

Regards,
Barry

6 comments
10 |5000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

HazyJay avatar image HazyJay commented ·

I keep trying to paste the code but comments will not submit.

0 Likes 0 ·
HazyJay avatar image HazyJay HazyJay commented ·

@barry@amazon What file types are allowed to be uploaded as I can't get this code pasted, and the site whilst telling me .txt or .pdf is not acceptable does NOT tell me what file types are acceptable!

0 Likes 0 ·
Amazon_Bernardo Bezerra avatar image Amazon_Bernardo Bezerra ♦♦ HazyJay commented ·

Hello @HazyJay,

There is a 2000 character limit for comments.

Did you take a look at the pages I mentioned in my previous comment? Sharing your entire code is not helpful, as we do not have the resources to sift through it looking for errors. Have you checked the logs from Cloudwatch? Please pin point where is this issue happening so we can provide you better support.

Regards,
Barry

0 Likes 0 ·
Show more comments
newuser-44742ea7-9a99-466b-b8a8-cc6e96832950 avatar image
newuser-44742ea7-9a99-466b-b8a8-cc6e96832950 answered

could you please tell me what is .t() doing


1 comment
10 |5000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Amazon_Bernardo Bezerra avatar image Amazon_Bernardo Bezerra ♦♦ commented ·

Hello and thank you for your message.

The .t() function is returning the String that is mapped to the WELCOME_MESSAGE, for example.

You can find more information about localising your code on this blog post.

Regards,
Barry

0 Likes 0 ·