A real-time collaborative chat application with AI streaming, demonstrating the power of Loro CRDTs with React and reactive server-side AI responses.
This chat app showcases a reactive architecture where:
- Real-time AI streaming - LLM responses stream character-by-character directly into Loro Text containers
- Server-side reactivity - The server detects new messages via Loro sync and automatically triggers AI responses
- Multi-user ready - Supports multiple users chatting together, with @mentions to invoke the AI
- Auto-mention mode - In single-user conversations, the AI responds automatically to every message
- Offline-first - Works offline and syncs when reconnected
- URL-based conversations - Share a URL to invite others to your conversation
- Beautiful UI - Modern, mobile-friendly design with Tailwind CSS
Unlike traditional REST APIs where the client explicitly calls an endpoint, this app uses Loro's subscription system to reactively detect when users send messages:
// Server subscribes to document changes
import { loro, subscribe } from "@loro-extended/change"
subscribe(loro(doc), () => {
const messages = doc.messages.toArray()
const lastMsg = messages[messages.length - 1]
// Check for @mentions or auto-mention in single-user mode
if (shouldRespondToMessage(lastMsg, doc)) {
// Create assistant message and stream LLM response into it
streamLLMResponse(repo, docId, lastMsg.id.get())
}
})The server streams LLM tokens directly into a Loro Text container, which automatically syncs to all connected clients:
const { textStream } = await streamText({
model: openrouter("openai/gpt-4o"),
messages: conversationHistory
})
let position = 0
for await (const chunk of textStream) {
// Insert directly into the CRDT container
textContainer.insert(position, chunk)
position += chunk.length
handle.doc.commit() // Triggers sync to all clients
}We use @loro-extended/change to define a type-safe schema shared between client and server:
export const MessageSchema = Shape.map({
id: Shape.plain.string(),
role: Shape.plain.string(), // 'user' | 'assistant'
author: Shape.plain.string(), // peerId or 'dot'
content: Shape.text(), // LoroText for streaming
timestamp: Shape.plain.number(),
mentions: Shape.plain.array(Shape.plain.string()) // Track @mentions
})
export const ChatSchema = Shape.doc({
messages: Shape.list(MessageSchema),
})The app intelligently switches between "companion mode" and "assistant mode" based on the number of participants:
-
Single User (Companion Mode):
- The AI responds to every message automatically.
- Feels like a 1-on-1 conversation with the AI.
-
Multiple Users (Assistant Mode):
- When a second user joins, the AI sends a system message: "Just @dot mention me if you need me!"
- The AI stops auto-responding to allow humans to chat.
- To invoke the AI, users must explicitly mention
@dotin their message.
This transition happens automatically as soon as the server detects a second unique author in the document history.
- Get an OpenRouter API key at https://openrouter.ai/keys
- Install dependencies from the monorepo root:
pnpm install- Create a
.envfile in this directory:
cp .env.example .env- Add your OpenRouter API key to
.env:
OPENROUTER_API_KEY=your_actual_key_here
pnpm -w buildcd examples/chat
pnpm devThis starts:
- React app on http://localhost:5173
- Express sync server on http://localhost:5170
Open multiple browser windows to see real-time collaboration in action!
- User types a message
- Client adds message to Loro document with parsed @mentions
- Loro automatically syncs to server
- User sees their message immediately
- Server discovers document via
ready-state-changedevent - Server subscribes to document changes
- When user message arrives, server checks if it should trigger AI
- Server creates empty assistant message in the document
- Server streams LLM response into the message's Text container
- Each chunk triggers a Loro sync to all clients
- Clients see the AI response streaming in real-time
src/
├── client/ # React application
│ ├── App.tsx # Main chat component (Tailwind CSS)
│ └── use-doc-id-from-hash.ts # URL hash management
├── server/ # Express sync server
│ └── server.ts # SSE endpoint + reactive AI streaming
└── shared/ # Shared types
└── types.ts # Chat schema definitions
- Loro Documentation
- @loro-extended/change README
- @loro-extended/repo README
- OpenRouter AI Documentation
- Vercel AI SDK
- Tailwind CSS
- Multiple AI assistants (@gpt4, @claude, etc.)
- Message editing with conflict resolution
- Typing indicators (real-time presence)
- Rich text formatting
- File attachments
- Conversation search