xiaohongshu
xiaohongshu
composable-svelte-chat
Streaming chat and collaborative features for Composable Svelte. Use when implementing LLM chat interfaces, real-time messaging, or collaborative features. Covers StreamingChat (transport-agnostic), presence tracking, typing indicators, and WebSocket integration from @composable-svelte/chat package.
Full skill instructions
Streaming chat with collaborative features for LLM interactions and real-time messaging.
Package: @composable-svelte/chat
Purpose: Transport-agnostic streaming chat designed for LLM interactions with collaborative features.
Technology Stack:
Core Components:
StreamingChat - Transport-agnostic streaming chatCollaborative Features - Presence, typing, cursorsWebSocket Manager - Connection managementState Management: All components follow Composable Architecture patterns with dedicated reducers and type-safe actions.
Purpose: Transport-agnostic streaming chat for LLM interactions (OpenAI, Anthropic, Ollama, etc.).
import { createStore } from '@composable-svelte/core';
import {
StandardStreamingChat,
streamingChatReducer,
createInitialStreamingChatState
} from '@composable-svelte/chat';
// Create chat store
const chatStore = createStore({
initialState: createInitialStreamingChatState(),
reducer: streamingChatReducer,
dependencies: {
sendMessage: async function*(message, signal) {
// Send to your LLM API (OpenAI, Anthropic, Ollama, etc.)
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message }),
signal
});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter(Boolean);
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
if (data.content) {
yield data.content;
}
}
}
}
}
}
});
<StandardStreamingChat {chatStore} />
MinimalStreamingChat:
StandardStreamingChat (recommended):
FullStreamingChat:
StreamingChat (legacy):
All variants accept:
chatStore: Store<StreamingChatState, StreamingChatAction> - Chat store (required)FullStreamingChat additional props:
showReactions: boolean - Enable reactions (default: true)showAttachments: boolean - Enable attachments (default: true)showVoiceInput: boolean - Enable voice input (default: true)interface StreamingChatState {
// Messages
messages: Message[];
streamingMessage: string | null; // Current streaming content
isStreaming: boolean;
// Input
inputValue: string;
inputDisabled: boolean;
// Attachments
attachments: MessageAttachment[];
isUploadingAttachment: boolean;
// UI State
isLoading: boolean;
error: string | null;
// Voice (if enabled)
isRecordingVoice: boolean;
voiceTranscript: string | null;
}
interface Message {
id: string;
role: 'user' | 'assistant' | 'system';
content: string;
timestamp: number;
attachments?: MessageAttachment[];
reactions?: MessageReaction[];
isEdited?: boolean;
isDeleted?: boolean;
}
interface MessageAttachment {
id: string;
type: 'image' | 'pdf' | 'audio' | 'file';
url: string;
name: string;
size: number;
metadata?: AttachmentMetadata;
}
type StreamingChatAction =
// Messaging
| { type: 'sendMessage'; content: string; attachments?: MessageAttachment[] }
| { type: 'streamingStarted' }
| { type: 'streamingChunk'; chunk: string }
| { type: 'streamingCompleted' }
| { type: 'streamingError'; error: string }
| { type: 'cancelStreaming' }
// Input
| { type: 'inputChanged'; value: string }
| { type: 'clearInput' }
// Messages
| { type: 'editMessage'; messageId: string; newContent: string }
| { type: 'deleteMessage'; messageId: string }
| { type: 'clearHistory' }
// Reactions
| { type: 'addReaction'; messageId: string; emoji: string }
| { type: 'removeReaction'; messageId: string; reactionId: string }
// Attachments
| { type: 'addAttachment'; attachment: MessageAttachment }
| { type: 'removeAttachment'; attachmentId: string }
| { type: 'uploadAttachment'; file: File }
// Voice
| { type: 'startVoiceRecording' }
| { type: 'stopVoiceRecording' }
| { type: 'voiceTranscriptionCompleted'; transcript: string };
interface StreamingChatDependencies {
// Message handler (required)
sendMessage: (
content: string,
attachments: MessageAttachment[],
signal: AbortSignal
) => AsyncGenerator<string, void, unknown>;
// File upload (optional)
uploadFile?: (file: File) => Promise<MessageAttachment>;
// Voice transcription (optional)
transcribeVoice?: (audioBlob: Blob) => Promise<string>;
}
<script lang="ts">
import { createStore, Effect } from '@composable-svelte/core';
import {
FullStreamingChat,
streamingChatReducer,
createInitialStreamingChatState,
type MessageAttachment
} from '@composable-svelte/chat';
// Create chat store with all features
const chatStore = createStore({
initialState: createInitialStreamingChatState(),
reducer: streamingChatReducer,
dependencies: {
// Stream from OpenAI
sendMessage: async function*(content, attachments, signal) {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${OPENAI_API_KEY}`
},
body: JSON.stringify({
model: 'gpt-4',
messages: [
{ role: 'user', content }
],
stream: true
}),
signal
});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter(line => line.trim().startsWith('data:'));
for (const line of lines) {
const data = line.replace('data: ', '');
if (data === '[DONE]') break;
try {
const parsed = JSON.parse(data);
const content = parsed.choices[0]?.delta?.content;
if (content) {
yield content;
}
} catch (e) {
// Skip invalid JSON
}
}
}
},
// Upload files to your storage
uploadFile: async (file: File) => {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const { url, id } = await response.json();
return {
id,
type: file.type.startsWith('image/') ? 'image' : 'file',
url,
name: file.name,
size: file.size
};
},
// Transcribe voice using Whisper
transcribeVoice: async (audioBlob: Blob) => {
const formData = new FormData();
formData.append('file', audioBlob, 'recording.webm');
formData.append('model', 'whisper-1');
const response = await fetch('https://api.openai.com/v1/audio/transcriptions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`
},
body: formData
});
const { text } = await response.json();
return text;
}
}
});
</script>
<div class="chat-container">
<FullStreamingChat
{chatStore}
showReactions={true}
showAttachments={true}
showVoiceInput={true}
/>
<!-- Error display -->
{#if $chatStore.error}
<div class="error-toast">{$chatStore.error}</div>
{/if}
</div>
Purpose: Real-time presence tracking, typing indicators, and live cursors for multi-user chat.
import { createStore } from '@composable-svelte/core';
import {
StandardStreamingChat,
collaborativeReducer,
createInitialCollaborativeState,
PresenceAvatarStack,
TypingIndicator
} from '@composable-svelte/chat';
// Create collaborative chat store
const chatStore = createStore({
initialState: createInitialCollaborativeState(),
reducer: collaborativeReducer,
dependencies: {
sendMessage: /* ... */,
connectWebSocket: (conversationId, userId, onMessage, onConnectionChange) => {
const ws = new WebSocket(`wss://api.example.com/chat/${conversationId}`);
ws.onopen = () => {
onConnectionChange({ status: 'connected', connectedAt: Date.now() });
ws.send(JSON.stringify({ type: 'join', userId }));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
onMessage(message);
};
ws.onclose = () => {
onConnectionChange({ status: 'disconnected' });
};
return () => ws.close();
},
sendWebSocketMessage: async (message) => {
ws.send(JSON.stringify(message));
}
}
});
// Connect to conversation
chatStore.dispatch({
type: 'connectToConversation',
conversationId: 'chat-123',
userId: 'user-456'
});
interface CollaborativeStreamingChatState extends StreamingChatState {
// Connection
connection: WebSocketConnectionState;
conversationId: string | null;
currentUserId: string | null;
// Users
users: Map<string, CollaborativeUser>;
// Sync
pendingActions: PendingAction[];
syncState: SyncState;
}
interface CollaborativeUser {
id: string;
name: string;
avatar?: string;
color: string;
presence: 'active' | 'idle' | 'away' | 'offline';
typing: TypingInfo | null;
cursor: CursorPosition | null;
permissions: UserPermissions;
lastSeen: number;
lastHeartbeat: number;
}
PresenceBadge:
<PresenceBadge presence="active" showText={true} />
PresenceAvatarStack:
import { PresenceAvatarStack, getActiveUsers } from '@composable-svelte/chat';
const activeUsers = $derived(getActiveUsers($chatStore.users, currentUserId));
<PresenceAvatarStack users={activeUsers} maxVisible={5} />
PresenceList:
<PresenceList users={activeUsers} groupByPresence={true} />
TypingIndicator:
import { TypingIndicator, getTypingUsers } from '@composable-svelte/chat';
const typingUsers = $derived(
getTypingUsers($chatStore.users, currentUserId, 'message')
);
<TypingIndicator users={typingUsers} />
TypingUsersList:
<TypingUsersList users={typingUsers} />
CursorMarker:
<CursorMarker
user={user}
position={user.cursor}
/>
CursorOverlay:
import { CursorOverlay, getCursorPositions } from '@composable-svelte/chat';
const cursorPositions = $derived(
getCursorPositions($chatStore.users, currentUserId)
);
<CursorOverlay cursors={cursorPositions} />
type CollaborativeAction =
// Connection
| { type: 'connectToConversation'; conversationId: string; userId: string }
| { type: 'disconnectFromConversation' }
| { type: 'connectionStateChanged'; state: WebSocketConnectionState }
// Users
| { type: 'userJoined'; user: CollaborativeUser }
| { type: 'userLeft'; userId: string }
| { type: 'userPresenceChanged'; userId: string; presence: UserPresence }
// Typing
| { type: 'userStartedTyping'; userId: string; info: TypingInfo }
| { type: 'userStoppedTyping'; userId: string }
// Cursors
| { type: 'userCursorMoved'; userId: string; position: CursorPosition }
// Sync
| { type: 'syncMessage'; messageId: string; action: string }
| { type: 'syncCompleted'; messageId: string };
<script lang="ts">
import { createStore } from '@composable-svelte/core';
import {
StandardStreamingChat,
collaborativeReducer,
createInitialCollaborativeState,
PresenceAvatarStack,
TypingIndicator,
getActiveUsers,
getTypingUsers,
type CollaborativeUser
} from '@composable-svelte/chat';
const currentUserId = 'user-123';
// Create WebSocket connection
let ws: WebSocket;
const chatStore = createStore({
initialState: createInitialCollaborativeState(),
reducer: collaborativeReducer,
dependencies: {
sendMessage: /* ... */,
connectWebSocket: (conversationId, userId, onMessage, onConnectionChange) => {
ws = new WebSocket(`wss://api.example.com/chat/${conversationId}`);
ws.onopen = () => {
onConnectionChange({ status: 'connected', connectedAt: Date.now() });
ws.send(JSON.stringify({
type: 'join',
userId,
user: {
id: userId,
name: 'Current User',
color: '#3b82f6'
}
}));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
onMessage(message);
};
ws.onerror = () => {
onConnectionChange({ status: 'error', error: 'Connection failed' });
};
ws.onclose = () => {
onConnectionChange({ status: 'disconnected' });
};
return () => {
ws.close();
};
},
sendWebSocketMessage: async (message) => {
if (ws?.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(message));
}
}
}
});
// Connect on mount
$effect(() => {
chatStore.dispatch({
type: 'connectToConversation',
conversationId: 'chat-room-123',
userId: currentUserId
});
return () => {
chatStore.dispatch({ type: 'disconnectFromConversation' });
};
});
// Emit typing events
let typingTimeout: ReturnType<typeof setTimeout>;
function handleInputChange(value: string) {
chatStore.dispatch({ type: 'inputChanged', value });
// Emit typing started
chatStore.dispatch({
type: 'userStartedTyping',
userId: currentUserId,
info: {
target: 'message',
startedAt: Date.now(),
lastUpdate: Date.now()
}
});
// Clear previous timeout
clearTimeout(typingTimeout);
// Stop typing after 2 seconds of inactivity
typingTimeout = setTimeout(() => {
chatStore.dispatch({ type: 'userStoppedTyping', userId: currentUserId });
}, 2000);
}
// Derived state
const activeUsers = $derived(getActiveUsers($chatStore.users, currentUserId));
const typingUsers = $derived(getTypingUsers($chatStore.users, currentUserId, 'message'));
</script>
<div class="collaborative-chat">
<!-- Presence indicators -->
<div class="chat-header">
<h2>Team Chat</h2>
<PresenceAvatarStack users={activeUsers} maxVisible={5} />
</div>
<!-- Chat interface -->
<StandardStreamingChat {chatStore} />
<!-- Typing indicator -->
{#if typingUsers.length > 0}
<TypingIndicator users={typingUsers} />
{/if}
<!-- Connection status -->
<div class="connection-status">
{$chatStore.connection.status}
{#if $chatStore.connection.status === 'connected'}
<span class="indicator active"></span>
{:else}
<span class="indicator inactive"></span>
{/if}
</div>
</div>
Purpose: Low-level WebSocket management for custom implementations.
import {
WebSocketManager,
createWebSocketManager,
type WebSocketConfig
} from '@composable-svelte/chat';
const config: WebSocketConfig = {
url: 'wss://api.example.com/chat',
reconnect: true,
reconnectInterval: 1000,
maxReconnectAttempts: 10,
heartbeatInterval: 30000
};
const manager = createWebSocketManager(config);
// Connect
manager.connect();
// Send message
manager.send({ type: 'message', content: 'Hello' });
// Listen for messages
manager.onMessage((message) => {
console.log('Received:', message);
});
// Listen for connection changes
manager.onConnectionChange((state) => {
console.log('Connection:', state.status);
});
// Disconnect
manager.disconnect();
Purpose: Testing and development without backend.
import { createMockStreamingChat } from '@composable-svelte/chat';
const chatStore = createStore({
initialState: createInitialStreamingChatState(),
reducer: streamingChatReducer,
dependencies: createMockStreamingChat({
responseDelay: 50, // Delay between chunks (ms)
mockResponses: [
'This is a mock response.',
'It simulates streaming behavior.'
]
})
});
When to use each variant:
MinimalStreamingChat:
StandardStreamingChat (recommended):
FullStreamingChat:
Collaborative Features:
Related Skills:
When to Use Each Package:
import { TestStore } from '@composable-svelte/core';
import {
streamingChatReducer,
createInitialStreamingChatState,
createMockStreamingChat
} from '@composable-svelte/chat';
const store = new TestStore({
initialState: createInitialStreamingChatState(),
reducer: streamingChatReducer,
dependencies: createMockStreamingChat()
});
// Test message send
await store.send({
type: 'sendMessage',
content: 'Hello',
attachments: []
});
await store.receive({ type: 'streamingStarted' }, (state) => {
expect(state.isStreaming).toBe(true);
});
await store.receive({ type: 'streamingChunk', chunk: 'Hi' }, (state) => {
expect(state.streamingMessage).toBe('Hi');
});
await store.receive({ type: 'streamingCompleted' }, (state) => {
expect(state.isStreaming).toBe(false);
expect(state.messages.length).toBe(2); // User + assistant
});
import { TestStore } from '@composable-svelte/core';
import {
collaborativeReducer,
createInitialCollaborativeState
} from '@composable-svelte/chat';
const store = new TestStore({
initialState: createInitialCollaborativeState(),
reducer: collaborativeReducer,
dependencies: {
sendMessage: async function*() {
yield 'Test response';
},
connectWebSocket: vi.fn(),
sendWebSocketMessage: vi.fn()
}
});
// Test user join
await store.send({
type: 'userJoined',
user: {
id: 'user-2',
name: 'Alice',
color: '#3b82f6',
presence: 'active'
}
}, (state) => {
expect(state.users.size).toBe(1);
expect(state.users.get('user-2')?.name).toBe('Alice');
});
// Test typing indicator
await store.send({
type: 'userStartedTyping',
userId: 'user-2',
info: {
target: 'message',
startedAt: Date.now(),
lastUpdate: Date.now()
}
}, (state) => {
expect(state.users.get('user-2')?.typing).toBeTruthy();
});
Streaming not working:
WebSocket connection failing:
Markdown not rendering:
Collaborative features not syncing:
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience