Chat
Streaming LLM chat with message persistence and cancellation.
useChat
Streaming LLM chat with message history, IndexedDB persistence, system prompts, and cancellation.
See it in action
Try LLM Chat and PDF Search for working demos of these hooks.
Basic Usage
import { useChat } from '@localmode/react';
import { webllm } from '@localmode/webllm';
const model = webllm.languageModel('Llama-3.2-1B-Instruct-q4f16_1-MLC');
function Chat() {
const { messages, isStreaming, send, cancel, clearMessages } = useChat({
model,
systemPrompt: 'You are a helpful assistant.',
});
return (
<div>
{messages.map((m) => (
<div key={m.id}><b>{m.role}:</b> {m.content}</div>
))}
<input onKeyDown={(e) => {
if (e.key === 'Enter') send(e.currentTarget.value);
}} />
{isStreaming && <button onClick={cancel}>Stop</button>}
<button onClick={clearMessages}>Clear</button>
</div>
);
}Options
Prop
Type
Return Value
Prop
Type
Persistence
Messages are persisted to IndexedDB by default. This means chat history survives page refreshes.
// Disable persistence
const chat = useChat({ model, persist: false });
// Custom storage key (useful for multiple chat instances)
const chat = useChat({ model, persistKey: 'my-project-chat' });Persisted messages take precedence over initialMessages. If IndexedDB has saved messages, initialMessages is ignored.
Cancellation
Calling cancel() during streaming preserves the partial assistant message — it won't be cleared.
const { isStreaming, cancel } = useChat({ model });
// The partial response stays in messages after cancelVision (Image Input)
For vision-capable models, pass images alongside text using the send() options:
import { useChat } from '@localmode/react';
import { webllm } from '@localmode/webllm';
const model = webllm.languageModel('Phi-3.5-vision-instruct-q4f16_1-MLC');
function VisionChat() {
const { messages, send } = useChat({ model });
const handleImageUpload = async (file: File) => {
const reader = new FileReader();
reader.onload = async () => {
const dataUrl = reader.result as string;
const base64 = dataUrl.split(',')[1];
await send('Describe this image', {
images: [{ data: base64, mimeType: file.type, name: file.name }],
});
};
reader.readAsDataURL(file);
};
// ... render messages and file input
}Check model.supportsVision to conditionally show image upload UI. Messages with images have content as ContentPart[] instead of string.
For the full multimodal API reference, see the Core Generation — Vision guide.
For full API reference on streamText(), see the Core Generation guide. For model setup, see WebLLM or Transformers.