I am currently developing a voice app with Google Actions. Basically, users have to ask for information in the following data stream and the voice app should reply based on information in that data stream. Currently, the app is able to match users' requests and data from the stream based on the following code:
// From here, there are all the required libraries to be loaded
const { conversation } = require('@assistant/conversation'); // This the app coversation
const functions = require('firebase-functions'); //These are the firebase functions
require('firebase-functions/lib/logger/compat'); // console.log compact
const axios = require('axios'); // This is axios to retrieve the data stream
// To here, there all the required libraries to be loaded
const app = conversation({debug: true}); // This instantiate the conversation
/* This function retrieve the data from the file stream */
async function getItem() {
const res = await axios.get('https://sheetdb.io/api/v1/n3ol4hwmfsmqd');
return res.data; // To use in your Action's response
}
/* This is the fuction to match user's responses and data stream*/
app.handle('getItem', async conv => { //getItem is the weekhook name used in Google Actions, conv is the conversation
const data = await getItem(); // Here the data stream is retrieved and send to the data variable
// console.log(data);
const itemParam = conv.intent.params.Item.resolved; // This is the user's response, in other words, what item the user's want to know from the data.
const itemIDParam = conv.intent.params.Item_ID.resolved.replace(/\s/g, ''); //This is the user's response for item ID
const itemFromUser = itemParam + " " + itemIDParam;
console.log(itemParam);
console.log(itemIDParam);
console.log(itemFromUser);
// conv.add(`This test to see if we are accessing the webhook for ${itemParam}`); // This is to know if I was getting the correct item from the user. Currently this is working
// console.log(data);
data.map(item => { //Then, I am trying to map the data stream to recognize the data headers and identify items
// console.log(data);
// console.log(item);
if (item.Name.toLowerCase() === itemFromUser.toLowerCase()){
console.log(item);
conv.add(`These are the details for ${itemFromUser}. It is located in zone ${item.Zone}, at level ${item.Level}.`);
// console.log(conv);
// console.log(data);
}
else {
conv.add(`I am sorry. I could not find any information about that object. Please try with another construction object.`);
}
});
});
exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app);
The data.map function has an if else statement to manage when data is matched and when is not.
data.map(item => { //Then, I am trying to map the data stream to recognize the data headers and identify items
// console.log(data);
// console.log(item);
if (item.Name.toLowerCase() === itemFromUser.toLowerCase()){
console.log(item);
conv.add(`These are the details for ${itemFromUser}. It is located in zone ${item.Zone}, at level ${item.Level}.`);
// console.log(conv);
// console.log(data);
}
else {
conv.add(`I am sorry. I could not find any information about that object. Please try with another construction object.`);
}
});
When the condition is true, the if works when there is no else statement. But for some reason, when adding the else statement and data is not matched to any user, it makes the app behave weirdly, showing the following error in the console:
Error: Error adding simple response: Two simple responses already defined
I get this error either if the data is matched or not.
What I am doing to test is commenting the else statement. This allows me to get the matches for data, but the app cuts in place when data is not matched. But ideally, I would like the app to tell the user that the query did not find any match.
Thanks in advance, and I hope anybody can give me some hints.
The error you are seeing is:
Error: Error adding simple response: Two simple responses already defined
Your action's response can only include two simple responses. Each response is rendered as a separate text bubble on a phone, for instance.
So it seems like the item.Name === itemParam
may run once, but with the else
condition you end up creating too many. You may want to re-evaluate how you run your conditional.
But there are two quick ways to fix this as far as I see it:
In this approach you can create a string variable before the loop and instead of adding to conv
right away you just keep appending to the string until you're done. Then you add the entire thing.
The downside to this approach is that you may end up with a massive string regardless and the platform will truncate the string at a max of 640 characters.
app.handle('getItem', async conv => {
const data = await getItem();
const itemParam = conv.intent.params.Item.resolved;
let res = '';
data.map(item => {
if (item.Name === itemParam);
res += `These are the datails for ${itemParam}. It is located in zone ${item.Zone}, at level ${item.Level}`;
});
if (res !== '') {
conv.add(res);
} else {
conv.add('No items were found.')
}
});
You can also write your loop to not use the functional programming methods but instead identify the first instance that the condition is true and then exit the handler early. This would move you to a for-of
loop and then adding a return
which will conclude execution.
The downside is that your handler is then done and you won't be able to do any other logic. Alternatively, you could stick this subroutine into its own function to call. Then you capture that response within the handler yourself.
function getItemDetails(data) {
for (const item of data) {
if (item.Name === itemParam) {
return item
}
}
return undefined
}
app.handle('getItem', async conv => {
const data = await getItem();
const itemParam = conv.intent.params.Item.resolved;
const item = getItemDetails(data)
if (item !== undefined) {
conv.add(`These are the datails for ${itemParam}. It is located in zone ${item.Zone}, at level ${item.Level}`);
} else {
conv.add('No items were found.')
}
});