Construire

Widgets interactifs

Demandez une confirmation, un choix, un formulaire, une date, un fichier ou affichez une progression — sous forme de cartes natives dans la conversation.

Les widgets transforment une question en carte à laquelle l'utilisateur répond d'un geste. Ils sont envoyés dans la conversation chiffrée comme n'importe quel message, affichés nativement sur le web et sur mobile, et la réponse revient à votre agent sous forme de nouveau message.

WidgetPour…Valeur de la réponse
ConfirmationObtenir un oui/non avant d'agirtrue ou false
ChoixChoisir une ou plusieurs options"id", ["id", …] ou { selected, other }
AutorisationDemander un accès, une fois ou pour la session{ granted, scope }
FormulaireRecueillir des champs typés{ nomDuChamp: valeur, … }
Date et heureChoisir une date, une heure ou les deux{ mode, value }
FichiersDemander un ou plusieurs fichiers{ files: [...] }
ProgressionAfficher l'avancement d'une tâche{ cancelled: true } en cas d'annulation

Principe

Envoyer le widget

Appelez une méthode send…Widget avec un id stable. Elle renvoie l'identifiant du widget.

L'utilisateur répond

La carte apparaît dans la conversation. Rien ne se passe de votre côté tant qu'il n'a pas répondu.

Traiter la réponse

Votre fonction reçoit un nouveau message dont widgetResponse contient l'identifiant du widget dans ref, la réponse dans value, et cancelled si l'utilisateur l'a fermé.

client.onMessage(async (message, agent) => {
  const answer = message.widgetResponse;
  if (answer) {
    if (answer.cancelled) return;
    if (answer.ref === "confirm-cleanup" && answer.value === true) {
      await cleanUp();
      await agent.reply(message, { text: "Cache vidé." });
    }
    return;
  }

  // Un message ordinaire : on demande avant d'agir.
  await agent.sendConfirmWidget({
    conversationId: message.conversationId,
    to: message.from,
    id: "confirm-cleanup",
    title: "Vider le cache local ?",
    body: "Cela supprime 2,3 Go de fichiers générés.",
    danger: true,
    labels: { yes: "Vider", no: "Conserver" },
  });
});
Donnez à chaque widget un identifiant parlant

C'est lui qui relie une réponse à sa question. Utilisez une valeur stable et explicite — ou générez un identifiant unique par demande en retenant à quoi il correspond.

Tous les widgets acceptent title (obligatoire), body, un id (widget_id en Python) et une expiration facultative, expiresAt (expires_at), après laquelle la carte ne peut plus recevoir de réponse.

Confirmation

Une question oui/non. Activez danger pour une action destructive et personnalisez les boutons.

await agent.sendConfirmWidget({
  conversationId: message.conversationId,
  to: message.from,
  id: "confirm-deploy",
  title: "Déployer en production ?",
  body: "Version 2.4.0 — 14 commits depuis la dernière release.",
  labels: { yes: "Déployer", no: "Pas maintenant" },
});

Réponse : true ou false.

Choix

Une liste d'options. Activez multi pour autoriser plusieurs réponses.

await agent.sendChoiceWidget({
  conversationId: message.conversationId,
  to: message.from,
  id: "export-format",
  title: "Quel format ?",
  options: [
    { id: "pdf", label: "PDF" },
    { id: "docx", label: "Word" },
    { id: "md", label: "Markdown" },
  ],
});

Réponse : l'identifiant de l'option ("pdf"), un tableau d'identifiants avec multi (["pdf", "md"]), ou { "selected": [...], "other": "…" } quand l'utilisateur saisit sa propre réponse. Ajoutez danger: true à une option pour la mettre en évidence.

Autorisation

Demande à l'utilisateur d'autoriser une action, selon les portées proposées : once (une fois) ou session.

await agent.sendPermissionWidget({
  conversationId: message.conversationId,
  to: message.from,
  id: "read-downloads",
  title: "Lire votre dossier Téléchargements ?",
  body: "Pour retrouver les factures du mois dernier.",
  scopes: ["once", "session"],
});

Réponse : { "granted": true, "scope": "once" } ou { "granted": false }.

Appliquez les autorisations dans le code

Ce widget enregistre seulement la décision de l'utilisateur. Pour lier un accord à une action, une ressource et une durée précises — et refuser tout le reste — utilisez la politique d'autorisation.

Formulaire

Des champs typés : text, textarea, number, email ou checkbox. Chaque champ peut être required et avoir un placeholder ; les nombres acceptent min et max.

await agent.sendFormWidget({
  conversationId: message.conversationId,
  to: message.from,
  id: "trip",
  title: "Préparer le voyage",
  submitLabel: "Rechercher",
  fields: [
    { name: "destination", label: "Destination", type: "text", required: true },
    { name: "travelers", label: "Voyageurs", type: "number", min: 1, max: 9 },
    { name: "notes", label: "Autre chose ?", type: "textarea" },
    { name: "flexible", label: "Dates flexibles", type: "checkbox" },
  ],
});

Réponse : un objet indexé par nom de champ — { "destination": "Lisbonne", "travelers": 2, "notes": "", "flexible": true }.

Date et heure

mode vaut date, time ou datetime. Limitez la plage avec min et max (ISO 8601).

await agent.sendDateTimeWidget({
  conversationId: message.conversationId,
  to: message.from,
  id: "meeting",
  title: "Quand dois-je réserver la réunion ?",
  mode: "datetime",
  min: "2026-10-01T09:00:00",
});

Réponse : { "mode": "datetime", "value": "2026-10-03T14:30" }.

Fichiers

Demande des fichiers à l'utilisateur. Limitez-les avec accept (types MIME ou extensions), multiple et maxFiles.

await agent.sendFilePickerWidget({
  conversationId: message.conversationId,
  to: message.from,
  id: "brief",
  title: "Envoyez-moi le cahier des charges",
  accept: ["application/pdf", ".md"],
  multiple: true,
  maxFiles: 3,
});

Réponse : { "files": [{ "id", "name", "mime", "size", ... }] }. Les fichiers eux-mêmes arrivent dans message.attachments ; lisez-les avec downloadAttachment — voir Fichiers et médias.

Progression

Une barre de progression que vous mettez à jour pendant une tâche. Avec cancellable, l'utilisateur peut vous demander d'arrêter.

const progressId = await agent.sendProgressWidget({
  conversationId: message.conversationId,
  to: message.from,
  id: "import",
  title: "Import des photos",
  value: 0,
  max: 100,
  cancellable: true,
});

// Plus tard, au fil du travail :
await agent.sendWidgetUpdate({
  conversationId: message.conversationId,
  to: message.from,
  ref: progressId,
  spec: { value: 60, body: "742 photos sur 1 230" },
});

Réponse : uniquement si l'utilisateur annule — { "cancelled": true }. Arrêtez la tâche et confirmez-le.

sendWidgetUpdate fonctionne pour tous les widgets : modifiez son spec (titre, texte, valeur…) ou son expiration après l'envoi.

Bonnes pratiques

  • Demandez avant d'agir. Confirmation ou Autorisation avant toute action destructive, coûteuse ou externe.
  • Une question par widget. Titre court, une ligne de contexte dans body.
  • Ne demandez jamais de secrets comme un mot de passe ou un numéro de carte dans un formulaire.
  • Validez les réponses comme toute saisie utilisateur — types, plages et tailles de fichiers.
  • Bouclez la boucle. Après une réponse, dites ce que vous avez fait.

Plusieurs agents ou personnes dans un groupe ? Les widgets Choix et Confirmation permettent des votes collectifs.