アノテーションの拡張

Browser Annotation API を使用すると、ウェブサイト上でユーザーが選択する対象、 フィードバックに添えるコンテキスト、アノテーションを ChatGPT に送信する前に変更をプレビューするためのコントロールをカスタマイズできます。

ブラウザーアノテーションは、コードを変更しなくてもサイトで機能します。 ユーザーはページの一部を選択してコメントを追加し、コンテキストとして Codex または ChatGPT Work に送信できます。

開発者は Browser Annotation API を使用して、アプリケーション固有のコンテキストやコントロールを提供できます。 たとえば、デザインシステムのプレビューにコンポーネントのバリエーションのプレビューを添付し、開発者がウェブサイト上のコンポーネントの更新方法を把握できるようにします。

Browser Annotation API の理解やウェブサイトへのアノテーション対応の追加について支援が必要な場合は、Annotations Extensibility プラグインをインストールしてください。

Annotations Extensibility プラグインをインストール

試してみる

このガイドで Browser Annotation API の動作を確認できます。

  1. ChatGPT の組み込みブラウザーでこのページを開きます。
  2. このカードに表示されるプロンプト候補を開いてみてください。
  3. アノテーションモードに入り、以下の表またはコードサンプルを選択して、事前定義されたレイアウトやテーマを切り替えます。
  4. ページ上のどの項目にも引き続きアノテーションを付けられ、デフォルトのアノテーションの動作を確認できます。

注釈を試すには ChatGPT の内蔵ブラウザーでこのページを開いてください。

ChatGPT のブラウザーで開く

カスタマイズする対象を選ぶ

ウェブサイトに合った連携方法から始めてください。

目的 連携方法
カードやその他の要素グループを 1 つのオブジェクトとして選択可能にする 選択対象
語句や文を選択できるようにする テキスト選択コンテナー
選択内容に追加のコンテキストを含める 選択メタデータ
独自のボタンからコメント候補付きのアノテーションを開く アノテーションリクエスト
独自の UI から特定の文章へのフィードバックを求める テキスト範囲リクエスト
アノテーションを開いたときに詳細コントロールを表示する エディターのデフォルト設定
アプリケーションのプロパティをプレビューする、または選択内容を収集する カスタムコントロール
キャンバス内に描画された個々のオブジェクトを選択する アノテーションサーフェス
サイトからアノテーションモードをオンまたはオフにする アノテーションモードの制御

このガイドは、ChatGPT デスクトップアプリの DevDay 2026 リリース以降を対象としています。 JavaScript API は、アプリの組み込みブラウザー内の document.oai.annotation を通じて、 HTTPS や localhost などの安全なトップレベルページで利用できます。 有効な場合、ブラウザーはページのスクリプトが実行される前に API を設定します。古いブラウザーや非対応のブラウザーにも対応できるように、 各メソッドの有無を検出し、DOM 要素が存在するようになった時点で連携を初期化してください。準備完了イベントやポーリングは不要です。

API メソッドは同期的に戻り、登録ハンドルはすぐに使用できます。その後、ブラウザーがアノテーションエディターの読み込みを完了する場合があります。 サーフェスの hitTest コールバックは Promise を返すことができます。

選択対象をカスタマイズする

デフォルトでは、アノテーションモードはページの DOM から要素を選択し、 テキスト、画像、コントロールなどの対象を優先します。より大きなオブジェクトを選択可能にするには、 それを含む領域に oai-annotation-container を付け、選択可能な子孫要素に oai-annotatable を付けます。

次の例では、グラフカードを 1 つのオブジェクトとして選択可能にします。

<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 を入れ子にすると要素選択に戻り、テキストコンテナーを入れ子にするとテキスト選択に戻ります。1 つの要素に両方の属性がある場合は、テキスト選択が優先されます。

テキスト選択はページの通常の選択ルールに従い、開始したコンテナーの外まで広げられます。コンテナーは範囲を切り詰めたり、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 オブジェクトを使用してください。

  • プロパティは最大 6 個で、値には文字列、有限数、ブール値、または null を使用できます。
  • キーは最大 64 文字、文字列の値は最大 256 文字です。
  • シリアライズされたオブジェクトは最大 2,048 バイトです。

キーは ASCII 英字で始め、ASCII 英字、数字、 スペース、アンダースコア、ハイフンのみを含める必要があります。単語間にはスペースを 1 つ使用してください。入れ子のオブジェクトと配列には対応していません。ブラウザーは無効なメタデータを無視します。

request() またはサーフェスの hitTest の結果を通じてメタデータを提供することもできます。

ウェブサイトからアノテーションを開く

ボタンの押下など、ユーザーの操作から直接 document.oai.annotation.request(target, options) を呼び出します。対象には、現在のドキュメントの表示領域内にあり、 ドキュメントに接続されている HTML 要素、または DOM Range を指定できます。アノテーション用の属性は不要です。仮想オブジェクト ID には対応していません。

サイトから開始するリクエストには、ユーザーの許可が必要な場合があります。ユーザーは サイトツール > アノテーション機能 で、 ブロックされたアノテーション機能を再度有効にできます。

選択の例にあるグラフカードの横にこのボタンを追加し、両方の要素が存在するようになってからスクリプトを実行します。

<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 にメッセージは送信されず、 送信できるのはユーザーだけです。

呼び出しは、ユーザー操作が有効な間に行ってください。先にネットワークリクエストの完了を待つと、 その操作の有効性が失われる場合があります。

省略可能な第 2 引数は、次のフィールドに対応しています。

オプション 動作
mode "advanced" を指定すると要素の詳細コントロールが開き、"default" を指定するとデフォルトのエディター動作を使用します。デフォルトは "default" です。"default" でもカスタムコントロールが開く場合があります。エディターのデフォルト設定を参照してください。テキスト範囲は "default" のみに対応しています。
enterAnnotationMode true に設定すると、アノテーションモードに入り、アノテーションのキャンセルまたは送信後もそのモードを維持します。
metadata 有効で空でないメタデータは、このリクエストにおける対象の HTML メタデータを置き換えます。このオプションは、テキスト範囲の場合、および対象要素が shadow DOM 内にある場合は無視されます。
initialComment 編集可能なコメントを指定します。上限は 240 UTF-16 コード単位です。

request() は、ブール値の accepted を含むオブジェクトを返します。 ブラウザーがリクエストを受信して検証したかどうかは、結果オブジェクトではなく result.accepted を確認してください。これは、エディターが開いたことや、 ユーザーがアノテーションを保存または送信したことを保証するものではありません。

エディターの読み込み中、ブラウザーは受理したリクエストを 1 件保持できます。 リクエストが保留中、エディターが開いている、または 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" を指定したリクエストは拒否され、リクエストのメタデータは無視されます。受理されたリクエストがエディターを待機している間は、対象のテキストを利用可能な状態に保ってください。ブラウザーは、 エディターを開く前に範囲を再確認します。テキスト選択コンテナーは、これとは別に、 アノテーションモード中にユーザーがドラッグしてテキストを選択できるようにします。

エディターのデフォルトモードを選ぶ

ブラウザーの選択 UI から開いたアノテーションで詳細コントロールをすぐに表示するには、ページの <head> に次のタグを追加します。

<meta name="oai-annotation-editor-default-mode" content="advanced" />

独自の UI から開くアノテーションでは、{ mode: "advanced" } を request() に渡します。デフォルトのエディター動作を使用するには、mode: "default" を指定するか、省略します。 ページのメタ設定は、このリクエストオプションを上書きしません。デフォルトモードでも、 コメント専用のエディターになるとは限りません。開始値の候補を持つカスタムコントロールや、 currentValue を省略したコントロールは、コントロールエディターを開く場合があります。

手動の 調整、折りたたみ、Option キーを押しながらクリック の操作は、 Codex または localhost でのみ利用できます。ChatGPT 内のホストされたサイトでは、登録されたカスタムコントロールが 調整 ボタンや折りたたみボタンなしで自動的に表示されます。ページから要求した詳細モードは、そこでも機能します。

カスタムコントロールを追加する

registerControls() を使用して、アノテーションコントロールを 1 つ以上の DOM 要素に関連付けます。コントロールは、余白トークンなどのアプリケーションのプロパティをプレビューしたり、 メールのトーンなど、リクエストに含める選択内容を収集したりできます。

共通の余白トークンをプレビューする

この例の 2 つのカードは、同じ 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 内の内部 ID は、 選択肢のテキストとしては送信されません。

コントロールと開始値を設定する

controlsMode: "replace" を使用すると、登録した対象に独自のコントロールのみを表示し、"extend" を使用すると、組み込みコントロールと並べて表示します。省略可能な controlsHeading はパネルの名前を指定します。ブラウザーは前後の空白を除去し、1~80 文字を受け付けます。見出しがない場合、パネルには要素の HTML タグが表示されます。 見出しは、ChatGPT に送信されるコンテキストには含まれません。

1 つの登録は最大 12 個のコントロールに対応します。各コントロールには type、表示用の label、callback 識別子が必要です。

型 値 追加フィールド
color "#2563eb" などの 16 進数の色 なし
range 数値 min、max、step
select 文字列 options:{ label, value } オブジェクトの配列
toggle ブール値 なし

callback は文字列の識別子であり、JavaScript 関数ではありません。 登録内で一意にし、ASCII 英字で始め、ASCII 英字、 数字、アンダースコア、ハイフンを使用してください。省略可能な reference は、変更するプロパティを識別し、アノテーション内でラベルと値に付随します。

currentValue には、未保存の編集も含めた、そのプロパティの通常の有効な値を設定します。プレビュー状態や未完了の入力を基準値として使用しないでください。ブラウザーは変更前後の比較とリセットにこの値を使用します。アプリケーションの状態が変わったら、 registration.update({ controls }) を使用して、コントロールの currentValue の値を更新します。更新は以降のアノテーションに影響し、既存のアノテーションは取得済みの値を保持します。

開始値の候補には defaultValue を設定します。コントロールの初期状態では、 currentValue よりも優先されます。両方を省略すると、色は白、範囲は最小値、 選択は最初の選択肢、トグルは false から始まります。

提案値は通常の下書きと分けて管理してください。更新で例外が発生した場合、またはリクエストが失敗するか accepted: false を返した場合は、試みた提案を破棄し、 以前のコントロールと提案の状態に戻します。リクエストが受理された場合は提案を保持してください。 ブラウザーは、コントロールを取得する前にリクエストをキューに入れる場合があります。

コントロールと登録を検証する

ブラウザーは、次の制限に照らして登録と更新を検証します。

フィールドまたはリソース 制約
コントロール 1 つの登録につき最大 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、0.001~10,000 で max - min 以下の step、範囲内の開始値が必要です。開始値は step に合わせる必要はありません。
色とトグル 色は、# の後に 3、4、6、または 8 桁の 16 進数を指定する必要があります。トグルの値は true または false である必要があります。
対象 登録時の対象エントリーは 1~128 個で、すべて現在のドキュメント内の要素である必要があります。更新時に targets: [] を渡すと切り離せます。各ドキュメントは、最大 64 件のコントロール登録と、1,024 件の登録と対象の関連付けに対応します。
シリアライズされたコントロール controls、controlsHeading、controlsMode を含む JSON ペイロードは、16,384 UTF-16 コード単位以内に収める必要があります。関数、シンボル、大きな整数を含めず、JSON としてエンコードできるデータを使用してください。

registerControls() と registration.update() は、定義や対象が無効な場合、 または制限を超えた場合に同期的に例外をスローすることがあります。dispose() 後の更新でも例外が発生します。更新が拒否された場合、コントロールと対象を含めて以前の登録が保持されます。呼び出し元で失敗を処理し、通常の編集を引き続き利用できるようにしてください。また、管理している登録を再利用または破棄して、 制限内に収めてください。

プレビューとリセットを処理する

oaiannotationcontrolchange イベントは、選択された要素からバブリングします。 その detail には、callback、value、action が含まれます。

アクション 渡された値の適用目的
preview 要求された変更を表示します。
preview-original 比較のために元の状態を一時的に表示します。
reset プレビューがクリアされたときに元の状態に戻します。

余白の例と同様に、すべてのアクションで渡された値を適用してください。 ハンドラーは元に戻せるようにし、繰り返し呼び出しても安全に動作するようにします。プレビューイベントは永続的な変更を要求するものではありません。保存はアプリケーションの通常の保存フローで行ってください。 無関係な編集がプレビューを上書きしないよう、通常の下書き、未完了の入力、プレビューを分けて管理します。同じ設定が編集された場合は、そのプレビューを置き換え、 以降のアノテーション用に基準値を更新してください。

更新とクリーンアップのために、登録ハンドルを保持してください。update() は、 targets、controls、controlsHeading、controlsMode の任意の組み合わせを受け付けます。 省略したフィールドは以前の値を保持します。たとえば、コンポーネントの DOM 要素を置き換えるときは、 registration.update({ targets: newElement }) を使用します。対象のコレクションは既存の要素を取得し、以後のセレクターの一致は追跡しません。

targets: [] を渡すと登録を切り離し、controls: [] を渡すとコントロールをクリアします。連携を削除するときは、dispose() を呼び出し、イベントリスナーを削除してください。クリーンアップでは、通常の描画も復元する必要があります。dispose() はコントロールの登録を削除するだけで、プレビューによる変更を元に戻しません。余白の例では、 インラインの上書きを削除して元の CSS 値を復元します。どちらのメソッドも、 値を返さずに同期的に終了します。更新は以降の選択に影響し、保存済みのアノテーションは取得済みのコントロールと対象を保持します。

コントロールイベントは、アノテーションが取得した要素を対象とします。登録の対象を更新しても、既存のアノテーションの対象は変更されません。リセットイベントは削除済みの要素でも発生する場合があるため、元の親要素上のリスナーでは受信できません。

キャンバス内のオブジェクトを選択可能にする

アノテーションサーフェスを使用すると、アプリケーションはキャンバス内に描画された個々のオブジェクトを識別できます。registerSurface() でホスト要素を登録し、 安定した id を持つオブジェクト、または空の領域に対して null を返す hitTest 関数を指定します。

ホストは、安全なトップレベルドキュメント内に接続された HTML 要素で、 shadow DOM の外側にある必要があります。iframe 内でのサーフェス登録には対応していません。

次の例は、売上の棒を描画して選択可能にします。スクリプトはキャンバスの後に配置してください。

<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"
    );
  },
});

アノテーションモードで棒にポインターを合わせると、ハイライトされます。選択すると、 オブジェクト名、メタデータ、選択内容のスクリーンショットを含むアノテーションが開きます。

サーフェス内では ID を安定させてください。省略可能な name はユーザーに表示され、 role は意味を簡潔に説明します。省略可能な rect は、ページの表示領域を基準とする CSS ピクセルを使用し、 clientX および clientY と対応します。スケール、パン、ズームを含めて、 シーン座標から変換してください。

hitTest は Promise を返すことができ、不要になった処理をキャンセルするために、signal として AbortSignal を受け取ります。ブラウザーは 250 ミリ秒待ってから DOM 選択にフォールバックします。エラーや無効な結果の場合もフォールバックします。ヒットテストが成功し、オブジェクトがなかった場合は、 null を明示的に返してください。

キャンセルされた処理でも、その Promise を解決または拒否する必要があります。ブラウザーは hitTest コールバックを同時に 1 つだけ許可し、キャンセルやタイムアウトの後でも、Promise が確定するまでその枠を保持します。ワーカーが選択処理を担当する場合は、 その処理が中止されたときに保留中の Promise を確定させてください。キャンセルされたワーカーの応答を破棄すると、その後のキャンバス選択がブロックされる場合があります。

アプリケーション固有のフィードバックには、省略可能な renderSelection コールバックを使用します。 両方の ID が null の場合は、フィードバックをクリアします。オブジェクトの移動やズームの変更後には surface?.invalidate() を呼び出し、 連携を削除するときは surface?.dispose() を呼び出してください。どちらも値を返さずに同期的に終了します。

キャンバス内のオブジェクトにコントロールを追加する

サーフェスの DOM 要素にカスタムコントロールを登録します。描画されたオブジェクトは、 API では仮想オブジェクトと呼ばれ、カスタムコントロールを使用します。組み込みの CSS コントロールとテキストコントロールは適用されません。

複数のオブジェクトがある場合は、renderSelection を使用して、 selectedId が変わったときに、ブラウザーがアノテーションを取得する前にホストのコントロールを更新します。コントロールイベントには detail.virtualTarget: { surfaceId, targetId } が含まれます。各イベントの振り分けには、 現在の選択ではなく、取得済みの識別情報とその callback を使用します。targetId は hitTest の id と一致し、surfaceId はブラウザーのサーフェス登録を識別します。通常の DOM イベントには virtualTarget が含まれません。

プレビュー、比較、リセットのイベントは、別のオブジェクトが選択された後も元のオブジェクトの識別情報を保持します。保存済みのアノテーションを開き直しても、 取得済みのオブジェクトやメタデータを置き換えるためにヒットテストが再実行されることはありません。

アノテーションモードを制御する

モードの変更を要求するには toggle()、確定した状態を読み取るには isActive() を使用し、 ドキュメントの oaiannotationmodechange イベントで UI を同期します。 古いブラウザーや非対応のブラウザーに対応するため、両方のメソッドの有無を検出してください。このボタンを追加し、 ボタンが存在するようになってからスクリプトを実行します。

<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 はブール値です。ブラウザーは初期イベントを送信せず、状態が変わらない強制指定に対して重複イベントも送信しないため、UI はゲッターから初期化してください。有効な間にアクセス権が取り消された場合、最後のイベントは active: false を報告し、保持しているゲッターは false を返します。

アノテーションモードはページのクリックを捕捉します。終了ボタンを含むページのコントロールを使うには、Space を押したまま操作するか、ブラウザーのアノテーション UI から終了してください。 Space を押しているだけでは、isActive() は true のままです。この API は、クリックを通過させる oai-annotation-ignore やその他の属性には対応していません。

終了するとエディターが閉じ、保存済みのアノテーションは送信されたり入力欄に移されたりせずに保持されます。コンポーネントを削除するときは、cleanupAnnotationButton() を呼び出してください。取得済みの API 参照を使えば、名前空間がなくなっていても、クリーンアップでリスナーを削除できます。

連携をテストする

デスクトップアプリの組み込みブラウザーでウェブサイトを開き、追加した機能をテストしてください。

  1. アノテーションモードに入り、オブジェクトとテキストを選択します。ハイライト、 名前、範囲、メタデータが意図した対象と一致することを確認します。
  2. サイトのボタンからアノテーションを開きます。選択された要素またはテキスト範囲、初期コメント、エディターモードを確認します。
  3. カスタムコントロールを変更し、元の状態と比較して、プレビューをクリアします。 アプリケーションが元の状態に戻ることを確認します。
  4. キャンバスのコンテンツでは、空の領域、サイズ変更、シーンの変更をテストします。オブジェクトを切り替え、コントロールイベントが引き続き取得済みの対象を更新することを確認します。 非同期ヒットテストをキャンセルし、その後も選択が機能することを確認します。
  5. 入力欄の添付ファイルプレビューから、アノテーションを保存し、開き直して編集します。 コントロールと対象が保持されていること、およびアノテーションを削除するとプレビューがすべてクリアされることを確認します。
  6. アノテーションをメッセージとともに送信します。ChatGPT が、選択されたコンテンツ、メタデータ、要求した値を受信することを確認します。表示される選択肢のラベルや、currentValue を省略したコントロールで変更されなかった選択内容も確認してください。
  7. API のないブラウザーでサイトを開き、通常の操作が引き続き機能することを確認します。

ChatGPT がウェブサイトで実行できるアクションを公開するには、 サイトツール(WebMCP)を追加します。アノテーションはユーザーの選択内容とフィードバックを会話に取り込みます。サイトツールは、アプリケーションの既存の機能を通じて、 エージェントがそのコンテキストに基づいて操作できるようにします。