Skip to content

Commit bc91937

Browse files
Merge branch '26_1' into fix-lint
2 parents bcbf530 + 5cbe8ad commit bc91937

16 files changed

Lines changed: 17331 additions & 225 deletions

File tree

-117 KB
Binary file not shown.

apps/demos/Demos/DataGrid/AIAssistant/jQuery/data.js

Lines changed: 16887 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
<link rel="stylesheet" type="text/css" href="../../../../node_modules/devextreme/dist/css/dx.light.css" />
9+
<script src="../../../../node_modules/jquery/dist/jquery.min.js"></script>
10+
<script src="../../../../node_modules/devextreme-dist/js/dx.all.js"></script>
11+
<script src="../../../../node_modules/devextreme-dist/js/dx.ai-integration.js"></script>
12+
<script type="module">
13+
import { AzureOpenAI } from "https://esm.sh/openai@4.73.1";
14+
15+
window.AzureOpenAI = AzureOpenAI;
16+
</script>
17+
<script src="data.js"></script>
18+
<script src="index.js"></script>
19+
</head>
20+
<body class="dx-viewport">
21+
<div class="demo-container">
22+
<div id="data-grid-demo">
23+
<div id="gridContainer"></div>
24+
</div>
25+
</div>
26+
</body>
27+
</html>
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
$(() => {
2+
const deployment = 'gpt-4o-mini';
3+
const apiVersion = '2024-02-01';
4+
const endpoint = 'https://public-api.devexpress.com/demo-openai';
5+
const apiKey = 'DEMO';
6+
7+
const aiService = new AzureOpenAI({
8+
dangerouslyAllowBrowser: true,
9+
deployment,
10+
endpoint,
11+
apiVersion,
12+
apiKey,
13+
});
14+
15+
async function getAIResponse(messages, signal, responseSchema) {
16+
const params = {
17+
messages,
18+
model: deployment,
19+
max_tokens: 1000,
20+
temperature: 0.7,
21+
};
22+
23+
params.response_format = {
24+
type: 'json_schema',
25+
json_schema: {
26+
name: 'grid_assistant_response',
27+
strict: true,
28+
schema: responseSchema,
29+
},
30+
};
31+
32+
const response = await aiService.chat.completions
33+
.create(params, { signal });
34+
const result = response.choices[0].message?.content;
35+
36+
return result;
37+
}
38+
39+
async function getAIResponseRecursive(messages, signal, responseSchema) {
40+
return getAIResponse(messages, signal, responseSchema)
41+
.catch(async (error) => {
42+
if (!error.message.includes('Connection error')) {
43+
return Promise.reject(error);
44+
}
45+
46+
DevExpress.ui.notify({
47+
message: 'Our demo AI service reached a temporary request limit. Retrying in 30 seconds.',
48+
width: 'auto',
49+
type: 'error',
50+
displayTime: 5000,
51+
});
52+
53+
await new Promise((resolve) => setTimeout(resolve, 30000));
54+
55+
return getAIResponseRecursive(messages, signal, responseSchema);
56+
});
57+
}
58+
59+
const aiIntegration = new DevExpress.aiIntegration.AIIntegration({
60+
sendRequest({ prompt, data }) {
61+
const isValidRequest = JSON.stringify(prompt.user).length < 5000;
62+
if (!isValidRequest) {
63+
return {
64+
promise: Promise.reject(new Error('Request is too long. Specify a shorter prompt.')),
65+
abort: () => {},
66+
};
67+
}
68+
const controller = new AbortController();
69+
const signal = controller.signal;
70+
71+
const aiPrompt = [
72+
{ role: 'system', content: prompt.system },
73+
{ role: 'user', content: prompt.user },
74+
];
75+
const promise = getAIResponseRecursive(aiPrompt, signal, data?.responseSchema);
76+
77+
const result = {
78+
promise,
79+
abort: () => {
80+
controller.abort();
81+
},
82+
};
83+
84+
return result;
85+
},
86+
});
87+
88+
let chatInstance;
89+
90+
$('#gridContainer').dxDataGrid({
91+
dataSource: sales,
92+
showBorders: true,
93+
keyExpr: 'Id',
94+
searchPanel: {
95+
visible: true,
96+
width: 240,
97+
placeholder: 'Search...',
98+
},
99+
groupPanel: {
100+
visible: true,
101+
},
102+
headerFilter: {
103+
visible: true,
104+
},
105+
filterRow: {
106+
visible: true,
107+
},
108+
paging: {
109+
pageSize: 10,
110+
},
111+
pager: {
112+
visible: true,
113+
allowedPageSizes: [10, 25, 50, 100],
114+
showPageSizeSelector: true,
115+
},
116+
aiAssistant: {
117+
enabled: true,
118+
aiIntegration,
119+
chat: {
120+
onInitialized(e) {
121+
chatInstance = e.component;
122+
},
123+
suggestions: {
124+
items: [
125+
{
126+
text: '💡 Help',
127+
prompt: `💡 The DataGrid AI Assistant allows you to control the component using natural language. You can execute commands such as the following:
128+
• Sort records
129+
• Apply a filter
130+
• Search for a specific value
131+
• Group records by a field
132+
• Focus and select rows
133+
• Modify paging settings
134+
• Pin, resize, and reorder columns
135+
• Configure data summaries
136+
• Pick a suggestion or enter a custom request to get started.`,
137+
},
138+
{
139+
text: '🔍 Filter Sector by Health',
140+
prompt: 'Filter Sector by Health',
141+
},
142+
{
143+
text: '↕️ Sort by Region',
144+
prompt: 'Sort by Region',
145+
},
146+
{
147+
text: '🧩 Group by Product',
148+
prompt: 'Group by Product',
149+
width: 170,
150+
},
151+
],
152+
onItemClick(e) {
153+
const { prompt, text } = e.itemData;
154+
155+
if (text === '💡 Help') {
156+
const message = {
157+
id: Date.now(),
158+
timestamp: new Date(),
159+
author: { id: 'user' },
160+
text: prompt,
161+
};
162+
163+
chatInstance.getDataSource().store().push([{ type: 'insert', data: message }]);
164+
} else {
165+
chatInstance.option('inputFieldText', prompt);
166+
}
167+
},
168+
},
169+
},
170+
},
171+
columns: [
172+
{
173+
dataField: 'Product',
174+
},
175+
{
176+
dataField: 'Amount',
177+
caption: 'Sale Amount',
178+
dataType: 'number',
179+
format: 'currency',
180+
},
181+
{
182+
dataField: 'Region',
183+
dataType: 'string',
184+
},
185+
{
186+
dataField: 'Sector',
187+
dataType: 'string',
188+
},
189+
{
190+
dataField: 'SaleDate',
191+
dataType: 'date',
192+
},
193+
{
194+
dataField: 'Customer',
195+
dataType: 'string',
196+
},
197+
],
198+
});
199+
});

apps/demos/menuMeta.json

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,22 @@
2121
"Modules": "openai"
2222
}
2323
]
24+
},
25+
{
26+
"Name": "AI Assistant",
27+
"Equivalents": "",
28+
"Demos": [
29+
{
30+
"Title": "AI Assistant",
31+
"Name": "AIAssistant",
32+
"Widget": "DataGrid",
33+
"Badge": "AI",
34+
"DemoType": "Web",
35+
"TopLevel": true,
36+
"Equivalents": "Smart DataGrid Assistant, Natural Language DataGrid Assistant, Natural Language Data Queries, AI-Powered DataGrid Assistant, AI Copilot for DataGrid, Conversational DataGrid, AI DataGrid Helper, AI-Assisted Data Manipulation",
37+
"Modules": "openai"
38+
}
39+
]
2440
}
2541
]
2642
},
@@ -743,9 +759,10 @@
743759
"Title": "AI Assistant",
744760
"Name": "AIAssistant",
745761
"Widget": "DataGrid",
746-
"Badge": "Roadmap",
762+
"Badge": "AI",
747763
"DemoType": "Web",
748-
"RoadmapSurveyUrl": "https://www.devexpress.com/support/surveys/devextreme-roadmap-2026-ai-assistant.xml"
764+
"Equivalents": "Smart DataGrid Assistant, Natural Language DataGrid Assistant, Natural Language Data Queries, AI-Powered DataGrid Assistant, AI Copilot for DataGrid, Conversational DataGrid, AI DataGrid Helper, AI-Assisted Data Manipulation",
765+
"Modules": "openai"
749766
}
750767
]
751768
},
103 KB
Loading
89.5 KB
Loading

packages/devextreme/js/__internal/grids/grid_core/ai_assistant/__tests__/utils.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import type { Message } from '@js/ui/chat';
66
import { AI_ASSISTANT_AUTHOR_ID, MessageStatus } from '../const';
77
import {
88
getMessageStatus,
9+
hasAbortedCommands,
10+
hasCommandErrors,
911
isAIMessage,
1012
isChatOptions,
1113
isEnabledOption,
@@ -119,6 +121,61 @@ describe('isChatOptions', () => {
119121
});
120122
});
121123

124+
describe('hasCommandErrors', () => {
125+
it('should return true when commands contain failure status', () => {
126+
const commands = [
127+
{ status: 'success' as const, message: 'OK' },
128+
{ status: 'failure' as const, message: 'Failed' },
129+
];
130+
131+
expect(hasCommandErrors(commands)).toBe(true);
132+
});
133+
134+
it('should return false when all commands are successful', () => {
135+
const commands = [
136+
{ status: 'success' as const, message: 'OK' },
137+
];
138+
139+
expect(hasCommandErrors(commands)).toBe(false);
140+
});
141+
142+
it('should return false when commands is undefined', () => {
143+
expect(hasCommandErrors(undefined)).toBe(false);
144+
});
145+
146+
it('should return false when commands contain only aborted status', () => {
147+
const commands = [
148+
{ status: 'aborted' as const, message: 'Aborted' },
149+
];
150+
151+
expect(hasCommandErrors(commands)).toBe(false);
152+
});
153+
});
154+
155+
describe('hasAbortedCommands', () => {
156+
it('should return true when commands contain aborted status', () => {
157+
const commands = [
158+
{ status: 'success' as const, message: 'OK' },
159+
{ status: 'aborted' as const, message: 'Aborted' },
160+
];
161+
162+
expect(hasAbortedCommands(commands)).toBe(true);
163+
});
164+
165+
it('should return false when no commands are aborted', () => {
166+
const commands = [
167+
{ status: 'success' as const, message: 'OK' },
168+
{ status: 'failure' as const, message: 'Failed' },
169+
];
170+
171+
expect(hasAbortedCommands(commands)).toBe(false);
172+
});
173+
174+
it('should return false when commands is undefined', () => {
175+
expect(hasAbortedCommands(undefined)).toBe(false);
176+
});
177+
});
178+
122179
describe('getMessageStatus', () => {
123180
it('should return Success when all commands are successful', () => {
124181
const commands = [

packages/devextreme/js/__internal/grids/grid_core/ai_assistant/ai_assistant_view.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export class AIAssistantView extends View {
7878
// (re-evaluated automatically on show and window resize).
7979
// @ts-expect-error type declaration
8080
height: () => this.getPopupHeight(),
81+
_ignoreFunctionValueDeprecation: true,
8182
onShowing: (): void => {
8283
this.visibilityChanged?.fire(true);
8384
},

packages/devextreme/js/__internal/grids/grid_core/ai_assistant/utils.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { isObject } from '@js/core/utils/type';
22
import type { Message } from '@js/ui/chat';
33

4-
import { hasAbortedCommands, hasCommandErrors } from '../ai_chat/utils';
54
import { AI_ASSISTANT_AUTHOR_ID, MessageStatus } from './const';
6-
import type { AIMessage, CommandResults } from './types';
5+
import type { AIMessage, CommandResult } from './types';
76

87
export const isAIMessage = (
98
message: Message,
@@ -21,7 +20,15 @@ export const isPopupOptions = (optionName: string, value: unknown): boolean => o
2120
export const isChatOptions = (optionName: string, value: unknown): boolean => optionName.startsWith('aiAssistant.chat')
2221
|| (optionName === 'aiAssistant' && isObject(value) && 'chat' in value);
2322

24-
export const getMessageStatus = (commands: CommandResults): MessageStatus => {
23+
export const hasCommandErrors = (
24+
commands: CommandResult[] | undefined,
25+
): boolean => !!commands?.some(({ status }) => status === 'failure');
26+
27+
export const hasAbortedCommands = (
28+
commands: CommandResult[] | undefined,
29+
): boolean => !!commands?.some(({ status }) => status === 'aborted');
30+
31+
export const getMessageStatus = (commands: CommandResult[]): MessageStatus => {
2532
if (hasCommandErrors(commands) || hasAbortedCommands(commands)) {
2633
return MessageStatus.Failure;
2734
}

0 commit comments

Comments
 (0)