构建 Web 应用 (Dialogflow)

Web 应用是使用 Interactive Canvas 的 Action 的界面。您可以使用现有网络技术(HTML、CSS 和 JavaScript)设计和开发 Web 应用。在大多数情况下,Interactive Canvas 能够像浏览器一样呈现 Web 内容,但为了保障用户隐私和安全,还存在一些强制执行的限制。在开始设计界面之前,请考虑 Design guidelines 部分中所述的设计原则。

您的 Web 应用的 HTML 和 JavaScript 将执行以下操作:

  • 注册 Interactive Canvas 事件callbacks
  • 初始化 Interactive Canvas JavaScript 库。
  • 提供用于根据状态更新 Web 应用的自定义逻辑。

本页面介绍了构建 Web 应用的推荐方法、如何在 Web 应用与执行方式之间实现通信,以及一般准则和限制。

虽然您可以使用任何方法构建界面,但 Google 建议您使用以下库:

架构

Google 强烈建议您使用单页应用架构。 这种方法可实现最佳性能,并支持持续对话的用户体验。Interactive Canvas 可与 VueAngularReact 等前端框架结合使用,这些框架有助于进行状态管理。

HTML 文件

HTML 文件定义界面的外观。此文件还会加载 Interactive Canvas JavaScript 库,该库支持您的 Web 应用与对话型 Action 之间的通信

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Immersive Canvas Sample</title>
    <!-- Disable favicon requests -->
    <link rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,">
    <!-- Load Interactive Canvas JavaScript -->
    <script src="https://www.gstatic.com/assistant/df-asdk/interactivecanvas/api/interactive_canvas.min.js"></script>
    <!-- Load PixiJS for graphics rendering -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.8.7/pixi.min.js"></script>
    <!-- Load Stats.js for fps monitoring -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js"></script>
    <!-- Load custom CSS -->
    <link rel="stylesheet" href="css/main.css">
  </head>
  <body>
    <div id="view" class="view">
      <div class="debug">
        <div class="stats"></div>
        <div class="logs"></div>
      </div>
    </div>
    <!-- Load custom JavaScript after elements are on page -->
    <script src="js/main.js"></script>
    <script src="js/log.js"></script>
  </body>
</html>

在执行方式和 Web 应用之间通信

现在,您已经构建了 Web 应用和执行方式,并已将其加载到 Web 应用文件的 Interactive Canvas 库中,接下来需要定义 Web 应用和执行方式的交互方式。为此,请修改包含 Web 应用逻辑的文件。

action.js

此文件包含用于定义callbacks并通过 interactiveCanvas 调用方法的代码。借助回调,您的 Web 应用可以响应来自对话 Action 的信息或请求,而方法可提供向对话 Action 发送信息或请求的方法。

interactiveCanvas.ready(callbacks); 添加到 HTML 文件中,以初始化和注册callbacks

//action.js
class Action {
  constructor(scene) {
    this.canvas = window.interactiveCanvas;
    this.scene = scene;
    const that = this;
    this.commands = {
      TINT: function(data) {
        that.scene.sprite.tint = data.tint;
      },
      SPIN: function(data) {
        that.scene.sprite.spin = data.spin;
      },
      RESTART_GAME: function(data) {
        that.scene.button.texture = that.scene.button.textureButton;
        that.scene.sprite.spin = true;
        that.scene.sprite.tint = 0x0000FF; // blue
        that.scene.sprite.rotation = 0;
      },
    };
  }

  /**
   * Register all callbacks used by Interactive Canvas
   * executed during scene creation time.
   *
   */
  setCallbacks() {
    const that = this;
    // declare interactive canvas callbacks
    const callbacks = {
      onUpdate(data) {
        try {
          that.commands[data.command.toUpperCase()](data);
        } catch (e) {
          // do nothing, when no command is sent or found
        }
      },
    };
    // called by the Interactive Canvas web app once web app has loaded to
    // register callbacks
    this.canvas.ready(callbacks);
  }
}

main.js

此文件用于为您的 Web 应用构建场景。在此示例中,它还会处理通过 sendTextQuery() 返回的 promise 的成功和失败情况。以下是摘自 main.js 的代码段:

// main.js
const view = document.getElementById('view');
// initialize rendering and set correct sizing
this.renderer = PIXI.autoDetectRenderer({
  transparent: true,
  antialias: true,
  resolution: this.radio,
  width: view.clientWidth,
  height: view.clientHeight,
});
view.appendChild(this.element);

// center stage and normalize scaling for all resolutions
this.stage = new PIXI.Container();
this.stage.position.set(view.clientWidth / 2, view.clientHeight / 2);
this.stage.scale.set(Math.max(this.renderer.width,
    this.renderer.height) / 1024);

// load a sprite from a svg file
this.sprite = PIXI.Sprite.from('triangle.svg');
this.sprite.anchor.set(0.5);
this.sprite.tint = 0x00FF00; // green
this.sprite.spin = true;
this.stage.addChild(this.sprite);

// toggle spin on touch events of the triangle
this.sprite.interactive = true;
this.sprite.buttonMode = true;
this.sprite.on('pointerdown', () => {
  this.sprite.spin = !this.sprite.spin;
});

支持触摸交互

您的 Interactive Canvas Action 可以响应用户的触摸及其语音输入。根据 Interactive Canvas 设计指南,您应将 Action 开发为“语音优先”。不过,有些智能显示屏支持触摸互动。

支持触摸类似于支持对话响应;不过,客户端 JavaScript 会查找触摸互动并使用这些互动更改 Web 应用中的元素,而不是用户的语音响应。

您可以在使用 Pixi.js 库的示例中查看以下示例:

...
this.sprite = PIXI.Sprite.from('triangle.svg');
...
this.sprite.interactive = true; // Enables interaction events
this.sprite.buttonMode = true; // Changes `cursor` property to `pointer` for PointerEvent
this.sprite.on('pointerdown', () => {
  this.sprite.spin = !this.sprite.spin;
});
...

在这种情况下,spin 变量的值作为 update 回调通过 interactiveCanvas API 发送。执行方式具有根据 spin 的值触发 intent 的逻辑。

...
app.intent('pause', (conv) => {
  conv.ask(`Ok, I paused spinning. What else?`);
  conv.ask(new HtmlResponse({
    data: {
      spin: false,
    },
  }));
});
...

添加更多功能

您已经了解了基础知识,现在您可以使用 Canvas 专用 API 增强和自定义 Action 了。本部分介绍了如何在 Interactive Canvas Action 中实现这些 API。

sendTextQuery()

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

//main.js
...
that.action.canvas.sendTextQuery('Restart game')
    .then((res) => {
      if (res.toUpperCase() === 'SUCCESS') {
        console.log(`Request in flight: ${res}`);
        that.button.texture = that.button.textureButtonDisabled;
        that.sprite.spin = false;
      } else {
        console.log(`Request in flight: ${res}`);
      }
    });
...

如果 promise 导致 SUCCESS,则 Restart game intent 会向您的 Web 应用发送 HtmlResponse

//index.js
...
app.intent('restart game', (conv) => {
  conv.ask(new HtmlResponse({
    data: {
      command: 'RESTART_GAME',
    },
...

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

//action.js
...
RESTART_GAME: function(data) {
  that.scene.button.texture = that.scene.button.textureButton;
  that.scene.sprite.spin = true;
  that.scene.sprite.tint = 0x0000FF; // blue
  that.scene.sprite.rotation = 0;
},
...

OnTtsMark()

当您在针对用户的 SSML 响应中添加具有唯一名称的 <mark> 标记时,系统会调用 OnTtsMark() 回调。在 Snowman 示例的以下摘录中,OnTtsMark() 会将 Web 应用的动画与相应的 TTS 输出同步。当 Action 向用户说出“Sorry, you missing”时,Web 应用会读出正确的单词并向用户显示相应字母。

intent Game Over Reveal Word 会在用户游戏失败时在响应中添加一个自定义标记:

//index.js
...
app.intent('Game Over Reveal Word', (conv, {word}) => {
  conv.ask(`<speak>Sorry, you lost.<mark name="REVEAL_WORD"/> The word is ${word}.` +
    `${PLAY_AGAIN_INSTRUCTIONS}</speak>`);
  conv.ask(new HtmlResponse());
});
...

然后,以下代码段会注册 OnTtsMark() 回调,检查该标记的名称,并执行 revealCorrectWord() 函数,该函数会更新 Web 应用:

//action.js
...
setCallbacks() {
  const that = this;
  // declare assistant canvas action callbacks
  const callbacks = {
    onTtsMark(markName) {
      if (markName === 'REVEAL_WORD') {
        // display the correct word to the user
        that.revealCorrectWord();
      }
    },
...

限制

在开发 Web 应用时,请考虑以下限制:

  • 没有 cookie
  • 无本地存储空间
  • 无地理定位
  • 不使用摄像头
  • 无弹出式窗口
  • 不要超过 200 MB 的内存限制
  • 第三方标头占据了屏幕的上半部分
  • 无法为视频应用任何样式
  • 一次只能使用一个媒体元素
  • 无 HLS 视频
  • 没有 Web SQL 数据库
  • 不支持 Web Speech APISpeechRecognition 接口。
  • 无音频或视频录制
  • 深色模式设置不适用

跨源资源共享

由于 Interactive Canvas Web 应用托管在 iframe 中,并且来源设置为 null,因此您必须为 Web 服务器和存储资源启用跨域资源共享 (CORS)。这样,您的资源就可以接受来自 null 源的请求。