Chat & Threads
useChat
The useChat() hook is the core hook for sending and receiving messages. It handles streaming, thread creation, message editing, and tool approvals.
New Conversation
Omit threadId to start a new conversation. Use onThreadCreated to capture the new thread ID:
import { useChat } from '@pandorakit/react-sdk'
function NewChat() {
const router = useRouter()
const { messages, sendMessage, status } = useChat({
onThreadCreated: (threadId) => {
router.push(`/chat/${threadId}`)
},
})
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>{msg.role}: {msg.parts.filter(p => p.type === 'text').map(p => p.text).join('')}</div>
))}
<form onSubmit={(e) => {
e.preventDefault()
const text = new FormData(e.currentTarget).get('message') as string
sendMessage({ text })
e.currentTarget.reset()
}}>
<input name="message" placeholder="Send a message..." />
<button type="submit" disabled={status !== 'ready'}>Send</button>
</form>
</div>
)
}Existing Thread
Pass threadId and initialMessages (from useThread) to continue a conversation. The hook automatically resumes active streams:
import { useChat, useThread } from '@pandorakit/react-sdk'
function ExistingChat({ threadId }: { threadId: string }) {
const { data } = useThread(threadId)
const { messages, sendMessage, status } = useChat({
threadId,
initialMessages: data?.messages,
})
// ... render messages and input
}Editing a Message
editMessage forks the conversation at the specified message and sends the edited text:
const { editMessage } = useChat({ threadId })
async function handleEdit(messageId: string, newText: string) {
const newThreadId = await editMessage(messageId, newText)
router.push(`/chat/${newThreadId}`)
}Tool Approvals
When the agent requests approval for a tool call, use addToolApprovalResponse:
const { messages, addToolApprovalResponse } = useChat({ threadId })
addToolApprovalResponse({ toolCallId, approved: true })
// or
addToolApprovalResponse({ toolCallId, approved: false })Error Handling
const { messages, error } = useChat({
threadId,
onError: (err) => {
toast.error(err.message || 'Something went wrong')
},
})UseChatOptions
| Property | Type | Description |
|---|---|---|
threadId | string | Thread ID for existing conversations. Omit for new chats. |
initialMessages | ServerMessage[] | Messages to populate the chat with (from useThread). |
onThreadCreated | (threadId: string) => void | Called when the server creates a new thread. |
onFinish | () => void | Called when the assistant response stream finishes. |
onError | (error: Error) => void | Called on streaming errors. |
UseChatReturn
| Property | Type | Description |
|---|---|---|
messages | UIMessage[] | The conversation messages. |
sendMessage | (message: { text: string; files?: FileUIPart[] }) => Promise<void> | Send a user message, optionally with files. |
status | ChatStatus | 'ready' | 'submitted' | 'streaming' | 'error' |
stop | () => Promise<void> | Stop the current stream. |
error | Error | undefined | The last error, if any. |
setMessages | (messages: UIMessage[] | ((msgs: UIMessage[]) => UIMessage[])) => void | Manually set messages. |
editMessage | (messageId: string, newText: string) => Promise<string> | Fork and send edited text. Returns new thread ID. |
addToolApprovalResponse | ChatAddToolApproveResponseFunction | Approve or deny a pending tool call. |
For rendering message part types, see Message Part Types.
useThreads
Lists all threads, with support for forking and deletion.
import { useThreads } from '@pandorakit/react-sdk'
function ThreadSidebar() {
const { data, remove } = useThreads()
return (
<nav>
{data?.threads.map((thread) => (
<div key={thread.id}>
<a href={`/chat/${thread.id}`}>{thread.title || 'New Chat'}</a>
<button onClick={() => remove(thread.id)}>Delete</button>
</div>
))}
</nav>
)
}| Property | Type | Description |
|---|---|---|
data | ThreadListResponse | undefined | Thread list including threads and activeStreamIds. |
isLoading | boolean | Whether the initial fetch is in progress. |
error | Error | null | Fetch error, if any. |
fork | (args: { threadId: string; messageId: string }) => Promise<ThreadForkResponse> | Fork a thread at a specific message. |
remove | (threadId: string) => Promise<void> | Delete a thread. |
useThread
Fetches a single thread with its full message history and fork metadata.
import { useThread } from '@pandorakit/react-sdk'
function ThreadView({ threadId }: { threadId: string }) {
const { data, isLoading } = useThread(threadId)
if (isLoading) return <div>Loading...</div>
return (
<div>
<h2>{data?.thread.title}</h2>
{data?.messages.map((msg) => (
<div key={msg.id}>{/* render message */}</div>
))}
</div>
)
}| Property | Type | Description |
|---|---|---|
data | ThreadDetailResponse | undefined | Thread details including thread, messages, forks, and forkInfo. |
isLoading | boolean | Whether the fetch is in progress. |
error | Error | null | Fetch error, if any. |
Rendering Messages
The messages array from useChat() contains UIMessage objects with a parts array:
import type { UIMessage } from 'ai'
function MessageParts({ message }: { message: UIMessage }) {
return (
<>
{message.parts.map((part, i) => {
switch (part.type) {
case 'text':
return <p key={i}>{part.text}</p>
case 'reasoning':
return (
<details key={i}>
<summary>Thinking...</summary>
{part.text}
</details>
)
case 'file':
return part.mediaType?.startsWith('image/') ? (
<img key={i} src={part.url} alt={part.filename || 'attachment'} />
) : (
<a key={i} href={part.url} download>{part.filename || 'File'}</a>
)
case 'source-url':
return <a key={i} href={part.url}>{part.title || part.url}</a>
default:
if (part.type.startsWith('tool-')) {
return (
<pre key={i}>
Tool: {part.type} ({part.state})
{part.state === 'output-available' && JSON.stringify(part.output, null, 2)}
</pre>
)
}
return null
}
})}
</>
)
}See the Message Part Types reference for the full list.