DevExtreme v24.2 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

JavaScript/jQuery Chat - AI and Chatbot Integration

This demo leverages the Azure OpenAI service alongside the DevExtreme JavaScript Chat component. You can integrate JavaScript Chat with multiple AI services, including OpenAI, Google Dialogflow, and Microsoft Bot Framework.

Handling dataSource (reloadOnChange: false)

The JavaScript Chat component's dataSource is a CustomStore that implements its own load and insert functions. The DevExtreme JavaScript Chat component deactivates reloadOnChange to push updates directly into the store and update the conversation manually. See theonMessageEntered event handler and the processMessageSending function to review the code that manages data transfer between our JavaScript Chat component and its data store.

Backend API
$(() => { const store = []; const messages = []; DevExpress.localization.loadMessages({ en: { 'dxChat-emptyListMessage': 'Chat is Empty', 'dxChat-emptyListPrompt': 'AI Assistant is ready to answer your questions.', 'dxChat-textareaPlaceholder': 'Ask AI Assistant...', }, }); const chatService = new AzureOpenAI({ dangerouslyAllowBrowser: true, deployment, endpoint, apiVersion, apiKey, }); async function getAIResponse(messages) { const params = { messages, model: deployment, max_tokens: 1000, temperature: 0.7, }; const response = await chatService.chat.completions.create(params); const data = { choices: response.choices }; return data.choices[0].message?.content; } function alertLimitReached() { instance.option({ alerts: [{ message: 'Request limit reached, try again in a minute.', }], }); setTimeout(() => { instance.option({ alerts: [] }); }, ALERT_TIMEOUT); } function toggleDisabledState(disabled, event) { instance.element().toggleClass(CHAT_DISABLED_CLASS, disabled); if (disabled) { event?.target.blur(); } else { event?.target.focus(); } }; async function processMessageSending(message, event) { toggleDisabledState(true, event); messages.push({ role: 'user', content: message.text }); instance.option({ typingUsers: [assistant] }); try { const aiResponse = await getAIResponse(messages); setTimeout(() => { instance.option({ typingUsers: [] }); messages.push({ role: 'assistant', content: aiResponse }); renderAssistantMessage(aiResponse); }, 200); } catch { instance.option({ typingUsers: [] }); messages.pop(); alertLimitReached(); } finally { toggleDisabledState(false, event); } } async function regenerate() { toggleDisabledState(true); try { const aiResponse = await getAIResponse(messages.slice(0, -1)); updateLastMessage(aiResponse); messages.at(-1).content = aiResponse; } catch { updateLastMessage(messages.at(-1).content); alertLimitReached(); } finally { toggleDisabledState(false); } } function renderAssistantMessage(text) { const message = { id: Date.now(), timestamp: new Date(), author: assistant, text, }; dataSource.store().push([{ type: 'insert', data: message }]); } function updateLastMessage(text) { const items = dataSource.items(); const lastMessage = items.at(-1); const data = { text: text ?? REGENERATION_TEXT, }; dataSource.store().push([{ type: 'update', key: lastMessage.id, data, }]); } function convertToHtml(value) { const result = unified() .use(remarkParse) .use(remarkRehype) .use(rehypeStringify) .processSync(value) .toString(); return result; } function onCopyButtonClick(component, text) { navigator.clipboard?.writeText(text); component.option({ icon: 'check' }); setTimeout(() => { component.option({ icon: 'copy' }); }, 2500); } function onRegenerateButtonClick() { if (instance.option('alerts').length) { return; } updateLastMessage(); regenerate(); } function renderMessageContent(message, element) { $('<div>') .addClass('dx-chat-messagebubble-text') .html(convertToHtml(message.text)) .appendTo(element); const $buttonContainer = $('<div>') .addClass('dx-bubble-button-container'); $('<div>') .dxButton({ icon: 'copy', stylingMode: 'text', hint: 'Copy', onClick: ({ component }) => { onCopyButtonClick(component, message.text); }, }) .appendTo($buttonContainer); $('<div>') .dxButton({ icon: 'refresh', stylingMode: 'text', hint: 'Regenerate', onClick: onRegenerateButtonClick, }) .appendTo($buttonContainer); $buttonContainer.appendTo(element); } const customStore = new DevExpress.data.CustomStore({ key: 'id', load: () => { const d = $.Deferred(); setTimeout(() => { d.resolve([...store]); }); return d.promise(); }, insert: (message) => { const d = $.Deferred(); setTimeout(() => { store.push(message); d.resolve(); }); return d.promise(); }, }); const dataSource = new DevExpress.data.DataSource({ store: customStore, paginate: false, }); const instance = $('#dx-ai-chat').dxChat({ user, height: 710, dataSource, reloadOnChange: false, showAvatar: false, showDayHeaders: false, onMessageEntered: (e) => { const { message, event } = e; dataSource.store().push([{ type: 'insert', data: { id: Date.now(), ...message } }]); if (!instance.option('alerts').length) { processMessageSending(message, event); } }, messageTemplate: (data, element) => { const { message } = data; if (message.text === REGENERATION_TEXT) { element.text(REGENERATION_TEXT); return; } renderMessageContent(message, element); }, }).dxChat('instance'); });
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" lang="en"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" /> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <script>window.jQuery || document.write(decodeURIComponent('%3Cscript src="js/jquery.min.js"%3E%3C/script%3E'))</script> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/24.2.7/css/dx.light.css" /> <script type="module"> import { AzureOpenAI } from "https://esm.sh/openai@4.73.1"; import { unified } from "https://esm.sh/unified@11?bundle"; import remarkParse from "https://esm.sh/remark-parse@11?bundle"; import remarkRehype from "https://esm.sh/remark-rehype@11?bundle"; import rehypeStringify from "https://esm.sh/rehype-stringify@10?bundle"; window.AzureOpenAI = AzureOpenAI; window.unified = unified; window.remarkParse = remarkParse; window.remarkRehype = remarkRehype; window.rehypeStringify = rehypeStringify; </script> <script src="js/dx.all.js"></script> <script src="data.js"></script> <link rel="stylesheet" type="text/css" href="styles.css" /> <script src="index.js"></script> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="dx-ai-chat"></div> </div> </body> </html>
.demo-container { display: flex; justify-content: center; } .dx-chat { max-width: 900px; } .dx-chat-messagelist-empty-image { display: none; } .dx-chat-messagelist-empty-message { font-size: var(--dx-font-size-heading-5); } .dx-chat-messagebubble-content, .dx-chat-messagebubble-text { display: flex; flex-direction: column; } .dx-bubble-button-container { display: none; } .dx-button { display: inline-block; color: var(--dx-color-icon); } .dx-chat-messagegroup-alignment-start:last-child .dx-chat-messagebubble:last-child .dx-bubble-button-container { display: flex; gap: 4px; margin-top: 8px; } .dx-chat-messagebubble-content > div > p:first-child { margin-top: 0; } .dx-chat-messagebubble-content > div > p:last-child { margin-bottom: 0; } .dx-chat-messagebubble-content ol, .dx-chat-messagebubble-content ul { white-space: normal; } .dx-chat-messagebubble-content h1, .dx-chat-messagebubble-content h2, .dx-chat-messagebubble-content h3, .dx-chat-messagebubble-content h4, .dx-chat-messagebubble-content h5, .dx-chat-messagebubble-content h6 { font-size: revert; font-weight: revert; } .dx-chat-disabled .dx-chat-messagebox { opacity: 0.5; pointer-events: none; }
const deployment = 'gpt-4o-mini'; const apiVersion = '2024-02-01'; const endpoint = 'https://public-api.devexpress.com/demo-openai'; const apiKey = 'DEMO'; const REGENERATION_TEXT = 'Regeneration...'; const CHAT_DISABLED_CLASS = 'dx-chat-disabled'; const ALERT_TIMEOUT = 1000 * 60; const user = { id: 'user', }; const assistant = { id: 'assistant', name: 'Virtual Assistant', };

Custom Message Template

The JavaScript Chat specifies a messageTemplate that displays "Copy" and "Regenerate" buttons in bot messages.

Response Format Conversion: Markdown to HTML

The AI model outputs responses in Markdown, while the JavaScript Chat requires HTML output. This example uses the unified plugin library to convert response content. Review convertToHtml function code for implementation details.

Default Caption Customization

The JavaScript Chat component in this demo displays modified captions when the conversation is empty. The demo uses localization techniques to alter built-in text.