Giter VIP home page Giter VIP logo

fulfillment-bike-shop-nodejs's Introduction

Dialogflow Bike Shop Sample

Setup Instructions

Dialogflow and Fulfillment Setup

To create this agent from our template:

 

Service Account Setup

  1. In Dialogflow's console, go to settings ⚙ and under the general tab, you'll see the project ID section with a Google Cloud link to open the Google Cloud console. Open Google Cloud.
  2. In the Cloud console, go to the menu icon ☰ > APIs & Services > Library
  3. Select Google Calendar API and then Enable to enable the API on your cloud project.
  4. Under the menu icon ☰ > APIs & Services > Credentials > Create Credentials > Service Account Key.
  5. Under Create service account key, select New Service Account from the dropdown and enter bike-shop-calendar for the name and click Create. In the popup, select Create Without Role.
    • JSON file will be downloaded to your computer that you will need in the setup sections below.

Bike Shop Calendar Setup

  1. Open the JSON file that was downloaded in the previous section and copy the email address indicated by the client_email field
// Ex:
bike-shop-calendar@${PROJECTID}.iam.gserviceaccount.com
  1. Open Google Calendar. On the left, next to Add a friend's calendar click the + and select New Calendar
  2. Enter Bike Shop for the name of the calendar and select Create Calendar. Next, go to the Bike Shop calendar that will appear on the left column.
  3. Paste the email copied from the first step into the Add people field of the Share with specific people section and then select Make changes to events in the permissions dropdown and select Send.
  4. While still in Settings, scroll down and copy the Calendar ID in the Integrate Calendar section.

Add Service Account and Calendar ID to Fulfillment

  1. Go to the index.js file in Dialogflow's Fulfillment section
  2. Take the Calendar ID copied from the prior section and replace <INSERT CALENDAR ID HERE> on line 24 of index.js.
// Ex:
const calendarId = '[email protected]';
  1. Next copy the contents of the JSON file downloaded in the "Service Account Setup" section and paste it into the empty object on line 25 of index.js const serviceAccount = {}.
//Ex:
    const serviceAccount = {
      "type": "service_account",
      "project_id": "bikesample",
    ...
  };
  1. Click Deploy at at the bottom of the page.

[OPTIONAL] Add Bike Shop FAQ Knowledge Connector

  1. Go to bike-shop-faq.csv in this repo > Raw > Copy the contents to a file on your computer.
  2. In Dialogflow's console go to Settings (⚙) > General tab > Enable Beta Features and APIs > Save.
  3. Go to the Knowledge tab in the left menu > Create Knowledge Base.
    • Name the knowledge base Bike Shop then Save
  4. Next, click Create the first one.
    • Use Bike Shop FAQ for document name,
    • text/csv for the "Mime Type",
    • FAQ for the "Knowledge type",
    • Under "Data source" > select Upload file from computer to upload bike-shop-faq.csv> Create.
  5. In the response section > click Add Response. In text response you should see $Knowledge.Answer[1] > Save.
  6. Try it out! In the simulator on the right enter do you sell ebikes?. You should see the exact response from the CSV you just uploaded: "We sell Electric bikes. We also can outfit your existing bike with a retrofit Bionx electric wheel kit. We do not do gas powered conversions."

Running the sample

  1. In Dialogflow's console, in the simulator on the right, query your Dialogflow agent with My bike is broken and respond to the questions your Dialogflow agent asks. After getting the required information, an appointment will be added to the "Bike Shop Calendar" calendar.

How to make contributions?

Please read and follow the steps in the CONTRIBUTING.md.

License

See LICENSE.

Terms

Your use of this sample is subject to, and by using or downloading the sample files you agree to comply with, the Google APIs Terms of Service.

fulfillment-bike-shop-nodejs's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fulfillment-bike-shop-nodejs's Issues

No appointment created!

when I ask an agent.

end result, always showing this message only.
I'm sorry, there are no slots available for Invalid Date, would you like to check another day?

No appointment created. :(

pls, help me.

Error creating agent

Hi,
When i try to create a new agent (I only have 3), the process is automatically cancelled. I have no idea what to do.
dialogfloe_error

Thanks in advance!

Edit: Sorry guys, I thought I was on another repo opening the issue!

Issue with Calender

image

I download this project and made absolutely no changes to the code. The time difference might be due to Time Zones, but the today/tomorrow issue seems weird.

TypeError: Cannot read property 'source' of undefined

Hey guys,

thanks a lot for the example. Based on that, I am developing an dialogflow agent, answering messenger messages via fulfillment.

It works perfectly by entering the message directly in the dialogflow console window (for testing)
But when I use the real messenger to receive the message, trigger the intent and perform the fulfilment, I get the following error

In Dialogflow I can see under History:
"Webhook call failed. Check response JSON for error details".
But if I look into the Raw interactions logs, there is nothing I can see...

In Firebase I can see:

TypeError: Cannot read property 'source' of undefined
at V2Agent.processRequest_ (/user_code/node_modules/dialogflow-fulfillment/src/v2-agent.js:108:86)
at new WebhookClient (/user_code/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:193:17)
at exports.dialogflowFirebaseFulfillment.functions.https.onRequest (/user_code/index.js:52:17)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:26:47)
at /var/tmp/worker/worker.js:684:7
at /var/tmp/worker/worker.js:668:9
at _combinedTickCallback (internal/process/next_tick.js:73:7)
at process._tickDomainCallback (internal/process/next_tick.js:128:9)

Although trying different approaches on StackOverflow I still have the issue.
Probably I am just too blind to see my mistake, can you help me?

This is my fulfillment-code, which is a simplified version of the bike-shop-nodejs-code:

'use strict';

const functions = require('firebase-functions');
const {google} = require('googleapis');
const {WebhookClient} = require('dialogflow-fulfillment');

// Enter your calendar ID below and service account JSON below, see https://github.com/dialogflow/bike-shop/blob/master/README.md#calendar-setup
const calendarId = '....(removed)....'
const serviceAccount = {
  "type": "service_account",
  "project_id": "...(removed)....",
  "private_key_id": "...(removed)....",
  "private_key": "...(removed)....",
  "client_email": "...(removed)....",
  "client_id": "...(removed)....",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://accounts.google.com/o/oauth2/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "...(removed)...."
}; 

// Set up Google Calendar Service account credentials
const serviceAccountAuth = new google.auth.JWT({
  email: serviceAccount.client_email,
  key: serviceAccount.private_key,
  scopes: 'https://www.googleapis.com/auth/calendar'
});

const calendar = google.calendar('v3');
process.env.DEBUG = 'dialogflow:*'; // enables lib debugging statements

const timeZone = 'Europa/Berlin';
const timeZoneOffset = '+02:00';

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
  const agent = new WebhookClient({ request, response });
  
  function meetingFindDateAndTime (agent) {
    const meetingDateTimeStart = new Date(agent.parameters.date.split('T')[0] + 'T' + agent.parameters.time.split('T')[1]);
    const meetingDateTimeEnd = new Date(new Date(meetingDateTimeStart).setHours(meetingDateTimeStart.getHours() + 1));

    const meetingDateTimeStartString = meetingDateTimeStart.toLocaleString(
      'de-CH',
      { month: 'long', day: 'numeric', hour: 'numeric', timeZone: 'Europe/Berlin' }
    );

    // Check the availability of the time, and make an appointment if there is time on the calendar
    return createCalendarEvent(meetingDateTimeStart, meetingDateTimeEnd).then(() => {
      agent.add(`Ok, das müsste passen: ${meetingDateTimeStartString}. Ich habs im Kalender schonmal geblockt, aber schreibe dir noch eine definitive Zusage.`);
    }).catch(() => {
      agent.add(`Mist, da habe ich leider schon einen Termin. Wann anders?`);
    });
  }

  let intentMap = new Map();
  intentMap.set('meeting.findDateAndTime', meetingFindDateAndTime);
  agent.handleRequest(intentMap);
});

function createCalendarEvent (dateTimeStart, dateTimeEnd) {
  return new Promise((resolve, reject) => {
    calendar.events.list({
      auth: serviceAccountAuth, // List events for time period
      calendarId: calendarId,
      timeMin: dateTimeStart.toISOString(),
      timeMax: dateTimeEnd.toISOString()
    }, (err, calendarResponse) => {
      // Check if there is a event already on the Bike Shop Calendar
      if (err || calendarResponse.data.items.length > 0) {
        reject(err || new Error('Requested time conflicts with another appointment'));
      } else {
        // Create event for the requested time period
        calendar.events.insert({ auth: serviceAccountAuth,
          calendarId: calendarId,
          resource: {summary: 'Bike Appointment',
            start: {dateTime: dateTimeStart},
            end: {dateTime: dateTimeEnd}}
        }, (err, event) => {
          err ? reject(err) : resolve(event);
        }
        );
      }
    });
  });
}

Invalid date

I'm sorry, there are no slots available for Invalid Date, would you like to check another day?

const dateTimeStart=...
Does it support in new version

"TypeError: Cannot read property 'split' of undefined at convertTimestampToDate" and other such annoyances

I'm having some serious difficulties getting a modified version (or the original version, for that matter) of this Bike Shop example to work.

I'm attempting to duplicate the basic functionality, but add fields for Name, Phone Number, Email, etc. into the calendar event.

Maybe it's because this is my first soiree with Node.js, but this is proving to be less enjoyable than bathing in hot cement.

I'll quickly summarize the main issues I've been having (edited 3/15/19):

Getting events to populate in Calendar

So I've gotten just about everything sorted out. My main issue was that my contexts were capitalized, therefore not recognized. Converting most of the intents to top-level helped as well. Now I can consistently get the fulfillment responses to fill in and give me my first confirmation message, but I always get my second 'error' response and no event is made in the calendar.

Not showing on calendar
How do I update the googleapis library? Just change the ^27 to ^30 in the package.json?

Entities with a hyphen
I removed hyphens from the names of parameters to allow for easier calling (I didn't know the name wasn't just a label), but for future reference and clarity, should an array of parameters be called using .parameters[], .properties[], or .params[]?

And just for reference, here's my Intent flow:
Scheduleappointment >

FirstLast (gets first and last name, assigns to system entities) >

ServiceNeeded (gets service needed, assigns to developer entity) >

Date Time MeetingPlace Email PhoneN (gets date, time, phone number & email: assigns to system entities; gets location: assigns to developer entity)

My index.js:

/**
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

const functions = require('firebase-functions');
const {google} = require('googleapis');
const {WebhookClient} = require('dialogflow-fulfillment');


// Set up Google Calendar service account credentials
const serviceAccountAuth = new google.auth.JWT({
  email: serviceAccount.client_email,
  key: serviceAccount.private_key,
  scopes: 'https://www.googleapis.com/auth/calendar'
});

const calendar = google.calendar('v3');
process.env.DEBUG = 'dialogflow:*'; // It enables lib debugging statements

const timeZone = 'America/New_York';  // Change it to your time zone

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
  const agent = new WebhookClient({ request, response });

  // This function receives the date and time values from the context 'MakeAppointment-followup'
  // and calls the createCalendarEvent() function to mark the specified time slot on Google Calendar.
  function makeAppointment (agent) {
	// Get the contexts
	const contextF = agent.context.get('firstlast');
	const contextS = agent.context.get('serviceneeded');
	const contextD = agent.context.get('datetimemeetingplaceemailphonen-followup');
	// This variable needs to hold an instance of Date object that specifies the start time of the appointment.
	const dateTimeStart = convertTimestampToDate(contextD.parameters.date, contextD.parameters.time);
	// This variable holds the end time of the appointment, which is calculated by adding an hour to the start time.
	const dateTimeEnd = addHours(dateTimeStart, 1);
	// Convert the Date object into human-readable strings.
	const appointmentTimeString = getLocaleTimeString(dateTimeStart);
	const appointmentDateString = getLocaleDateString(dateTimeStart);
// set properties to variables
const appointmentLocationString = contextD.parameters.meetingPlace;
const appointmentEmail = contextD.parameters.email;
const appointmentService = contextS.parameters.ServiceNeeded;
const appointmentFullName = contextF.parameters.givenName + " " + contextF.parameters.lastName;
const appointmentFirstName = contextF.parameters.givenName;
const appointmentPhoneString = contextD.parameters.phoneNumber;
	// Delete the context 'MakeAppointment-followup'; this is the final step of the path.
	agent.context.delete('datetimemeetingplaceemailphonen-followup');
	// The createCalendarEvent() function checks the availability of the time slot and marks the time slot on Google Calendar if the slot is available.
	return createCalendarEvent(agent, dateTimeStart, dateTimeEnd, appointmentFullName, appointmentPhoneString, appointmentLocationString, appointmentEmail, appointmentService).then(() => {
		agent.context.delete('serviceneeded');
		agent.context.delete('firstlast');
		agent.context.delete('schedule');
	  agent.add(`Got it! I have your appointment scheduled on ${appointmentDateString} at ${appointmentTimeString}—we'll contact you shortly to confirm the deets! See you soon, ${appointmentFirstName}. Good-bye!`);
	}).catch(() => {
	  agent.add(`Sorry, ${appointmentFirstName}, something went wrong—I couldn't book ${appointmentDateString} at ${appointmentTimeString}. Try trying again! If that doesn't work, let us know—Mitch probably just messed up something...`);
	});
  }

  // This function receives the date and time values from the context 'MakeAppointment-followup'
  // and calls the checkCalendarAvailablity() function to check the availability of the time slot on Google Calendar.
  function checkAppointment (agent) {
	  // Get the contexts
	  const contextF = agent.context.get('firstlast');
	  const contextS = agent.context.get('serviceneeded');
	// This variable needs to hold an instance of Date object that specifies the start time of the appointment.
	const dateTimeStart = convertTimestampToDate(agent.parameters.date, agent.parameters.time);
	// This variable holds the end time of the appointment, which is calculated by adding an hour to the start time.
	const dateTimeEnd = addHours(dateTimeStart, 1);
	// Convert the Date object into human-readable strings.
	const appointmentTimeString = getLocaleTimeString(dateTimeStart);
	const appointmentDateString = getLocaleDateString(dateTimeStart);
	// set properties into variables
	const appointmentLocationString = agent.parameters.meetingPlace;
	const appointmentEmail = agent.parameters.email;
	const appointmentService = contextS.parameters.ServiceNeeded;
	const appointmentFullName = contextF.parameters.givenName + " " + contextF.parameters.lastName;
	const appointmentFirstName = contextF.parameters.givenName;
	const appointmentPhoneString = agent.parameters.phoneNumber;
	// The checkCalendarAvailablity() function checks the availability of the time slot.
	return checkCalendarAvailablity(dateTimeStart, dateTimeEnd).then(() => {
		// The time slot is available.
	   // The function returns a response that asks for the confirmation of the date and time.
	   agent.add(`Okay, ${appointmentFullName}, so you've said that you'd like your appointment on ${appointmentDateString} at ${appointmentTimeString}. We'll call ${appointmentPhoneString} and/or email ${appointmentEmail} to confirm this appointment ${appointmentLocationString} about ${appointmentService}. Did I get that right?`);
	 }).catch(() => {
	   // The time slot is not available.
	   agent.add(`Sorry, ${appointmentFirstName}, we're booked up on ${appointmentDateString} at ${appointmentTimeString}. Huge bummer, I know =/ But is there another time you'd like to schedule your appointment?`);
	   // Delete the context 'MakeAppointment-followup' to return the flow of conversation to the beginning.
	   agent.context.delete('datetimemeetingplaceemailphonen-followup');
   });
  }
  // Mapping of the functions to the agent's intents.
  let intentMap = new Map();
  intentMap.set('Date Time MeetingPlace Email PhoneN', checkAppointment);
  intentMap.set('Date Time MeetingPlace Email PhoneN - yes', makeAppointment);
  agent.handleRequest(intentMap);
});

// This function checks for the availability of the time slot, which starts at 'dateTimeStart' and ends at 'dateTimeEnd'.
// 'dateTimeStart' and 'dateTimeEnd' are instances of a Date object.
function checkCalendarAvailablity (dateTimeStart, dateTimeEnd) {
  return new Promise((resolve, reject) => {
	calendar.events.list({
	  auth: serviceAccountAuth, // List events for time period
	  calendarId: calendarId,
	  timeMin: dateTimeStart.toISOString(),
	  timeMax: dateTimeEnd.toISOString()
	}, (err, calendarResponse) => {
	  // Check if there is an event already on the Calendar
	  if (err || calendarResponse.data.items.length > 0) {
		reject(err || new Error('Requested time conflicts with another appointment'));
	  }else {
		resolve(calendarResponse);
	  }
	});
  });
}

// This function marks the time slot on Google Calendar. The time slot on the calendar starts at 'dateTimeStart' and ends at 'dateTimeEnd'.
// 'dateTimeStart' and 'dateTimeEnd' are instances of a Date object.
function createCalendarEvent (agent, dateTimeStart, dateTimeEnd, appointmentFullName, appointmentPhoneString, appointmentLocationString, appointmentEmail, appointmentService) {

// assign values to variables
	appointmentPhoneString = agent.parameters.phoneNumber;
	appointmentLocationString = agent.parameters.meetingPlace;
	appointmentEmail = agent.parameters.email;
	appointmentService = agent.parameters.ServiceNeeded;
	appointmentFullName = agent.parameters.givenName + " " + agent.parameters.lastName;

  return new Promise((resolve, reject) => {
	calendar.events.list({
	  auth: serviceAccountAuth, // List events for time period
	  calendarId: calendarId,
	  timeMin: dateTimeStart.toISOString(),
	  timeMax: dateTimeEnd.toISOString()
	}, (err, calendarResponse) => {
	  // Check if there is an event already on the Calendar
	  if (err || calendarResponse.data.items.length > 0) {
		reject(err || new Error('Requested time conflicts with another appointment'));
	  } else {
		// Create event for the requested time period
		calendar.events.insert({ auth: serviceAccountAuth,
		  calendarId: calendarId,
		  resource: {
		   summary: 'Appsoft Appointment',
		   start: {
			 dateTime: dateTimeStart
		   },
		   end: {
			 dateTime: dateTimeEnd
		   },
		   attendees:[ {
			 displayName: appointmentFullName,
			 email: appointmentEmail,
		   }],
		   location: appointmentLocationString,
		   description: 'Phone Number: ' + appointmentPhoneString + '; Service Needed: ' + appointmentService}
		}, (err, event) => {
		  err ? reject(err) : resolve(event);
		}
		);
	  }
	});
  });
}

// A helper function that receives Dialogflow's 'date' and 'time' parameters and creates a Date instance.
function convertTimestampToDate(date, time){
  // Parse the date, time, and time zone offset values from the input parameters and create a new Date object
  return new Date(Date.parse(date.split('T')[0] + 'T' + time.split('T')[1].split('-')[0] + '-' + time.split('T')[1].split('-')[1]));
}

// A helper function that adds the integer value of 'hoursToAdd' to the Date instance 'dateObj' and returns a new Data instance.
function addHours(dateObj, hoursToAdd){
  return new Date(new Date(dateObj).setHours(dateObj.getHours() + hoursToAdd));
}

// A helper function that converts the Date instance 'dateObj' into a string that represents this time in English.
function getLocaleTimeString(dateObj){
  return dateObj.toLocaleTimeString('en-US', { hour: 'numeric', hour12: true, timeZone: timeZone });
}

// A helper function that converts the Date instance 'dateObj' into a string that represents this date in English.
function getLocaleDateString(dateObj){
  return dateObj.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', timeZone: timeZone });
}

My package.json:

{
  "name": "DialogflowFirebaseWebhook",
  "description": "Firebase Webhook dependencies for a Dialogflow agent.",
  "version": "0.0.1",
  "private": true,
  "license": "Apache Version 2.0",
  "author": "Google Inc.",
  "engines": {
    "node": "6"
  },
  "scripts": {
    "lint": "semistandard --fix \"**/*.js\"",
    "start": "firebase deploy --only functions",
    "deploy": "firebase deploy --only functions"
  },
  "dependencies": {
    "firebase-functions": "^2.0.2",
    "firebase-admin": "^5.13.1",
    "googleapis": "^27.0.0",
    "actions-on-google": "2.2.0",
    "dialogflow-fulfillment": "0.6.1"
  }
}

Thanks in advance for your help!

Invalid date

Hi there,

thanks for creating this template.

After i have followed all the steps, i tried the bot and after i fill up the date and time, it says " I'm sorry, there are no slots available for Invalid Date. "

Possible to assist? Thanks in advance!
Kenny

Invalid Date

appointmentTimeString on index.js return Invalid Date when integrated with any platform(I'm using Line and Web Demo).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.