DialogFlow - 修改后的“Bike Shop Sample”无法使用

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (1)
  • 关注 (0)
  • 查看 (29)

我在使用这个Bike Shop Sample示例的修改版本(或原始版本)时遇到了一些严重的困难。

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

也许是因为这是我第一次使用Node.js

我会很快总结一下我遇到的主要问题:

无法在convertTimestampToDate中读取未定义的属性'split'(/user_code/index.js:192:34) 这是我存在的原因。它直到我使代码变得更复杂才出现。

获得正确的响应以显示 我希望在履行代码中显示响应,但更常见的是,DialogFlow界面中的响应显示。

避免聊天机器人响应中的动态值显示为undefined 当我从执行代码中获取响应时,给定名称和姓氏的填充值表示未定义。

让一个事件完全显示在日历上 即使是全部200个回复,即使是股票示例也不会这样做。我完全复制了JSON文件,所以我90%确定密钥是正确的。

我有几个问题:为什么不用const context = agent.context.get('Scheduleappointment-DateTimemeetingPlace-followup');使context实际可用?任何时候我尝试直接从它引用一个参数,它会抛出一个Cannot read property "**blank**" at undefined.它(大多数)工作时,我使用agent而不是context,实质上使声明行浪费代码。这是否意味着context仍然注册为未定义?这与使用代理从当前意图和上下文拉出以从先前意图中拉出的股票示例不同。

如何在属性中引用带有连字符的系统实体,如@sys.given-name和@sys.phone-number?namenumber注册为未声明的变量。我找到了我在StackOverflow中使用的方法,但我不确定它是最正确的方法。

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

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

Scheduleappointment - ServiceNeeded(需要服务,分配给开发者实体)>

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

我的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": "**omitted**",
  "private_key_id": "**omitted**",
  "private_key": "**omitted**",
  "client_email": "**omitted**",
  "client_id": "**omitted**,
  "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": "**omitted**"
}; // 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 context 'Scheduleappointment-DateTimemeetingPlace-followup'
const context = agent.context.get('Scheduleappointment-DateTimemeetingPlace-followup');
    // 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 to variables
const appointmentLocationString = agent.parameters.meetingPlace;
const appointmentEmail = agent.parameters.email;
const appointmentService = agent.parameters.ServiceNeeded;
const appointmentFullName = agent.parameters['given-name'] + " " + agent.parameters['last-name'];
const appointmentFirstName = agent.parameters['given-name'];
const appointmentPhoneString = agent.parameters['phone-number'];
    // Delete the context 'MakeAppointment-followup'; this is the final step of the path.
    agent.context.delete('Scheduleappointment-DateTimemeetingPlace-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.add(`Got it! I have your appointment scheduled on ${appointmentDateString} at ${appointmentTimeString}. See you soon, ${appointmentFirstName}. Good-bye!`);
    }).catch(() => {
      agent.add(`Sorry, ${appointmentFirstName}, something went wrong—I couldn't book the ${appointmentDateString} at ${appointmentTimeString}. Try trying again! If that doesn't work, let us know—Mitch probably just messed something up...`);
    });
  }

  // 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 context 'Scheduleappointment-DateTimemeetingPlace-followup'
    const context = agent.context.get('Scheduleappointment-DateTimemeetingPlace-followup');
    // 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 = agent.parameters.ServiceNeeded;
    const appointmentFullName = agent.parameters['given-name'] + " " + agent.parameters['last-name'];
    const appointmentFirstName = agent.parameters['given-name'];
    const appointmentPhoneString = agent.parameters['phone-number'];
    // 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('Scheduleappointment-DateTimemeetingPlace-followup');
   });
  }
  // Mapping of the functions to the agent's intents.
  let intentMap = new Map();
  intentMap.set('Schedule appointment - Date Time meetingPlace', checkAppointment);
  intentMap.set('Schedule appointment - Date Time meetingPlace - 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['phone-number'];
    appointmentLocationString = agent.parameters.meetingPlace;
    appointmentEmail = agent.parameters.email;
    appointmentService = agent.parameters.ServiceNeeded;
    appointmentFullName = agent.parameters['given-name'] + " " + agent.parameters['last-name'];

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

在此先感谢您的帮助!

提问于
用户回答回答于

很多问题,让我们看看我们可以解开什么。

无法读取属性'拆分'

第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意图配置,但听起来其中一个实际上没有datetime参数。

履行的回应

你对哪些响应没有生成有点模糊,但对于有两个具有处理程序的Intent,响应似乎设置正确。

来自UI的那些将在以下任何情况下使用:

  • 该Intent未启用Fulfillment。
  • 履行未明确添加回复。

还有就是每封邮件640个字符的限制,但我不认为你打你的答复该限制,尽管我想它可能在某些情况下是不可能的。

动态参数未定义

我不确定你的“动态”是什么意思,但datetime未定义的问题相同。同样,我会检查以确保这些参数是在您认为的Intents中发送的。

从Intent“flow”(我将在下面讨论)中,听起来你期望参数继续从Intent填充到Intent。通常,参数是当前 Intent 提供的参数。

如果您设置了输出上下文,则也可以在上下文中设置参数,这些参数将结转到将来的Intents。所以你可能希望从Context中获取它们,而不是从参数中获取它们。

没有在日历上显示

您正在使用相当旧版本的googleapis库,这可能是也可能不是问题。然而,较新的,本机支持Promises,这肯定会使您的代码更容易使用。

我不是肯定的,但是你指定了一个名为的属性resource,它包含事件资源(根据规范),并且应该在请求的主体中。我不知道这是否从您正在使用的库中更改,但在当前库中,应该调用此字段requestBody

处理环境最好的办法是使用直接访问方法,如agent.getContext()agent.setContext()agent.clearContext()

您可能已经看过一些关于Google上的操作的旧文档,这些文档conv.contexts在这种情况下不适用。

带连字符的实体

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

如果你想保留连字符,你可以将它们与agent.properties对象进行索引(这似乎是你这样做的方式)。所以

agent.properties['what-ever']

Intent 流

您没有显示Intent配置,但听起来您将这些问题作为一系列后续意图。

这...并不总是一个好主意。对话并不总是线性的,用户可能会尝试在对话中倒退(或转发),后续意图不能很好地处理。

更好的解决方案是将大多数Intent作为顶级,所有这些都使用履行。意图负责将参数传递给存储它们的履行,然后确定哪些值仍然缺失并要求一个。如果您觉得需要缩小对用户的期望,可以考虑使用上下文,但这并不像您想象的那样必要。(有关此问题的一些讨论,请参阅思考语音:设计对话而不是逻辑。)

扫码关注云+社区

领取腾讯云代金券