Skip to content

Commit 46c5821

Browse files
committed
feat: add thread list and deletion API with new component
- switch docs, stories, and UI to the slot-based CopilotThreadList API - surface delete controls with optimistic updates and error feedback - extend useThreads with deleteThread/currentThreadId helpers plus tests - prevent suggestion-generated runs from polluting thread history - wire DELETE handler through runtime endpoints and runner backends - harden CopilotChat reconnect flow and tool-call rendering keys Signed-off-by: Tyler Slaton <[email protected]>
1 parent b9e50d0 commit 46c5821

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+8354
-537
lines changed
Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
---
2+
title: "Thread Management"
3+
description: "Learn how to retrieve, list, and switch between conversation threads in CopilotKit"
4+
---
5+
6+
# Thread Management
7+
8+
CopilotKit provides built-in support for managing multiple conversation threads, allowing you to build chat applications with conversation history similar to ChatGPT.
9+
10+
## Overview
11+
12+
Threads are automatically created and stored when you use CopilotChat. Each thread maintains:
13+
- Complete conversation history
14+
- Message metadata
15+
- Running state
16+
- Creation and last activity timestamps
17+
18+
## Using the Thread List Component
19+
20+
The easiest way to add thread management is using the `CopilotThreadList` component:
21+
22+
```tsx
23+
import {
24+
CopilotKitProvider,
25+
CopilotThreadList,
26+
CopilotChat,
27+
CopilotChatConfigurationProvider,
28+
useCopilotChatConfiguration,
29+
} from "@copilotkitnext/react";
30+
31+
function ThreadSidebar() {
32+
return (
33+
<aside className="w-80 border-r">
34+
<CopilotThreadList limit={20} />
35+
</aside>
36+
);
37+
}
38+
39+
function ChatPanel() {
40+
const config = useCopilotChatConfiguration();
41+
42+
if (!config?.threadId) {
43+
return (
44+
<div className="flex h-full items-center justify-center text-muted-foreground">
45+
Select a conversation to start chatting
46+
</div>
47+
);
48+
}
49+
50+
return <CopilotChat agentId="my-agent" threadId={config.threadId} className="h-full" />;
51+
}
52+
53+
export function ChatApp() {
54+
return (
55+
<CopilotKitProvider>
56+
<CopilotChatConfigurationProvider>
57+
<div className="flex h-screen">
58+
<ThreadSidebar />
59+
<div className="flex-1">
60+
<ChatPanel />
61+
</div>
62+
</div>
63+
</CopilotChatConfigurationProvider>
64+
</CopilotKitProvider>
65+
);
66+
}
67+
```
68+
69+
## Using the useThreads Hook
70+
71+
For more control over thread data, use the `useThreads` hook:
72+
73+
```tsx
74+
import { useThreads } from "@copilotkitnext/react";
75+
76+
function MyThreadManager() {
77+
const {
78+
threads,
79+
total,
80+
isLoading,
81+
error,
82+
fetchThreads,
83+
getThreadMetadata,
84+
refresh,
85+
} = useThreads({
86+
limit: 20,
87+
autoFetch: true,
88+
});
89+
90+
if (isLoading) return <div>Loading threads...</div>;
91+
if (error) return <div>Error: {error.message}</div>;
92+
93+
return (
94+
<div>
95+
<h2>Your Conversations ({total})</h2>
96+
{threads.map((thread) => (
97+
<div key={thread.threadId}>
98+
<h3>{thread.firstMessage || "New conversation"}</h3>
99+
<p>{thread.messageCount} messages</p>
100+
<p>Last active: {new Date(thread.lastActivityAt).toLocaleString()}</p>
101+
</div>
102+
))}
103+
<button onClick={refresh}>Refresh</button>
104+
</div>
105+
);
106+
}
107+
```
108+
109+
## Thread Metadata
110+
111+
Each thread includes the following metadata:
112+
113+
```typescript
114+
interface ThreadMetadata {
115+
threadId: string; // Unique thread identifier
116+
createdAt: number; // Timestamp (ms)
117+
lastActivityAt: number; // Timestamp (ms)
118+
isRunning: boolean; // Is thread currently active?
119+
messageCount: number; // Total messages in thread
120+
firstMessage?: string; // Preview of first message (100 chars)
121+
}
122+
```
123+
124+
## Creating New Threads
125+
126+
To create a new thread, simply generate a new UUID and pass it to CopilotChat:
127+
128+
```tsx
129+
import { CopilotChat } from "@copilotkitnext/react";
130+
import { useState } from "react";
131+
132+
function ChatWithNewThread() {
133+
const [threadId, setThreadId] = useState<string>(crypto.randomUUID());
134+
135+
const handleNewThread = () => {
136+
setThreadId(crypto.randomUUID());
137+
};
138+
139+
return (
140+
<div>
141+
<button onClick={handleNewThread}>New Conversation</button>
142+
<CopilotChat
143+
agentId="my-agent"
144+
threadId={threadId}
145+
/>
146+
</div>
147+
);
148+
}
149+
```
150+
151+
## Switching Between Threads
152+
153+
CopilotChat automatically handles thread switching when the `threadId` prop changes:
154+
155+
```tsx
156+
function MultiThreadChat() {
157+
const [currentThread, setCurrentThread] = useState("thread-1");
158+
const { threads } = useThreads();
159+
160+
return (
161+
<div>
162+
{/* Thread selector */}
163+
<select
164+
value={currentThread}
165+
onChange={(e) => setCurrentThread(e.target.value)}
166+
>
167+
{threads.map((thread) => (
168+
<option key={thread.threadId} value={thread.threadId}>
169+
{thread.firstMessage || thread.threadId}
170+
</option>
171+
))}
172+
</select>
173+
174+
{/* Chat will automatically load the selected thread */}
175+
<CopilotChat
176+
agentId="my-agent"
177+
threadId={currentThread}
178+
/>
179+
</div>
180+
);
181+
}
182+
```
183+
184+
## Customizing the Thread List
185+
186+
### Custom Thread Renderer
187+
188+
```tsx
189+
<CopilotThreadList
190+
threadItem={({ thread, isActive, onClick }) => (
191+
<button
192+
type="button"
193+
onClick={onClick}
194+
className={[
195+
"flex w-full flex-col items-start gap-1 rounded-md border p-3 text-left transition",
196+
isActive ? "border-primary bg-primary/10" : "hover:bg-muted",
197+
].join(" ")}
198+
>
199+
<h4 className="text-sm font-medium">{thread.firstMessage || "Untitled conversation"}</h4>
200+
<span className="text-xs text-muted-foreground">
201+
{thread.messageCount} messages {thread.isRunning ? "• Running" : ""}
202+
</span>
203+
</button>
204+
)}
205+
/>
206+
```
207+
208+
### Custom Empty State
209+
210+
```tsx
211+
import { randomUUID } from "@copilotkitnext/shared";
212+
import { CopilotThreadList, useThreads, useCopilotChatConfiguration } from "@copilotkitnext/react";
213+
214+
function ThreadListWithEmptyState() {
215+
const { threads, isLoading } = useThreads();
216+
const config = useCopilotChatConfiguration();
217+
218+
if (isLoading) {
219+
return <div className="p-6 text-sm text-muted-foreground">Loading threads…</div>;
220+
}
221+
222+
if (threads.length === 0) {
223+
return (
224+
<div className="flex flex-col items-center gap-3 rounded-lg border border-dashed p-8 text-center">
225+
<p className="text-muted-foreground">No conversations yet</p>
226+
<button
227+
type="button"
228+
className="rounded bg-primary px-4 py-2 text-primary-foreground"
229+
onClick={() => config?.setThreadId(randomUUID())}
230+
>
231+
Start chatting
232+
</button>
233+
</div>
234+
);
235+
}
236+
237+
return <CopilotThreadList />;
238+
}
239+
```
240+
241+
### Pagination
242+
243+
```tsx
244+
function PaginatedThreadList() {
245+
const [offset, setOffset] = useState(0);
246+
const limit = 20;
247+
const { threads, total, fetchThreads } = useThreads({ limit });
248+
249+
const handleNextPage = () => {
250+
const newOffset = offset + limit;
251+
setOffset(newOffset);
252+
fetchThreads(newOffset);
253+
};
254+
255+
const handlePrevPage = () => {
256+
const newOffset = Math.max(0, offset - limit);
257+
setOffset(newOffset);
258+
fetchThreads(newOffset);
259+
};
260+
261+
return (
262+
<div>
263+
<CopilotThreadList limit={limit} />
264+
<div>
265+
<button onClick={handlePrevPage} disabled={offset === 0}>
266+
Previous
267+
</button>
268+
<span>Showing {offset + 1}-{Math.min(offset + limit, total)} of {total}</span>
269+
<button onClick={handleNextPage} disabled={offset + limit >= total}>
270+
Next
271+
</button>
272+
</div>
273+
</div>
274+
);
275+
}
276+
```
277+
278+
## Backend Configuration
279+
280+
Thread storage is handled automatically by the AgentRunner. Choose the appropriate runner for your deployment:
281+
282+
### In-Memory (Development)
283+
284+
```typescript
285+
import { CopilotRuntime, InMemoryAgentRunner } from "@copilotkitnext/runtime";
286+
287+
const runtime = new CopilotRuntime({
288+
agents: myAgents,
289+
runner: new InMemoryAgentRunner(), // Default
290+
});
291+
```
292+
293+
### SQLite (Single Server)
294+
295+
```typescript
296+
import { SqliteAgentRunner } from "@copilotkitnext/runtime";
297+
298+
const runtime = new CopilotRuntime({
299+
agents: myAgents,
300+
runner: new SqliteAgentRunner({
301+
dbPath: "./copilot.db", // Persistent storage
302+
}),
303+
});
304+
```
305+
306+
### Enterprise (Multi-Server with Redis)
307+
308+
```typescript
309+
import { EnterpriseAgentRunner } from "@copilotkitnext/runtime";
310+
import { Kysely } from "kysely";
311+
import Redis from "ioredis";
312+
313+
const runtime = new CopilotRuntime({
314+
agents: myAgents,
315+
runner: new EnterpriseAgentRunner({
316+
kysely: new Kysely({
317+
// Your database config
318+
}),
319+
redis: new Redis({
320+
// Your Redis config
321+
}),
322+
}),
323+
});
324+
```
325+
326+
## API Reference
327+
328+
### Components
329+
330+
- [`CopilotThreadList`](/reference/copilot-thread-list) - Thread list UI component
331+
332+
### Hooks
333+
334+
- [`useThreads`](/reference/use-threads) - Hook for managing threads
335+
336+
### Core Methods
337+
338+
- `core.listThreads(params)` - List all threads
339+
- `core.getThreadMetadata(threadId)` - Get single thread metadata
340+
341+
## Best Practices
342+
343+
1. **Always use stable thread IDs**: Generate thread IDs once and persist them in your app state
344+
2. **Handle loading states**: Thread fetching is async, show loading indicators
345+
3. **Implement error handling**: Network requests can fail, provide fallback UI
346+
4. **Consider pagination**: For apps with many threads, implement pagination
347+
5. **Provide visual feedback**: Show active threads, running status, etc.
348+
349+
## Examples
350+
351+
Check out our Storybook for interactive examples:
352+
- Basic thread list
353+
- Thread list with chat integration
354+
- Custom thread renderers
355+
- Pagination examples
356+
357+
## Troubleshooting
358+
359+
### Threads not showing up
360+
361+
- Ensure `runtimeUrl` is set in CopilotKitProvider
362+
- Check that threads have been created (send at least one message)
363+
- Verify your backend is running and accessible
364+
365+
### Thread switching not working
366+
367+
- Ensure `threadId` prop on CopilotChat changes when selecting a new thread
368+
- Check browser console for connection errors
369+
- Verify the thread exists by calling `getThreadMetadata`
370+
371+
### Performance issues with many threads
372+
373+
- Implement pagination using the `limit` and `offset` parameters
374+
- Consider implementing search/filter functionality
375+
- Use custom renderers to optimize rendering

0 commit comments

Comments
 (0)