Documentation

Powered by Algolia

How-To

How to get the right input from the user

intent("I want a coffee", async p => {
    p.play("What coffee do you prefer?");
    const coffee = await p.then(whatCoffee);
    p.play(`Your ${coffee} coffee will be ready in a moment`);
});

const whatCoffee = context(() => {
    follow("$(COFFEE Black|Americano|Latte|Cappuccino|Expresso|Macchiato)", p => p.resolve(p.COFFEE));
})

How to send data to a client application

You can send a JSON object to your application

intent("Change color to $(C red|green|blue)", p => {
    p.play({command: "setColor", color: p.C});
});

How to get arbitrary user inputs

Use a RegEx expression (asterisk * follows the slot value) inside intent or follow to get user input by template.

intent("I want to give a feedback", async p => {
    p.play("Thank you! What do you think about our product?");
    const feedback = await p.then(userInput);
    p.play({command: "feedback", value: feedback});
    p.play(`Your feedback is: ${feedback}`);
});

const userInput = context(() => {
    follow("$(I* .+)", p => p.resolve(p.I));
})

How to handle unexpected voice commands from the user

Use fallback function in the user input to get the right response from the user

intent("Check my math", async p => {
    const a = Math.floor(Math.random()*10)
    const b = Math.floor(Math.random()*10)
    p.play(`How much ${a} plus ${b}?`)
    const sum = await p.then(getNumber)
    p.play(`Your answer ${sum} is ${a+b==sum? 'correct': 'incorrect'}`)
});

const getNumber = context(() => {
    follow("$(NUMBER)", p => p.resolve(p.NUMBER.number))

    fallback("You have to say a number")
})

How to iterate over a list of objects (back and forward)

let fruits = ["apple", "orange", "lemon", "mango", "banana", "avocado", "kiwi"]

intent("(I want to|) (select|choose) fruits", async p => {
    p.play("Iterate over the list of fruits and select your favorite one. To select fruit say 'select', say 'forward' or 'back' to traverse over the list");
    p.play("Say 'finish' to complete your choice")
    p.userData.favorite = []
    p.play(`We have ${fruits.length} fruit in our list. First is ${fruits[0]}`)
    await p.then(selectFruits, {state: { index : 0}})
    if (p.userData.favorite.length > 0) {
        p.play(`Your favorite fruits are: ${p.userData.favorite.join(", ")}`)
    } else {
        p.play(`You have not chosen any fruit`)
    }
});

const selectFruits = context(() => {
    follow("(select|choice)",  p => {
        let selected = false
        if(!p.userData.favorite.includes(fruits[p.state.index])) {
            p.userData.favorite.push(fruits[p.state.index])
        }
        p.play(fruits[p.state.index] + " is selected")
        p.play(p.userData.favorite)
    })

    follow("(repeat|say again)", p => playFruit(p, p.state.index))

    follow("(forward|next)", p => {
        if(p.state.index < fruits.length-1) {
            p.state.index++;
            playFruit(p, p.state.index)
        }
        else {
            p.play("It's the last fruit in the list")
        }
    })

    follow("(back|previous)", p => {
        if(p.state.index > 0) {
            p.state.index--;
            playFruit(p, p.state.index)
        }
        else {
            p.play("It's the first fruit in the list")
        }
    })

    follow("(finish|stop|it's all)", p => p.resolve())

    fallback("Say select to choose fruit, say forward or back to navigate through the list or say 'finish' to return")
})

const playFruit = (p, index) => {
    p.play(`Fruit number ${p.state.index+1} is ${fruits[p.state.index]}`)
}

How to pass state object to a context

intent("I have friends", async p => {
    p.play("How many friends do you have?");
    let number = await p.then(numberContext)
    p.play("What is name of your friend number one?");
    let index = 0, friends = [];
    let list = await p.then(friendsContext, {state: {number, index, friends}})
    p.play({command: "friends", list})
})

const friendsContext = context(() => {
    follow("$(NAME)", p => {
        let {number, friends} = p.state;
        friends[p.state.index++] = p.NAME;
        if (p.state.index == number) {
            p.resolve(friends)
        }else {
            p.play(`What is name of your friend number ${p.state.index+1}?`);
        }
    })
})

const numberContext = context(() => {
    follow("$(NUMBER)", p => {
        if (p.NUMBER.number > 0) {
            p.resolve(p.NUMBER.number)
        } else {
            p.play("Number of friends must be one or more")
        }
    })
})

How to use intents with fuzzy parameters

const array = ["small", "medium", "large"];
intent(`$(ORD~ one~1|first~1|two~2|second~2|middle~2|three~3|third~3|last~3)`, p => {
    // one is value, 1 is label
    let index = parseInt(p.ORD.label);
    p.play(`You've selected ${array[index-1]}`);
})

How to use several slots of the same type in one intent

You can reference to the same slot name in one intent. In this case all slot values will be collected in an array with a special name which come from the slot name plus 's'. For example, if you have several slots named ITEM, you can access object ITEMs with an array of size 2 and you can reference first item by ITEMs[0].

const items = ["burger", "cola", "taco"].join('|');
intent(`I want $(NUMBER) $(ITEM ${items}) and $(NUMBER) $(ITEM ${items})`, p => {
    let order = _.reduce(_.range(_.size(p.NUMBERs)), (acc, i) => {acc[p.ITEMs[i]] = p.NUMBERs[i].number; return acc}, {});
    p.play({command: "createOrder", order });
})

How to get data from external web services

Here is an example how to get data from a weather web service

const serviceURL = "http://api.openweathermap.org/data/2.5/weather?appid=4acdb6432d18884ebc890c13a9c3cc85"
intent("what is the weather in $(LOC)", p => {
    let location = p.LOC;
    api.request(serviceURL + `&q=${location}&units=imperial`, (error, res, body) => {
        if (error || res && res.statusCode !== 200) {
            p.play(`Could not get weather information`)
        } else {
            let data = JSON.parse(body);
            p.play(`The temperature in ${location} is ${Math.floor(data.main.temp)} degrees in Fahrenheit`);
        }
    });
})

How to use nouns regardless of single or plural form

  intent("I want $(NUMBER) ($P cake_)", p => {
      p.play(`Here is your ${p.NUMBER} ${p.P}`);
  })

Note. This syntax is applicable only to nouns whose plural forms are no exception.

How to use predefined slots

intent(`I meet $(NAME) from $(ORG) $(DATE) (in|at|near|) $(LOC) (at|) $(TIME)`, p => {
    p.play(`OK. I will meet ${p.NAME} from ${p.ORG} ${p.DATE} at ${p.TIME} in ${p.LOC}`)
})

How to allow users to say synonyms for the same thing

It is often necessary to give the user the opportunity to say the same thing in different ways. For example, a product in the price list can be pronounced in several ways, but we still must recognize which product the user is talking about. For these purposes, you can compile a synonym dictionary for each element and use fuzzy parameters in the intent/follow. Fuzzy parameters make it possible to associate a key with the value that was recognized as the user input.

In the example below, we construct a list of slot values out of a JSON object. aliases become slot values and id becomes the label.

const products = [
    {id: "cola",  aliases: ["cola","coca-cola", "soda", "coca"]},
    {id: "juice", aliases: ["juice", "fresh", "orange juice", "orange fresh"]}
]
// We have to construct following fuzzy parameters with all value~label items concatenated with |
// cola~cola|cola-cola~cola|soda~cola|coca~cola|juice~juice|fresh~juice|orange juice~juice|orange fresh~juice

const PROD_INTENTS = _.flatten(products.map(p => p.aliases.map(a => `${a}~${p.id}`))).join('|')

intent(`(I want|give me) $(ITEM ${PROD_INTENTS})`, p => {
    p.play(`We added ${p.ITEM.label} to your order`)
})

Here is another example of how to construct a list of slot values out of the JSON object passed with visualState. In this example, we use dynamic entities to get the user feedback. The list of slot values is constructed out of the values passed in the synonyms key, and the values in the category key are used as the label.

//Passed visualState
{
    "answers": [
        {
            "category": "positive",
            "synonyms": [
                "yes",
                "of course",
                "definitely"
            ]
        },
        {
            "category": "negative",
            "synonyms": [
                "no",
                "in no case",
                "I don't think so"
            ]
        },
        {
            "category": "neutral",
            "synonyms": [
                "maybe",
                "I'll think about it",
                "I don't know"
            ]
        }
    ]
}

// Voice script
onVisualState((p, s) => {
    if (s.answers) {
        p.visual.answersEntity = s.answers.filter(ans => !ans.unique).map(ans => ans.synonyms.map(syn => syn + '~' + ans.category).join('|')).join('|');
    } else {
        p.visual.answersEntity = "";
    }
});

intent(`(My answer is|) $(ANS v:answersEntity)`, p => {
        switch(p.ANS.label){
            case "positive":
                p.play(`That's great!`);
                break;
			case "negative":
                p.play(`Sorry to hear that`);
                break;
            case "neutral":
                p.play(`Thanks for sharing`);
                break;
        }
    });

How voice commands can respond differently to the user based on visual context

You can set the visual state object on the client side and it will be available in p object when user input matches intent or follow.
Check our documentation to see how to send visual state in Аndroid and iOS application.

intent('What can I do here?', p => {
    switch (p.visual.screen) {
       case "main":
           p.play("Here is main screen. Choose a category.");
           break;
       case "checkout":
           p.play("You are in your cart. You can change or finish your order.");
           break;
       case "product":
           p.play(`It's product detail page for ${p.visual.product.name}`);
           break;    
       default:
           p.play("Say open menu to start");
   }
})

You can also filter intents matching based on visual state value.
For example, this intent will never match if cart is empty.

intent(visual(v => !_.isEmpty(v.order)), `(What is|) my order (details|)`, p => {
    p.play("You have ordered:");
    for (let product in p.visual.order) {
        p.play(p.visual.order[product] + " " + product);
    }
});