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.
- 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 disposition ou d’un thème prédéfini à l’autre.
- 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 :
- 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 une commande personnalisée, comparez avec 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 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.
- 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.
- 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. - 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.