Con la API de anotaciones del navegador, puedes personalizar qué pueden seleccionar los usuarios en tu sitio web, qué contexto acompaña sus comentarios y qué controles pueden usar para previsualizar los cambios antes de enviar una anotación a ChatGPT.
En los espacios de trabajo de ChatGPT Enterprise y ChatGPT Edu, un administrador debe permitir las personalizaciones de anotaciones del sitio mediante la política del espacio de trabajo.
Las anotaciones del navegador funcionan en tu sitio sin que tengas que cambiar el código. Los usuarios pueden seleccionar parte de una página, agregar un comentario y enviarlo en Contexto a Codex o a ChatGPT Work.
Al desarrollar tu aplicación, puedes usar la API de anotaciones del navegador para aportar contexto o controles específicos de la aplicación. Por ejemplo, en la vista previa de un sistema de diseño, puedes adjuntar vistas previas de las variantes de un componente para que los desarrolladores sepan cómo actualizar los componentes de un sitio web.
Si necesitas ayuda para entender la API de anotaciones del navegador o incorporar anotaciones en tu sitio web, instala el complemento Annotations Extensibility.
Instalar el complemento Annotations Extensibility
Prueba la API de anotaciones del navegador en esta guía.
- Abre esta página en el navegador integrado de ChatGPT.
- Abre el prompt sugerido que aparecerá en esta tarjeta.
- Activa el modo de anotación y selecciona la tabla que aparece a continuación o un ejemplo de código para alternar entre diseños y temas predefinidos.
- También puedes anotar cualquier elemento de la página para ver cómo funcionan las anotaciones de forma predeterminada.
Elige qué personalizar
Empieza con la integración que se ajuste a tu sitio web:
| Objetivo | Integración |
|---|---|
| Permitir que una tarjeta u otro grupo de elementos se seleccione como un solo objeto | Objetos seleccionables |
| Permitir que los usuarios seleccionen una frase o una oración | Contenedores de selección de texto |
| Incluir contexto adicional con una selección | Metadatos de la selección |
| Abrir una anotación con un comentario sugerido desde tu propio botón | Solicitudes de anotación |
| Solicitar comentarios sobre un fragmento específico desde tu propia interfaz | Solicitudes para rangos de texto |
| Mostrar controles avanzados al abrir una anotación | Configuración predeterminada del editor |
| Previsualizar propiedades de la aplicación o recopilar las opciones elegidas | Controles personalizados |
| Seleccionar objetos individuales dibujados en un canvas | Superficies de anotación |
| Activar o desactivar el modo de anotación desde tu sitio | Controles del modo de anotación |
Esta guía se aplica a la versión de la aplicación de escritorio de ChatGPT lanzada en DevDay 2026 y a las versiones posteriores. La API de JavaScript está disponible a través de document.oai.annotation en el navegador integrado de la aplicación, en páginas seguras de nivel superior, como las que se sirven mediante HTTPS o desde localhost. Cuando la API está habilitada, el navegador la instala antes de que se ejecuten los scripts de tu página. Comprueba la disponibilidad de cada método para que tu sitio funcione también en navegadores antiguos o sin soporte para la API. Luego inicializa la integración una vez que existan sus elementos del DOM. No necesitas esperar un evento ni consultar periódicamente si la API está lista.
Los métodos de la API devuelven sus resultados de forma síncrona, y puedes usar de inmediato las referencias que obtienes al registrar controles o superficies. El navegador puede terminar de cargar el editor de anotaciones después. El callback hitTest de una superficie puede devolver una promesa.
Personaliza los objetos seleccionables
De forma predeterminada, el modo de anotación selecciona elementos del DOM de la página y da prioridad al texto, las imágenes y los controles. Para que se pueda seleccionar un objeto más grande, marca la región que lo contiene con oai-annotation-container y los descendientes que se puedan seleccionar con oai-annotatable.
En este ejemplo, la tarjeta que contiene un gráfico se puede seleccionar como un solo objeto:
<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>Al colocar el puntero en cualquier parte de la tarjeta, se resalta toda la tarjeta. El valor opcional de oai-annotatable le da al objeto un nombre visible para el usuario y el modelo. Elige nombres que permitan distinguir objetos cercanos u omite el valor.
El contenedor define dónde se aplican estas reglas de selección. Por sí solo, el atributo oai-annotatable no cambia el comportamiento de la selección. Dentro de un contenedor, el navegador selecciona el objeto marcado más cercano que contiene el elemento bajo el puntero. Si los contenedores están anidados, se usa el más cercano. Al seleccionar un área sin marcar dentro de un contenedor, se crea una anotación de página web. Fuera de los contenedores, se mantiene el comportamiento predeterminado.
Habilita la selección de texto
Agrega oai-annotation-container-text a una región para que los usuarios puedan seleccionar texto arrastrando el puntero en el modo de anotación. El atributo no necesita un valor:
<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>Al soltar el puntero después de seleccionar texto, se abre el editor de anotaciones de texto con el rango seleccionado y su contexto. Hacer clic sin seleccionar texto no crea una anotación. Si presionas Escape o cancelas el gesto, se cancela la selección.
El contenedor de selección de texto o de elementos del DOM más cercano determina cómo se inicia el gesto. Los contenedores de texto no usan marcadores oai-annotatable para seleccionar elementos. Anida un contenedor oai-annotation-container para volver a permitir la selección de elementos, o un contenedor de texto para volver a permitir la selección de texto. Si un elemento tiene ambos atributos, la selección de texto tiene prioridad.
La selección de texto sigue las reglas habituales de la página y puede extenderse más allá del contenedor donde se inició. El contenedor no recorta el rango ni habilita la selección dentro de un iframe. Los campos de texto siguen siendo seleccionables, pero los clics habituales en la página y las acciones nativas de los controles siguen bloqueados en el modo de anotación. Las llamadas explícitas a request() conservan su comportamiento habitual.
Los contenedores de selección de texto solo funcionan en el modo de anotación cuando están habilitadas tanto la función de anotaciones de sitios como la configuración del sitio en cuestión.
Agrega contexto a una selección
Agrega oai-annotation-metadata a un objeto marcado para incluir contexto que podría no estar visible en la página. Por ejemplo, una fila con un contacto ficticio puede incluir su dirección de correo electrónico para una solicitud de redacción de un correo:
<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>Selecciona el contacto en el modo de anotación para ver la dirección de correo electrónico que se incluye como contexto.
Al seleccionar cualquiera de las dos líneas, se selecciona toda la fila. Los metadatos aparecen junto a la anotación y la acompañan en la conversación. Incluye solo el contexto que quieras compartir tanto con el usuario como con el modelo. Para enviar un correo, se seguiría necesitando una herramienta de correo electrónico conectada.
Usa un objeto JSON pequeño, sin anidamiento, que cumpla estos límites:
- Hasta seis propiedades cuyos valores sean cadenas, números finitos, booleanos o
null. - Hasta 64 caracteres por clave y hasta 256 caracteres por valor de cadena.
- Hasta 2048 bytes para el objeto serializado.
Las claves deben comenzar con una letra ASCII y contener solo letras ASCII, dígitos, espacios, guiones bajos o guiones. Separa las palabras con un solo espacio. No se admiten objetos anidados ni arreglos. El navegador ignora los metadatos no válidos.
También puedes proporcionar metadatos mediante request() o en el resultado de hitTest de una superficie.
Abre una anotación desde tu sitio web
Llama a document.oai.annotation.request(target, options) directamente desde una interacción del usuario, por ejemplo, cuando presiona un botón. El objeto de la solicitud puede ser un elemento HTML conectado al documento actual y ubicado en su área visible, o un objeto Range del DOM. No necesita atributos de anotación. No se admiten identificadores de objetos virtuales.
Las solicitudes iniciadas por el sitio pueden requerir permiso del usuario. Los usuarios pueden volver a habilitar las funciones de anotación bloqueadas en Herramientas del sitio > Funciones de anotación.
Este ejemplo abre una anotación en un párrafo sin atributos de selección. Ejecuta el script una vez que existan ambos elementos:
<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.",
});
});Al seleccionar Explicar en detalle, se le pide al navegador que abra una anotación con el párrafo seleccionado y un comentario editable. El usuario puede editarla, guardarla y enviarla junto con su mensaje. Abrir una anotación no envía un mensaje a ChatGPT. Solo el usuario puede enviarlo.
La entrega estándar tarda de tres a cinco días hábiles.
Haz la llamada mientras la interacción del usuario siga activa. Si primero esperas a que termine una solicitud de red, la interacción puede dejar de estar activa.
El segundo argumento es opcional y admite estos campos:
| Opción | Comportamiento |
|---|---|
mode |
Usa "advanced" para abrir los controles avanzados de un elemento o "default" para usar el comportamiento predeterminado del editor. El valor predeterminado es "default". Los controles personalizados también pueden abrirse con "default". Consulta Configuración predeterminada del editor. Los rangos de texto solo admiten "default". |
enterAnnotationMode |
Usa true para activar el modo de anotación y mantenerlo activo después de cancelar o enviar la anotación. |
omitScreenshot |
Usa true para omitir la captura de pantalla automática de esta anotación. El valor predeterminado es false, que conserva el comportamiento habitual de las capturas de pantalla. Esta opción se aplica tanto a las solicitudes para elementos como a las solicitudes para rangos de texto. |
metadata |
Si los metadatos son válidos y no están vacíos, reemplazan los metadatos HTML del elemento para esta solicitud. Esta opción se ignora para los rangos de texto y cuando el elemento se encuentra dentro de un DOM de sombra. |
initialComment |
Proporciona un comentario editable de hasta 240 unidades de código UTF-16. |
request() devuelve un objeto con un campo booleano accepted. Comprueba result.accepted en vez de evaluar el objeto de resultado completo para saber si el navegador recibió y validó la solicitud. Ese campo no confirma que se haya abierto el editor ni que el usuario haya guardado o enviado una anotación.
Mientras carga el editor, el navegador puede mantener una solicitud aceptada en espera. Puede rechazar otra si hay una solicitud pendiente, si el editor está abierto o si ChatGPT está controlando el navegador.
Solicita una anotación para un rango de texto
Pasa un objeto Range del DOM para solicitar comentarios sobre un fragmento sin cambiar la selección de texto del navegador. Este ejemplo selecciona solo la frase resaltada y no necesita el atributo 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>Ejecuta este script una vez que existan ambos elementos:
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.",
});
});Tu periodo de prueba incluye acceso completo durante 14 días.
Abre una anotación que abarque solo las palabras resaltadas.
El rango debe contener texto visible no vacío en el documento actual, y al menos parte de la selección debe estar en el área visible de la página. Puede contener hasta 20 000 unidades de código UTF-16. No se admiten rangos colapsados, texto compuesto solo por espacios en blanco, texto seleccionado oculto ni rangos dentro de raíces de sombra cerradas.
Las solicitudes para rangos de texto solo usan el editor de texto predeterminado. Se rechazan las solicitudes con mode: "advanced" y se ignoran los metadatos de la solicitud. Mantén disponible el texto de destino mientras la solicitud aceptada espera a que el editor esté listo. El navegador vuelve a comprobar el rango antes de abrir el editor. Por separado, los contenedores de selección de texto permiten que los usuarios seleccionen texto arrastrando el puntero en el modo de anotación.
Elige el modo predeterminado del editor
Para mostrar de inmediato los controles avanzados de las anotaciones que se abren desde la interfaz de selección del navegador, agrega esta etiqueta al <head> de tu página:
<meta name="oai-annotation-editor-default-mode" content="advanced" />Para las anotaciones que se abren desde tu propia interfaz, pasa { mode: "advanced" } a request(). Usa mode: "default" u omite esa opción para obtener el comportamiento predeterminado del editor. La configuración de la etiqueta meta de la página no reemplaza esta opción de la solicitud. El modo predeterminado no garantiza que el editor muestre solo el comentario. Los controles personalizados con valores iniciales sugeridos o que omitan currentValue pueden abrir el editor de controles.
Los controles manuales para Ajustar, contraer y hacer clic con la tecla Opción solo están disponibles en Codex o en localhost. En los sitios alojados que abres en ChatGPT, los controles personalizados registrados aparecen automáticamente, sin el botón Ajustar ni el botón para contraerlos. En esos sitios también funciona el modo avanzado solicitado por la página.
Agrega controles personalizados
Usa registerControls() para asociar controles de anotación con uno o más elementos del DOM. Los controles permiten previsualizar propiedades de la aplicación, como un token de espaciado, o recopilar opciones para incluirlas en una solicitud, como el tono de un correo electrónico.
Previsualiza un token de espaciado compartido
Las dos tarjetas de este ejemplo usan la misma propiedad CSS. Registra los controles directamente en las tarjetas. No se necesitan atributos de selección:
<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>Ejecuta este script después de crear la vista previa:
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);
}Tarjeta de perfil
Tarjeta de resumen
Selecciona cualquiera de las tarjetas en el modo de anotación y cambia Espaciado interno de la tarjeta (píxeles). Selecciona Ajustar si es necesario. Las dos tarjetas se actualizan al mismo tiempo.
Anota cualquiera de las tarjetas y cambia Espaciado interno de la tarjeta (píxeles) de 16 a 24. En los sitios alojados que abres en ChatGPT, los controles aparecen automáticamente. En Codex o en localhost, selecciona Ajustar si es necesario. Ambas tarjetas se actualizan. La anotación registra la etiqueta, la referencia, el valor anterior y el nuevo. Al borrar la vista previa, se restablece el espaciado interno original. Llama a disposeAnnotationControls() cuando quites el componente.
Recopila una opción sin mostrar una vista previa
Este ejemplo permite elegir el tono de un correo electrónico para un párrafo simple. No necesita atributos de selección ni metadatos. Ejecuta el script una vez que exista el párrafo:
<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",
},
],
});Este control no necesita un manejador de eventos porque no previsualiza ningún cambio en la página. Al omitir currentValue, le indicas al navegador que incluya el tono seleccionado aunque el usuario conserve la opción inicial. Llama a registration?.dispose() cuando quites el párrafo.
En los controles de selección, los callbacks de vista previa reciben option.value, por ejemplo, "professional". El historial de anotaciones y ChatGPT reciben la etiqueta visible option.label, por ejemplo, "Professional", tanto para la opción anterior como para la seleccionada. Usa etiquetas que expliquen cada opción. Los identificadores internos de value no se envían como texto de la opción elegida.
Configura los controles y sus valores iniciales
Usa controlsMode: "replace" para mostrar solo tus controles en los elementos registrados o "extend" para mostrarlos junto con los controles integrados. El campo opcional controlsHeading le da un título al panel. El navegador elimina los espacios en blanco al inicio y al final del título y admite entre 1 y 80 caracteres. Si no hay título, el panel muestra la etiqueta HTML del elemento. El título no se incluye en el contexto que se envía a ChatGPT.
Cada registro admite hasta 12 controles. Cada control requiere un campo type, una etiqueta visible label y un identificador callback.
Nombra los controles según lo que modifican. Usa una etiqueta que identifique una propiedad estable o su función en el diseño, como “Fondo de la página”, “Color principal de la marca” o “Color de acento”. Evita nombrar el control según su valor actual, como “Marfil cálido” o “Verde bosque oscuro”. La etiqueta debe seguir teniendo sentido cuando cambie el valor. Usa currentValue para el valor existente y reference para el token o la propiedad subyacente.
Por ejemplo, esta definición del control mantiene la etiqueta separada del color:
const pageBackgroundControl = {
type: "color",
label: "Page background",
callback: "setPageBackground",
reference: "--color-background",
currentValue: "#f6f3ec",
};Selecciona la vista previa en el modo de anotación y cambia Fondo de la página. Selecciona Ajustar si es necesario. La etiqueta se mantiene aunque cambie el color.
Se admiten los siguientes tipos de controles:
| Tipo | Valor | Campos adicionales |
|---|---|---|
color |
Color hexadecimal, como "#2563eb" |
Ninguno |
range |
Número | min, max y step |
select |
Cadena | options, un arreglo de objetos { label, value } |
toggle |
Booleano | Ninguno |
callback es un identificador de tipo cadena, no una función de JavaScript. Debe ser único dentro del registro, comenzar con una letra ASCII y contener solo letras ASCII, dígitos, guiones bajos o guiones. El campo opcional reference identifica la propiedad que se modifica y acompaña a la etiqueta y al valor en la anotación.
Asigna a currentValue el valor válido que tenga la propiedad durante la edición normal, incluidos los cambios sin guardar. No uses el estado de la vista previa ni una entrada incompleta como valor de referencia. El navegador usa ese valor para registrar el antes y el después de los cambios y para restablecer el estado original. Cuando cambie el estado de la aplicación, usa registration.update({ controls }) para actualizar los valores de currentValue de los controles. Las actualizaciones afectan a las anotaciones futuras. Las existentes conservan los valores que capturaron.
Usa defaultValue para sugerir un valor inicial. Este tiene prioridad sobre currentValue al inicializar el control. Si omites ambos, el valor inicial será blanco para un control de color, el mínimo para uno de rango, la primera opción para uno de selección o false para un interruptor.
Mantén los valores propuestos separados de los borradores de edición. Si una actualización lanza una excepción, o si una solicitud falla o devuelve accepted: false, descarta la propuesta que intentaste aplicar y restaura los controles y la propuesta a su estado anterior. Conserva las propuestas cuando se acepte la solicitud, ya que el navegador puede ponerla en cola antes de capturar los controles.
Valida los controles y los registros
El navegador valida los registros y las actualizaciones según estos límites:
| Campo o recurso | Restricción |
|---|---|
| Controles | Hasta 12 por registro, con identificadores callback únicos. Usa solo los campos definidos para el tipo de control. |
| Etiquetas y títulos | Las etiquetas de los controles, las etiquetas de las opciones y controlsHeading deben contener texto después de eliminar los espacios en blanco al inicio y al final, y tener como máximo 80 unidades de código UTF-16. El texto de los controles no puede contener caracteres de control ni caracteres que cambien la dirección del texto. |
| Identificadores | callback admite hasta 80 unidades de código UTF-16 después de eliminar los espacios en blanco al inicio y al final, y debe seguir el formato descrito arriba. reference admite entre 1 y 80 caracteres, que pueden ser letras ASCII, dígitos y los caracteres _ . / : @ $ # -, sin espacios. |
| Opciones de selección | Entre 1 y 12 opciones con cadenas value distintas, de hasta 512 unidades de código UTF-16 cada una. Cada valor que especifiques en currentValue o defaultValue debe coincidir con el valor de alguna opción. |
| Valores de rango | min, max, currentValue y defaultValue deben ser números finitos entre −10 000 y 10 000. Se requiere min < max, un valor de step entre 0,001 y 10 000 que no supere max - min, y valores iniciales dentro del rango. Los valores iniciales no tienen que coincidir con los incrementos de step. |
| Colores e interruptores | Los colores deben tener 3, 4, 6 u 8 dígitos hexadecimales después de #. Los valores de los interruptores deben ser true o false. |
| Elementos de destino | Al crear el registro, indica entre 1 y 128 elementos de destino, todos del documento actual. En una actualización, puedes pasar targets: [] para desvincularlos. Cada documento admite hasta 64 registros de controles y 1024 asociaciones entre registros y elementos de destino. |
| Controles serializados | Los datos JSON que contienen controls, controlsHeading y controlsMode no deben superar las 16 384 unidades de código UTF-16. Usa datos que puedan codificarse como JSON, sin funciones, símbolos ni enteros de tipo BigInt. |
registerControls() y registration.update() pueden lanzar una excepción de forma síncrona si las definiciones o los elementos de destino no son válidos o si se superan los límites. Actualizar después de llamar a dispose() también lanza una excepción. Si se rechaza una actualización, se conserva el registro anterior, incluidos sus controles y elementos de destino. Maneja los errores en el punto de llamada, mantén disponible la edición normal y reutiliza o libera los registros de tu integración para no superar los límites.
Gestiona las vistas previas y el restablecimiento de valores
El evento oaiannotationcontrolchange se propaga desde el elemento seleccionado hacia sus ancestros. Su campo detail contiene callback, value y action:
| Acción | Aplica el valor proporcionado para |
|---|---|
preview |
Mostrar el cambio solicitado. |
preview-original |
Mostrar temporalmente el estado original para compararlo. |
reset |
Restablecer el estado original al borrar la vista previa. |
Aplica el valor proporcionado en cada acción, como en el ejemplo de espaciado. Diseña los manejadores de modo que sus efectos sean reversibles y que se puedan ejecutar varias veces sin problemas. Los eventos de vista previa no solicitan cambios permanentes. Guarda los cambios mediante el flujo habitual de tu aplicación. Mantén separados los borradores de edición, las entradas incompletas y las vistas previas para que los cambios no relacionados no sobrescriban una vista previa. Cuando alguien edite la misma propiedad, reemplaza su vista previa y actualiza el valor de referencia para las anotaciones futuras.
Conserva la referencia al registro para actualizarlo y liberar sus recursos. update() admite cualquier combinación de targets, controls, controlsHeading y controlsMode. Los campos omitidos conservan sus valores anteriores. Por ejemplo, usa registration.update({ targets: newElement }) cuando reemplaces el elemento del DOM de un componente. Las colecciones de elementos de destino capturan los elementos existentes y no incorporan elementos que coincidan con el selector más adelante.
Pasa targets: [] para desvincular el registro de sus elementos de destino o controls: [] para borrar sus controles. Cuando quites la integración, llama a dispose() y elimina los listeners de eventos. También debes restablecer la presentación normal de la página, ya que dispose() solo elimina el registro de controles y no deshace los cambios de la vista previa. En el ejemplo de espaciado, se elimina el estilo en línea para restablecer el valor CSS original. Ambos métodos se completan de forma síncrona sin devolver ningún valor. Las actualizaciones afectan a las selecciones futuras. Las anotaciones guardadas conservan los controles y el elemento de destino que capturaron.
Los eventos de los controles se dirigen al elemento capturado por la anotación. Actualizar los elementos de destino de un registro no redirige las anotaciones existentes. Los eventos de restablecimiento pueden seguir activándose en un elemento eliminado, por lo que un listener en su antiguo elemento padre no los recibirá.
Permite seleccionar objetos de un canvas
Una superficie de anotación permite que tu aplicación identifique objetos individuales dibujados en un canvas. Registra el elemento host con registerSurface() y proporciona una función hitTest que devuelva un objeto con un id estable, o null si se apunta a un espacio vacío.
El host debe ser un elemento HTML conectado, fuera de un DOM de sombra y dentro de un documento seguro de nivel superior. No se pueden registrar superficies dentro de un iframe.
Empieza por detectar e identificar el objeto
Este ejemplo dibuja una barra que representa los ingresos y permite seleccionarla usando solo hitTest y un identificador de objeto estable. Coloca el script después del 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" };
},
});Activa el modo de anotación y selecciona una figura para ver su nombre, su identificador y su color de relleno en la anotación. No se puede seleccionar el espacio entre las figuras.
En el ejemplo de código, al seleccionar la barra en el modo de anotación, se la identifica como revenue-this-quarter. Mantén estables los identificadores dentro de cada superficie. El ejemplo devuelve null para los espacios vacíos.
hitTest puede devolver una promesa y recibe un objeto AbortSignal en signal para cancelar operaciones que ya se hayan reemplazado por otras. El navegador espera hasta 250 milisegundos antes de recurrir a la selección del DOM. También recurre a ella si se produce un error o el resultado no es válido. Devuelve null de forma explícita si la detección se completa correctamente pero no encuentra ningún objeto.
Aunque se cancele una operación, su promesa debe resolverse o rechazarse. El navegador solo permite una llamada a hitTest a la vez y espera a que la promesa se resuelva o se rechace antes de permitir otra, incluso si se cancela la operación o se agota el tiempo de espera. Si un worker se encarga de detectar el objeto, resuelve o rechaza la promesa pendiente cuando se interrumpa la operación. Descartar la respuesta de un worker cuya operación se canceló puede impedir que se seleccionen otros objetos del canvas.
Llama a surface?.invalidate() después de mover objetos o cambiar el zoom, y a surface?.dispose() cuando quites la integración. Ambos métodos se completan de forma síncrona sin devolver ningún valor.
Agrega contexto opcional sobre el objeto
El campo id basta para identificar el objeto. Para incluir un nombre visible, un rol semántico, contexto oculto o los límites de la selección, reemplaza la instrucción que devuelve el objeto detectado en el callback hitTest anterior por la siguiente:
return {
id: "revenue-this-quarter",
name: "Revenue this quarter",
role: "chart-bar",
metadata: { metricId: "quarterly-revenue" },
rect,
};El campo opcional name es visible para el usuario. El campo role proporciona una breve descripción semántica. Incluye metadatos solo si aportan contexto que aún no está visible. El campo opcional rect usa píxeles CSS relativos al área visible de la página, igual que clientX y clientY. El ejemplo de detección ya calcula este rectángulo. Convierte las coordenadas de la escena teniendo en cuenta la escala, el desplazamiento y el zoom.
Agrega indicadores visuales opcionales de selección
Usa el callback opcional renderSelection para dibujar tus propios indicadores cuando el puntero esté sobre un objeto o cuando se seleccione el objeto. No necesita los campos de contexto opcionales.
Para el canvas anterior, define esta función y agrega renderSelection: renderRevenueSelection a las opciones 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);
}
}Este callback vuelve a dibujar la barra con un contorno mientras el puntero esté sobre ella o la barra esté seleccionada. Cuando ambos identificadores son null, vuelve a dibujarla sin contorno.
Agrega controles a los objetos del canvas
Registra los controles personalizados en el elemento del DOM de la superficie. Los objetos dibujados, que la API denomina objetos virtuales, usan tus controles personalizados. Los controles integrados de CSS y de texto no se aplican a estos objetos.
Si hay varios objetos, usa renderSelection para actualizar los controles del host cuando cambie selectedId, antes de que el navegador capture la anotación. Los eventos de los controles incluyen detail.virtualTarget: { surfaceId, targetId }. Dirige cada evento según la identidad capturada y su callback, sin depender de la selección actual. targetId coincide con el id que devuelve hitTest, y surfaceId identifica el registro de la superficie en el navegador. Los eventos normales del DOM omiten virtualTarget.
Los eventos de vista previa, comparación y restablecimiento conservan la identidad del objeto original después de seleccionar otro objeto. Al volver a abrir una anotación guardada, no se repite la detección para reemplazar el objeto capturado ni sus metadatos.
Controla el modo de anotación
Usa toggle() para solicitar un cambio de modo, isActive() para consultar el estado confirmado y el evento oaiannotationmodechange del documento para mantener la interfaz sincronizada. Comprueba la disponibilidad de ambos métodos para que tu sitio funcione también en navegadores antiguos o sin soporte para la API. Agrega este botón y ejecuta el script una vez que exista:
<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() invierte el estado del modo. Pasa true para activarlo o false para desactivarlo. Las solicitudes repetidas con el mismo valor booleano son idempotentes. Pasar true mantiene abierto el editor activo o conserva una activación pendiente. Pasar false cancela la activación pendiente y sigue el flujo de salida habitual.
Llama a toggle() durante una interacción activa del usuario. Su resultado síncrono { accepted } indica que se recibió la solicitud, pero no confirma el cambio de modo. El navegador aún puede impedir el cambio al comprobar si se cumplen los requisitos. Consulta el estado mediante isActive() y el evento, como en el ejemplo.
isActive() no requiere ningún gesto del usuario. El navegador actualiza su valor antes de emitir oaiannotationmodechange, cuyo campo event.detail.active es un booleano. El navegador no envía un evento inicial ni repite el evento cuando se solicita el estado que el modo ya tiene. Por eso, inicializa la interfaz con el método que consulta el estado. Si se revoca el acceso mientras el modo está activo, un último evento indica active: false y, si conservaste la referencia al método de consulta, este devuelve false.
El modo de anotación captura los clics en la página. Mantén presionada la barra espaciadora para usar los controles de la página, incluido tu botón de salida, o sal del modo de anotación desde la interfaz de anotaciones del navegador. Si solo mantienes presionada la barra espaciadora, isActive() sigue devolviendo verdadero. La API no admite oai-annotation-ignore ni ningún otro atributo que deje pasar los clics.
Al salir, se cierra el editor de anotaciones y se conservan las anotaciones guardadas sin enviarlas ni moverlas al editor de mensajes. Llama a cleanupAnnotationButton() cuando quites el componente. La referencia capturada a la API permite eliminar los listeners incluso si el espacio de nombres ya no existe.
Prueba tu integración
Abre tu sitio web en el navegador integrado de la aplicación de escritorio y prueba las funciones que agregaste:
- Activa el modo de anotación y selecciona objetos y texto. Comprueba que el resaltado, los nombres, los rangos y los metadatos correspondan a los objetos o al texto que querías seleccionar.
- Abre una anotación desde el botón de tu sitio. Comprueba el elemento o rango de texto seleccionado, el comentario inicial y el modo del editor.
- Cambia el valor de un control personalizado, compáralo con el original y borra la vista previa. Comprueba que tu aplicación restablezca el estado original.
- Para el contenido del canvas, prueba qué ocurre en los espacios vacíos, al cambiar el tamaño y al modificar la escena. Selecciona otro objeto y confirma que los eventos de los controles sigan actualizando el objeto capturado. Cancela una operación de detección asíncrona y confirma que puedas seguir seleccionando objetos después.
- Guarda una anotación, vuelve a abrirla desde la vista previa de archivos adjuntos del editor de mensajes y edítala. Comprueba que conserve sus controles y su objeto de destino, y que al quitarla se borre cualquier vista previa.
- Envía una anotación con un mensaje. Confirma que ChatGPT reciba el contenido seleccionado, los metadatos y los valores solicitados, incluidas las etiquetas visibles de las opciones de selección y las opciones que el usuario dejó sin cambios en los controles que omiten
currentValue. - Abre el sitio en un navegador que no tenga la API y verifica que las interacciones habituales sigan funcionando.
Para poner a disposición de ChatGPT acciones que pueda realizar en tu sitio web, agrega herramientas del sitio (WebMCP). Las anotaciones incorporan a la conversación la selección y los comentarios del usuario. Las herramientas del sitio permiten que el agente actúe a partir de ese contexto con las funciones que tu aplicación ya ofrece.