Com a API de anotações do navegador, você pode definir o que as pessoas podem selecionar no seu site, que contexto acompanha os comentários e quais controles elas podem usar para visualizar as alterações antes de enviar uma anotação ao ChatGPT.
Nos espaços de trabalho do ChatGPT Enterprise e do ChatGPT Edu, um administrador precisa permitir Personalizações de anotações de sites na política do espaço de trabalho.
As anotações do navegador funcionam no seu site sem alterações no código. As pessoas podem selecionar parte de uma página, adicionar um comentário e enviar a anotação em Contexto para o Codex ou o ChatGPT Work.
Ao desenvolver seu site, você pode usar a API de anotações do navegador para oferecer contexto ou controles específicos do aplicativo. Por exemplo, em uma página de prévia de um sistema de design, você pode anexar prévias de variantes de componentes para mostrar aos desenvolvedores como atualizar esses componentes no site.
Para entender a API de anotações do navegador ou obter ajuda para adicionar suporte a anotações no seu site, instale o plugin Annotations Extensibility.
Instalar o plugin Annotations Extensibility
Veja como a API de anotações do navegador funciona neste guia.
- Abra esta página no navegador integrado do ChatGPT.
- Experimente abrir o prompt sugerido que aparecerá neste cartão.
- Entre no modo de anotação e selecione a tabela abaixo ou um exemplo de código para alternar entre layouts e temas predefinidos.
- Você também pode anotar qualquer item da página para ver como as anotações funcionam por padrão.
Escolha o que personalizar
Comece pela integração adequada ao seu site.
| Objetivo | Integração |
|---|---|
| Permitir que um cartão ou outro grupo de elementos seja selecionado como um único objeto | Alvos de seleção |
| Permitir a seleção de uma expressão ou frase | Contêineres de seleção de texto |
| Incluir mais contexto na seleção | Metadados da seleção |
| Abrir uma anotação com um comentário sugerido a partir de um botão do site | Solicitações de anotação |
| Pedir feedback sobre um trecho específico a partir da sua interface | Solicitações para intervalos de texto |
| Mostrar controles avançados ao abrir uma anotação | Modo padrão do editor |
| Visualizar propriedades do aplicativo ou registrar escolhas | Controles personalizados |
| Selecionar objetos individuais desenhados em um canvas | Superfícies de anotação |
| Ativar ou desativar o modo de anotação pelo site | Controles do modo de anotação |
Este guia se aplica à versão do aplicativo para desktop do ChatGPT lançada no DevDay 2026 e às versões posteriores. A API JavaScript está disponível em document.oai.annotation no navegador integrado do aplicativo, em páginas seguras de nível superior, como as servidas via HTTPS ou em localhost. Quando a API está habilitada, o navegador a instala antes de executar os scripts da página. Verifique se cada método está disponível para que a integração funcione também em navegadores antigos ou sem suporte à API. Inicialize a integração assim que os elementos necessários existirem no DOM. Não é preciso aguardar um evento nem fazer consultas periódicas para saber se a API está pronta.
Os métodos da API retornam de forma síncrona, e os objetos de registro ficam disponíveis para uso imediato. O navegador pode terminar de carregar o editor de anotações depois disso. O callback hitTest de uma superfície pode retornar uma promessa.
Personalize os alvos de seleção
Por padrão, o modo de anotação seleciona elementos do DOM da página e dá preferência a alvos como texto, imagens e controles. Para permitir a seleção de um objeto maior, marque a região que o contém com oai-annotation-container e os descendentes que podem ser selecionados com oai-annotatable.
Neste exemplo, é possível selecionar um cartão de gráfico como um único 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>Ao passar o ponteiro sobre qualquer parte do cartão, o cartão inteiro fica destacado. O valor opcional de oai-annotatable dá ao objeto um nome que aparece para o usuário e para o modelo. Escolha nomes que permitam distinguir objetos próximos ou omita o valor.
O contêiner define onde essas regras de seleção se aplicam. O atributo oai-annotatable, por si só, não muda o comportamento da seleção. Dentro de um contêiner, o navegador seleciona o alvo marcado mais próximo que contém o elemento sob o ponteiro. Se os contêineres estiverem aninhados, vale o mais próximo. Selecionar uma área sem marcação dentro de um contêiner cria uma anotação de página. Fora dos contêineres, a seleção segue o comportamento padrão.
Habilite a seleção de texto
Adicione oai-annotation-container-text a uma região para que as pessoas possam selecionar texto arrastando o ponteiro no modo de anotação. O atributo dispensa um 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>Ao soltar o ponteiro com algum texto selecionado, o editor de anotações de texto abre com o trecho selecionado e seu contexto. Clicar sem selecionar texto não cria uma anotação. Pressionar Escape ou cancelar o gesto cancela a seleção.
O contêiner de seleção de texto ou de elementos do DOM mais próximo determina como o gesto começa. Contêineres de texto não usam marcadores oai-annotatable para selecionar elementos. Aninhe um oai-annotation-container para voltar a selecionar elementos, ou um contêiner de texto para voltar a selecionar texto. Se um elemento tiver os dois atributos, a seleção de texto tem prioridade.
A seleção de texto segue as regras normais da página e pode se estender além do contêiner em que começou. O contêiner não limita o intervalo nem habilita a seleção dentro de um iframe. Ainda é possível selecionar texto em campos de texto, mas os cliques comuns na página e as ações nativas dos controles continuam bloqueados no modo de anotação. Chamadas explícitas a request() mantêm o comportamento habitual.
Os contêineres de seleção de texto só têm efeito no modo de anotação, quando tanto o recurso de anotações de sites quanto a configuração específica do site estão habilitados.
Adicione contexto a uma seleção
Adicione oai-annotation-metadata a um alvo marcado para incluir contexto que talvez não esteja visível na página. Por exemplo, o registro de um contato fictício pode incluir o endereço de e-mail para quando alguém pedir um rascunho de 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>Selecione o contato no modo de anotação para ver o endereço de e-mail incluído no contexto.
Selecionar qualquer uma das duas linhas de texto seleciona o registro inteiro. Os metadados aparecem na anotação e a acompanham na conversa. Inclua apenas o contexto que você pretende compartilhar com o usuário e com o modelo. Para enviar um e-mail, ainda seria necessário ter uma ferramenta de e-mail conectada.
Use um objeto JSON pequeno, sem aninhamento, que respeite estes limites.
- Até seis propriedades, com valores do tipo string, número finito, booleano ou
null. - Até 64 caracteres por chave e até 256 caracteres por valor do tipo string.
- Até 2.048 bytes para o objeto serializado.
As chaves devem começar com uma letra ASCII e conter apenas letras ASCII, dígitos, espaços, sublinhados ou hífens. Use um único espaço entre as palavras. Não há suporte a objetos aninhados nem a arrays. O navegador ignora metadados inválidos.
Você também pode fornecer metadados por meio de request() ou no resultado de hitTest de uma superfície.
Abra uma anotação pelo seu site
Chame document.oai.annotation.request(target, options) diretamente em uma interação do usuário, como um clique em um botão. O alvo pode ser um elemento HTML conectado que esteja na área visível do documento atual ou um Range do DOM. Não é preciso adicionar atributos de anotação ao alvo. Não há suporte a IDs de objetos virtuais.
Solicitações iniciadas pelo site podem exigir permissão do usuário. Para reativar recursos de anotação bloqueados, o usuário pode acessar Ferramentas do site > Recursos de anotação.
Este exemplo abre uma anotação em um parágrafo sem atributos de seleção. Execute o script depois que os dois elementos existirem.
<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.",
});
});Ao selecionar Explique melhor, o usuário pede ao navegador que abra uma anotação com o parágrafo selecionado e um comentário editável. Ele pode editar a anotação, salvá-la e enviá-la junto com a mensagem. Abrir a anotação não envia uma mensagem ao ChatGPT. Só o usuário pode enviá-la.
A entrega padrão leva de três a cinco dias úteis.
Faça a chamada enquanto a interação do usuário ainda estiver ativa. Se você aguardar uma solicitação de rede antes, a interação pode deixar de estar ativa.
O segundo argumento é opcional e aceita os campos a seguir.
| Opção | Comportamento |
|---|---|
mode |
Use "advanced" para abrir os controles avançados de um elemento ou "default" para usar o comportamento padrão do editor. O valor padrão é "default". Os controles personalizados também podem abrir com "default". Consulte Modo padrão do editor. Intervalos de texto aceitam apenas "default". |
enterAnnotationMode |
Defina como true para entrar no modo de anotação e permanecer nele depois de cancelar ou enviar a anotação. |
omitScreenshot |
Defina como true para omitir a captura de tela automática nesta anotação. O valor padrão é false, que mantém o comportamento normal da captura de tela. A opção se aplica a solicitações de anotação para elementos e para intervalos de texto. |
metadata |
Metadados válidos e não vazios substituem os metadados HTML do alvo nesta solicitação. O navegador ignora essa opção para intervalos de texto e quando o elemento alvo está dentro de um shadow DOM. |
initialComment |
Fornece um comentário editável de até 240 unidades de código UTF-16. |
request() retorna um objeto com o campo booleano accepted. Verifique o valor de result.accepted em vez de testar o próprio objeto para saber se o navegador recebeu e validou a solicitação. Esse valor não confirma que o editor abriu nem que a pessoa salvou ou enviou a anotação.
Enquanto o editor carrega, o navegador pode manter uma solicitação aceita em espera. Ele pode recusar outra se houver uma solicitação pendente, se o editor estiver aberto ou se o ChatGPT estiver controlando o navegador.
Solicite uma anotação para um intervalo de texto
Passe um Range do DOM para pedir feedback sobre um trecho sem alterar a seleção de texto do navegador. Este exemplo seleciona apenas a expressão destacada e dispensa o 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>Execute o script depois que os dois elementos existirem.
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.",
});
});Seu período de teste inclui acesso completo por 14 dias.
Abra uma anotação apenas nas palavras destacadas.
O intervalo deve conter texto visível e não vazio no documento atual, com pelo menos parte da seleção na área visível da página. O limite é de 20.000 unidades de código UTF-16. Não há suporte a intervalos recolhidos, texto composto apenas por espaços em branco, texto selecionado oculto ou intervalos em raízes fechadas do shadow DOM.
Solicitações para intervalos de texto usam apenas o editor de texto padrão. O navegador rejeita solicitações com mode: "advanced" e ignora os metadados da solicitação. Mantenha o trecho de texto disponível enquanto uma solicitação aceita aguarda o editor, pois o navegador verifica o intervalo novamente antes de abrir a anotação. Os contêineres de seleção de texto têm uma função independente e permitem selecionar texto arrastando o ponteiro no modo de anotação.
Escolha o modo padrão do editor
Para mostrar os controles avançados assim que uma anotação for aberta pela interface de seleção do navegador, adicione esta tag ao <head> da página.
<meta name="oai-annotation-editor-default-mode" content="advanced" />Para anotações abertas pela sua própria interface, passe { mode: "advanced" } para request(). Use mode: "default" ou omita essa opção para usar o comportamento padrão do editor. A configuração da metatag da página não substitui essa opção da solicitação. Mesmo no modo padrão, o editor pode mostrar mais do que o campo de comentário. Controles personalizados com valores iniciais sugeridos ou que omitem currentValue podem abrir o editor de controles.
Os controles manuais para Ajustar, recolher e clicar com a tecla Option pressionada estão disponíveis apenas no Codex ou em localhost. Em sites hospedados acessados pelo ChatGPT, os controles personalizados registrados aparecem automaticamente, sem o botão Ajustar nem o botão de recolher. O site ainda pode solicitar o modo avançado nesse ambiente.
Adicione controles personalizados
Use registerControls() para associar controles de anotação a um ou mais elementos do DOM. Os controles podem mostrar uma prévia de alterações em propriedades do aplicativo, como um token de espaçamento, ou registrar escolhas a serem incluídas na solicitação, como o tom de um e-mail.
Visualize alterações em um token de espaçamento compartilhado
Os dois cartões deste exemplo usam a mesma propriedade CSS. Registre os controles diretamente nos cartões. Não é preciso adicionar atributos de seleção.
<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>Execute este script depois de criar a prévia.
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);
}Cartão de perfil
Cartão de resumo
Selecione um dos cartões no modo de anotação e altere o valor de Espaçamento interno do cartão (pixels). Se necessário, selecione Ajustar. Os dois cartões mudam ao mesmo tempo.
Anote um dos cartões e altere Espaçamento interno do cartão (pixels) de 16 para 24. Em sites hospedados acessados pelo ChatGPT, os controles aparecem automaticamente. No Codex ou em localhost, selecione Ajustar se necessário. Os dois cartões são atualizados. A anotação registra o rótulo, a referência e os valores anterior e novo. Limpar a prévia restaura o espaçamento interno original. Chame disposeAnnotationControls() ao remover o componente.
Registre uma escolha sem mostrar uma prévia
Este exemplo permite escolher o tom de um e-mail a partir de um parágrafo simples. Não é preciso incluir atributos de seleção nem metadados. Execute o script depois que o parágrafo existir.
<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",
},
],
});Esse controle dispensa um manipulador de eventos porque não mostra uma prévia de alteração na página. Omitir currentValue indica ao navegador que ele deve incluir o tom selecionado mesmo que o usuário mantenha a opção inicial. Chame registration?.dispose() ao remover o parágrafo.
Nos controles de seleção, os callbacks de prévia recebem option.value, como "professional". O histórico de anotações e o ChatGPT recebem o rótulo visível em option.label, como "Professional", tanto para a opção anterior quanto para a selecionada. Use rótulos que expliquem cada escolha. Os IDs internos em value não são enviados como texto da opção.
Configure os controles e os valores iniciais
Use controlsMode: "replace" para mostrar apenas os seus controles nos alvos registrados ou "extend" para exibi-los junto com os controles integrados. O campo opcional controlsHeading define o título do painel. O navegador remove os espaços em branco nas extremidades e aceita títulos de 1 a 80 caracteres. Sem título, o painel mostra a tag HTML do elemento. O título não faz parte do contexto enviado ao ChatGPT.
Cada registro aceita até 12 controles. Cada controle exige um type, um label visível e um identificador callback.
Dê a cada controle um nome que descreva o que ele altera. Use um rótulo estável que descreva a propriedade ou sua função no design, como “Fundo da página”, “Cor principal da marca” ou “Cor de destaque”. Evite usar o valor atual, como “Marfim quente” ou “Verde-floresta escuro”. O rótulo deve continuar fazendo sentido quando o valor mudar. Use currentValue para o valor existente e reference para o token ou a propriedade correspondente.
Neste exemplo, a definição do controle mantém o rótulo separado da cor.
const pageBackgroundControl = {
type: "color",
label: "Page background",
callback: "setPageBackground",
reference: "--color-background",
currentValue: "#f6f3ec",
};Selecione a prévia no modo de anotação e altere o valor de Fundo da página. Se necessário, selecione Ajustar. O rótulo permanece o mesmo quando a cor muda.
A API aceita os seguintes tipos de controle.
| Tipo | Valor | Campos adicionais |
|---|---|---|
color |
Cor hexadecimal, como "#2563eb" |
Nenhum |
range |
Número | min, max e step |
select |
String | options, um array de objetos { label, value } |
toggle |
Booleano | Nenhum |
callback é um identificador do tipo string, e não uma função JavaScript. Use um identificador único dentro do registro. Comece com uma letra ASCII e use apenas letras ASCII, dígitos, sublinhados ou hífens. O campo opcional reference identifica a propriedade alterada e acompanha o rótulo e o valor na anotação.
Defina currentValue como o valor válido da propriedade na edição normal, incluindo alterações ainda não salvas. Não use o estado da prévia nem dados parcialmente inseridos como valor de referência. O navegador usa essa referência para comparar o antes e o depois e restaurar o estado original. Quando o estado do aplicativo mudar, use registration.update({ controls }) para atualizar os valores de currentValue dos controles. As atualizações se aplicam às próximas anotações. As existentes mantêm os valores capturados.
Defina defaultValue para sugerir um valor inicial. Ele tem prioridade sobre currentValue ao inicializar o controle. Se você omitir os dois campos, o valor inicial será branco para uma cor, o mínimo para um intervalo, a primeira opção para um controle de seleção ou false para um controle de ativação.
Mantenha os valores sugeridos separados dos rascunhos comuns. Se uma atualização lançar uma exceção, ou se uma solicitação falhar ou retornar accepted: false, descarte a sugestão que tentou aplicar e restaure tanto os controles quanto as sugestões ao estado anterior. Se a solicitação for aceita, mantenha as sugestões, pois o navegador pode colocar a solicitação na fila antes de capturar os controles.
Valide controles e registros
O navegador aplica os limites abaixo ao validar registros e atualizações.
| Campo ou recurso | Restrição |
|---|---|
| Controles | Até 12 por registro, com identificadores callback únicos. Use apenas os campos definidos para o tipo de controle. |
| Rótulos e títulos | Os rótulos dos controles, os rótulos das opções e controlsHeading devem conter de 1 a 80 unidades de código UTF-16 após a remoção de espaços em branco nas extremidades. O texto dos controles não pode conter caracteres de controle nem caracteres que alterem a direção do texto. |
| Identificadores | callback aceita até 80 unidades de código UTF-16 após a remoção de espaços em branco nas extremidades e deve seguir o formato descrito acima. reference aceita de 1 a 80 caracteres, entre letras ASCII, dígitos e os símbolos _ . / : @ $ # -, sem espaços. |
| Opções de seleção | De 1 a 12 opções, cada uma com uma string value distinta de até 512 unidades de código UTF-16. Quando fornecidos, currentValue e defaultValue devem corresponder ao valor de uma opção. |
| Valores de intervalo | min, max, currentValue e defaultValue devem ser números finitos entre −10.000 e 10.000. É obrigatório que min < max, que step esteja entre 0,001 e 10.000 e não ultrapasse max - min, e que os valores iniciais estejam dentro do intervalo. Os valores iniciais não precisam coincidir com os incrementos definidos por step. |
| Cores e controles de ativação | As cores devem usar 3, 4, 6 ou 8 dígitos hexadecimais após #. Os controles de ativação aceitam true ou false. |
| Alvos | Ao criar o registro, forneça de 1 a 128 alvos, todos elementos do documento atual. Nas atualizações, passe targets: [] para desvincular os alvos. Cada documento aceita até 64 registros de controles e 1.024 associações entre registros e alvos. |
| Controles serializados | Os dados JSON que contêm controls, controlsHeading e controlsMode devem ocupar no máximo 16.384 unidades de código UTF-16. Use dados que possam ser codificados como JSON, sem funções, símbolos ou inteiros de precisão arbitrária. |
registerControls() e registration.update() podem lançar exceções de forma síncrona se uma definição ou um alvo for inválido ou se algum limite for excedido. Atualizar o registro depois de chamar dispose() também lança uma exceção. Uma atualização rejeitada preserva o registro anterior, inclusive os controles e alvos. Trate as falhas no ponto da chamada, mantenha a edição normal disponível e reutilize ou descarte os registros sob sua responsabilidade para respeitar os limites.
Trate prévias e restaurações
O evento oaiannotationcontrolchange se propaga do elemento selecionado para seus ancestrais. A propriedade detail contém callback, value e action.
| Ação | Finalidade do valor fornecido |
|---|---|
preview |
Mostrar a alteração solicitada. |
preview-original |
Mostrar temporariamente o estado original para comparação. |
reset |
Restaurar o estado original quando a prévia for limpa. |
Aplique o valor fornecido em cada ação, como no exemplo de espaçamento. Implemente manipuladores cujos efeitos possam ser revertidos e que possam ser chamados várias vezes com segurança. Eventos de prévia não solicitam alterações permanentes. Salve as alterações pelo fluxo normal do aplicativo. Mantenha separados os rascunhos comuns, os dados parcialmente inseridos e as prévias para que edições não relacionadas não sobrescrevam uma prévia. Quando alguém editar a mesma configuração, substitua a prévia e atualize o valor de referência para as próximas anotações.
Guarde o objeto de registro para fazer atualizações e liberar os recursos. update() aceita qualquer combinação de targets, controls, controlsHeading e controlsMode. Os campos omitidos mantêm os valores anteriores. Por exemplo, use registration.update({ targets: newElement }) ao substituir o elemento do DOM de um componente. As coleções de alvos capturam os elementos existentes e não acompanham os que vierem a corresponder ao seletor.
Passe targets: [] para desvincular o registro dos alvos ou controls: [] para limpar seus controles. Ao remover a integração, chame dispose() e remova os ouvintes de eventos. Nessa etapa, restaure também a renderização normal. dispose() apenas remove o registro de controles e não desfaz as alterações da prévia. No exemplo de espaçamento, o código remove o estilo definido diretamente no elemento para restaurar o valor original do CSS. Os dois métodos retornam de forma síncrona, sem valor de retorno. As atualizações afetam as próximas seleções. Anotações salvas mantêm os controles e o alvo capturados.
O alvo dos eventos de controle é o elemento capturado pela anotação. Atualizar os alvos de um registro não redireciona as anotações existentes. Eventos de restauração ainda podem ser disparados em um elemento removido. Nesse caso, um ouvinte no antigo elemento pai não os receberá.
Permita selecionar objetos em um canvas
Com uma superfície de anotação, seu aplicativo pode identificar objetos individuais desenhados em um canvas. Registre o elemento host com registerSurface() e forneça uma função hitTest que retorne um objeto com um id estável ou null para um espaço vazio.
O host deve ser um elemento HTML conectado, fora do shadow DOM, em um documento seguro de nível superior. Não é possível registrar superfícies dentro de um iframe.
Comece pela seleção e identificação dos objetos
Este exemplo desenha uma barra que representa a receita e permite selecioná-la usando apenas hitTest e um ID de objeto estável. Coloque o script depois do 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" };
},
});Entre no modo de anotação e selecione uma forma para ver o nome, o ID e a cor de preenchimento na anotação. Não é possível selecionar o espaço entre as formas.
No exemplo de código, selecionar a barra no modo de anotação a identifica como revenue-this-quarter. Mantenha os IDs estáveis dentro de cada superfície. O exemplo retorna null para espaços vazios.
hitTest pode retornar uma promessa e recebe um AbortSignal em signal para cancelar operações substituídas por outras mais recentes. O navegador aguarda 250 milissegundos antes de recorrer à seleção pelo DOM. Erros e resultados inválidos também levam à seleção pelo DOM. Retorne null explicitamente quando o teste for bem-sucedido, mas não encontrar um objeto na posição indicada.
Mesmo que a operação seja cancelada, sua promessa precisa ser resolvida ou rejeitada. O navegador permite apenas um callback hitTest em andamento e só permite outro quando a promessa é resolvida ou rejeitada, mesmo em caso de cancelamento ou de tempo limite excedido. Se um worker identificar os objetos sob o ponteiro, resolva ou rejeite a promessa pendente quando a operação for abortada. Descartar a resposta de uma operação cancelada do worker pode impedir novas seleções no canvas.
Chame surface?.invalidate() depois de mover objetos ou alterar o zoom e surface?.dispose() ao remover a integração. Os dois métodos retornam de forma síncrona, sem valor de retorno.
Adicione contexto opcional ao objeto
O id basta para identificar o objeto. Para incluir um nome de exibição, uma função semântica, contexto oculto ou os limites da seleção, altere o retorno do callback hitTest do exemplo anterior. Quando encontrar o objeto, retorne os campos a seguir.
return {
id: "revenue-this-quarter",
name: "Revenue this quarter",
role: "chart-bar",
metadata: { metricId: "quarterly-revenue" },
rect,
};O campo opcional name fica visível para o usuário. Use role para descrever brevemente a função semântica do objeto. Inclua metadados apenas quando acrescentarem contexto que ainda não esteja visível. O campo opcional rect usa pixels CSS relativos à área visível da página, no mesmo sistema de coordenadas de clientX e clientY. O exemplo de seleção já calcula esse retângulo. Ao converter as coordenadas da cena, leve em conta a escala, o deslocamento e o zoom.
Adicione indicadores visuais de seleção, se necessário
Use o callback opcional renderSelection para desenhar seus próprios indicadores de que um objeto está sob o ponteiro ou selecionado. Esse callback dispensa os campos de contexto opcionais.
Para o canvas acima, defina esta função e adicione renderSelection: renderRevenueSelection às opções 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);
}
}Esse callback redesenha a barra com um contorno enquanto ela estiver sob o ponteiro ou selecionada. Quando os dois IDs são null, ele redesenha a barra sem contorno.
Adicione controles aos objetos do canvas
Registre os controles personalizados no elemento do DOM da superfície. Os objetos desenhados, chamados de objetos virtuais na API, usam seus controles personalizados. Os controles integrados de CSS e texto não se aplicam a eles.
Quando houver vários objetos, use renderSelection para atualizar os controles do host quando selectedId mudar, antes que o navegador capture a anotação. Os eventos de controle incluem detail.virtualTarget: { surfaceId, targetId }. Encaminhe cada evento com base na identidade capturada e no callback do evento, sem depender da seleção atual. targetId corresponde ao id retornado por hitTest. O campo surfaceId identifica o registro da superfície no navegador. Eventos comuns do DOM omitem virtualTarget.
Os eventos de prévia, comparação e restauração mantêm a identidade do objeto original mesmo depois que outro objeto é selecionado. Reabrir uma anotação salva não executa um novo teste para substituir o objeto ou os metadados capturados.
Controle o modo de anotação
Use toggle() para solicitar uma mudança de modo, isActive() para consultar o estado confirmado e o evento oaiannotationmodechange do documento para manter sua interface sincronizada. Verifique se os dois métodos estão disponíveis para atender também a navegadores antigos ou sem suporte à API. Adicione o botão abaixo e execute o script depois que ele existir.
<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() inverte o estado do modo de anotação. Passe true para ativá-lo ou false para desativá-lo. Solicitações repetidas com o mesmo valor booleano são idempotentes. Passar true preserva o editor ativo ou uma ativação pendente. Passar false cancela a ativação pendente e segue o fluxo normal de saída.
Chame toggle() enquanto uma interação do usuário estiver ativa. O resultado síncrono { accepted } indica se a solicitação foi aceita, sem confirmar a mudança de modo. O navegador ainda pode impedir a mudança ao verificar se as condições necessárias foram atendidas. Consulte o estado por meio de isActive() e do evento, como no exemplo.
isActive() dispensa um gesto do usuário. O navegador atualiza o estado retornado pelo método antes de disparar oaiannotationmodechange, cujo event.detail.active é booleano. O navegador não envia um evento inicial nem repete o evento quando uma solicitação explícita mantém o estado atual. Por isso, inicialize a interface consultando o método. Se o acesso for revogado enquanto o modo estiver ativo, um último evento informará active: false e uma referência mantida ao método de consulta retornará false.
O modo de anotação captura os cliques na página. Mantenha a barra de espaço pressionada para usar os controles da página, inclusive o botão de saída, ou saia pela interface de anotações do navegador. Enquanto você apenas mantém a barra de espaço pressionada, isActive() continua retornando verdadeiro. A API não aceita oai-annotation-ignore nem outro atributo que permita que os cliques cheguem diretamente aos elementos da página.
Ao sair do modo de anotação, o editor é fechado e as anotações salvas são preservadas, sem que sejam enviadas ou movidas para o editor de mensagens. Chame cleanupAnnotationButton() ao remover o componente. A referência capturada à API permite remover os ouvintes de eventos mesmo que o namespace já não esteja disponível.
Teste sua integração
Abra o site no navegador integrado do aplicativo para desktop e teste os recursos que você adicionou.
- Entre no modo de anotação e selecione objetos e texto. Verifique se os destaques, nomes, intervalos e metadados correspondem aos alvos pretendidos.
- Abra uma anotação pelo botão do site. Confira o elemento ou intervalo de texto selecionado, o comentário inicial e o modo do editor.
- Altere um controle personalizado, compare a prévia com o original e limpe a prévia. Verifique se o aplicativo restaura o estado original.
- Para conteúdo em canvas, teste a seleção de espaços vazios, o redimensionamento e as mudanças na cena. Alterne entre objetos e confirme que os eventos de controle continuam atualizando o alvo capturado. Cancele um teste assíncrono de identificação do objeto sob o ponteiro e confirme que as próximas seleções ainda funcionam.
- Salve uma anotação, reabra-a pela prévia do anexo no editor de mensagens e edite-a. Verifique se ela mantém os controles e o alvo e se, ao removê-la, qualquer prévia é limpa.
- Envie uma anotação junto com uma mensagem. Confirme que o ChatGPT recebe o conteúdo selecionado, os metadados e os valores solicitados, incluindo os rótulos visíveis das opções de seleção e as escolhas mantidas sem alteração nos controles que omitem
currentValue. - Abra o site em um navegador sem a API e verifique se as interações normais continuam funcionando.
Para disponibilizar ações que o ChatGPT pode executar no seu site, adicione ferramentas do site (WebMCP). As anotações levam para a conversa o conteúdo selecionado e os comentários da pessoa. Com as ferramentas do site, o agente pode usar esse contexto para agir por meio dos recursos já disponíveis no seu aplicativo.