Editor
Editor
AI-first 콘텐츠 에디터. 스키마 기반 스트리밍, 15개 블록 타입, Markdown 양방향 변환, 필수 CSS, 커스텀 블록 확장을 지원합니다.
reopt designUpdated
콘텐츠 제작 도구와 AI authoring 흐름 구현자opt-editor
블록 시스템, 스트리밍 프로토콜, Markdown 변환, AI 백엔드 연결과 커스텀 블록 확장을 담당합니다.
EditorProvider와 빠른 시작Flat spec, block catalog, block typesNDJSON streaming과 AI integrationMarkdown serialize와 custom blocks
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. 설치
bash
bun add @reopt-ai/opt-editor앱의 전역 스타일 엔트리에서 패키지 CSS를 한 번 import합니다.
tsx
import "@reopt-ai/opt-editor/styles.css";3. 빠른 시작
AI 스트림을 에디터에 연결하는 최소 코드:
tsx
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에 자동 반영됩니다.
tsx
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 등 아무 라이브러리나 사용 가능합니다.
tsx
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 — 라이브 데모