在資訊卡中新增互動式 UI 元素

本頁說明如何在資訊卡中加入小工具和 UI 元素,讓使用者可以與 Google Chat 應用程式互動,例如按一下按鈕或提交資訊。

Chat 應用程式可以使用下列 Chat 介面建立互動式資訊卡:

  • 含有一或多張資訊卡的訊息
  • 首頁:這是在 Chat 應用程式即時訊息的「首頁」分頁中顯示的資訊卡。
  • 對話方塊:在訊息和首頁中開啟新視窗的資訊卡。

當使用者與資訊卡互動時,即時通訊應用程式可以使用接收到的資料來處理並據以回應。詳情請參閱「收集並處理 Google Chat 使用者的資訊」。


使用資訊卡建構工具設計及預覽 Chat 應用程式的訊息和使用者介面:

開啟資訊卡建立工具

必要條件

已啟用互動功能的 Google Chat 應用程式。如要建立互動式 Chat 應用程式,請根據要使用的應用程式架構,完成下列其中一個快速入門:

新增按鈕

ButtonList 小工具會顯示一組按鈕。按鈕可以顯示文字、圖示,或同時顯示文字和圖示。每個 Button 都支援使用者點選按鈕時發生的 OnClick 動作。例如:

  • 使用 OpenLink 開啟超連結,為使用者提供更多資訊。
  • 執行可執行自訂函式 (例如呼叫 API) 的 action

按鈕支援替代文字,以便無障礙使用。

新增可執行自訂函式的按鈕

以下資訊卡包含包含兩個按鈕的 ButtonList 小工具。按一下這個按鈕,即可在新分頁中開啟 Google Chat 開發人員說明文件。另一個按鈕會執行名為 goToView() 的自訂函式,並傳遞 viewType="BIRD EYE VIEW" 參數。

新增 Material Design 風格的按鈕

以下為採用不同質感設計按鈕樣式的一組按鈕。

如要套用 Material Design 樣式,請勿加入「color」屬性。

新增含有自訂顏色和停用的按鈕

您可以透過設定 "disabled": "true" 來避免使用者點選按鈕。

以下顯示的卡片包含一個 ButtonList 小工具,其中有兩個按鈕。其中一個按鈕會使用 Color 欄位自訂按鈕的背景顏色。另一個按鈕則會使用 Disabled 欄位停用,以免使用者按下按鈕並執行函式。

新增有圖示的按鈕

以下顯示包含 ButtonList 小工具和兩個圖示 Button 小工具的資訊卡。一個按鈕使用 knownIcon 欄位,顯示 Google Chat 內建的電子郵件圖示。另一個按鈕會使用 iconUrl 欄位顯示自訂圖示小工具

新增含有圖示和文字的按鈕

以下顯示的資訊卡包含 ButtonList 小工具,可提示使用者傳送電子郵件。第一個按鈕會顯示電子郵件圖示,第二個按鈕則會顯示文字。使用者可以按一下圖示或文字按鈕執行 sendEmail 函式。

自訂可摺疊區段的按鈕

自訂控制按鈕,用於在資訊卡中收合和展開各個部分。從多種圖示或圖片中選擇,以視覺化方式呈現該部分的內容,方便使用者瞭解並與資訊互動。

新增溢位選單

Overflow menu 可用於 Chat 資訊卡,提供其他選項和動作。這項功能可讓您加入更多選項,同時不讓資訊卡介面顯得雜亂,確保設計簡潔有條理。

新增 Chips 清單

ChipList 小工具提供多用途且視覺效果出色的資訊顯示方式。使用方塊清單來代表標記、類別或其他相關資料,方便使用者瀏覽及與內容互動。

向使用者收集資訊

本節說明如何新增可收集資訊的資訊方塊,例如文字或選項。

如要瞭解如何處理使用者輸入的內容,請參閱「收集並處理 Google Chat 使用者的資訊」一文。

收集文字

TextInput 小工具提供可讓使用者輸入文字的欄位。小工具支援建議功能,可協助使用者輸入統一資料,以及變更時的動作,也就是在文字輸入欄位發生變更時執行的 Actions,例如使用者新增或刪除文字。

如需從使用者收集抽象或未知的資料,請使用這個 TextInput 小工具。如要收集使用者定義的資料,請改用 SelectionInput 小工具。

以下是包含 TextInput 小工具的資訊卡:

收集日期或時間

DateTimePicker 小工具可讓使用者輸入日期、時間,或同時輸入日期和時間。或者,使用者也可以使用挑選器選取日期和時間。如果使用者輸入無效的日期或時間,挑選器會顯示錯誤訊息,提示使用者正確輸入資訊。

以下顯示一張卡片,其中包含三種不同類型的 DateTimePicker 小工具:

讓使用者選取項目

SelectionInput 小工具提供一組可選項目,例如核取方塊、圓形按鈕、切換鈕或下拉式選單。您可以使用這個小工具,向使用者收集已定義且標準化的資料。如要從使用者收集未定義的資料,請改用 TextInput 小工具。

SelectionInput 小工具支援建議,可協助使用者輸入一致的資料,以及變更時的動作,也就是在選取輸入欄位發生變更時執行的 Actions,例如使用者選取或取消選取項目。

Chat 應用程式可以接收及處理所選項目的值。如要進一步瞭解如何使用表單輸入,請參閱「處理使用者輸入的資訊」。

本節提供使用 SelectionInput 小工具的資訊卡範例。這些範例使用不同類型的區段輸入:

新增核取方塊

下圖顯示了一張資訊卡,要求使用者指定聯絡人是專業或個人,還是兩者皆用,並提供一個使用核取方塊的 SelectionInput 小工具:

新增單選按鈕

以下資訊卡會使用使用者使用單選按鈕的 SelectionInput 小工具,詢問使用者要指定聯絡人是職業還是私人:

新增切換鈕

下圖顯示了一張資訊卡,要求使用者透過 SelectionInput 小工具使用切換按鈕,指定聯絡人是專業或私人人士,還是兩者並用:

下圖顯示了一張資訊卡,要求使用者透過 SelectionInput 小工具,使用下拉式選單指定聯絡人是專業或個人:

新增多重選取選單

以下資訊卡會要求使用者從多重選取選單中選取聯絡人:

您可以透過 Google Workspace 的下列資料來源,為複選選單填入項目:

  • Google Workspace 使用者:您只能填入相同 Google Workspace 機構的使用者。
  • Chat 聊天室:在多重選取選單中輸入項目的使用者,只能查看及選取自己所屬 Google Workspace 機構中的聊天室。

如要使用 Google Workspace 資料來源,請指定 platformDataSource 欄位。與其他選項輸入類型不同,您可以省略 SectionItem 物件,因為這些選項項目會動態取自 Google Workspace。

以下程式碼會顯示 Google Workspace 使用者的多重選取選單。如要填入使用者,請在選取輸入內容中將 commonDataSource 設為 USER

JSON

{
  "selectionInput": {
    "name": "contacts",
    "type": "MULTI_SELECT",
    "label": "Selected contacts",
    "multiSelectMaxSelectedItems": 5,
    "multiSelectMinQueryLength": 1,
    "platformDataSource": {
      "commonDataSource": "USER"
    }
  }
}

下列程式碼顯示 Chat 聊天室的複選選單。如要填入空格,選項的輸入內容會指定 hostAppDataSource 欄位。多重選取選單也會將 defaultToCurrentSpace 設為 true,讓目前的聊天室成為選單中的預設選項:

JSON

{
  "selectionInput": {
    "name": "spaces",
    "type": "MULTI_SELECT",
    "label": "Selected contacts",
    "multiSelectMaxSelectedItems": 3,
    "multiSelectMinQueryLength": 1,
    "platformDataSource": {
      "hostAppDataSource": {
        "chatDataSource": {
          "spaceDataSource": {
            "defaultToCurrentSpace": true
          }
        }
      }
    }
  }
}

多重選取選單也可以從第三方或外部資料來源填入項目。舉例來說,您可以使用複選選單,讓使用者從客戶關係管理 (CRM) 系統中的銷售待開發客戶清單中,選取項目。

如要使用外部資料來源,請使用 externalDataSource 欄位指定可從資料來源傳回項目的函式。

為減少對外部資料來源的要求,您可以納入建議項目,讓這些項目在使用者在選單中輸入內容前,就會顯示在多重選取選單中。舉例來說,您可以填入最近搜尋的使用者聯絡人。如要從外部資料來源填入建議項目,請指定 SelectionItem 物件。

以下程式碼會顯示使用者外部聯絡人組合的多重選取選單。選單預設會顯示一個聯絡人,並執行 getContacts 函式,從外部資料來源擷取並填入項目:

Node.js

node/selection-input/index.js
selectionInput: {
  name: "contacts",
  type: "MULTI_SELECT",
  label: "Selected contacts",
  multiSelectMaxSelectedItems: 3,
  multiSelectMinQueryLength: 1,
  externalDataSource: { function: "getContacts" },
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  items: [getContact("3")]
}

Python

python/selection-input/main.py
'selectionInput': {
  'name': "contacts",
  'type': "MULTI_SELECT",
  'label': "Selected contacts",
  'multiSelectMaxSelectedItems': 3,
  'multiSelectMinQueryLength': 1,
  'externalDataSource': { 'function': "getContacts" },
  # Suggested items loaded by default.
  # The list is static here but it could be dynamic.
  'items': [get_contact("3")]
}

Java

java/selection-input/src/main/java/com/google/chat/selectionInput/App.java
.setSelectionInput(new GoogleAppsCardV1SelectionInput()
  .setName("contacts")
  .setType("MULTI_SELECT")
  .setLabel("Selected contacts")
  .setMultiSelectMaxSelectedItems(3)
  .setMultiSelectMinQueryLength(1)
  .setExternalDataSource(new GoogleAppsCardV1Action().setFunction("getContacts"))
  .setItems(List.of(getContact("3")))))))))));

Apps Script

apps-script/selection-input/selection-input.gs
selectionInput: {
  name: "contacts",
  type: "MULTI_SELECT",
  label: "Selected contacts",
  multiSelectMaxSelectedItems: 3,
  multiSelectMinQueryLength: 1,
  externalDataSource: { function: "getContacts" },
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  items: [getContact("3")]
}

針對外部資料來源,您也可以在多重選取選單中,自動完成使用者開始輸入的項目。舉例來說,如果使用者開始在選單中輸入 Atl,以便填入美國的城市,Chat 應用程式可以在使用者輸入完畢前,自動建議 Atlanta。最多可自動完成 100 個項目。

如要自動完成項目,您可以建立函式,讓系統在使用者在多重選取選單中輸入內容時,查詢外部資料來源並傳回項目。這個函式必須執行以下操作:

  • 傳遞代表使用者與選單互動的事件物件。
  • 確認互動事件的 invokedFunction 值與 externalDataSource 欄位的函式相符。
  • 當函式相符時,會從外部資料來源傳回建議項目。如要根據使用者輸入內容建議項目,請取得 autocomplete_widget_query 鍵的值。這個值代表使用者在選單中輸入的內容。

以下程式碼會自動完成外部資料資源中的項目。在上例中,Chat 應用程式會根據觸發 getContacts 函式的時間來建議項目:

Node.js

node/selection-input/index.js
/**
 * Responds to a WIDGET_UPDATE event in Google Chat.
 *
 * @param {Object} event The event object from Chat API.
 * @return {Object} Response from the Chat app.
 */
function onWidgetUpdate(event) {
  if (event.common["invokedFunction"] === "getContacts") {
    const query = event.common.parameters["autocomplete_widget_query"];
    return { actionResponse: {
      type: "UPDATE_WIDGET",
      updatedWidget: { suggestions: { items: [
        // The list is static here but it could be dynamic.
        getContact("1"), getContact("2"), getContact("3"), getContact("4"), getContact("5")
      // Only return items based on the query from the user
      ].filter(e => !query || e.text.includes(query))}}
    }};
  }
}

/**
 * Generate a suggested contact given an ID.
 *
 * @param {String} id The ID of the contact to return.
 * @return {Object} The contact formatted as a suggested item for selectors.
 */
function getContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}

Python

python/selection-input/main.py
def on_widget_update(event: dict) -> dict:
  """Responds to a WIDGET_UPDATE event in Google Chat."""
  if "getContacts" == event.get("common").get("invokedFunction"):
    query = event.get("common").get("parameters").get("autocomplete_widget_query")
    return { 'actionResponse': {
      'type': "UPDATE_WIDGET",
      'updatedWidget': { 'suggestions': { 'items': list(filter(lambda e: query is None or query in e["text"], [
        # The list is static here but it could be dynamic.
        get_contact("1"), get_contact("2"), get_contact("3"), get_contact("4"), get_contact("5")
      # Only return items based on the query from the user
      ]))}}
    }}


def get_contact(id: str) -> dict:
  """Generate a suggested contact given an ID."""
  return {
    'value': id,
    'startIconUri': "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    'text': "Contact " + id
  }

Java

java/selection-input/src/main/java/com/google/chat/selectionInput/App.java
// Responds to a WIDGET_UPDATE event in Google Chat.
Message onWidgetUpdate(JsonNode event) {
  if ("getContacts".equals(event.at("/invokedFunction").asText())) {
    String query = event.at("/common/parameters/autocomplete_widget_query").asText();
    return new Message().setActionResponse(new ActionResponse()
      .setType("UPDATE_WIDGET")
      .setUpdatedWidget(new UpdatedWidget()
        .setSuggestions(new SelectionItems().setItems(List.of(
          // The list is static here but it could be dynamic.
          getContact("1"), getContact("2"), getContact("3"), getContact("4"), getContact("5")
        // Only return items based on the query from the user
        ).stream().filter(e -> query == null || e.getText().indexOf(query) > -1).toList()))));
  }
  return null;
}

// Generate a suggested contact given an ID.
GoogleAppsCardV1SelectionItem getContact(String id) {
  return new GoogleAppsCardV1SelectionItem()
    .setValue(id)
    .setStartIconUri("https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png")
    .setText("Contact " + id);
}

Apps Script

apps-script/selection-input/selection-input.gs
/**
 * Responds to a WIDGET_UPDATE event in Google Chat.
 *
 * @param {Object} event The event object from Chat API.
 * @return {Object} Response from the Chat app.
 */
function onWidgetUpdate(event) {
  if (event.common["invokedFunction"] === "getContacts") {
    const query = event.common.parameters["autocomplete_widget_query"];
    return { actionResponse: {
      type: "UPDATE_WIDGET",
      updatedWidget: { suggestions: { items: [
        // The list is static here but it could be dynamic.
        getContact("1"), getContact("2"), getContact("3"), getContact("4"), getContact("5")
      // Only return items based on the query from the user
      ].filter(e => !query || e.text.includes(query))}}
    }};
  }
}

/**
 * Generate a suggested contact given an ID.
 *
 * @param {String} id The ID of the contact to return.
 * @return {Object} The contact formatted as a suggested item for selectors.
 */
function getContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}

驗證輸入至資訊卡的資料

本頁面說明如何驗證輸入至資訊卡 action 和小工具的資料。舉例來說,您可以驗證文字輸入欄位是否有使用者輸入的文字,或是否含有特定數量的字元。

設定動作所需的小工具

在資訊卡的 action 中,將動作所需的小工具名稱新增至 requiredWidgets 清單。

如果在叫用此動作時,這裡列出的任何小工具都沒有值,則表單動作提交作業會取消。

為動作設定 "all_widgets_are_required": "true" 時,此動作會要求資訊卡中的所有小工具。

在多重選取中設定 all_widgets_are_required 動作

JSON

{
  "sections": [
    {
      "header": "Select contacts",
      "widgets": [
        {
          "selectionInput": {
            "type": "MULTI_SELECT",
            "label": "Selected contacts",
            "name": "contacts",
            "multiSelectMaxSelectedItems": 3,
            "multiSelectMinQueryLength": 1,
            "onChangeAction": {
              "all_widgets_are_required": true
            },
            "items": [
              {
                "value": "contact-1",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 1",
                "bottomText": "Contact one description",
                "selected": false
              },
              {
                "value": "contact-2",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 2",
                "bottomText": "Contact two description",
                "selected": false
              },
              {
                "value": "contact-3",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 3",
                "bottomText": "Contact three description",
                "selected": false
              },
              {
                "value": "contact-4",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 4",
                "bottomText": "Contact four description",
                "selected": false
              },
              {
                "value": "contact-5",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 5",
                "bottomText": "Contact five description",
                "selected": false
              }
            ]
          }
        }
      ]
    }
  ]
}
在 dateTimePicker 中設定 all_widgets_are_required 動作

JSON

{
  "sections": [
    {
      "widgets": [
        {
          "textParagraph": {
            "text": "A datetime picker widget with both date and time:"
          }
        },
        {
          "divider": {}
        },
        {
          "dateTimePicker": {
            "name": "date_time_picker_date_and_time",
            "label": "meeting",
            "type": "DATE_AND_TIME"
          }
        },
        {
          "textParagraph": {
            "text": "A datetime picker widget with just date:"
          }
        },
        {
          "divider": {}
        },
        {
          "dateTimePicker": {
            "name": "date_time_picker_date_only",
            "label": "Choose a date",
            "type": "DATE_ONLY",
            "onChangeAction":{
              "all_widgets_are_required": true
            }
          }
        },
        {
          "textParagraph": {
            "text": "A datetime picker widget with just time:"
          }
        },
        {
          "divider": {}
        },
        {
          "dateTimePicker": {
            "name": "date_time_picker_time_only",
            "label": "Select a time",
            "type": "TIME_ONLY"
          }
        }
      ]
    }
  ]
}
在下拉式選單中設定 all_widgets_are_required 動作

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 1,
      "widgets": [
        {
          "selectionInput": {
            "name": "location",
            "label": "Select Color",
            "type": "DROPDOWN",
            "onChangeAction": {
              "all_widgets_are_required": true
            },
            "items": [
              {
                "text": "Red",
                "value": "red",
                "selected": false
              },
              {
                "text": "Green",
                "value": "green",
                "selected": false
              },
              {
                "text": "White",
                "value": "white",
                "selected": false
              },
              {
                "text": "Blue",
                "value": "blue",
                "selected": false
              },
              {
                "text": "Black",
                "value": "black",
                "selected": false
              }
            ]
          }
        }
      ]
    }
  ]
}

設定文字輸入小工具的驗證機制

textInput 小工具的驗證欄位中,可以指定文字輸入小工具的字元限制和輸入類型。

設定文字輸入小工具的字元限制

JSON

{
  "sections": [
    {
      "header": "Tell us about yourself",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 2,
      "widgets": [
        {
          "textInput": {
            "name": "favoriteColor",
            "label": "Favorite color",
            "type": "SINGLE_LINE",
            "validation": {"character_limit":15},
            "onChangeAction":{
              "all_widgets_are_required": true
            }
          }
        }
      ]
    }
  ]
}
設定文字輸入小工具的輸入類型

JSON

{
  "sections": [
    {
      "header": "Validate text inputs by input types",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 2,
      "widgets": [
        {
          "textInput": {
            "name": "mailing_address",
            "label": "Please enter a valid email address",
            "type": "SINGLE_LINE",
            "validation": {
              "input_type": "EMAIL"
            },
            "onChangeAction": {
              "all_widgets_are_required": true
            }
          }
        },
        {
          "textInput": {
            "name": "validate_integer",
            "label": "Please enter a number",
              "type": "SINGLE_LINE",
            "validation": {
              "input_type": "INTEGER"
            }
          }
        },
        {
          "textInput": {
            "name": "validate_float",
            "label": "Please enter a number with a decimal",
            "type": "SINGLE_LINE",
            "validation": {
              "input_type": "FLOAT"
            }
          }
        }
      ]
    }
  ]
}

疑難排解

當 Google Chat 應用程式或資訊卡傳回錯誤時,Chat 介面會顯示「發生錯誤」的訊息。或「無法處理您的要求」。Chat UI 有時不會顯示任何錯誤訊息,但 Chat 應用程式或資訊卡卻產生非預期的結果,例如資訊卡可能不會顯示。

雖然 Chat UI 可能不會顯示錯誤訊息,但只要開啟 Chat 應用程式的錯誤記錄功能,您就能取得描述性錯誤訊息和記錄資料,協助修正錯誤。如需查看、偵錯及修正錯誤的相關說明,請參閱「排解及修正 Google Chat 錯誤」一文。