주석 확장성
Browser Annotation API를 사용하면 웹사이트에서 사용자가 선택할 대상, 피드백에 함께 제공할 컨텍스트, ChatGPT에 주석을 보내기 전에 변경 사항을 미리 보는 데 사용할 컨트롤을 맞춤 설정할 수 있습니다.
브라우저 주석은 코드를 변경하지 않아도 사이트에서 작동합니다. 사용자는 페이지의 일부를 선택하고 댓글을 추가한 뒤, 컨텍스트에 담아 Codex 또는 ChatGPT Work에 보낼 수 있습니다.
개발자는 Browser Annotation API를 사용해 애플리케이션에 맞는 컨텍스트나 컨트롤을 제공할 수 있습니다. 예를 들어 디자인 시스템 미리 보기에 컴포넌트 변형별 미리 보기를 첨부하여 개발자가 웹사이트의 컴포넌트를 어떻게 업데이트할지 알 수 있도록 할 수 있습니다.
Browser Annotation API를 이해하거나 웹사이트에 주석 지원을 추가하는 데 도움이 필요하면 Annotations Extensibility 플러그인을 설치하세요.
Annotations Extensibility 플러그인 설치
직접 사용해 보기
이 가이드에서 Browser Annotation API가 어떻게 작동하는지 확인하세요.
- ChatGPT의 내장 브라우저에서 이 페이지를 여세요.
- 이 카드에 표시되는 추천 프롬프트를 열어 보세요.
- 주석 모드로 들어간 다음, 아래 표나 코드 예시를 선택하여 미리 정의된 레이아웃과 테마 간에 전환하세요.
- 페이지의 어떤 항목에든 주석을 달고 기본 주석 동작을 확인할 수 있습니다.
주석을 사용해 보려면 ChatGPT 내장 브라우저에서 이 페이지를 여세요.
맞춤 설정할 항목 선택
웹사이트에 맞는 통합 방식으로 시작하세요.
| 목표 | 통합 방식 |
|---|---|
| 카드나 다른 요소 그룹을 하나의 객체로 선택할 수 있게 만들기 | 선택 대상 |
| 사용자가 구문이나 문장을 선택할 수 있게 하기 | 텍스트 선택 컨테이너 |
| 선택 항목에 추가 컨텍스트 포함하기 | 선택 메타데이터 |
| 자체 버튼으로 추천 댓글이 포함된 주석 열기 | 주석 요청 |
| 자체 UI에서 특정 구절에 대한 피드백 요청하기 | 텍스트 범위 요청 |
| 주석이 열릴 때 고급 컨트롤 표시하기 | 편집기 기본 설정 |
| 애플리케이션 속성을 미리 보거나 선택 사항 수집하기 | 사용자 지정 컨트롤 |
| 캔버스 안에 그려진 개별 객체 선택하기 | 주석 표면 |
| 사이트에서 주석 모드를 켜거나 끄기 | 주석 모드 제어 |
이 가이드는 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값을 갖는 속성을 최대 6개까지 사용할 수 있습니다. - 키는 최대 64자, 문자열 값은 최대 256자까지 사용할 수 있습니다.
- 직렬화된 객체의 크기는 최대 2,048바이트입니다.
키는 ASCII 문자로 시작해야 하며 ASCII 문자, 숫자, 공백, 밑줄 또는 하이픈만 포함할 수 있습니다. 단어 사이에는 공백을 하나만 사용하세요. 중첩된 객체와 배열은 지원되지 않습니다. 브라우저는 잘못된 메타데이터를 무시합니다.
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에 메시지가 전송되지는 않으며, 사용자만 제출할 수 있습니다.
활성 사용자 상호작용 안에서 호출하세요. 먼저 네트워크 요청이 완료되기를 기다리면 해당 상호작용이 더 이상 활성 상태가 아닐 수 있습니다.
선택 사항인 두 번째 인수는 다음 필드를 지원합니다.
| 옵션 | 동작 |
|---|---|
mode |
"advanced"를 사용하면 요소의 고급 컨트롤을 열고, "default"를 사용하면 기본 편집기 동작을 사용합니다. 기본값은 "default"입니다. "default"에서도 사용자 지정 컨트롤이 열릴 수 있습니다. 편집기 기본 설정을 참고하세요. 텍스트 범위는 "default"만 지원합니다. |
enterAnnotationMode |
true로 설정하면 주석 모드로 들어가며, 주석을 취소하거나 전송한 후에도 해당 모드를 유지합니다. |
metadata |
유효하고 비어 있지 않은 메타데이터는 이 요청에서 대상의 HTML 메타데이터를 대체합니다. 텍스트 범위이거나 대상 요소가 shadow 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 코드 단위를 포함할 수 있습니다. 축소된 범위, 공백으로만 구성된 텍스트, 선택된 숨겨진 텍스트, 닫힌 shadow root 안의 범위는 지원되지 않습니다.
텍스트 범위 요청은 기본 텍스트 편집기만 사용합니다.
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()를 사용하여 하나 이상의 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의 내부 ID는
선택 사항의 텍스트로 전송되지 않습니다.
컨트롤 및 시작 값 구성
controlsMode: "replace"를 사용하면 등록된 대상에 자체 컨트롤만
표시하고, "extend"를 사용하면 기본 제공 컨트롤과 함께 표시합니다. 선택 사항인
controlsHeading는 패널의 이름을 지정합니다. 브라우저는 앞뒤 공백을 제거하며 1~80자를
허용합니다. 제목이 없으면 패널에 요소의 HTML 태그가 표시됩니다.
제목은 ChatGPT에 전송되는 컨텍스트에 포함되지 않습니다.
하나의 등록은 컨트롤을 최대 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를 반환하면 시도한 제안을 폐기하고
이전 컨트롤 및 제안 상태를 복원하세요. 요청이 수락되면 제안을
유지하세요. 브라우저가 컨트롤을 캡처하기 전에 요청을 대기열에 넣을 수 있습니다.
컨트롤 및 등록 유효성 검사
브라우저는 다음 제한에 따라 등록과 업데이트의 유효성을 검사합니다.
| 필드 또는 리소스 | 제약 조건 |
|---|---|
| 컨트롤 | 등록당 최대 12개이며, callback 식별자는 고유해야 합니다. 해당 컨트롤 유형에 정의된 필드만 사용하세요. |
| 레이블 및 제목 | 컨트롤 레이블, 옵션 레이블 및 controlsHeading는 앞뒤 공백을 제거한 후 비어 있지 않아야 하며 최대 80 UTF-16 코드 단위여야 합니다. 컨트롤 텍스트에는 제어 문자나 텍스트 방향을 변경하는 문자를 포함할 수 없습니다. |
| 식별자 | callback는 앞뒤 공백을 제거한 후 최대 80 UTF-16 코드 단위이며, 위에서 설명한 형식을 사용합니다. reference는 1~80자이며 공백 없이 ASCII 문자, 숫자 및 _ . / : @ $ # -를 허용합니다. |
| 선택 옵션 | 옵션은 1~12개이며, 각각 최대 512 UTF-16 코드 단위의 고유한 value 문자열을 사용합니다. 제공된 currentValue 및 defaultValue는 옵션의 값과 일치해야 합니다. |
| 범위 값 | min, max, currentValue 및 defaultValue는 −10,000min < max 조건을 충족해야 하며, step는 0.001max - min보다 크지 않아야 하고, 시작 값은 범위 안에 있어야 합니다. 시작 값은 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 함수를
제공하세요.
호스트는 보안 최상위 문서에서 shadow DOM 바깥에 있는, 문서에 연결된 HTML 요소여야 합니다. 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는
clientX 및 clientY와 같은 방식으로 페이지의 보이는 영역을 기준으로 한 CSS 픽셀을 사용합니다. 배율, 이동 및 확대/축소를
반영하여 장면 좌표에서 변환하세요.
hitTest는 프로미스를 반환할 수 있으며, 대체된 작업을 취소하기 위해 AbortSignal를 signal로
받습니다. 브라우저는 250밀리초 동안 기다린 후
DOM 선택으로 대체합니다. 오류나 잘못된 결과가 발생해도 대체됩니다. 히트 테스트가 성공했지만 객체가 없으면
null를 명시적으로 반환하세요.
취소된 작업도 프로미스를 이행하거나 거부해야 합니다. 브라우저는
한 번에 하나의 hitTest 콜백만 실행하도록 허용하며, 취소되거나 시간이 초과되어도 프로미스가
완료될 때까지 해당 실행 슬롯을 유지합니다. 워커가 선택을 처리한다면
작업이 중단될 때 대기 중인 프로미스를 완료하세요. 취소된 워커의 응답을
버리면 이후 캔버스 선택이 차단될 수 있습니다.
애플리케이션별 피드백을 제공하려면 선택 사항인 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()를 사용하고,
UI를 동기화하려면 문서의 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는 불리언입니다. 브라우저는
초기 이벤트나 상태 변화가 없는 강제 설정에 대한 중복 이벤트를 보내지 않으므로, getter에서
UI를 초기화하세요. 활성 상태에서 액세스가 취소되면 마지막 이벤트가
active: false를 알리고, 보관된 getter는 false를 반환합니다.
주석 모드는 페이지 클릭을 가로챕니다. 종료 버튼을 비롯한 페이지 컨트롤을 사용하려면 Space 를
누른 상태로 조작하거나, 브라우저의 주석 UI를 통해 종료하세요.
Space 를 누르고 있는 것만으로는 isActive()가 true인 상태가 유지됩니다. API는 클릭을 통과시키는
oai-annotation-ignore 또는 다른 속성을 지원하지 않습니다.
종료하면 편집기가 닫히며, 저장된 주석은 제출되거나 작성란으로 옮겨지지 않고
유지됩니다. 컴포넌트를 제거할 때 cleanupAnnotationButton()를
호출하세요. 캡처된 API 참조를 사용하면 네임스페이스가 사라진 경우에도 정리 과정에서
리스너를 제거할 수 있습니다.
통합 테스트
데스크톱 앱의 내장 브라우저에서 웹사이트를 열고 추가한 기능을 테스트하세요.
- 주석 모드로 들어가 객체와 텍스트를 선택하세요. 강조 표시, 이름, 범위 및 메타데이터가 의도한 대상과 일치하는지 확인하세요.
- 사이트의 버튼에서 주석을 여세요. 선택된 요소 또는 텍스트 범위, 초기 댓글 및 편집기 모드를 확인하세요.
- 사용자 지정 컨트롤을 변경하고 원본과 비교한 뒤 미리 보기를 지우세요. 애플리케이션이 원래 상태를 복원하는지 확인하세요.
- 캔버스 콘텐츠의 경우 빈 공간, 크기 조정 및 장면 변경을 테스트하세요. 객체를 전환하고 컨트롤 이벤트가 여전히 캡처된 대상을 업데이트하는지 확인하세요. 비동기 히트 테스트를 취소하고 이후 선택이 계속 작동하는지 확인하세요.
- 작성란의 첨부 파일 미리 보기에서 주석을 저장하고 다시 연 뒤 편집하세요. 컨트롤과 대상이 유지되는지, 주석을 제거하면 미리 보기가 모두 지워지는지 확인하세요.
- 메시지와 함께 주석을 보내세요. ChatGPT가 선택한 콘텐츠,
메타데이터 및 요청한 값을 수신하는지 확인하세요. 여기에는 표시되는 선택 옵션의
레이블과
currentValue를 생략한 컨트롤에서 변경하지 않은 선택 사항도 포함됩니다. - API가 없는 브라우저에서 사이트를 열고 일반적인 상호작용이 계속 작동하는지 확인하세요.
ChatGPT가 웹사이트에서 수행할 수 있는 작업을 제공하려면 사이트 도구(WebMCP)를 추가하세요. 주석은 사용자의 선택과 피드백을 대화에 전달하며, 사이트 도구는 에이전트가 애플리케이션의 기존 기능을 통해 해당 컨텍스트에 따라 작업할 수 있게 합니다.