Editor
AI-first structured content editor with block catalogs, JSON Patch streaming, edit and diff review modes, Markdown conversion, and custom blocks.
reopt designUpdated
Block systems, streaming protocols, Markdown conversion, AI backend integration, and custom block extensions.
When to use it and what to try
Build notes, reports, and authoring tools where AI drafts content and people refine it. Work with the same document in edit, stream, and diff modes, and define allowed content through a block catalog.
Choose opt-chat for conversation messages and opt-editor for editable, persistent structured documents. Define rendering and schema together for custom blocks, and connect AI changes to a review flow.
Try the live examples
Edit blocks and inspect how the toolbar connects to document state.
Review AI changesCompare before and after states and try accepting or rejecting proposed edits.
1. 개요
@reopt-ai/opt-editor는 AI 에이전트가 스트림으로 콘텐츠를 생성하는 시나리오에 최적화된 에디터입니다. PlateJS/Slate 대비 핵심 차별점:
- Flat element map — 스트리밍 중 인덱스 시프트 없음. 트리 대신
Record<string, EditorElement>구조 - RFC 6902 NDJSON — 산업 표준 JSON Patch를 한 줄씩 스트리밍. 결정론적 적용
- 스키마 기반 AI 프롬프트 —
catalog.prompt()로 블록 스키마 + 스트리밍 규칙 자동 생성 - Runtime boundary — 런타임 peer는 React/React DOM이고, AI SDK·Zod는
./ai-sdk헬퍼를 쓸 때만 필요한 optional peer입니다.
2. 설치
bun add @reopt-ai/opt-editor앱의 전역 스타일 엔트리에서 패키지 CSS를 한 번 import합니다.
import "@reopt-ai/opt-editor/styles.css";3. 빠른 시작
AI 스트림을 에디터에 연결하는 최소 코드:
import { useMemo } from "react";
import {
Editor,
defaultCatalog,
createEditorStore,
useEditorStream,
} from "@reopt-ai/opt-editor";
import "@reopt-ai/opt-editor/styles.css";
function AIEditor() {
const store = useMemo(() => createEditorStore(), []);
const { start, status, appliedCount } = useEditorStream(store);
const handleGenerate = async () => {
const response = await fetch("/api/ai/generate", {
method: "POST",
body: JSON.stringify({ prompt: "Write an article about..." }),
});
// AI 응답을 NDJSON 스트림으로 에디터에 주입
await start(response.body!.pipeThrough(new TextDecoderStream()));
};
return (
<div>
<button onClick={handleGenerate} disabled={status === "streaming"}>
{status === "streaming"
? `생성 중... (${appliedCount} patches)`
: "AI 생성"}
</button>
<Editor store={store} catalog={defaultCatalog} mode="stream" />
</div>
);
}4. Stream / Edit / Diff 모드
에디터는 세 가지 모드를 지원합니다:
| 모드 | 용도 | 특징 |
|---|---|---|
| stream | AI 생성 콘텐츠 표시 | 읽기 전용, MarkedText 렌더링, syntax highlighting |
| edit | 사용자 직접 편집 | contentEditable, split/merge, slash commands |
| diff | AI 제안 검수 | 읽기 전용 리뷰, 변경 블록 강조/배지/hover diff, 블록 단위 승인·거절 |
5. 블록 타입 (15개)
기본 카탈로그에 포함된 블록 타입입니다. 각 블록의 Props와 사용법은 블록 타입 레퍼런스를 참고하세요.
| 카테고리 | 블록 |
|---|---|
| 텍스트 | paragraph, heading (h1-h6), quote, callout |
| 구조 | list / list-item, table / table-row / table-cell |
| 미디어 | image, video, file, embed |
| 기타 | code, divider |
6. 인라인 서식
텍스트 props는 마크다운 인라인 서식을 지원합니다. AI가 생성한 마크다운이 그대로 렌더링됩니다.
| 서식 | 문법 | HTML |
|---|---|---|
| Bold | **text** | <strong> |
| Italic | *text* | <em> |
| Code | `text` | <code> |
| Strikethrough | ~~text~~ | <s> |
| Link | [text](url) | <a> |
7. 커스텀 블록 확장
defineCatalog()로 커스텀 블록을 추가하면, AI 프롬프트와 JSON Schema에 자동 반영됩니다.
import {
MarkedText,
defineCatalog,
defaultCatalog,
type BlockDefinition,
type BlockRenderProps,
} from "@reopt-ai/opt-editor";
type AlertAttrs = {
severity?: string;
};
function AlertBlock({
attrs,
content,
editableContent,
}: BlockRenderProps<AlertAttrs>) {
return (
<aside data-alert-severity={attrs.severity ?? "info"}>
{editableContent?.({
as: "div",
placeholder: "Alert message",
}) ?? <MarkedText content={content} />}
</aside>
);
}
const alertDefinition: BlockDefinition<AlertAttrs> = {
type: "alert",
attrsSchema: {
severity: { type: "string", default: "info" },
},
contentKind: "rich-text",
component: AlertBlock,
prompt: "An alert box with severity (info/warning/error) and message.",
};
const customCatalog = defineCatalog({
...defaultCatalog.blocks,
alert: alertDefinition,
});
// catalog.prompt() 에 alert 블록이 자동 포함됨
// catalog.jsonSchema() 에도 alert 스키마가 포함됨8. Syntax Highlighting
코드 블록에 하이라이팅을 적용하려면 highlightCode prop을 전달합니다. shiki, hljs, prism 등 아무 라이브러리나 사용 가능합니다.
import { codeToHtml } from "shiki";
const highlighter = (code: string, lang: string) =>
codeToHtml(code, { lang, theme: "github-dark" });
<Editor
store={store}
catalog={defaultCatalog}
mode="stream"
highlightCode={highlighter}
/>9. 하위 문서
- 블록 타입 레퍼런스 — 15개 블록의 Props, 중첩 구조, 렌더링 방식
- 스트리밍 프로토콜 — NDJSON 패치, StreamCompiler, 재시도/이어쓰기
- Markdown 변환 — specToMarkdown, markdownToSpec
- Skills — 설치, 진단, Plate 마이그레이션, 업그레이드, release workflow
- Playground — 라이브 데모