构建 Web 应用 (Dialogflow)

Web 应用是使用 Interactive Canvas 的 Action 的界面。您可以使用 现有的网络技术(HTML、CSS 和 JavaScript)来设计和开发 您的 Web 应用。Interactive Canvas 在大多数情况下能够渲染 Web 内容,例如 但浏览器会强制实施一些限制 用户隐私和安全。在开始设计界面之前,请考虑 Design guidelines 中列出的设计原则 部分。

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

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

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

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

架构

Google 强烈建议使用单页应用架构。 这种方法可实现最佳性能,并支持持续 对话式用户体验。Interactive Canvas 可以与 Vue 等前端框架, AngularReact, 帮助管理状态

HTML 文件

HTML 文件定义了界面的外观。此文件还会加载 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 应用文件,您需要定义 Web 应用和执行方式的交互方式。接收者 为此,请修改包含 Web 应用逻辑的文件。

action.js

此文件包含定义 回调 并调用方法 直到 interactiveCanvas。回调可让你的 Web 应用 通过对话型 Action 提供信息或请求,而方法 提供一种向对话型 Action 发送信息或请求的方式。

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

//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 变量的值通过 interactiveCanvas API 作为 update 回调。该执行方式具有逻辑 (基于 spin 的值触发 intent)。

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

添加更多功能

至此你已经了解了基础知识,现在可以改进和自定义你的 Action 。本部分介绍了如何实现这些 API 。

sendTextQuery()

sendTextQuery() 方法向对话型 Action 发送文本查询 以编程方式调用 intent。此示例使用 sendTextQuery() 来 在用户点击按钮时重新开始旋转三角形游戏。当用户 请点击“重新开始游戏”按钮,sendTextQuery() 会调用 Restart game intent 并返回 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 导致 SUCCESSRestart game intent 会发送 HtmlResponse 添加到您的 Web 应用:

//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()

当您在OnTtsMark()<mark> SSML 响应。以下摘录自雪人示例OnTtsMark() 将 Web 应用的动画与相应 TTS 同步 输出。当该 Action 对用户说 Sorry, youlow 时,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 数据库
  • 不支持 SpeechRecognition 接口的 Web Speech API
  • 无法录制音频或视频
  • 深色模式设置不适用

跨域资源共享

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