snapshot.val() returning null, while there is data - node.js

I am trying to retrieve information from Firebase Realtime Database, inside Dialogflow's inline editor using Google Assistant, However for some reason I'm getting back null, Google Assistant answers person1's Alex is null
Is my reference not targeting correctly? I'm using dialogflow and realtime database for the first time.
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
var admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.cert({
projectId: ' ',
clientEmail: ' ',
privateKey: ' '
}),
databaseURL: 'https://*****.firebaseio.com'
});
process.env.DEBUG = 'dialogflow:debug';
// enables lib debugging statements
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request,response)=>{
const agent = new WebhookClient({
request,
response
});
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
function welcome(agent) {
agent.add(`Welcome to my agent!`);
}
function fallback(agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
function personFacts(agent) {
const personId = agent.parameters["personId"];
const personMeasurement = agent.parameters["personMeasurement"];
var db = admin.database();
var ref = db.ref(`person/${personId}/${personMeasurement}`);
return ref.once("value").then(snapshot=>{
var result = snapshot.val();
agent.add(`${personId}'s ${personMeasurement} is ${result}`);
}).catch(err=>{
agent.add('uh oh, something went wrong.');
console.error(err);
});
}
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
intentMap.set('person', personFacts);
agent.handleRequest(intentMap);
});
Here is Realtime Database:
As suggested i changed numbers 0, 1, 2 into person1, person2, person3
for the reference to realtime database need little help :
function personFacts(agent) {
const personId = agent.parameters["personId"];
const personMeasurement = agent.parameters["personMeasurement"];
const pperson = person[personId];
const result = pperson[personMeasurement];
var db = admin.database();
var ref = db.ref(`person/${personId}/${personMeasurement}`);
return ref.once("value")
.then( snapshot => {
var result = snapshot.val();
agent.add(`${personId}'s ${personMeasurement} is ${result}`);
})
.catch( err => {
agent.add('uh oh, something went wrong.');
console.error( err );
});
}
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
intentMap.set('person', personFacts);
agent.handleRequest(intentMap);
});

It sounds like you're asking for information by asking for "person 1", but there is no key in the database with the value "person1". There is a key "1" under the "person" hierarchy, and perhaps this is what you want.
You have a few options, depending what you're actually trying to do.
You can either change your keys to reflect what you're asking for. So instead of using "0" or "1" or "2" for your keys, you can just use "person1", or "person2", or "person3". You don't need to use numbers in the Firebase database. You can use however you want to reference the data.
Another alternative is to keep the keys in the database as you have, and when the person says "person1", to somehow map that to the correct key that is in the database (ie - "0"). You can write code to do this - in this case, you want a substring of the personId parameter. So perhaps something like
let personKey = personId.substring(6);
and then use personKey in your reference path
var ref = db.ref( `person/${personKey}/${personMeasurement}` );
However, these are just examples - how you do it depends on how you want to structure and access your data.
The important part is to understand how the Firebase database lets you access the data. Data is stored in a hierarchy, and we can think of each node in that hierarchy having a path to it. In your example, you have one node at the top named "person". Under that node, are several nodes that are just numbers - a node named "0" is one example. Under the "0" node, you have several nodes, including one named "Alex".
You can reference this specific "Alex" node by specifying the full reference path to it. We separate the name of each node with a "/". So the full reference path to this particular "Alex" would be person/0/Alex. To get the value at this node, we can build a ref object and then use once("value") to get the value just once (as opposed to listening to it for changes).
Since the "0" and "Alex" portions of this are stored in variables, we need to build a string that contains these values. One simple way to do this is to use a template literal or "backtick string" in JavaScript. In this type of string, everything inside a ${} is evaluated. So ${personKey} means to insert into the string the value of personKey.
So the line
var ref = db.ref( `person/${personKey}/${personMeasurement}` );
means "create a reference to a node the database, starting at the 'person' node, then finding the node under this that has the value in personKey, then find the node under that with the value in personMeasurement".
You can, of course, replace personKey and personMeasurement with whatever variables contain the values you'd be looking for at that point in the node hierarchy. You can build this string in other ways and build the reference using other tools. The important point is to understand that the reference is to a point in the database and that you need to build this reference before you can fetch the reference from this point.

Related

Repeat sentence in Dialogflow for VUI without external libraries (multivocal/VoiceRepeater)

I am trying to make my VUI repeat its last sentence when prompted for this (e.g. when user says 'I'm sorry, I did not hear you). I have tried to do this using the libraries multivocal and VoiceRepeater but this is not working for me so I want to implement it according to this guide: https://developers.google.com/assistant/conversational/tips
I have already taken the following steps:
Created an intent called 'Repeat'.
Added training phrases for the intent
Enabled webhook call for this intent
In Node.js added the following code:
// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs
// for Dialogflow fulfillment library docs, samples, and to report issues
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
function repeat(agent) {
const REPEAT_PREFIX = [
'Sorry, Ik zei ',
'Laat me het herhalen: ',
'Wat ik zei is'
];
const reply = (agent, inputPrompt, noInputPrompts) => {
agent.data.lastPrompt = inputPrompt;
agent.data.lastNoInputPrompts = noInputPrompts;
agent.ask(inputPrompt, noInputPrompts);
};
// Intent handlers
const normalIntent = (agent) => {
reply(agent, 'Hey this is a question', 'Ik zie niks');
};
let repeatPrefix = promptFetch.getRepeatPrefix(); // randomly chooses from REPEAT_PREFIX
// Move SSML start tags over
if (agent.data.lastPrompt.startsWith(promptFetch.getSSMLPrefix())) {
agent.data.lastPrompt =
agent.data.lastPrompt.slice(promptFetch.getSSMLPrefix().length);
repeatPrefix = promptFetch.getSSMLPrefix() + repeatPrefix;
}
agent.add(repeatPrefix + agent.data.lastPrompt,
agent.data.lastNoInputPrompts);
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('Repeat', repeat);
agent.handleRequest(intentMap);
});
Unfortunately, this does not work for me, one error I get it that it says 'FetchPrompt is not defined', which I don't understand. I know the setup is okay because this code does return: 'this is a response from the webhook' if I prompt the VUI for repeating its sentence:
// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs
// for Dialogflow fulfillment library docs, samples, and to report issues
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
function repeat(agent) {
agent.add('this is a response from the webhook');
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('Repeat', repeat);
agent.handleRequest(intentMap);
});
As #Prisoner points out, your code is intended to work only with dialogflow-fulfillment but you based it on an actions-on-google example. That's the cause of the code errors.
Some things you want to do are only available for Actions on Google, for example, the object conv contains functionality that only works when executed within Google Assistant, and this is the core of the problem.
Even if you fix the code, the example you're following uses conv.data as a temporary storage for the last reply of the agent during the conversation; however conv.data functionality is not availabe outside Google Assistant platforms, and, at the moment, Dialogflow doesn't have a straightforward approach to get the agent last response.
If you don't want to integrate with Google Assistant, then you'd need to find a suitable workaround to store your agent last reply. Here's a question where this issue with the temporary storage was discussed, maybe you can use it as a reference.
For your use case, I think you could get away with it by using contexts to store the last replies.

Not able to add data to firestore using dialogflow fulfillment

I want to send data to my firestore using dialogFlow fulfillment but it is not working.
Here is my index.js:
// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs
// for Dialogflow fulfillment library docs, samples, and to report issues
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
var product,phoneNo,eMail;;
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
var flag=0;
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
function welcome(agent) {
if(flag==0){
agent.add(`Can you please tell me what is your product?`);
flag=1;
}
else if(flag==1){
var prod=request.body.queryResult.queryText;
product=prod;
flag=2;
agent.add(`Please provide me your Phone No or your E-Mail ID so that my team can contact you.`);
}
else if(flag==2||flag==3){
let missingSlots1 = [];
var queryRes=request.body.queryResult.queryText;
var [phone,email] = [agent.parameters[`phone`], agent.parameters[`mail`]];
if(queryRes.includes(`#`)&&queryRes.includes(`.`)){
email=queryRes;
eMail=queryRes;
agent.parameters[`mail`]=queryRes;
}
else if(queryRes.length>=10&&queryRes!=product){
console.log(`phone ke andar wala if `+queryRes);
phone=queryRes;
phoneNo=queryRes;
agent.parameters[`phone`]=phoneNo;
}
if(!phoneNo){missingSlots1.push(`Phone No`);}
if(!eMail){missingSlots1.push(`E-mail`);}
if(missingSlots1.length==2){
agent.add(`Please provide me your Phone No or your E-Mail ID so that my team can contact you.`);
}
else if(flag==2){
if(!eMail){
agent.add(`Would you please provide your E-Mail ID?`);
}
if(!phoneNo){
agent.add(`Would you please provide your Phone No?`);
}
flag=3;
}
else{
flag=4;
addLeads();
agent.add(`Okay.Now you are good to go!`);
}
}
}
function addLeads(){
var data={
'product':product,
'email':eMail,
'phoneNo':phoneNo
};
const dialogflowAgentRef = db.collection('botData').doc(eMail);
let setDoc = dialogflowAgentRef.set(data,{merge:true});
}
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
agent.handleRequest(intentMap);
});
I have removed other functions for simplicity.
Here is my package.json dependencies:
"dependencies": {
"actions-on-google": "^2.2.0",
"firebase-functions": "^2.0.2",
"dialogflow": "^0.6.0",
"dialogflow-fulfillment": "^0.5.0",
"#google-cloud/firestore": "^0.16.1",
"firebase-admin": "^6.0.0"
}
And here is my firestore permission:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
Main error that I can see in my logs is:
Warning, estimating Firebase Config based on GCLOUD_PROJECT. Initializing firebase-admin may fail
If I comment the let setDoc = dialogflowAgentRef.set(data,{merge:true}); line ,my program works fine but with this line, the program does not even enters this function and shows the intent response rather than my fulfillment response. How should I fix this?
The "error" you're showing is a warning and does not usually prevent things from working. It is saying that it is assuming the environment based on the Project it is running in. If you're accessing a data store in the same project - you shouldn't be encountering any problems with this.
You don't indicate what is happening when you try to call set(), but it sounds like this may not even be happening.
Given the state machine that is depending on the value of the global flag variable, that may not be surprising. Trying to track the conversation this way has two problems:
It looks like they're only getting invoked at the welcome Intent. You're not showing this Intent definition, but this may only be happening when the bot is first invoked and not afterwards.
Since this is a global variable, and not a value that is attached to the conversation, it could be changed if multiple users tried to use the bot at the same time or if the server it is running on is reset. If you're using Firebase Cloud Functions or the Dialogflow Built In editor, this could happen without you knowing it.
In particular, (2) may be causing it to never get to the state where set() is called.
There does not appear to be anything wrong with the call to set() itself, but you're not doing any error handling. And it looks like you're sending the "You're good to go" message before you know if the set() actually works. To address this, you may want to change addLeads() so it returns a Promise, and then make the call to it also work with a Promise. This might change addLeads() to something like
function addLeads(){
var data={
'product':product,
'email':eMail,
'phoneNo':phoneNo
};
const dialogflowAgentRef = db.collection('botData').doc(eMail);
return dialogflowAgentRef.set(data,{merge:true});
}
Then, when you call it, you need to both work with the Promise and return it (so Dialogflow will wait to send the reply until the set() completes.) Probably something like:
return addLeads()
.then( () => {
agent.add(`Okay. Now you are good to go!`);
})
.catch( err => {
console.error( "There was a problem", err );
agent.add( "There was a problem saving the data." );
});

DialogFlow with Telegram: How to receive an image and save it along with the conversation

I'm developing a chat bot for Telegram using DialogFlow, but I can't go through two topics, and I can't find the documentation for them.
The flow of the conversation, is the user answer some closed questions and send an image.
How do I get this image?
And to save her along with the other answers?
The answers need to be saved as a form/survey and not as a conversation history.
I have a similar setup in my chatbot. I store the answers in a Firebase database.
In order to interact with the Firestore Database you should implement a Fulfillment
You can see a guide on how to implement Firebase for DialogFlow here
Here you can see a sample of my code. In general lines after setting up the connection to the Firebase database you just want to map your intents to your functions using intentMap.set.
As you said you are using closed answers you can set intets to handle the responses and each "final" intent will trigger a different function that will write a different message to the db.
To write the response to the Firesbase database you just only need to implement admin.database().ref().push().set({}) with the information and the desired structure.
In my example I also store the conversation Id from the chat payload and the date.
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
//const DialogflowApp = require('actions-on-google').DialogflowApp;
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
admin.initializeApp({
credential : admin.credential.applicationDefault(),
databaseURL: 'ws://YOURDATABASE.firebaseio.com/'
});
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
var userId;
let conv = agent.conv();
const ROOTREF = admin.database().ref();
const actions = new Map();
let intentMap = new Map();
intentMap.set('Default Fallback Intent', fallback);
intentMap.set('NoTunel', handleWriteToDbNoTunnel(agent));
agent.handleRequest(intentMap);
function assignConv(agent){
userId = agent.parameters.UserId;
return admin.database().ref('Users/'+ userId).set({
Apellido:"XXXX",
Nombre:"XXXX",
chatId:333,
});}
function fallback(agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
var token = "YOUR TOKEN HERE";
var url = "https://api.telegram.org/bot"+ token;
function handleWriteToDbNoTunnel(agent){
const Dia = new Date();
if(matricula !== "")
return admin.database().ref('Limpieza/').push().set({
chatId: request.body.queryResult.outputContexts[3].parameters.telegram_chat_id+'"',
Field1: answer1,
Field2: answer2,
day: day.getTime()
});
}
});
Also if you want to store images with the user responses you can implement the getfile method from the telegram api and store the image code or the image itself
I am adding this answer to slightly improve on Chris32's answer.
There is a better way to get the value of the Telegram Chat ID as I am using it in a personal project.
I will go end to end to explain my approach.
I have mapped some files to some specific intents. In my intent-mapper.js file, I have mapped Default Welcome Intent to welcome.js file as prescribed in the documentation for the Dialogflow Fufillment library for NodeJS (Please note that the library is deprecated and not being updated, personally I am using a fork of the repo that I have worked on personally).
const intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
.
.
Then, in welcome.js,
const globalParameters = {
'name': 'global-parameters',
'lifespan': 9999,
'parameters': {}
};
globalParameters.parameters.telegramChatId = agent.originalRequest?.payload?.data?.chat?.id || -1;
.
.
agent.setContext(globalParameters);
The telegramChatId variable in the global parameters context will save the value for the chat ID which can be passed to a helper function to send a message. In order to to retrieve the value from the global parameters, the code snippet is this.
const globalParameters = agent.getContext('global-parameters');
const telegramChatId = globalParameters.parameters.telegramChatId;
Then the Telegram message helper function is largely the same as in Chris32's answer. The message can be any string and chatId can be passed as an argument to the following helper function.
const TelegramBot = require('node-telegram-bot-api');
const { telegramBotToken } = process.env.TELEGRAM_BOT_TOKEN;
const bot = new TelegramBot(telegramBotToken, { polling: false });
const sendTelegramTextMessage = (message, chatId) => {
try {
bot.sendMessage(chatId, message, {parse_mode: 'html'});
} catch (err) {
console.log('Something went wrong when trying to send a Telegram notification', err);//remove console.log()
}
};
The reason I have put this all in a context since in my use case I am sending push notifications via Telegram once the user asks for it (this happens later in the conversation flow), so I have implemented it this way. The main point to note is that the agent object already has the detectIntentRequest variable saved inside it which in turn has the value we need as a part of its payload. Here's a snippet of the same.
Please note I have removed many lines from my code for brevity, but in a nutshell, the chat ID can be accessed from
agent.originalRequest?.payload?.data?.chat?.id
And the value for the telegram bot token is a secret value which can be saved in an environment variable or Secrets Manager. Please note my answer explains a better way to retrieve the chat ID without needing to refer directly to the request object since Dialogflow Fulfillment library already caches the value in the body for us. The other stuff for receiving and sending images is explained in the main answer.

get json data from realtime database to Dialogflow inline editor (google assistant)

Beginner here on programming, I'm working on my free time on a project which is related to Google Assistant, for the first time i'm using the Firebase Realtime Database and don't know how to get the data from there, the code below is in Dialogflow's inline editor, where is category1 2 and 3 inside those are students with credits. I have made some changes and put these three (categories1 2 and 3) in the database picture below, i want to remove these three categories from code and replace it with the ones in realtime database.
Because this is my first time using that database, I don't know how to get that data from there with nodejs.
function categoryac(agent) {
const categories = {
category1: {"Alex" : 25, "Jennifer" : 45, "Justin" : 35, "Peter" : 89},
category2: {"Alex" : 95, "Jennifer" : 75, "Justin" : 85, "Peter" : 59},
category3: {"Alex" : 67, "Jennifer" : 55, "Justin" : 45, "Peter" : 15},
};
const categoried = agent.parameters["categoried"];
const categoryAmount = agent.parameters["categoryAmount"];
const category = category[categoried];
const aggregate = category[categoryAmount];
agent.add(`${categoried}'s ${categoryAmount} is ${aggregate}`);
}
let intentMap = new Map();
intentMap.set('category', categoryac);
agent.handleRequest(intentMap);
});
UPDATE
I used the code below, like this:
function categoryac( agent ){
const categoried = agent.parameters["categoried"];
const categoryAmount = agent.parameters["categoryAmount"];
var admin = require( 'firebase-admin' );
admin.initializeApp( {
credential: admin.credential.cert( {
projectId: ' ',
clientEmail: ' ',
privateKey: ' '
} ),
dblink: 'www.*****.*****.com'
} );
var thing = admin.database();
var relating= thing.ref( `category/${categoried}/${categoryAmount}` );
return relating.once( "value" ).then( snapshot =>{
var aggregate = snapshot.value();
agent.add( `${categoried}'s ${categoryAmount} is ${aggregate}` );
} )
.catch( fail =>{
agent.add( 'uh oh, something went wrong.' );
console.error( fail );
} );
}
let intentMap = new Map();
intentMap.set( 'category', categoryac );
agent.handleRequest( intentMap );
} );
got error message : 'MalformedResponse
Failed to parse Dialogflow response into AppResponse because of empty speech response.' error from log :
{
insertId: "v*****2"
labels: {
channel: "preview"
querystream: "GOOGLE_USER"
source: "JSON_RESPONSE_VALIDATION"
}
logName: "projects/****/logs/actions.googleapis.com%2Factions"
receiveTimestamp: "2019-01-07T14:45:29.274840871Z"
resource: {
labels: {
action_id: "actions.intent.TEXT"
project_id: "******"
version_id: ""
}
type: "assistant_action"
}
severity: "ERROR"
textPayload: "MalformedResponse: Failed to parse Dialogflow response into AppResponse because of empty speech response"
timestamp: "2019-01-07T14:45:29.266062732Z"
trace: "projects/383182941858/traces/ABwppHFK_PehMj1XEs_Arng9VL7_zShy-EWvoziK0Ro6v74TaduNG1cJaRMnGAZMoLZhtILdG2hEBkDvJQ"
}
Here is the error in the logs:
Error: The default Firebase app already exists. This means you called initializeApp() more than once without providing an app name as the second argument. In most cases you only need to call initializeApp() once. But if you do want to initialize multiple apps, pass a second argument to initializeApp() to give each app a unique name.
at FirebaseAppError.Error (native)
at FirebaseAppError.FirebaseError [as constructor] (/user_code/node_modules/firebase-admin/lib/utils/error.js:39:28)
at FirebaseAppError.PrefixedFirebaseError [as constructor] (/user_code/node_modules/firebase-admin/lib/utils/error.js:85:28)
at new FirebaseAppError (/user_code/node_modules/firebase-admin/lib/utils/error.js:119:28)
at FirebaseNamespaceInternals.initializeApp (/user_code/node_modules/firebase-admin/lib/firebase-namespace.js:68:23)
at FirebaseNamespace.initializeApp (/user_code/node_modules/firebase-admin/lib/firebase-namespace.js:362:30)
at categoryac (/user_code/index.js:34:11)
at WebhookClient.handleRequest (/user_code/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:303:44)
at exports.dialogflowFirebaseFulfillment.functions.https.onRequest (/user_code/index.js:91:9)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:57:9)
There are several things that you'll need to learn.
The first is how to add the Firebase Admin SDK to your project.
You'll also need to learn how to retrieve the data using the library. Firebase uses a reference-path based method to fetch the data, so you need to make sure you build the path correctly.
Finally, since you're doing this inside a fulfillment handler, and you are making an asynchronous call, you need to make sure you return a Promise. Fortunately, fetching the data also involves returning a Promise, so you can return this Promise.
The code might, partially, look something like this (untested):
function personFacts(agent) {
const personId = agent.parameters["personId"];
const personMeasurement = agent.parameters["personMeasurement"];
var db = admin.database();
var ref = db.ref(`person/${personId}/${personMeasurement}`);
return ref.once("value")
.then( snapshot => {
var result = snapshot.val();
agent.add(`${personId}'s ${personMeasurement} is ${result}`);
})
.catch( err => {
agent.add('uh oh, something went wrong.');
console.error( err );
});
}
As you noted, you need to initialize the Firebase admin library with a key that will give you access through the service account. You can generate the key and download it, and then point to the folder where you have saved it. (It looks like you've just inlined the information, which also works.)
The "Malformed Response" error means that no response was set. This can be due to a number of things, but generally means that your program crashed or failed to call agent.add() for some reason. Consult the logs of your action running for more info. (If you are using the Dialogflow Inline Editor, you can get to the logs by going to https://console.firebase.google.com/, selecting your project, selecting the "Functions" tab on the left, and selecting the "logs" tab.)
Update based on the code and error message.
As the error message suggests, you called admin.initializeApp() more than once. This should only be done when the function is first configured, rather than each time your function is called. Once initialized once - it can be used multiple times.
In your case, this can be done by moving the require that imports firebase-admin and the call to admin.initializeApp() out of the personFacts() function and put them both closer to the top - probably right after the other require() calls.

Mapping mulitiple intents to one function using actionMap for a DialogFlowApp

I am building an app using Dialogflow. The user answers some questions, and can review their answers later. My problem is with building the server to return the user's previous answers.
This is the code so far, where the intents are QUESTION_1 and QUESTION_2, and the parameters are GRATEFUL_1 and GRATEFUL_2:
'use strict';
process.env.DEBUG = 'actions-on-google:*';
const App = require('actions-on-google').DialogflowApp;
const functions = require('firebase-functions');
// a. the action names from the Dialogflow intents
const QUESTION_1 = 'Question-1';
const QUESTION_2 = 'Question-2';
// b. the parameters that are parsed from the intents
const GRATEFUL_1 = 'any-grateful-1';
const GRATEFUL_2 = 'any-grateful-2';
exports.JournalBot = functions.https.onRequest((request, response) => {
const app = new App({request, response});
console.log('Request headers: ' + JSON.stringify(request.headers));
console.log('Request body: ' + JSON.stringify(request.body));
// Return the last journal entry
function reflect (app) {
let grateful_1 = app.getArgument(GRATEFUL_1);
app.tell('Here is your previous entry: ' + grateful_1);
}
// Build an action map, which maps intent names to functions
let actionMap = new Map();
actionMap.set(QUESTION_1, reflect);
app.handleRequest(actionMap);
});
I want the 'reflect' function to be mapped to the GRATEFUL_2 response as well as GRATEFUL_1. I know how to do this, but how do I change this next bit to include both intents:
actionMap.set(QUESTION_1, reflect);
If you wanted the QUESTION_2 intent to also go to the reflect() function, you can simply add
actionMap.set(QUESTION_2, reflect);
But I don't think that is your problem. Inside reflect() you need to know which intent it was that got you there.
You can use app.getIntent() to get a string with the intent name and then match this to which response you want to give. So something like this might work:
function reflect( app ){
let intent = app.getIntent();
var grateful;
switch( intent ){
case QUESTION_1:
grateful = GRATEFUL_1;
break;
case QUESTION_2:
grateful = GRATEFUL_2;
break;
}
var response = app.getArgument( grateful );
app.tell( 'You previously said: '+response );
}
There are other variants, of course.
There is no requirement you actually use the actionMap and app.handleRequest() at all. If you have another way you want to determine which output you want to give based on the intent string, you're free to use it.

Resources