Skip to main content
AI Elements 是一个基于 shadcn/ui 的可组合组件库,专为 AI 聊天界面构建。诸如 ConversationMessageToolReasoningPromptInput 等组件设计为可直接放入任何 React 项目,并通过最少的胶水代码连接到 stream.messages
克隆并运行 完整的 AI Elements 示例,以在实际项目中查看工具调用渲染、推理显示、消息流式传输等功能。

工作原理

  1. 以源文件形式安装组件: AI Elements 通过 CLI 提供,可将组件直接添加到您的项目中(类似于 shadcn/ui 注册表风格)
  2. 将消息映射到组件: 遍历 stream.messages,将 HumanMessage 实例渲染为用户气泡,将 AIMessage 实例渲染为助手回复
  3. 组合更丰富的 UI: 将工具调用包裹在 <Tool> 中,推理内容包裹在 <Reasoning> 中,所有内容包裹在 <Conversation> 中以管理滚动

安装

通过 CLI 安装 AI Elements 组件。它们将以可编辑的源文件形式添加到您的项目中:
npm install @langchain/react @ai-elements/react
npx ai-elements@latest add conversation message prompt-input tool reasoning suggestion

连接 useStream

直接从 stream.messages 渲染 AI Elements 组件。每个 LangChain BaseMessage 都映射到一个组件:
import { useStream } from "@langchain/react";
import { HumanMessage, AIMessage } from "@langchain/core/messages";

import {
  Conversation,
  ConversationContent,
  ConversationScrollButton,
} from "@/components/ai-elements/conversation";
import {
  Message,
  MessageContent,
  MessageResponse,
} from "@/components/ai-elements/message";
import {
  Tool,
  ToolHeader,
  ToolContent,
  ToolInput,
  ToolOutput,
} from "@/components/ai-elements/tool";
import {
  Reasoning,
  ReasoningTrigger,
  ReasoningContent,
} from "@/components/ai-elements/reasoning";
import {
  PromptInput,
  PromptInputBody,
  PromptInputTextarea,
  PromptInputFooter,
  PromptInputSubmit,
} from "@/components/ai-elements/prompt-input";

export function Chat() {
  const stream = useStream({
    apiUrl: "http://localhost:2024",
    assistantId: "agent",
  });

  return (
    <div className="flex flex-col h-dvh">
      <Conversation className="flex-1">
        <ConversationContent>
          {stream.messages.map((msg, i) => {
            if (HumanMessage.isInstance(msg)) {
              return (
                <Message key={i} from="user">
                  <MessageContent>{msg.content as string}</MessageContent>
                </Message>
              );
            }
            if (AIMessage.isInstance(msg)) {
              return (
                <div key={i}>
                  {/* 推理块(当模型发出思考令牌时显示) */}
                  <Reasoning>
                    <ReasoningTrigger />
                    <ReasoningContent>{getReasoningText(msg)}</ReasoningContent>
                  </Reasoning>

                  {/* 内联工具调用,带有输入/输出显示 */}
                  {getToolCalls(msg).map((tc) => (
                    <Tool key={tc.id} defaultOpen>
                      <ToolHeader type={`tool-${tc.name}`} state={tc.state} />
                      <ToolContent>
                        <ToolInput input={tc.args} />
                        {tc.output && (
                          <ToolOutput output={tc.output} errorText={undefined} />
                        )}
                      </ToolContent>
                    </Tool>
                  ))}

                  {/* 流式文本回复 */}
                  <Message from="assistant">
                    <MessageContent>
                      <MessageResponse>{getTextContent(msg)}</MessageResponse>
                    </MessageContent>
                  </Message>
                </div>
              );
            }
          })}
        </ConversationContent>
        <ConversationScrollButton />
      </Conversation>

      <PromptInput
        onSubmit={({ text }) =>
          stream.submit({ messages: [{ type: "human", content: text }] })
        }
      >
        <PromptInputBody>
          <PromptInputTextarea placeholder="问我点什么..." />
        </PromptInputBody>
        <PromptInputFooter>
          <PromptInputSubmit
            status={stream.isLoading ? "streaming" : "ready"}
          />
        </PromptInputFooter>
      </PromptInput>
    </div>
  );
}

最佳实践

  • 自由编辑源文件: 组件以项目内文件形式提供,而非外部包依赖,因此您可以修改任何内容而无需分叉
  • 使用 MessageResponse 处理流式传输: 它能正确处理流式传输的部分令牌;避免在流式传输期间直接渲染原始的 msg.content
  • 使用 Conversation 包裹: Conversation 组件管理滚动行为,确保新消息自动滚动到视图中
  • 使用 isInstance 进行类型守卫: 使用 HumanMessage.isInstance(msg)AIMessage.isInstance(msg) 而不是检查 msg.getType(),以获得正确的 TypeScript 类型收窄