Extensibilité des annotations

L’API d’annotation du navigateur permet à votre site web de personnaliser ce que les utilisateurs sélectionnent, le contexte qui accompagne leurs commentaires et les commandes qu’ils utilisent pour prévisualiser les modifications avant d’envoyer une annotation à ChatGPT.

Les annotations du navigateur fonctionnent sur votre site sans modification du code. Les utilisateurs peuvent sélectionner une partie d’une page, ajouter un commentaire et l’envoyer dans le contexte à Codex ou à ChatGPT Work.

En tant que développeur, vous pouvez utiliser l’API d’annotation du navigateur pour fournir du contexte ou des commandes propres à votre application. Par exemple, vous pouvez joindre des aperçus de variantes de composants dans un aperçu de système de conception pour aider les développeurs à savoir comment mettre à jour les composants d’un site web.

Pour obtenir de l’aide afin de comprendre l’API d’annotation du navigateur ou d’ajouter la prise en charge des annotations à votre site web, installez le plugin Annotations Extensibility.

Installer le plugin Annotations Extensibility

Essayez

Découvrez l’API d’annotation du navigateur à l’œuvre dans ce guide.

  1. Ouvrez cette page dans le navigateur intégré de ChatGPT.
  2. Essayez d’ouvrir le prompt suggéré qui apparaîtra dans cette carte.
  3. Activez le mode Annotation, puis sélectionnez le tableau ci-dessous ou un exemple de code pour passer d’une disposition ou d’un thème prédéfini à l’autre.
  4. Vous pouvez toujours annoter n’importe quel élément de la page et observer le comportement par défaut des annotations.

Ouvrez cette page dans le navigateur intégré de ChatGPT pour essayer les annotations.

Ouvrir dans le navigateur de ChatGPT

Choisir les éléments à personnaliser

Commencez par l’intégration adaptée à votre site web :

Objectif Intégration
Rendre une carte ou un autre groupe d’éléments sélectionnable comme un seul objet Cibles de sélection
Permettre aux utilisateurs de sélectionner une expression ou une phrase Conteneurs de sélection de texte
Inclure du contexte supplémentaire avec une sélection Métadonnées de sélection
Ouvrir une annotation depuis votre propre bouton avec un commentaire suggéré Demandes d’annotation
Demander un avis sur un passage précis depuis votre propre interface Demandes portant sur une plage de texte
Afficher des commandes avancées à l’ouverture d’une annotation Paramètres par défaut de l’éditeur
Prévisualiser les propriétés de l’application ou recueillir des choix Commandes personnalisées
Sélectionner des objets individuels dessinés dans un canvas Surfaces d’annotation
Activer ou désactiver le mode Annotation depuis votre site Commandes du mode Annotation

Ce guide concerne la version DevDay 2026 de l’application de bureau ChatGPT et les versions ultérieures. L’API JavaScript est accessible via document.oai.annotation dans le navigateur intégré de l’application, sur les pages sécurisées de premier niveau, par exemple en HTTPS ou sur localhost. Lorsqu’elle est activée, le navigateur l’installe avant l’exécution des scripts de votre page. Détectez la disponibilité de chaque méthode pour prendre en charge les navigateurs plus anciens ou non compatibles, puis initialisez votre intégration une fois ses éléments DOM présents. Aucun événement de disponibilité ni interrogation périodique n’est nécessaire.

Les méthodes de l’API renvoient leur résultat de manière synchrone, et les handles d’enregistrement sont utilisables immédiatement. Le navigateur peut terminer le chargement de l’éditeur d’annotations ensuite. Le callback hitTest d’une surface peut renvoyer une promesse.

Personnaliser les cibles de sélection

Par défaut, le mode Annotation sélectionne des éléments du DOM de la page, en privilégiant des cibles telles que le texte, les images et les commandes. Pour rendre un objet plus grand sélectionnable, marquez la région qui le contient avec oai-annotation-container et ses descendants sélectionnables avec oai-annotatable.

Cet exemple rend une carte de graphique sélectionnable comme un seul objet :

<section oai-annotation-container>
  <article id="chart-card" oai-annotatable="Q3 Revenue">
    <h2>Q3 Revenue</h2>
    <p>$120,000 this quarter</p>
    <button type="button">More info</button>
  </article>
</section>

Pointer n’importe où dans la carte met toute la carte en surbrillance. La valeur facultative oai-annotatable donne à l’objet un nom présenté à l’utilisateur et au modèle. Choisissez des noms qui permettent de distinguer les objets voisins, ou omettez cette valeur.

Le conteneur définit où ces règles de sélection s’appliquent. Un attribut oai-annotatable utilisé seul ne modifie pas le comportement de sélection. Dans un conteneur, le navigateur sélectionne la cible marquée la plus proche contenant l’élément sous le pointeur. Si les conteneurs sont imbriqués, le conteneur le plus proche est utilisé. Les zones non marquées à l’intérieur d’un conteneur créent une annotation de page web ; les zones situées en dehors de tous les conteneurs conservent le comportement par défaut.

Activer la sélection de texte

Ajoutez oai-annotation-container-text à une région pour permettre aux utilisateurs de faire glisser le pointeur afin de sélectionner du texte pendant le mode Annotation. L’attribut n’a pas besoin de valeur :

<article oai-annotation-container-text>
  <h2>Design guidelines</h2>
  <p>Use consistent spacing between related components.</p>
  <p>Leave more space between separate groups.</p>
</article>

Relâcher le pointeur après une sélection non vide ouvre l’éditeur d’annotations de texte avec la plage sélectionnée et son contexte. Cliquer sans sélectionner de texte ne crée pas d’annotation. Échap ou l’annulation du geste annule la sélection.

Le conteneur de sélection de texte ou de DOM le plus proche détermine comment un geste commence. Les conteneurs de texte n’utilisent pas les marqueurs oai-annotatable pour sélectionner des éléments. Imbriquez un oai-annotation-container pour rétablir la sélection d’éléments, ou un conteneur de texte pour rétablir la sélection de texte. Si les deux attributs figurent sur un même élément, la sélection de texte est prioritaire.

La sélection de texte suit les règles de sélection normales de la page et peut s’étendre au-delà du conteneur de départ. Un conteneur ne limite pas la plage et n’active pas la sélection à l’intérieur d’un iframe. Les champs de texte restent sélectionnables, mais les clics ordinaires sur la page et les actions natives sur les commandes restent bloqués pendant le mode Annotation. Les appels explicites à request() conservent leur comportement existant.

Ajouter du contexte à une sélection

Ajoutez oai-annotation-metadata à une cible marquée pour inclure du contexte qui n’est pas nécessairement visible sur la page. Par exemple, une ligne de contact fictif peut inclure une adresse e-mail pour une demande de rédaction d’e-mail :

<section oai-annotation-container>
  <div
    id="contact-row"
    oai-annotatable="Alex Morgan"
    oai-annotation-metadata='{"email":"alex@example.com"}'
  >
    <div>Alex Morgan</div>
    <div>Project lead</div>
  </div>
</section>

Sélectionner l’une ou l’autre des lignes sélectionne toute la ligne de contact. Les métadonnées apparaissent avec l’annotation et l’accompagnent dans la conversation. N’incluez que le contexte que vous souhaitez partager à la fois avec l’utilisateur et le modèle. L’envoi d’un e-mail nécessiterait toujours un outil de messagerie connecté.

Utilisez un petit objet JSON plat respectant ces limites :

  • Jusqu’à six propriétés, avec des valeurs de type chaîne, nombre fini, booléen ou null.
  • Des clés de 64 caractères maximum et des valeurs de type chaîne de 256 caractères maximum.
  • Jusqu’à 2 048 octets pour l’objet sérialisé.

Les clés doivent commencer par une lettre ASCII et ne contenir que des lettres ASCII, des chiffres, des espaces, des traits de soulignement ou des traits d’union. Utilisez un seul espace entre les mots. Les objets imbriqués et les tableaux ne sont pas pris en charge. Le navigateur ignore les métadonnées non valides.

Vous pouvez aussi fournir des métadonnées via request() ou le résultat hitTest d’une surface.

Ouvrir une annotation depuis votre site web

Appelez document.oai.annotation.request(target, options) directement à partir d’une interaction utilisateur, comme l’appui sur un bouton. La cible peut être un élément HTML rattaché au DOM dans la zone visible du document actuel ou un Range du DOM. Elle n’a pas besoin d’attributs d’annotation. Les identifiants d’objets virtuels ne sont pas pris en charge.

Les demandes initiées par le site peuvent nécessiter l’autorisation de l’utilisateur ; les utilisateurs peuvent réactiver les fonctionnalités d’annotation bloquées sous Outils du site > Fonctionnalités d’annotation.

Ajoutez ce bouton à côté de la carte de graphique de l’exemple de sélection, puis exécutez le script une fois les deux éléments présents :

<button id="discuss-chart" type="button" hidden>Explain more</button>
const card = document.getElementById("chart-card");
const button = document.getElementById("discuss-chart");

button.hidden = typeof document.oai?.annotation?.request !== "function";

button.addEventListener("click", () => {
  const annotation = document.oai?.annotation;
  if (typeof annotation?.request !== "function") return;

  annotation.request(card, {
    initialComment: "Explain the latest trend in this graph.",
  });
});

Sélectionner Expliquer davantage demande au navigateur d’ouvrir une annotation avec la carte sélectionnée et un commentaire modifiable. L’utilisateur peut le modifier, l’enregistrer et l’envoyer avec son message. Ouvrir une annotation n’envoie pas de message à ChatGPT ; seul l’utilisateur peut l’envoyer.

Conservez l’appel dans l’interaction utilisateur active. Attendre d’abord la fin d’une requête réseau peut faire perdre cette interaction.

Le deuxième argument, facultatif, prend en charge les champs suivants :

Option Comportement
mode Utilisez "advanced" pour ouvrir les commandes avancées d’un élément, ou "default" pour utiliser le comportement par défaut de l’éditeur. La valeur par défaut est "default". Les commandes personnalisées peuvent tout de même s’ouvrir avec "default" ; voir Paramètres par défaut de l’éditeur. Les plages de texte ne prennent en charge que "default".
enterAnnotationMode Définissez cette option sur true pour activer le mode Annotation et y rester après l’annulation ou l’envoi de l’annotation.
metadata Des métadonnées valides et non vides remplacent les métadonnées HTML de la cible pour cette demande. Cette option est ignorée pour les plages de texte et lorsque l’élément cible se trouve dans un DOM fantôme.
initialComment Fournit un commentaire modifiable, limité à 240 unités de code UTF-16.

request() renvoie un objet contenant un booléen accepted. Vérifiez result.accepted, et non l’objet résultat, pour savoir si le navigateur a reçu et validé la demande. Cela ne confirme ni que l’éditeur s’est ouvert ni que l’utilisateur a enregistré ou envoyé une annotation.

Pendant le chargement de l’éditeur, le navigateur peut conserver une demande acceptée en attente. Il peut refuser une autre demande si une demande est en attente, si un éditeur est ouvert ou si ChatGPT contrôle le navigateur.

Demander une annotation pour une plage de texte

Transmettez un Range du DOM pour demander un avis sur un passage sans modifier la sélection de texte du navigateur. Cet exemple sélectionne le contenu du paragraphe ; il n’a pas besoin d’un attribut oai-annotation-container-text :

<p id="draft-passage">Leave more space between separate groups.</p>
<button id="discuss-passage" type="button" hidden>Discuss this passage</button>

Exécutez ce script une fois les deux éléments présents :

const passage = document.getElementById("draft-passage");
const button = document.getElementById("discuss-passage");

button.hidden = typeof document.oai?.annotation?.request !== "function";

button.addEventListener("click", () => {
  const annotation = document.oai?.annotation;
  if (typeof annotation?.request !== "function") return;

  const range = document.createRange();
  range.selectNodeContents(passage);
  annotation.request(range, {
    initialComment: "Suggest a clearer version of this guidance.",
  });
});

La plage doit contenir du texte visible non vide dans le document actuel, avec au moins une partie de la sélection dans la zone visible de la page. Elle peut contenir au maximum 20 000 unités de code UTF-16. Les plages réduites à un point, le texte composé uniquement d’espaces, le texte sélectionné masqué et les plages situées dans des racines fantômes fermées ne sont pas pris en charge.

Les demandes portant sur une plage de texte utilisent uniquement l’éditeur de texte par défaut. Une demande avec mode: "advanced" est rejetée, et les métadonnées de la demande sont ignorées. Gardez le texte cible disponible pendant qu’une demande acceptée attend l’éditeur : le navigateur vérifie à nouveau la plage avant de l’ouvrir. Les conteneurs de sélection de texte permettent, indépendamment, aux utilisateurs de faire glisser le pointeur pour sélectionner du texte en mode Annotation.

Choisir le mode par défaut de l’éditeur

Pour afficher immédiatement les commandes avancées pour les annotations ouvertes depuis l’interface de sélection du navigateur, ajoutez cette balise au <head> de votre page :

<meta name="oai-annotation-editor-default-mode" content="advanced" />

Pour les annotations ouvertes depuis votre propre interface, transmettez { mode: "advanced" } à request(). Utilisez mode: "default", ou omettez cette option, pour obtenir le comportement par défaut de l’éditeur. Le paramètre meta de la page ne remplace pas cette option de demande. Le mode par défaut ne garantit pas un éditeur limité au commentaire : les commandes personnalisées avec des valeurs initiales proposées, ou les commandes qui omettent currentValue, peuvent ouvrir l’éditeur de commandes.

Les commandes manuelles Ajuster, de réduction et Option-clic ne sont disponibles que dans Codex ou sur localhost. Sur les sites hébergés dans ChatGPT, les commandes personnalisées enregistrées apparaissent automatiquement sans bouton Ajuster ni bouton de réduction. Le mode avancé demandé par la page continue d’y fonctionner.

Ajouter des commandes personnalisées

Utilisez registerControls() pour associer des commandes d’annotation à un ou plusieurs éléments DOM. Les commandes peuvent prévisualiser des propriétés de l’application, comme un jeton d’espacement, ou recueillir des choix à inclure dans une demande, comme le ton d’un e-mail.

Prévisualiser un jeton d’espacement partagé

Les deux cartes de cet exemple utilisent la même propriété CSS :

<style>
  #component-preview {
    --card-padding: 16px;
  }

  .preview-card {
    padding: var(--card-padding);
    border: 1px solid #d1d5db;
  }
</style>

<section id="component-preview" oai-annotation-container>
  <article class="preview-card" oai-annotatable="Profile card">
    Profile card
  </article>
  <article class="preview-card" oai-annotatable="Summary card">
    Summary card
  </article>
</section>

Exécutez ce script après avoir créé l’aperçu :

const preview = document.getElementById("component-preview");
const annotation = document.oai?.annotation;

function previewSpacing(event) {
  const { callback, value } = event.detail;
  if (callback === "setCardPadding") {
    preview.style.setProperty("--card-padding", `${value}px`);
  }
}

let registration;
if (typeof annotation?.registerControls === "function") {
  preview.addEventListener("oaiannotationcontrolchange", previewSpacing);
  registration = annotation.registerControls({
    targets: preview.querySelectorAll(".preview-card"),
    controlsHeading: "Card spacing",
    controlsMode: "replace",
    controls: [
      {
        type: "range",
        label: "Card padding (pixels)",
        callback: "setCardPadding",
        reference: "--card-padding",
        min: 8,
        max: 32,
        step: 4,
        currentValue: 16,
      },
    ],
  });
}

function disposeAnnotationControls() {
  preview.style.removeProperty("--card-padding");
  registration?.dispose();
  preview.removeEventListener("oaiannotationcontrolchange", previewSpacing);
}

Annotez l’une des cartes et faites passer Marge intérieure de la carte (pixels) de 16 à 24. Sur les sites hébergés dans ChatGPT, les commandes apparaissent automatiquement ; dans Codex ou sur localhost, sélectionnez Ajuster si nécessaire. Les deux cartes se mettent à jour. L’annotation enregistre le libellé, la référence, ainsi que l’ancienne et la nouvelle valeur. Effacer l’aperçu rétablit la marge intérieure d’origine. Appelez disposeAnnotationControls() lors de la suppression du composant.

Recueillir un choix sans aperçu

Utilisez la ligne de contact de l’exemple de métadonnées pour proposer un ton d’e-mail :

const contact = document.getElementById("contact-row");
const registration = document.oai?.annotation?.registerControls?.({
  targets: contact,
  controlsHeading: "Email options",
  controlsMode: "replace",
  controls: [
    {
      type: "select",
      label: "Email tone",
      callback: "emailTone",
      options: [
        { label: "Professional", value: "professional" },
        { label: "Friendly", value: "friendly" },
        { label: "Direct", value: "direct" },
      ],
      defaultValue: "professional",
    },
  ],
});

Cette commande n’a pas besoin de gestionnaire d’événements, car elle ne prévisualise aucune modification de la page. Omettre currentValue indique au navigateur d’inclure le ton sélectionné même si l’utilisateur conserve l’option initiale. Appelez registration?.dispose() lors de la suppression de la ligne.

Pour les commandes de sélection, les callbacks d’aperçu reçoivent option.value, par exemple "professional". L’historique des annotations et ChatGPT reçoivent le option.label visible, par exemple "Professional", pour l’option précédente comme pour l’option sélectionnée. Utilisez des libellés qui expliquent chaque choix ; les identifiants internes dans value ne sont pas envoyés comme texte du choix.

Configurer les commandes et les valeurs initiales

Utilisez controlsMode: "replace" pour afficher uniquement vos commandes pour les cibles enregistrées, ou "extend" pour les afficher à côté des commandes intégrées. Un controlsHeading facultatif donne un nom au panneau. Le navigateur supprime les espaces en début et en fin de valeur et accepte de 1 à 80 caractères. Sans titre, le panneau affiche la balise HTML de l’élément. Le titre n’est pas inclus dans le contexte envoyé à ChatGPT.

Un enregistrement prend en charge jusqu’à 12 commandes. Chacune nécessite un type, un label visible et un identifiant callback :

Type Valeur Champs supplémentaires
color Couleur hexadécimale, par exemple "#2563eb" Aucun
range Nombre min, max et step
select Chaîne options, un tableau d’objets { label, value }
toggle Booléen Aucun

callback est un identifiant de type chaîne, et non une fonction JavaScript. Rendez-le unique dans l’enregistrement, commencez-le par une lettre ASCII et utilisez des lettres ASCII, des chiffres, des traits de soulignement ou des traits d’union. Un reference facultatif identifie la propriété modifiée et accompagne le libellé et la valeur dans l’annotation.

Définissez currentValue sur la valeur normale valide de la propriété, en incluant les modifications non enregistrées. N’utilisez pas l’état de l’aperçu ni une saisie inachevée comme valeur de référence. Le navigateur l’utilise pour les comparaisons avant-après et les réinitialisations. Lorsque l’état de l’application change, utilisez registration.update({ controls }) pour actualiser les valeurs currentValue des commandes. Les mises à jour s’appliquent aux futures annotations ; les annotations existantes conservent leurs valeurs capturées.

Définissez defaultValue pour suggérer une valeur initiale ; elle est prioritaire sur currentValue pour l’état initial de la commande. Si vous omettez les deux, la commande commence avec du blanc pour une couleur, le minimum pour une plage, la première option pour une sélection ou false pour un interrupteur.

Gardez les valeurs proposées séparées des brouillons ordinaires. Si une mise à jour lève une exception ou si une demande échoue ou renvoie accepted: false, abandonnez la proposition tentée et rétablissez l’état précédent des commandes et de la proposition. Conservez les propositions lorsqu’une demande est acceptée : le navigateur peut la mettre en attente avant de capturer les commandes.

Valider les commandes et les enregistrements

Le navigateur valide les enregistrements et les mises à jour en appliquant les limites suivantes :

Champ ou ressource Contrainte
Commandes Jusqu’à 12 par enregistrement, avec des identifiants callback uniques. Utilisez uniquement les champs définis pour le type de commande.
Libellés et titres Les libellés des commandes, les libellés des options et controlsHeading doivent être non vides après suppression des espaces en début et en fin de valeur, et ne pas dépasser 80 unités de code UTF-16. Le texte des commandes ne peut pas contenir de caractères de contrôle ni de caractères modifiant le sens d’écriture.
Identifiants callback est limité à 80 unités de code UTF-16 après suppression des espaces en début et en fin de valeur, selon le format décrit ci-dessus. reference contient de 1 à 80 caractères et autorise les lettres ASCII, les chiffres et _ . / : @ $ # -, sans espaces.
Options de sélection De 1 à 12 options avec des chaînes value distinctes d’au plus 512 unités de code UTF-16. Les valeurs currentValue et defaultValue fournies doivent correspondre à la valeur d’une option.
Valeurs de plage min, max, currentValue et defaultValue doivent être des nombres finis compris entre −10 000 et 10 000. Les exigences sont min < max, un step compris entre 0,001 et 10 000 et ne dépassant pas max - min, ainsi que des valeurs initiales comprises dans la plage. Les valeurs initiales n’ont pas à s’aligner sur step.
Couleurs et interrupteurs Les couleurs doivent comporter 3, 4, 6 ou 8 chiffres hexadécimaux après #. Les valeurs des interrupteurs doivent être true ou false.
Cibles De 1 à 128 entrées cibles lors de l’enregistrement, toutes étant des éléments du document actuel. Les mises à jour peuvent transmettre targets: [] pour les détacher. Chaque document prend en charge jusqu’à 64 enregistrements de commandes et 1 024 associations entre enregistrements et cibles.
Commandes sérialisées La charge utile JSON contenant controls, controlsHeading et controlsMode ne doit pas dépasser 16 384 unités de code UTF-16. Utilisez des données encodables en JSON, sans fonctions, symboles ni grands entiers.

registerControls() et registration.update() peuvent lever une exception de manière synchrone en cas de définitions ou de cibles non valides, ou de dépassement des limites. Une mise à jour après dispose() lève également une exception. Une mise à jour rejetée préserve l’enregistrement précédent, y compris ses commandes et ses cibles. Gérez les échecs au point d’appel, préservez le fonctionnement de l’édition ordinaire et réutilisez ou libérez les enregistrements dont vous êtes responsable pour respecter les limites.

Gérer les aperçus et les réinitialisations

L’événement oaiannotationcontrolchange se propage depuis l’élément sélectionné. Son detail contient callback, value et action :

Action Appliquer la valeur fournie pour
preview Afficher la modification demandée.
preview-original Afficher temporairement l’état d’origine pour comparaison.
reset Rétablir l’état d’origine lorsque l’aperçu est effacé.

Appliquez la valeur fournie pour chaque action, comme dans l’exemple d’espacement. Rendez les gestionnaires réversibles et sûrs en cas d’appels répétés. Les événements d’aperçu ne demandent pas de modification permanente ; enregistrez via le processus habituel de votre application. Gardez les brouillons ordinaires, les saisies inachevées et les aperçus séparés pour que des modifications sans rapport n’écrasent pas un aperçu. Lorsqu’une personne modifie le même paramètre, remplacez son aperçu et mettez à jour la valeur de référence pour les futures annotations.

Conservez le handle d’enregistrement pour les mises à jour et le nettoyage. update() accepte toute combinaison de targets, controls, controlsHeading et controlsMode. Les champs omis conservent leurs valeurs précédentes. Par exemple, utilisez registration.update({ targets: newElement }) lors du remplacement de l’élément DOM d’un composant. Les collections de cibles capturent les éléments existants et ne suivent pas les futurs éléments correspondant au sélecteur.

Transmettez targets: [] pour détacher l’enregistrement ou controls: [] pour effacer ses commandes. Appelez dispose() et supprimez les écouteurs d’événements lors du retrait de l’intégration. Le nettoyage doit également rétablir le rendu normal : dispose() supprime uniquement l’enregistrement des commandes et n’annule pas vos modifications d’aperçu. L’exemple d’espacement supprime sa surcharge de style en ligne pour rétablir la valeur CSS d’origine. Les deux méthodes reviennent de manière synchrone sans renvoyer de valeur. Les mises à jour s’appliquent aux futures sélections ; les annotations enregistrées conservent leurs commandes et leur cible capturées.

Les événements des commandes ciblent l’élément capturé par l’annotation. Mettre à jour les cibles d’un enregistrement ne redirige pas les annotations existantes. Les événements de réinitialisation peuvent encore se déclencher sur un élément supprimé ; un écouteur sur son ancien parent ne les recevra donc pas.

Rendre les objets d’un canvas sélectionnables

Une surface d’annotation permet à votre application d’identifier des objets individuels dessinés à l’intérieur d’un canvas. Enregistrez l’élément hôte avec registerSurface() et fournissez une fonction hitTest qui renvoie un objet avec un id stable, ou null pour un espace vide.

L’hôte doit être un élément HTML rattaché au DOM, en dehors d’un DOM fantôme, dans un document sécurisé de premier niveau. L’enregistrement de surfaces n’est pas pris en charge à l’intérieur d’un iframe.

Cet exemple dessine une barre représentant le chiffre d’affaires et la rend sélectionnable. Placez le script après le canvas :

<canvas id="revenue-canvas" width="480" height="240">
  Revenue this quarter: $120,000.
</canvas>
const canvas = document.getElementById("revenue-canvas");
const context = canvas.getContext("2d");
const bar = { x: 40, y: 60, width: 320, height: 100 };

function drawRevenue(highlighted = false) {
  context.clearRect(0, 0, canvas.width, canvas.height);
  context.fillStyle = "#2563eb";
  context.fillRect(bar.x, bar.y, bar.width, bar.height);
  if (highlighted) {
    context.strokeStyle = "#111827";
    context.lineWidth = 3;
    context.strokeRect(bar.x, bar.y, bar.width, bar.height);
  }
}

drawRevenue();

const surface = document.oai?.annotation?.registerSurface?.({
  element: canvas,
  hitTest({ clientX, clientY }) {
    const bounds = canvas.getBoundingClientRect();
    const scaleX = bounds.width / canvas.width;
    const scaleY = bounds.height / canvas.height;
    const rect = {
      x: bounds.left + bar.x * scaleX,
      y: bounds.top + bar.y * scaleY,
      width: bar.width * scaleX,
      height: bar.height * scaleY,
    };

    if (
      clientX < rect.x ||
      clientX > rect.x + rect.width ||
      clientY < rect.y ||
      clientY > rect.y + rect.height
    ) {
      return null;
    }

    return {
      id: "revenue-this-quarter",
      name: "Revenue this quarter",
      role: "chart-bar",
      metadata: { Metric: "Revenue", Value: 120000 },
      rect,
    };
  },
  renderSelection({ hoveredId, selectedId }) {
    drawRevenue(
      hoveredId === "revenue-this-quarter" ||
        selectedId === "revenue-this-quarter"
    );
  },
});

Survoler la barre en mode Annotation la met en surbrillance. La sélectionner ouvre une annotation contenant le nom de l’objet, ses métadonnées et une capture d’écran de la sélection.

Gardez les identifiants stables au sein d’une surface. Le name facultatif est visible par l’utilisateur ; role fournit une courte description sémantique. Le rect facultatif utilise des pixels CSS relatifs à la zone visible de la page, en accord avec clientX et clientY. Convertissez les coordonnées de la scène, en tenant compte de l’échelle, du déplacement et du zoom.

hitTest peut renvoyer une promesse et reçoit un AbortSignal en tant que signal pour annuler un traitement devenu obsolète. Le navigateur accorde 250 millisecondes avant de revenir à la sélection DOM. Les erreurs et les résultats non valides déclenchent également ce repli. Renvoyez null explicitement pour un test de ciblage réussi sans objet.

Un traitement annulé doit quand même résoudre ou rejeter sa promesse. Le navigateur n’autorise qu’un seul callback hitTest en cours et conserve cet emplacement occupé jusqu’à ce que la promesse soit résolue ou rejetée, même après une annulation ou un dépassement du délai. Si un worker gère la sélection, résolvez ou rejetez la promesse en attente lorsque son traitement est interrompu ; ignorer la réponse d’un worker annulé peut bloquer les sélections ultérieures dans le canvas.

Utilisez le callback facultatif renderSelection pour fournir un retour propre à l’application. Effacez ce retour lorsque les deux identifiants sont null. Appelez surface?.invalidate() après avoir déplacé des objets ou changé le zoom, et surface?.dispose() lors du retrait de l’intégration. Les deux reviennent de manière synchrone sans renvoyer de valeur.

Ajouter des commandes aux objets d’un canvas

Enregistrez des commandes personnalisées sur l’élément DOM de la surface. Les objets dessinés, appelés objets virtuels dans l’API, utilisent vos commandes personnalisées ; les commandes CSS et de texte intégrées ne s’appliquent pas à eux.

Pour plusieurs objets, utilisez renderSelection pour mettre à jour les commandes de l’hôte lorsque selectedId change, avant que le navigateur ne capture l’annotation. Les événements des commandes incluent detail.virtualTarget: { surfaceId, targetId }. Acheminez chaque événement en utilisant cette identité capturée et son callback, plutôt que la sélection actuelle. targetId correspond au id de hitTest ; surfaceId identifie l’enregistrement de la surface dans le navigateur. Les événements DOM ordinaires omettent virtualTarget.

Les événements d’aperçu, de comparaison et de réinitialisation conservent l’identité de l’objet d’origine après la sélection d’un autre objet. Rouvrir une annotation enregistrée ne relance pas le test de ciblage pour remplacer l’objet ou les métadonnées qu’elle a capturés.

Contrôler le mode Annotation

Utilisez toggle() pour demander un changement de mode, isActive() pour lire l’état confirmé, et l’événement oaiannotationmodechange du document pour synchroniser votre interface. Détectez la disponibilité des deux méthodes pour les navigateurs plus anciens ou non compatibles. Ajoutez ce bouton, puis exécutez le script une fois celui-ci présent :

<button id="toggle-annotations" type="button" hidden>
  Enter annotation mode
</button>
const button = document.getElementById("toggle-annotations");
const annotation = document.oai?.annotation;

function renderMode(active) {
  button.textContent = active
    ? "Exit annotation mode"
    : "Enter annotation mode";
}

const onModeChange = (event) => renderMode(event.detail.active);
const onClick = () => annotation.toggle(!annotation.isActive());

if (
  typeof annotation?.toggle === "function" &&
  typeof annotation?.isActive === "function"
) {
  document.addEventListener("oaiannotationmodechange", onModeChange);
  button.addEventListener("click", onClick);
  renderMode(annotation.isActive());
  button.hidden = false;
}

function cleanupAnnotationButton() {
  document.removeEventListener("oaiannotationmodechange", onModeChange);
  button.removeEventListener("click", onClick);
  button.hidden = true;
}

toggle() inverse le mode. Transmettez true pour vous assurer qu’il est activé, ou false pour vous assurer qu’il est désactivé. Les demandes répétées avec le même booléen sont idempotentes. Transmettre true préserve un éditeur actif ou une activation en attente ; false annule l’activation en attente et utilise le processus normal de sortie.

Appelez toggle() depuis une interaction utilisateur en cours. Son résultat synchrone { accepted } accuse réception de la demande, sans confirmer le changement de mode. Les vérifications d’éligibilité du navigateur peuvent encore empêcher ce changement. Lisez l’état depuis isActive() et l’événement, comme dans l’exemple.

isActive() ne nécessite aucun geste utilisateur. Le navigateur le met à jour avant d’émettre oaiannotationmodechange, dont event.detail.active est un booléen. Le navigateur n’envoie ni événement initial ni doublon lorsqu’une demande explicite ne change pas l’état ; initialisez donc votre interface à partir de l’accesseur. Si l’accès est révoqué pendant que le mode est actif, un dernier événement signale active: false et un accesseur conservé renvoie false.

Le mode Annotation intercepte les clics sur la page. Maintenez Espace pour utiliser les commandes de la page, y compris votre bouton de sortie, ou quittez le mode via l’interface d’annotation du navigateur. Maintenir Espace seul laisse isActive() à la valeur true. L’API ne prend pas en charge oai-annotation-ignore ni aucun autre attribut permettant de laisser passer les clics.

Quitter le mode ferme l’éditeur et préserve les annotations enregistrées sans les envoyer ni les déplacer dans la zone de rédaction. Appelez cleanupAnnotationButton() lors de la suppression du composant. La référence à l’API capturée permet au nettoyage de supprimer les écouteurs même si l’espace de noms a disparu.

Tester votre intégration

Ouvrez votre site web dans le navigateur intégré de l’application de bureau et testez les fonctionnalités que vous avez ajoutées :

  1. Activez le mode Annotation et sélectionnez des objets et du texte. Vérifiez que les surbrillances, les noms, les plages et les métadonnées correspondent aux cibles prévues.
  2. Ouvrez une annotation depuis le bouton de votre site. Vérifiez l’élément ou la plage de texte sélectionnés, le commentaire initial et le mode de l’éditeur.
  3. Modifiez une commande personnalisée, comparez avec l’original et effacez l’aperçu. Vérifiez que votre application rétablit l’état d’origine.
  4. Pour le contenu d’un canvas, testez les espaces vides, le redimensionnement et les changements de scène. Changez d’objet et confirmez que les événements des commandes mettent toujours à jour la cible capturée. Annulez un test de ciblage asynchrone et confirmez que les sélections suivantes fonctionnent toujours.
  5. Enregistrez, rouvrez et modifiez une annotation depuis l’aperçu des pièces jointes de la zone de rédaction. Vérifiez qu’elle conserve ses commandes et sa cible, et que sa suppression efface tout aperçu.
  6. Envoyez une annotation avec un message. Confirmez que ChatGPT reçoit le contenu sélectionné, les métadonnées et les valeurs demandées, y compris les libellés visibles des options de sélection et les choix inchangés des commandes qui omettent currentValue.
  7. Ouvrez le site dans un navigateur sans l’API et vérifiez que les interactions normales fonctionnent toujours.

Pour exposer des actions que ChatGPT peut effectuer sur votre site web, ajoutez des outils de site (WebMCP). Les annotations apportent la sélection de l’utilisateur et ses commentaires dans la conversation ; les outils de site permettent à l’agent d’agir sur ce contexte grâce aux fonctionnalités existantes de votre application.