Aller au contenu principal

Widgets : interfaces dans ChatGPT

Mis à jour le 29 juillet 2026

Créer des interfaces visuelles dans la conversation

Les Widgets font passer ChatGPT du statut d’assistant textuel à celui de plateforme applicative. Plutôt que de décrire un graphique en mots, vous l’affichez ; plutôt que d’énumérer des options, vous présentez des boutons. Cette leçon détaille le système de widgets du SDK, des composants de base jusqu’à l’affichage conditionnel.

Le système de composants

Vous assemblez des composants fournis par le SDK pour composer vos interfaces. Le principe est déclaratif : vous décrivez ce que vous voulez afficher, jamais comment le rendre. Aucun HTML, aucune feuille de style — c’est ChatGPT qui applique son propre thème, ce qui garantit que votre app aura l’air d’appartenir à la plateforme et restera cohérente en clair comme en sombre.

import { defineWidget } from "@openai/apps-sdk";

export const productCard = defineWidget({
  name: "productCard",
  description: "Affiche les détails d'un produit avec son prix et un bouton d'achat",
  render: (data) => ({
    type: "card",
    title: data.name,
    subtitle: data.category,
    content: [
      { type: "image", src: data.imageUrl, alt: data.name },
      { type: "text", value: data.description },
      {
        type: "grid",
        columns: 2,
        items: [
          { type: "metric", label: "Prix", value: `${data.price} €` },
          { type: "metric", label: "Stock", value: `${data.stock} unités` },
        ],
      },
      {
        type: "button",
        label: "Ajouter au panier",
        action: "addToCart",
        params: { productId: data.id },
        variant: "primary",
      },
    ],
  }),
});

Types de composants disponibles

Composants de mise en page

Ces composants ne portent aucune donnée : ils organisent l’espace. La card sert de conteneur principal et encadre presque toujours le reste ; la grid aligne des éléments de même nature, la stack les empile ; tabs et accordion permettent de replier ce qui n’est pas immédiatement utile, réflexe précieux dans une conversation où la place verticale est comptée.

// Card — conteneur principal
{ type: "card", title: "Titre", subtitle: "Sous-titre", content: [...] }

// Grid — grille responsive
{ type: "grid", columns: 3, gap: "md", items: [...] }

// Stack — empilement vertical ou horizontal
{ type: "stack", direction: "vertical", spacing: "sm", items: [...] }

// Tabs — onglets
{ type: "tabs", items: [
  { label: "Aperçu", content: [...] },
  { label: "Détails", content: [...] },
]}

// Accordion — sections dépliables
{ type: "accordion", items: [
  { title: "Section 1", content: [...] },
]}

Composants de données

Le table accepte directement un tableau d’objets et gère lui-même le tri et la pagination : inutile de découper vos résultats à la main. Le chart couvre les quatre représentations les plus courantes — barres, lignes, camembert, aires. Le metric, enfin, met en avant une valeur unique avec sa tendance, format idéal pour un chiffre qu’on veut voir en un coup d’œil.

// Table — tableau de données
{
  type: "table",
  columns: [
    { key: "name", label: "Nom" },
    { key: "price", label: "Prix", align: "right" },
    { key: "status", label: "Statut" },
  ],
  rows: data.products,
  sortable: true,
  paginated: true,
  pageSize: 10,
}

// Chart — graphiques
{
  type: "chart",
  chartType: "bar", // "line", "pie", "area"
  data: {
    labels: ["Jan", "Fév", "Mar", "Avr"],
    datasets: [{ label: "Ventes", values: [120, 190, 300, 250] }],
  },
}

// Metric — indicateur chiffré
{ type: "metric", label: "Chiffre d'affaires", value: "12 450 €", trend: "+15%" }

Composants interactifs

Le bouton déclenche une action unique, et sa variante (primary, secondary, danger) signale visuellement l’importance ou le danger du geste — réservez danger aux suppressions. Le formulaire regroupe plusieurs champs et n’envoie leur contenu qu’à la soumission, ce qui évite d’interrompre l’utilisateur à chaque frappe.

// Button — bouton d'action
{
  type: "button",
  label: "Confirmer",
  action: "confirmOrder",
  variant: "primary", // "secondary", "danger"
}

// Form — formulaire complet
{
  type: "form",
  fields: [
    { name: "email", label: "Email", inputType: "email", required: true },
    { name: "message", label: "Message", inputType: "textarea" },
    {
      name: "priority",
      label: "Priorité",
      inputType: "select",
      options: [
        { value: "low", label: "Basse" },
        { value: "high", label: "Haute" },
      ],
    },
  ],
  submitAction: "sendMessage",
  submitLabel: "Envoyer",
}

Gérer les interactions

Un widget sans action associée n’est qu’une image. Quand l’utilisateur clique un bouton ou soumet un formulaire, le SDK appelle l’action nommée dans le champ action et lui transmet les params déclarés. Le couple se lit de haut en bas dans l’exemple suivant : le bouton annonce deleteItem et l’identifiant 123, l’action deleteItem les reçoit comme paramètres typés.

// Le bouton dans le widget
{ type: "button", label: "Supprimer", action: "deleteItem", params: { id: "123" } }

// L'action correspondante
app.action("deleteItem", {
  description: "Supprime un élément par son identifiant",
  parameters: {
    id: { type: "string", required: true },
  },
  handler: async ({ id }) => {
    await db.items.delete(id);
    return { success: true, message: "Élément supprimé" };
  },
});

Mettre à jour un widget

Une interaction laisse rarement l’affichage inchangé. En ajoutant la clé _widget à la valeur de retour de votre action, vous demandez le rendu à nouveau du widget avec les données fraîches. Ci-dessous, cliquer sur l’étoile des favoris bascule l’état en base puis renvoie le produit mis à jour : la carte se redessine avec l’étoile pleine, sans que l’utilisateur ait à reposer sa question.

app.action("toggleFavorite", {
  description: "Ajoute ou retire un produit des favoris",
  parameters: { productId: { type: "string", required: true } },
  handler: async ({ productId }, context) => {
    const isFav = await toggleFavorite(context.user.id, productId);
    return {
      _widget: "productCard", // Re-render le widget avec les nouvelles données
      ...await getProduct(productId),
      isFavorite: isFav,
    };
  },
});

Widgets dynamiques et conditionnels

La fonction render étant du TypeScript ordinaire, rien ne vous empêche d’y mettre de la logique. Le suivi de commande ci-dessous en tire deux effets : la couleur du statut change selon l’avancement, et le bouton « Suivre le colis » n’apparaît que si une URL de suivi existe. Un widget qui affiche un bouton inopérant, parce que la commande n’est pas encore expédiée, fait douter de la fiabilité de toute l’application.

export const orderStatus = defineWidget({
  name: "orderStatus",
  render: (data) => ({
    type: "card",
    title: `Commande #${data.orderId}`,
    content: [
      {
        type: "status",
        value: data.status,
        color: data.status === "delivered" ? "green" :
               data.status === "shipped" ? "blue" : "orange",
      },
      // Afficher le suivi seulement si expédié
      ...(data.trackingUrl ? [{
        type: "button",
        label: "Suivre le colis",
        action: "openUrl",
        params: { url: data.trackingUrl },
      }] : []),
      // Afficher les articles
      {
        type: "table",
        columns: [
          { key: "name", label: "Article" },
          { key: "qty", label: "Qté" },
          { key: "price", label: "Prix" },
        ],
        rows: data.items,
      },
    ],
  }),
});

Vous avez maintenant tout ce qu’il faut pour composer un écran complet. Construisez un widget userDashboard qui présente un en-tête avec le nom et l’avatar de l’utilisateur, une grille de trois métriques — commandes, dépenses, points fidélité —, un tableau des cinq dernières commandes et un bouton « Voir toutes les commandes ». Puis testez-le avec un utilisateur sans aucune commande : si l’écran affiche un tableau vide et des zéros, revenez à l’affichage conditionnel et prévoyez ce cas.

Points clés à retenir

  • Les widgets sont déclaratifs : vous décrivez la structure, le SDK gère le rendu
  • Les composants couvrent la mise en page, les données et les interactions
  • Les boutons et formulaires déclenchent des actions côté serveur
  • Un widget peut être mis à jour dynamiquement après une interaction
  • L’affichage conditionnel permet d’adapter l’interface aux données