向卡片添加互动式界面元素

本页介绍了如何向卡片添加 widget 和界面元素,以便用户与您的 Google Chat 应用互动,例如点击按钮或提交信息。

聊天应用可以使用以下聊天界面来构建互动式卡片:

  • 包含一个或多个卡片的消息。
  • 首页:一种卡片,显示在与 Chat 应用的私信中的首页标签页中。
  • 对话框:从消息和首页在新窗口中打开的卡片。

当用户与卡片互动时,Chat 应用可以使用收到的数据进行处理并做出相应回应。如需了解详情,请参阅收集和处理 Google Chat 用户的信息。


使用卡片构建器设计和预览 Chat 应用的消息和界面:

打开卡片构建器

前提条件

一种 Google Chat 应用,配置为接收和响应用户互动。如需创建交互式 Chat 应用,请根据要使用的应用架构完成以下快速入门教程之一:

添加按钮

ButtonList widget 会显示一组按钮。按钮可以显示文字、图标,或者同时显示文字和图标。每个 Button 都支持用户点击按钮时发生的 OnClick 操作。例如:

  • 使用 OpenLink 打开超链接,以便为用户提供更多信息。
  • 运行一个action,该命令会运行自定义函数,就像调用 API 一样。

为了方便残障人士使用,按钮支持替代文本。

添加一个用于运行自定义函数的按钮

以下卡片包含一个带有两个按钮的 ButtonList widget。 一个按钮可在新标签页中打开 Google Chat 开发者文档。另一个按钮运行名为 goToView() 的自定义函数,并传递 viewType="BIRD EYE VIEW" 参数。

添加具有 Material Design 样式的按钮

以下显示了一组采用不同 Material Design 按钮样式的按钮。

如需应用 Material Design 样式,请勿添加“color”属性。

添加了具有自定义颜色和已停用的按钮

您可以通过设置 "disabled": "true" 来禁止用户点击按钮。

以下显示了一张包含一个 ButtonList widget 和两个按钮的卡片。一个按钮使用 Color 字段自定义按钮的背景颜色。另一个按钮通过 Disabled 字段停用,这可防止用户点击该按钮并执行相应功能。

添加带图标的按钮

以下显示了一张卡片,其中包含一个 ButtonList widget 和两个图标 Button widget。一个按钮使用 knownIcon 字段来显示 Google Chat 的内置电子邮件图标。另一个按钮使用 iconUrl 字段显示自定义图标 widget。

添加带有图标和文字的按钮

以下显示了一张卡片,其中包含一个 ButtonList widget,用于提示用户发送电子邮件。第一个按钮显示电子邮件图标,第二个按钮显示文字。用户可以点击图标或文本按钮来运行 sendEmail 函数。

自定义可折叠部分的按钮

自定义用于收起和展开卡片内各部分的控制按钮。您可以从一系列图标或图片中进行选择,以直观地呈现相应部分的内容,从而让用户更轻松地理解和互动。

添加溢出菜单

Overflow menu 可在 Chat 卡片中使用,以提供其他选项和操作。这样一来,您就可以添加更多选项,而不会使卡片的界面显得杂乱无章,从而确保设计简洁有序。

添加条状标签列表

ChipList widget 提供了一种用途广泛且极具视觉吸引力的信息显示方式。使用条状标签列表来表示标签、类别或其他相关数据,让用户更轻松地浏览和互动您的内容。

收集用户的信息

本部分介绍了如何添加用于收集信息(例如文本或选择)的 widget。

如需了解如何处理用户输入的内容,请参阅收集和处理 Google Chat 用户的信息。

收集文本

TextInput widget 提供了一个可供用户输入文本的字段。该 widget 支持建议(可帮助用户输入统一数据)和 on-change 操作(即在文本输入字段中发生更改时运行的 Actions,例如用户添加或删除文本)。

当您需要从用户那里收集抽象或未知数据时,请使用此 TextInput widget。如需从用户那里收集已定义的数据,请改用 SelectionInput widget。

以下是一个包含 TextInput widget 的卡片:

收集日期或时间

借助 DateTimePicker widget,用户可以输入日期、时间或同时输入日期和时间。或者,用户可以使用选择器选择日期和时间。如果用户输入了无效的日期或时间,选择器会显示一条错误消息,提示用户正确输入信息。

以下显示了一张包含三种不同类型 DateTimePicker widget 的卡片:

允许用户选择商品

SelectionInput widget 提供了一组可选择的项,例如复选框、单选按钮、开关或下拉菜单。您可以使用此 widget 从用户那里收集已定义且标准化的数据。如需从用户处收集未定义的数据,请改用 TextInput widget。

SelectionInput widget 支持建议(可帮助用户输入统一的数据)和 on-change 操作(即 Actions,当选择输入字段发生更改时运行,例如用户选择或取消选择某个项)。

聊天应用可以接收和处理所选商品的值。 如需详细了解如何处理表单输入,请参阅处理用户输入的信息。

本部分提供了使用 SelectionInput widget 的卡片示例。这些示例使用了不同类型的部分输入:

添加复选框

以下显示了一张卡片,其中包含一个使用复选框的 SelectionInput widget,用于询问用户联系人是专业联系人、个人联系人还是两者兼而有之:

添加单选按钮

以下显示了一张卡片,其中包含一个使用单选按钮的 SelectionInput widget,用于询问用户联系人是专业联系人还是个人联系人:

添加开关

下图显示了一张卡片,其中包含一个使用开关的 SelectionInput widget,用于询问用户联系人是专业联系人、个人联系人还是两者兼而有之:

以下显示了一张卡片,其中包含一个使用下拉菜单的 SelectionInput widget,用于询问用户联系人是专业联系人还是个人联系人:

动态填充下拉菜单

适用于 Google Chat 应用。

您可以从 Google Workspace 中的数据源或外部数据源动态填充下拉菜单中的项。如需使用动态数据源,请指定 data_source_configs 字段,该字段是一个 DataSourceConfig 对象数组。每个 DataSourceConfig 可以包含 platformDataSource 或 remoteDataSource。目前仅支持一个 DataSourceConfig。

填充 Google Workspace 中的内容

如需填充来自 Google Workspace 数据源(例如 Google Workspace 用户)的项,您需要在 DataSourceConfig 中指定 platformDataSource 字段。与使用静态 items 不同,您无需使用 SelectionItem 对象,因为这些选择项是从 Google Workspace 动态获取的。

以下代码显示了一个用于填充 Google Workspace 用户的下拉菜单:

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "widgets": [
        {
          "selectionInput": {
            "name": "contacts",
            "type": "DROPDOWN",
            "label": "Select contact from organization",
            "data_source_configs": [
              {
                "platformDataSource": {
                  "commonDataSource": "USER"
                },
                "min_characters_trigger": 1
              }
            ]
          }
        }
      ]
    }
  ]
}
从外部数据源填充商品

如需填充来自第三方或外部数据源(例如客户关系管理 [CRM] 系统)的商品,您可以使用 DataSourceConfig 中的 remoteDataSource 字段来指定一个从数据源返回商品的函数。

以下代码展示了一个下拉菜单,该菜单通过运行函数 getCrmLeads 从一组外部联系人填充项:

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "widgets": [
        {
          "selectionInput": {
            "name": "crm_leads",
            "type": "DROPDOWN",
            "label": "Select CRM Lead",
            "data_source_configs": [
              {
                "remoteDataSource": {
                  "function": "getCrmLeads"
                },
                "min_characters_trigger": 2
              }
            ],
            "items": [
              {
                "text": "Suggested Lead 1",
                "value": "lead-1"
              }
            ]
          }
        }
      ]
    }
  ]
}

为了减少对动态数据源的请求,您可以添加在用户输入之前显示在下拉菜单中的建议项。您还可以通过在 DataSourceConfig 中设置 min_characters_trigger,将下拉菜单配置为根据用户输入的内容自动补全项目。当用户输入的字符数至少达到 min_characters_trigger 中指定的数量时,系统会触发 remoteDataSource 中指定的函数。传递给函数的事件对象包含 autocomplete_widget_query 键中的用户输入。

添加多选菜单

以下显示了一张卡片,其中要求用户从多选菜单中选择联系人:

您可以在 Google Workspace 中从以下数据源填充多选菜单的项:

  • Google Workspace 用户:您只能填充同一 Google Workspace 组织内的用户。
  • 聊天室:在多选菜单中输入内容的用户只能查看和选择其所属 Google Workspace 组织内的聊天室。

如需使用 Google Workspace 数据源,请指定 platformDataSource 字段。与其他选择输入类型不同,您需要省略 SelectionItem 对象,因为这些选择项是从 Google Workspace 动态获取的。

以下代码显示了 Google Workspace 用户的多选菜单。 为了填充用户,选择输入将 commonDataSource 设置为 USER:

JSON

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

以下代码显示了聊天室的多选菜单。如需填充空间,选择输入会指定 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 对象。

以下代码示例展示了一个多选菜单,该菜单会查询外部数据源并从中填充项:

Node.js

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

将 FUNCTION_URL 替换为用于查询外部数据源的 HTTP 端点。

Python

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

将 FUNCTION_URL 替换为用于查询外部数据源的 HTTP 端点。

Java

java/chat/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(FUNCTION_URL))
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  .setItems(List.of(getSuggestedContact("3")))))))))));

将 FUNCTION_URL 替换为用于查询外部数据源的 HTTP 端点。

Apps 脚本

此示例通过返回 card JSON 来发送卡片消息。您还可以使用 Apps 脚本卡片服务。

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

从动态数据源填充建议的商品

对于外部数据源,您还可以自动补全并建议用户在多选菜单或下拉菜单中开始输入的项。例如,如果用户开始输入 Atl 以显示美国城市填充的菜单,您的 Chat 应用可以在用户完成输入之前自动建议 Atlanta。您最多可以建议 100 个商品。

如需返回建议的商品,查询外部数据源的函数必须执行以下操作:

  1. 处理 event 对象,当用户在菜单中输入内容时,Chat 应用会收到该对象。
  2. 从事件对象中获取用户输入的值,该值在 event.commonEventObject.parameters["autocomplete_widget_query"] 字段中表示。
  3. 使用用户输入的值查询数据源,以获取一个或多个 SelectionItems 来向用户提供建议。
  4. 通过返回包含 modifyCard 对象的 RenderActions 操作来返回建议的商品。

以下代码示例展示了 Chat 应用如何在卡片上的多选菜单中动态建议项。当用户在菜单中输入内容时,微件的 externalDataSource 字段中提供的函数或端点会查询外部数据源,并建议用户可以选择的项目:

Node.js

node/chat/selection-input/index.js
/**
 * Web app that responds to events sent from a Google Chat space.
 *
 * @param {Object} req Request sent from Google Chat space
 * @param {Object} res Response to send back
 */
app.post('/', async (req, res) => {
  // Stores the Google Chat event
  const chatEvent = req.body.chat;

  // Handle user interaction with multiselect.
  if(chatEvent.widgetUpdatedPayload) {
    return res.json(queryContacts(req.body));
  }

  // Replies with a card that contains the multiselect menu.
  return res.json({ hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
    cardsV2: [{
      cardId: "contactSelector",
      card: { sections:[{ widgets: [{
        selectionInput: {
          name: "contacts",
          type: "MULTI_SELECT",
          label: "Selected contacts",
          multiSelectMaxSelectedItems: 3,
          multiSelectMinQueryLength: 1,
          externalDataSource: { function: FUNCTION_URL },
          // Suggested items loaded by default.
          // The list is static here but it could be dynamic.
          items: [getSuggestedContact("3")]
        }
      }]}]}
    }]
  }}}}});
});

/**
 * Get contact suggestions based on text typed by users.
 *
 * @param {Object} event the event object that contains the user's query
 * @return {Object} suggestions
 */
function queryContacts(event) {
  const query = event.commonEventObject.parameters["autocomplete_widget_query"];
  return { action: { modifyOperations: [{ updateWidget: { selectionInputWidgetSuggestions: { suggestions: [
    // The list is static here but it could be dynamic.
    getSuggestedContact("1"), getSuggestedContact("2"), getSuggestedContact("3"), getSuggestedContact("4"), getSuggestedContact("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 selection item in the menu.
 */
function getSuggestedContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}

将 FUNCTION_URL 替换为用于查询外部数据源的 HTTP 端点。

Python

python/chat/selection-input/main.py
@app.route('/', methods=['POST'])
def post() -> Mapping[str, Any]:
  """Handle requests from Google Chat

  Returns:
      Mapping[str, Any]: The response
  """
  # Stores the Google Chat event
  chatEvent = request.get_json().get('chat')

  # Handle user interaction with multiselect.
  if chatEvent.get('widgetUpdatedPayload') is not None:
    return json.jsonify(query_contacts(request.get_json()))

  # Replies with a card that contains the multiselect menu.
  return json.jsonify({ 'hostAppDataAction': { 'chatDataAction': { 'createMessageAction': {
    'message': { 'cardsV2': [{
      'cardId': "contactSelector",
      'card': { 'sections':[{ 'widgets': [{
        'selectionInput': {
          'name': "contacts",
          'type': "MULTI_SELECT",
          'label': "Selected contacts",
          'multiSelectMaxSelectedItems': 3,
          'multiSelectMinQueryLength': 1,
          'externalDataSource': { 'function': FUNCTION_URL },
          # Suggested items loaded by default.
          # The list is static here but it could be dynamic.
          'items': [get_suggested_contact("3")]
        }
      }]}]}
    }]}
  }}}})


def query_contacts(event: dict) -> dict:
  """Get contact suggestions based on text typed by users.

  Args:
      event (Mapping[str, Any]): The event object that contains the user's query

  Returns:
      Mapping[str, Any]: The response with contact suggestions.
  """
  query = event.get("commonEventObject").get("parameters").get("autocomplete_widget_query")
  return { 'action': { 'modifyOperations': [{ 'updateWidget': { 'selectionInputWidgetSuggestions': { 'suggestions': list(
    filter(lambda e: query is None or query in e["text"], [
      # The list is static here but it could be dynamic.
      get_suggested_contact("1"), get_suggested_contact("2"), get_suggested_contact("3"), get_suggested_contact("4"), get_suggested_contact("5")
    # Only return items based on the query from the user
    ])
  )}}}]}}


def get_suggested_contact(id: str) -> dict:
  """Generate a suggested contact given an ID.

  Args:
      id (str): The ID of the contact to return.

  Returns:
      Mapping[str, Any]: The contact formatted as a selection item in the menu.
  """
  return {
    'value': id,
    'startIconUri': "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    'text': "Contact " + id
  }

将 FUNCTION_URL 替换为用于查询外部数据源的 HTTP 端点。

Java

java/chat/selection-input/src/main/java/com/google/chat/selectionInput/App.java
@SpringBootApplication
@RestController
// Web app that responds to events sent from a Google Chat space.
public class App {
  private static final String FUNCTION_URL = "your-function-url";

  public static void main(String[] args) {
    SpringApplication.run(App.class, args);
  }

  /**
   * Handle requests from Google Chat
   * 
   * @param event the event object sent by Google Chat
   * @return The response to be sent back to Google Chat
   */
  @PostMapping("/")
  @ResponseBody
  public GenericJson onEvent(@RequestBody JsonNode event) throws Exception {
    // Stores the Google Chat event
    JsonNode chatEvent = event.at("/chat");

    // Handle user interaction with multiselect.
    if (!chatEvent.at("/widgetUpdatedPayload").isEmpty()) {
      return queryContacts(event);
    }

    // Replies with a card that contains the multiselect menu.
    Message message = new Message().setCardsV2(List.of(new CardWithId()
      .setCardId("contactSelector")
      .setCard(new GoogleAppsCardV1Card()
        .setSections(List.of(new GoogleAppsCardV1Section().setWidgets(List.of(new GoogleAppsCardV1Widget()
          .setSelectionInput(new GoogleAppsCardV1SelectionInput()
            .setName("contacts")
            .setType("MULTI_SELECT")
            .setLabel("Selected contacts")
            .setMultiSelectMaxSelectedItems(3)
            .setMultiSelectMinQueryLength(1)
            .setExternalDataSource(new GoogleAppsCardV1Action().setFunction(FUNCTION_URL))
            // Suggested items loaded by default.
            // The list is static here but it could be dynamic.
            .setItems(List.of(getSuggestedContact("3")))))))))));

    return new GenericJson() {{
      put("hostAppDataAction", new GenericJson() {{
        put("chatDataAction", new GenericJson() {{
          put("createMessageAction", new GenericJson() {{
            put("message", message);
          }});
        }});
      }});
    }};
  }

  /**
   * Get contact suggestions based on text typed by users.
   *
   * @param event the event object that contains the user's query.
   * @return The response with contact suggestions.
   */
  GenericJson queryContacts(JsonNode event) throws Exception {
    String query = event.at("/commonEventObject/parameters/autocomplete_widget_query").asText();
    List<GoogleAppsCardV1SelectionItem> suggestions = List.of(
      // The list is static here but it could be dynamic.
      getSuggestedContact("1"), getSuggestedContact("2"), getSuggestedContact("3"), getSuggestedContact("4"), getSuggestedContact("5")
    // Only return items based on the query from the user
    ).stream().filter(e -> query == null || e.getText().indexOf(query) > -1).toList();

    return new GenericJson() {{
      put("action", new GenericJson() {{
        put("modifyOperations", List.of(new GenericJson() {{
          put("updateWidget", new GenericJson() {{
            put("selectionInputWidgetSuggestions", new GenericJson() {{
              put("suggestions", suggestions);
            }});
          }});
        }}));
      }});
    }};
  }

  /**
   * Generate a suggested contact given an ID.
   * 
   * @param id The ID of the contact to return.
   * @return The contact formatted as a selection item in the menu.
   */
  GoogleAppsCardV1SelectionItem getSuggestedContact(String id) {
    return new GoogleAppsCardV1SelectionItem()
      .setValue(id)
      .setStartIconUri("https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png")
      .setText("Contact " + id);
  }
}

将 FUNCTION_URL 替换为用于查询外部数据源的 HTTP 端点。

Apps 脚本

此示例通过返回 card JSON 来发送卡片消息。您还可以使用 Apps 脚本卡片服务。

apps-script/chat/selection-input/selection-input.gs
/**
* Responds to a Message trigger in Google Chat.
*
* @param {Object} event the event object from Google Chat
* @return {Object} Response from the Chat app.
*/
function onMessage(event) {
  // Replies with a card that contains the multiselect menu.
  return { hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
    cardsV2: [{
      cardId: "contactSelector",
      card: { sections:[{ widgets: [{
        selectionInput: {
          name: "contacts",
          type: "MULTI_SELECT",
          label: "Selected contacts",
          multiSelectMaxSelectedItems: 3,
          multiSelectMinQueryLength: 1,
          externalDataSource: { function: "queryContacts" },
          // Suggested items loaded by default.
          // The list is static here but it could be dynamic.
          items: [getSuggestedContact("3")]
        }
      }]}]}
    }]
  }}}}};
}

/**
* Get contact suggestions based on text typed by users.
*
* @param {Object} event the event object that contains the user's query
* @return {Object} suggestions
*/
function queryContacts(event) {
  const query = event.commonEventObject.parameters["autocomplete_widget_query"];
  return { action: { modifyOperations: [{ updateWidget: { selectionInputWidgetSuggestions: { suggestions: [
    // The list is static here but it could be dynamic.
    getSuggestedContact("1"), getSuggestedContact("2"), getSuggestedContact("3"), getSuggestedContact("4"), getSuggestedContact("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 selection item in the menu.
*/
function getSuggestedContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}

验证输入到卡片中的数据

本页介绍了如何验证输入到卡片的 action 和 widget 中的数据。例如,您可以验证文本输入字段是否包含用户输入的文本,或者是否包含一定数量的字符。

为操作设置必需的 widget

在卡片的 action 中,将操作所需的 widget 名称添加到其 requiredWidgets 列表中。

如果此处列出的任何 widget 在调用此操作时没有值,则取消表单操作提交。

如果为某项操作设置了 "all_widgets_are_required": "true",则该操作需要卡片中的所有 widget。

在多选模式下设置 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
              }
            ]
          }
        }
      ]
    }
  ]
}

为文本输入 widget 设置验证

在 textInput widget 的验证字段中,可以为此文本输入 widget 指定字符数限制和输入类型。

为文本输入 widget 设置字符数限制

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
            }
          }
        }
      ]
    }
  ]
}
为文本输入 widget 设置输入类型

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 界面会显示一条消息,提示“出了点问题”。 或“无法处理您的请求”。有时,聊天界面不会显示任何错误消息,但聊天应用或卡片会产生意外结果;例如,卡片消息可能不会显示。

虽然聊天界面中可能不会显示错误消息,但当您为聊天应用启用错误日志记录功能后,系统会提供描述性错误消息和日志数据,帮助您修复错误。如需有关查看、调试和修正错误的帮助,请参阅排查和修正 Google Chat 错误。

非插件的 Chat 应用:设计互动式卡片和对话框

以下文档适用于非 Google Workspace 加载项的 Chat 应用。如需迁移非插件的 Chat 应用,请参阅将 Google Chat 应用转换为 Google Workspace 加载项。

以下代码展示了聊天应用(非插件)中用户的一组外部联系人的多选菜单。 菜单默认显示一个联系人,并运行函数 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 脚本

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")]
}

如需在非插件的 Chat 应用中自动补全项,请创建一个函数,该函数可在用户在多选菜单中输入内容时查询外部数据源并返回项。该函数必须执行以下操作:

  • 传递一个表示用户与菜单互动的事件对象。
  • 确定互动事件的 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 脚本

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
  };
}