Chat operations
Written By Stanislas
Last updated 16 days ago
Send messages to your AI agent, manage responses, and control the conversation flow. Learn how to send messages with streaming, regenerate responses, stop generation, and use text-to-speech features.
Overview
Chat operations are the core interactions in a conversation. You can send messages to trigger AI responses, listen to streaming replies in real-time, regenerate unsatisfactory responses, stop long-running generations, and convert responses to audio. This guide covers all the mutations and queries you need for a complete chat experience.
Each message sent can optionally trigger an AI response. Responses stream back character by character, giving users immediate feedback. You can also use synchronous requests if you need the complete response before proceeding.
Prerequisites
Before performing chat operations, ensure you have:
Authenticated with the API β Follow the Authentication & Setup guide to get your
accessTokenandworkspaceIdCreated or retrieved a session β Follow the Managing chat sessions guide to get a
sessionIdConfigured your GraphQL client β Your Apollo Client must include the required authentication headers
Set up subscriptions (optional) β For real-time streaming, follow the Real-time subscriptions guide
Getting started
Here's the minimal setup to send a message and receive a response:
Step 1: Send a message
Send a message to the bot and trigger a streaming response.
import { gql } from '@apollo/client';
const SEND_MESSAGE = gql`
mutation SendNewMessage($newMessageData: NewMessageInput!) {
sendNewMessage(newMessageData: $newMessageData) {
id
message
createdAt
sessionId
sentBy {
id
firstName
}
isBotReply
}
}
`;
const sendMessage = async (client, sessionId, messageText) => {
const { data } = await client.mutate({
mutation: SEND_MESSAGE,
variables: {
newMessageData: {
message: messageText,
sessionId: sessionId,
isForAiReply: true, // Trigger AI response
},
},
});
return data.sendNewMessage;
};
// Usage
const userMessage = await sendMessage(client, 67890, 'What is your pricing?');
console.log('Message sent:', userMessage.id);
Step 2: Subscribe to streaming responses
Listen to the AI response as it streams in real-time.
const MESSAGE_STREAM = gql`
subscription OnMessageStream($sessionId: Float!) {
onMessageStream(sessionId: $sessionId) {
messageChunk
botResponseMessageId
isStoppable
}
}
`;
const subscribeToStream = (client, sessionId, onChunk) => {
let fullMessage = '';
return client
.subscribe({
query: MESSAGE_STREAM,
variables: { sessionId },
})
.subscribe({
next: ({ data }) => {
fullMessage += data.onMessageStream.messageChunk;
onChunk(fullMessage, data.onMessageStream);
},
error: (error) => {
console.error('Stream error:', error);
},
complete: () => {
console.log('Stream complete');
},
});
};
// Usage
const subscription = subscribeToStream(client, 67890, (fullMessage, data) => {
console.log('Streaming:', fullMessage);
if (data.isStoppable) {
console.log('User can stop generation');
}
});
// Cleanup when done
// subscription.unsubscribe();
Step 3: Stop generation if needed
If the response is taking too long, stop the generation.
const STOP_AI_RESPONSE = gql`
mutation StopAIResponse($sessionId: Float!, $botId: Float!) {
stopAIResponse(sessionId: $sessionId, botId: $botId)
}
`;
const stopGeneration = async (client, sessionId, botId) => {
const { data } = await client.mutate({
mutation: STOP_AI_RESPONSE,
variables: { sessionId, botId },
});
return data.stopAIResponse;
};
// Usage
const stopped = await stopGeneration(client, 67890, 123);
console.log('Generation stopped:', stopped);
Sending messages
Send a new message
Send a message to the session and optionally trigger an AI response.
GraphQL Mutation:
mutation SendNewMessage($newMessageData: NewMessageInput!) {
sendNewMessage(newMessageData: $newMessageData) {
id
message
createdAt
sessionId
sentBy {
id
firstName
lastName
}
isBotReply
}
}
Input parameters:
Returns: The created message object
Basic example:
const SEND_MESSAGE = gql`
mutation SendNewMessage($newMessageData: NewMessageInput!) {
sendNewMessage(newMessageData: $newMessageData) {
id
message
createdAt
isBotReply
}
}
`;
// Simple message without AI response
const message1 = await client.mutate({
mutation: SEND_MESSAGE,
variables: {
newMessageData: {
message: 'Just saving this note',
sessionId: 67890,
},
},
});
// Message that triggers AI response
const message2 = await client.mutate({
mutation: SEND_MESSAGE,
variables: {
newMessageData: {
message: 'What can you do for me?',
sessionId: 67890,
isForAiReply: true,
},
},
});
With file attachments:
const sendMessageWithFiles = async (client, sessionId, messageText, fileIds) => {
const { data } = await client.mutate({
mutation: SEND_MESSAGE,
variables: {
newMessageData: {
message: messageText,
sessionId,
fileIds, // Array of file IDs
isForAiReply: true,
},
},
});
return data.sendNewMessage;
};
// Usage
const message = await sendMessageWithFiles(
client,
67890,
'Please analyze these documents',
[101, 102, 103]
);
With threading (reply to specific message):
const sendReplyToMessage = async (client, sessionId, messageText, parentMessageId) => {
const { data } = await client.mutate({
mutation: SEND_MESSAGE,
variables: {
newMessageData: {
message: messageText,
sessionId,
parentMessageId, // Reply to this message
isForAiReply: true,
},
},
});
return data.sendNewMessage;
};
// Usage
const reply = await sendReplyToMessage(
client,
67890,
'Can you elaborate on that?',
12345 // ID of the message to reply to
);
Using a predefined prompt:
const sendWithPrompt = async (client, sessionId, botPromptId) => {
const { data } = await client.mutate({
mutation: SEND_MESSAGE,
variables: {
newMessageData: {
sessionId,
botPromptId, // Use a predefined prompt instead of typing
isForAiReply: true,
},
},
});
return data.sendNewMessage;
};
// Usage - useful for quick actions like "Summarize" or "Translate"
const summary = await sendWithPrompt(client, 67890, 456);
Synchronous requests
Send message and wait for complete response
For simple Q&A without streaming, use the synchronous endpoint to get the complete response immediately.
GraphQL Mutation:
mutation SyncBotRequest($newMessageData: NewMessageInput!) {
syncBotRequest(newMessageData: $newMessageData) {
text
botId
botSlug
isBotError
sessionId
files
sourceMetadatas
sources
responseJson
jobId
}
}
Input parameters: Same as SendNewMessage
Returns: Complete response with metadata
Example:
const SYNC_BOT_REQUEST = gql`
mutation SyncBotRequest($newMessageData: NewMessageInput!) {
syncBotRequest(newMessageData: $newMessageData) {
text
isBotError
sources
botSlug
}
}
`;
const syncMessage = async (client, sessionId, messageText) => {
const { data } = await client.mutate({
mutation: SYNC_BOT_REQUEST,
variables: {
newMessageData: {
message: messageText,
sessionId,
},
},
});
return data.syncBotRequest;
};
// Usage
const response = await syncMessage(client, 67890, 'What is 2 + 2?');
console.log('Bot response:', response.text);
console.log('Sources used:', response.sources);
if (response.isBotError) {
console.error('Bot encountered an error');
}
When to use synchronous requests:
Simple Q&A where you need the complete answer before proceeding
Batch processing multiple messages
When you don't need real-time streaming feedback
API integrations where you need the full response immediately
Managing responses
Regenerate a response
If the bot's response wasn't satisfactory, regenerate it.
GraphQL Mutation:
mutation RegenerateResponse($messageId: Float!) {
regenerateResponse(messageId: $messageId) {
id
message
createdAt
}
}
Parameters:
messageId(required) β ID of the bot's response message to regenerate
Returns: The new response message
Example:
const REGENERATE_RESPONSE = gql`
mutation RegenerateResponse($messageId: Float!) {
regenerateResponse(messageId: $messageId) {
id
message
createdAt
}
}
`;
const regenerateResponse = async (client, messageId) => {
const { data } = await client.mutate({
mutation: REGENERATE_RESPONSE,
variables: { messageId },
});
return data.regenerateResponse;
};
// Usage
const newResponse = await regenerateResponse(client, 54321);
console.log('New response generated:', newResponse.id);
Workflow with regenerate:
// User sends a message
const userMessage = await sendMessage(client, sessionId, 'Explain quantum computing');
// Subscribe to streaming response
let botMessageId;
subscribeToStream(client, sessionId, (fullMessage, data) => {
botMessageId = data.botResponseMessageId;
updateUI(fullMessage);
});
// Later, user clicks "Regenerate" button
const newResponse = await regenerateResponse(client, botMessageId);
console.log('Response regenerated');
Stop AI response
Stop the ongoing AI response generation.
GraphQL Mutation:
mutation StopAIResponse($sessionId: Float!, $botId: Float!) {
stopAIResponse(sessionId: $sessionId, botId: $botId)
}
Parameters:
sessionId(required) β Session IDbotId(required) β Bot ID
Returns: Boolean (true if stopped successfully)
Example:
const STOP_AI_RESPONSE = gql`
mutation StopAIResponse($sessionId: Float!, $botId: Float!) {
stopAIResponse(sessionId: $sessionId, botId: $botId)
}
`;
const stopGeneration = async (client, sessionId, botId) => {
const { data } = await client.mutate({
mutation: STOP_AI_RESPONSE,
variables: { sessionId, botId },
});
return data.stopAIResponse;
};
// Usage
const stopped = await stopGeneration(client, 67890, 123);
if (stopped) {
console.log('Generation stopped by user');
}
UI implementation with stop button:
let isGenerating = false;
let currentBotId = 123;
const startGeneration = async (sessionId) => {
isGenerating = true;
showStopButton();
subscribeToStream(client, sessionId, (fullMessage, data) => {
updateMessageUI(fullMessage);
if (!data.isStoppable) {
hideStopButton();
}
});
};
const handleStopClick = async (sessionId) => {
if (isGenerating) {
await stopGeneration(client, sessionId, currentBotId);
isGenerating = false;
hideStopButton();
}
};
Retry failed message
Retry sending a message that failed.
GraphQL Mutation:
mutation RetryBotMessage($sessionId: Float!) {
retryBotMessage(sessionId: $sessionId)
}
Parameters:
sessionId(required) β Session ID
Returns: Boolean (true if retry succeeded)
Example:
const RETRY_MESSAGE = gql`
mutation RetryBotMessage($sessionId: Float!) {
retryBotMessage(sessionId: $sessionId)
}
`;
const retryMessage = async (client, sessionId) => {
const { data } = await client.mutate({
mutation: RETRY_MESSAGE,
variables: { sessionId },
});
return data.retryBotMessage;
};
// Usage
const success = await retryMessage(client, 67890);
if (success) {
console.log('Message retry successful');
}
Text-to-speech
Convert message to audio
Convert a message to audio using text-to-speech.
GraphQL Query:
query GetSpeechFromText($messageId: Float!) {
getSpeechFromText(messageId: $messageId)
}
Parameters:
messageId(required) β ID of the message to convert
Returns: Audio URL string
Example:
const GET_SPEECH = gql`
query GetSpeechFromText($messageId: Float!) {
getSpeechFromText(messageId: $messageId)
}
`;
const getAudioUrl = async (client, messageId) => {
const { data } = await client.query({
query: GET_SPEECH,
variables: { messageId },
});
return data.getSpeechFromText;
};
// Usage
const audioUrl = await getAudioUrl(client, 12345);
const audio = new Audio(audioUrl);
audio.play();
With audio player UI:
const playMessageAudio = async (client, messageId) => {
try {
const audioUrl = await getAudioUrl(client, messageId);
const audio = new Audio(audioUrl);
audio.addEventListener('ended', () => {
console.log('Audio playback finished');
});
audio.play();
return audio;
} catch (error) {
console.error('Failed to play audio:', error);
}
};
// Usage in UI
const audioElement = await playMessageAudio(client, messageId);
Complete chat manager class
Here's a reusable class that encapsulates all chat operations:
class ChatManager {
constructor(client, sessionId) {
this.client = client;
this.sessionId = sessionId;
this.currentBotId = null;
this.isGenerating = false;
}
async sendMessage(messageText, options = {}) {
const SEND_MESSAGE = gql`
mutation SendNewMessage($newMessageData: NewMessageInput!) {
sendNewMessage(newMessageData: $newMessageData) {
id
message
createdAt
sessionId
isBotReply
}
}
`;
const { data } = await this.client.mutate({
mutation: SEND_MESSAGE,
variables: {
newMessageData: {
message: messageText,
sessionId: this.sessionId,
isForAiReply: true,
...options,
},
},
});
return data.sendNewMessage;
}
async sendSyncMessage(messageText, options = {}) {
const SYNC_REQUEST = gql`
mutation SyncBotRequest($newMessageData: NewMessageInput!) {
syncBotRequest(newMessageData: $newMessageData) {
text
isBotError
sources
botId
}
}
`;
const { data } = await this.client.mutate({
mutation: SYNC_REQUEST,
variables: {
newMessageData: {
message: messageText,
sessionId: this.sessionId,
...options,
},
},
});
return data.syncBotRequest;
}
subscribeToStream(onChunk, onComplete) {
const MESSAGE_STREAM = gql`
subscription OnMessageStream($sessionId: Float!) {
onMessageStream(sessionId: $sessionId) {
messageChunk
botResponseMessageId
isStoppable
botId
}
}
`;
let fullMessage = '';
return this.client
.subscribe({
query: MESSAGE_STREAM,
variables: { sessionId: this.sessionId },
})
.subscribe({
next: ({ data }) => {
fullMessage += data.onMessageStream.messageChunk;
this.currentBotId = data.onMessageStream.botId;
this.isGenerating = true;
onChunk(fullMessage, data.onMessageStream);
},
error: (error) => {
console.error('Stream error:', error);
this.isGenerating = false;
},
complete: () => {
this.isGenerating = false;
if (onComplete) onComplete(fullMessage);
},
});
}
async stopGeneration() {
if (!this.isGenerating || !this.currentBotId) {
return false;
}
const STOP = gql`
mutation StopAIResponse($sessionId: Float!, $botId: Float!) {
stopAIResponse(sessionId: $sessionId, botId: $botId)
}
`;
const { data } = await this.client.mutate({
mutation: STOP,
variables: {
sessionId: this.sessionId,
botId: this.currentBotId,
},
});
this.isGenerating = false;
return data.stopAIResponse;
}
async regenerateResponse(messageId) {
const REGENERATE = gql`
mutation RegenerateResponse($messageId: Float!) {
regenerateResponse(messageId: $messageId) {
id
message
}
}
`;
const { data } = await this.client.mutate({
mutation: REGENERATE,
variables: { messageId },
});
return data.regenerateResponse;
}
async retryMessage() {
const RETRY = gql`
mutation RetryBotMessage($sessionId: Float!) {
retryBotMessage(sessionId: $sessionId)
}
`;
const { data } = await this.client.mutate({
mutation: RETRY,
variables: { sessionId: this.sessionId },
});
return data.retryBotMessage;
}
async getAudioUrl(messageId) {
const GET_SPEECH = gql`
query GetSpeechFromText($messageId: Float!) {
getSpeechFromText(messageId: $messageId)
}
`;
const { data } = await this.client.query({
query: GET_SPEECH,
variables: { messageId },
});
return data.getSpeechFromText;
}
async playAudio(messageId) {
const audioUrl = await this.getAudioUrl(messageId);
const audio = new Audio(audioUrl);
audio.play();
return audio;
}
}
// Usage example
const chatManager = new ChatManager(client, 67890);
// Send a message and listen to streaming
await chatManager.sendMessage('Tell me a story');
const subscription = chatManager.subscribeToStream(
(fullMessage, data) => {
console.log('Streaming:', fullMessage);
updateUI(fullMessage);
},
(finalMessage) => {
console.log('Response complete');
}
);
// User clicks stop button
await chatManager.stopGeneration();
// User clicks regenerate
await chatManager.regenerateResponse(messageId);
// User clicks speaker icon
await chatManager.playAudio(messageId);
Practical use cases
Real-time chat UI
Build a chat interface with streaming responses:
const chatManager = new ChatManager(client, sessionId);
// User types and sends message
const handleSendMessage = async (userInput) => {
// Display user message immediately
addMessageToUI(userInput, 'user');
// Send message and start streaming
await chatManager.sendMessage(userInput);
// Create placeholder for bot response
let botMessageElement = addMessageToUI('', 'bot');
// Subscribe to streaming
chatManager.subscribeToStream(
(fullMessage) => {
// Update bot message as it streams
updateMessageContent(botMessageElement, fullMessage);
},
() => {
// Finalize when complete
finalizeMessage(botMessageElement);
}
);
};
Quick answer without streaming
For simple Q&A, get the complete response immediately:
const handleQuickQuestion = async (question) => {
try {
const response = await chatManager.sendSyncMessage(question);
if (response.isBotError) {
showError('Bot encountered an error');
return;
}
displayAnswer(response.text);
if (response.sources) {
displaySources(response.sources);
}
} catch (error) {
showError('Failed to get answer');
}
};
Audio playback for accessibility
Allow users to listen to responses:
const handleAudioButton = async (messageId) => {
try {
showLoadingSpinner();
const audio = await chatManager.playAudio(messageId);
hideLoadingSpinner();
audio.addEventListener('ended', () => {
console.log('Audio finished');
});
} catch (error) {
hideLoadingSpinner();
showError('Failed to play audio');
}
};
Best practices
Always set
isForAiReply: truewhen you want an AI response; otherwise, the message is just stored without triggering generation.Use streaming for better UX β Streaming shows responses character by character, giving immediate feedback. Use synchronous only for simple cases.
Handle stop gracefully β Show a stop button during generation and handle the stop action cleanly.
Store message IDs β Keep track of message IDs for regeneration and audio features.
Validate input before sending β Check message length and content before sending to avoid errors.
Implement error handling β Always wrap mutations in try-catch blocks and show user-friendly error messages.
Use file attachments wisely β Only attach files when necessary; validate file IDs before sending.
Cache audio URLs β Store generated audio URLs to avoid regenerating the same audio multiple times.