시작하기
opt-chat을 설치하고 첫 번째 채팅 UI를 만드는 과정을 안내합니다.
reopt designUpdated
1. 설치
권장 설정: 전용 스킬
신규 설치와 업그레이드, 스타일·AI SDK endpoint·Conversation scaffold 연결 및 검증을 처리합니다.
소비자 프로젝트 루트에서 스킬을 에이전트 런타임에 설치합니다. 이미 설치했다면 이 명령은 생략할 수 있습니다.
npx skills add reopt-ai/reopt-skills/opt-chat-install설치 후 에이전트에게 아래처럼 요청하세요. 스킬은 현재 설치 상태를 확인해 신규 설치와 업그레이드를 구분합니다.
opt-chat-install 스킬로 이 프로젝트에 @reopt-ai/opt-chat을 설정하고 결과까지 검증해줘.스킬은 소비자 프로젝트의 AGENTS.md(없으면 CLAUDE.md)에 reopt marker 블록을 멱등하게 갱신하고, 설치된 패키지 버전의 문서를 읽은 뒤 typecheck·doctor 등 해당 모듈의 검증을 실행합니다.
아래 패키지 설치 명령은 스킬을 사용할 수 없거나 설정 과정을 직접 통제해야 할 때의 수동 대안입니다. 스킬 소스와 최신 지원 범위 확인.
bun add @reopt-ai/opt-chat2. 피어 의존성
opt-chat은 React 19 이상을 요구합니다. 이미 설치되어 있다면 이 단계는 건너뛰어도 됩니다.
# 피어 의존성 (이미 설치되어 있다면 생략)
bun add react@^19 react-dom@^193. CSS 설정
Tailwind v4와 opt-ui 토큰 위에 opt-chat 스타일을 추가합니다. opt-chat의 CHAT_* 토큰은 opt-ui의 --opt-* 변수를 참조하여 자동으로 테마를 상속합니다.
/* app/globals.css — Tailwind v4 + opt-ui 토큰 */
@import "tailwindcss";
@import "@reopt-ai/opt-ui/styles.css";
@import "@reopt-ai/opt-chat/styles.css";4. API 라우트
AI SDK의 streamText로 스트리밍 응답을 반환하는 API 라우트를 만듭니다.
// app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import {
convertToModelMessages,
createUIMessageStreamResponse,
streamText,
toUIMessageStream,
} from "ai";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
messages: await convertToModelMessages(messages),
});
return createUIMessageStreamResponse({
stream: toUIMessageStream({ stream: result.stream }),
});
}5. 기본 예제
useChatSession으로 세션을 생성하고, Conversation 안에 Message 루프와 PromptInput을 배치하면 완성입니다.
"use client";
import { DefaultChatTransport } from "ai";
import {
useChatSession,
Conversation,
ConversationContent,
Message,
MessageContent,
MessageParts,
PromptInput,
PromptInputActionAddAttachment,
PromptInputAttachments,
PromptInputFooter,
PromptInputSubmit,
PromptInputTextarea,
} from "@reopt-ai/opt-chat";
export default function ChatPage() {
const session = useChatSession({
transport: new DefaultChatTransport({ api: "/api/chat" }),
});
return (
<div className="mx-auto flex h-dvh max-w-2xl flex-col py-8">
<Conversation>
<ConversationContent>
{session.messages.map((msg) => (
<Message key={msg.id} from={msg.role}>
<MessageContent>
<MessageParts parts={msg.parts} isStreaming={session.isStreaming} />
</MessageContent>
</Message>
))}
</ConversationContent>
</Conversation>
<PromptInput onSubmit={session.handleSubmit} isLoading={session.isLoading} onStop={session.stop}>
<PromptInputAttachments />
<PromptInputTextarea placeholder="메시지를 입력하세요..." />
<PromptInputFooter>
<PromptInputActionAddAttachment />
<div className="flex-1" />
<PromptInputSubmit />
</PromptInputFooter>
</PromptInput>
</div>
);
}