DialogFlow-修改后的“自行车商店”示例无法正常运行

时间:2019-03-14 21:19:54

标签: parameters split properties dialogflow

要使此Bike Shop示例的修改版本(或相应的原始版本)起作用,我遇到了一些严重困难。

我试图复制基本功能,但将名称,电话号码,电子邮件等字段添加到日历事件中。

也许是因为这是我第一次使用Node.js,但是事实证明,这不如在热水泥中沐浴。

我将快速总结一下我遇到的主要问题:

获取要在日历中填充的事件

因此,感谢@Prisoner,我几乎可以解决所有问题。我的主要问题是我的上下文是大写的,因此未被认可。将大多数意图转换为顶层也有帮助。现在,我可以始终如一地得到履行答复,并给我我的第一条确认消息,但是我总是得到我的第二次“错误”答复,并且日历中没有任何事件。

未显示在日历上 如何更新googleapis库?只需将package.json中的^ 27更改为^ 30?

上下文 .getContext()是否已过时?这就是它在这里(https://dialogflow.com/docs/contexts/contexts-fulfillment)所说的,这就是我昨天尝试时出现的错误。

带有连字符的实体 我更改了名称(我不知道名称不只是一个标签),但是为了将来参考和清楚起见,是.parameters.properties还是.params

仅供参考,这是我的Intent流程: Scheduleappointment>

FirstLast(获取名字和姓氏,分配给系统实体)>

ServiceNeeded(获取所需的服务,分配给开发者实体)>

Date Time MeetingPlace电子邮件电话N(获取日期,时间,电话号码和电子邮件:分配给系统实体;获取位置:分配给开发者实体)

我的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');

// Enter your calendar ID and service account JSON below.
// See https://github.com/dialogflow/bike-shop/blob/master/README.md#calendar-setup
const calendarId = 'm6t2gekgnv2qro9ln8genh10o8@group.calendar.google.com'; // Example: 6ujc6j6rgfk02cp02vg6h38cs0@group.calendar.google.com
const serviceAccount = {
  "type": "service_account",
  "project_id": "appsoft-lead-net",
  "private_key_id": "863c577bd9249f09cbce540cf082fb7c1f7349ac",
  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDBMdrUIViDt+s6\noFFrdUFxNamOpzmUN5Eu8zezQeKov4Spz73mvb29R7E903TtvmpibWhKQ4d7Liqj\ncX+CAJvOQDbmr62xQCzqsTeYGErQLC+fmpeY9gbZj96cdP4rG2mSfKak29hlSTaM\nKqhvzL+d7PsawDIoMOgBDtws9SATU/yYMVibAv+F2CYtPIemPpoU4w2K8ePaRl2P\nog96h802aGWtnTmyaW7pMk/3zYHZdbC2jp8x7l1T0ODxD23vVRe8hQOHFa1j+qxb\nCp5Xliq7AShibdESNluO4jvNUEVRFAPITv4MxzfdafRWomwub9ybKiaPlAOgrciu\nh2FeCO6nAgMBAAECggEAAl6eAappCgXi+jnw1bcUoZKtbOUAhUtghKlnXlOpYorQ\nJz8M5BF2ttAezJR/43ye6cHQHNJoN9Vl9ibpup/5npWAtuKiaEFSa7csaI6qYuma\nf0QnpjDPqQfP4gO4rFxZNqVAbAGQTWm7pAt6kRMl/zC4FxNoFMBzerPXyoC1LyzR\nP6T2zW66hGOizr8qJpbNb/XVzCpugX6UL8JSgoZayJs3j7OEJ94UgNXNKcnAfbL2\nwDzu8GfWWY82zWJPxohmQ0fwWQmkkMUxAd6PiFS4h9EfSeCMR2UkpRvDJg1qz+HU\nB9ZUZ2NMO9Gzwlcz2UPtmALBkFk6p6IBGaVm77A2QQKBgQDmjKGDP+UUXvAB0JG8\nFJrlPLhwNL511Pyq2TMN1w8RBiw5OXGKLURgIKLpfQtbqhn4YU8wWA/bcBQ7jtJU\nz0C4O2OUMor5jrek8dLDSfqjUzo06qLJ2RENXxzo3KzBOzHTHL9pX+6a+HMZrH55\ns+lriibGkMqxBDbfiNwgxzDAZwKBgQDWhZNoGWUqpmgosuBU20ZimCwtjVR/FSGs\nBfFxJzQqO85aVw8D8+YKgtLPp1GV/CbDXLnvnnBXFnXGytIzBjo6+06rlRa+ve5w\nVC1glNNAvscuz3qCwZVlP7laN75dMHwKdCNGogHDchGUvvWns1ZzNYa4w1nNet4u\na6BYSWh3wQKBgDBOnzkFFlbiXZ/DuAWUu6/0vSClrJK8AgU6ZXws57dFd9a1tpih\nS4Zo0DnkPZEDX1Wv7gvPJ+Nu5I5mKqQimQkBSKgwvvXzdX9WPDc3NREryzJzR5DL\nrssCduhD9N64LIRzrZfCxhgqntNuVUHi9LHXzLAmfpIAtA66eoVTt19ZAoGARvEr\nm0xyqVUYswTcrRNKxoY+bd4EKHariQD6JIprdOAMUNHjlFIPLECxSJyznV8izo6W\nqsiBrDswPcqLEvanQSQkkxedyvxyotEjpIHzDsFIi5FvmVg9eJtHR6+Rk63aB3b6\nx3wCC0loyQERqiDjoPv9jpKD2zEb0swi3a+BDcECgYAJm+N9r6REfo+2jSTxik9Z\name799EyusFdOgmiYv2NQuhid+ThKH2ZpmoNmGHOvwA2ak9EYjb+dgWv97aqSq5x\n+i8kDd7AdqmMT+Q/jUPHx6Nd5kzw4RyRKk/Oezs+SHJ18rn2T0xxv/fgtMEfGU1m\n8MG8dRp42wXsXhHTUa224Q==\n-----END PRIVATE KEY-----\n",
  "client_email": "id-appsoft-chatbot-v1-calendar@appsoft-lead-net.iam.gserviceaccount.com",
  "client_id": "113420745582984409565",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/id-appsoft-chatbot-v1-calendar%40appsoft-lead-net.iam.gserviceaccount.com"
}; // The JSON object looks like: { "type": "service_account", ... }

// 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 });
}

我的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"
  }
}

在此先感谢您的帮助!

{固定的编辑:终于起作用了,原来上下文名称必须完全小写才能被识别。另外,我在createCalendarEvent中毫无理由地声明了变量。 }

1 个答案:

答案 0 :(得分:1)

很多问题,让我们来看看我们可以解决的问题。

无法读取属性“拆分”

第192行是

return new Date(Date.parse(date.split('T')[0] + 'T' + time.split('T')[1].split('-')[0] + '-' + time.split('T')[1].split('-')[1]));

,它有许多对split()的调用,因此尚不清楚是哪个引起了问题,但这是因为datetime(或两者)都没有尚未定义。

包含两个行的convertTimestampToDate()函数是从两个不同的地方调用的。您的makeAppointment()checkAppointment()函数似乎都在同一行

    const dateTimeStart = convertTimestampToDate(agent.parameters.date, agent.parameters.time);

您自己没有显示Schedule appointment - Date Time meetingPlaceSchedule appointment - Date Time meetingPlace - yes Intent配置,但听起来其中一个实际上没有datetime参数。

对实现的回应

您对于未生成响应的状态有点不确定,但是对于具有处理程序的两个Intent,响应似乎已正确设置。

在以下任何情况下,都将使用UI中的内容:

  • 该意图未启用实现。
  • 履行并未明确添加回复。

每封邮件最多只能有640个字符,但我认为您的答复没有达到这个限制,我想在某些情况下可能会做到这一点。

动态参数未定义

我不确定您所说的“动态”是什么意思,但这听起来像是datetime未定义的问题。再次,我将检查以确保在您认为是的Intent中发送了这些参数。

从Intent的“流”(我将在下面讨论),听起来像您期望参数从Intent到Intent一样不断填充。通常,参数是从 current Intent提供的。

如果设置了“输出上下文”,则还可以在上下文中设置参数,这些参数将继承到将来的Intent。因此,您可能希望从上下文而不是参数中获取它们。

根据以下sid的注释进行更新:如果您想要希望当前Intent参数中的过去参数,则需要使用一些内容从上下文中显式设置Intent中的值类似于Intent参数部分的#output-context.param

未显示在日历上

您使用的是googleapis库的相当老的版本,这可能是问题也可能不是问题。但是,较新的版本本身支持Promises,这肯定会使您的代码更易于使用。

我不是很确定,但是您要指定一个名为resource的属性,该属性包含事件资源(根据specification),并且应位于请求的正文中。我不知道您正在使用的库是否对此进行了更改,但是在current library中,此字段应称为requestBody

上下文

处理上下文的最佳方法是使用直接访问器方法,例如agent.getContext()agent.setContext()agent.clearContext()

您可能已经在Google上看到了一些有关Actions的较旧的文档,其中涉及conv.contexts,但在这种情况下不适用。

带有连字符的实体

最简单的方法是将Intent UI中的参数名称更改为不带连字符的名称。您可以随心所欲地调用它们-无需在实体类型之后命名它们。

如果您确实想保留连字符,请针对agent.properties对象对它们进行索引(这似乎是您这样做的方式)。所以

agent.properties['what-ever']

意图流

您没有显示Intent配置,但这听起来像是一系列后续意图中的这些问题。

这...并不总是一个好主意。对话并不总是线性的,用户可能会尝试在对话中向后(或向前)前进,而“后续意图”则无法很好地处理。

一个更好的解决方案是将大多数这些Intent作为顶层,所有这些都使用实现。 Intent负责将参数传递给实现,将其存储起来,然后确定仍然缺少的值并要求一个。如果您认为需要缩小对用户的期望,可以考虑使用Context,但这并不是您想的那样必要。 (有关此问题的讨论,请参见Thinking for Voice: Design Conversations not Logic。)