Skip to content

Commit 96ffb9b

Browse files
authored
Merge pull request #3 from DevExpress-Examples/24.2-AIAssistant-Example-add-copy-refresh
Add copy and refresh buttons for AIChat - Assistant messages
2 parents 85bfa6d + f15f19c commit 96ffb9b

File tree

8 files changed

+103
-31
lines changed

8 files changed

+103
-31
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -311,3 +311,8 @@ TesterMetadata.xml
311311
package-lock.json
312312

313313
.angular/
314+
**/wwwroot/css/ace
315+
**/wwwroot/css/icons/
316+
**/wwwroot/css/*bundle.css
317+
**/wwwroot/css/*skeleton-screen.css
318+
**/wwwroot/js/*bundle.js

CS/ReportingApp/Views/Home/DocumentViewer.cshtml

+5-4
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
@await Html.PartialAsync("_AILayout")
33
<script>
44
let aiTab;
5-
function BeforeRender(sender, args) {
5+
async function BeforeRender(sender, args) {
66
const previewModel = args;
77
const reportPreview = previewModel.reportPreview;
8-
9-
aiTab = createAssistantTab();
8+
const result = await fetch(`/AI/CreateUserAssistant`);
9+
const chatId = await result.text();
10+
aiTab = createAssistantTab(chatId);
1011
const model = aiTab.model;
1112
previewModel.tabPanel.tabs.push(aiTab);
1213
reportPreview.events.on('documentBuildingChanged', (args) => {
@@ -27,7 +28,7 @@
2728
2829
async function DocumentReady(sender, args) {
2930
const response = await sender.PerformCustomDocumentOperation(null, true);
30-
if (response.customData) {
31+
if (response.customData && aiTab?.model) {
3132
aiTab.model.chatId = response.customData;
3233
aiTab.visible = true;
3334
}

CS/ReportingApp/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
"license": "MIT",
55
"private": true,
66
"dependencies": {
7-
"@devexpress/analytics-core": "24.2.2-beta",
7+
"@devexpress/analytics-core": "24.2-stable",
88
"bootstrap": "^4.3.1",
9-
"devexpress-reporting": "24.2.2-beta",
10-
"devextreme-dist": "24.2.2-beta",
9+
"devexpress-reporting": "24.2-stable",
10+
"devextreme-dist": "24.2-stable",
1111
"marked": "^14.1.3"
1212
}
1313
}

CS/ReportingApp/wwwroot/css/site.css

+6
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,9 @@ min-height: 100%;
112112
line-height: 50px;
113113
height: 60px;
114114
}
115+
116+
.dx-chat-messagegroup-alignment-start:last-child .dx-chat-messagebubble:last-child .dx-bubble-button-containder {
117+
display: flex;
118+
gap: 4px;
119+
margin-top: 8px;
120+
}

CS/ReportingApp/wwwroot/js/aiIntegration.js

+84-23
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,107 @@
1-
function normilizeAIResponse(text) {
1+

2+
let lastUserQuery;
3+
4+
const assistant = {
5+
id: 'assistant',
6+
name: 'Virtual Assistant',
7+
};
8+
9+
const user = {
10+
id: 'user',
11+
};
12+
13+
function normilizeAIResponse(text) {
214
text = text.replace(/\d+:\d+[^\】]+/g, "");
315
let html = marked.parse(text);
416
if (/<p>\.\s*<\/p>\s*$/.test(html))
517
html = html.replace(/<p>\.\s*<\/p>\s*$/, "")
618
return html;
719
}
820

21+
function copyText(text) {
22+
navigator.clipboard.writeText(text);
23+
}
24+
25+
async function getAIResponse(text, id) {
26+
const formData = new FormData();
27+
formData.append('text', text);
28+
formData.append('chatId', id);
29+
lastUserQuery = text;
30+
const response = await fetch(`/AI/GetAnswer`, {
31+
method: 'POST',
32+
body: formData
33+
});
34+
return await response.text();
35+
}
36+
37+
function RenderAssistantMessage(instance, message) {
38+
instance.option({ typingUsers: [] });
39+
instance.renderMessage({ timestamp: new Date(), text: message, author: assistant.name, id: assistant.id });
40+
}
41+
42+
async function refreshAnswer(instance) {
43+
const items = instance.option('items');
44+
const newItems = items.slice(0, -1);
45+
instance.option({ items: newItems });
46+
instance.option({ typingUsers: [assistant] });
47+
const aiResponse = await getAIResponse(lastUserQuery, assistant.id);
48+
setTimeout(() => {
49+
instance.option({ typingUsers: [] });
50+
RenderAssistantMessage(instance, aiResponse);
51+
}, 200);
52+
}
53+
954
function createAssistantTab(chatId) {
55+
assistant.id = chatId;
1056
const model = {
1157
title: 'AI Assistant',
12-
chatId: chatId,
1358
showAvatar: false,
1459
showUserName: false,
1560
showMessageTimestamp: false,
16-
user: {
17-
id: 1,
18-
},
61+
user: user,
1962
messageTemplate: (data, container) => {
20-
if(data.message.author.id === 'Assistant')
21-
return normilizeAIResponse(data.message.text);
22-
return data.message.text;
63+
const { message } = data;
64+
65+
if (message.author.id && message.author.id !== assistant.id)
66+
return message.text;
67+
68+
const $textElement = $('<div>')
69+
.html(normilizeAIResponse(message.text))
70+
.appendTo(container);
71+
const $buttonContainer = $('<div>')
72+
.addClass('dx-bubble-button-containder');
73+
$('<div>')
74+
.dxButton({
75+
icon: 'copy',
76+
stylingMode: 'text',
77+
onClick: () => {
78+
const text = $textElement.text();
79+
copyText(text);
80+
}
81+
})
82+
.appendTo($buttonContainer);
83+
$('<div>')
84+
.dxButton({
85+
icon: 'refresh',
86+
stylingMode: 'text',
87+
onClick: () => {
88+
refreshAnswer(data.component);
89+
},
90+
})
91+
.appendTo($buttonContainer);
92+
$buttonContainer.appendTo(container);
2393
},
24-
onMessageEntered: (e) => {
94+
onMessageEntered: async (e) => {
2595
const instance = e.component;
2696
instance.renderMessage(e.message);
97+
instance.option({ typingUsers: [assistant] });
98+
const userInput = e.message.text;
2799

28-
const formData = new FormData();
29-
formData.append('text', e.message.text);
30-
formData.append('chatId', model.chatId);
31-
fetch(`/AI/GetAnswer`, {
32-
method: 'POST',
33-
body: formData
34-
}).then((x) => {
35-
x.text().then((res) => {
36-
instance.renderMessage({
37-
text: res,
38-
author: { id: 'Assistant' }
39-
}, { id: 'Assistant' });
40-
});
41-
});
100+
var response = await getAIResponse(userInput, assistant.id);
101+
RenderAssistantMessage(instance, response);
42102
}
43103
};
104+
44105
return new DevExpress.Analytics.Utils.TabInfo({
45106
text: 'AI Assistant',
46107
template: 'dxrd-ai-panel',

README.md

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<!-- default badges list -->
2-
![](https://img.shields.io/endpoint?url=https://codecentral.devexpress.com/api/v1/VersionRange/853003889/24.2.2%2B)
32
[![](https://img.shields.io/badge/Open_in_DevExpress_Support_Center-FF7200?style=flat-square&logo=DevExpress&logoColor=white)](https://supportcenter.devexpress.com/ticket/details/T1252182)
43
[![](https://img.shields.io/badge/📖_How_to_use_DevExpress_Examples-e9f6fc?style=flat-square)](https://docs.devexpress.com/GeneralInformation/403183)
54
[![](https://img.shields.io/badge/💬_Leave_Feedback-feecdd?style=flat-square)](#does-this-example-address-your-development-requirementsobjectives)

web-document-viewer.png

-50.6 KB
Loading

web-report-designer.png

-173 KB
Loading

0 commit comments

Comments
 (0)