# Assistant UI - The composable AI chat UI toolkit

上一篇 CopilotKit - The frontend stack for Agent 介绍了如何把 Agent 接到产品里—读取页面上下文、调用前端工具、渲染 React 组件、暂停等待用户审批。CopilotKit 是产品的智能助手。

当你的核心需求变成构建精细、可组合的聊天界面 时,Assistant UI (opens new window) 会更合适。它提供 Thread、Message、Composer 等聊天原语,以及连接 Vercel AI SDK、LangGraph、AG-UI 或自定义后端的 Runtime 层,让你像搭积木一样组装 ChatGPT 风格的对话体验。

Assistant UI 的核心能力如下:

产品能力 说明
UI Primitives 无样式、可访问的 Thread、Message、Composer 等原语,交互细节(自动滚动、流式渲染、分支切换)内置
Elements 基于 shadcn/ui 的预置聊天组件,源码复制到项目,样式完全可控
Runtime 连接 UI 与后端的会话状态层,负责消息、Composer、运行生命周期与分支
AI SDK v7 集成 通过 @assistant-ui/ai-sdkuseChatRuntime 对接 Vercel AI SDK v7 流式对话
Tool UI 为工具调用注册自定义 React 渲染器,展示 loading、结果与交互状态
ThreadList 多会话列表:创建、切换、归档、重命名
Assistant Cloud 托管线程持久化、历史记录与用户授权
CLI 脚手架创建项目、向现有项目添加组件、升级与 codemod

Assistant UI 当前版本 0.15.x,AI SDK 集成面向 v7(ai@^7@ai-sdk/react@^4)。

# 架构

从前端到后端,Assistant UI 可以理解为三个部分:UI Primitives / ElementsRuntimeAdapters / Backend。更多详情,请参考 Architecture (opens new window)

# 三层职责

UI 层(Primitives / Elements / Hooks)

渲染线程、消息、输入框、附件、建议等界面。原语层(ThreadPrimitiveMessagePrimitiveComposerPrimitive)不带样式,行为类似 Radix UI;Elements 是在原语之上用 shadcn/ui 做好的完整组件(如 Thread),源码通过 CLI 复制到项目。

UI 通过 Runtime 上下文读写状态,不直接调用 LLM API。useAui 返回当前作用域的 AssistantClientuseAuiState 用选择器订阅线程、Composer、消息等切片状态;useAuiEvent 订阅 Runtime 事件。这些 Hooks 是 UI 层定制行为的入口。

Runtime 层

会话状态的边界。AssistantRuntimeProvider 注入 Runtime,其下所有原语和 Hooks 共享同一份线程状态。不同 Runtime 适配不同后端对消息、分支、工具调用的组织方式。

Adapters / Backend

实际产生模型输出和应用行为的地方。AI SDK 适配器把 streamText 的 UI Message 流映射进 Runtime;LangGraph、AG-UI 等各有专属适配器;Assistant Cloud 或自定义 persistence adapter 负责线程与消息持久化。

# 请求过程

以 Next.js + AI SDK v7 + OpenAI 为主线,用户发送消息后,Runtime 把本地状态转成 API 请求,后端流式返回,UI 逐段更新消息部件。

一句话概括:用户说话 → Runtime 组装请求 POST 到 API Route → streamText 流式生成 → Runtime 把 UI Message 部件写回 Thread → 原语渲染。

# 集成 Assistant UI

如果想快速搭脚手架,或向已有项目追加组件,可以用官方 CLI。更多详情,请参考 CLI 文档 (opens new window)

CLI 当前版本 assistant-ui@0.0.117

# 创建新项目

create 从模板或示例生成完整项目:

# 默认模板(Vercel AI SDK)
$ npx assistant-ui@latest create my-app

# 指定 AI SDK v7 示例
$ npx assistant-ui@latest create my-app -e with-ai-sdk-v7

# Cloud 持久化模板
$ npx assistant-ui@latest create my-app -t cloud
1
2
3
4
5
6
7
8

常用模板( -t template):

Name 模板 说明
default Default 默认模板,内置 Vercel AI SDK
minimal Minimal 最小起点
cloud Cloud Cloud 持久化 Starter
cloud-clerk Cloud + Clerk Cloud 持久化 + Clerk 鉴权
langchain LangChain LangGraph + react-langchain 适配器
mcp MCP MCP Tools + MCP Apps Renderer
eve Eve Eve Agent + Next.js

常用示例(-e example):

Name 示例 说明
with-ag-ui AG-UI AG-UI 协议集成
with-google-adk Google ADK Google ADK Agent 集成
with-ai-sdk-v7 AI SDK v7 Vercel AI SDK v7 集成
with-eve Eve Eve Agent 集成
with-artifacts Artifacts HTML Artifact 渲染与实时预览
with-assistant-transport Assistant Transport 通过 Assistant Transport 对接自定义后端
with-chain-of-thought Chain of Thought Chain of Thought、Tool Call 与来源引用
with-cloud Cloud Example Assistant Cloud 持久化
with-custom-thread-list Custom Thread List 自定义 ThreadList UI
with-elevenlabs-conversational ElevenLabs Conversational ElevenLabs 实时语音对话
with-elevenlabs-scribe ElevenLabs Scribe ElevenLabs 语音转写
with-livekit LiveKit Voice LiveKit 实时语音
with-expo Expo Expo / React Native
with-interactables Interactables AI 驱动的交互式 UI 组件
with-external-store External Store External Message Store
with-ffmpeg FFmpeg FFmpeg 视频处理工具
with-langgraph LangGraph Example LangGraph Agent 与自定义工具
with-react-hook-form React Hook Form React Hook Form 集成
with-react-ink React Ink React Ink 终端聊天界面
with-react-router React Router React Router v7 集成
with-tanstack TanStack TanStack Start 集成
with-resumable-stream Resumable Stream 页面刷新后可恢复的 LLM Stream
with-openui OpenUI OpenUI Generative UI 集成

# 向现有项目添加

已有 package.json 的 Next.js 项目,用 init 完成首次配置:

$ npx assistant-ui@latest init
1

init 会检测项目、通过 shadcn registry 安装 quick-start 组件、配置 TypeScript paths。

之后按需追加单个组件:

# 基础 Thread
$ npx assistant-ui@latest add thread

# 多会话 ThreadList
$ npx assistant-ui@latest add thread-list

# 浮窗形态
$ npx assistant-ui@latest add assistant-modal

# 一次添加多个
$ npx assistant-ui@latest add thread thread-list assistant-sidebar
1
2
3
4
5
6
7
8
9
10
11

# 升级与维护

# 添加组件
$ npx assistant-ui@latest add [component]

# 查看可更新包(dry run)
$ npx assistant-ui@latest update --dry

# 更新所有 @assistant-ui/* 包
$ npx assistant-ui@latest update

# 运行 breaking change codemod
$ npx assistant-ui@latest upgrade
1
2
3
4
5
6
7
8
9
10
11

CLI 添加的组件源码会直接写入项目,可以继续按需修改组件、样式与 Runtime 配置。

# 原语与 Hooks

完整 API 见 Primitives 概览 (opens new window)Hooks API Reference (opens new window)

# 核心原语一览

原语 功能
ThreadPrimitive 可滚动消息容器:自动滚动、空状态、建议、ViewportFooter
MessagePrimitive 单条消息渲染:按 role 展示 parts、附件、元数据
ComposerPrimitive 输入区:文本、发送、附件、语音听写
ActionBarPrimitive 消息操作:复制、重新生成、编辑、反馈
BranchPickerPrimitive 在多条 assistant 分支回复间切换
ThreadListPrimitive 多线程列表:创建、切换、归档
AssistantModalPrimitive 浮窗聊天面板
AttachmentPrimitive 附件渲染
SuggestionPrimitive 建议提示
ChainOfThoughtPrimitive 折叠展示推理步骤与工具调用
AuiIf 按 Runtime 状态条件渲染

# 原语详解

# ThreadPrimitive

线程容器,负责 Viewport 滚动、消息迭代与 Composer 锚点。典型结构:

import { ThreadPrimitive, MessagePrimitive, ComposerPrimitive, AuiIf } from "@assistant-ui/react";

<ThreadPrimitive.Root className="flex h-full flex-col">
  <ThreadPrimitive.Viewport turnAnchor="top" className="flex-1 overflow-y-auto">
    <AuiIf condition={(s) => s.thread.isEmpty}>
      <p>开始提问吧</p>
    </AuiIf>

    <ThreadPrimitive.Messages>
      {({ message }) => {
        if (message.role === "user") return <UserMessage />;
        return <AssistantMessage />;
      }}
    </ThreadPrimitive.Messages>

    <ThreadPrimitive.ViewportFooter className="sticky bottom-0">
      <ComposerPrimitive.Root>
        <ComposerPrimitive.Input placeholder="Ask anything..." />
        <ComposerPrimitive.Send>Send</ComposerPrimitive.Send>
      </ComposerPrimitive.Root>
    </ThreadPrimitive.ViewportFooter>
  </ThreadPrimitive.Viewport>
</ThreadPrimitive.Root>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

turnAnchor="top" 时,用户消息锚定在 Viewport 顶部,assistant 回复在下方展开,接近 ChatGPT 的阅读体验。autoScrollscrollToBottomOnRunStart 等控制滚动行为。

# MessagePrimitive

单条消息的渲染单元。MessagePrimitive.Parts 按 part 类型(text、tool-call、reasoning 等)渲染内容;配合 ActionBarPrimitiveBranchPickerPrimitive 实现复制、重新生成与分支切换。

<MessagePrimitive.Root className="flex justify-start">
  <div className="rounded-2xl bg-muted px-4 py-2.5">
    <MessagePrimitive.Parts />
  </div>
</MessagePrimitive.Root>
1
2
3
4
5

ActionBar 和 BranchPicker 必须放在 MessagePrimitive.Root 内部。

# ComposerPrimitive

消息输入区。处理 Enter 发送、空内容禁用、流式运行中禁用、附件与听写适配器。既可用于 Thread 底部的新消息 Composer,也可用于 MessagePrimitive 内的编辑模式。

<ComposerPrimitive.Root>
  <ComposerPrimitive.Input placeholder="输入消息…" />
  <ComposerPrimitive.Send />
  <ComposerPrimitive.Cancel />  {/* 运行中取消 */}
</ComposerPrimitive.Root>
1
2
3
4
5

# 核心 Hooks 一览

Hook 功能
useChatRuntime 创建 AI SDK v7 Runtime(推荐主线)
useAISDKRuntime 把已有 useChat 实例适配为 Runtime
useAui 获取 AssistantClient,调用 thread / composer / message 作用域方法
useAuiState 选择器订阅 Runtime 状态切片
useAuiEvent 订阅 assistant 事件(如 modelContext 更新)
useLangGraphRuntime LangGraph Cloud / SDK 集成
useAgUiRuntime AG-UI 协议 agent 集成
useLocalRuntime Runtime 内部管理状态,后端只需简单 fetch
useExternalStoreRuntime 消息状态放在 Redux / Zustand 等外部 store
useRemoteThreadListRuntime 自定义数据库的多线程列表

# Hook 详解

# useAui

返回当前上下文中的 AssistantClient,用于 主动触发行为 而非订阅状态:

import { useAui } from "@assistant-ui/react";

function SendHello() {
  const aui = useAui();

  return (
    <button
      onClick={() => {
        aui.composer().setText("Hello");
        aui.composer().send();
      }}
    >
      发送 Hello
    </button>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

常见调用:aui.thread().cancelRun() 取消生成;aui.threads().switchToThread(id) 切换线程;aui.threadListItem().initialize() 创建远程线程。

# useAuiState

用选择器订阅状态,只在选中切片变化时重渲染:

import { useAuiState } from "@assistant-ui/react";

function RunningIndicator() {
  const isRunning = useAuiState((s) => s.thread.isRunning);
  const messages = useAuiState((s) => s.thread.messages);

  if (!isRunning) return null;
  return <span>生成中…(共 {messages.length} 条消息)</span>;
}
1
2
3
4
5
6
7
8
9

注意:选择器应返回原始字段或稳定引用;不要返回每次新建的 object/array,否则会触发多余渲染。可能不存在的作用域用 s.optional. 读取。

# Runtime

Runtime 是 UI 原语与 AI 后端之间的 状态与行为边界。选型指南见 Picking a runtime (opens new window)

# 如何选择

场景 Runtime 说明
已用 Vercel AI SDK v7 useChatRuntime 本文主线;Streaming、tools、attachments 开箱即用
LangGraph agent useLangGraphRuntime 对接 LangGraph Cloud / SDK,支持子图事件与 UI messages
AG-UI 兼容 agent(含 CopilotKit 后端) useAgUiRuntime 消费 AG-UI 事件流:text、thinking、tool calls、state snapshots
消息在 Redux / Zustand 等外部 store useExternalStoreRuntime UI 读写你的 store,Runtime 只做适配
简单 fetch 到自己的 API,Runtime 自持状态 useLocalRuntime 后端只需返回 assistant-stream 或 Data Stream
多线程 + 自建数据库 useRemoteThreadListRuntime + adapter 线程元数据与消息 history 走自定义 API
托管持久化 useChatRuntime + Assistant Cloud 零 adapter 代码的 Cloud 线程同步

# AI SDK v7(主线)

useChatRuntime 封装了 useChatAssistantChatTransport 与消息格式转换。默认单线程、内存态;通过 adapters.history 或 Cloud 开启持久化。

常用选项:

const runtime = useChatRuntime({
  onThreadIdChange: (threadId) => {
    // 同步 URL query,例如 ?thread=xxx
  },
  joinStrategy: "none", //  consecutive assistant messages 不合并
  adapters: {
    attachments: myAttachmentAdapter,
    history: myHistoryAdapter,
  },
});
1
2
3
4
5
6
7
8
9
10

# 其他 Runtime 简述

LangGraph — 适合已有 LangGraph agent 图、需要 checkpoint、HITL 或多 agent 编排的场景。通过 @assistant-ui/react-langgraphuseLangGraphRuntime 接入。

AG-UI — 后端已 speak AG-UI 协议(例如 LangGraph JS + AG-UI adapter、CopilotKit Runtime 对外暴露 AG-UI)时使用 useAgUiRuntime,无需自己解析 SSE 事件格式。

External Store — 当你要把聊天消息与应用全局状态(如 Zustand store)合并管理,useExternalStoreRuntime 把 assistant-ui 的 Thread 视图绑到你的 store 读写函数上。

Local Runtime — 后端是一个简单 POST endpoint,返回 streaming 文本或 assistant-stream 协议;Runtime 在浏览器侧维护完整会话状态,适合原型或内网工具。

# Tools 与 Generative UI

Assistant UI 通过 toolkit 把工具定义、执行与 UI 渲染绑在一起。工具可在客户端执行(frontend tool)、仅渲染后端工具(backend render-only),或等待用户填表后再 addResult(human tool)。详见 Tool UI 文档 (opens new window)

# 后端工具 + 自定义 Tool UI

服务端在 streamTexttools 里定义工具;客户端用 defineToolkit 注册 仅 render 的 backend entry:

"use client";

import {
  AssistantRuntimeProvider,
  AuiConfig,
  Tools,
  defineToolkit,
} from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/ai-sdk";

const toolkit = defineToolkit({
  get_current_weather: {
    type: "backend",
    render: ({ args, result, status }) => {
      if (status.type === "running") {
        return <div>正在查询 {args.city} 的天气…</div>;
      }
      if (status.type === "incomplete" && status.reason === "error") {
        return <div>查询失败</div>;
      }
      return (
        <div className="rounded-lg border p-3">
          <p className="font-medium">{args.city}</p>
          <p>{result}</p>
        </div>
      );
    },
  },
});

export function ChatProvider({ children }: { children: React.ReactNode }) {
  const runtime = useChatRuntime();
  const config = AuiConfig({ tools: Tools({ toolkit }) });

  return (
    <AssistantRuntimeProvider runtime={runtime} config={config}>
      {children}
    </AssistantRuntimeProvider>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

status.type 常见值:running(执行中)、complete(成功)、incomplete(错误或未决)。AI SDK v7 还支持服务端 toolApproval 门控,assistant-ui 在 tool part 上暴露 approvalrespondToApproval,用于生产环境部署等人审批场景。

# 前端工具

客户端定义并执行的工具,通过 toolkit 的 execute + render 注册,AssistantChatTransport 会把 schema 序列化给后端,后端用 frontendTools(tools) 合并:

const toolkit = defineToolkit({
  highlightText: {
    description: "Highlight text on the page",
    parameters: z.object({ text: z.string() }),
    execute: async ({ text }) => {
      "use client";
      document.designMode = "on";
      window.find(text);
      return { found: true };
    },
    render: ({ args, status }) => {
      if (status.type === "running") return <span>高亮「{args.text}」…</span>;
      return <span>已高亮「{args.text}</span>;
    },
  },
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

若模型需要从 组件 vocabulary 组合 UI(而非一对一绑定 tool name),可进一步看 Generative UI 指南;本文不展开。

# 会话与持久化

默认情况下,消息只存在内存里,刷新即丢失。多会话与持久化有三条路径。

# ThreadList

ThreadListPrimitive(或 CLI 安装的 Thread + ThreadList Elements)提供会话列表 UI:新建、切换、归档、重命名。单线程应用只需 Thread;产品型聊天通常 ThreadList + Thread 并排。

$ npx assistant-ui@latest add thread-list
1

# Assistant Cloud

Assistant Cloud (opens new window) 是托管服务,提供线程持久化、自动标题、消息反馈与用户授权,无需自建数据库。

Cloud 适合快速上线、多租户线程隔离、不想维护 history 存储的团队。

# 自定义 Persistence Adapter

合规、自建数据库或要把线程与应用数据存在同一张表时,实现 RemoteThreadListAdapter(线程元数据)和 ThreadHistoryAdapter(消息 history)。

AI SDK v7 路径下,history adapter 必须实现 withFormat,让 useChatRuntimeUIMessage 编码为固定行形状 { id, parent_id, format, content }

const historyAdapter: ThreadHistoryAdapter = {
  async load() {
    return { headId: null, messages: [] };
  },
  async append() {},
  withFormat: (fmt) => ({
    async load() {
      const rows = await fetch("/api/history").then((r) => r.json());
      return { messages: rows.map(fmt.decode) };
    },
    async append(item) {
      await fetch("/api/history", {
        method: "POST",
        body: JSON.stringify({
          id: fmt.getId(item.message),
          parent_id: item.parentId,
          format: fmt.format,
          content: fmt.encode(item),
        }),
      });
    },
  }),
};

const runtime = useChatRuntime({ adapters: { history: historyAdapter } });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

多线程场景用 useRemoteThreadListRuntime 把 thread list adapter 与 per-thread useChatRuntime 组合。完整 schema、路由与 Drizzle 示例见 Custom thread persistence (opens new window)

怎么决策:

需求 方案
单线程、原型 默认内存态,无需 adapter
托管、快速上线 Assistant Cloud
线程与用户数据同库、合规自控 RemoteThreadListAdapter + ThreadHistoryAdapter
仅 AI SDK hook、自有 UI Cloud 的 useCloudChat 或自建 history

# References