useChat
The useChat hook provides real-time AI chat with SSE (Server-Sent Events) streaming, conversation management, and automatic state updates. Perfect for building chat interfaces with streaming responses.
Import
import { useChat } from '@enterpriseaigroup/core';
Basic Usage
import { useChat } from '@enterpriseaigroup/core';
function ChatInterface() {
const { messages, sendMessage, isStreaming } = useChat();
const handleSend = (text: string) => {
sendMessage(text);
};
return (
<div>
{messages.map(msg => (
<div key={msg.id}>
<strong>{msg.role}:</strong> {msg.content}
</div>
))}
{isStreaming && <div>AI is typing...</div>}
</div>
);
}
Signature
useChat(options?: UseChatOptions): UseChatResult
Parameters
UseChatOptions
| Property | Type | Required | Description |
|---|---|---|---|
conversationId | string | No | Existing conversation ID to load |
systemPrompt | string | No | System prompt for AI behavior |
temperature | number | No | Response creativity (0-1, default: 0.7) |
maxTokens | number | No | Maximum response length |
context | ChatContext | No | Additional context (resources, documents) |
onMessage | (msg: Message) => void | No | Callback when message received |
onError | (error: Error) => void | No | Error callback |
initialMessages | Message[] | No | Pre-populate messages |
Return Value
UseChatResult
| Property | Type | Description |
|---|---|---|
messages | Message[] | Array of conversation messages |
sendMessage | (text: string, options?) => Promise<void> | Send a message |
isStreaming | boolean | True while AI is responding |
error | Error | null | Error object if request failed |
conversationId | string | null | Current conversation ID |
clearMessages | () => void | Clear all messages |
regenerate | () => Promise<void> | Regenerate last AI response |
stop | () => void | Stop current streaming response |
citations | Citation[] | Citations from last response |
Message Interface
interface Message {
id: string;
role: 'user' | 'assistant' | 'system';
content: string;
timestamp: string;
citations?: Citation[];
metadata?: Record<string, any>;
}
interface Citation {
source: string;
sourceType: 'document' | 'resource' | 'web';
content: string;
confidence: number;
metadata?: Record<string, any>;
}
Sending Messages
Basic Message
function ChatInput() {
const { sendMessage, isStreaming } = useChat();
const [input, setInput] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || isStreaming) return;
sendMessage(input);
setInput('');
};
return (
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={e => setInput(e.target.value)}
disabled={isStreaming}
/>
<button type="submit" disabled={isStreaming || !input.trim()}>
Send
</button>
</form>
);
}
Message with Options
const { sendMessage } = useChat();
// Send with custom options
await sendMessage('Analyze this project', {
temperature: 0.3,
context: {
resources: [{ type: 'project', id: 'proj_123' }],
documents: ['doc_abc']
}
});
Displaying Messages
Simple Message List
function MessageList() {
const { messages, isStreaming } = useChat();
return (
<div className="messages">
{messages.map(msg => (
<div key={msg.id} className={`message ${msg.role}`}>
<div className="role">{msg.role}</div>
<div className="content">{msg.content}</div>
<div className="timestamp">
{new Date(msg.timestamp).toLocaleTimeString()}
</div>
</div>
))}
{isStreaming && (
<div className="message assistant">
<div className="typing-indicator">●●●</div>
</div>
)}
</div>
);
}
With Citations
function MessageWithCitations({ message }: { message: Message }) {
return (
<div className="message">
<div className="content">{message.content}</div>
{message.citations && message.citations.length > 0 && (
<div className="citations">
<h4>Sources:</h4>
{message.citations.map((citation, i) => (
<div key={i} className="citation">
<strong>[{i + 1}]</strong> {citation.source}
<span className="confidence">
{(citation.confidence * 100).toFixed(0)}% relevant
</span>
</div>
))}
</div>
)}
</div>
);
}
Streaming Responses
The hook automatically handles SSE streaming and updates messages in real-time:
function StreamingChat() {
const { messages, sendMessage, isStreaming } = useChat();
// Messages update automatically as chunks arrive
const lastMessage = messages[messages.length - 1];
return (
<div>
{messages.map(msg => (
<div key={msg.id}>{msg.content}</div>
))}
{isStreaming && (
<div>
<span>AI is responding</span>
<div className="streaming-indicator">
{/* Show animation while streaming */}
</div>
</div>
)}
</div>
);
}
Conversation Management
Loading Existing Conversation
function ConversationView({ conversationId }: { conversationId: string }) {
const { messages, sendMessage } = useChat({
conversationId
});
// Messages are automatically loaded
return (
<div>
<h3>Conversation: {conversationId}</h3>
{messages.map(msg => (
<div key={msg.id}>{msg.content}</div>
))}
</div>
);
}
Clearing Messages
function ChatControls() {
const { clearMessages, messages } = useChat();
return (
<button
onClick={clearMessages}
disabled={messages.length === 0}
>
Clear Chat
</button>
);
}
Context and RAG
Adding Resource Context
function ProjectChat({ projectId }: { projectId: string }) {
const { sendMessage } = useChat({
context: {
resources: [{ type: 'project', id: projectId }]
}
});
// AI has automatic access to project data
return (
<button onClick={() => sendMessage('What is the project status?')}>
Ask AI about this project
</button>
);
}
Adding Document Context
function DocumentChat({ documentIds }: { documentIds: string[] }) {
const { sendMessage } = useChat({
context: {
documents: documentIds,
includeHistory: true
}
});
return (
<button onClick={() => sendMessage('Summarize these documents')}>
Get Summary
</button>
);
}
System Prompts
function SpecializedAssistant() {
const { sendMessage } = useChat({
systemPrompt: `You are a sales analyst assistant.
Always provide specific numbers and metrics.
Focus on actionable insights.
Format responses with clear sections.`
});
return (
<button onClick={() => sendMessage('Analyze the sales pipeline')}>
Analyze Sales
</button>
);
}
Regenerating Responses
function RegenerateButton() {
const { regenerate, isStreaming, messages } = useChat();
const canRegenerate =
!isStreaming &&
messages.length > 0 &&
messages[messages.length - 1].role === 'assistant';
return (
<button
onClick={regenerate}
disabled={!canRegenerate || isStreaming}
>
Regenerate Response
</button>
);
}
Stopping Streaming
function StopButton() {
const { stop, isStreaming } = useChat();
if (!isStreaming) return null;
return (
<button onClick={stop}>
Stop Generating
</button>
);
}
Error Handling
function ChatWithErrorHandling() {
const { messages, sendMessage, error, isStreaming } = useChat({
onError: (error) => {
console.error('Chat error:', error);
toast.error(error.message);
}
});
return (
<div>
{error && (
<div className="error-banner">
Error: {error.message}
<button onClick={() => sendMessage('retry')}>Retry</button>
</div>
)}
{/* Rest of chat UI */}
</div>
);
}
Event Callbacks
function ChatWithCallbacks() {
const { sendMessage } = useChat({
onMessage: (message) => {
console.log('New message:', message);
// Track analytics
if (message.role === 'assistant') {
analytics.track('ai_response_received', {
messageId: message.id,
length: message.content.length
});
}
},
onError: (error) => {
console.error('Chat error:', error);
Sentry.captureException(error);
}
});
return <div>{/* Chat UI */}</div>;
}
Complete Example
Full-featured chat component:
import { useChat } from '@enterpriseaigroup/core';
import { useState, useRef, useEffect } from 'react';
function ChatInterface() {
const {
messages,
sendMessage,
isStreaming,
error,
clearMessages,
regenerate,
stop,
citations
} = useChat({
systemPrompt: 'You are a helpful assistant for project management.',
temperature: 0.7,
onMessage: (msg) => {
// Auto-scroll to new messages
scrollToBottom();
}
});
const [input, setInput] = useState('');
const messagesEndRef = useRef<HTMLDivElement>(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || isStreaming) return;
await sendMessage(input);
setInput('');
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSubmit(e);
}
};
return (
<div className="chat-container">
<div className="chat-header">
<h2>AI Assistant</h2>
<button onClick={clearMessages} disabled={messages.length === 0}>
Clear
</button>
</div>
{error && (
<div className="error-banner">
<span>{error.message}</span>
<button onClick={() => regenerate()}>Retry</button>
</div>
)}
<div className="messages-container">
{messages.map((msg) => (
<div key={msg.id} className={`message ${msg.role}`}>
<div className="message-header">
<span className="role">
{msg.role === 'user' ? 'You' : 'AI'}
</span>
<span className="timestamp">
{new Date(msg.timestamp).toLocaleTimeString()}
</span>
</div>
<div className="message-content">{msg.content}</div>
{msg.citations && msg.citations.length > 0 && (
<div className="citations">
<details>
<summary>
{msg.citations.length} source(s)
</summary>
{msg.citations.map((citation, i) => (
<div key={i} className="citation">
<strong>[{i + 1}]</strong> {citation.source}
<br />
<small>{citation.content}</small>
</div>
))}
</details>
</div>
)}
</div>
))}
{isStreaming && (
<div className="message assistant">
<div className="typing-indicator">
<span>●</span>
<span>●</span>
<span>●</span>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
<form onSubmit={handleSubmit} className="input-form">
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Ask me anything..."
disabled={isStreaming}
rows={3}
/>
<div className="input-actions">
{isStreaming ? (
<button type="button" onClick={stop}>
Stop
</button>
) : (
<button type="submit" disabled={!input.trim()}>
Send
</button>
)}
</div>
</form>
</div>
);
}
export default ChatInterface;
Performance Tips
- Memoize callbacks: Use
useCallbackfor message handlers - Virtual scrolling: Use virtual lists for long conversations
- Lazy load history: Load conversation history on demand
- Debounce typing indicators: Show "typing" after a short delay
Next Steps
- Learn about useDocuments hook
- Explore Chat module for SDK usage
- See Chat API endpoints
- Read AI integration guide