ChatAssistant
surfaceAI 챗봇 어시스턴트 화면. ChatSidebar + ChatMessageList + ChatInput 조합.
컴포넌트 의존 관계
깊이
100%
프로젝트 구조 설계
AIShiftEnter로 줄바꿈
테스트 커버리지
2026년 2월 4일생성된 테스트 결과를 찾지 못했습니다.
ChatAssistant 항목이 문서 메타에 연결되어 있지만 현재 생성 파일에는 없습니다.
테스트를 추가한 뒤 `bun run generate:test-results`를 실행하거나 `testDescribe` 매핑을 다시 확인하세요.
ChatAssistant Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
conversations | ChatConversationDef[] | — | 사이드바 대화 목록 |
messages | ChatMessageDef[] | — | 현재 대화 메시지 배열 |
onSend | (message: string) => void | — | 메시지 전송 핸들러 |
models | ChatModelDef[] | — | 모델 선택 옵션 |
stylePresets | ChatStylePresetDef[] | — | 스타일 프리셋 옵션 |
showSidebar | boolean | true | 사이드바 표시 여부 |
header | ReactNode | — | 상단 헤더 영역 |
actions | ReactNode | — | 헤더 우측 액션 버튼 |
loading | boolean | — | Surface 전체 로딩 상태 |
labels | ChatAssistantLabels | — | i18n 라벨 오버라이드 |
Surface 설치
CLI가 공식 배포 채널입니다. 필요한 Surface를 프로젝트로 복사한 뒤 직접 수정할 수 있습니다.
bash
npx @reopt-ai/opt-cli surface add chat-assistantConsumer target
복사된 파일은 components/surfaces 아래에 저장됩니다.
tsx
import { ChatAssistant } from "@/components/surfaces/chat-assistant";Registry metadata
- 설명
- AI 챗봇 어시스턴트 화면. ChatSidebar + ChatMessageList + ChatInput 조합.
- 파일 수
- 1개
- Registry dependencies
- 없음
- Package dependencies
- 없음
- 태그
- 없음
- Install notes
- 없음
포함 파일
chat-assistant.tsxchat-assistant.tsx
Surface 소스 보기
chat-assistant.tsx
"use client";
import type { ReactNode } from "react";
import { cn, SurfaceLayout, ChatSidebar, ChatInput, ChatMessageList } from "@reopt-ai/opt-ui";
import type { ChatConversationDef, ChatMessageDef, ChatModelDef, ChatStylePresetDef, ChatSidebarLabels, ChatInputLabels, ChatMessageListLabels } from "@reopt-ai/opt-ui";
/** Localized labels for the chat assistant component. */
export interface ChatAssistantLabels {
sidebar?: ChatSidebarLabels;
input?: ChatInputLabels;
messageList?: ChatMessageListLabels;
emptyHistoryAnnouncement?: string;
}
/** Props for the chat assistant component. */
export interface ChatAssistantProps {
conversations?: ChatConversationDef[];
activeConversationId?: string;
onConversationSelect?: (conversation: ChatConversationDef) => void;
onNewChat?: () => void;
messages?: ChatMessageDef[];
onSend?: (message: string) => void;
onFileAttach?: () => void;
models?: ChatModelDef[];
selectedModel?: string;
onModelChange?: (modelId: string) => void;
stylePresets?: ChatStylePresetDef[];
selectedStyle?: string;
onStyleChange?: (styleId: string) => void;
assistantTyping?: boolean;
assistantAvatar?: string;
userName?: string;
renderContent?: (message: ChatMessageDef) => ReactNode;
showSidebar?: boolean;
header?: ReactNode;
actions?: ReactNode;
loading?: boolean;
labels?: ChatAssistantLabels;
className?: string;
}
/** Renders the chat assistant component. */
export function ChatAssistant({
conversations = [],
activeConversationId,
onConversationSelect,
onNewChat,
messages = [],
onSend,
onFileAttach,
models,
selectedModel,
onModelChange,
stylePresets,
selectedStyle,
onStyleChange,
assistantTyping = false,
assistantAvatar,
userName,
renderContent,
showSidebar = true,
header,
actions,
loading = false,
labels,
className,
}: ChatAssistantProps) {
const isEmptyAssistant = conversations.length === 0 && messages.length === 0;
return (
<SurfaceLayout
loading={loading}
className={cn("h-full flex-row gap-0", className)}
data-opt-id="AB2NY"
data-opt-slug="chat-assistant.ChatAssistant"
>
{/* Sidebar */}
{showSidebar && (
<aside className="border-border flex w-[260px] shrink-0 flex-col border-r p-4">
<ChatSidebar
conversations={conversations}
activeConversationId={activeConversationId}
onConversationSelect={onConversationSelect}
onNewChat={onNewChat}
labels={labels?.sidebar}
/>
</aside>
)}
{/* Main area */}
<div className="flex min-w-0 flex-1 flex-col">
{isEmptyAssistant && !loading && (
<div className="sr-only" aria-live="polite">
{labels?.emptyHistoryAnnouncement ?? "No chat history yet"}
</div>
)}
{/* Header */}
{(header || actions) && (
<div className="border-border flex items-center justify-between border-b px-6 py-3">
<div>{header}</div>
{actions && (
<div className="gap-element flex items-center">{actions}</div>
)}
</div>
)}
{/* Messages */}
<ChatMessageList
messages={messages}
loading={assistantTyping}
renderContent={renderContent}
assistantAvatar={assistantAvatar}
userName={userName}
labels={labels?.messageList}
className="flex-1"
/>
{/* Input */}
<div className="border-border shrink-0 border-t px-6 py-4">
<ChatInput
onSend={onSend}
onFileAttach={onFileAttach}
models={models}
selectedModel={selectedModel}
onModelChange={onModelChange}
stylePresets={stylePresets}
selectedStyle={selectedStyle}
onStyleChange={onStyleChange}
labels={labels?.input}
/>
</div>
</div>
</SurfaceLayout>
);
}
ChatAssistant.displayName = "ChatAssistant";