LocalMode
React

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 cancel

Vision (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.

Showcase Apps

AppDescriptionLinks
LLM ChatFull chat interface with streaming, model selection, and vision image inputDemo · Source
PDF SearchDocument Q&A chat powered by RAGDemo · Source
GGUF ExplorerChat with locally-loaded GGUF modelsDemo · Source

On this page