Chat Implementation
This guide walks you through implementing a complete text chat interface with BlackBox agents using WebSockets. You’ll learn how to establish connections, send messages, receive responses, and handle the full conversation lifecycle.Prerequisites
Before starting, ensure you have:- Web Integration Token: Generated from your agent’s web integration settings
- Agent Configured: Agent created and enabled with chat capabilities
- Development Environment: Node.js, browser, or your preferred runtime
Quick Setup: If you haven’t created a web integration yet, see Web Widget Embedding for instructions on creating integrations and generating tokens.
Step 1: Install Dependencies
You only need a WebSocket client library:- Browser: Native
WebSocketAPI (no installation needed) - Node.js:
wspackage (npm install ws)
Step 2: Create Connection
Copy
Ask AI
class ChatConnection {
private ws: WebSocket | null = null;
private messageHandlers: Map<string, Function> = new Map();
constructor(
private serverUrl: string,
private token: string
) {}
connect() {
const wsUrl = `wss://${this.serverUrl.replace(/^https?:\/\//, '')}/api/v1/ws/webCall?token=${encodeURIComponent(this.token)}`;
this.ws = new WebSocket(wsUrl);
this.ws.onopen = () => {
// Send initialization message
this.send({
type: 'initialize',
timestamp: new Date().toISOString(),
request: {
callType: 'chat',
additionalData: {}
}
});
};
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleMessage(message);
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
this.ws.onclose = () => {
console.log('WebSocket closed');
};
}
private handleMessage(message: any) {
switch (message.type) {
case 'event':
if (message.name === 'connection') {
console.log('Connection established');
}
break;
case 'text':
const text = message.content?.text;
if (text && message.content?.source === 'assistant') {
// Agent message received
this.onMessage?.(text);
}
break;
case 'error':
console.error('Error:', message);
break;
case 'conversationResult':
console.log('Conversation ended:', message.result);
break;
}
}
sendMessage(text: string) {
this.send({
type: 'incomingChatMessage',
content: text,
timestamp: new Date().toISOString()
});
}
private send(data: any) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
}
}
disconnect() {
this.send({
type: 'terminate',
timestamp: new Date().toISOString()
});
this.ws?.close();
}
onMessage?: (text: string) => void;
}
Step 3: Send Messages
Copy
Ask AI
// Send message
chatConnection.sendMessage('Hello, agent!');
// Or directly via WebSocket
ws.send(JSON.stringify({
type: 'incomingChatMessage',
content: 'Hello, agent!',
timestamp: new Date().toISOString()
}));
Step 4: Receive Messages
Message Types
Chat conversations receive several message types: Text Messages (text type):
Copy
Ask AI
{
type: 'text',
timestamp: '2025-01-20T10:00:00Z',
content: {
source: 'assistant' | 'user',
text: 'Message content',
segmentId?: string,
type?: 'potential' | 'final' | 'confident'
}
}
event type):
Copy
Ask AI
{
type: 'event',
name: 'connection' | 'opened' | 'closed',
timestamp: '2025-01-20T10:00:00Z'
}
conversationResult type):
Copy
Ask AI
{
type: 'conversationResult',
result: {
// Conversation summary and metadata
},
timestamp: '2025-01-20T10:00:00Z'
}
Handling Messages
Copy
Ask AI
// Handle incoming WebSocket messages
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
switch (message.type) {
case 'text':
const content = message.content;
if (content.source === 'assistant') {
// Agent message
displayAgentMessage(content.text);
addMessageToUI(content.text || '', 'agent');
} else if (content.source === 'user') {
// User message (echo of what you sent)
displayUserMessage(content.text);
}
break;
case 'toolCall':
// Agent is calling a tool
displayToolCall(message);
break;
case 'toolCallResult':
// Tool execution result
displayToolResult(message);
break;
case 'event':
// Handle connection events
if (message.name === 'connection') {
console.log('Session ready');
}
break;
}
};
Step 5: Complete Chat Example
Here’s a complete, production-ready chat implementation:- React
- Vanilla JavaScript
- Node.js
Copy
Ask AI
import React, { useState, useEffect, useRef } from 'react';
interface Message {
id: string;
text: string;
sender: 'user' | 'agent';
timestamp: Date;
}
export function ChatWidget() {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const [isConnected, setIsConnected] = useState(false);
const wsRef = useRef<WebSocket | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// Connect to WebSocket
const ws = new WebSocket(
'wss://blackbox.dasha.ai/api/v1/ws/webCall?token=YOUR_WEB_INTEGRATION_TOKEN'
);
wsRef.current = ws;
ws.onopen = () => {
setIsConnected(true);
// Send initialization message
ws.send(JSON.stringify({
type: 'initialize',
timestamp: new Date().toISOString(),
request: {
callType: 'chat',
additionalData: {}
}
}));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'text' && message.content.source === 'assistant') {
addMessage(message.content.text || '', 'agent');
} else if (message.type === 'event' && message.name === 'connection') {
setIsConnected(true);
} else if (message.type === 'error') {
console.error('Chat error:', message);
addMessage('Sorry, an error occurred. Please try again.', 'agent');
}
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
setIsConnected(false);
};
ws.onclose = () => {
setIsConnected(false);
};
return () => {
if (wsRef.current) {
wsRef.current.close();
}
};
}, []);
const addMessage = (text: string, sender: 'user' | 'agent') => {
setMessages(prev => [...prev, {
id: `${Date.now()}-${Math.random()}`,
text,
sender,
timestamp: new Date()
}]);
};
const sendMessage = () => {
if (!input.trim() || !wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return;
const text = input.trim();
addMessage(text, 'user');
// Send message via WebSocket
wsRef.current.send(JSON.stringify({
type: 'incomingChatMessage',
content: text,
timestamp: new Date().toISOString()
}));
setInput('');
};
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
return (
<div className="chat-widget">
<div className="chat-header">
<h3>Chat with Agent</h3>
<div className={`status ${isConnected ? 'connected' : 'disconnected'}`}>
{isConnected ? '● Connected' : '○ Disconnected'}
</div>
</div>
<div className="chat-messages">
{messages.map(msg => (
<div key={msg.id} className={`message ${msg.sender}`}>
<div className="message-content">{msg.text}</div>
<div className="message-time">
{msg.timestamp.toLocaleTimeString()}
</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
<div className="chat-input">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
}}
placeholder="Type a message..."
disabled={!isConnected}
/>
<button onClick={sendMessage} disabled={!isConnected || !input.trim()}>
Send
</button>
</div>
</div>
);
}
Copy
Ask AI
<!DOCTYPE html>
<html>
<head>
<title>BlackBox Chat</title>
<style>
.chat-container {
max-width: 600px;
margin: 0 auto;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}
.chat-messages {
height: 400px;
overflow-y: auto;
padding: 16px;
background: #f5f5f5;
}
.message {
margin-bottom: 12px;
padding: 8px 12px;
border-radius: 8px;
max-width: 80%;
}
.message.user {
background: #007bff;
color: white;
margin-left: auto;
}
.message.agent {
background: white;
border: 1px solid #ddd;
}
.chat-input {
display: flex;
padding: 16px;
border-top: 1px solid #ddd;
}
.chat-input input {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.chat-input button {
margin-left: 8px;
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="chat-container">
<div class="chat-messages" id="messages"></div>
<div class="chat-input">
<input type="text" id="input" placeholder="Type a message...">
<button id="send">Send</button>
</div>
</div>
<script type="module">
const messagesDiv = document.getElementById('messages');
const input = document.getElementById('input');
const sendButton = document.getElementById('send');
function addMessage(text, sender) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender}`;
messageDiv.textContent = text;
messagesDiv.appendChild(messageDiv);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
const ws = new WebSocket(
'wss://blackbox.dasha.ai/api/v1/ws/webCall?token=YOUR_WEB_INTEGRATION_TOKEN'
);
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'initialize',
timestamp: new Date().toISOString(),
request: {
callType: 'chat',
additionalData: {}
}
}));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'text' && message.content.source === 'assistant') {
addMessage(message.content.text, 'agent');
} else if (message.type === 'error') {
addMessage('Error: ' + (message.data?.message || message.message || 'Unknown error'), 'agent');
}
};
sendButton.addEventListener('click', () => {
const text = input.value.trim();
if (text && ws.readyState === WebSocket.OPEN) {
addMessage(text, 'user');
ws.send(JSON.stringify({
type: 'incomingChatMessage',
content: text,
timestamp: new Date().toISOString()
}));
input.value = '';
}
});
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendButton.click();
}
});
</script>
</body>
</html>
Copy
Ask AI
const WebSocket = require('ws');
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const ws = new WebSocket(
'wss://blackbox.dasha.ai/api/v1/ws/webCall?token=YOUR_WEB_INTEGRATION_TOKEN'
);
let isConnected = false;
ws.on('open', () => {
isConnected = true;
console.log('[Status] Connected');
// Send initialization message
ws.send(JSON.stringify({
type: 'initialize',
timestamp: new Date().toISOString(),
request: {
callType: 'chat',
additionalData: {}
}
}));
console.log('Chat started! Type your messages (Ctrl+C to exit)\n');
rl.setPrompt('You: ');
rl.prompt();
});
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
if (message.type === 'text' && message.content.source === 'assistant') {
console.log(`\n[Agent] ${message.content.text}\n`);
rl.prompt();
} else if (message.type === 'event' && message.name === 'connection') {
console.log('[Status] Session ready');
} else if (message.type === 'error') {
console.error('[Error]', message);
}
});
ws.on('error', (error) => {
console.error('[Error]', error);
});
ws.on('close', () => {
isConnected = false;
console.log('[Status] Disconnected');
});
rl.on('line', (input) => {
const text = input.trim();
if (text && isConnected && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
type: 'incomingChatMessage',
content: text,
timestamp: new Date().toISOString()
}));
}
rl.prompt();
});
rl.on('close', () => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
type: 'terminate',
timestamp: new Date().toISOString()
}));
ws.close();
}
process.exit(0);
});
Step 6: Handle Connection States
Monitor and handle connection states for better UX:Copy
Ask AI
let connectionStatus: 'connecting' | 'open' | 'closed' | 'error' = 'closed';
const ws = new WebSocket('wss://blackbox.dasha.ai/api/v1/ws/webCall?token=YOUR_WEB_INTEGRATION_TOKEN');
ws.onopen = () => {
connectionStatus = 'open';
showStatus('Connected');
enableInput();
hideRetryButton();
};
ws.onclose = () => {
connectionStatus = 'closed';
showStatus('Disconnected');
disableInput();
showRetryButton();
};
ws.onerror = () => {
connectionStatus = 'error';
showStatus('Connection error');
disableInput();
showRetryButton();
};
// Track connecting state
connectionStatus = 'connecting';
showStatus('Connecting...');
disableInput();
Step 7: Handle Errors
Implement robust error handling:Copy
Ask AI
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'error') {
// Parse error message
const errorMessage = message.data?.message || message.message || 'An unexpected error occurred';
console.error('Error details:', message);
// Show user-friendly error message
showErrorMessage(errorMessage);
// Optionally retry connection
if (shouldRetry(message)) {
setTimeout(() => {
ws.close();
connect(); // Reconnect function
}, 3000);
}
}
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
showErrorMessage('Connection error occurred');
};
Step 8: Graceful Disconnection
Properly terminate conversations:Copy
Ask AI
let conversationResult: any = null;
// Listen for conversation result
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'conversationResult') {
conversationResult = message.result;
}
};
// Option 1: Immediate disconnect
ws.close();
// Option 2: Graceful end (wait for conversation result)
function gracefulEnd(timeoutMs: number = 3000): Promise<'result' | 'timeout' | 'error'> {
return new Promise((resolve) => {
// Send terminate message
ws.send(JSON.stringify({
type: 'terminate',
timestamp: new Date().toISOString()
}));
// Wait for conversation result
const timeout = setTimeout(() => {
resolve('timeout');
ws.close();
}, timeoutMs);
const checkResult = () => {
if (conversationResult) {
clearTimeout(timeout);
resolve('result');
ws.close();
}
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'conversationResult') {
conversationResult = message.result;
checkResult();
}
};
});
}
// Usage
const result = await gracefulEnd(3000);
if (result === 'result') {
console.log('Conversation completed:', conversationResult);
} else if (result === 'timeout') {
console.log('Timeout waiting for result');
} else {
console.log('Error during graceful end');
}
Advanced Features
Message History
Maintain and access full conversation history:Copy
Ask AI
const messageHistory: any[] = [];
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
messageHistory.push(message);
// Filter by type
const textMessages = messageHistory.filter(msg => msg.type === 'text');
const toolCalls = messageHistory.filter(msg => msg.type === 'toolCall');
// Access conversation result
const result = messageHistory.find(msg => msg.type === 'conversationResult')?.result;
};
Streaming Responses
Handle streaming/partial messages:Copy
Ask AI
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'text') {
const content = message.content;
// Handle partial messages
if (content.type === 'potential') {
// Agent is still thinking (streaming)
updatePartialMessage(content.text);
} else if (content.type === 'final' || content.type === 'confident') {
// Final message
finalizeMessage(content.text);
}
}
};
Custom Data
Pass custom context to agent:Copy
Ask AI
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'initialize',
timestamp: new Date().toISOString(),
request: {
callType: 'chat',
additionalData: {
userId: 'user123',
productId: 'prod456',
sessionData: {
page: '/products',
referrer: 'google.com'
}
}
}
}));
};
Troubleshooting
Connection Fails
Symptoms: Connection status stays atconnecting or goes to error
Solutions:
- Verify token is valid and not expired
- Check server URL is correct
- Ensure HTTPS/WSS is used (not HTTP/WS)
- Check network/firewall allows WebSocket connections
- Verify agent is enabled and configured
Messages Not Received
Symptoms: Messages sent but no response from agent Solutions:- Check WebSocket message handler is registered
- Verify WebSocket
readyStateisOPENbefore sending - Check browser console for WebSocket errors
- Ensure agent has valid LLM configuration
- Verify
AllowWebChatfeature is enabled
Connection Drops
Symptoms: Connection closes unexpectedly Solutions:- Implement reconnection logic
- Check for network interruptions
- Monitor WebSocket connection state (
readyState) - Handle WebSocket
oncloseandonerrorevents - Send
terminatemessage before closing connection
Next Steps
- Voice Call Implementation - Add WebRTC voice calls
- Tool Execution - Handle agent tool calls
- Message Reference - Complete message documentation
- Best Practices - Production patterns