Ekstensibilitas Anotasi

Browser Annotation API memungkinkan situs web Anda menyesuaikan apa yang dipilih pengguna, konteks yang menyertai masukan mereka, dan kontrol yang mereka gunakan untuk mempratinjau perubahan sebelum mengirim anotasi ke ChatGPT.

Anotasi browser dapat digunakan di situs Anda tanpa perubahan kode. Pengguna dapat memilih bagian halaman, menambahkan komentar, dan mengirimkannya dalam Konteks ke Codex atau ChatGPT Work.

Sebagai pengembang, Anda dapat menggunakan Browser Annotation API untuk menyediakan konteks atau kontrol khusus aplikasi Anda. Sebagai contoh, Anda dapat melampirkan pratinjau varian komponen dalam pratinjau sistem desain agar pengembang mengetahui cara memperbarui komponen di situs web.

Untuk mendapatkan bantuan dalam memahami Browser Annotation API atau menambahkan dukungan anotasi ke situs web Anda, instal plugin Annotations Extensibility.

Instal plugin Annotations Extensibility

Coba

Lihat cara kerja Browser Annotation API dalam panduan ini.

  1. Buka halaman ini di browser bawaan ChatGPT.
  2. Coba buka saran prompt yang akan muncul di kartu ini.
  3. Masuk ke mode Anotasi, lalu pilih tabel di bawah atau contoh kode untuk beralih di antara tata letak dan tema yang telah ditentukan.
  4. Anda tetap dapat memberi anotasi pada item apa pun di halaman dan melihat perilaku anotasi default.

Buka halaman ini di browser bawaan ChatGPT untuk mencoba anotasi.

Buka di browser ChatGPT

Pilih apa yang akan disesuaikan

Mulailah dengan integrasi yang sesuai dengan situs web Anda:

Tujuan Integrasi
Membuat kartu atau kelompok elemen lain dapat dipilih sebagai satu objek Target pilihan
Memungkinkan pengguna memilih frasa atau kalimat Kontainer pemilihan teks
Menyertakan konteks tambahan pada pilihan Metadata pilihan
Membuka anotasi dari tombol Anda sendiri dengan saran komentar Permintaan anotasi
Meminta masukan tentang bagian teks tertentu dari UI Anda sendiri Permintaan rentang teks
Menampilkan kontrol lanjutan saat anotasi dibuka Pengaturan default editor
Mempratinjau properti aplikasi atau mengumpulkan pilihan Kontrol kustom
Memilih objek individual yang digambar di dalam kanvas Permukaan anotasi
Mengaktifkan atau menonaktifkan mode Anotasi dari situs Anda Kontrol mode Anotasi

Panduan ini ditujukan untuk aplikasi desktop ChatGPT rilis DevDay 2026 dan yang lebih baru. API JavaScript tersedia melalui document.oai.annotation di browser bawaan aplikasi, pada halaman tingkat teratas yang aman seperti HTTPS atau localhost. Saat diaktifkan, browser memasangnya sebelum skrip halaman Anda dijalankan. Deteksi ketersediaan setiap metode untuk mendukung browser lama atau yang tidak didukung, lalu inisialisasi integrasi Anda setelah elemen DOM-nya tersedia. Tidak diperlukan peristiwa kesiapan atau polling.

Metode API mengembalikan hasil secara sinkron, dan handle pendaftaran siap digunakan segera. Browser dapat menyelesaikan pemuatan editor anotasi setelahnya. Callback hitTest suatu permukaan dapat mengembalikan promise.

Sesuaikan target pilihan

Secara default, mode Anotasi memilih elemen dari DOM halaman, dengan mengutamakan target seperti teks, gambar, dan kontrol. Agar objek yang lebih besar dapat dipilih, tandai area yang memuatnya dengan oai-annotation-container dan elemen turunannya yang dapat dipilih dengan oai-annotatable.

Contoh ini membuat kartu grafik dapat dipilih sebagai satu objek:

<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>

Mengarahkan penunjuk ke mana pun di dalam kartu akan menyorot seluruh kartu. Nilai opsional oai-annotatable memberi objek nama yang ditampilkan kepada pengguna dan model. Pilih nama yang membedakan objek-objek yang berdekatan, atau hilangkan nilainya.

Kontainer menentukan tempat aturan pemilihan ini berlaku. Atribut oai-annotatable saja tidak mengubah perilaku pemilihan. Di dalam kontainer, browser memilih target bertanda terdekat yang memuat elemen di bawah penunjuk. Kontainer bertingkat menggunakan kontainer terdekat. Area tanpa tanda di dalam kontainer menghasilkan anotasi halaman web; area di luar semua kontainer mempertahankan perilaku default.

Aktifkan pemilihan teks

Tambahkan oai-annotation-container-text ke suatu area agar pengguna dapat menyeret untuk memilih teks selama mode Anotasi. Atribut ini tidak memerlukan nilai:

<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>

Melepaskan pilihan yang tidak kosong akan membuka editor anotasi teks dengan rentang yang dipilih beserta konteksnya. Mengklik tanpa memilih teks tidak membuat anotasi. Escape atau gestur yang dibatalkan akan membatalkan pilihan.

Kontainer pemilihan teks atau DOM terdekat menentukan bagaimana gestur dimulai. Kontainer teks tidak menggunakan penanda oai-annotatable untuk memilih elemen. Sarangkan oai-annotation-container untuk memulihkan pemilihan elemen, atau kontainer teks untuk memulihkan pemilihan teks. Jika kedua atribut ada pada satu elemen, pemilihan teks diutamakan.

Pemilihan teks mengikuti aturan pemilihan normal halaman dan dapat meluas melampaui kontainer awal. Kontainer tidak membatasi rentang atau mengaktifkan pemilihan di dalam iframe. Kolom teks tetap dapat dipilih, tetapi klik halaman biasa dan tindakan bawaan pada kontrol tetap diblokir selama mode Anotasi. Pemanggilan request() secara eksplisit mempertahankan perilaku yang ada.

Tambahkan konteks ke pilihan

Tambahkan oai-annotation-metadata ke target bertanda untuk menyertakan konteks yang mungkin tidak terlihat di halaman. Misalnya, baris kontak fiktif dapat menyertakan alamat email untuk permintaan penyusunan draf email:

<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>

Memilih salah satu baris teks akan memilih seluruh baris kontak. Metadata muncul bersama anotasi dan menyertainya dalam percakapan. Sertakan hanya konteks yang memang ingin Anda bagikan kepada pengguna dan model. Mengirim email tetap memerlukan alat email yang terhubung.

Gunakan objek JSON kecil dan datar dengan batasan berikut:

  • Hingga enam properti, dengan nilai string, angka berhingga, boolean, atau null.
  • Kunci hingga 64 karakter dan nilai string hingga 256 karakter.
  • Hingga 2.048 byte untuk objek yang diserialisasi.

Kunci harus dimulai dengan huruf ASCII dan hanya berisi huruf ASCII, angka, spasi, garis bawah, atau tanda hubung. Gunakan satu spasi di antara kata. Objek bertingkat dan array tidak didukung. Browser mengabaikan metadata yang tidak valid.

Anda juga dapat menyediakan metadata melalui request() atau hasil hitTest suatu permukaan.

Buka anotasi dari situs web Anda

Panggil document.oai.annotation.request(target, options) langsung dari interaksi pengguna, seperti menekan tombol. Target dapat berupa elemen HTML yang terhubung di dalam area dokumen saat ini yang terlihat atau Range DOM. Target tidak memerlukan atribut anotasi. ID objek virtual tidak didukung.

Permintaan yang dimulai situs mungkin memerlukan izin pengguna; pengguna dapat mengaktifkan kembali fitur anotasi yang diblokir melalui Alat situs > Fitur anotasi.

Tambahkan tombol ini di samping kartu grafik dari contoh pemilihan, lalu jalankan skrip setelah kedua elemen tersedia:

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

Memilih Jelaskan lebih lanjut meminta browser membuka anotasi dengan kartu terpilih dan komentar yang dapat diedit. Pengguna dapat mengedit, menyimpan, dan mengirimkannya bersama pesan mereka. Membuka anotasi tidak mengirim pesan ke ChatGPT; hanya pengguna yang dapat mengirimkannya.

Pastikan pemanggilan tetap berada dalam interaksi pengguna yang aktif. Menunggu permintaan jaringan terlebih dahulu dapat menyebabkan konteks interaksi tersebut hilang.

Argumen kedua yang opsional mendukung kolom berikut:

Opsi Perilaku
mode Gunakan "advanced" untuk membuka kontrol lanjutan bagi suatu elemen, atau "default" untuk menggunakan perilaku editor default. Nilai defaultnya adalah "default". Kontrol kustom tetap dapat dibuka dengan "default"; lihat Pengaturan default editor. Rentang teks hanya mendukung "default".
enterAnnotationMode Tetapkan ke true untuk masuk ke mode Anotasi dan tetap berada di dalamnya setelah membatalkan atau mengirim anotasi.
metadata Metadata yang valid dan tidak kosong menggantikan metadata HTML target untuk permintaan ini. Opsi ini diabaikan untuk rentang teks dan ketika elemen target berada di dalam shadow DOM.
initialComment Menyediakan komentar yang dapat diedit, hingga 240 unit kode UTF-16.

request() mengembalikan objek dengan boolean accepted. Periksa result.accepted, bukan objek hasilnya, untuk mengetahui apakah browser menerima dan memvalidasi permintaan. Hasil ini tidak mengonfirmasi bahwa editor telah terbuka atau bahwa pengguna telah menyimpan atau mengirim anotasi.

Saat editor dimuat, browser dapat menampung satu permintaan yang diterima. Browser dapat menolak permintaan lain saat ada permintaan yang menunggu, editor terbuka, atau ChatGPT sedang mengendalikan browser.

Minta anotasi untuk rentang teks

Teruskan Range DOM untuk meminta masukan tentang suatu bagian teks tanpa mengubah pilihan teks browser. Contoh ini memilih isi paragraf; contoh ini tidak memerlukan atribut 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>

Jalankan skrip ini setelah kedua elemen tersedia:

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

Rentang harus memuat teks terlihat yang tidak kosong dalam dokumen saat ini, dengan setidaknya sebagian pilihan berada di area halaman yang terlihat. Rentang dapat memuat paling banyak 20.000 unit kode UTF-16. Rentang yang diciutkan, teks yang hanya berisi spasi, teks terpilih yang tersembunyi, dan rentang dalam shadow root tertutup tidak didukung.

Permintaan rentang teks hanya menggunakan editor teks default. Permintaan dengan mode: "advanced" ditolak, dan metadata permintaan diabaikan. Pastikan teks target tetap tersedia saat permintaan yang diterima menunggu editor: browser memeriksa rentang lagi sebelum membukanya. Secara terpisah, kontainer pemilihan teks memungkinkan pengguna menyeret untuk memilih teks selama mode Anotasi.

Pilih mode default editor

Untuk langsung menampilkan kontrol lanjutan bagi anotasi yang dibuka melalui UI pemilihan browser, tambahkan tag ini ke <head> halaman Anda:

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

Untuk anotasi yang dibuka dari UI Anda sendiri, teruskan { mode: "advanced" } ke request(). Gunakan mode: "default", atau hilangkan, untuk perilaku editor default. Pengaturan meta halaman tidak menimpa opsi permintaan ini. Mode default tidak menjamin editor yang hanya berisi komentar: kontrol kustom dengan nilai awal yang diusulkan, atau kontrol yang menghilangkan currentValue, dapat membuka editor kontrol.

Kontrol manual Sesuaikan, ciutkan, dan Option-klik hanya tersedia di Codex atau di localhost. Pada situs yang dihosting di ChatGPT, kontrol kustom terdaftar muncul secara otomatis tanpa tombol Sesuaikan atau ciutkan. Mode lanjutan yang diminta halaman tetap berfungsi di sana.

Tambahkan kontrol kustom

Gunakan registerControls() untuk mengaitkan kontrol anotasi dengan satu atau beberapa elemen DOM. Kontrol dapat mempratinjau properti aplikasi, seperti token jarak, atau mengumpulkan pilihan untuk disertakan dalam permintaan, seperti nada bahasa email.

Pratinjau token jarak bersama

Kedua kartu dalam contoh ini menggunakan properti CSS yang sama:

<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>

Jalankan skrip ini setelah membuat pratinjau:

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

Beri anotasi pada salah satu kartu dan ubah Padding kartu (piksel) dari 16 menjadi 24. Pada situs yang dihosting di ChatGPT, kontrol muncul secara otomatis; di Codex atau di localhost, pilih Sesuaikan jika diperlukan. Kedua kartu diperbarui. Anotasi mencatat label, referensi, serta nilai lama dan baru. Menghapus pratinjau memulihkan padding asli. Panggil disposeAnnotationControls() saat menghapus komponen.

Kumpulkan pilihan tanpa pratinjau

Gunakan baris kontak dari contoh metadata untuk menawarkan nada bahasa email:

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

Kontrol ini tidak memerlukan penangan peristiwa karena tidak mempratinjau perubahan halaman. Menghilangkan currentValue memberi tahu browser untuk menyertakan nada bahasa yang dipilih meskipun pengguna mempertahankan opsi awal. Panggil registration?.dispose() saat menghapus baris.

Untuk kontrol pilihan, callback pratinjau menerima option.value, seperti "professional". Riwayat anotasi dan ChatGPT menerima option.label yang terlihat, seperti "Professional", untuk opsi sebelumnya maupun opsi yang dipilih. Gunakan label yang menjelaskan setiap pilihan; ID internal dalam value tidak dikirim sebagai teks pilihan.

Konfigurasikan kontrol dan nilai awal

Gunakan controlsMode: "replace" untuk hanya menampilkan kontrol Anda bagi target terdaftar, atau "extend" untuk menampilkannya bersama kontrol bawaan. Nilai opsional controlsHeading memberi nama panel. Browser memangkas spasi di tepinya dan menerima satu hingga 80 karakter. Tanpa judul, panel menampilkan tag HTML elemen. Judul tidak disertakan dalam konteks yang dikirim ke ChatGPT.

Satu pendaftaran mendukung hingga 12 kontrol. Masing-masing memerlukan type, label yang terlihat, dan pengenal callback:

Jenis Nilai Kolom tambahan
color Warna heksadesimal, seperti "#2563eb" Tidak ada
range Angka min, max, dan step
select String options, array objek { label, value }
toggle boolean Tidak ada

callback adalah pengenal string, bukan fungsi JavaScript. Pastikan nilainya unik dalam pendaftaran, dimulai dengan huruf ASCII, dan menggunakan huruf ASCII, angka, garis bawah, atau tanda hubung. Nilai opsional reference mengidentifikasi properti yang diubah dan menyertai label serta nilai dalam anotasi.

Tetapkan currentValue ke nilai normal properti yang valid, termasuk pengeditan yang belum disimpan. Jangan gunakan status pratinjau atau masukan yang belum selesai sebagai nilai dasar. Browser menggunakannya untuk perubahan sebelum dan sesudah serta pengaturan ulang. Saat status aplikasi berubah, gunakan registration.update({ controls }) untuk memperbarui nilai currentValue kontrol. Pembaruan memengaruhi anotasi berikutnya; anotasi yang sudah ada mempertahankan nilai yang telah direkam.

Tetapkan defaultValue untuk nilai awal yang disarankan; nilai ini lebih diutamakan daripada currentValue untuk status awal kontrol. Jika keduanya dihilangkan, kontrol dimulai dengan putih untuk warna, nilai minimum untuk rentang, opsi pertama untuk pilihan, atau false untuk sakelar.

Pisahkan nilai yang diusulkan dari draf biasa. Jika pembaruan melempar kesalahan atau permintaan gagal atau mengembalikan accepted: false, buang usulan yang dicoba dan pulihkan kontrol serta status usulan sebelumnya. Pertahankan usulan saat permintaan diterima: browser mungkin memasukkannya ke antrean sebelum merekam kontrol.

Validasi kontrol dan pendaftaran

Browser memvalidasi pendaftaran dan pembaruan berdasarkan batasan berikut:

Kolom atau sumber daya Batasan
Kontrol Hingga 12 per pendaftaran, dengan pengenal callback yang unik. Gunakan hanya kolom yang ditentukan untuk jenis kontrol tersebut.
Label dan judul Label kontrol, label opsi, dan controlsHeading tidak boleh kosong setelah spasi di tepinya dipangkas dan panjangnya paling banyak 80 unit kode UTF-16. Teks kontrol tidak boleh berisi karakter kontrol atau karakter pengubah arah.
Pengenal callback panjangnya paling banyak 80 unit kode UTF-16 setelah spasi di tepinya dipangkas, menggunakan format yang dijelaskan di atas. reference panjangnya 1–80 karakter dan mengizinkan huruf ASCII, angka, serta _ . / : @ $ # -, tanpa spasi.
Opsi pilihan 1–12 opsi dengan string value yang berbeda dan panjangnya paling banyak 512 unit kode UTF-16. currentValue dan defaultValue yang diberikan harus cocok dengan nilai salah satu opsi.
Nilai rentang min, max, currentValue, dan defaultValue harus berupa angka berhingga antara −10.000 dan 10.000. Memerlukan min < max, step dari 0,001 hingga 10.000 yang tidak lebih besar dari max - min, serta nilai awal dalam rentang. Nilai awal tidak harus selaras dengan step.
Warna dan sakelar Warna harus menggunakan 3, 4, 6, atau 8 digit heksadesimal setelah #. Nilai sakelar harus berupa true atau false.
Target 1–128 entri target saat mendaftar, semuanya merupakan elemen dalam dokumen saat ini. Pembaruan dapat meneruskan targets: [] untuk melepaskan kaitan. Setiap dokumen mendukung hingga 64 pendaftaran kontrol dan 1.024 kaitan pendaftaran ke target.
Kontrol yang diserialisasi Payload JSON yang berisi controls, controlsHeading, dan controlsMode harus muat dalam 16.384 unit kode UTF-16. Gunakan data yang dapat dikodekan sebagai JSON, tanpa fungsi, simbol, atau bilangan bulat besar.

registerControls() dan registration.update() dapat melempar kesalahan secara sinkron untuk definisi atau target yang tidak valid, atau batas yang terlampaui. Pembaruan setelah dispose() juga melempar kesalahan. Pembaruan yang ditolak mempertahankan pendaftaran sebelumnya, termasuk kontrol dan targetnya. Tangani kegagalan di lokasi pemanggilan, pastikan pengeditan biasa tetap dapat digunakan, dan gunakan kembali atau hapus pendaftaran yang dimiliki agar tetap berada dalam batasan.

Tangani pratinjau dan pengaturan ulang

Peristiwa oaiannotationcontrolchange merambat ke atas dari elemen yang dipilih. detail miliknya berisi callback, value, dan action:

Tindakan Terapkan nilai yang diberikan untuk
preview Menampilkan perubahan yang diminta.
preview-original Menampilkan sementara status asli untuk perbandingan.
reset Memulihkan status asli saat pratinjau dihapus.

Terapkan nilai yang diberikan untuk setiap tindakan, seperti pada contoh jarak. Buat penangan yang dapat dibalik dan aman untuk dipanggil berulang kali. Peristiwa pratinjau tidak meminta perubahan permanen; simpan melalui alur penyimpanan normal aplikasi Anda. Pisahkan draf biasa, masukan yang belum selesai, dan pratinjau agar pengeditan yang tidak terkait tidak menimpa pratinjau. Saat seseorang mengedit pengaturan yang sama, ganti pratinjaunya dan perbarui nilai dasar untuk anotasi berikutnya.

Simpan handle pendaftaran untuk pembaruan dan pembersihan. update() menerima kombinasi apa pun dari targets, controls, controlsHeading, dan controlsMode. Kolom yang dihilangkan mempertahankan nilai sebelumnya. Misalnya, gunakan registration.update({ targets: newElement }) saat mengganti elemen DOM suatu komponen. Kumpulan target merekam elemen yang sudah ada dan tidak melacak kecocokan selektor di masa mendatang.

Teruskan targets: [] untuk melepaskan kaitan pendaftaran atau controls: [] untuk mengosongkan kontrolnya. Panggil dispose() dan hapus pendengar peristiwa saat menghapus integrasi. Pembersihan juga harus memulihkan perenderan normal: dispose() hanya menghapus pendaftaran kontrol dan tidak membatalkan perubahan pratinjau Anda. Contoh jarak menghapus penimpaan inline-nya untuk memulihkan nilai CSS asli. Kedua metode kembali secara sinkron tanpa nilai. Pembaruan memengaruhi pilihan berikutnya; anotasi tersimpan mempertahankan kontrol dan target yang telah direkam.

Peristiwa kontrol menargetkan elemen yang direkam oleh anotasi. Memperbarui target pendaftaran tidak mengalihkan anotasi yang sudah ada. Peristiwa pengaturan ulang masih dapat dipicu pada elemen yang telah dihapus, sehingga pendengar pada induknya yang lama tidak akan menerimanya.

Buat objek kanvas dapat dipilih

Permukaan anotasi memungkinkan aplikasi Anda mengidentifikasi objek individual yang digambar di dalam kanvas. Daftarkan elemen host dengan registerSurface() dan sediakan fungsi hitTest yang mengembalikan objek dengan id yang stabil, atau null untuk ruang kosong.

Host harus berupa elemen HTML yang terhubung di luar shadow DOM dalam dokumen tingkat teratas yang aman. Pendaftaran permukaan tidak didukung di dalam iframe.

Contoh ini menggambar batang pendapatan dan membuatnya dapat dipilih. Letakkan skrip setelah kanvas:

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

Mengarahkan penunjuk ke batang dalam mode Anotasi akan menyorotnya. Memilihnya akan membuka anotasi dengan nama objek, metadata, dan tangkapan layar pilihan.

Pastikan ID tetap stabil dalam suatu permukaan. Nilai opsional name terlihat oleh pengguna; role memberikan deskripsi semantik singkat. Nilai opsional rect menggunakan piksel CSS relatif terhadap area halaman yang terlihat, sesuai dengan clientX dan clientY. Konversikan dari koordinat adegan, termasuk skala, pergeseran, dan zoom.

hitTest dapat mengembalikan promise dan menerima AbortSignal sebagai signal untuk membatalkan pekerjaan yang telah digantikan. Browser memberikan waktu 250 milidetik sebelum kembali menggunakan pemilihan DOM. Kesalahan dan hasil yang tidak valid juga memicu mekanisme pengganti ini. Kembalikan null secara eksplisit untuk uji deteksi objek yang berhasil tanpa menemukan objek.

Pekerjaan yang dibatalkan tetap harus menyelesaikan atau menolak promise-nya. Browser hanya mengizinkan satu callback hitTest yang sedang berjalan dan mempertahankan slot tersebut hingga promise selesai, bahkan setelah pembatalan atau waktu habis. Jika worker menangani pemilihan, selesaikan promise yang menunggu saat pekerjaannya dibatalkan; membuang respons worker yang dibatalkan dapat memblokir pemilihan kanvas berikutnya.

Gunakan callback opsional renderSelection untuk umpan balik khusus aplikasi. Hapus umpan balik ketika kedua ID bernilai null. Panggil surface?.invalidate() setelah memindahkan objek atau mengubah zoom, dan surface?.dispose() saat menghapus integrasi. Keduanya kembali secara sinkron tanpa nilai.

Tambahkan kontrol ke objek kanvas

Daftarkan kontrol kustom pada elemen DOM permukaan. Objek yang digambar, yang disebut objek virtual dalam API, menggunakan kontrol kustom Anda; kontrol CSS dan teks bawaan tidak berlaku untuk objek tersebut.

Untuk beberapa objek, gunakan renderSelection untuk memperbarui kontrol host saat selectedId berubah, sebelum browser merekam anotasi. Peristiwa kontrol menyertakan detail.virtualTarget: { surfaceId, targetId }. Arahkan setiap peristiwa menggunakan identitas yang telah direkam tersebut beserta callback miliknya, bukan pilihan saat ini. targetId cocok dengan id dari hitTest; surfaceId mengidentifikasi pendaftaran permukaan browser. Peristiwa DOM biasa tidak menyertakan virtualTarget.

Peristiwa pratinjau, perbandingan, dan pengaturan ulang mempertahankan identitas objek asli setelah objek lain dipilih. Membuka kembali anotasi tersimpan tidak menjalankan ulang uji deteksi objek untuk mengganti objek atau metadata yang telah direkam.

Kendalikan mode Anotasi

Gunakan toggle() untuk meminta perubahan mode, isActive() untuk membaca status yang dikonfirmasi, dan peristiwa oaiannotationmodechange pada dokumen agar UI Anda tetap sinkron. Deteksi ketersediaan kedua metode untuk browser lama atau yang tidak didukung. Tambahkan tombol ini, lalu jalankan skrip setelah tombol tersedia:

<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() membalik mode. Teruskan true untuk memastikannya aktif atau false untuk memastikannya nonaktif. Permintaan berulang dengan boolean yang sama bersifat idempoten. Meneruskan true mempertahankan editor yang aktif atau aktivasi yang menunggu; false membatalkan aktivasi yang menunggu dan menggunakan alur keluar normal.

Panggil toggle() dari interaksi pengguna yang sedang berlangsung. Hasil sinkron { accepted } mengakui penerimaan permintaan, bukan mengonfirmasi perubahan mode. Pemeriksaan kelayakan browser masih dapat mencegah perubahan tersebut. Baca status dari isActive() dan peristiwa, seperti pada contoh.

isActive() tidak memerlukan gestur pengguna. Browser memperbaruinya sebelum mengirim oaiannotationmodechange, yang event.detail.active miliknya berupa boolean. Browser tidak mengirim peristiwa awal atau duplikat untuk operasi paksa yang tidak mengubah apa pun, jadi inisialisasi UI Anda dari getter. Jika akses dicabut saat aktif, peristiwa terakhir melaporkan active: false dan getter yang dipertahankan mengembalikan false.

Mode Anotasi menangkap klik halaman. Tahan Spasi untuk menggunakan kontrol halaman, termasuk tombol keluar Anda, atau keluar melalui UI anotasi browser. Menahan Spasi saja membuat isActive() tetap bernilai true. API tidak mendukung oai-annotation-ignore atau atribut lain yang memungkinkan klik diteruskan.

Keluar akan menutup editor dan mempertahankan anotasi tersimpan tanpa mengirimkannya atau memindahkannya ke kolom penulisan pesan. Panggil cleanupAnnotationButton() saat menghapus komponen. Referensi API yang telah direkam memungkinkan pembersihan menghapus pendengar meskipun namespace telah hilang.

Uji integrasi Anda

Buka situs web Anda di browser bawaan aplikasi desktop dan uji fitur yang Anda tambahkan:

  1. Masuk ke mode Anotasi dan pilih objek serta teks. Periksa apakah sorotan, nama, rentang, dan metadata sesuai dengan target yang dimaksud.
  2. Buka anotasi dari tombol situs Anda. Periksa elemen atau rentang teks yang dipilih, komentar awal, dan mode editor.
  3. Ubah kontrol kustom, bandingkan dengan aslinya, dan hapus pratinjau. Periksa apakah aplikasi Anda memulihkan status asli.
  4. Untuk konten kanvas, uji ruang kosong, perubahan ukuran, dan perubahan adegan. Beralihlah antarobjek dan pastikan peristiwa kontrol tetap memperbarui target yang telah direkam. Batalkan uji deteksi objek asinkron dan pastikan pemilihan berikutnya tetap berfungsi.
  5. Simpan, buka kembali, dan edit anotasi dari pratinjau lampiran kolom penulisan pesan. Periksa apakah anotasi mempertahankan kontrol dan targetnya, serta apakah menghapusnya akan menghapus semua pratinjau.
  6. Kirim anotasi bersama pesan. Pastikan ChatGPT menerima konten yang dipilih, metadata, dan nilai yang diminta, termasuk label pilihan yang terlihat serta pilihan yang tidak berubah pada kontrol yang menghilangkan currentValue.
  7. Buka situs di browser tanpa API dan verifikasi bahwa interaksi normal tetap berfungsi.

Untuk menyediakan tindakan yang dapat dilakukan ChatGPT di situs web Anda, tambahkan Alat situs (WebMCP). Anotasi membawa pilihan pengguna dan masukan mereka ke dalam percakapan; alat situs memungkinkan agen bertindak berdasarkan konteks tersebut melalui kemampuan aplikasi Anda yang sudah ada.