Chat History
Save and restore chat conversations across sessions
Save and persist chat threads and messages so users can continue conversations across browser sessions, devices, or after logging out.
Browser Storage
For simple browser-level persistence without server setup:
<CopilotChat
persistence={true}
showThreadPicker={true}
/>Data is stored in localStorage (~5MB limit, single device only).
Server Persistence
Store threads in your own database for cross-device sync and user accounts.
<CopilotChat
persistence={{
type: "server",
endpoint: "/api/threads",
headers: { Authorization: `Bearer ${token}` },
}}
showThreadPicker={true}
/>API Contract
Your endpoint must implement these routes:
| Method | Endpoint | Description |
|---|---|---|
GET | /api/threads | List threads |
POST | /api/threads | Create thread |
GET | /api/threads/:id | Get thread with messages |
PATCH | /api/threads/:id | Update thread |
DELETE | /api/threads/:id | Delete thread |
Implementation
// app/api/threads/route.ts
export async function GET() {
const threads = await db.thread.findMany({
orderBy: { updatedAt: 'desc' },
});
return Response.json({ threads, total: threads.length, hasMore: false });
}
export async function POST(request: Request) {
const body = await request.json();
const thread = await db.thread.create({ data: body });
return Response.json(thread, { status: 201 });
}// app/api/threads/[id]/route.ts
import { NextRequest } from 'next/server';
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const thread = await db.thread.findUnique({ where: { id } });
if (!thread) return Response.json({ error: 'Not found' }, { status: 404 });
return Response.json(thread);
}
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const updates = await request.json();
const thread = await db.thread.update({ where: { id }, data: updates });
return Response.json(thread);
}
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
await db.thread.delete({ where: { id } });
return new Response(null, { status: 204 });
}import { Router } from 'express';
const router = Router();
router.get('/', async (req, res) => {
const threads = await db.thread.findMany();
res.json({ threads, total: threads.length, hasMore: false });
});
router.post('/', async (req, res) => {
const thread = await db.thread.create({ data: req.body });
res.status(201).json(thread);
});
router.get('/:id', async (req, res) => {
const thread = await db.thread.findUnique({ where: { id: req.params.id } });
if (!thread) return res.status(404).json({ error: 'Not found' });
res.json(thread);
});
router.patch('/:id', async (req, res) => {
const thread = await db.thread.update({
where: { id: req.params.id },
data: req.body,
});
res.json(thread);
});
router.delete('/:id', async (req, res) => {
await db.thread.delete({ where: { id: req.params.id } });
res.status(204).send();
});
export default router;useThreadManager Hook
Access thread management functions directly for custom UIs:
import { useThreadManager } from '@yourgpt/copilot-sdk/react';
function CustomThreadList() {
const {
threads, // All threads
currentThread, // Currently active thread
currentThreadId, // Current thread ID
isLoading, // Loading state
createThread, // Create new thread
switchThread, // Switch to a thread
deleteThread, // Delete a thread
updateCurrentThread, // Update current thread
} = useThreadManager();
return (
<div>
{threads.map(thread => (
<div key={thread.id}>
<span onClick={() => switchThread(thread.id)}>
{thread.title || 'Untitled'}
</span>
<button onClick={() => deleteThread(thread.id)}>Delete</button>
</div>
))}
<button onClick={() => createThread()}>New Thread</button>
</div>
);
}Thread Object
interface Thread {
id: string;
title?: string;
preview?: string;
messageCount?: number;
createdAt?: Date;
updatedAt?: Date;
messages?: Message[];
}Custom Adapters
Create custom storage adapters for any backend:
import { useThreadManager } from '@yourgpt/copilot-sdk/react';
import type { ThreadStorageAdapter } from '@yourgpt/copilot-sdk/react';
const customAdapter: ThreadStorageAdapter = {
async getThreads() {
const response = await fetch('/api/my-threads');
return response.json();
},
async getThread(id) {
const response = await fetch(`/api/my-threads/${id}`);
return response.json();
},
async createThread(thread) {
const response = await fetch('/api/my-threads', {
method: 'POST',
body: JSON.stringify(thread),
});
return response.json();
},
async updateThread(id, updates) {
const response = await fetch(`/api/my-threads/${id}`, {
method: 'PATCH',
body: JSON.stringify(updates),
});
return response.json();
},
async deleteThread(id) {
await fetch(`/api/my-threads/${id}`, { method: 'DELETE' });
},
};
// Use with hook
const threadManager = useThreadManager({ adapter: customAdapter });
// Or use built-in server adapter
import { createServerAdapter } from '@yourgpt/copilot-sdk/react';
const serverAdapter = createServerAdapter({
endpoint: '/api/threads',
headers: { Authorization: `Bearer ${token}` },
});ChatWelcome Component
Display a welcome screen when starting a new conversation, with suggestions and recent chat history:
import { ChatWelcome } from '@yourgpt/copilot-sdk/ui';
function WelcomeScreen() {
const { sendMessage, threads, switchThread } = useCopilot();
return (
<ChatWelcome
config={{
title: "How can I help you today?",
subtitle: "Ask anything and get it done.",
logo: "/logo.png",
showRecentChats: true,
maxRecentChats: 3,
}}
suggestions={[
"Help me write an email",
"Explain this code",
"Create a marketing plan",
]}
recentThreads={threads}
onSendMessage={sendMessage}
onSelectThread={switchThread}
/>
);
}ChatWelcome Props
| Prop | Type | Description |
|---|---|---|
config | WelcomeConfig | Title, subtitle, logo, and display settings |
suggestions | string[] | Clickable suggestion prompts |
recentThreads | Thread[] | Recent conversations to display |
onSendMessage | (message, attachments?) => void | Called when user sends a message |
onSelectThread | (threadId) => void | Called when user selects a thread |
onDeleteThread | (threadId) => void | Called when user deletes a thread |
attachmentsEnabled | boolean | Enable file attachments (default: true) |
WelcomeConfig
interface WelcomeConfig {
title?: string; // Main heading
subtitle?: string; // Subheading
logo?: string; // Logo image URL
showRecentChats?: boolean; // Show recent threads
recentChatsLabel?: string; // Label for recent chats section
maxRecentChats?: number; // Max threads to show (default: 3)
suggestionsLabel?: string; // Label for suggestions
}