Features
Media, Emoji & AI (short)
Media uploads
Media (images, video, audio) are uploaded to Cloudinary and the returned secure URL is stored on the message document. See Getting Started → Cloudinaryfor detailed setup, presets and code examples.
Emoji support
The chat input integrates emoji-mart for picking emojis which are inserted as unicode characters. No schema change is required—emojis are stored as text.
AI assistant
The project can send prompts to Vercel's groq model via@vercel/ai. For full configuration and an example API route, see Getting Started → AI (Groq).
Real-time Subscription
Messages are subscribed to in real-time using subscribeMessagesByRoomId which sets up a Firestore listener:
const q = query(
collection(db, "rooms", roomId, "messages"),
orderBy("createdAt", "asc")
);
return onSnapshot(q, (snapshot) => {
const messages = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
onChange(messages);
});The listener automatically fires whenever a new message is added or an existing message is modified, keeping the UI synchronized.
Using the Hook
Components use useMessages to get the current message list:
const { messages, isLoading, error } = useMessages(roomId);
// messages is automatically updated when new messages arrive
// The hook handles subscription cleanup on unmountUnread Count Management
How Unread Counts Work
Unread counts are stored per-user in the room document:
rooms/{roomId} {
unreadCounts: {
"uid1": 3,
"uid2": 0,
"uid3": 5
}
}- Incremented: When a message is sent, counts are incremented for all participants except the sender and users currently viewing the room (tracked via room presence)
- Reset: When a user opens a room,
resetUnreadCountsets their count to 0 - Displayed: Room list items show a badge with the unread count if it's greater than 0
Integration with Room Presence
The template uses room presence (Realtime Database) to determine who is currently viewing a room:
- When a user opens a room,
useRoomPresencecallsenterRoom(uid, roomId) - When sending a message, the service checks room presence to exclude active viewers from unread count increments
- When leaving a room,
leaveRoom(uid, roomId)is called automatically
This ensures users don't get unread badges for messages they're actively viewing.
Attachments & media
Messages can include an attachments array. Attachments use the project's Cloudinary helpers; images and videos are returned as objects that match the UploadMediashape and are stored on the message as attachments: UploadMedia[].
rooms/{roomId}/messages/{messageId} {
senderId: string;
text?: string;
type: 'text' | 'system' | 'media' | 'audio';
attachments?: UploadMedia[]; // UploadMedia is UploadImage | UploadVideo
createdAt: Timestamp;
}Client helpers: src/features/chat/services/cloudinary.service.tsand the hook src/features/chat/hooks/use-upload-media.ts.
Emoji
The input uses emoji-mart to insert unicode emoji into the message text. No schema change is necessary.
AI assistant (short)
The repo includes a server API at src/app/api/chat/route.ts which calls the@ai-sdk/groq model via streamText. See Getting Started → AI (Groq) for full details and examples.
Message Types
The template currently supports these message types:
"text"– Regular user messages with text content"system"– System-generated messages (e.g. "created the group") that are styled differently in the UI
You can extend this to support "image", "file", or "reply" types by updating the message type definition and adding appropriate UI components.
Implementation Details
Key files for messaging:
features/chat/services/messages.service.ts– sendMessage, subscribeMessagesByRoomIdfeatures/chat/hooks/use-messages.ts– React hook for subscribing to messagesfeatures/chat/hooks/use-send-message.ts– React hook for sending messagesfeatures/chat/services/unread-count.service.ts– resetUnreadCount functionfeatures/chat/components/room-pane/chat-message-list.tsx– Component that renders the message list