继续使用客户端或服务器端执行方式进行构建

您已经了解了基础知识,现在您可以使用 Canvas 专用方法增强和自定义 Action 了。创建 Actions 项目时,您可以选择使用客户端执行方式模型或服务器端执行方式模型开发 Action。如需详细了解这些选项,请参阅启用 Interactive Canvas

如果您选择客户端执行方式模型选项,则可以在 Action 中使用以下内容:

如果您选择服务器执行方式模型选项,则可以在 Action 中使用以下代码:

不建议将某些 Interactive Canvas API 与特定执行方式模型搭配使用。下表显示了选择客户端执行方式选项时启用的 API,以及对于每种模型是建议还是不建议使用这些 API:

API 名称 服务器执行方式模型是否支持? 客户端执行方式模型是否支持?
sendTextQuery() 支持,但不建议(如需了解详情,请参阅 sendtextQuery()
outputTts()
triggerScene()
createIntentHandler(), expect(), clearExpectations(), prompt()
createNumberSlot(),createTextSlot, createConfirmationSlot, createOptionsSlot()
setHomeParam(), getHomeParam(), setUserParam(), getUserParam()

以下部分介绍了如何在 Interactive Canvas Action 中为客户端和服务器端执行方式模型实现 API。

使用客户端执行方式构建

您可以在 Web 应用逻辑中实现以下 Interactive Canvas API:

outputTts()

借助此 API,您无需从 Actions Builder 发送静态提示或调用 webhook,即可从设备中输出文字转语音 (TTS)。如果不需要与 TTS 关联的服务器端逻辑,您可以在客户端使用 outputTts() 跳过服务器访问,并更快地向用户提供响应。

客户端 outputTts() 可以中断或取消服务器端 TTS。您可以采取以下预防措施,避免干扰服务器端 TTS:

  • 避免在会话开始时调用 outputTts();相反,应在 Action 的第一轮对话中使用服务器端 TTS。
  • 避免在用户未执行操作的情况下连续调用 outputTts()

以下代码段展示了如何使用 outputTts() 从客户端输出 TTS:

interactiveCanvas.outputTts(
      '<speak>This is an example response.</speak>', true);

您还可以将 outputTts()onTtsMark() 搭配使用,将 SSML 标记放置在文本序列中。使用 onTtsMark() 可在 SSML TTS 字符串的特定点同步 Web 应用动画或游戏状态,如以下代码段所示:

interactiveCanvas.outputTts(
      '<speak>Speak as <mark name="number" /> number <break time="700ms"/>' +
      '<say-as interpret-as="cardinal">12345</say-as> <break time="300ms"/> ' +
      'Speak as <mark name="digits" /> digits <break time="700ms"/>' +
      '<say-as interpret-as="characters">12345</say-as></speak>', true);

在前面的示例中,自定义响应的两个标记通过 TTS 发送到 Web 应用。

在客户端上处理 intent 执行方式

在 Interactive Canvas 的服务器执行方式模型中,所有 intent 都必须由网络钩子处理,这会增加 Action 的延迟时间。您可以在 Web 应用中处理 intent 的执行,而不是调用 webhook。

如需在客户端处理 intent,您可以使用以下 API:

  • createIntentHandler():此方法允许您在 Web 应用代码中为 Actions Builder 中定义的自定义 intent 定义 intent 处理程序。
  • expect():用于激活/注册 intent 处理程序以便用户可以匹配 intent 的方法。
  • clearExpectations():该方法清除了对所有当前已激活 intent 的预期,这样一来,即使用户说出的话语与 intent 匹配,也无法匹配 intent。
  • deleteHandler():该方法会停用各个 intent 处理程序,以便无法匹配这些 intent。

借助这些 API,您可以针对 Interactive Canvas Action 的不同状态选择性地启用或停用 intent。您必须在 intent 处理程序上使用 expect() 才能激活这些 intent。

激活 intent 处理程序

激活 intent 处理程序的过程分为两个步骤。首先,您必须在 Actions Builder 中定义 intent。接下来,为了使 intent 可匹配,您需要对 intent 处理程序调用 expect()

如需在客户端配置和激活 intent 处理程序,请按以下步骤操作:

  1. 在 Actions 控制台中打开您的项目,然后添加一个 Custom intent
  2. 针对“这是一项全球意图吗?”部分选择

  3. 配置 intent,然后点击 Save

  4. 在您的 Web 应用逻辑中定义 intent 的处理程序,如以下代码段所示:

    /**
    * Define handler for intent.
    */
    const bookTableIntent = interactiveCanvas.createIntentHandler('reserveTable',
      matchedIntent => {
        console.log("Intent match handler to reserve a table was triggered!");
      });
    
    /**
    * Define handler for intent with an argument.
    */
    const bookTableIntent = interactiveCanvas.createIntentHandler('reserveTable',
      matchedIntent => {
        const numberOfPeople = matchedIntent.getIntentArg('numberOfPeople');
        console.log(`Intent match handler to reserve a table for ${number of people} was triggered!`);
      });
    
  5. 调用 expect() 方法以注册 intent 处理程序,如以下代码段所示:

    /**
    * Define handler for intent and expect() it.
    */
    const bookTableIntent = interactiveCanvas.createIntentHandler('reserveTable',
      matchedIntent => {
        console.log("Intent match handler to reserve a table was triggered!");
      });
    var handler = interactiveCanvas.expect(bookTableIntent);
    

停用 intent 处理程序

定义 intent 处理程序后,您可以根据需要针对您的 Action 启用或停用 intent。当您调用 expect() 来激活 intent 时,它会返回一个包含 deleteHandler() 方法的对象,您可以使用该方法停用新创建的处理程序。即使 intent 当前未处于活跃状态,intent 处理程序定义也会保留,因此您可以根据需要重新激活 intent。

如需停用 intent 处理程序,请在 intent 处理程序上调用 deleteHandler(),如以下代码段所示:

    /**
    * Define handler for intent and expect() it.
    */
    const bookTableIntent = interactiveCanvas.createIntentHandler('reserveTable',
      matchedIntent => {
        console.log("Intent match handler to reserve a table was triggered!");
      });
    var handler = interactiveCanvas.expect(bookTableIntent);
    
    // Delete the handler for `bookTableIntent`.
    handler.deleteHandler();
    

您可以调用 expect() 以重新添加已停用的 intent 处理程序,如以下代码段所示:

    // Re-add the `bookTableIntent` handler.
    handler = interactiveCanvas.expect(bookTableIntent);

如需批量停用 intent,您可以使用 clearExpectations() 方法,该方法会停用当前已激活的所有 intent。以下代码段展示了如何清除所有 intent 处理程序的预期:

interactiveCanvas.clearExpectations();

处理客户端上的槽填充

您可以直接在 Web 应用中处理槽填充,而不是在 Actions Builder 中为场景添加槽填充。

如需在客户端处理槽填充,您必须先使用以下 API 之一创建槽:

  • createNumberSlot(callback, hints):一种用于在 Web 应用代码中定义数字槽位的方法。用于提示用户输入数字。
  • createTextSlot(callback, hints):一种用于在 Web 应用代码中定义文本槽的方法。用于提示用户输入某个字词。
  • createConfirmationSlot(callback, hints):该方法可用于在 Web 应用代码中定义确认槽。用于提示用户确认(是/否)。
  • createOptionsSlot(options, callback, hints):该方法可用于在 Web 应用代码中定义选项槽。用于提示用户从预定义的选项列表中进行选择。

创建槽位时,您可以选择定义 triggerHints,这些关键字有助于提高 Action 的自然语言理解 (NLU) 系统。这些关键字应该是用户在填充槽时可能会说出的简短字词。例如,数字槽的 triggerHints 关键字可以是 years。如果用户在对话中以“我有三十岁”作为回答来回复与他们的年龄有关的问题,您的 Action 更有可能识别出用户正在以适当的方式填补空档。

创建槽后,您可以使用 prompt API 提示用户选择槽:

  • prompt(tts, slot):向用户输出 TTS 提示,提示用户输入预期的槽位的方法。

调用 prompt() 会返回一个 promise,其中包含已填充槽的状态和值。

创建号码槽

通过数字槽,您可以在对话期间提示用户输入数字。如需详细了解槽填充,请参阅 Actions Builder 文档的槽填充部分。

如需提示用户在客户端填充号码槽,请按以下步骤操作:

  1. 调用 createNumberSlot() 方法,在 Web 应用逻辑中创建数字槽:

    /**
     * Create number slot.
     */
    const triggerHints = { associatedWords: ['guess number', 'number'] };
    const slot = interactiveCanvas.createNumberSlot(
      number => {
        console.log(`Number guessed: ${number}.`);
      }, triggerHints);
    
    
  2. 调用 prompt() 方法以提示用户选择槽,并处理返回的 promise 中的槽值,如以下代码段所示:

    const promptPromise = interactiveCanvas.prompt(
      { text: 'What number am I thinking of between 1 and 10?' }, slot);
    
    promptPromise.then(
      answer => {
        if (answer.status == interactiveCanvas.AnswerStatus.ANSWERED) {
          // answer === {value: 5, status: ANSWERED}
          // Do something with answer.value
        } else {
          console.error('Promise returned unsuccessful status ' + answer.status);
        }
      });
    

创建文字位置

通过文字槽,您可以在对话过程中提示用户输入某个字词。如需详细了解槽填充,请参阅 Actions Builder 文档的槽填充部分。

如需提示用户在客户端填充一个文本槽,请按以下步骤操作:

  1. 调用 createTextSlot() 方法,以在 Web 应用逻辑中创建文本槽:

    /**
     * Create text slot.
     */
    const triggerHints = { associatedWords: ['favorite color', 'color'] };
    const slot = interactiveCanvas.createTextSlot(
      text => {
        console.log(`Favorite color: ${text}.`);
      }, triggerHints);
    
    
  2. 调用 prompt() 方法以提示用户选择槽,并处理返回的 promise 中的槽值,如以下代码段所示:

    const promptPromise = interactiveCanvas.prompt(
      { text: 'What is your favorite color?' }, slot);
    
    promptPromise.then(
      answer => {
        if (answer.status == interactiveCanvas.AnswerStatus.ANSWERED) {
          // answer === {value: "red", status: ANSWERED}
          // Do something with answer.value
        } else {
          console.error('Promise returned unsuccessful status ' + answer.status);
        }
      });
    

创建确认槽

通过确认槽,您可以提示用户确认(用户可以回复“Yes”或“No”以填充该槽)。如需详细了解槽填充,请参阅 Actions Builder 文档的槽填充部分。

如需提示用户在客户端填充确认槽,请按以下步骤操作:

  1. 调用 createConfirmationSlot() 方法,以在 Web 应用逻辑中创建确认槽:

    /**
     * Create confirmation slot (boolean).
     */
    const triggerHints = { associatedWords: ['user confirmation', 'confirmation'] };
    const slot = interactiveCanvas.createConfirmationSlot(
      yesOrNo => {
        console.log(`Confirmation: ${yesOrNo}`);
      }, triggerHints);
    
    
  2. 调用 prompt() 方法以提示用户选择槽,并处理返回的 promise 中的槽值,如以下代码段所示:

    const promptPromise = interactiveCanvas.prompt(
      { text: 'Do you agree to the Terms of Service?' }, slot);
    
    promptPromise.then(
      answer => {
        if (answer.status == interactiveCanvas.AnswerStatus.ANSWERED) {
          // answer === {value: true, status: ANSWERED}
          // Do something with answer.value
        } else {
          console.error('Promise returned unsuccessful status ' + answer.status);
        }
      });
    

创建选项槽

通过选项槽,您可以提示用户从预定义的选项列表中进行选择。如需详细了解槽填充,请参阅 Actions Builder 文档的槽填充部分。

如需提示用户在客户端填充选项槽,请按以下步骤操作:

  1. 调用 createOptionsSlot() 方法,以在 Web 应用逻辑中创建选项槽位:

    /**
     * Create options slot (list selection).
     */
    const triggerHints = { associatedWords: ['select fruit', 'choose fruit'] };
    // Define selectable options
    const options = [{
      key: 'apple',
      synonyms: ['apple', 'large apple', 'gala apple'],
    }, {
      key: 'banana',
      synonyms: ['banana', 'green banana', 'plantain'],
    }];
    const slot = interactiveCanvas.createOptionsSlot(
      options,
      selectedOption => {
        console.log(`You have selected ${selectedOption} as your fruit.`);
      }, triggerHints);
    
    
  2. 调用 prompt() 方法以提示用户选择槽,并处理返回的 promise 中的槽值,如以下代码段所示:

    const promptPromise = interactiveCanvas.prompt(
      { text: 'Would you like a banana or an apple?' }, slot);
    
    promptPromise.then(
      answer => {
        if (answer.status == interactiveCanvas.AnswerStatus.ANSWERED) {
          // answer === {value: 'apple', status: ANSWERED}
          // Do something with answer.value
        } else {
          console.error('Promise returned unsuccessful status ' + answer.status);
        }
      });
    

triggerScene()

借助 triggerScene() API,您可以从客户端执行方式过渡到 Interactive Canvas Action 中的其他场景。借助 triggerScene(),当用户需要访问 Actions Builder 中需要网络钩子的系统场景时,您还可以从客户端执行方式切换到服务器端执行方式。例如,当用户需要关联其帐号或接收通知时,您可以调用 triggerScene();然后,您可以通过 Canvas 提示从该场景返回到客户端执行方式。

以下代码段展示了如何在 Action 中实现 triggerScene()

interactiveCanvas.triggerScene('SceneName').then((status) => {
  console.log("sent the request to trigger scene.");
}).catch(e => {
  console.log("Failed to trigger a scene.");
})

客户端上的主屏幕和用户存储空间

您可以调用客户端 API 来处理 Web 应用中的住宅存储空间和用户存储空间,而无需使用 webhook 来获取和设置住宅存储空间值。然后,您的 Web 应用可以在多个会话中(例如,在提示和条件中)使用这些存储的值,并在需要时访问特定家庭或用户的值。使用这些 API 可以减少 Interactive Canvas Action 中的延迟时间,因为您不再需要调用 webhook 来获取和设置存储值。

Web 应用中的主屏幕和用户存储遵循与网络钩子中的存储相同的基本原则。如需详细了解主存储空间和用户存储空间,请参阅主存储空间用户存储空间的文档。

客户端家庭存储

借助住宅存储空间,您可以根据住宅图存储家庭用户的值,并在家庭中的所有会话之间共享。例如,如果用户在家中玩 Interactive Canvas 游戏,则游戏的得分可以存储在家庭存储空间中,而其他家庭成员可以使用存储的得分继续玩游戏。

如需让您的 Action 支持家庭存储空间,请按以下步骤操作:

  1. 在 Actions 控制台中,依次转到 Deploy > Directory information > Additional Information
  2. 针对您的 Action 是否使用家庭存储空间?勾选复选框。

如需向 Web 应用中的主存储空间写入一个值,请调用 setHomeParam() 方法,如以下代码段所示:

interactiveCanvas.setHomeParam('familySize',  10).then(
      result => {
        console.log('Set home param success');
      },
      fail => {
        console.error(err);
      });

如需从 Web 应用的主存储空间中读取值,请调用 getHomeParam() 方法,如以下代码段所示:

interactiveCanvas.getHomeParam('familySize').then(
      value => {
        console.log(JSON.stringify(result));
      },
      err => {
        console.error(err);
      }
  );

如需清除所有现有主屏幕存储空间,请调用 resetHomeParam() 方法,如以下代码段所示:

interactiveCanvas.resetHomeParam();

客户端用户存储

借助用户存储空间,您可以存储经过验证的特定用户在多个会话中对应的参数值。例如,如果用户正在玩游戏,则可以为该用户存储游戏得分。在后续的游戏会话中,用户可以继续以相同的得分继续玩游戏。

如需将值写入 Web 应用的用户存储空间,请调用 setUserParam() 方法,如以下代码段所示:

interactiveCanvas.setUserParam('color',  'blue').then(
      result => {
        console.log('Set user param success');
      },
      err => {
        console.error(err);
      });

如需从 Web 应用的用户存储空间中读取值,请调用 getUserParam() 方法,如以下代码段所示:

interactiveCanvas.getUserParam('color').then(
      value => {
        console.log(JSON.stringify(result));
      },
      err => {
        console.error(err);
      }
  );

如需清除所有现有用户存储空间,请调用 resetUserParam() 方法,如以下代码段所示:

interactiveCanvas.resetUserParam();

setCanvasState()

借助 setCanvasState() 方法,您可以将状态数据从 Interactive Canvas Web 应用发送到执行方式,并通知 Google 助理 Web 应用已更新其状态。Web 应用会以 JSON 对象的形式发送更新后的状态。

调用 setCanvasState() 不会调用 intent。调用 setCanvasState() 后,如果调用了 sendTextQuery() 或用户查询与对话中的 intent 匹配,则使用上一轮对话中的 setCanvasState() 设置的数据将可用于后续对话回合。

在以下代码段中,Web 应用使用 setCanvasState() 设置画布状态数据:

JavaScript

this.action.canvas.setCanvasState({ score: 150 })
    

从 webhook 引用画布状态

您可以在执行方式代码中引用存储的 Canvas 状态值。如需引用该值,请使用 conv.context.canvas.state.KEY 语法,其中 KEY 是设置 Canvas 状态值时指定的键。

例如,如果您之前在 Canvas 状态下存储了某款游戏的最高得分值作为参数 score,请使用 conv.context.canvas.state.score 引用该值,以在执行方式中访问该值:

Node.js

app.handle('webhook-name', conv => {
    console.log(conv.context.canvas.state.score);
})
    

在提示内引用画布状态

您可以在提示中引用存储的 Canvas 状态值。如需引用该值,请使用 $canvas.state.KEY 语法,其中 KEY 是设置 Canvas 状态值时指定的键。

例如,如果您之前将一款处于 Canvas 状态的游戏的高分值存储为参数 score,请使用 $canvas.state.score 引用该值,以在提示中访问该值:

JSON

{
  "candidates": [{
    "first_simple": {
      "variants": [{
        "speech": "Your high score is $canvas.state.score."
      }]
    }
  }]
}
    

条件内引用画布状态

您还可以在条件中引用存储的画布状态值。如需引用该值,请使用 canvas.state.KEY 语法,其中 KEY 是设置 Canvas 状态值时指定的键。

例如,如果您之前在 Canvas 状态下存储了某款游戏的高分值作为参数 score,并希望将其与条件中的 999 值进行比较,则可以使用 canvas.state.score 引用条件中的存储值。您的条件表达式如下所示:

条件语法

canvas.state.score >= 999
    

sendTextQuery()

sendTextQuery() 方法向对话型 Action 发送文本查询,以程序化地匹配 intent。此示例使用 sendTextQuery() 在用户点击按钮时重启三角形旋转游戏。当用户点击“Restart game”按钮时,sendTextQuery() 会发送一个与 Restart game intent 匹配的文本查询并返回 promise。此 promise 会在 intent 触发时生成 SUCCESS,否则会导致 BLOCKED。以下代码段会与 intent 匹配,并处理 promise 的成功和失败情况:

JavaScript

…
/**
* Handle game restarts
*/
async handleRestartGame() {
    console.log(`Request in flight`);
    this.button.texture = this.button.textureButtonDisabled;
    this.sprite.spin = false;
    const res = await this.action.canvas.sendTextQuery('Restart game');
    if (res.toUpperCase() !== 'SUCCESS') {
        console.log(`Request in flight: ${res}`);
        return;
    }
    console.log(`Request in flight: ${res}`);
    this.button.texture = this.button.textureButtonDisabled;
    this.sprite.spin = false;
}
…
    

如果 promise 导致 SUCCESS,则 Restart game 网络钩子处理程序会向您的 Web 应用发送 Canvas 响应:

JavaScript

…
app.handle('restart', conv => {
  conv.add(new Canvas({
    data: {
      command: 'RESTART_GAME'
    }
  }));
});
…
    

Canvas 响应会触发 onUpdate() 回调,该回调会执行以下 RESTART_GAME 代码段中的代码:

JavaScript

…
RESTART_GAME: (data) => {
    this.scene.button.texture = this.scene.button.textureButton;
    this.scene.sprite.spin = true;
    this.scene.sprite.tint = 0x00FF00; // green
    this.scene.sprite.rotation = 0;
},
…
    

使用服务器端执行方式构建

您可以在网络钩子中实现以下 Interactive Canvas API:

启用全屏模式

默认情况下,Interactive Canvas Web 应用会在屏幕顶部添加一个包含 Action 名称的标题。您可以使用 enableFullScreen 移除该标头,并将其替换为加载屏幕中显示的临时消息框,这样一来,用户便可以在与您的 Action 互动时获享全屏体验。消息框消息会显示相应 Action 的显示名称、开发者名称以及有关退出该 Action 的说明,而且消息框颜色会根据用户在设备上选择的主题而变化。

图 1. 加载屏幕上的操作框消息。

如果用户频繁与您的 Action 互动,消息框消息会暂时停止显示在加载屏幕上。如果用户有一段时间未与您的 Action 互动,系统会在用户启动该 Action 时重新显示消息框消息。

您可以在网络钩子或 Actions Builder 中的静态提示中启用全屏模式。

如需在网络钩子中启用全屏模式,请按以下步骤操作:

  1. 在会话中的 webhook 返回的第一个 canvas 响应中将 enableFullScreen 字段设置为 true。以下代码段是使用 Node.js 客户端库的实现示例:

     const { conversation, Canvas } = require('@assistant/conversation');
     const functions = require('firebase-functions');
    
     const app = conversation();
    
     app.handle('invocation_fullscreen', conv => {
       conv.add(new Canvas(
         {
           url: 'https://example-url.com',
           enableFullScreen: true
         }));
     });
    
     exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app);
    

如需在 Actions Builder 中的静态提示中启用全屏模式,请按以下步骤操作:

  1. Actions 控制台中打开您的项目。
  2. 点击导航栏中的 Develop,然后打开包含第一个 canvas 响应的提示。
  3. enable_full_screen 设置为 true,如以下代码段所示:

     {
      "candidates": [
        {
          "canvas": {
            "url": "https://example-url.com",
            "enable_full_screen": true
          }
        }
      ]
    }
    

continueTtsDuringTouch

默认情况下,当用户在使用 Interactive Canvas Action 时点按屏幕时,TTS 会停止播放。您可以启用 TTS,以便在用户通过 continueTtsDuringTouch 轻触屏幕时继续播放。此行为不能在同一会话中开启或关闭。

您可以在网络钩子中或 Actions Builder 中的静态提示中实现此行为。

如需让 TTS 在用户点按网络钩子中的屏幕后继续,请按以下步骤操作:

  • 在会话中的 webhook 返回的第一个 canvas 响应中将 continueTtsDuringTouch 字段设置为 true。以下代码段是使用 Node.js 客户端库的实现示例:

    const { conversation, Canvas } = require('@assisant/conversation');
    const functions = require('firebase-functions');
    
    const app = conversation();
    
    app.handle('intent-name', conv => {
      conv.add(new Canvas(
        {
          url: 'https://example-url.com',
          continueTtsDuringTouch: true
        }));
    });
    
    exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app);
    

如需在用户点按 Actions Builder 中的静态提示中的屏幕后启用 TTS 功能,请按以下步骤操作:

  1. Actions 控制台中打开您的项目。
  2. 点击导航栏中的开发,并打开包含第一个 canvas 响应的提示。
  3. continue_tts_during_touch 设置为 true,如以下代码段所示:

      {
       "candidates": [
         {
           "canvas": {
             "url": "https://example-url.com",
             "continue_tts_during_touch": true
           }
         }
       ]
     }