Core Concepts
핵심 컴포넌트
opt-chat의 core 모듈을 구성하는 7개 컴포넌트와 훅의 역할, props, 조합 패턴을 설명합니다.
reopt designUpdated
1. Core 모듈 개요
Core 모듈은 채팅 UI의 뼈대를 담당합니다. * 표시는 필수 컴포넌트를 나타냅니다.
| 컴포넌트 | 설명 | 주요 Props |
|---|---|---|
| Conversation* | 채팅 로그의 스크롤 컨테이너. use-stick-to-bottom 기반 자동 스크롤을 제공합니다. | children, className |
| Message* | 단일 메시지 렌더러. role에 따라 사용자/어시스턴트 버블을 자동 구분합니다. | from, isStreaming, children |
| PromptInput* | 메시지 입력 영역. 자동 리사이즈, 파일 첨부, 전역 드롭, 모델 선택을 지원합니다. | onSubmit, accept, maxFiles, globalDrop, children |
| PromptInputAttachments | PromptInput이 관리하는 첨부 목록을 렌더링하고 제거 액션을 연결합니다. | variant, labels, onRemove |
| MessageParts | AI SDK UIMessage.parts 배열을 파트 타입별로 자동 매핑하여 렌더링합니다. | parts, renderPart |
| Shimmer | 스트리밍 중 표시되는 타이핑 애니메이션 컴포넌트. | className |
| Suggestion* | 추천 프롬프트 버튼. 클릭 시 자동으로 메시지를 전송합니다. | prompt, label, onClick |
| useChatSession | AI SDK useChat를 감싸는 훅. PromptInput 첨부 payload를 files 전송으로 연결합니다. | transport, messages, onToolCall |
2. Compound Component 패턴
opt-chat은 compound component 패턴을 사용합니다. Conversation이 세션 컨텍스트를 제공하고, Message와 PromptInput이 이를 소비합니다. children을 통해 각 단계에서 커스텀 렌더링을 삽입할 수 있습니다.
tsx
// Conversation > Message > MessageParts 계층 구조
<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}>
<PromptInputAttachments />
<PromptInputTextarea />
<PromptInputFooter>
<PromptInputActionAddAttachment />
<PromptInputSubmit />
</PromptInputFooter>
</PromptInput>3. MessageParts 자동 매핑
Message 컴포넌트는 내부적으로 MessageParts를 사용하여 메시지의 content 배열을 파트 타입별로 자동 렌더링합니다. 기본 매핑 규칙과 커스텀 renderPart 콜백을 설명합니다.
tsx
// MessageParts의 자동 매핑 규칙
// AI SDK UIMessage.parts 배열의 type에 따라 렌더러가 결정됩니다:
// type: "text" → MessageResponse (마크다운 렌더링)
// type: "reasoning" → Reasoning (사고 과정 접기/펼치기)
// type: "tool-*" → Tool (도구 호출 + 상태 배지)
// type: "source-url" → Sources (접을 수 있는 출처 목록)
// type: "file" → ChatImage (image/*) 또는 null
// 커스텀 파트 타입은 renderPart 콜백으로 처리합니다
<MessageParts
parts={message.parts}
renderPart={(part) => {
if (part.type === "chart") return <MyChart data={part.data} />;
return null; // null 반환 시 기본 렌더러 사용
}}
/>