建構網頁應用程式 (Dialogflow)

網頁應用程式是指使用互動式畫布動作的使用者介面。您可以使用現有的網頁技術 (HTML、CSS 和 JavaScript) 設計及開發網頁應用程式。大多數情況下,互動式畫布都能轉譯網頁內容,例如瀏覽器,但有使用者隱私和安全方面的一些限制。開始設計 UI 之前,請考量 Design guidelines 一節所述的設計原則。

網頁應用程式的 HTML 和 JavaScript 會提供以下功能:

  • 註冊互動式畫布事件callbacks
  • 初始化互動式 Canvas JavaScript 程式庫。
  • 提供用於根據狀態更新網頁應用程式的自訂邏輯。

本頁將說明建構網頁應用程式的建議做法、如何啟用網頁應用程式和執行要求之間的通訊,以及一般指南和限制。

雖然您可以使用任何方法建構 UI,但 Google 建議您使用下列程式庫:

架構

Google 強烈建議您使用單頁應用程式架構。這個方法可達到最佳效能,並支援連續的使用者體驗。互動式畫布可與 VueAngularReact 等前端架構搭配使用,藉此協助管理狀態。

HTML 檔案

HTML 檔案會定義使用者介面的外觀。這個檔案也會載入互動式畫布 JavaScript 程式庫,以便在網頁應用程式和對話動作之間進行通訊

<!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>

在執行要求和網頁應用程式之間進行通訊

現在您已建構網頁應用程式和執行要求,並在網頁應用程式的互動式畫布程式庫中載入,接著您需要定義網頁應用程式和執行要求的互動方式。方法是修改包含網頁應用程式邏輯的檔案。

action.js

這個檔案包含用於定義callbacks及透過 interactiveCanvas 叫用方法的程式碼。回呼可讓網頁應用程式回應「來自」對話動作的資訊或要求,而方法則提供傳送資訊或要求「傳送至」對話動作的方式。

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

這個檔案會建構網頁應用程式的情境。在這個範例中,您還可以處理 sendTextQuery() 回傳的成功和失敗情況。以下是 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;
});

支援觸控互動

互動式畫布動作可以回應使用者的觸控動作和使用者的語音輸入內容。根據互動畫布設計指南,您應將動作開發為「語音優先」。不過,有些智慧螢幕支援觸控互動。

輔助觸控與支援對話回應類似,不過用戶端 JavaScript 會尋找觸控互動,並使用此類互動變更網頁應用程式的元素。

您可以在範例中查看使用 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 值觸發意圖的邏輯。

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

新增其他功能

現在您已經瞭解基本知識,可以使用 Canvas 專屬 API 強化及自訂動作。本節說明如何在互動式畫布動作中實作這些 API。

sendTextQuery()

sendTextQuery() 方法會將文字查詢傳送至對話動作,以程式輔助方式叫用意圖。這個範例使用 sendTextQuery(),可在使用者點選按鈕時重新啟動旋轉的三角形遊戲。當使用者按一下「重新啟動遊戲」按鈕時,sendTextQuery() 會呼叫 Restart game 意圖並傳回承諾。如果觸發意圖,這個承諾會於 SUCCESS 中產生;如未觸發,則會產生 BLOCKED。下列程式碼片段會觸發意圖,並處理承諾的成功和失敗情況:

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

如果 SUCCESS 中的承諾產生結果,Restart game 意圖會傳送 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() 回呼。在以下雪人範例摘錄中,OnTtsMark() 會將網頁應用程式的動畫與對應的 TTS 輸出內容同步處理。當動作向使用者說明「很抱歉,你遺失」時,網頁應用程式會拼出正確的字詞,並向使用者顯示字母。

使用者遺失遊戲時,意圖 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() 函式來更新網頁應用程式:

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

限制

開發網頁應用程式時,請將下列限制納入考量:

  • 沒有 Cookie
  • 沒有本機儲存空間
  • 無地理位置
  • 未使用攝影機
  • 不顯示彈出式視窗
  • 不要超過 200 MB 的記憶體限制
  • 第三方標頭佔據畫面的上方部分
  • 無法將任何樣式套用至影片
  • 一次只能使用一個媒體元素
  • 沒有 HTTP 即時串流影片
  • 沒有網路 SQL 資料庫
  • 不支援 Web Speech APISpeechRecognition 介面。
  • 沒有錄音或錄影
  • 深色模式設定不適用

跨源資源共享

由於互動式 Canvas 網頁應用程式是在 iframe 中代管,且來源設為空值,因此您必須為網路伺服器和儲存空間資源啟用跨源資源共享 (CORS)。這樣您的資產就能接受來自空值來源的要求。