Grâce à l’API d’annotation du navigateur, votre site peut définir ce que les utilisateurs peuvent sélectionner, le contexte qui accompagne leurs commentaires et les commandes qui leur permettent de prévisualiser les modifications avant d’envoyer une annotation à ChatGPT.
Dans les espaces de travail ChatGPT Enterprise et ChatGPT Edu, un administrateur doit autoriser les personnalisations des annotations par les sites dans la politique de l’espace de travail.
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 à Codex ou à ChatGPT Work via Contexte.
L’API d’annotation du navigateur vous permet de fournir du contexte ou des commandes propres à votre application. Par exemple, dans l’aperçu d’un système de design, vous pouvez joindre des aperçus de variantes pour montrer aux développeurs comment modifier les composants d’un site.
Pour mieux comprendre l’API d’annotation du navigateur ou intégrer les annotations à votre site, installez le plugin Annotations Extensibility.
Installer le plugin Annotations Extensibility
Essayez l’API d’annotation du navigateur directement dans ce guide.
- Ouvrez cette page dans le navigateur intégré de ChatGPT.
- Essayez d’ouvrir le prompt suggéré qui apparaîtra dans cette carte.
- Activez le mode annotation, puis sélectionnez le tableau ci-dessous ou un exemple de code pour passer d’une mise en page ou d’un thème prédéfini à l’autre.
- Vous pouvez toujours annoter n’importe quel élément de la page pour observer le comportement par défaut des annotations.
Choisissez ce que vous souhaitez personnaliser
Commencez par l’intégration qui correspond aux besoins de votre site :
| Objectif | Intégration |
|---|---|
| Permettre de sélectionner une carte ou un autre groupe d’éléments comme un seul objet | Cibles de sélection |
| Permettre de sélectionner une expression ou une phrase | Conteneurs de sélection de texte |
| Joindre du contexte supplémentaire à une sélection | Métadonnées de sélection |
| Ouvrir une annotation avec un commentaire suggéré depuis un bouton de votre site | Demandes d’annotation |
| Demander un commentaire sur un passage précis depuis votre interface | Demandes d’annotation d’une plage de texte |
| Afficher les commandes avancées à l’ouverture d’une annotation | Mode par défaut de l’éditeur |
| Prévisualiser des propriétés de l’application ou recueillir les choix des utilisateurs | Commandes personnalisées |
| Sélectionner individuellement les objets dessinés dans un canvas | Surfaces d’annotation |
| Activer ou désactiver le mode annotation depuis votre site | Commandes du mode annotation |
Ce guide s’applique à la version de l’application de bureau ChatGPT publiée lors du DevDay 2026 et aux versions ultérieures. Dans le navigateur intégré de l’application, l’API JavaScript est accessible via document.oai.annotation sur les pages sécurisées de premier niveau, par exemple en HTTPS ou sur localhost. Lorsque l’API est activée, le navigateur l’installe avant l’exécution des scripts de votre page. Vérifiez la disponibilité de chaque méthode pour prendre en charge les navigateurs anciens ou non compatibles, puis initialisez votre intégration dès que ses éléments DOM existent. Vous n’avez besoin ni d’attendre un événement ni d’interroger périodiquement l’API.
Les méthodes de l’API renvoient leur résultat de manière synchrone. Les objets d’enregistrement sont immédiatement utilisables. Le navigateur peut finir de charger l’éditeur d’annotations par la suite. La fonction de rappel hitTest d’une surface peut renvoyer une promesse.
Personnalisez les cibles de sélection
Par défaut, le mode annotation sélectionne des éléments du DOM de la page en privilégiant le texte, les images et les commandes. Pour permettre la sélection d’un objet plus grand, marquez la zone qui le contient avec oai-annotation-container et les descendants sélectionnables avec oai-annotatable.
Dans cet exemple, la carte du graphique se sélectionne 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>Pointez n’importe où dans la carte pour la mettre en surbrillance dans son intégralité. La valeur facultative de oai-annotatable donne à l’objet un nom visible par l’utilisateur et le modèle. Choisissez des noms qui permettent de distinguer les objets voisins, ou omettez cette valeur.
Le conteneur délimite la zone où ces règles de sélection s’appliquent. À lui seul, l’attribut oai-annotatable ne modifie pas le comportement de la sélection. Dans un conteneur, le navigateur sélectionne la cible marquée la plus proche qui contient l’élément sous le pointeur. Si plusieurs conteneurs sont imbriqués, le plus proche s’applique. Dans une zone non marquée à l’intérieur d’un conteneur, la sélection crée une annotation de page web. En dehors de tout conteneur, le comportement par défaut s’applique.
Activez la sélection de texte
Ajoutez oai-annotation-container-text à une zone pour permettre aux utilisateurs d’y sélectionner du texte en faisant glisser le pointeur en mode annotation. Cet attribut ne nécessite aucune 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>Si la sélection contient du texte, relâcher le pointeur ouvre l’éditeur d’annotations de texte avec la plage sélectionnée et son contexte. Un clic sans sélection de texte ne crée aucune annotation. Appuyez sur Échap ou annulez le geste pour annuler la sélection.
Le conteneur de sélection de texte ou d’éléments DOM le plus proche détermine le comportement au début du geste. Les conteneurs de texte n’utilisent pas les marqueurs oai-annotatable pour sélectionner des éléments. Imbriquez un conteneur 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 un même élément porte les deux attributs, la sélection de texte est prioritaire.
La sélection de texte suit les règles habituelles de la page et peut s’étendre au-delà du conteneur de départ. Le conteneur ne limite pas la plage sélectionnée et ne permet pas la sélection à l’intérieur d’une iframe. Le texte des champs de saisie reste sélectionnable, mais le mode annotation bloque toujours les clics ordinaires sur la page et les actions natives des commandes. Les appels explicites à request() conservent leur comportement habituel.
Les conteneurs de sélection de texte ne prennent effet qu’en mode annotation, lorsque la fonctionnalité d’annotation des sites et le paramètre propre au site sont tous deux activés.
Ajoutez du contexte à une sélection
Ajoutez oai-annotation-metadata à une cible marquée pour inclure du contexte qui n’est pas forcément visible sur la page. Par exemple, une ligne représentant un contact fictif peut inclure son 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électionnez le contact en mode annotation pour voir l’adresse e-mail incluse dans le contexte.
Sélectionnez le nom ou la fonction pour sélectionner toute la ligne du contact. Les métadonnées s’affichent 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 sans imbrication et respectez les limites suivantes :
- Jusqu’à six propriétés dont les valeurs sont des chaînes de caractères, des nombres finis, des booléens ou
null. - Jusqu’à 64 caractères par clé et 256 caractères par valeur de type chaîne.
- Jusqu’à 2 048 octets pour l’objet sérialisé.
Les clés doivent commencer par une lettre ASCII et ne peuvent 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 également fournir des métadonnées via request() ou dans le résultat de hitTest pour une surface.
Ouvrez une annotation depuis votre site
Appelez document.oai.annotation.request(target, options) directement lors d’une interaction de l’utilisateur, par exemple lorsqu’il appuie sur un bouton. La cible peut être un élément HTML rattaché au document actif et situé dans sa zone visible, ou une plage du DOM (Range). Elle n’a pas besoin d’attributs d’annotation. Les identifiants d’objets virtuels ne sont pas pris en charge.
Les demandes provenant du site peuvent nécessiter l’autorisation de l’utilisateur. Celui-ci peut réactiver les fonctionnalités d’annotation bloquées sous Outils du site > Fonctionnalités d’annotation.
Cet exemple ouvre une annotation sur un paragraphe sans attributs de sélection. Exécutez le script une fois que les deux éléments existent :
<p id="delivery-summary">
Standard delivery takes three to five business days.
</p>
<button id="discuss-delivery" type="button" hidden>Explain more</button>const summary = /** @type {HTMLElement} */ (
document.getElementById("delivery-summary")
);
const button = /** @type {HTMLButtonElement} */ (
document.getElementById("discuss-delivery")
);
button.hidden = typeof document.oai?.annotation?.request !== "function";
button.addEventListener("click", () => {
const annotation = document.oai?.annotation;
if (typeof annotation?.request !== "function") return;
annotation.request(summary, {
initialComment: "Explain this delivery estimate.",
});
});Lorsque l’utilisateur sélectionne En savoir plus, le navigateur reçoit une demande d’ouverture d’annotation avec le paragraphe sélectionné et un commentaire modifiable. L’utilisateur peut modifier l’annotation, l’enregistrer et l’envoyer avec son message. L’ouverture d’une annotation n’envoie aucun message à ChatGPT. Seul l’utilisateur peut l’envoyer.
La livraison standard prend trois à cinq jours ouvrés.
Effectuez l’appel pendant que l’interaction de l’utilisateur est encore active. Si vous attendez d’abord la réponse à une requête réseau, l’interaction risque de ne plus l’être.
Le deuxième argument, facultatif, accepte les champs suivants :
| Option | Comportement |
|---|---|
mode |
Utilisez "advanced" pour ouvrir les commandes avancées d’un élément ou "default" pour conserver le comportement par défaut de l’éditeur. La valeur par défaut est "default". Les commandes personnalisées peuvent aussi s’afficher avec "default". Consultez la section Mode par défaut de l’éditeur. Les plages de texte acceptent uniquement "default". |
enterAnnotationMode |
Définissez cette option sur true pour activer le mode annotation et le garder actif après l’annulation ou l’envoi de l’annotation. |
omitScreenshot |
Définissez cette option sur true pour omettre la capture d’écran automatique pour cette annotation. La valeur par défaut, false, conserve le comportement habituel des captures d’écran. Cette option s’applique aux demandes portant sur un élément ou une plage de texte. |
metadata |
Des métadonnées valides et non vides remplacent les métadonnées HTML de la cible pour cette demande. Le navigateur ignore cette option pour les plages de texte et lorsque l’élément cible se trouve dans un DOM fantôme. |
initialComment |
Fournit un commentaire modifiable de 240 unités de code UTF-16 maximum. |
request() renvoie un objet dont la propriété accepted est un booléen. Vérifiez la valeur de result.accepted plutôt que celle de l’objet lui-même pour savoir si le navigateur a reçu et validé la demande. Cette valeur ne confirme ni l’ouverture de l’éditeur ni l’enregistrement ou l’envoi d’une annotation par l’utilisateur.
Pendant le chargement de l’éditeur, le navigateur peut garder une demande acceptée en attente. Il peut refuser une autre demande si une demande est déjà en attente, si un éditeur est ouvert ou si ChatGPT contrôle le navigateur.
Demandez une annotation pour une plage de texte
Passez un objet DOM Range pour demander un commentaire sur un passage sans modifier la sélection de texte du navigateur. Cet exemple sélectionne uniquement les mots surlignés et ne nécessite pas d’attribut oai-annotation-container-text :
<p>
Your trial includes full access for <mark id="draft-passage">14 days</mark>.
</p>
<button id="discuss-passage" type="button" hidden>
Ask about highlighted text
</button>Exécutez ce script une fois que les deux éléments existent :
const passage = /** @type {HTMLElement} */ (
document.getElementById("draft-passage")
);
const button = /** @type {HTMLButtonElement} */ (
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: "Explain when this trial ends.",
});
});Votre essai comprend un accès complet pendant 14 jours.
Ouvrez une annotation portant uniquement sur les mots surlignés.
La plage doit contenir du texte visible non vide dans le document actif, et une partie au moins de la sélection doit se trouver dans la zone visible de la page. La plage peut contenir jusqu’à 20 000 unités de code UTF-16. Les plages dont les bornes coïncident, le texte composé uniquement de caractères d’espacement, le texte sélectionné masqué et les plages situées dans une racine fermée du DOM fantôme ne sont pas pris en charge.
Les demandes portant sur une plage de texte utilisent uniquement l’éditeur de texte par défaut. Le navigateur rejette les demandes avec mode: "advanced" et ignore les métadonnées de la demande. Gardez le texte cible disponible tant qu’une demande acceptée attend l’éditeur : le navigateur vérifie à nouveau la plage avant d’ouvrir l’éditeur. Indépendamment de ces demandes, les conteneurs de sélection de texte permettent aux utilisateurs de sélectionner du texte par glissement en mode annotation.
Choisissez le mode par défaut de l’éditeur
Pour afficher immédiatement les commandes avancées lors de l’ouverture d’une annotation depuis l’interface de sélection du navigateur, ajoutez cette balise dans le <head> de votre page :
<meta name="oai-annotation-editor-default-mode" content="advanced" />Pour les annotations ouvertes depuis votre interface, passez { mode: "advanced" } à request(). Utilisez mode: "default" ou omettez cette option pour conserver le comportement par défaut de l’éditeur. Le paramètre de la balise meta de la page ne remplace pas cette option de la demande. En mode par défaut, l’éditeur ne se limite pas forcément aux commentaires : les commandes personnalisées qui proposent des valeurs initiales ou qui omettent currentValue peuvent ouvrir l’éditeur de commandes.
La commande Ajuster, le bouton de réduction et le clic avec la touche Option ne sont disponibles que dans Codex ou sur localhost. Sur les sites hébergés ouverts dans ChatGPT, les commandes personnalisées enregistrées apparaissent automatiquement, sans bouton Ajuster ni bouton de réduction. La page peut toujours demander l’ouverture en mode avancé.
Ajoutez des commandes personnalisées
Utilisez registerControls() pour associer des commandes d’annotation à un ou plusieurs éléments du DOM. Ces commandes permettent de prévisualiser des propriétés de l’application, comme un token d’espacement, ou de recueillir des choix à joindre à une demande, comme le ton d’un e-mail.
Prévisualisez un token d’espacement partagé
Les deux cartes de cet exemple utilisent la même propriété CSS. Enregistrez les commandes directement sur les cartes. Aucun attribut de sélection n’est nécessaire :
<style>
#component-preview {
--card-padding: 16px;
}
.preview-card {
padding: var(--card-padding);
border: 1px solid #d1d5db;
}
</style>
<section id="component-preview">
<article class="preview-card">Profile card</article>
<article class="preview-card">Summary card</article>
</section>Exécutez ce script après avoir créé l’aperçu :
const preview = /** @type {HTMLElement} */ (
document.getElementById("component-preview")
);
const annotation = document.oai?.annotation;
/** @param {CustomEvent<{ callback: string; value: number | string }>} event */
function previewSpacing(event) {
const { callback, value } = event.detail;
if (callback === "setCardPadding") {
preview.style.setProperty("--card-padding", `${value}px`);
}
}
/** @type {{ dispose(): void } | undefined} */
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);
}Fiche de profil
Fiche récapitulative
Sélectionnez l’une des cartes en mode annotation, puis modifiez la valeur de « Marge intérieure des cartes (pixels) ». Si nécessaire, sélectionnez Ajuster. Les deux cartes se mettent à jour en même temps.
Annotez l’une des cartes et faites passer la valeur de Marge intérieure des cartes (pixels) de 16 à 24. Sur les sites hébergés ouverts 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 conserve le libellé, la référence, l’ancienne valeur et la nouvelle. Effacer l’aperçu rétablit la marge intérieure d’origine. Appelez disposeAnnotationControls() lorsque vous retirez le composant.
Recueillez un choix sans aperçu
Cet exemple permet de choisir le ton d’un e-mail à partir d’un simple paragraphe. Il ne nécessite ni attributs de sélection ni métadonnées. Exécutez le script une fois que le paragraphe existe :
<p id="email-draft">Draft a follow-up email about the project timeline.</p>const draft = /** @type {HTMLElement} */ (
document.getElementById("email-draft")
);
const registration = document.oai?.annotation?.registerControls?.({
targets: draft,
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. Si vous omettez currentValue, le navigateur inclut le ton sélectionné même si l’utilisateur conserve l’option initiale. Appelez registration?.dispose() lorsque vous retirez le paragraphe.
Pour les listes de sélection, les fonctions de rappel de l’aperçu reçoivent option.value, par exemple "professional". L’historique des annotations et ChatGPT reçoivent le libellé visible option.label, par exemple "Professional", aussi bien pour l’option précédente que pour l’option sélectionnée. Utilisez des libellés qui expliquent chaque choix. Les identifiants internes de value ne sont pas envoyés comme texte du choix.
Configurez les commandes et leurs valeurs initiales
Utilisez controlsMode: "replace" pour afficher uniquement vos commandes sur les cibles enregistrées, ou "extend" pour les afficher aux côtés des commandes intégrées. Le champ facultatif controlsHeading donne un titre au panneau. Le navigateur supprime les espaces en début et en fin de titre et accepte de 1 à 80 caractères. Sans titre, le panneau affiche la balise HTML de l’élément. Le titre ne fait pas partie du contexte envoyé à ChatGPT.
Un enregistrement accepte jusqu’à 12 commandes. Chacune doit avoir un type, un libellé visible label et un identifiant callback.
Nommez les commandes d’après ce qu’elles modifient. Utilisez un libellé stable qui indique la propriété ou son rôle dans le design, par exemple « Arrière-plan de la page », « Couleur principale de la marque » ou « Couleur d’accentuation ». Évitez les libellés qui décrivent la valeur actuelle, comme « Ivoire chaud » ou « Vert forêt foncé ». Le libellé doit garder son sens lorsque la valeur change. Utilisez currentValue pour la valeur existante et reference pour le token ou la propriété sous-jacente.
Par exemple, cette définition distingue le libellé de la commande de la couleur :
const pageBackgroundControl = {
type: "color",
label: "Page background",
callback: "setPageBackground",
reference: "--color-background",
currentValue: "#f6f3ec",
};Sélectionnez l’aperçu en mode annotation, puis modifiez la valeur de la commande Arrière-plan de la page. Si nécessaire, sélectionnez Ajuster. Le libellé reste le même lorsque la couleur change.
Les types de commandes pris en charge sont les suivants :
| Type | Valeur | Champs supplémentaires |
|---|---|---|
color |
Couleur hexadécimale, par exemple "#2563eb" |
Aucun |
range |
Nombre | min, max et step |
select |
Chaîne de caractères | options, un tableau d’objets { label, value } |
toggle |
Booléen | Aucun |
callback est un identifiant de type chaîne, et non une fonction JavaScript. Utilisez un identifiant unique au sein de l’enregistrement. Commencez par une lettre ASCII et utilisez uniquement des lettres ASCII, des chiffres, des traits de soulignement ou des traits d’union. Le champ facultatif reference identifie la propriété modifiée et accompagne le libellé et la valeur dans l’annotation.
Affectez à currentValue la valeur valide de la propriété en dehors de l’aperçu, en tenant compte des modifications non enregistrées. N’utilisez ni l’état de l’aperçu ni une saisie inachevée comme valeur de référence. Le navigateur utilise cette valeur pour comparer l’état avant et après modification et pour réinitialiser les commandes. 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 les valeurs qu’elles ont mémorisées.
Définissez defaultValue pour proposer une valeur de départ. Cette valeur est prioritaire sur currentValue pour l’état initial de la commande. Si vous omettez les deux, la valeur initiale est le blanc pour une couleur, le minimum pour un curseur, la première option pour une liste de sélection ou false pour un interrupteur.
Conservez les valeurs proposées séparément des brouillons ordinaires. Si une mise à jour lève une exception ou si une demande échoue ou renvoie accepted: false, abandonnez la proposition et rétablissez les commandes ainsi que l’état précédent des propositions. Lorsqu’une demande est acceptée, conservez les propositions : le navigateur peut mettre la demande en attente avant de mémoriser les commandes.
Validez les commandes et les enregistrements
Le navigateur vérifie que les enregistrements et les mises à jour respectent 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 et des options, ainsi que controlsHeading, doivent contenir de 1 à 80 unités de code UTF-16 après suppression des espaces en début et en fin de chaîne. Le texte des commandes ne peut contenir ni caractères de contrôle ni caractères modifiant le sens d’écriture. |
| Identifiants | callback doit respecter le format décrit ci-dessus et ne pas dépasser 80 unités de code UTF-16 après suppression des espaces en début et en fin de chaîne. reference accepte de 1 à 80 caractères : des lettres ASCII, des chiffres et les caractères _ . / : @ $ # -, sans espaces. |
| Options des listes de sélection | De 1 à 12 options avec des chaînes value distinctes de 512 unités de code UTF-16 maximum chacune. Toute valeur fournie pour currentValue ou defaultValue doit correspondre à la valeur d’une option. |
| Valeurs des curseurs | min, max, currentValue et defaultValue doivent être des nombres finis compris entre −10 000 et 10 000. Respectez min < max, choisissez un step compris entre 0,001 et 10 000 qui ne dépasse pas max - min et utilisez des valeurs initiales comprises dans l’intervalle. Les valeurs initiales ne doivent pas nécessairement correspondre à un cran défini par step. |
| Couleurs et interrupteurs | Les couleurs doivent comporter 3, 4, 6 ou 8 chiffres hexadécimaux après #. Les interrupteurs acceptent uniquement true ou false. |
| Cibles | À l’enregistrement, fournissez de 1 à 128 cibles, chacune étant un élément du document actif. Lors d’une mise à jour, passez targets: [] pour détacher l’enregistrement. Chaque document accepte jusqu’à 64 enregistrements de commandes et 1 024 associations entre enregistrements et cibles. |
| Commandes sérialisées | Les données JSON contenant controls, controlsHeading et controlsMode ne doivent pas dépasser 16 384 unités de code UTF-16. Utilisez des données encodables en JSON, sans fonctions, symboles ni entiers de type BigInt. |
registerControls() et registration.update() peuvent lever une exception de manière synchrone si les définitions ou les cibles sont invalides ou si une limite est dépassée. Toute mise à jour après dispose() lève également une exception. En cas de rejet d’une mise à jour, l’enregistrement précédent conserve ses commandes et ses cibles. Traitez les erreurs à l’endroit de l’appel, veillez à ce que l’utilisateur puisse continuer à modifier la page normalement et réutilisez ou libérez vos enregistrements pour respecter les limites.
Gérez les aperçus et les réinitialisations
L’événement oaiannotationcontrolchange se propage depuis l’élément sélectionné vers ses ancêtres. Sa propriété detail contient callback, value et action :
| Action | Effet attendu de l’application de la valeur fournie |
|---|---|
preview |
Afficher la modification demandée. |
preview-original |
Afficher temporairement l’état d’origine pour permettre la 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 sur l’espacement. Écrivez des gestionnaires dont les effets sont réversibles et qui peuvent être appelés plusieurs fois sans risque. Les événements d’aperçu ne demandent aucune modification permanente. Enregistrez les modifications via le mécanisme habituel de votre application. Gérez séparément les brouillons ordinaires, les saisies inachevées et les aperçus pour éviter qu’une modification sans rapport n’écrase un aperçu. Lorsqu’un utilisateur modifie le même paramètre, remplacez son aperçu et actualisez la valeur de référence pour les futures annotations.
Conservez l’objet 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 }) lorsque vous remplacez l’élément DOM d’un composant. Les collections de cibles mémorisent les éléments existants. Elles n’intègrent pas automatiquement les éléments qui correspondront au sélecteur par la suite.
Passez targets: [] pour détacher l’enregistrement ou controls: [] pour effacer ses commandes. Lorsque vous retirez l’intégration, appelez dispose() et supprimez les écouteurs d’événements. Le nettoyage doit également rétablir l’affichage normal : dispose() supprime uniquement l’enregistrement des commandes et n’annule pas les modifications de l’aperçu. Dans l’exemple sur l’espacement, le nettoyage supprime le style appliqué directement à l’élément pour rétablir la valeur CSS d’origine. Les deux méthodes sont synchrones et ne renvoient aucune valeur. Les mises à jour s’appliquent aux futures sélections. Les annotations enregistrées conservent les commandes et la cible qu’elles ont mémorisées.
Les événements des commandes ciblent l’élément mémorisé par l’annotation. Modifier les cibles d’un enregistrement ne redirige pas les annotations existantes. Un événement de réinitialisation peut encore se déclencher sur un élément retiré du DOM. Un écouteur sur son ancien parent ne le recevra donc pas.
Rendez les objets d’un canvas sélectionnables
Une surface d’annotation permet à votre application d’identifier individuellement les objets dessinés dans un canvas. Enregistrez l’élément hôte avec registerSurface() et fournissez une fonction hitTest qui renvoie un objet doté d’un id stable, ou null pour une zone vide.
L’hôte doit être un élément HTML rattaché à un document sécurisé de premier niveau, en dehors du DOM fantôme. L’enregistrement de surfaces n’est pas pris en charge dans une iframe.
Détectez et identifiez l’objet pointé
Cet exemple dessine une barre représentant le chiffre d’affaires et la rend sélectionnable à l’aide de hitTest et d’un identifiant d’objet stable. Placez le script après le canvas :
<canvas id="revenue-canvas" width="480" height="240">
Revenue this quarter: $120,000.
</canvas>const canvas = /** @type {HTMLCanvasElement} */ (
document.getElementById("revenue-canvas")
);
const context = /** @type {CanvasRenderingContext2D} */ (
canvas.getContext("2d")
);
const bar = { x: 40, y: 60, width: 320, height: 100 };
context.fillStyle = "#2563eb";
context.fillRect(bar.x, bar.y, bar.width, bar.height);
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" };
},
});Activez le mode annotation, puis sélectionnez une forme pour voir son nom, son identifiant et sa couleur de remplissage dans l’annotation. L’espace entre les formes n’est pas sélectionnable.
Dans l’exemple de code, sélectionner la barre en mode annotation permet de l’identifier par revenue-this-quarter. Utilisez des identifiants stables au sein d’une même surface. L’exemple renvoie null pour une zone vide.
hitTest peut renvoyer une promesse et reçoit un AbortSignal dans signal pour annuler un traitement devenu inutile. Le navigateur attend 250 millisecondes avant de revenir à la sélection dans le DOM. Il revient également à la sélection dans le DOM en cas d’erreur ou de résultat invalide. Renvoyez explicitement null si le test réussit mais ne détecte aucun objet.
Même si le traitement est annulé, sa promesse doit être résolue ou rejetée. Le navigateur n’autorise qu’un seul appel à hitTest à la fois et attend que sa promesse soit résolue ou rejetée avant d’en autoriser un autre, même après une annulation ou un dépassement de délai. Si vous confiez la détection à un worker, résolvez ou rejetez la promesse en attente lorsque son traitement est interrompu. Ignorer la réponse d’un worker après annulation risque de bloquer les sélections suivantes dans le canvas.
Appelez surface?.invalidate() après avoir déplacé des objets ou modifié le zoom, et surface?.dispose() lorsque vous retirez l’intégration. Les deux méthodes sont synchrones et ne renvoient aucune valeur.
Ajoutez du contexte facultatif sur l’objet
Le champ id suffit pour identifier l’objet. Pour inclure un nom à afficher, un rôle sémantique, du contexte masqué ou les limites de la sélection, remplacez la valeur renvoyée lorsqu’un objet est détecté dans la fonction de rappel hitTest précédente par :
return {
id: "revenue-this-quarter",
name: "Revenue this quarter",
role: "chart-bar",
metadata: { metricId: "quarterly-revenue" },
rect,
};Le champ facultatif name est visible par l’utilisateur. Le champ role décrit brièvement le rôle sémantique de l’objet. N’incluez des métadonnées que si elles apportent du contexte qui n’est pas déjà visible. Le champ facultatif rect utilise des pixels CSS relatifs à la zone visible de la page, dans le même repère que clientX et clientY. L’exemple de détection calcule déjà ce rectangle. Convertissez les coordonnées de la scène en tenant compte de l’échelle, du déplacement de la vue et du zoom.
Ajoutez une indication visuelle de la sélection
Utilisez la fonction de rappel facultative renderSelection pour dessiner vos propres indicateurs de survol et de sélection. Elle ne nécessite pas les champs de contexte facultatifs.
Pour le canvas ci-dessus, définissez cette fonction et ajoutez renderSelection: renderRevenueSelection aux options de registerSurface() :
/** @param {{ hoveredId: string | null; selectedId: string | null }} selection */
function renderRevenueSelection({ hoveredId, selectedId }) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "#2563eb";
context.fillRect(bar.x, bar.y, bar.width, bar.height);
if (
hoveredId === "revenue-this-quarter" ||
selectedId === "revenue-this-quarter"
) {
context.strokeStyle = "#111827";
context.lineWidth = 3;
context.strokeRect(bar.x, bar.y, bar.width, bar.height);
}
}Cette fonction redessine la barre avec un contour lorsqu’elle est survolée ou sélectionnée. Lorsque les deux identifiants valent null, elle redessine la barre sans contour.
Ajoutez des commandes aux objets d’un canvas
Enregistrez les 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 les commandes de texte intégrées ne s’appliquent pas à ces objets.
Si la surface contient plusieurs objets, utilisez renderSelection pour actualiser les commandes de l’hôte lorsque selectedId change, avant que le navigateur ne mémorise l’annotation. Les événements des commandes incluent detail.virtualTarget: { surfaceId, targetId }. Acheminez chaque événement en fonction de l’identité mémorisée et de son callback, indépendamment de la sélection actuelle. targetId correspond à l’id renvoyé par hitTest. surfaceId identifie l’enregistrement de la surface dans le navigateur. Les événements DOM ordinaires n’incluent pas virtualTarget.
Les événements d’aperçu, de comparaison et de réinitialisation conservent l’identité de l’objet d’origine même après la sélection d’un autre objet. Rouvrir une annotation enregistrée ne relance pas la détection pour remplacer l’objet ou les métadonnées mémorisés.
Contrôlez 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. Vérifiez la disponibilité des deux méthodes pour prendre en charge les navigateurs anciens ou non compatibles. Ajoutez ce bouton, puis exécutez le script une fois que le bouton existe :
<button id="toggle-annotations" type="button" hidden>
Enter annotation mode
</button>const button = /** @type {HTMLButtonElement} */ (
document.getElementById("toggle-annotations")
);
const annotation = document.oai?.annotation;
/** @param {boolean} active */
function renderMode(active) {
button.textContent = active
? "Exit annotation mode"
: "Enter annotation mode";
}
/** @param {CustomEvent<{ active: boolean }>} event */
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 l’état du mode annotation. Passez true pour l’activer ou false pour le désactiver. Les demandes répétées avec le même booléen sont idempotentes. La valeur true préserve l’éditeur actif ou l’activation en attente. La valeur false annule l’activation en attente et suit la procédure habituelle de sortie du mode annotation.
Appelez toggle() pendant une interaction de l’utilisateur. Le résultat synchrone { accepted } accuse réception de la demande sans confirmer le changement de mode. Les vérifications du navigateur peuvent encore empêcher ce changement. Lisez l’état à partir de isActive() et de l’événement, comme dans l’exemple.
isActive() ne nécessite aucune interaction de l’utilisateur. Le navigateur actualise sa valeur avant d’émettre oaiannotationmodechange, dont la propriété event.detail.active est un booléen. Le navigateur n’émet pas d’événement initial ni d’événement en double lorsqu’une demande impose un état déjà effectif. Initialisez donc votre interface à partir de la méthode de lecture. Si l’accès est révoqué alors que le mode est actif, un dernier événement indique active: false et toute référence conservée à la méthode de lecture renvoie false.
Le mode annotation intercepte les clics sur la page. Maintenez la touche Espace enfoncée pour utiliser les commandes de la page, y compris votre bouton de sortie, ou quittez le mode annotation depuis l’interface d’annotation du navigateur. Tant que vous maintenez seulement la touche Espace enfoncée, isActive() continue de renvoyer vrai. L’API ne prend en charge ni oai-annotation-ignore ni aucun autre attribut permettant de laisser passer les clics.
Quitter le mode annotation ferme l’éditeur et conserve les annotations enregistrées sans les envoyer ni les déplacer dans la zone de saisie. Appelez cleanupAnnotationButton() lorsque vous retirez le composant. La référence conservée à l’API permet au code de nettoyage de supprimer les écouteurs même si l’espace de noms a disparu.
Testez votre intégration
Ouvrez votre site dans le navigateur intégré de l’application de bureau ChatGPT et testez les fonctionnalités que vous avez ajoutées :
- 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.
- 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.
- Modifiez la valeur d’une commande personnalisée, comparez le résultat à l’original et effacez l’aperçu. Vérifiez que votre application rétablit l’état d’origine.
- Pour le contenu d’un canvas, testez les zones vides, le redimensionnement et les modifications de la scène. Sélectionnez un autre objet et vérifiez que les événements des commandes modifient toujours la cible mémorisée. Annulez une détection asynchrone et vérifiez que les sélections suivantes fonctionnent toujours.
- Enregistrez une annotation, puis rouvrez-la et modifiez-la depuis l’aperçu de la pièce jointe dans la zone de saisie. Vérifiez qu’elle conserve ses commandes et sa cible, et que sa suppression efface tout aperçu associé.
- Envoyez une annotation avec un message. Vérifiez 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 sélectionnées et les choix laissés tels quels pour les commandes qui omettent
currentValue. - Ouvrez le site dans un navigateur dépourvu de l’API et vérifiez que les interactions habituelles fonctionnent toujours.
Pour permettre à ChatGPT d’effectuer des actions sur votre site, ajoutez des outils de site (WebMCP). Les annotations intègrent la sélection et les commentaires de l’utilisateur à la conversation. Les outils de site permettent à l’agent d’agir à partir de ce contexte en utilisant les fonctionnalités existantes de votre application.