关于 thread-list
assistant-ui 的 thread-list 技能指导在聊天界面添加和定制多会话列表,适合需要管理多个对话的开发者。它涵盖托管云端、后端适配器和外部状态存储等路径。
主要功能
- 01连接运行时的会话列表
- 02托管、远程和外部存储路径
- 03会话操作与选择事件
- 04自定义列表布局、搜索和分组
适用场景
- 01为聊天界面添加会话创建与切换
- 02通过后端适配器管理会话元数据
- 03为会话列表实现自定义搜索和分组
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ assistant-ui/ skills --skill thread-list安装后在 Claude Code 中输入/thread-list确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/custom-ui.mdmanagement.mdremote-adapter.md详细介绍
assistant-ui 对话列表
请始终查阅 assistant-ui.com/llms.txt,以获取最新 API。
运行时初始只有一个内存中的对话。只有在用户需要创建、选择和管理多个对话时,才添加对话列表。选择 UI 之前,先决定使用托管云方案、后端适配器还是外部存储。
参考资料
- ./references/management.md -- CRUD、初始化、刷新、分页、元数据和标识符
- ./references/custom-ui.md -- 所有对话列表原语、自定义布局、搜索、分组和已安装元素的组合方式
- ./references/remote-adapter.md -- 远程适配器约定、历史记录适配器、存储条目、身份验证重新加载和外部存储
快速开始
使用 npx assistant-ui@latest add thread-list 安装运行时连接列表。它会创建 components/assistant-ui/elements/thread-list.aui.tsx。将 AssistantCloud 实例传递给 useChatRuntime 以使用托管对话,然后在 Provider 下方渲染列表和当前对话。
"use client";
import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/ai-sdk";
import { AssistantCloud } from "assistant-cloud";
import { Thread } from "@/components/assistant-ui/elements/thread.aui";
import { ThreadList } from "@/components/assistant-ui/elements/thread-list.aui";
const cloud = new AssistantCloud({
baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL,
anonymous: true,
});
export function Chat() {
const runtime = useChatRuntime({ cloud });
return (
<AssistantRuntimeProvider runtime={runtime}>
<div className="flex h-dvh">
<aside className="w-72 border-e p-2">
<ThreadList />
</aside>
<main className="min-w-0 flex-1">
<Thread />
</main>
</div>
</AssistantRuntimeProvider>
);
}
如果项目已使用 shadcn 侧边栏原语,请使用完整的侧边栏外壳。ThreadListSidebar 会渲染运行时连接的对话列表本身,因此不要在其中再挂载 ThreadList。
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import { Thread } from "@/components/assistant-ui/elements/thread.aui";
import { ThreadListSidebar } from "@/components/assistant-ui/elements/threadlist-sidebar.aui";
export function ChatLayout() {
return (
<SidebarProvider>
<ThreadListSidebar />
<SidebarInset>
<Thread />
</SidebarInset>
</SidebarProvider>
);
}
ThreadListSidebar 仍需要第一个示例中的 AssistantRuntimeProvider 祖先节点。它的 SidebarProvider 是独立的布局上下文。
对话操作
使用 useAui() 执行命令。作用域访问器是属性,因此应使用 aui.threads 和 aui.threadListItem,而不是 aui.threads()。使用 useAuiState 分别读取每个状态值。选择器可以返回单个基本类型值或稳定的运行时引用,但不能返回对象或数组字面量。
import { useAui, useAuiState } from "@assistant-ui/react";
export function ThreadControls({ threadId }: { threadId: string }) {
const aui = useAui();
const activeThreadId = useAuiState((s) => s.threads.mainThreadId);
const threadIds = useAuiState((s) => s.threads.threadIds);
const isLoading = useAuiState((s) => s.threads.isLoading);
const item = aui.threads.item({ id: threadId });
return (
<div>
<p>{threadIds.length} conversations</p>
<p>{activeThreadId === threadId ? "Current" : "Available"}</p>
<button disabled={isLoading} onClick={() => aui.threads.switchToNewThread()}>
New conversation
</button>
<button onClick={() => item.switchTo()}>Open</button>
<button onClick={() => item.rename("Project planning")}>Rename</button>
<button onClick={() => item.archive()}>Archive</button>
<button onClick={() => item.delete()}>Delete</button>
</div>
);
}
在对话条目原语内部,s.threadListItem 是作用域中的条目。如果一行只需要一个值,请逐个选择字段,而不是选择整个状态对象。
import { useAuiState } from "@assistant-ui/react";
export function ThreadRunningBadge() {
const isRunning = useAuiState((s) => s.threadListItem.isRunning);
return isRunning ? <span>Working</span> : null;
}
组合自定义列表
如果已安装元素的日期分组、搜索或视觉样式与产品不符,请从三个原语命名空间入手。Items 提供当前条目作用域,其子项渲染函数可替代已弃用的 components 属性。
import {
ThreadListItemMorePrimitive,
ThreadListItemPrimitive,
ThreadListPrimitive,
} from "@assistant-ui/react";
export function CustomThreadList() {
return (
<ThreadListPrimitive.Root className="flex flex-col gap-1">
<ThreadListPrimitive.New>New conversation</ThreadListPrimitive.New>
<ThreadListPrimitive.Items>
{({ threadListItem }) => (
<ThreadListItemPrimitive.Root data-thread-id={threadListItem.id}>
<ThreadListItemPrimitive.Trigger className="flex-1 text-left">
<ThreadListItemPrimitive.Title fallback="New conversation" />
</ThreadListItemPrimitive.Trigger>
<ThreadListItemMorePrimitive.Root sharedFocusGroup>
<ThreadListItemMorePrimitive.Trigger>
More
</ThreadListItemMorePrimitive.Trigger>
<ThreadListItemMorePrimitive.Content>
<ThreadListItemPrimitive.Archive asChild>
<ThreadListItemMorePrimitive.Item>
Archive
</ThreadListItemMorePrimitive.Item>
</ThreadListItemPrimitive.Archive>
<ThreadListItemMorePrimitive.Separator />
<ThreadListItemPrimitive.Delete asChild>
<ThreadListItemMorePrimitive.Item>
Delete
</ThreadListItemMorePrimitive.Item>
</ThreadListItemPrimitive.Delete>
</ThreadListItemMorePrimitive.Content>
</ThreadListItemMorePrimitive.Root>
</ThreadListItemPrimitive.Root>
)}
</ThreadListPrimitive.Items>
</ThreadListPrimitive.Root>
);
}
有关归档行、加载更多、自定义搜索、分组、键盘行为和完整部件参考,请参阅自定义 UI。
选择多对话方案
| 方案 | 适用场景 | 对话元数据存储位置 |
|---|---|---|
AssistantCloud | 需要托管身份验证、持久化、同步和标题 | assistant-cloud |
useRemoteThreadListRuntime 与 RemoteThreadListAdapter | 基于 LocalRuntime 的运行时使用应用的数据库 | 你的后端 |
ExternalStoreThreadListAdapter | ExternalStoreRuntime 已拥有消息和选择状态 | 你的状态存储 |
上方的云端快速入门采用托管方案。远程和外部存储方案的所有权和生命周期要求各不相同。请遵循远程适配器,不要混用它们的 API。
监听选择状态
threads.selectionChanged 取代了旧的逐项切换事件。每次选择状态发生变化时都会触发,并提供两个 ID。初始选择状态不会在挂载时触发此事件。
import { useAuiEvent } from "@assistant-ui/react";
export function SelectionTelemetry() {
useAuiEvent(
"threads.selectionChanged",
({ threadId, previousThreadId }) => {
recordThreadSelection({ threadId, previousThreadId });
},
);
return null;
}
如果此监听器位于条目行内,并且只应在该行成为当前项时响应,请将 threadId 与 s.threadListItem.id 进行比较。
常见注意事项
列表正常显示,但对话未持久化
- 默认运行时是一个内存中的线程。传入
cloud、使用RemoteThreadListAdapter,或提供ExternalStoreThreadListAdapter。 - 远程适配器只管理元数据。为每个对话添加历史记录适配器或其他消息持久化层。
切换线程后显示的是过期消息
- 对于
RemoteThreadList存储条目,将线程工厂包装在withKey(id, ...)中,以便为选中的 ID 挂载历史记录适配器。 - 仅当运行应在选择变更后继续,且可以接受将访问过的线程保留在内存中时,才使用
backgroundThreads。
侧边栏没有运行时
ThreadList和ThreadListSidebar需要一个AssistantRuntimeProvider祖先。ThreadListSidebar还需要 shadcnSidebarProvider,因为它是侧边栏外壳,而不是运行时提供程序。
每当无关的存储发生变化时,行都会重新渲染
- 将返回对象或数组字面量的选择器替换为单独的
useAuiState调用。直接返回s.threads.threadIds是安全的,因为这是由运行时提供的稳定引用。
升级后切换处理程序不再触发
- 订阅
threads.selectionChanged并使用previousThreadId。已弃用的threadListItem.switchedTo和threadListItem.switchedAway事件不是当前的集成入口。