Расширение возможностей аннотаций
Browser Annotation API позволяет вашему сайту настраивать, что пользователи могут выбирать, какой контекст сопровождает их отзывы и какие элементы управления используются для предварительного просмотра изменений перед отправкой аннотации в ChatGPT.
Аннотации в браузере работают на вашем сайте без изменений в коде. Пользователи могут выбрать часть страницы, добавить комментарий и отправить её в контексте в Codex или ChatGPT Work.
Как разработчик, вы можете использовать Browser Annotation API, чтобы предоставлять контекст или элементы управления, специфичные для вашего приложения. Например, в средстве предварительного просмотра дизайн-системы можно добавить предпросмотры вариантов компонентов, чтобы разработчики понимали, как обновить компоненты на сайте.
Чтобы получить помощь в изучении Browser Annotation API или добавлении поддержки аннотаций на ваш сайт, установите плагин Annotations Extensibility.
Установить плагин Annotations Extensibility
Попробуйте
Посмотрите, как Browser Annotation API работает в этом руководстве.
- Откройте эту страницу во встроенном браузере ChatGPT.
- Попробуйте открыть предложенный запрос, который появится в этой карточке.
- Перейдите в режим аннотаций, затем выберите таблицу ниже или пример кода, чтобы переключаться между предустановленными макетами и темами.
- Вы по-прежнему можете добавлять аннотации к любому элементу страницы и наблюдать стандартное поведение аннотаций.
Чтобы попробовать аннотации, откройте эту страницу во встроенном браузере ChatGPT.
Выберите, что настроить
Начните с интеграции, подходящей для вашего сайта:
| Цель | Интеграция |
|---|---|
| Сделать карточку или другую группу элементов доступной для выбора как единый объект | Целевые объекты выбора |
| Дать пользователям возможность выделять фразу или предложение | Контейнеры выделения текста |
| Добавить дополнительный контекст к выбранному объекту | Метаданные выбора |
| Открыть аннотацию с предложенным комментарием по нажатию собственной кнопки | Запросы аннотаций |
| Запросить отзыв о конкретном фрагменте текста из собственного интерфейса | Запросы для диапазонов текста |
| Показывать расширенные элементы управления при открытии аннотации | Настройки редактора по умолчанию |
| Предварительно просматривать свойства приложения или собирать выбранные значения | Пользовательские элементы управления |
| Выбирать отдельные объекты, нарисованные внутри canvas | Поверхности аннотаций |
| Включать или выключать режим аннотаций с вашего сайта | Управление режимом аннотаций |
Это руководство предназначено для версии настольного приложения ChatGPT, выпущенной к DevDay 2026, и более поздних версий.
JavaScript API доступен через document.oai.annotation во
встроенном браузере приложения на защищённых страницах верхнего уровня, например HTTPS или localhost. Когда API
включён, браузер устанавливает его до запуска скриптов страницы. Проверяйте наличие
каждого метода для поддержки старых или неподдерживаемых браузеров, затем инициализируйте
интеграцию, когда её DOM-элементы уже существуют. Событие готовности или опрос не нужны.
Методы API возвращают результат синхронно, а дескрипторы регистраций готовы к использованию
сразу. Браузер может завершить загрузку редактора аннотаций позже.
Обратный вызов hitTest поверхности может возвращать промис.
Настройте целевые объекты выбора
По умолчанию режим аннотаций выбирает элементы из DOM страницы, отдавая предпочтение
таким объектам, как текст, изображения и элементы управления. Чтобы можно было выбрать более крупный объект,
пометьте содержащую его область атрибутом oai-annotation-container, а доступные для выбора
дочерние элементы — атрибутом oai-annotatable.
В этом примере карточка с диаграммой становится доступна для выбора как единый объект:
<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>При наведении указателя на любую точку внутри карточки выделяется вся карточка. Необязательное
значение oai-annotatable задаёт имя объекта, которое видят пользователь и модель.
Выбирайте имена, позволяющие различать соседние объекты, или не указывайте значение.
Контейнер определяет, где действуют эти правила выбора. Сам по себе
атрибут oai-annotatable не меняет поведение выбора.
Внутри контейнера браузер выбирает ближайший помеченный целевой объект, содержащий
элемент под указателем. Для вложенных контейнеров используется ближайший контейнер.
Выбор непомеченных областей внутри контейнера создаёт аннотацию к веб-странице; области за пределами
всех контейнеров сохраняют стандартное поведение.
Включите выделение текста
Добавьте oai-annotation-container-text к области, чтобы пользователи могли выделять текст перетаскиванием
в режиме аннотаций. Атрибуту не требуется значение:
<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>При завершении непустого выделения открывается редактор текстовой аннотации с выбранным диапазоном и его контекстом. Щелчок без выделения текста не создаёт аннотацию. Escape или отмена жеста отменяют выделение.
Ближайший контейнер выделения текста или выбора DOM-элементов определяет, как начинается жест.
Текстовые контейнеры не используют маркеры oai-annotatable для выбора элементов. Вложите
oai-annotation-container, чтобы снова включить выбор элементов, или текстовый контейнер, чтобы
снова включить выделение текста. Если оба атрибута заданы на одном элементе, выделение текста
имеет приоритет.
Выделение текста следует обычным правилам выделения на странице и может выходить за пределы
исходного контейнера. Контейнер не обрезает диапазон и не включает выделение
внутри iframe. Текстовые поля остаются доступными для выделения, но обычные щелчки по странице и
стандартные действия с элементами управления остаются заблокированными в режиме аннотаций. Явные
вызовы request() сохраняют прежнее поведение.
Добавьте контекст к выбранному объекту
Добавьте oai-annotation-metadata к помеченному целевому объекту, чтобы включить контекст, который может
не отображаться на странице. Например, строка с вымышленным контактом может содержать
адрес электронной почты для запроса на составление письма:
<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>При выборе любой из двух строк выбирается вся строка контакта. Метаданные отображаются вместе с аннотацией и сопровождают её в разговоре. Добавляйте только тот контекст, которым намерены поделиться и с пользователем, и с моделью. Для отправки письма по-прежнему потребуется подключённый почтовый инструмент.
Используйте небольшой плоский объект JSON со следующими ограничениями:
- Не более шести свойств со значениями типа строка, конечное число, логическое значение или
null. - Ключи длиной до 64 символов и строковые значения длиной до 256 символов.
- Не более 2 048 байт для сериализованного объекта.
Ключи должны начинаться с буквы ASCII и содержать только буквы ASCII, цифры, пробелы, знаки подчёркивания или дефисы. Используйте одиночные пробелы между словами. Вложенные объекты и массивы не поддерживаются. Браузер игнорирует некорректные метаданные.
Вы также можете передавать метаданные через request() или результат hitTest
поверхности.
Откройте аннотацию с вашего сайта
Вызывайте document.oai.annotation.request(target, options) непосредственно при действии
пользователя, например при нажатии кнопки. Целью может быть подключённый к DOM HTML-элемент
в видимой области текущего документа или
DOM Range. Атрибуты аннотаций для него
не требуются. Идентификаторы виртуальных объектов не поддерживаются.
Запросы, инициированные сайтом, могут требовать разрешения пользователя; пользователи могут повторно включить заблокированные функции аннотаций в разделе Инструменты сайта > Функции аннотаций.
Добавьте эту кнопку рядом с карточкой диаграммы из примера выбора, затем запустите скрипт, когда оба элемента уже существуют:
<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.",
});
});Нажатие Объяснить подробнее запрашивает у браузера открытие аннотации с выбранной карточкой и редактируемым комментарием. Пользователь может изменить, сохранить и отправить её со своим сообщением. Открытие аннотации не отправляет сообщение в ChatGPT; только пользователь может отправить его.
Выполняйте вызов в рамках активного действия пользователя. Если сначала ожидать завершения сетевого запроса, связь с этим действием может быть потеряна.
Необязательный второй аргумент поддерживает следующие поля:
| Параметр | Поведение |
|---|---|
mode |
Используйте "advanced", чтобы открыть расширенные элементы управления для элемента, или "default" для стандартного поведения редактора. По умолчанию — "default". Пользовательские элементы управления могут открываться и с "default"; см. Настройки редактора по умолчанию. Диапазоны текста поддерживают только "default". |
enterAnnotationMode |
Установите true, чтобы перейти в режим аннотаций и оставаться в нём после отмены или отправки аннотации. |
metadata |
Корректные непустые метаданные заменяют HTML-метаданные целевого объекта для этого запроса. Параметр игнорируется для диапазонов текста и когда целевой элемент находится внутри теневого DOM. |
initialComment |
Задаёт редактируемый комментарий длиной до 240 кодовых единиц UTF-16. |
request() возвращает объект с логическим полем accepted. Проверяйте
result.accepted, а не сам объект результата, чтобы определить, получил ли браузер
запрос и прошёл ли он проверку. Это не подтверждает, что редактор открылся или что
пользователь сохранил либо отправил аннотацию.
Пока редактор загружается, браузер может удерживать один принятый запрос. Он может отклонить другой запрос, пока предыдущий ожидает обработки, редактор открыт или ChatGPT управляет браузером.
Запросите аннотацию для диапазона текста
Передайте DOM Range, чтобы запросить отзыв о фрагменте, не меняя
выделение текста в браузере. В этом примере выбирается содержимое абзаца; для него не нужен
атрибут 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>Запустите этот скрипт, когда оба элемента уже существуют:
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.",
});
});Диапазон должен содержать непустой видимый текст в текущем документе, причём хотя бы часть выделения должна находиться в видимой области страницы. Он может содержать не более 20 000 кодовых единиц UTF-16. Свёрнутые диапазоны, текст только из пробельных символов, скрытый выделенный текст и диапазоны в закрытых теневых корнях не поддерживаются.
Запросы для диапазонов текста используют только стандартный текстовый редактор. Запрос с
mode: "advanced" отклоняется, а метаданные запроса игнорируются. Сохраняйте целевой
текст доступным, пока принятый запрос ожидает редактора: браузер
повторно проверяет диапазон перед его открытием. Контейнеры выделения текста отдельно
позволяют пользователям выделять текст перетаскиванием в режиме аннотаций.
Выберите режим редактора по умолчанию
Чтобы сразу показывать расширенные элементы управления для аннотаций, открываемых через
интерфейс выбора браузера, добавьте этот тег в <head> вашей страницы:
<meta name="oai-annotation-editor-default-mode" content="advanced" />Для аннотаций, открываемых из собственного интерфейса, передавайте { mode: "advanced" } в
request(). Используйте mode: "default" или опустите этот параметр для стандартного поведения редактора.
Мета-настройка страницы не переопределяет этот параметр запроса. Режим по умолчанию
не гарантирует, что редактор будет содержать только комментарий: пользовательские элементы управления с предложенными начальными
значениями или элементы управления без currentValue могут открывать редактор элементов управления.
Ручное управление через Настроить, сворачивание и Option-щелчок доступно только в Codex или на localhost. На размещённых сайтах в ChatGPT зарегистрированные пользовательские элементы управления появляются автоматически, без кнопок Настроить и сворачивания. Запрошенный страницей расширенный режим там по-прежнему работает.
Добавьте пользовательские элементы управления
Используйте registerControls(), чтобы связать элементы управления аннотациями с одним или несколькими DOM-
элементами. Элементы управления могут предварительно показывать свойства приложения, например токен отступа,
или собирать выбранные значения для включения в запрос, например тон письма.
Предварительный просмотр общего токена отступа
Обе карточки в этом примере используют одно и то же свойство 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>Запустите этот скрипт после создания предпросмотра:
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);
}Добавьте аннотацию к любой карточке и измените Внутренний отступ карточки (пиксели) с 16 на 24. На
размещённых сайтах в ChatGPT элементы управления появляются автоматически; в Codex или на
localhost при необходимости выберите Настроить. Обе карточки обновятся. Аннотация сохранит подпись, ссылку
и старое и новое значения. Очистка предпросмотра восстанавливает исходный отступ.
Вызывайте disposeAnnotationControls() при удалении компонента.
Сбор выбранного значения без предпросмотра
Используйте строку контакта из примера метаданных, чтобы предложить выбрать тон письма:
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",
},
],
});Этому элементу управления не нужен обработчик событий, поскольку он не показывает изменение страницы
в предпросмотре. Отсутствие currentValue указывает браузеру включить выбранный тон,
даже если пользователь оставит начальный вариант. Вызывайте registration?.dispose() при
удалении строки.
Для элементов управления выбором обратные вызовы предпросмотра получают option.value, например
"professional". История аннотаций и ChatGPT получают видимую
option.label, например "Professional", как для предыдущего, так и для выбранного
варианта. Используйте подписи, объясняющие каждый вариант; внутренние идентификаторы в value не
отправляются в качестве текста выбранного варианта.
Настройте элементы управления и начальные значения
Используйте controlsMode: "replace", чтобы показывать только ваши элементы управления для зарегистрированных
целевых объектов, или "extend", чтобы показывать их вместе со встроенными. Необязательный
controlsHeading задаёт название панели. Браузер удаляет пробелы по краям и принимает от 1 до 80
символов. Без заголовка панель показывает HTML-тег элемента. Этот
заголовок не включается в контекст, отправляемый в ChatGPT.
Одна регистрация поддерживает до 12 элементов управления. Каждому необходимы type, видимая
label и идентификатор callback:
| Тип | Значение | Дополнительные поля |
|---|---|---|
color |
Цвет в шестнадцатеричном формате, например "#2563eb" |
Нет |
range |
Число | min, max и step |
select |
Строка | options, массив объектов { label, value } |
toggle |
Логическое значение | Нет |
callback — строковый идентификатор, а не функция JavaScript. Он должен быть уникальным
в пределах регистрации, начинаться с буквы ASCII и содержать буквы ASCII,
цифры, знаки подчёркивания или дефисы. Необязательный reference определяет изменяемое
свойство и сопровождает подпись и значение в аннотации.
Задайте currentValue равным допустимому обычному значению свойства, включая несохранённые
изменения. Не используйте состояние предпросмотра или незавершённый ввод в качестве исходного значения. Браузер
использует его для сравнения значений до и после изменения и для сброса. При изменении состояния приложения используйте
registration.update({ controls }), чтобы обновить значения currentValue
элементов управления. Обновления влияют на будущие аннотации; существующие аннотации сохраняют
зафиксированные значения.
Задайте defaultValue для предложенного начального значения; оно имеет приоритет над
currentValue при определении начального
состояния элемента управления. Если опустить оба параметра, начальным значением будет белый цвет для выбора цвета, минимум
для диапазона, первый вариант для списка выбора или false для переключателя.
Храните предложенные значения отдельно от обычных черновиков. Если обновление выбрасывает исключение или
запрос завершается ошибкой либо возвращает accepted: false, отбросьте новое предложение и
восстановите прежнее состояние элементов управления и предложений. Сохраняйте предложения, когда запрос
принят: браузер может поставить его в очередь перед фиксацией элементов управления.
Проверяйте элементы управления и регистрации
Браузер проверяет регистрации и обновления на соответствие следующим ограничениям:
| Поле или ресурс | Ограничение |
|---|---|
| Элементы управления | До 12 на регистрацию, с уникальными идентификаторами callback. Используйте только поля, определённые для соответствующего типа элемента управления. |
| Подписи и заголовки | Подписи элементов управления, подписи вариантов и controlsHeading должны быть непустыми после удаления пробелов по краям и содержать не более 80 кодовых единиц UTF-16. Текст элементов управления не может содержать управляющие символы или символы изменения направления текста. |
| Идентификаторы | callback содержит не более 80 кодовых единиц UTF-16 после удаления пробелов по краям и использует описанный выше формат. reference содержит 1–80 символов и допускает буквы ASCII, цифры и _ . / : @ $ # -, без пробелов. |
| Варианты выбора | 1–12 вариантов с различающимися строками value длиной не более 512 кодовых единиц UTF-16. Переданные currentValue и defaultValue должны совпадать со значением одного из вариантов. |
| Значения диапазона | min, max, currentValue и defaultValue должны быть конечными числами от −10 000 до 10 000. Требуются min < max, значение step от 0.001 до 10 000, не превышающее max - min, и начальные значения в пределах диапазона. Начальные значения не обязаны соответствовать шагу step. |
| Цвета и переключатели | Цвета должны содержать 3, 4, 6 или 8 шестнадцатеричных цифр после #. Значения переключателей должны быть true или false. |
| Целевые объекты | От 1 до 128 целевых объектов при регистрации; все должны быть элементами текущего документа. В обновлениях можно передать targets: [] для отсоединения. Каждый документ поддерживает до 64 регистраций элементов управления и 1 024 связей между регистрациями и целевыми объектами. |
| Сериализованные элементы управления | Данные JSON, содержащие controls, controlsHeading и controlsMode, должны укладываться в 16 384 кодовые единицы UTF-16. Используйте данные, которые можно закодировать в JSON, без функций, символов и больших целых чисел. |
registerControls() и registration.update() могут синхронно выбрасывать исключения при
некорректных определениях, целевых объектах или превышении ограничений. Обновление после dispose()
также выбрасывает исключение. Отклонённое обновление сохраняет прежнюю регистрацию, включая
её элементы управления и целевые объекты. Обрабатывайте ошибки в месте вызова, сохраняйте возможность обычного
редактирования и повторно используйте или освобождайте принадлежащие вам регистрации, чтобы не выходить за
пределы ограничений.
Обрабатывайте предпросмотр и сброс
Событие oaiannotationcontrolchange всплывает от выбранного элемента. Его
detail содержит callback, value и action:
| Действие | Для чего применять переданное значение |
|---|---|
preview |
Показать запрошенное изменение. |
preview-original |
Временно показать исходное состояние для сравнения. |
reset |
Восстановить исходное состояние при очистке предпросмотра. |
Применяйте переданное значение для каждого действия, как в примере с отступом. Сделайте обработчики обратимыми и безопасными для повторных вызовов. События предпросмотра не запрашивают постоянное изменение; сохраняйте его через обычный процесс сохранения в вашем приложении. Храните обычные черновики, незавершённый ввод и предпросмотры отдельно, чтобы несвязанные изменения не перезаписывали предпросмотр. Когда пользователь меняет ту же настройку, заменяйте её предпросмотр и обновляйте исходное значение для будущих аннотаций.
Сохраняйте дескриптор регистрации для обновлений и очистки. update() принимает любую
комбинацию targets, controls, controlsHeading и controlsMode.
Пропущенные поля сохраняют прежние значения. Например, используйте
registration.update({ targets: newElement }) при замене DOM-элемента
компонента. Коллекции целевых объектов фиксируют существующие элементы и не отслеживают будущие
совпадения с селектором.
Передайте targets: [], чтобы отсоединить регистрацию, или controls: [], чтобы очистить её
элементы управления. Вызывайте dispose() и удаляйте обработчики событий при удалении
интеграции. Очистка также должна восстанавливать обычное отображение: dispose() только
удаляет регистрацию элементов управления и не отменяет изменения предпросмотра. Пример с отступом
удаляет переопределение во встроенном стиле, чтобы восстановить исходное значение CSS. Оба метода
возвращаются синхронно без значения. Обновления влияют на
будущие выборы; сохранённые аннотации сохраняют зафиксированные элементы управления и целевой объект.
События элементов управления направляются элементу, зафиксированному аннотацией. Обновление целевых объектов регистрации не перенаправляет существующие аннотации. События сброса могут по-прежнему возникать на удалённом элементе, поэтому обработчик на его бывшем родителе не получит их.
Сделайте объекты canvas доступными для выбора
Поверхность аннотаций позволяет вашему приложению определять отдельные объекты, нарисованные
внутри canvas. Зарегистрируйте элемент-контейнер с помощью registerSurface() и предоставьте
функцию hitTest, которая возвращает объект со стабильным id или null для
пустого пространства.
Контейнер должен быть подключённым к DOM HTML-элементом вне теневого DOM в защищённом документе верхнего уровня. Регистрация поверхностей внутри iframe не поддерживается.
В этом примере рисуется столбец выручки, который становится доступен для выбора. Разместите скрипт после 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"
);
},
});Наведение указателя на столбец в режиме аннотаций выделяет его. При выборе открывается аннотация с именем объекта, метаданными и снимком экрана выбранной области.
Сохраняйте идентификаторы стабильными в пределах поверхности. Необязательный name виден пользователю;
role задаёт краткое смысловое описание. Необязательный rect использует CSS-пиксели
относительно видимой области страницы, что соответствует clientX и clientY. Преобразуйте координаты сцены,
учитывая масштаб, панорамирование и приближение.
hitTest может возвращать промис и получает AbortSignal как signal для
отмены работы, которая уже не актуальна. Браузер ждёт 250 миллисекунд, прежде чем
вернуться к выбору DOM-элементов. Ошибки и некорректные результаты также приводят к этому переходу. Возвращайте
null явно, если проверка попадания выполнена успешно, но объект не найден.
Отменённая работа всё равно должна разрешить или отклонить свой промис. Браузер допускает
только один выполняющийся обратный вызов hitTest и удерживает этот слот до завершения промиса,
даже после отмены или тайм-аута. Если выбор обрабатывает worker,
завершайте ожидающий промис при прерывании его работы; отбрасывание ответа отменённого worker
может заблокировать последующий выбор объектов canvas.
Используйте необязательный обратный вызов renderSelection для обратной связи, специфичной для приложения.
Очищайте эту обратную связь, когда оба идентификатора равны null. Вызывайте surface?.invalidate() после
перемещения объектов или изменения масштаба, а surface?.dispose() — при удалении
интеграции. Оба вызова возвращаются синхронно без значения.
Добавьте элементы управления к объектам canvas
Зарегистрируйте пользовательские элементы управления на DOM-элементе поверхности. Нарисованные объекты, называемые виртуальными объектами в API, используют ваши пользовательские элементы управления; встроенные элементы управления CSS и текстом к ним не применяются.
Для нескольких объектов используйте renderSelection, чтобы обновлять элементы управления контейнера при
изменении selectedId, прежде чем браузер зафиксирует аннотацию. События элементов управления
содержат detail.virtualTarget: { surfaceId, targetId }. Направляйте каждое событие
по зафиксированному идентификатору и его callback, а не по текущему
выбору. targetId совпадает с id из hitTest; surfaceId определяет
регистрацию поверхности в браузере. Обычные DOM-события не содержат virtualTarget.
События предпросмотра, сравнения и сброса сохраняют идентификатор исходного объекта после выбора другого объекта. Повторное открытие сохранённой аннотации не запускает заново проверку попадания для замены зафиксированного объекта или метаданных.
Управляйте режимом аннотаций
Используйте toggle() для запроса смены режима, isActive() для чтения подтверждённого состояния,
а событие документа oaiannotationmodechange — для синхронизации вашего интерфейса.
Проверяйте наличие обоих методов в старых или неподдерживаемых браузерах. Добавьте эту кнопку,
затем запустите скрипт, когда она уже существует:
<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() переключает режим на противоположный. Передайте true, чтобы включить его, или false, чтобы
выключить. Повторные запросы с тем же логическим значением идемпотентны. Передача
true сохраняет активный редактор или ожидающую активацию; false отменяет
ожидающую активацию и использует обычный процесс выхода.
Вызывайте toggle() в рамках текущего действия пользователя. Синхронный результат { accepted }
подтверждает приём запроса, а не смену режима. Проверки
доступности в браузере всё ещё могут помешать изменению. Читайте состояние из isActive()
и события, как показано в примере.
isActive() не требует действия пользователя. Браузер обновляет его перед отправкой
oaiannotationmodechange, у которого event.detail.active — логическое значение. Браузер
не отправляет начальное событие или повторное событие при явном запросе уже установленного состояния, поэтому инициализируйте интерфейс
через геттер. Если доступ отозван при активном режиме, последнее событие сообщает
active: false, а сохранённый геттер возвращает false.
Режим аннотаций перехватывает щелчки по странице. Удерживайте Пробел, чтобы использовать элементы управления страницы,
включая вашу кнопку выхода, или выйдите через интерфейс аннотаций браузера.
Само по себе удержание Пробела оставляет isActive() в значении true. API не поддерживает
oai-annotation-ignore или другой атрибут, позволяющий пропускать щелчки.
При выходе редактор закрывается, а сохранённые аннотации остаются без отправки
или переноса в поле ввода сообщения. Вызывайте cleanupAnnotationButton() при
удалении компонента. Сохранённая ссылка на API позволяет при очистке удалить
обработчики, даже если пространство имён исчезло.
Протестируйте интеграцию
Откройте ваш сайт во встроенном браузере настольного приложения и протестируйте добавленные функции:
- Перейдите в режим аннотаций и выберите объекты и текст. Проверьте, что выделение, имена, диапазоны и метаданные соответствуют нужным целевым объектам.
- Откройте аннотацию с помощью кнопки на вашем сайте. Проверьте выбранный элемент или диапазон текста, начальный комментарий и режим редактора.
- Измените пользовательский элемент управления, сравните с оригиналом и очистите предпросмотр. Проверьте, что ваше приложение восстанавливает исходное состояние.
- Для содержимого canvas проверьте пустое пространство, изменение размера и изменения сцены. Переключайтесь между объектами и убедитесь, что события элементов управления по-прежнему обновляют зафиксированный целевой объект. Отмените асинхронную проверку попадания и убедитесь, что последующие выборы работают.
- Сохраните, повторно откройте и отредактируйте аннотацию из предпросмотра вложения в поле ввода сообщения. Проверьте, что она сохраняет свои элементы управления и целевой объект и что её удаление очищает любой предпросмотр.
- Отправьте аннотацию вместе с сообщением. Убедитесь, что ChatGPT получает
выбранное содержимое, метаданные и запрошенные значения, включая видимые подписи
вариантов выбора и неизменённые значения элементов управления, в которых отсутствует
currentValue. - Откройте сайт в браузере без API и убедитесь, что обычные действия по-прежнему работают.
Чтобы предоставить ChatGPT доступ к действиям на вашем сайте, добавьте инструменты сайта (WebMCP). Аннотации переносят выбранное пользователем содержимое и его отзыв в разговор; инструменты сайта позволяют агенту действовать с учётом этого контекста, используя существующие возможности вашего приложения.