thread-list について
assistant-ui のチャット画面に複数会話のリストを追加・カスタマイズするためのスキルです。マネージドクラウド、バックエンドアダプター、外部ストアの構成を案内します。
主な機能
- 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 スレッドリスト
最新の API については、常に assistant-ui.com/llms.txt を参照してください。
ランタイムは、1 つのメモリ内会話から始まります。複数の会話を作成、選択、管理する必要がある場合にのみ、スレッドリストを追加してください。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 に渡し、プロバイダーの下でリストとアクティブなスレッドをレンダリングします。
"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.threads と aui.threadListItem を使用してください。各状態値は useAuiState で個別に読み取ります。セレクターは 1 つのプリミティブ値または安定したランタイム参照を返すことができますが、オブジェクトや配列リテラルを返すことはできません。
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 がスコープ内のアイテムです。行が 1 つの値だけを必要とする場合は、状態オブジェクト全体ではなく、フィールドを個別に選択してください。
import { useAuiState } from "@assistant-ui/react";
export function ThreadRunningBadge() {
const isRunning = useAuiState((s) => s.threadListItem.isRunning);
return isRunning ? <span>Working</span> : null;
}
カスタムリストの構成
インストールされた要素の日付グループ化、検索、または視覚的処理が製品に合わない場合は、3 つのプリミティブ名前空間から始めてください。Items は現在のアイテムスコープを提供し、その children レンダー関数は非推奨の 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 |
RemoteThreadListAdapter を伴う useRemoteThreadListRuntime | LocalRuntime ベースのランタイムがアプリケーションのデータベースを使用する場合 | ユーザーのバックエンド |
ExternalStoreThreadListAdapter | ExternalStoreRuntime がすでにメッセージと選択を所有している場合 | ユーザーの状態ストア |
上記のクラウドクイックスタートはマネージドパスです。リモートおよび外部ストアのパスは、所有権とライフサイクルの要件が異なります。それらの API を混在させるのではなく、リモートアダプター に従ってください。
選択の監視
threads.selectionChanged は、古いアイテムごとの switch イベントを置き換えます。これはすべての選択変更に対して実行され、両方の 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はサイドバーシェルであり、ランタイムプロバイダーではないため、shadcn のSidebarProviderも必要です。
無関係なストアの変更のたびに行が再レンダリングされる
- オブジェクトリテラルや配列リテラルを返すセレクターは、個別の
useAuiState呼び出しに置き換えてください。s.threads.threadIdsを直接返すのは、ランタイムが提供する安定した参照であるため安全です。
アップグレード後に switch ハンドラーが発火しなくなった
threads.selectionChangedを購読し、previousThreadIdを使用してください。廃止されたthreadListItem.switchedToおよびthreadListItem.switchedAwayイベントは、現在の統合ポイントではありません。