Chat Implementation
This guide walks you through implementing a complete text chat interface with Dasha 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',
name?: string | null,
// For user messages:
segmentId?: string,
type?: 'potential' | 'final' | 'confident',
startISOTimes?: string,
endISOTimes?: string | null,
// For assistant messages (voice calls):
synthStartISOTimes?: string | null, // TTS synthesis start
synthEndISOTimes?: string | null, // TTS synthesis end
playStartISOTimes?: string | null, // Audio playback start
playEndISOTimes?: string | null // Audio playback end
}
}
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>Dasha 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