气泡/容器
Message List
Chat message list component with auto-scroll, multiple message types, and status support
"use client";
import {
MessageList,
type MessageItem,
type AIMessageItem,
} from "@/components/composed/message/message-list";
import { ThinkingStep } from "@/components/composed/thinking-process";
import { User, Bot } from "lucide-react";
import * as React from "react";
const messages = [
{
id: "1",
role: "user" as const,
content: "请分析一下这个问题",
contentForCopy: "请分析一下这个问题",
avatar: { icon: <User className="w-5 h-5" />, name: "用户", time: "10:30" },
},
{
id: "2",
role: "ai" as const,
content: {
type: "thinking",
title: "分析完成",
status: "completed" as const,
duration: 12,
content: "已从多维度分析问题,并生成详细方案与建议。",
defaultOpen: true,
},
contentForCopy: "已从多维度分析问题,并生成详细方案与建议。",
avatar: {
icon: <Bot className="w-5 h-5" />,
name: "AI 助手",
time: "10:31",
},
},
] as (MessageItem | AIMessageItem)[];
function AIContent({ content }: { content: React.ReactNode }) {
const c = content as {
type?: string;
title?: string;
status?: string;
duration?: number;
content?: string;
defaultOpen?: boolean;
};
if (c && typeof c === "object" && c.type === "thinking" && c.title) {
const { type: _, ...props } = c;
return (
<ThinkingStep
title={props.title}
content={props.content}
status={props.status as "completed"}
duration={props.duration}
defaultOpen={props.defaultOpen}
/>
);
}
return <>{content}</>;
}
export function MessageListDemo() {
return (
<div className="h-[400px] w-full overflow-hidden">
<MessageList
messages={messages}
showDefaultFeedback
renderContent={(content, msg) =>
msg.role === "ai" ? <AIContent content={content} /> : content
}
/>
</div>
);
}
MessageList 组件是一个完整的聊天消息列表解决方案,集成了消息渲染、自动滚动、状态管理等功能,支持自定义渲染,适用于 AI 聊天界面、客服系统、在线问答等场景。
概述
- 多种消息类型:支持用户消息(user)和 AI 消息(ai)
- AI 消息状态:支持 idle、generating、failed 三种状态
- 自动滚动:新消息时自动滚动到底部
- 点击事件:支持消息点击回调
- 空状态提示:无消息时显示友好提示
- 头像支持:支持 AvatarHeader 组件,显示头像、名称、时间
- 自定义渲染:支持自定义内容渲染器和完整消息渲染器
- 无障碍支持:ARIA live regions 屏幕阅读器友好
快速开始
import { MessageList } from "@/registry/wuhan/composed/message/message-list";
interface Message {
id: string;
role: "user" | "ai";
content: React.ReactNode;
}
export function Example() {
const messages: Message[] = [
{ id: "1", role: "user", content: "你好!" },
{ id: "2", role: "ai", content: "你好,有什么可以帮助你的吗?" },
];
return <MessageList messages={messages} />;
}特性
- 自动滚动:当消息列表更新时,自动滚动到最底部(可禁用)
- 消息状态:AI 消息支持生成中状态,可显示自定义 loading 内容
- 错误处理:AI 消息支持失败状态,可显示自定义错误内容
- 消息点击:支持 onMessageClick 回调
- 自定义反馈:支持 feedback 属性添加反馈区域
- 头像支持:使用 AvatarHeader 组件,支持图片头像、图标头像、名称和时间
- 自定义渲染:
renderContent- 自定义消息内容渲染(如 Markdown)renderMessage- 完全自定义消息项渲染
安装
代码演示
基本使用
最基础的消息列表,包含用户消息和 AI 消息。
"use client";
import {
MessageList,
type MessageItem,
type AIMessageItem,
} from "@/components/composed/message/message-list";
import { ThinkingStep } from "@/components/composed/thinking-process";
import { User, Bot } from "lucide-react";
import * as React from "react";
const messages = [
{
id: "1",
role: "user" as const,
content: "请分析一下这个问题",
contentForCopy: "请分析一下这个问题",
avatar: { icon: <User className="w-5 h-5" />, name: "用户", time: "10:30" },
},
{
id: "2",
role: "ai" as const,
content: {
type: "thinking",
title: "分析完成",
status: "completed" as const,
duration: 12,
content: "已从多维度分析问题,并生成详细方案与建议。",
defaultOpen: true,
},
contentForCopy: "已从多维度分析问题,并生成详细方案与建议。",
avatar: {
icon: <Bot className="w-5 h-5" />,
name: "AI 助手",
time: "10:31",
},
},
] as (MessageItem | AIMessageItem)[];
function AIContent({ content }: { content: React.ReactNode }) {
const c = content as {
type?: string;
title?: string;
status?: string;
duration?: number;
content?: string;
defaultOpen?: boolean;
};
if (c && typeof c === "object" && c.type === "thinking" && c.title) {
const { type: _, ...props } = c;
return (
<ThinkingStep
title={props.title}
content={props.content}
status={props.status as "completed"}
duration={props.duration}
defaultOpen={props.defaultOpen}
/>
);
}
return <>{content}</>;
}
export function MessageListDemo() {
return (
<div className="h-[400px] w-full overflow-hidden">
<MessageList
messages={messages}
showDefaultFeedback
renderContent={(content, msg) =>
msg.role === "ai" ? <AIContent content={content} /> : content
}
/>
</div>
);
}
带输入的聊天界面
完整的聊天界面演示,包含输入框和消息发送功能。
"use client";
import {
MessageList,
type MessageItem,
type AIMessageItem,
} from "@/components/composed/message/message-list";
import { ThinkingStep } from "@/components/composed/thinking-process";
import { User, Bot } from "lucide-react";
import * as React from "react";
const messages = [
{
id: "1",
role: "user" as const,
content: "请分析一下这个问题",
contentForCopy: "请分析一下这个问题",
avatar: { icon: <User className="w-5 h-5" />, name: "用户", time: "10:30" },
},
{
id: "2",
role: "ai" as const,
content: {
type: "thinking",
title: "分析完成",
status: "completed" as const,
duration: 12,
content: "已从多维度分析问题,并生成详细方案与建议。",
defaultOpen: true,
},
contentForCopy: "已从多维度分析问题,并生成详细方案与建议。",
avatar: {
icon: <Bot className="w-5 h-5" />,
name: "AI 助手",
time: "10:31",
},
},
] as (MessageItem | AIMessageItem)[];
function AIContent({ content }: { content: React.ReactNode }) {
const c = content as {
type?: string;
title?: string;
status?: string;
duration?: number;
content?: string;
defaultOpen?: boolean;
};
if (c && typeof c === "object" && c.type === "thinking" && c.title) {
const { type: _, ...props } = c;
return (
<ThinkingStep
title={props.title}
content={props.content}
status={props.status as "completed"}
duration={props.duration}
defaultOpen={props.defaultOpen}
/>
);
}
return <>{content}</>;
}
export function MessageListDemo() {
return (
<div className="h-[400px] w-full overflow-hidden">
<MessageList
messages={messages}
showDefaultFeedback
renderContent={(content, msg) =>
msg.role === "ai" ? <AIContent content={content} /> : content
}
/>
</div>
);
}
空状态
当没有消息时显示友好提示。
暂无消息
"use client";
import { MessageList } from "@/components/composed/message/message-list";
export function MessageListEmpty() {
return (
<div className="h-[400px] border rounded-lg overflow-hidden">
<MessageList messages={[]} />
</div>
);
}
带头像的消息列表
使用图标头像显示用户和 AI 助手。
"use client";
import { MessageList } from "@/components/composed/message/message-list";
import { User, Bot } from "lucide-react";
export function MessageListIconAvatar() {
const messages = [
{
id: "1",
role: "user" as const,
content: "你好!",
avatar: {
icon: <User className="w-5 h-5" />,
name: "访客",
time: "12:00",
},
},
{
id: "2",
role: "ai" as const,
content: "你好!有什么可以帮助你的?",
status: "idle" as const,
avatar: {
icon: <Bot className="w-5 h-5" />,
name: "AI 助手",
time: "12:01",
},
},
{
id: "3",
role: "user" as const,
content: "我想了解你的功能",
avatar: {
icon: <User className="w-5 h-5" />,
name: "访客",
time: "12:02",
},
},
];
return (
<div className="h-[400px] border rounded-lg overflow-hidden">
<MessageList messages={messages} />
</div>
);
}
生成中状态
展示 AI 消息的生成中状态。
"use client";
import { MessageList } from "@/components/composed/message/message-list";
import { useState } from "react";
import { LoadingDots } from "@/components/wuhan/blocks/message/message-01";
import type {
MessageItem,
AIMessageItem,
} from "@/components/composed/message/message-list";
export function MessageListGenerating() {
const [messages, setMessages] = useState<(MessageItem | AIMessageItem)[]>([
{ id: "1", role: "user" as const, content: "请帮我分析这段代码" },
{
id: "2",
role: "ai" as const,
content: "",
status: "generating" as const,
generatingContent: (
<div className="flex items-center gap-2 text-muted-foreground">
<LoadingDots />
<span>正在分析...</span>
</div>
),
},
]);
// 模拟生成完成
setTimeout(() => {
setMessages((prev) =>
prev.map((msg) =>
msg.id === "2"
? {
...msg,
status: "idle" as const,
content:
"分析完成!这段代码实现了一个计数器组件,使用了 React Hooks 来管理状态。",
}
: msg,
),
);
}, 3000);
return (
<div className="h-[300px] border rounded-lg overflow-hidden">
<MessageList messages={messages} />
</div>
);
}
可重试的错误消息
失败状态的消息支持重试功能。
"use client";
import { MessageList } from "@/components/composed/message/message-list";
import { useState } from "react";
import { RefreshCw } from "lucide-react";
import type {
MessageItem,
AIMessageItem,
} from "@/components/composed/message/message-list";
export function MessageListRetryable() {
const [messages, setMessages] = useState<(MessageItem | AIMessageItem)[]>([
{ id: "1", role: "user" as const, content: "请帮我查询数据" },
{
id: "2",
role: "ai" as const,
content: "",
status: "failed" as const,
errorContent: undefined,
},
]);
const handleRetry = (messageId: string) => {
setMessages((prev) =>
prev.map((msg) =>
msg.id === messageId
? {
...msg,
status: "generating" as const,
content: "",
errorContent: undefined,
}
: msg,
),
);
// 模拟重新请求
setTimeout(() => {
setMessages((prev) =>
prev.map((msg) =>
msg.id === messageId
? {
...msg,
status: "idle" as const,
content: "数据查询成功!这是查询结果...",
}
: msg,
),
);
}, 1500);
};
// 设置错误内容
const secondMessage = messages[1];
if (
secondMessage &&
"status" in secondMessage &&
secondMessage.status === "failed" &&
!secondMessage.errorContent
) {
setMessages((prev) =>
prev.map((msg, index) =>
index === 1
? {
...msg,
errorContent: (
<div className="flex flex-col gap-2 p-3 bg-red-50 rounded-lg">
<p className="text-red-600">请求失败,请稍后重试</p>
<button
onClick={() => handleRetry(msg.id)}
className="flex items-center gap-2 px-3 py-1.5 bg-primary text-primary-foreground rounded hover:bg-primary/90 transition-colors w-fit"
>
<RefreshCw className="w-4 h-4" />
重试
</button>
</div>
),
}
: msg,
),
);
}
return (
<div className="h-[300px] border rounded-lg overflow-hidden">
<MessageList messages={messages} />
</div>
);
}
API
MessageList
消息列表主组件。
Props
| Prop | Type | Default | Description |
|---|---|---|---|
messages | (MessageItem | AIMessageItem | UserMessageItem)[] | [] | 消息列表数据 |
onMessageClick | (message: MessageItem) => void | - | 消息点击回调 |
className | string | - | 额外的样式类名 |
autoScroll | boolean | true | 是否自动滚动到底部 |
renderContent | MessageContentRenderer | - | 自定义内容渲染器 |
renderMessage | MessageRenderer | - | 自定义消息渲染器 |
MessageItem
基础消息数据结构。
interface MessageItem {
/** 唯一消息 ID */
id: string;
/** 消息角色 */
role: "user" | "ai";
/** 消息内容 */
content: React.ReactNode;
/** 时间戳 */
timestamp?: number;
/** 反馈内容 */
feedback?: React.ReactNode;
/** 头像配置(显示在消息上方) */
avatar?: MessageAvatar;
}MessageAvatar
头像配置(使用 AvatarHeader 组件)。
interface MessageAvatar {
/** 头像图片 URL 或 ReactNode */
src?: string | React.ReactNode;
/** 头像图标 */
icon?: React.ReactNode;
/** 头像大小 */
size?: "small" | "default" | "large";
/** 名称 */
name?: React.ReactNode;
/** 时间 */
time?: React.ReactNode;
}AIMessageItem
AI 消息额外属性。
interface AIMessageItem extends MessageItem {
role: "ai";
/** AI 消息状态 */
status?: "idle" | "generating" | "failed";
/** 生成中自定义内容 */
generatingContent?: React.ReactNode;
/** 失败自定义内容 */
errorContent?: React.ReactNode;
}UserMessageItem
用户消息类型。
interface UserMessageItem extends MessageItem {
role: "user";
}MessageContentRenderer
自定义内容渲染器类型。
type MessageContentRenderer = (
content: React.ReactNode,
message: MessageItem | AIMessageItem | UserMessageItem,
) => React.ReactNode;MessageRenderer
自定义消息渲染器类型(完全自定义)。
type MessageRenderer = (
message: MessageItem | AIMessageItem | UserMessageItem,
defaultRender: () => React.ReactNode,
) => React.ReactNode;Example
import { MessageList, type AIMessageItem } from "@/registry/wuhan/composed/message/message-list";
function ChatExample() {
const messages: (MessageItem | AIMessageItem)[] = [
{
id: "1",
role: "user",
content: "你好,请帮我写一个函数",
},
{
id: "2",
role: "ai",
content: "当然可以,请告诉我具体需求",
status: "idle",
},
{
id: "3",
role: "ai",
content: "",
status: "generating",
generatingContent: <LoadingDots />,
},
];
return (
<MessageList
messages={messages}
onMessageClick={(msg) => console.log("点击消息:", msg.id)}
autoScroll={true}
/>
);
}自定义渲染
MessageList 提供了两种自定义渲染方式,让你可以灵活地定制消息的展示效果。
renderContent - 自定义内容渲染
使用 renderContent 可以自定义消息内容的渲染方式,常用于:
- 使用 Markdown 组件渲染 AI 消息
- 根据消息类型使用不同的渲染组件
- 对特殊内容格式进行自定义处理
import { MessageList } from "@/registry/wuhan/composed/message/message-list";
import Markdown from "@/registry/wuhan/composed/markdown";
function ChatWithMarkdown() {
const messages = [
{
id: "1",
role: "user",
content: "请解释一下 React",
},
{
id: "2",
role: "ai",
content: "## React 简介\nReact 是一个用于构建用户界面的 JavaScript 库...",
},
];
return (
<MessageList
messages={messages}
renderContent={(content, msg) => {
// AI 消息使用 Markdown 渲染
if (msg.role === "ai" && typeof content === "string") {
return <Markdown>{content}</Markdown>;
}
return content;
}}
/>
);
}renderMessage - 完全自定义消息渲染
使用 renderMessage 可以完全自定义消息项的渲染,包括:
- 为特定消息添加特殊样式或装饰
- 为失败消息添加重试按钮
- 根据消息内容显示不同的布局
import { MessageList, type AIMessageItem } from "@/registry/wuhan/composed/message/message-list";
import { Button } from "@/registry/wuhan/ui/button";
function CustomMessageList() {
return (
<MessageList
messages={messages}
renderMessage={(message, defaultRender) => {
const aiMsg = message as AIMessageItem;
// 为失败消息添加重试按钮
if (aiMsg.status === "failed") {
return (
<div className="relative group">
{defaultRender()}
<Button
className="absolute -top-2 -right-2 opacity-0 group-hover:opacity-100"
onClick={() => retryMessage(message.id)}
>
重试
</Button>
</div>
);
}
// 为第一条消息添加特殊样式
if (message.id === messages[0]?.id) {
return (
<div className="transform scale-105 transition-transform">
{defaultRender()}
</div>
);
}
return defaultRender();
}}
/>
);
}结合使用
你可以同时使用 renderContent 和 renderMessage:
<MessageList
messages={messages}
renderContent={(content, msg) => {
// 自定义内容渲染
if (msg.role === "ai") {
return <Markdown>{content}</Markdown>;
}
return content;
}}
renderMessage={(message, defaultRender) => {
// 自定义消息项渲染
if (message.id === "special") {
return (
<div className="border-l-4 border-primary pl-4">
{defaultRender()}
</div>
);
}
return defaultRender();
}}
/>使用场景
- AI 聊天应用:ChatGPT、Claude 等对话界面
- 客服系统:在线客服、智能问答
- 协作工具:团队沟通、讨论区
- 教育平台:在线答疑、学习辅导
- 代码助手:编程辅助、技术问答
最佳实践
- 状态管理:使用 generating 状态显示 loading,内容为空时渲染自定义 loading
- 错误恢复:失败状态提供重试按钮或重新生成选项
- 流式更新:配合 SSE/WebSocket 实现流式内容渲染
- 消息历史:保存消息历史,支持加载更多和分页
- 用户体验:新消息时自动滚动,但允许用户取消自动滚动
- 内容渲染:使用 renderContent 配合 Markdown 组件渲染 AI 消息
- 特殊消息:使用 renderMessage 为特定消息添加特殊效果
- 头像使用:使用 avatar 属性为每条消息配置独立头像,或设置统一的默认头像
注意事项
autoScroll默认为 true,新消息时自动滚动到底部generatingContent只在 status 为 "generating" 时显示errorContent只在 status 为 "failed" 时显示renderContent和renderMessage可以结合使用renderMessage的第二个参数defaultRender返回默认渲染结果- 消息列表高度需要自行设置,建议使用固定高度或 flex 布局
- 头像显示在消息上方,使用 AvatarHeader 组件实现
与原语组件的关系
MessageList 基于以下组件构建:
- AIMessage - AI 消息组件(来自
@/registry/wuhan/composed/message) - UserMessage - 用户消息组件(来自
@/registry/wuhan/composed/message) - AvatarHeader - 头像头部组件(来自
@/registry/wuhan/composed/avatar-header)
这些组件又基于原语组件构建:
- MessageAIPrimitive - AI 消息容器原语
- MessageUserPrimitive - 用户消息容器原语
- AvatarHeaderPrimitive - 头像头部原语
如果需要更深入的定制,可以直接使用 AIMessage、UserMessage 和 AvatarHeader 组件。
带头像的消息列表
import { MessageList } from "@/registry/wuhan/composed/message/message-list";
function MessageListWithAvatar() {
const messages = [
{
id: "1",
role: "user",
content: "你好,我想咨询一下产品",
avatar: {
src: "https://example.com/avatar.jpg",
name: "张三",
time: "10:30",
},
},
{
id: "2",
role: "ai",
content: "你好!很高兴为您服务。",
avatar: {
src: "https://example.com/ai-avatar.jpg",
name: "智能客服",
time: "10:31",
},
},
];
return <MessageList messages={messages} />;
}使用图标头像
import { MessageList } from "@/registry/wuhan/composed/message/message-list";
import { User, Bot } from "lucide-react";
function MessageListWithIconAvatar() {
const messages = [
{
id: "1",
role: "user",
content: "你好!",
avatar: {
icon: <User className="w-5 h-5" />,
name: "访客",
time: "12:00",
},
},
{
id: "2",
role: "ai",
content: "你好!有什么可以帮助你的?",
avatar: {
icon: <Bot className="w-5 h-5" />,
name: "AI 助手",
time: "12:01",
},
},
];
return <MessageList messages={messages} />;
}不同尺寸头像
import { MessageList } from "@/registry/wuhan/composed/message/message-list";
import { User } from "lucide-react";
function MessageListAvatarSizes() {
const messages = [
{
id: "1",
role: "user",
content: "小头像",
avatar: {
icon: <User className="w-3 h-3" />,
size: "small",
name: "小",
time: "12:00",
},
},
{
id: "2",
role: "ai",
content: "默认尺寸头像",
avatar: {
icon: <Bot className="w-5 h-5" />,
size: "default",
name: "默认",
time: "12:01",
},
},
{
id: "3",
role: "user",
content: "大头像",
avatar: {
icon: <User className="w-7 h-7" />,
size: "large",
name: "大",
time: "12:02",
},
},
];
return <MessageList messages={messages} />;
}自定义生成中状态
import { MessageList, type AIMessageItem } from "@/registry/wuhan/composed/message/message-list";
import { LoadingDots } from "@/registry/wuhan/blocks/message/message-01";
function CustomGeneratingDemo() {
const [messages, setMessages] = useState<MessageItem[]>([
{ id: "1", role: "user", content: "请帮我分析这段代码" },
]);
const addAiMessage = () => {
const newMessage: AIMessageItem = {
id: Date.now().toString(),
role: "ai",
content: "",
status: "generating",
generatingContent: (
<div className="flex items-center gap-2 text-[var(--Text-text-secondary)]">
<LoadingDots />
<span>正在分析...</span>
</div>
),
};
setMessages([...messages, newMessage]);
// 模拟生成完成
setTimeout(() => {
setMessages((prev) =>
prev.map((msg) =>
msg.id === newMessage.id
? { ...msg, status: "idle", content: "分析完成!这是一段很长的分析结果..." }
: msg
)
);
}, 2000);
};
return (
<div className="space-y-4">
<MessageList messages={messages} />
<button onClick={addAiMessage}>添加 AI 消息</button>
</div>
);
}可重试的错误状态
import { MessageList, type AIMessageItem } from "@/registry/wuhan/composed/message/message-list";
import { Button } from "@/registry/wuhan/ui/button";
import { RefreshCw } from "lucide-react";
function RetryableMessageList() {
const [messages, setMessages] = useState<(MessageItem | AIMessageItem)[]>([
{ id: "1", role: "user", content: "请帮我查询数据" },
]);
const handleRetry = (messageId: string) => {
setMessages((prev) =>
prev.map((msg) =>
msg.id === messageId
? { ...msg, status: "generating", content: "", errorContent: undefined }
: msg
)
);
// 模拟重新请求
setTimeout(() => {
setMessages((prev) =>
prev.map((msg) =>
msg.id === messageId
? { ...msg, status: "idle", content: "数据查询成功!" }
: msg
)
);
}, 1500);
};
const addFailedMessage = () => {
const newMessage: AIMessageItem = {
id: Date.now().toString(),
role: "ai",
content: "",
status: "failed",
errorContent: (
<div className="flex flex-col gap-2 p-3 bg-[var(--Container-bg-error-light)] rounded-lg">
<p className="text-[var(--Text-text-error)]">请求失败,请稍后重试</p>
<Button
variant="outline"
size="sm"
onClick={() => handleRetry(newMessage.id)}
>
<RefreshCw className="w-4 h-4 mr-1" />
重试
</Button>
</div>
),
};
setMessages([...messages, newMessage]);
};
return (
<div className="space-y-4">
<MessageList messages={messages} />
<button onClick={addFailedMessage}>添加失败消息</button>
</div>
);
}完整的聊天应用
import { useState } from "react";
import {
MessageList,
type MessageItem,
type AIMessageItem,
} from "@/registry/wuhan/composed/message/message-list";
import { Button } from "@/registry/wuhan/ui/button";
import { Send } from "lucide-react";
interface ChatMessage extends MessageItem {
status?: "idle" | "generating" | "failed";
}
export function ChatApp() {
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [inputValue, setInputValue] = useState("");
const handleSend = () => {
if (!inputValue.trim()) return;
// 添加用户消息
const userMsg: ChatMessage = {
id: Date.now().toString(),
role: "user",
content: inputValue,
avatar: {
src: "https://api.dicebear.com/7.x/avataaars/svg?seed=user1",
name: "用户",
time: new Date().toLocaleTimeString("zh-CN", {
hour: "2-digit",
minute: "2-digit",
}),
},
};
setMessages((prev) => [...prev, userMsg]);
setInputValue("");
// 模拟 AI 回复
setTimeout(() => {
const aiMsg: ChatMessage = {
id: (Date.now() + 1).toString(),
role: "ai",
content: "",
status: "generating",
avatar: {
src: "https://api.dicebear.com/7.x/bottts/svg?seed=ai",
name: "AI 助手",
time: new Date().toLocaleTimeString("zh-CN", {
hour: "2-digit",
minute: "2-digit",
}),
},
};
setMessages((prev) => [...prev, aiMsg]);
// 模拟生成完成
setTimeout(() => {
setMessages((prev) =>
prev.map((msg) =>
msg.id === aiMsg.id
? { ...msg, status: "idle", content: "这是 AI 的回复!" }
: msg
)
);
}, 2000);
}, 500);
};
return (
<div className="flex flex-col h-[600px] border rounded-lg overflow-hidden">
{/* 消息区域 */}
<div className="flex-1 overflow-hidden">
<MessageList
messages={messages}
onMessageClick={(msg) => console.log("点击:", msg.id)}
/>
</div>
{/* 输入区域 */}
<div className="border-t p-4 bg-[var(--Container-bg-neutral)]">
<div className="flex gap-2">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleSend()}
placeholder="输入消息..."
className="flex-1 px-4 py-2 border rounded-lg"
/>
<Button onClick={handleSend}>
<Send className="w-4 h-4 mr-1" />
发送
</Button>
</div>
</div>
</div>
);
}与原语组件的关系
MessageList 基于以下组件构建:
- AIMessage - AI 消息组件(来自
@/registry/wuhan/composed/message) - UserMessage - 用户消息组件(来自
@/registry/wuhan/composed/message) - AvatarHeader - 头像头部组件(来自
@/registry/wuhan/composed/avatar-header)
这些组件又基于原语组件构建:
- MessageAIPrimitive - AI 消息容器原语
- MessageUserPrimitive - 用户消息容器原语
- AvatarHeaderPrimitive - 头像头部原语
如果需要更深入的定制,可以直接使用 AIMessage、UserMessage 和 AvatarHeader 组件。