Skip to content

Commit d02efd5

Browse files
authored
AI Assistant: Implement new command for aiIntegration (#33314)
1 parent e301f33 commit d02efd5

19 files changed

Lines changed: 856 additions & 1 deletion

File tree

packages/devextreme-angular/src/common/ai-integration/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export type {
55
AIIntegrationOptions,
66
AIProvider,
77
AIResponse,
8+
ExecuteGridAssistantCommandResponse,
89
GenerateGridColumnCommandResponse,
910
Prompt,
1011
RequestParams,

packages/devextreme-angular/src/common/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export namespace AiIntegration {
9494
export type AIIntegrationOptions = AiIntegrationModule.AIIntegrationOptions;
9595
export type AIProvider = AiIntegrationModule.AIProvider;
9696
export type AIResponse = AiIntegrationModule.AIResponse;
97+
export type ExecuteGridAssistantCommandResponse = AiIntegrationModule.ExecuteGridAssistantCommandResponse;
9798
export type GenerateGridColumnCommandResponse = AiIntegrationModule.GenerateGridColumnCommandResponse;
9899
export type Prompt = AiIntegrationModule.Prompt;
99100
export type RequestParams = AiIntegrationModule.RequestParams;

packages/devextreme-angular/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ export namespace Common {
179179
export type AIIntegrationOptions = import('devextreme/common/ai-integration').AIIntegrationOptions;
180180
export type AIProvider = import('devextreme/common/ai-integration').AIProvider;
181181
export type AIResponse = import('devextreme/common/ai-integration').AIResponse;
182+
export type ExecuteGridAssistantCommandResponse = import('devextreme/common/ai-integration').ExecuteGridAssistantCommandResponse;
182183
export type GenerateGridColumnCommandResponse = import('devextreme/common/ai-integration').GenerateGridColumnCommandResponse;
183184
export type Prompt = import('devextreme/common/ai-integration').Prompt;
184185
export type RequestParams = import('devextreme/common/ai-integration').RequestParams;

packages/devextreme-react/src/common/ai-integration.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export type {
55
AIIntegrationOptions,
66
AIProvider,
77
AIResponse,
8+
ExecuteGridAssistantCommandResponse,
89
GenerateGridColumnCommandResponse,
910
Prompt,
1011
RequestParams,

packages/devextreme-react/src/common/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export namespace AiIntegration {
9494
export type AIIntegrationOptions = AiIntegrationModule.AIIntegrationOptions;
9595
export type AIProvider = AiIntegrationModule.AIProvider;
9696
export type AIResponse = AiIntegrationModule.AIResponse;
97+
export type ExecuteGridAssistantCommandResponse = AiIntegrationModule.ExecuteGridAssistantCommandResponse;
9798
export type GenerateGridColumnCommandResponse = AiIntegrationModule.GenerateGridColumnCommandResponse;
9899
export type Prompt = AiIntegrationModule.Prompt;
99100
export type RequestParams = AiIntegrationModule.RequestParams;

packages/devextreme-vue/src/common/ai-integration.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export type {
55
AIIntegrationOptions,
66
AIProvider,
77
AIResponse,
8+
ExecuteGridAssistantCommandResponse,
89
GenerateGridColumnCommandResponse,
910
Prompt,
1011
RequestParams,

packages/devextreme-vue/src/common/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export namespace AiIntegration {
9494
export type AIIntegrationOptions = AiIntegrationModule.AIIntegrationOptions;
9595
export type AIProvider = AiIntegrationModule.AIProvider;
9696
export type AIResponse = AiIntegrationModule.AIResponse;
97+
export type ExecuteGridAssistantCommandResponse = AiIntegrationModule.ExecuteGridAssistantCommandResponse;
9798
export type GenerateGridColumnCommandResponse = AiIntegrationModule.GenerateGridColumnCommandResponse;
9899
export type Prompt = AiIntegrationModule.Prompt;
99100
export type RequestParams = AiIntegrationModule.RequestParams;
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import {
2+
beforeEach,
3+
describe,
4+
expect,
5+
it,
6+
jest,
7+
} from '@jest/globals';
8+
import type {
9+
AIProvider,
10+
ExecuteGridAssistantCommandParams,
11+
ExecuteGridAssistantCommandResult,
12+
RequestCallbacks,
13+
} from '@js/common/ai-integration';
14+
import type { PromptData } from '@ts/core/ai_integration/core/prompt_manager';
15+
import { PromptManager } from '@ts/core/ai_integration/core/prompt_manager';
16+
import { RequestManager } from '@ts/core/ai_integration/core/request_manager';
17+
import { templates } from '@ts/core/ai_integration/templates';
18+
import { Provider } from '@ts/core/ai_integration/test_utils/provider_mock';
19+
20+
import { ExecuteGridAssistantCommand } from './executeGridAssistant';
21+
22+
const COMMAND_NAME = 'executeGridAssistant';
23+
const USER_TEXT = 'Sort by name ascending';
24+
const CONTEXT = { columns: ['id', 'name', 'value'], pageSize: 10 };
25+
const RESPONSE_SCHEMA = {
26+
type: 'object',
27+
properties: {
28+
actions: { type: 'array' },
29+
},
30+
};
31+
const PROCESSED_CONTEXT = JSON.stringify(CONTEXT);
32+
33+
describe('ExecuteGridAssistantCommand', () => {
34+
const params: ExecuteGridAssistantCommandParams = {
35+
text: USER_TEXT,
36+
context: CONTEXT,
37+
responseSchema: RESPONSE_SCHEMA,
38+
};
39+
let promptManager = null as unknown as PromptManager;
40+
let requestManager = null as unknown as RequestManager;
41+
let command = null as unknown as ExecuteGridAssistantCommand;
42+
43+
beforeEach(() => {
44+
const provider: AIProvider = new Provider();
45+
46+
requestManager = new RequestManager(provider);
47+
promptManager = new PromptManager();
48+
49+
command = new ExecuteGridAssistantCommand(promptManager, requestManager);
50+
});
51+
52+
describe('getTemplateName', () => {
53+
it('should return the name of the corresponding template', () => {
54+
// @ts-expect-error Access to protected property for a test
55+
const templateName = command.getTemplateName();
56+
57+
expect(templateName).toStrictEqual(COMMAND_NAME);
58+
});
59+
});
60+
61+
describe('buildPromptData', () => {
62+
it('should form PromptData with text and context', () => {
63+
// @ts-expect-error Access to protected property for a test
64+
const promptData: PromptData = command.buildPromptData(params);
65+
66+
expect(promptData).toStrictEqual({
67+
user: {
68+
text: USER_TEXT,
69+
context: PROCESSED_CONTEXT,
70+
},
71+
});
72+
});
73+
});
74+
75+
describe('parseResult', () => {
76+
it('should return the parsed result from a JSON string', () => {
77+
const response = '{"actions":[{"name":"sort","args":{"columnName":"name","sortOrder":"asc"}}]}';
78+
// @ts-expect-error Access to protected property for a test
79+
const result = command.parseResult(response);
80+
81+
const expectedResult = {
82+
actions: [{ name: 'sort', args: { columnName: 'name', sortOrder: 'asc' } }],
83+
};
84+
85+
expect(result).toStrictEqual(expectedResult);
86+
});
87+
88+
it('should return empty actions when response is an empty string', () => {
89+
const response = '';
90+
// @ts-expect-error Access to protected property for a test
91+
const result = command.parseResult(response);
92+
93+
expect(result).toStrictEqual({
94+
actions: [],
95+
});
96+
});
97+
98+
it('should return the response object as-is when actions is an array', () => {
99+
const response = {
100+
actions: [{ name: 'sort', args: { columnName: 'id', sortOrder: 'desc' } }],
101+
};
102+
// @ts-expect-error Access to protected property for a test
103+
const result = command.parseResult(response);
104+
105+
expect(result).toStrictEqual({
106+
actions: [{ name: 'sort', args: { columnName: 'id', sortOrder: 'desc' } }],
107+
});
108+
});
109+
110+
it('should parse actions when response actions is a stringified array', () => {
111+
const response = {
112+
actions: '[{"name":"sort","args":{"columnName":"name","sortOrder":"asc"}}]',
113+
};
114+
// @ts-expect-error Access to protected property for a test
115+
const result = command.parseResult(response);
116+
117+
expect(result).toStrictEqual({
118+
actions: [{ name: 'sort', args: { columnName: 'name', sortOrder: 'asc' } }],
119+
});
120+
});
121+
});
122+
123+
describe('execute', () => {
124+
const callbacks: RequestCallbacks<ExecuteGridAssistantCommandResult> = { onComplete: () => {} };
125+
126+
it('promptManager.buildPrompt should be called with the passed data', () => {
127+
const buildPromptSpy = jest.spyOn(promptManager, 'buildPrompt');
128+
129+
command.execute(params, callbacks);
130+
131+
expect(buildPromptSpy).toHaveBeenCalledTimes(1);
132+
expect(promptManager.buildPrompt).toHaveBeenCalledWith(COMMAND_NAME, {
133+
user: {
134+
text: USER_TEXT,
135+
context: PROCESSED_CONTEXT,
136+
},
137+
}, { applyMetaTemplates: true });
138+
});
139+
140+
it('promptManager.buildPrompt should return prompt with passed values', () => {
141+
jest.spyOn(promptManager, 'buildPrompt');
142+
143+
command.execute(params, callbacks);
144+
145+
const expectedUserPrompt = templates.executeGridAssistant.user
146+
?.replace('{{text}}', USER_TEXT)
147+
.replace('{{context}}', PROCESSED_CONTEXT);
148+
149+
expect(promptManager.buildPrompt).toHaveReturnedWith({
150+
system: templates.executeGridAssistant.system,
151+
user: expectedUserPrompt,
152+
});
153+
});
154+
155+
it('should call provider.sendRequest once and return the abort function', () => {
156+
const sendRequestSpy = jest.spyOn(requestManager, 'sendRequest');
157+
158+
const abort = command.execute(params, callbacks);
159+
160+
expect(typeof abort).toBe('function');
161+
expect(sendRequestSpy).toHaveBeenCalledTimes(1);
162+
});
163+
});
164+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import type {
2+
ExecuteGridAssistantCommandParams,
3+
ExecuteGridAssistantCommandResponse,
4+
ExecuteGridAssistantCommandResult,
5+
} from '@js/common/ai-integration';
6+
import { BaseCommand } from '@ts/core/ai_integration/commands/base';
7+
import type { PromptData, PromptTemplateName } from '@ts/core/ai_integration/core/prompt_manager';
8+
9+
export class ExecuteGridAssistantCommand extends BaseCommand<
10+
ExecuteGridAssistantCommandParams,
11+
ExecuteGridAssistantCommandResult
12+
> {
13+
protected getTemplateName(): PromptTemplateName {
14+
return 'executeGridAssistant';
15+
}
16+
17+
protected buildPromptData(params: ExecuteGridAssistantCommandParams): PromptData {
18+
return {
19+
user: {
20+
text: params.text,
21+
context: JSON.stringify(params.context),
22+
},
23+
};
24+
}
25+
26+
protected parseResult(
27+
response: ExecuteGridAssistantCommandResponse,
28+
): ExecuteGridAssistantCommandResult {
29+
if (typeof response === 'string') {
30+
if (response === '') {
31+
return { actions: [] };
32+
}
33+
return JSON.parse(response) as ExecuteGridAssistantCommandResult;
34+
}
35+
36+
const actions = typeof response.actions === 'string'
37+
? JSON.parse(response.actions)
38+
: response.actions;
39+
40+
return { actions };
41+
}
42+
}

packages/devextreme/js/__internal/core/ai_integration/commands/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ import { SmartPasteCommand } from '@ts/core/ai_integration/commands/smartPaste';
99
import { SummarizeCommand } from '@ts/core/ai_integration/commands/summarize';
1010
import { TranslateCommand } from '@ts/core/ai_integration/commands/translate';
1111

12+
import { ExecuteGridAssistantCommand } from './executeGridAssistant';
13+
1214
export {
1315
BaseCommand,
1416
ChangeStyleCommand,
1517
ChangeToneCommand,
1618
ExecuteCommand,
19+
ExecuteGridAssistantCommand,
1720
ExpandCommand,
1821
ProofreadCommand,
1922
ShortenCommand,

0 commit comments

Comments
 (0)