Build & Operate
커스텀 파트
MessageParts의 renderPart 콜백을 사용하여 기본 제공되지 않는 메시지 파트 타입에 대한 맞춤 렌더러를 작성하는 방법을 설명합니다.
reopt designUpdated
1. renderPart API
MessageParts의 renderPart 콜백은 각 파트에 대해 호출됩니다. undefined를 반환하면 기본 렌더러가 사용되고, ReactNode를 반환하면 해당 렌더러를 완전히 대체합니다.
tsx
import type { UIMessage } from "ai";
import { MessageParts } from "@reopt-ai/opt-chat";
// renderPart 콜백 시그니처
type RenderPart = (
part: UIMessage["parts"][number],
index: number,
) => React.ReactNode | undefined;
// undefined를 반환하면 해당 파트에 기본 렌더러가 적용됩니다.
// ReactNode를 반환하면 기본 렌더러를 완전히 대체합니다.
<MessageParts
parts={message.parts}
renderPart={(part) => {
// 커스텀 타입만 처리하고, 나머지는 기본 렌더러에 위임
switch (part.type) {
case "chart":
return <MyChartRenderer data={part.data} />;
case "approval":
return <MyApprovalCard request={part.request} />;
default:
return undefined; // 기본 렌더러 사용
}
}}
/>2. 예제: 차트 파트 렌더러
AI 응답에 차트 데이터가 포함된 경우, 커스텀 "chart" 파트 렌더러를 만들어 opt-ui의 BarChart로 시각화하는 전체 예제입니다.
tsx
"use client";
import { DefaultChatTransport, type UIMessage } from "ai";
import {
useChatSession,
Conversation,
ConversationContent,
Message,
MessageContent,
MessageParts,
PromptInput,
PromptInputFooter,
PromptInputSubmit,
PromptInputTextarea,
} from "@reopt-ai/opt-chat";
import { BarChart } from "@reopt-ai/opt-charts";
// 커스텀 차트 파트 타입 정의
interface ChartPart {
type: "chart";
data: Array<{ name: string; value: number }>;
title: string;
chartType: "bar" | "line" | "pie";
}
function ChartPartRenderer({ part }: { part: ChartPart }) {
return (
<div className="border-border my-2 rounded-lg border p-4">
<h4 className="text-text-primary mb-3 text-sm font-semibold">
{part.title}
</h4>
<BarChart
data={part.data}
series={[{ dataKey: "value", name: "값" }]}
height={200}
/>
</div>
);
}
export default function ChatWithCustomParts() {
const session = useChatSession({
transport: new DefaultChatTransport({ api: "/api/chat" }),
});
return (
<div className="flex h-dvh flex-col">
<Conversation>
<ConversationContent>
{session.messages.map((msg) => (
<Message key={msg.id} from={msg.role}>
<MessageContent>
<MessageParts
parts={msg.parts}
renderPart={(part: UIMessage["parts"][number]) => {
if (part.type === "chart") {
return <ChartPartRenderer part={part as ChartPart} />;
}
return undefined;
}}
/>
</MessageContent>
</Message>
))}
</ConversationContent>
</Conversation>
<PromptInput onSubmit={session.handleSubmit} isLoading={session.isLoading}>
<PromptInputTextarea />
<PromptInputFooter>
<div className="flex-1" />
<PromptInputSubmit />
</PromptInputFooter>
</PromptInput>
</div>
);
}3. 여러 커스텀 파트 등록
커스텀 파트가 여러 개인 경우, 레지스트리 패턴으로 renderPart 콜백을 깔끔하게 관리할 수 있습니다.
tsx
// 여러 커스텀 파트를 등록하는 패턴
const customRenderers: Record<string, (part: any) => React.ReactNode> = {
chart: (part) => <ChartPartRenderer part={part} />,
kanban: (part) => <KanbanPartRenderer part={part} />,
form: (part) => <FormPartRenderer part={part} />,
diff: (part) => <DiffPartRenderer part={part} />,
};
<MessageParts
parts={msg.parts}
renderPart={(part) => {
const renderer = customRenderers[part.type];
return renderer ? renderer(part) : undefined;
}}
/>