Thêm thành phần giao diện người dùng tương tác vào thẻ

Trang này giải thích cách thêm tiện ích và phần tử trên giao diện người dùng vào thẻ để người dùng có thể tương tác với ứng dụng Google Chat của bạn, chẳng hạn như bằng cách nhấp vào nút hoặc gửi thông tin.

Các ứng dụng Chat có thể sử dụng các giao diện Chat sau đây để tạo thẻ tương tác:

  • Tin nhắn chứa một hoặc nhiều thẻ.
  • Trang chủ, là một thẻ xuất hiện trong thẻ Trang chủ trong tin nhắn trực tiếp với ứng dụng Chat.
  • Hộp thoại, là các thẻ mở ra trong một cửa sổ mới từ tin nhắn và trang chủ.

Khi người dùng tương tác với thẻ, ứng dụng Chat có thể sử dụng dữ liệu mà họ nhận được để xử lý và phản hồi cho phù hợp. Để biết thông tin chi tiết, hãy xem bài viết Thu thập và xử lý thông tin của người dùng Google Chat.


Sử dụng Trình tạo thẻ để thiết kế và xem trước giao diện người dùng cũng như thông báo cho ứng dụng Chat:

Mở Trình tạo thẻ

Điều kiện tiên quyết

Một ứng dụng Google Chat được định cấu hình để nhận và phản hồi các hoạt động tương tác của người dùng. Để tạo một ứng dụng Chat có tính tương tác, hãy hoàn tất một trong các hướng dẫn nhanh sau đây dựa trên kiến trúc ứng dụng mà bạn muốn sử dụng:

Thêm nút

Tiện ích ButtonList hiển thị một nhóm nút. Các nút có thể hiển thị văn bản, biểu tượng hoặc cả văn bản và biểu tượng. Mỗi Button hỗ trợ một hành động OnClick xảy ra khi người dùng nhấp vào nút. Ví dụ:

  • Mở siêu liên kết bằng OpenLink, để cung cấp thêm thông tin cho người dùng.
  • Chạy một action để chạy một hàm tuỳ chỉnh, chẳng hạn như gọi một API.

Để hỗ trợ tiếp cận, các nút hỗ trợ văn bản thay thế.

Thêm nút chạy hàm tuỳ chỉnh

Sau đây là một thẻ bao gồm một tiện ích ButtonList có hai nút. Một nút mở tài liệu dành cho nhà phát triển Google Chat trong thẻ mới. Nút còn lại chạy một hàm tuỳ chỉnh có tên là goToView() và truyền tham số viewType="BIRD EYE VIEW".

Thêm nút theo kiểu Material Design

Phần sau đây cho thấy một tập hợp các nút theo nhiều kiểu nút Material Design.

Để áp dụng kiểu Material Design, đừng thêm thuộc tính "color".

Thêm nút có màu sắc tuỳ chỉnh và nút đã bị vô hiệu hoá

Bạn có thể ngăn người dùng nhấp vào nút bằng cách đặt "disabled": "true".

Sau đây là một thẻ bao gồm một tiện ích ButtonList có hai nút. Một nút sử dụng trường Color để tuỳ chỉnh màu nền của nút. Nút còn lại bị vô hiệu hoá bằng trường Disabled, giúp ngăn người dùng nhấp vào nút và thực thi hàm.

Thêm nút có biểu tượng

Phần sau đây cho thấy một thẻ bao gồm một tiện ích ButtonList có hai tiện ích biểu tượng Button. Một nút sử dụng trường knownIcon để hiển thị biểu tượng email tích hợp sẵn của Google Chat. Nút còn lại sử dụng trường iconUrl để hiển thị một tiện ích biểu tượng tuỳ chỉnh.

Thêm nút có biểu tượng và văn bản

Phần sau đây hiển thị một thẻ bao gồm một tiện ích ButtonList nhắc người dùng gửi email. Nút đầu tiên hiển thị biểu tượng email và nút thứ hai hiển thị văn bản. Người dùng có thể nhấp vào nút biểu tượng hoặc nút văn bản để chạy hàm sendEmail.

Tuỳ chỉnh nút cho một mục có thể thu gọn

Tuỳ chỉnh nút điều khiển thu gọn và mở rộng các phần trong một thẻ. Chọn trong số nhiều biểu tượng hoặc hình ảnh để thể hiện nội dung của mục một cách trực quan, giúp người dùng dễ dàng hiểu và tương tác với thông tin.

Thêm trình đơn mục bổ sung

Bạn có thể dùng Overflow menu trong thẻ Chat để cung cấp thêm các lựa chọn và hành động. Thành phần này cho phép bạn thêm nhiều lựa chọn mà không làm lộn xộn giao diện của thẻ, đảm bảo thiết kế gọn gàng và ngăn nắp.

Thêm danh sách Khối

Tiện ích ChipList cung cấp một cách linh hoạt và hấp dẫn để hiển thị thông tin. Sử dụng danh sách khối để thể hiện thẻ, danh mục hoặc dữ liệu liên quan khác, giúp người dùng dễ dàng điều hướng và tương tác với nội dung của bạn hơn.

Thu thập thông tin từ người dùng

Phần này giải thích cách bạn có thể thêm các tiện ích thu thập thông tin, chẳng hạn như văn bản hoặc lựa chọn.

Để tìm hiểu cách xử lý thông tin mà người dùng nhập, hãy xem bài viết Thu thập và xử lý thông tin của người dùng Google Chat.

Thu thập văn bản

Tiện ích TextInput cung cấp một trường để người dùng có thể nhập văn bản. Tiện ích này hỗ trợ các đề xuất giúp người dùng nhập dữ liệu đồng nhất và các hành động khi thay đổi, tức là Actions chạy khi có thay đổi trong trường nhập dữ liệu, chẳng hạn như người dùng thêm hoặc xoá văn bản.

Khi bạn cần thu thập dữ liệu trừu tượng hoặc không xác định từ người dùng, hãy sử dụng tiện ích TextInput này. Để thu thập dữ liệu đã xác định từ người dùng, hãy sử dụng tiện ích SelectionInput.

Sau đây là một thẻ bao gồm một tiện ích TextInput:

Thu thập ngày hoặc giờ

Tiện ích DateTimePicker cho phép người dùng nhập ngày, giờ hoặc cả ngày và giờ. Hoặc người dùng có thể sử dụng bộ chọn để chọn ngày và giờ. Nếu người dùng nhập ngày hoặc giờ không hợp lệ, bộ chọn sẽ hiển thị lỗi nhắc người dùng nhập thông tin chính xác.

Phần sau đây cho thấy một thẻ bao gồm ba loại tiện ích DateTimePicker khác nhau:

Cho phép người dùng chọn mục

Tiện ích SelectionInput cung cấp một tập hợp các mục có thể chọn, chẳng hạn như hộp đánh dấu, nút chọn, nút chuyển hoặc trình đơn thả xuống. Bạn có thể sử dụng tiện ích này để thu thập dữ liệu đã xác định và chuẩn hoá từ người dùng. Để thu thập dữ liệu không xác định của người dùng, hãy sử dụng tiện ích TextInput.

Tiện ích SelectionInput hỗ trợ các đề xuất giúp người dùng nhập dữ liệu đồng nhất và các hành động khi thay đổi, tức là Actions chạy khi có thay đổi trong trường nhập lựa chọn, chẳng hạn như người dùng chọn hoặc bỏ chọn một mục.

Ứng dụng Chat có thể nhận và xử lý giá trị của các mục đã chọn. Để biết thông tin chi tiết về cách xử lý dữ liệu đầu vào của biểu mẫu, hãy xem phần Xử lý thông tin do người dùng nhập.

Phần này cung cấp ví dụ về các thẻ sử dụng tiện ích SelectionInput. Các ví dụ này sử dụng nhiều loại dữ liệu đầu vào cho mục:

Thêm hộp đánh dấu

Phần sau đây cho thấy một thẻ yêu cầu người dùng chỉ định xem một người liên hệ là người liên hệ công việc, cá nhân hay cả hai, bằng một tiện ích SelectionInput sử dụng hộp đánh dấu:

Thêm nút chọn

Phần sau đây hiển thị một thẻ yêu cầu người dùng chỉ định xem một người liên hệ là chuyên nghiệp hay cá nhân bằng tiện ích SelectionInput sử dụng nút chọn:

Thêm một nút chuyển

Phần sau đây hiển thị một thẻ yêu cầu người dùng chỉ định xem một người liên hệ là người liên hệ công việc, cá nhân hay cả hai bằng một tiện ích SelectionInput sử dụng nút chuyển:

Phần sau đây hiển thị một thẻ yêu cầu người dùng chỉ định xem một người liên hệ là người liên hệ công việc hay cá nhân bằng tiện ích SelectionInput sử dụng trình đơn thả xuống:

Tự động điền trình đơn thả xuống

Dành cho các ứng dụng Google Chat.

Bạn có thể tự động điền các mục cho trình đơn thả xuống từ các nguồn dữ liệu trong Google Workspace hoặc từ một nguồn dữ liệu bên ngoài. Để sử dụng nguồn dữ liệu động, bạn chỉ định trường dataSourceConfigs, đây là một mảng gồm các đối tượng DataSourceConfig. Mỗi DataSourceConfig có thể chứa platformDataSource hoặc remoteDataSource. Hiện chỉ hỗ trợ một DataSourceConfig.

Điền các mục từ Google Workspace

Để điền các mục từ nguồn dữ liệu của Google Workspace, chẳng hạn như người dùng Google Workspace, bạn chỉ định trường platformDataSource trong DataSourceConfig. Khác với khi sử dụng items tĩnh, bạn bỏ qua các đối tượng SelectionItem vì các mục lựa chọn này được lấy động từ Google Workspace.

Đoạn mã sau đây cho thấy một trình đơn thả xuống điền sẵn người dùng Google Workspace:

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "widgets": [
        {
          "selectionInput": {
            "name": "contacts",
            "type": "DROPDOWN",
            "label": "Select contact from organization",
            "dataSourceConfigs": [
              {
                "platformDataSource": {
                  "commonDataSource": "USER"
                },
                "minCharactersTrigger": 1
              }
            ]
          }
        }
      ]
    }
  ]
}
Điền các mục từ một nguồn dữ liệu bên ngoài

Để điền các mục từ một nguồn dữ liệu bên thứ ba hoặc bên ngoài, chẳng hạn như hệ thống quản lý quan hệ khách hàng (CRM), bạn sử dụng trường remoteDataSource trong DataSourceConfig để chỉ định một hàm trả về các mục từ nguồn dữ liệu.

Đoạn mã sau đây cho thấy một trình đơn thả xuống điền sẵn các mục từ một nhóm người liên hệ bên ngoài bằng cách chạy hàm getCrmLeads:

JSON

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

Để giảm số lượng yêu cầu đến nguồn dữ liệu động, bạn có thể thêm các mục đề xuất xuất hiện trong trình đơn thả xuống trước khi người dùng nhập. Bạn cũng có thể định cấu hình trình đơn thả xuống để tự động hoàn thành các mục dựa trên nội dung người dùng nhập bằng cách đặt minCharactersTrigger trong DataSourceConfig. Khi người dùng nhập ít nhất số ký tự được chỉ định trong minCharactersTrigger, hàm được chỉ định trong remoteDataSource sẽ được kích hoạt. Đối tượng sự kiện được truyền đến hàm của bạn bao gồm dữ liệu đầu vào của người dùng trong khoá autocomplete_widget_query.

Thêm trình đơn chọn nhiều mục

Sau đây là một thẻ yêu cầu người dùng chọn người liên hệ trong trình đơn chọn nhiều mục:

Bạn có thể điền các mục cho trình đơn nhiều lựa chọn từ các nguồn dữ liệu sau trong Google Workspace:

  • Người dùng Google Workspace: Bạn chỉ có thể điền thông tin người dùng trong cùng một tổ chức Google Workspace.
  • Không gian trò chuyện: Người dùng nhập các mục trong trình đơn chọn nhiều mục chỉ có thể xem và chọn những không gian mà họ thuộc về trong tổ chức Google Workspace của họ.

Để sử dụng các nguồn dữ liệu của Google Workspace, bạn cần chỉ định trường platformDataSource. Không giống như các loại dữ liệu đầu vào lựa chọn khác, bạn bỏ qua các đối tượng SelectionItem vì các mục lựa chọn này được lấy động từ Google Workspace.

Mã sau đây cho thấy một trình đơn nhiều lựa chọn gồm những người dùng Google Workspace. Để điền người dùng, dữ liệu đầu vào lựa chọn sẽ đặt commonDataSource thành USER:

JSON

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

Mã sau đây cho thấy một trình đơn chọn nhiều mục của không gian trò chuyện. Để điền vào không gian, dữ liệu đầu vào của lựa chọn sẽ chỉ định trường hostAppDataSource. Trình đơn nhiều lựa chọn cũng đặt defaultToCurrentSpace thành true, giúp không gian hiện tại trở thành lựa chọn mặc định trong trình đơn:

JSON

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

Trình đơn nhiều lựa chọn cũng có thể điền sẵn các mục từ một nguồn dữ liệu bên ngoài hoặc bên thứ ba. Ví dụ: bạn có thể sử dụng trình đơn chọn nhiều mục để giúp người dùng chọn trong danh sách khách hàng tiềm năng từ hệ thống quản lý quan hệ khách hàng (CRM).

Để sử dụng nguồn dữ liệu bên ngoài, bạn dùng trường externalDataSource để chỉ định một hàm trả về các mục từ nguồn dữ liệu.

Để giảm số lượng yêu cầu đến nguồn dữ liệu bên ngoài, bạn có thể thêm các mục đề xuất xuất hiện trong trình đơn chọn nhiều mục trước khi người dùng nhập vào trình đơn. Ví dụ: bạn có thể điền sẵn những người liên hệ mà người dùng tìm kiếm gần đây. Để điền các mục được đề xuất từ một nguồn dữ liệu bên ngoài, hãy chỉ định các đối tượng SelectionItem.

Mã mẫu sau đây cho thấy một trình đơn nhiều lựa chọn truy vấn và điền sẵn các mục từ một nguồn dữ liệu bên ngoài:

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

Thay thế FUNCTION_URL bằng điểm cuối HTTP truy vấn nguồn dữ liệu bên ngoài.

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

Thay thế FUNCTION_URL bằng điểm cuối HTTP truy vấn nguồn dữ liệu bên ngoài.

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")))))))))));

Thay thế FUNCTION_URL bằng điểm cuối HTTP truy vấn nguồn dữ liệu bên ngoài.

Apps Script

Ví dụ này gửi một tin nhắn dạng thẻ bằng cách trả về JSON của thẻ. Bạn cũng có thể sử dụng dịch vụ thẻ Apps Script.

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

Điền các mục được đề xuất từ một nguồn dữ liệu động

Đối với các nguồn dữ liệu bên ngoài, bạn cũng có thể tự động hoàn thành và đề xuất các mục mà người dùng bắt đầu nhập trong trình đơn thả xuống hoặc trình đơn chọn nhiều mục. Ví dụ: nếu người dùng bắt đầu nhập Atl cho một trình đơn điền sẵn các thành phố ở Hoa Kỳ, thì ứng dụng Chat có thể tự động đề xuất Atlanta trước khi người dùng nhập xong. Bạn có thể đề xuất tối đa 100 mục.

Để trả về các mục được đề xuất, hàm truy vấn nguồn dữ liệu bên ngoài phải làm như sau:

  1. Xử lý một đối tượng sự kiện mà ứng dụng Chat nhận được khi người dùng nhập vào trình đơn.
  2. Từ đối tượng sự kiện, hãy lấy giá trị mà người dùng nhập, được biểu thị trong trường event.commonEventObject.parameters["autocomplete_widget_query"].
  3. Truy vấn nguồn dữ liệu bằng giá trị do người dùng nhập để nhận một hoặc nhiều SelectionItems đề xuất cho người dùng.
  4. Trả về các mục được đề xuất bằng cách trả về hành động RenderActions bằng đối tượng modifyCard.

Mã mẫu sau đây cho thấy cách một ứng dụng Chat đề xuất các mục một cách linh động trong trình đơn nhiều lựa chọn trên một thẻ. Khi người dùng nhập vào trình đơn, hàm hoặc điểm cuối được cung cấp trong trường externalDataSource của tiện ích sẽ truy vấn một nguồn dữ liệu bên ngoài và đề xuất các mục mà người dùng có thể chọn:

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

Thay thế FUNCTION_URL bằng điểm cuối HTTP truy vấn nguồn dữ liệu bên ngoài.

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
  }

Thay thế FUNCTION_URL bằng điểm cuối HTTP truy vấn nguồn dữ liệu bên ngoài.

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

Thay thế FUNCTION_URL bằng điểm cuối HTTP truy vấn nguồn dữ liệu bên ngoài.

Apps Script

Ví dụ này gửi một tin nhắn dạng thẻ bằng cách trả về JSON của thẻ. Bạn cũng có thể sử dụng Dịch vụ thẻ Apps Script.

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

Xác thực dữ liệu được nhập vào thẻ

Trang này giải thích cách xác thực dữ liệu được nhập vào action và các tiện ích của thẻ. Ví dụ: bạn có thể xác thực rằng trường nhập dữ liệu văn bản có văn bản do người dùng nhập hoặc chứa một số ký tự nhất định.

Đặt các tiện ích bắt buộc cho hành động

Trong action của thẻ, hãy thêm tên của các tiện ích mà một hành động cần vào danh sách requiredWidgets của thẻ.

Nếu bất kỳ tiện ích nào được liệt kê ở đây không có giá trị khi hành động này được gọi, thì việc gửi hành động biểu mẫu sẽ bị huỷ.

Khi "allWidgetsAreRequired": true được đặt cho một hành động, thì tất cả các tiện ích trong thẻ đều bắt buộc đối với hành động này.

Đặt hành động allWidgetsAreRequired trong chế độ chọn nhiều mục

JSON

{
  "sections": [
    {
      "header": "Select contacts",
      "widgets": [
        {
          "selectionInput": {
            "type": "MULTI_SELECT",
            "label": "Selected contacts",
            "name": "contacts",
            "multiSelectMaxSelectedItems": 3,
            "multiSelectMinQueryLength": 1,
            "onChangeAction": {
              "allWidgetsAreRequired": 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
              }
            ]
          }
        }
      ]
    }
  ]
}
Đặt một thao tác allWidgetsAreRequired trong dateTimePicker

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":{
              "allWidgetsAreRequired": 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"
          }
        }
      ]
    }
  ]
}
Đặt hành động allWidgetsAreRequired trong trình đơn thả xuống

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 1,
      "widgets": [
        {
          "selectionInput": {
            "name": "location",
            "label": "Select Color",
            "type": "DROPDOWN",
            "onChangeAction": {
              "allWidgetsAreRequired": 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
              }
            ]
          }
        }
      ]
    }
  ]
}

Đặt quy trình xác thực cho tiện ích nhập văn bản

Trong trường xác thực của tiện ích textInput, bạn có thể chỉ định giới hạn ký tự và loại dữ liệu đầu vào cho tiện ích nhập văn bản này.

Đặt giới hạn ký tự cho tiện ích nhập văn bản

JSON

{
  "sections": [
    {
      "header": "Tell us about yourself",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 2,
      "widgets": [
        {
          "textInput": {
            "name": "favoriteColor",
            "label": "Favorite color",
            "type": "SINGLE_LINE",
            "validation": {"characterLimit": 15},
            "onChangeAction":{
              "allWidgetsAreRequired": true
            }
          }
        }
      ]
    }
  ]
}
Đặt loại dữ liệu đầu vào cho tiện ích nhập văn bản

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": {
              "inputType": "EMAIL"
            },
            "onChangeAction": {
              "allWidgetsAreRequired": true
            }
          }
        },
        {
          "textInput": {
            "name": "validate_integer",
            "label": "Please enter a number",
              "type": "SINGLE_LINE",
            "validation": {
              "inputType": "INTEGER"
            }
          }
        },
        {
          "textInput": {
            "name": "validate_float",
            "label": "Please enter a number with a decimal",
            "type": "SINGLE_LINE",
            "validation": {
              "inputType": "FLOAT"
            }
          }
        }
      ]
    }
  ]
}

Khắc phục sự cố

Khi một ứng dụng Google Chat hoặc thẻ trả về lỗi, giao diện Chat sẽ hiển thị thông báo "Đã xảy ra lỗi". hoặc "Không thể xử lý yêu cầu của bạn". Đôi khi, giao diện người dùng của Chat không hiển thị thông báo lỗi, nhưng ứng dụng Chat hoặc thẻ lại tạo ra kết quả không mong muốn; ví dụ: thông báo dạng thẻ có thể không xuất hiện.

Mặc dù thông báo lỗi có thể không xuất hiện trong giao diện người dùng của Chat, nhưng thông báo lỗi mô tả và dữ liệu nhật ký sẽ giúp bạn khắc phục lỗi khi bật tính năng ghi nhật ký lỗi cho ứng dụng Chat. Để được trợ giúp xem, gỡ lỗi và khắc phục lỗi, hãy xem bài viết Khắc phục sự cố và sửa lỗi trong Google Chat.

Ứng dụng trò chuyện không phải là tiện ích bổ sung: Thiết kế thẻ và hộp thoại tương tác

Tài liệu sau đây áp dụng cho các ứng dụng Chat không phải là tiện ích bổ sung của Google Workspace. Để di chuyển một ứng dụng Chat không phải là tiện ích bổ sung, hãy xem bài viết Chuyển đổi ứng dụng Google Chat thành tiện ích bổ sung của Google Workspace.

Mã sau đây cho thấy một trình đơn nhiều lựa chọn gồm các mục trong một nhóm người liên hệ bên ngoài cho người dùng trong một ứng dụng Chat không phải là tiện ích bổ sung. Trình đơn này hiển thị một người liên hệ theo mặc định và chạy hàm getContacts để truy xuất và điền các mục từ nguồn dữ liệu bên ngoài:

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

Để tự động hoàn thành các mục trong một ứng dụng Chat không phải là tiện ích bổ sung, hãy tạo một hàm truy vấn nguồn dữ liệu bên ngoài và trả về các mục bất cứ khi nào người dùng nhập vào trình đơn nhiều lựa chọn. Hàm phải thực hiện những việc sau:

  • Truyền một đối tượng sự kiện đại diện cho hoạt động tương tác của người dùng với trình đơn.
  • Xác định rằng giá trị invokedFunction của sự kiện tương tác khớp với hàm trong trường externalDataSource.
  • Khi các hàm khớp nhau, hãy trả về các mục được đề xuất từ nguồn dữ liệu bên ngoài. Để đề xuất các mục dựa trên nội dung người dùng nhập, hãy lấy giá trị cho khoá autocomplete_widget_query. Giá trị này thể hiện nội dung người dùng nhập vào trình đơn.

Mã sau đây tự động hoàn thành các mục từ một tài nguyên dữ liệu bên ngoài. Sử dụng ví dụ trước, ứng dụng Chat không phải là tiện ích bổ sung sẽ đề xuất các mục dựa trên thời điểm hàm getContacts được kích hoạt:

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