气泡/容器
Message
Message component for displaying AI and user messages in chat interfaces
"use client";
import { useState } from "react";
import {
ComposedMessageList,
MessageItem,
} from "@/components/wuhan/examples/message/message-composed-demo";
export function MessageDemo() {
const [messages, setMessages] = useState<MessageItem[]>([
{
id: "1",
role: "user",
content: "你好,我想了解一下你们的产品",
feedback: {
onCopy: () => {},
},
},
{
id: "2",
role: "assistant",
content: "你好!很高兴为您介绍我们的产品。请问您对哪个方面比较感兴趣?",
feedback: {
onCopy: () => {},
},
},
{
id: "3",
role: "user",
content: "能详细介绍一下核心功能吗?",
feedback: {
onCopy: () => {},
},
},
{
id: "4",
role: "assistant",
content: (
<div className="space-y-2">
我们的产品主要有以下核心功能:
<ul className="list-disc list-inside space-y-1 ml-2 mb-0">
<li>智能对话系统</li>
<li>多模态内容支持</li>
<li>实时协作功能</li>
</ul>
</div>
),
feedback: {
onCopy: () => {},
},
},
]);
const handleFeedbackChange = (
id: string,
feedback: Partial<MessageItem["feedback"]>,
) => {
setMessages((prev) =>
prev.map((msg) =>
msg.id === id
? {
...msg,
feedback: {
...msg.feedback,
...feedback,
},
}
: msg,
),
);
};
return (
<ComposedMessageList
messages={messages}
onFeedbackChange={handleFeedbackChange}
className="max-w-full"
/>
);
}
Message 组件提供了用于显示 AI 消息和用户消息的完整样式组件,支持消息状态管理、反馈区域、附件展示等功能,适用于聊天界面、对话系统、AI 助手等场景。
概述
- 两种消息类型:AIMessage(AI 消息)和 UserMessage(用户消息),样式自动区分
- 状态管理:AI 消息支持 idle、generating、failed 三种状态
- 内容渲染:根据状态智能渲染内容,生成中显示 loading,失败显示错误信息
- 反馈区域:内置反馈插槽,方便添加点赞、复制等操作
- 样式隔离:消息样式不受文档框架影响,保持一致性
- 无障碍访问:支持 ARIA live regions,屏幕阅读器友好
快速开始
import { AIMessage, UserMessage } from "@/registry/wuhan/composed/message";
export function Example() {
return (
<div className="space-y-4">
<UserMessage>
请帮我解释一下 React Hooks 的工作原理
</UserMessage>
<AIMessage
status="idle"
content="React Hooks 是 React 16.8 引入的新特性..."
/>
</div>
);
}特性
- AI 消息状态:idle(正常)、generating(生成中)、failed(失败)三种状态
- 智能内容渲染:根据状态自动渲染 content 或 children
- 用户消息:简洁的用户消息样式,带背景色区分
- 反馈支持:通过 feedback 属性添加操作按钮
- 附件支持:可在消息上方展示附件(图片、文件等)
- 错误处理:失败状态自动显示错误信息
安装
代码演示
基本
基础的消息展示,包含用户消息和 AI 消息。
这是一条用户消息
这是一条 AI 消息
"use client";
import {
AIMessage,
UserMessage,
} from "@/components/composed/message/message";
export function MessageDefault() {
return (
<div className="flex flex-col gap-4 items-center w-full">
<UserMessage>这是一条用户消息</UserMessage>
<AIMessage>这是一条 AI 消息</AIMessage>
</div>
);
}
状态的消息
展示 AI 消息的不同状态,包括生成中状态和生成失败状态。
正常状态
这是一条正常的 AI 消息,显示完整的内容。
生成中状态(使用原语组件)
正在思考中...
生成中状态(自定义)
原始内容(不会显示)正在生成回复,请稍候...
生成失败状态(默认)
生成失败,请稍后重试
生成失败状态(使用原语组件)
生成失败,请稍后重试
生成失败状态(带重试,使用原语组件)
生成失败,请稍后重试
交互式演示
这是一条正常的 AI 消息回复。
"use client";
import { useState } from "react";
import { AIMessage } from "@/components/composed/message/message";
import {
LoadingDots,
MessageGeneratingPrimitive,
MessageFailedPrimitive,
} from "@/components/wuhan/blocks/message/message-01";
import { Button } from "@/components/ui/button";
import { AlertCircle, RefreshCw } from "lucide-react";
export function MessageWithStatus() {
const [status, setStatus] = useState<"idle" | "generating" | "failed">(
"idle",
);
const handleGenerate = () => {
setStatus("generating");
// 模拟生成过程
setTimeout(() => {
setStatus("idle");
}, 2000);
};
const handleFail = () => {
setStatus("failed");
};
const handleRetry = () => {
setStatus("generating");
setTimeout(() => {
setStatus("idle");
}, 2000);
};
return (
<div className="flex flex-col gap-6 w-full h-full max-w-2xl">
{/* 示例 1: 正常状态 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
正常状态
</h3>
<div className="flex justify-start w-full">
<AIMessage status="idle">
这是一条正常的 AI 消息,显示完整的内容。
</AIMessage>
</div>
</div>
{/* 示例 2: 生成中状态 - 使用原语组件 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
生成中状态(使用原语组件)
</h3>
<div className="flex justify-start w-full">
<AIMessage
status="generating"
generatingContent={
<MessageGeneratingPrimitive
indicator={<LoadingDots />}
text="正在思考中..."
/>
}
/>
</div>
</div>
{/* 示例 3: 生成中状态 - 自定义内容 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
生成中状态(自定义)
</h3>
<div className="flex justify-start w-full">
<AIMessage
status="generating"
generatingContent={
<span className="text-[var(--Text-text-secondary)]">
正在生成回复,请稍候...
</span>
}
>
原始内容(不会显示)
</AIMessage>
</div>
</div>
{/* 示例 4: 生成失败状态 - 使用默认错误消息 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
生成失败状态(默认)
</h3>
<div className="flex justify-start w-full">
<AIMessage status="failed" errorMessage="生成失败,请稍后重试">
原始内容(不会显示)
</AIMessage>
</div>
</div>
{/* 示例 5: 生成失败状态 - 使用原语组件 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
生成失败状态(使用原语组件)
</h3>
<div className="flex justify-start w-full">
<AIMessage
status="failed"
errorContent={
<MessageFailedPrimitive
icon={
<AlertCircle className="size-4 text-[var(--Text-text-error)]" />
}
message="生成失败,请稍后重试"
/>
}
>
原始内容(不会显示)
</AIMessage>
</div>
</div>
{/* 示例 6: 生成失败状态 - 带重试按钮(使用原语组件) */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)]">
生成失败状态(带重试,使用原语组件)
</h3>
<div className="flex justify-start w-full">
<AIMessage
status="failed"
errorContent={
<MessageFailedPrimitive
icon={
<AlertCircle className="size-4 text-[var(--Text-text-error)]" />
}
message="生成失败,请稍后重试"
actions={
<Button
variant="outline"
size="sm"
onClick={handleRetry}
className="w-fit"
>
<RefreshCw className="size-3 mr-1" />
重试
</Button>
}
/>
}
>
原始内容(不会显示)
</AIMessage>
</div>
</div>
{/* 示例 7: 交互式演示 */}
<div className="space-y-4 p-4 border rounded-lg">
<h3 className="text-sm font-medium">交互式演示</h3>
<div className="flex justify-start w-full">
<AIMessage
status={status}
generatingContent={
<MessageGeneratingPrimitive
indicator={<LoadingDots />}
text="正在生成回复..."
/>
}
errorMessage="生成失败,请稍后重试"
errorContent={
<MessageFailedPrimitive
icon={
<AlertCircle className="size-4 text-[var(--Text-text-error)]" />
}
message="生成失败"
actions={
<Button
variant="outline"
size="sm"
onClick={handleRetry}
className="w-fit"
>
<RefreshCw className="size-3 mr-1" />
重试
</Button>
}
/>
}
>
{status === "idle" && "这是一条正常的 AI 消息回复。"}
</AIMessage>
</div>
<div className="flex gap-2">
<Button onClick={handleGenerate} disabled={status === "generating"}>
模拟生成
</Button>
<Button
onClick={handleFail}
variant="destructive"
disabled={status === "generating"}
>
模拟失败
</Button>
</div>
</div>
</div>
);
}
消息反馈
带反馈区域的消息,可以在消息下方添加操作按钮。
请帮我解释一下 React Hooks
React Hooks 是 React 16.8 引入的新特性,允许你在函数组件中使用状态和其他 React 特性。常用的 Hooks 包括 useState、useEffect、useContext 等。
"use client";
import {
AIMessage,
UserMessage,
} from "@/components/composed/message/message";
import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { ThumbsUp, ThumbsDown, Copy } from "lucide-react";
import { cn } from "@/lib/utils";
export function MessageWithFeedback() {
return (
<div className="flex flex-col gap-4 w-full">
<div className="flex justify-end w-full">
<UserMessage>请帮我解释一下 React Hooks</UserMessage>
</div>
<div className="flex justify-start w-full">
<AIMessage
feedback={
<div className="flex items-center gap-[var(--Gap-gap-xs)]">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-6 !px-1 cursor-pointer"
>
<ThumbsUp
className={cn(
"size-4",
"text-[var(--Text-text-secondary)]",
)}
/>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>点赞</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-6 !px-1 cursor-pointer"
>
<ThumbsDown
className={cn(
"size-4",
"text-[var(--Text-text-secondary)]",
)}
/>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>踩</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-6 !px-1 cursor-pointer"
>
<Copy
className={cn(
"size-4",
"text-[var(--Text-text-secondary)]",
)}
/>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>复制</p>
</TooltipContent>
</Tooltip>
</div>
}
>
React Hooks 是 React 16.8
引入的新特性,允许你在函数组件中使用状态和其他 React 特性。常用的
Hooks 包括 useState、useEffect、useContext 等。
</AIMessage>
</div>
</div>
);
}
显示附件
展示带附件的消息,附件显示在消息上方。
这是 AI 消息
这是用户消息,上方有附件
"use client";
import {
UserMessage,
AIMessage,
} from "@/components/composed/message/message";
import { AttachmentListComposed } from "@/components/composed/attachment-list/attachment-list";
import { FileText } from "lucide-react";
export function MessageWithAttachment() {
return (
<div className="flex flex-col gap-4 w-full">
{/* AI 消息:带附件的消息 */}
<div className="flex justify-start w-full">
<div className="w-fit max-w-full flex flex-col gap-2">
<AIMessage>这是 AI 消息</AIMessage>
</div>
</div>
{/* 用户消息:带附件的消息 */}
<div className="flex justify-end w-full">
<div className="w-fit max-w-full flex flex-col gap-2">
<AttachmentListComposed
items={[
{
id: "file-1",
name: "文件名称.pdf",
fileType: "PDF",
fileSize: "14kb",
icon: <FileText className="size-4" />,
},
]}
/>
<UserMessage>这是用户消息,上方有附件</UserMessage>
</div>
</div>
</div>
);
}
流式消息渲染
模拟流式响应,逐字显示消息内容。
"use client";
import { AIMessage } from "@/components/composed/message/message";
import { useState, useEffect } from "react";
export function MessageStreaming() {
const [content, setContent] = useState("");
const [status, setStatus] = useState<"generating" | "idle">("generating");
useEffect(() => {
// 模拟流式响应
const fullText =
"这是一段流式生成的文本。React 是一个用于构建用户界面的 JavaScript 库,由 Meta(原 Facebook)开发和维护。它采用组件化的开发方式,使得代码更易于维护和复用。";
let index = 0;
const interval = setInterval(() => {
if (index < fullText.length) {
setContent(fullText.slice(0, index + 1));
index++;
} else {
setStatus("idle");
clearInterval(interval);
}
}, 50);
return () => clearInterval(interval);
}, []);
return (
<div className="h-[300px] w-full border rounded-lg overflow-hidden">
<AIMessage
status={status}
generatingContent={status === "generating" ? content : undefined}
>
{status === "idle" ? content : null}
</AIMessage>
</div>
);
}
带代码高亮
消息中包含代码块的示例。
这是一个 React 组件示例:
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}"use client";
import { AIMessage } from "@/components/composed/message/message";
export function MessageWithCode() {
return (
<AIMessage status="idle">
<div className="space-y-2">
<p>这是一个 React 组件示例:</p>
<pre className="bg-muted p-3 rounded-md overflow-x-auto">
<code className="text-sm">{`function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}`}</code>
</pre>
</div>
</AIMessage>
);
}
可重试的错误消息
失败状态的消息支持重试功能。
消息生成失败,请重试
"use client";
import { AIMessage } from "@/components/composed/message/message";
import { RefreshCw } from "lucide-react";
import { useState } from "react";
export function MessageRetryable() {
const [status, setStatus] = useState<"failed" | "generating" | "idle">(
"failed",
);
const [content, setContent] = useState("");
const handleRetry = () => {
setStatus("generating");
// 模拟重试逻辑
setTimeout(() => {
setStatus("idle");
setContent(
"重试后成功生成的内容:React Hooks 是 React 16.8 引入的新特性,允许你在不编写 class 的情况下使用 state 和其他 React 特性。",
);
}, 2000);
};
return (
<div className="h-[300px] w-full border rounded-lg overflow-hidden">
<AIMessage
status={status}
errorContent={
status === "failed" ? (
<div className="space-y-3">
<p className="text-destructive">消息生成失败,请重试</p>
<button
onClick={handleRetry}
className="flex items-center gap-2 px-3 py-1.5 bg-primary text-primary-foreground rounded hover:bg-primary/90 transition-colors"
>
<RefreshCw className="w-4 h-4" />
重试
</button>
</div>
) : undefined
}
>
{status === "idle" ? content : null}
</AIMessage>
</div>
);
}
API
AIMessage
AI 消息组件,支持状态管理和内容渲染。
Props
| Prop | Type | Default | Description |
|---|---|---|---|
status | "idle" | "generating" | "failed" | - | AI 消息的生成状态(必填) |
content | ReactNode | - | 消息内容,当 status 为 idle 时显示 |
children | ReactNode | - | 替代 content 的内容,优先级更高 |
feedback | ReactNode | - | 反馈区域内容(如点赞、复制按钮) |
className | string | - | 额外的样式类名 |
Example
import { AIMessage } from "@/registry/wuhan/composed/message";
import { ThumbsUp, ThumbsDown, Copy } from "lucide-react";
function ChatMessage() {
const [status, setStatus] = React.useState<"idle" | "generating" | "failed">("generating");
const [content, setContent] = React.useState("");
return (
<div className="space-y-4">
{/* 生成中状态 */}
<AIMessage status="generating" />
{/* 正常状态,显示内容 */}
<AIMessage
status="idle"
content="这是 AI 的回复内容,支持 Markdown 格式..."
feedback={
<div className="flex gap-2 mt-2">
<button className="p-1 hover:bg-muted rounded">
<ThumbsUp className="w-4 h-4" />
</button>
<button className="p-1 hover:bg-muted rounded">
<ThumbsDown className="w-4 h-4" />
</button>
<button className="p-1 hover:bg-muted rounded">
<Copy className="w-4 h-4" />
</button>
</div>
}
/>
{/* 失败状态 */}
<AIMessage status="failed" />
{/* 使用 children 而非 content */}
<AIMessage status="idle">
<div className="prose">
<h3>自定义内容</h3>
<p>可以使用任意 React 组件作为内容</p>
</div>
</AIMessage>
</div>
);
}UserMessage
用户消息组件,简洁的样式设计。
Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | 消息内容(必填) |
className | string | - | 额外的样式类名 |
Example
import { UserMessage } from "@/registry/wuhan/composed/message";
function UserChatMessage() {
return (
<div className="space-y-4">
<UserMessage>
这是用户发送的消息
</UserMessage>
<UserMessage className="max-w-md">
用户消息支持自定义样式
</UserMessage>
</div>
);
}AIMessageStatus
AI 消息状态类型定义。
type AIMessageStatus =
| "idle" // 正常状态,显示完整内容
| "generating" // 生成中,显示 loading 动画
| "failed"; // 生成失败,显示错误提示使用场景
- AI 聊天应用:ChatGPT、Claude 等 AI 对话界面
- 客服系统:智能客服、在线咨询等场景
- 文档问答:基于文档的问答系统
- 代码助手:编程辅助、代码生成等工具
- 教学助手:在线学习、答疑解惑等场景
- 协作工具:团队沟通、任务分配等应用
最佳实践
- 状态管理:使用状态机管理消息的生成流程(generating → idle/failed)
- 错误处理:生成失败时提供重试按钮,提升用户体验
- 内容格式:AI 消息内容建议使用 Markdown 渲染,支持富文本
- 反馈收集:在 feedback 区域添加点赞、点踩,收集用户反馈
- 流式渲染:配合 SSE 或 WebSocket 实现流式内容更新
- 无障碍性:组件已内置 ARIA live regions,确保屏幕阅读器可访问
注意事项
children优先级高于content,两者同时存在时使用childrenstatus="generating"时,content和children会被忽略status="failed"时,显示默认错误信息,可通过children自定义- UserMessage 组件样式相对简单,主要用于区分用户和 AI 消息
- 建议为长消息添加滚动或折叠功能,避免页面过长
原语组件
Message 基于以下原语组件构建:
MessageAIPrimitive- AI 消息容器原语MessageUserPrimitive- 用户消息容器原语
原语组件提供了基础的样式和结构,可以在 registry/wuhan/blocks/message/message-01.tsx 中找到。
样式定制
组件使用 Tailwind CSS,可以通过以下方式定制:
// 自定义 AI 消息样式
<AIMessage
status="idle"
content="内容"
className="border-l-4 border-primary pl-4"
/>
// 自定义用户消息样式
<UserMessage className="bg-gradient-to-r from-blue-500 to-purple-500 text-white">
自定义背景的用户消息
</UserMessage>