Skip to main content

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

PropertyTypeRequiredDescription
conversationIdstringNoExisting conversation ID to load
systemPromptstringNoSystem prompt for AI behavior
temperaturenumberNoResponse creativity (0-1, default: 0.7)
maxTokensnumberNoMaximum response length
contextChatContextNoAdditional context (resources, documents)
onMessage(msg: Message) => voidNoCallback when message received
onError(error: Error) => voidNoError callback
initialMessagesMessage[]NoPre-populate messages

Return Value

UseChatResult

PropertyTypeDescription
messagesMessage[]Array of conversation messages
sendMessage(text: string, options?) => Promise<void>Send a message
isStreamingbooleanTrue while AI is responding
errorError | nullError object if request failed
conversationIdstring | nullCurrent conversation ID
clearMessages() => voidClear all messages
regenerate() => Promise<void>Regenerate last AI response
stop() => voidStop current streaming response
citationsCitation[]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

  1. Memoize callbacks: Use useCallback for message handlers
  2. Virtual scrolling: Use virtual lists for long conversations
  3. Lazy load history: Load conversation history on demand
  4. Debounce typing indicators: Show "typing" after a short delay

Next Steps