Ajouter des éléments d'interface utilisateur interactifs aux fiches

Cette page explique comment ajouter des widgets et des éléments d'interface utilisateur aux cartes afin que les utilisateurs puissent interagir avec votre application Google Chat, par exemple en cliquant sur un bouton ou en envoyant des informations.

Les applications Chat peuvent utiliser les interfaces Chat suivantes pour créer des fiches interactives :

  • Les messages contenant une ou plusieurs cartes.
  • Pages d'accueil, qui est une fiche qui s'affiche dans l'onglet Accueil des messages privés avec l'application Chat.
  • Les boîtes de dialogue, qui sont des fiches qui s'ouvrent dans une nouvelle fenêtre à partir de messages et de pages d'accueil.

Lorsque les utilisateurs interagissent avec des cartes, les applications Chat peuvent utiliser les données qu'elles reçoivent pour les traiter et y répondre en conséquence. Pour en savoir plus, consultez Collecter et traiter les informations des utilisateurs de Google Chat.


Utilisez l'outil de création de cartes pour concevoir et prévisualiser les interfaces utilisateur et de messagerie des applications Chat :

Ouvrir l'outil de création de cartes

Prérequis

Une application Google Chat pour laquelle les fonctionnalités interactives sont activées. Pour créer une application Chat interactive, suivez l'un des guides de démarrage rapide suivants en fonction de l'architecture de l'application que vous souhaitez utiliser :

Ajouter un bouton

Le widget ButtonList affiche un ensemble de boutons. Les boutons peuvent afficher du texte, une icône ou les deux. Chaque Button est compatible avec une action OnClick qui se produit lorsque les utilisateurs cliquent sur le bouton. Exemple :

  • Ouvrez un lien hypertexte avec OpenLink pour fournir aux utilisateurs des informations supplémentaires.
  • Exécutez un action qui exécute une fonction personnalisée, comme l'appel d'une API.

Pour des raisons d'accessibilité, les boutons sont compatibles avec le texte alternatif.

Ajouter un bouton qui exécute une fonction personnalisée

Voici une fiche composée d'un widget ButtonList avec deux boutons. Un bouton permet d'ouvrir la documentation destinée aux développeurs de Google Chat dans un nouvel onglet. L'autre bouton exécute une fonction personnalisée appelée goToView() et transmet le paramètre viewType="BIRD EYE VIEW".

Ajouter un bouton de style Material Design

L'image suivante affiche un ensemble de boutons dans différents styles de boutons Material Design.

Pour appliquer le style Material Design, n'incluez pas l'attribut 'couleur' [color].

Ajouter un bouton avec une couleur personnalisée et un bouton désactivé

Vous pouvez empêcher les utilisateurs de cliquer sur un bouton en définissant "disabled": "true".

Vous trouverez ci-dessous une fiche composée d'un widget ButtonList avec deux boutons. Un bouton utilise le champ Color pour personnaliser la couleur d'arrière-plan du bouton. L'autre bouton est désactivé avec le champ Disabled, ce qui empêche l'utilisateur de cliquer sur le bouton et d'exécuter la fonction.

Ajouter un bouton avec une icône

L'image suivante affiche une fiche composée d'un widget ButtonList avec deux widgets Button d'icône. Un bouton utilise le champ knownIcon pour afficher l'icône de messagerie intégrée de Google Chat. L'autre bouton utilise le champ iconUrl pour afficher un widget d'icône personnalisée.

Ajouter un bouton avec une icône et du texte

L'exemple suivant affiche une fiche composée d'un widget ButtonList qui invite l'utilisateur à envoyer un e-mail. Le premier bouton affiche une icône d'e-mail et le second affiche du texte. L'utilisateur peut cliquer sur l'icône ou sur le bouton de texte pour exécuter la fonction sendEmail.

Personnaliser le bouton pour une section réductible

Personnalisez le bouton de commande qui réduit et développe les sections d'une fiche. Choisissez parmi une gamme d'icônes ou d'images pour représenter visuellement le contenu de la section, ce qui permet aux utilisateurs de comprendre et d'interagir plus facilement avec les informations.

Ajouter un menu à développer

Vous pouvez utiliser le Overflow menu dans les fiches Chat pour proposer des options et actions supplémentaires. Elle vous permet d'inclure plus d'options sans encombrer l'interface de la fiche, ce qui garantit une conception propre et organisée.

Ajouter une liste Chips

Le widget ChipList permet d'afficher des informations de manière polyvalente et visuellement attrayante. Utilisez des listes de chips pour représenter des balises, des catégories ou d'autres données pertinentes, ce qui permet aux utilisateurs de naviguer et d'interagir plus facilement avec votre contenu.

Collecter des informations auprès des utilisateurs

Cette section explique comment ajouter des widgets qui collectent des informations, telles que du texte ou des sélections.

Pour découvrir comment traiter les entrées des utilisateurs, consultez la section Collecter et traiter les informations des utilisateurs de Google Chat.

Collecter du texte

Le widget TextInput fournit un champ dans lequel les utilisateurs peuvent saisir du texte. Le widget est compatible avec les suggestions, qui aident les utilisateurs à saisir des données uniformes, et les actions de modification, qui sont des Actions exécutées lorsqu'un changement se produit dans le champ de saisie de texte, par exemple lorsqu'un utilisateur ajoute ou supprime du texte.

Lorsque vous devez collecter des données abstraites ou inconnues auprès des utilisateurs, utilisez ce widget TextInput. Pour collecter des données définies auprès des utilisateurs, utilisez plutôt le widget SelectionInput.

Voici une fiche composée d'un widget TextInput:

Collecter les dates ou les heures

Le widget DateTimePicker permet aux utilisateurs de saisir une date, une heure ou les deux. Les utilisateurs peuvent également utiliser le sélecteur pour sélectionner des dates et des heures. Si les utilisateurs saisissent une date ou une heure non valide, le sélecteur affiche une erreur qui les invite à saisir les informations correctement.

Vous trouverez ci-dessous une fiche comportant trois types différents de widgets DateTimePicker:

Autoriser les utilisateurs à sélectionner des éléments

Le widget SelectionInput fournit un ensemble d'éléments sélectionnables, tels que des cases à cocher, des boutons d'option, des boutons d'activation/de désactivation ou un menu déroulant. Vous pouvez utiliser ce widget pour collecter des données définies et normalisées auprès des utilisateurs. Pour collecter des données non définies auprès des utilisateurs, utilisez plutôt le widget TextInput.

Le widget SelectionInput est compatible avec les suggestions, qui aident les utilisateurs à saisir des données uniformes, et les actions de modification, qui sont des Actions qui s'exécutent lorsqu'un changement se produit dans un champ de saisie de sélection, par exemple lorsqu'un utilisateur sélectionne ou désélectionne un élément.

Les applications Chat peuvent recevoir et traiter la valeur des éléments sélectionnés. Pour en savoir plus sur l'utilisation des entrées de formulaire, consultez la section Traiter les informations saisies par les utilisateurs.

Cette section fournit des exemples de cartes qui utilisent le widget SelectionInput. Les exemples utilisent différents types d'entrées de section:

Ajouter une case à cocher

L'exemple suivant affiche une fiche demandant à l'utilisateur de spécifier si un contact est professionnel, personnel ou les deux, avec un widget SelectionInput qui utilise des cases à cocher:

Ajouter un bouton radio

L'exemple suivant affiche une fiche demandant à l'utilisateur de spécifier si un contact est professionnel ou personnel à l'aide d'un widget SelectionInput qui utilise des boutons radio:

Ajouter un bouton bascule

L'exemple suivant affiche une fiche qui demande à l'utilisateur de spécifier si un contact est professionnel, personnel ou les deux à l'aide d'un widget SelectionInput qui utilise des boutons d'activation/de désactivation :

L'exemple suivant affiche une fiche qui demande à l'utilisateur d'indiquer si un contact est professionnel ou personnel, avec un widget SelectionInput qui utilise un menu déroulant:

Ajouter un menu multi-sélection

L'exemple suivant affiche une fiche qui invite l'utilisateur à sélectionner des contacts dans un menu à sélection multiple :

Vous pouvez renseigner les éléments d'un menu à sélection multiple à partir des sources de données suivantes dans Google Workspace :

  • Utilisateurs Google Workspace: vous ne pouvez renseigner que des utilisateurs appartenant à la même organisation Google Workspace.
  • Salons Chat: l'utilisateur qui saisit des éléments dans le menu de sélection multiple ne peut afficher et sélectionner que les espaces auxquels il appartient dans son organisation Google Workspace.

Pour utiliser des sources de données Google Workspace, vous devez spécifier le champ platformDataSource. Contrairement aux autres types de saisie de sélection, vous omettez les objets SectionItem, car ces éléments de sélection sont issus de Google Workspace de manière dynamique.

Le code suivant montre un menu multi-sélection d'utilisateurs Google Workspace. Pour renseigner les utilisateurs, la saisie de sélection définit commonDataSource sur USER :

JSON

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

Le code suivant affiche un menu de sélection multiple d'espaces Chat. Pour renseigner les espaces, la saisie de sélection spécifie le champ hostAppDataSource. Le menu de sélection multiple définit également defaultToCurrentSpace sur true, ce qui fait de l'espace actuel la sélection par défaut dans le menu:

JSON

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

Les menus à sélection multiple peuvent également renseigner des éléments à partir d'une source de données tierce ou externe. Par exemple, vous pouvez utiliser des menus à sélection multiple pour aider un utilisateur à choisir parmi une liste de prospects commerciaux à partir d'un système de gestion de la relation client (CRM).

Pour utiliser une source de données externe, vous devez spécifier une fonction qui renvoie des éléments à partir de la source de données à l'aide du champ externalDataSource.

Pour réduire les requêtes envoyées à une source de données externe, vous pouvez inclure des suggestions d'éléments qui s'affichent dans le menu à sélection multiple avant que les utilisateurs ne saisissent du texte dans le menu. Par exemple, vous pouvez renseigner les contacts récemment recherchés par l'utilisateur. Pour renseigner des suggestions d'articles à partir d'une source de données externe, spécifiez des objets SelectionItem.

Le code suivant affiche un menu multisélection d'éléments à partir d'un ensemble externe de contacts pour l'utilisateur. Le menu affiche un contact par défaut et exécute la fonction getContacts pour récupérer et renseigner des éléments à partir de la source de données externe :

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

Pour les sources de données externes, vous pouvez également compléter automatiquement les éléments que les utilisateurs commencent à saisir dans le menu de sélection multiple. Par exemple, si un utilisateur commence à saisir Atl pour un menu qui affiche les villes des États-Unis, votre application Chat peut suggérer automatiquement Atlanta avant que l'utilisateur ait terminé de saisir. Vous pouvez utiliser la saisie semi-automatique pour jusqu'à 100 éléments.

Pour effectuer la saisie semi-automatique d'éléments, vous devez créer une fonction qui interroge la source de données externe et renvoie des éléments chaque fois qu'un utilisateur saisit du texte dans le menu à sélection multiple. La fonction doit effectuer les opérations suivantes:

  • Transmettez un objet événement qui représente l'interaction de l'utilisateur avec le menu.
  • Vérifiez que la valeur invokedFunction de l'événement d'interaction correspond à la fonction du champ externalDataSource.
  • Lorsque les fonctions correspondent, renvoyez les suggestions d'articles de la source de données externe. Pour suggérer des éléments en fonction de ce que l'utilisateur saisit, obtenez la valeur de la clé autocomplete_widget_query. Cette valeur représente ce que l'utilisateur saisit dans le menu.

Le code suivant permet de compléter automatiquement les éléments à partir d'une ressource de données externe. À l'aide de l'exemple précédent, l'application Chat suggère des éléments en fonction du moment où la fonction getContacts est déclenchée:

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

Valider les données saisies dans les fiches

Cette page explique comment valider les données saisies dans les action et les widgets d'une fiche. Par exemple, vous pouvez vérifier qu'un champ de saisie de texte contient du texte saisi par l'utilisateur ou qu'il contient un certain nombre de caractères.

Définir les widgets requis pour les actions

Dans le action de la fiche, ajoutez les noms des widgets nécessaires à une action à sa liste requiredWidgets.

Si l'un des widgets listés ici n'a pas de valeur lorsque cette action est appelée, l'envoi de l'action du formulaire est annulé.

Lorsque "all_widgets_are_required": "true" est défini pour une action, tous les widgets de la fiche sont obligatoires pour cette action.

Définir une action all_widgets_are_required dans la sélection multiple

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
              }
            ]
          }
        }
      ]
    }
  ]
}
Définir une action all_widgets_are_required dans 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":{
              "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"
          }
        }
      ]
    }
  ]
}
Définir une action all_widgets_are_required dans un menu déroulant

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

Définir la validation pour un widget de saisie de texte

Dans le champ de validation du widget textInput, vous pouvez spécifier la limite de caractères et le type de saisie pour ce widget de saisie de texte.

Définir une limite de caractères pour un widget de saisie de texte

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
            }
          }
        }
      ]
    }
  ]
}
Définir le type de saisie pour un widget de saisie de texte

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

Résoudre les problèmes

Lorsqu'une application ou une fiche Google Chat renvoie une erreur, l'interface Chat affiche le message "Un problème est survenu". ou "Impossible de traiter votre demande." Il arrive que l'interface utilisateur de Chat n'affiche aucun message d'erreur, mais que l'application ou la fiche Chat produise un résultat inattendu. Par exemple, un message de fiche peut ne pas s'afficher.

Bien qu'un message d'erreur ne s'affiche pas dans l'UI Chat, des messages d'erreur descriptifs et des données de journal sont disponibles pour vous aider à résoudre les erreurs lorsque la journalisation des erreurs pour les applications Chat est activée. Pour obtenir de l'aide concernant l'affichage, le débogage et la correction des erreurs, consultez Dépanner et corriger les erreurs dans Google Chat.