Skip to content

Commit a350304

Browse files
authored
Chat: AI Integration jQuery demo
1 parent f3a699d commit a350304

File tree

9 files changed

+309
-0
lines changed

9 files changed

+309
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Chat is an interactive interface that allows users to send and receive messages in real time.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const deployment = 'gpt-4o-mini';
2+
const apiVersion = '2024-02-01';
3+
const endpoint = 'https://public-api.devexpress.com/demo-openai';
4+
const apiKey = 'DEMO';
5+
const REGENERATION_TEXT = 'Regeneration...';
6+
const user = {
7+
id: 'user',
8+
};
9+
const assistant = {
10+
id: 'assistant',
11+
name: 'Virtual Assistant',
12+
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
3+
<head>
4+
<title>DevExtreme Demo</title>
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
8+
<script src="../../../../node_modules/jquery/dist/jquery.min.js"></script>
9+
<link rel="stylesheet" type="text/css" href="../../../../node_modules/devextreme-dist/css/dx.light.css" />
10+
<script src="../../../../node_modules/devextreme-dist/js/dx.all.js"></script>
11+
<script src="data.js"></script>
12+
<link rel="stylesheet" type="text/css" href="styles.css" />
13+
<script type="module" src="index.js"></script>
14+
</head>
15+
<body class="dx-viewport">
16+
<div class="demo-container">
17+
<div id="dx-ai-chat"></div>
18+
</div>
19+
</body>
20+
</html>
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import { AzureOpenAI } from 'https://esm.sh/openai';
2+
import { unified } from 'https://esm.sh/unified@11?bundle';
3+
import remarkParse from 'https://esm.sh/remark-parse@11?bundle';
4+
import remarkRehype from 'https://esm.sh/remark-rehype@11?bundle';
5+
import rehypeStringify from 'https://esm.sh/rehype-stringify@10?bundle';
6+
7+
$(() => {
8+
const store = [];
9+
let messages = [];
10+
11+
DevExpress.localization.loadMessages({
12+
'en': {
13+
'dxChat-emptyListMessage': 'Chat is Empty',
14+
'dxChat-emptyListPrompt': 'AI Assistant is ready to answer your questions.',
15+
'dxChat-textareaPlaceholder': 'Ask AI Assistant...',
16+
},
17+
});
18+
19+
const chatService = new AzureOpenAI({
20+
dangerouslyAllowBrowser: true,
21+
deployment,
22+
endpoint,
23+
apiVersion,
24+
apiKey,
25+
});
26+
27+
async function getAIResponse(messages) {
28+
const params = {
29+
messages: messages,
30+
max_tokens: 1000,
31+
temperature: 0.7,
32+
};
33+
34+
const responseAzure = await chatService.chat.completions.create(params);
35+
const data = { choices: responseAzure.choices };
36+
37+
return data.choices[0].message?.content;
38+
};
39+
40+
function alertLimitReached() {
41+
instance.option({
42+
alerts: [{
43+
message: 'Request limit reached, try again in a minute.',
44+
}],
45+
});
46+
47+
setTimeout(() => {
48+
instance.option({ alerts: [] });
49+
}, 10000);
50+
};
51+
52+
async function processMessageSending() {
53+
instance.option({ typingUsers: [assistant] });
54+
55+
try {
56+
const aiResponse = await getAIResponse(messages);
57+
58+
setTimeout(() => {
59+
instance.option({ typingUsers: [] });
60+
61+
messages.push({ role: 'assistant', content: aiResponse });
62+
63+
renderMessage(aiResponse);
64+
}, 200);
65+
} catch {
66+
instance.option({ typingUsers: [] });
67+
alertLimitReached();
68+
}
69+
};
70+
71+
async function regenerate() {
72+
try {
73+
const aiResponse = await getAIResponse(messages.slice(0, -1));
74+
75+
updateLastMessage(aiResponse);
76+
messages.at(-1).content = aiResponse;
77+
} catch {
78+
updateLastMessage(messages.at(-1).content);
79+
alertLimitReached();
80+
}
81+
};
82+
83+
function renderMessage(text) {
84+
const message = {
85+
id: Date.now(),
86+
timestamp: new Date(),
87+
author: assistant,
88+
text,
89+
};
90+
91+
customStore.push([{ type: 'insert', data: message }]);
92+
};
93+
94+
function updateLastMessage(text) {
95+
const { items } = instance.option();
96+
const lastMessage = items.at(-1);
97+
const data = {
98+
text: text ?? REGENERATION_TEXT,
99+
};
100+
101+
customStore.push([{
102+
type: 'update',
103+
key: lastMessage.id,
104+
data,
105+
}]);
106+
};
107+
108+
function convertToHtml(value) {
109+
const result = unified()
110+
.use(remarkParse)
111+
.use(remarkRehype)
112+
.use(rehypeStringify)
113+
.processSync(value)
114+
.toString();
115+
116+
return result;
117+
};
118+
119+
const customStore = new DevExpress.data.CustomStore({
120+
key: 'id',
121+
load: () => {
122+
const d = $.Deferred();
123+
124+
setTimeout(function () {
125+
d.resolve([...store]);
126+
});
127+
128+
return d.promise();
129+
},
130+
insert: (message) => {
131+
const d = $.Deferred();
132+
133+
setTimeout(function () {
134+
store.push(message);
135+
d.resolve();
136+
});
137+
138+
return d.promise();
139+
},
140+
});
141+
142+
const instance = $("#dx-ai-chat").dxChat({
143+
dataSource: customStore,
144+
reloadOnChange: false,
145+
showAvatar: false,
146+
showDayHeaders: false,
147+
user,
148+
height: 710,
149+
onMessageEntered: (e) => {
150+
const { message } = e;
151+
152+
customStore.push([{ type: 'insert', data: { id: Date.now(), ...message } }]);
153+
messages.push({ role: 'user', content: message.text });
154+
155+
processMessageSending();
156+
},
157+
messageTemplate: (data, element) => {
158+
const { message } = data;
159+
160+
if (message.text === REGENERATION_TEXT) {
161+
element.text(REGENERATION_TEXT);
162+
return;
163+
}
164+
165+
const $textElement = $('<div>')
166+
.addClass('dx-chat-messagebubble-text')
167+
.html(convertToHtml(message.text))
168+
.appendTo(element);
169+
170+
const $buttonContainer = $('<div>')
171+
.addClass('dx-bubble-button-container');
172+
173+
$('<div>')
174+
.dxButton({
175+
icon: 'copy',
176+
stylingMode: 'text',
177+
hint: 'Copy',
178+
onClick: ({ component }) => {
179+
navigator.clipboard.writeText($textElement.text());
180+
component.option({ icon: 'check' });
181+
setTimeout(() => {
182+
component.option({ icon: 'copy' });
183+
}, 5000);
184+
},
185+
})
186+
.appendTo($buttonContainer);
187+
188+
$('<div>')
189+
.dxButton({
190+
icon: 'refresh',
191+
stylingMode: 'text',
192+
hint: 'Regenerate',
193+
onClick: () => {
194+
updateLastMessage();
195+
regenerate();
196+
},
197+
})
198+
.appendTo($buttonContainer);
199+
200+
$buttonContainer.appendTo(element);
201+
},
202+
}).dxChat('instance');
203+
});
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
.demo-container {
2+
display: flex;
3+
justify-content: center;
4+
}
5+
6+
.dx-chat {
7+
max-width: 900px;
8+
}
9+
10+
.dx-chat-messagelist-empty-image {
11+
display: none;
12+
}
13+
14+
.dx-chat-messagelist-empty-message {
15+
font-size: var(--dx-font-size-heading-5);
16+
}
17+
18+
.dx-chat-messagebubble-content,
19+
.dx-chat-messagebubble-text {
20+
display: flex;
21+
flex-direction: column;
22+
}
23+
24+
.dx-bubble-button-container {
25+
display: none;
26+
}
27+
28+
.dx-button {
29+
display: inline-block;
30+
color: var(--dx-color-icon);
31+
}
32+
33+
.dx-chat-messagegroup-alignment-start:last-child .dx-chat-messagebubble:last-child .dx-bubble-button-container {
34+
display: flex;
35+
gap: 4px;
36+
margin-top: 8px;
37+
}
38+
39+
.dx-chat-messagebubble-content > div > p:first-child {
40+
margin-top: 0;
41+
}
42+
43+
.dx-chat-messagebubble-content > div > p:last-child {
44+
margin-bottom: 0;
45+
}
46+
47+
.dx-chat-messagebubble-content ol,
48+
.dx-chat-messagebubble-content ul {
49+
white-space: normal;
50+
}
51+
52+
.dx-chat-messagebubble-content h1,
53+
.dx-chat-messagebubble-content h2,
54+
.dx-chat-messagebubble-content h3,
55+
.dx-chat-messagebubble-content h4,
56+
.dx-chat-messagebubble-content h5,
57+
.dx-chat-messagebubble-content h6 {
58+
font-size: revert;
59+
font-weight: revert;
60+
}

apps/demos/menuMeta.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2076,6 +2076,19 @@
20762076
]
20772077
}
20782078
]
2079+
},
2080+
{
2081+
"Name": "AI and Chatbot Integration",
2082+
"Equivalents": "AI, Chatbot, Assistant",
2083+
"Demos": [
2084+
{
2085+
"Title": "AI and Chatbot Integration",
2086+
"Name": "AIAndChatbotIntegration",
2087+
"Widget": "Chat",
2088+
"DemoType": "Web",
2089+
"Badge": "New"
2090+
}
2091+
]
20792092
}
20802093
]
20812094
},
18.8 KB
Loading
19.5 KB
Loading
16.8 KB
Loading

0 commit comments

Comments
 (0)