React Chat - Integrate with AI Service
You can integrate DevExtreme Chat with various AI services, such as OpenAI, Azure OpenAI, Google Dialogflow, and Microsoft Bot Framework.
Review this demo for Azure OpenAI integration:
OpenAI
This help topic describes how to integrate Chat with OpenAI. You can find the full example code in the following GitHub repository:
Prerequisites and Installation
First, obtain an API key. Next, install OpenAI SDK:
jQuery
<head>
<!-- ... -->
<script type="module">
import OpenAI from "https://esm.sh/openai@4.73.1";
</script>
</hea>Angular
npm install openai
Vue
npm install openai
React
npm install openai
AI Configuration
Create an instance of OpenAI:
jQuery
const chatService = new OpenAI({
dangerouslyAllowBrowser: true,
apiKey: "OPENAI_API_KEY", // insert your OpenAI API key
}); Angular
import { OpenAI } from "openai";
export class AppService {
chatService: OpenAI;
OpenAIConfig = {
dangerouslyAllowBrowser: true,
apiKey: "OPENAI_API_KEY", // insert your OpenAI API key
};
constructor() {
this.chatService = new OpenAI(this.OpenAIConfig);
}
}Vue
import { OpenAI } from 'openai';
const OpenAIConfig = {
dangerouslyAllowBrowser: true,
apiKey: 'OPEN_AI_KEY', // insert your OpenAI API key
};
const chatService = new OpenAI(OpenAIConfig);React
import { OpenAI } from "openai";
class AppService {
chatService: OpenAI;
OpenAIConfig = {
dangerouslyAllowBrowser: true,
apiKey: "OPENAI_API_KEY", // insert your OpenAI API key
};
constructor() {
this.chatService = new OpenAI(this.OpenAIConfig);
}
}dangerouslyAllowBrowser: true enables browser-side requests. This exposes the API key. For production, route requests through your backend.Implement a getAIResponse(messages) function to call the OpenAI API for responses. Here, the incoming argument messages is an array of { role: "user" | "assistant" | "system"; content: string } objects.
jQuery
async function getAIResponse(messages) {
const params = {
messages,
model: 'gpt-4o-mini',
};
const response = await chatService.chat.completions.create(params);
return response.choices[0].message?.content;
}Angular
async getAIResponse(messages: Array<{ role: "user" | "assistant" | "system"; content: string }>) {
const params = {
messages: messages.map(msg => ({
role: msg.role,
content: msg.content
})),
model: 'gpt-4o-mini',
};
const response = await this.chatService.chat.completions.create(params);
const data = { choices: response.choices };
return data.choices[0].message?.content;
}Vue
const getAIResponse = async(messages: Array<{ role: 'user' | 'assistant' | 'system'; content: string }>) => {
const params = {
messages: messages.map(msg => ({
role: msg.role,
content: msg.content
})),
model: 'gpt-4o-mini'
};
const response = await chatService.chat.completions.create(params);
return response.choices[0].message?.content;
};React
async getAIResponse(messages: { role: 'user' | 'assistant' | 'system'; content: string }[]): Promise<any> {
const params = {
messages: messages.map((msg) => ({
role: msg.role,
content: msg.content,
})),
model: 'gpt-4o-mini',
};
const response = await this.chatService.chat.completions.create(params);
const data = { choices: response.choices };
return data.choices[0].message?.content;
}Chat Configuration
To synchronize Chat and OpenAI, declare the processMessageSending() function. This function configures typingUsers, pushes the assistant message to the store, and renders the message.
jQuery
async function processMessageSending() {
toggleDisabledState(true);
instance.option({ typingUsers: [assistant] });
try {
const aiResponse = await getAIResponse(messages);
setTimeout(() => {
instance.option({ typingUsers: [] });
messages.push({ role: 'assistant', content: aiResponse });
renderMessage(aiResponse);
}, 200);
} catch {
instance.option({ typingUsers: [] });
alertLimitReached();
} finally {
toggleDisabledState(false);
}
}Angular
async processMessageSending() {
this.toggleDisabledState(true);
this.typingUsersSubject.next([this.assistant]);
try {
const aiResponse = await this.getAIResponse(this.messages);
setTimeout(() => {
this.typingUsersSubject.next([]);
this.messages.push({ role: "assistant", content: aiResponse ?? "" });
this.renderAssistantMessage(aiResponse ?? "");
}, 200);
} catch {
this.typingUsersSubject.next([]);
this.alertLimitReached();
} finally {
this.toggleDisabledState(false);
}
}Vue
const processMessageSending = async() => {
toggleDisabledState(true);
typingUsers.value = [assistant];
try {
const aiResponse = await getAIResponse(messages.value);
setTimeout(() => {
typingUsers.value = [];
messages.value.push({ role: 'assistant', content: aiResponse ?? '' });
renderAssistantMessage(aiResponse ?? '');
}, 200);
} catch {
typingUsers.value = [];
alertLimitReached();
} finally {
toggleDisabledState(false);
}
};React
async processMessageSending(): Promise<void> {
this.toggleDisabledState(true);
this.typingUsersSubject.next([this.assistant]);
try {
const aiResponse = await this.getAIResponse(this.messages);
setTimeout(() => {
this.typingUsersSubject.next([]);
this.messages.push({ role: 'assistant', content: aiResponse ?? '' });
this.renderAssistantMessage(aiResponse ?? '');
}, 200);
} catch {
this.typingUsersSubject.next([]);
this.alertLimitReached();
} finally {
this.toggleDisabledState(false);
}
}Call processMessageSending() after a user message is sent in the onMessageEntered event handler:
jQuery
const instance = $('#dx-ai-chat').dxChat({
// ...
dataSource: customStore,
reloadOnChange: false,
onMessageEntered: (e) => {
const { message } = e;
customStore.push([{ type: 'insert', data: { id: Date.now(), ...message } }]);
messages.push({ role: 'user', content: message.text });
processMessageSending();
}
}).dxChat('instance');Angular
async onMessageEntered({ message, event }: MessageEnteredEvent) {
this.dataSource
?.store()
.push([{ type: "insert", data: { id: Date.now(), ...message } }]);
this.messages.push({ role: "user", content: message?.text ?? "" });
this.processMessageSending();
}Vue
const onMessageEntered = async(e: MessageEnteredEvent) => {
let { message } = e;
dataSource.value?.store().push([{
type: 'insert',
data: { id: Date.now(), ...message }
}]);
messages.value.push({ role: 'user', content: message?.text ?? '' });
await processMessageSending();
};React
onMessageEntered({ message }: MessageEnteredEvent): void {
this.dataSource
?.store()
.push([{ type: 'insert', data: { id: Date.now(), ...message } }]);
this.messages.push({ role: 'user', content: message?.text ?? '' });
void this.processMessageSending();
}Additional Configuration
You can add UI features to improve user experience:
- Add a Markdown converter for assistant output. For more information, refer to the Markdown Support help topic.
- Define a messageTemplate for assistant responses and add the Copy and Regenerate Response buttons. See the example code in the GitHub repository:
Google Dialogflow
This section describes Google Dialogflow integration into DevExtreme Chat. The Dialogflow platform includes two virtual agent services:
This tutorial uses Dialogflow ES. You can find the full example code in the following GitHub repository:
Prerequisites
Create a Dialogflow Agent and configure it as your needs dictate.
Then, obtain a Google Cloud key:
- Navigate to Google Cloud Console → IAM & Admin → Service Accounts.
- Create a new or use an existing service account.
- Create a key (JSON) for this account.
- Save the resulting JSON file as
key.json.
In the Google Cloud Console, find your Project ID.
Server Configuration
Dialogflow requires a server layer. You cannot use this library on the client only because Google’s CORS policy blocks required requests.
To create a server layer, perform the following steps:
Create a
serverfolder. It should include yourkey.json,server.js, anddialogflow.jsfiles (see the configuration in the GitHub example repository).Create
package.jsonand add the following script:package.json{ // ... "scripts": { "start:server": "node server.js" } }Install the following dependencies:
npm i @google-cloud/dialogflow npm i path browser-sync body-parser express
Use the following command to deploy the server:
npm run start:server
Ensure that the key path matches the passed parameter in dialogflow.js:
const keyFilePath = path.join(__dirname, 'key.json');
AI Configuration
jQuery
Create a jQuery application and install DevExtreme.
Angular
Create an Angular application and install DevExtreme.
Vue
Create a Vue application and install DevExtreme.
React
Create a React application and install DevExtreme.
Implement a getAIResponse(text) function to query the Dialogflow server for responses.
jQuery
async function getAIResponse(text) {
const response = await fetch('http://localhost:3000/webhook', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: text, sessionId }),
});
const data = await response.json();
return data.response;
}Angular
async getAIResponse(text: string | undefined): Promise<string> {
let id = this.sessionId;
const response = await fetch('http://localhost:3000/webhook', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: text, sessionId: id }),
});
const data: { response: string } = await response.json();
return data.response;
}Vue
const getAIResponse = async(text: string | undefined) => {
let id = sessionId;
const response = await fetch('http://localhost:3000/webhook', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: text, sessionId: id }),
});
const data: { response: string } = await response.json();
return data.response;
};React
async getAIResponse(text: string | undefined): Promise<string> {
let id = sessionId;
const response = await fetch('http://localhost:3000/webhook', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: text, sessionId: id }),
});
const data: { response: string } = await response.json();
return data.response;
}Chat Configuration
To process DevExtreme Chat messages with Google Dialogflow, declare the processMessageSending() function. This function configures typingUsers, pushes the assistant message to the store, and renders the message.
jQuery
async function processMessageSending(text, instance, customStore) {
toggleDisabledState(true);
instance.option({ typingUsers: [assistant] });
try {
const aiResponse = await getAIResponse(text);
setTimeout(() => {
instance.option({ typingUsers: [] });
renderMessage(aiResponse, customStore);
}, 200);
} catch (error) {
instance.option({ typingUsers: [] });
pushAlert(error, instance);
} finally {
toggleDisabledState(false, instance);
}
}Angular
async processMessageSending(e: DxChatTypes.MessageEnteredEvent): Promise<void> {
let { message } = e;
this.toggleDisabledState(true, e.event);
this.typingUsersSubject.next([this.assistant]);
try {
const aiResponse = await this.getAIResponse(message.text);
setTimeout(() => {
this.typingUsersSubject.next([]);
this.renderAssistantMessage(aiResponse ?? '');
}, 200);
} catch (error) {
this.typingUsersSubject.next([]);
this.alertLimitReached(error);
} finally {
this.toggleDisabledState(false, e.event);
}
}Vue
const processMessageSending = async(e: DxChatTypes.MessageEnteredEvent) => {
let { message } = e;
toggleDisabledState(true);
(e.event?.target as HTMLElement).blur();
typingUsers.value = [assistant];
try {
const aiResponse = await getAIResponse(message.text);
setTimeout(() => {
typingUsers.value = [];
renderAssistantMessage(aiResponse ?? '');
}, 200);
} catch {
(e.event?.target as HTMLElement).focus();
typingUsers.value = [];
alertLimitReached(error);
} finally {
(e.event?.target as HTMLElement).focus();
toggleDisabledState(false);
}
};React
async processMessageSending(setDisabled: Function, e: ChatTypes.MessageEnteredEvent): Promise<void> {
let { message } = e;
setDisabled(true);
(e.event?.target as HTMLElement).blur();
this.typingUsersSubject.next([assistant]);
try {
const aiResponse = await this.getAIResponse(message.text);
setTimeout(() => {
this.typingUsersSubject.next([]);
this.renderAssistantMessage(aiResponse ?? '');
}, 200);
} catch (error) {
(e.event?.target as HTMLElement).focus();
this.typingUsersSubject.next([]);
this.alertLimitReached(error);
} finally {
(e.event?.target as HTMLElement).focus();
setDisabled(false);
}
}Call processMessageSending() after a user message is sent in the onMessageEntered event handler:
jQuery
const instance = $('#dx-ai-chat').dxChat({
// ...
dataSource: customStore,
reloadOnChange: false,
onMessageEntered: (e) => {
const { message } = e;
customStore.push([{ type: 'insert', data: { id: Date.now(), ...message } }]);
processMessageSending(message.text, instance, customStore);
},
}).dxChat('instance');Angular
async onMessageEntered(event: DxChatTypes.MessageEnteredEvent): Promise<void> {
let { message } = event;
this.dataSource
?.store()
.push([{ type: 'insert', data: { id: Date.now(), ...message } }]);
this.messages.push({ role: 'user', content: message?.text ?? '' });
await this.processMessageSending(event);
}Vue
const onMessageEntered = async(e: DxChatTypes.MessageEnteredEvent) => {
let { message } = e;
dataSource.value?.store().push([{
type: 'insert',
data: { id: Date.now(), ...message }
}]);
messages.value.push({ role: 'user', content: message?.text ?? '' });
await processMessageSending(e);
};React
async onMessageEntered(event: ChatTypes.MessageEnteredEvent, setDisabled: Function): Promise<void> {
let { message } = event;
this.dataSource
?.store()
.push([{ type: 'insert', data: { id: Date.now(), ...message } }]);
this.messages.push({ role: 'user', content: message?.text ?? '' });
await this.processMessageSending(setDisabled, event);
}For additional configuration options, refer to the Additional Configuration help topic.