Skip to content

Commit 05c6fb4

Browse files
authored
✨AI Column: Create cache mechanism (#31569)
1 parent dfe2a4a commit 05c6fb4

File tree

9 files changed

+862
-140
lines changed

9 files changed

+862
-140
lines changed

packages/devextreme/js/__internal/grids/grid_core/ai_column/ai_column.integration.test.ts

Lines changed: 698 additions & 8 deletions
Large diffs are not rendered by default.
Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,46 @@
1-
/* eslint-disable @typescript-eslint/no-unused-vars */
21
import { Controller } from '../m_modules';
32

43
export class AIColumnCacheController extends Controller {
5-
public clearCache(columnName: string): void {
4+
private readonly cache: Record<string, Record<PropertyKey, string> | undefined> = {};
65

6+
public clearCache(columnName: string): void {
7+
this.cache[columnName] = undefined;
78
}
89

910
public getCachedResponse(columnName: string, keys: PropertyKey[]):
1011
Record<PropertyKey, string> {
11-
return {};
12+
const columnCache = this.cache[columnName];
13+
if (!columnCache) return {};
14+
return keys.reduce<Record<PropertyKey, string>>((acc, key) => {
15+
const value = columnCache[key];
16+
if (value !== undefined && value !== '') {
17+
acc[key] = value;
18+
}
19+
return acc;
20+
}, {});
21+
}
22+
23+
public setCachedResponse(
24+
columnName: string,
25+
data: Record<PropertyKey, string>,
26+
): void {
27+
let columnCache = this.cache[columnName];
28+
if (!columnCache) {
29+
columnCache = {};
30+
this.cache[columnName] = columnCache;
31+
}
32+
Object.entries(data).forEach(([key, value]) => {
33+
if (columnCache && value !== '') {
34+
columnCache[key] = value;
35+
}
36+
});
1237
}
1338

14-
public getCachedString(columnName: string, key: PropertyKey): string | null {
15-
return null;
39+
public getCachedString(columnName: string, key: PropertyKey): string | undefined {
40+
return this.cache[columnName]?.[key];
1641
}
1742

1843
public isEmptyCache(columnName: string): boolean {
19-
return true;
44+
return Object.keys(this.cache[columnName] ?? {}).length === 0;
2045
}
2146
}

packages/devextreme/js/__internal/grids/grid_core/ai_column/m_ai_column_controller.ts

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { Column, ColumnsController } from '../columns_controller/m_columns_
66
import type { DataController } from '../data_controller/m_data_controller';
77
import { Controller } from '../m_modules';
88
import { AIColumnIntegrationController } from './m_ai_column_integration_controller';
9-
import { getAICommandColumnDefaultOptions, isAIColumnAutoMode } from './utils';
9+
import { getAICommandColumnDefaultOptions, isAIColumnAutoMode, isPromptOption } from './utils';
1010

1111
export class AIColumnController extends Controller {
1212
private dataController!: DataController;
@@ -17,6 +17,12 @@ export class AIColumnController extends Controller {
1717

1818
private dataChangedHandler!: (e) => any;
1919

20+
private aiColumnOptionChangedHandler!: (
21+
column: Column,
22+
optionName: string,
23+
value: unknown,
24+
) => void;
25+
2026
public aiRequestCompleted!: Callback;
2127

2228
public aiRequestRejected!: Callback;
@@ -40,6 +46,9 @@ export class AIColumnController extends Controller {
4046

4147
this.dataChangedHandler = this.handleDataChanged.bind(this);
4248
this.dataController.changed.add(this.dataChangedHandler);
49+
50+
this.aiColumnOptionChangedHandler = this.aiColumnOptionChanged.bind(this);
51+
this.columnsController.aiColumnOptionChanged.add(this.aiColumnOptionChangedHandler);
4352
}
4453

4554
private showResults(
@@ -59,7 +68,7 @@ export class AIColumnController extends Controller {
5968

6069
for (const col of aiColumns) {
6170
if (isAIColumnAutoMode(col)) {
62-
this.refreshAIColumn(col.name as string);
71+
this.sendRequest(col.name as string, true);
6372
}
6473
}
6574
}
@@ -80,16 +89,24 @@ export class AIColumnController extends Controller {
8089
this.aiColumnIntegrationController.abortRequest(columnName);
8190
}
8291

92+
private sendRequest(
93+
columnName: string,
94+
useCache: boolean,
95+
): void {
96+
const callbacks = this.getRequestCallbacks();
97+
this.aiColumnIntegrationController.sendRequest(columnName, useCache, callbacks);
98+
}
99+
83100
public sendAIColumnRequest(
84101
columnName: string,
85102
): void {
86-
this.aiColumnIntegrationController.sendRequest(columnName, true, this.getRequestCallbacks());
103+
this.sendRequest(columnName, false);
87104
}
88105

89106
public refreshAIColumn(
90107
columnName: string,
91108
): void {
92-
this.sendAIColumnRequest(columnName);
109+
this.sendRequest(columnName, false);
93110
}
94111

95112
private getRequestCallbacks(): RequestCallbacks<GenerateGridColumnCommandResult> {
@@ -105,14 +122,33 @@ export class AIColumnController extends Controller {
105122

106123
public clearAIColumn(columnName: string): void {
107124
this.aiColumnIntegrationController.abortRequest(columnName);
125+
this.aiColumnIntegrationController.clearAIColumn(columnName);
108126
this.columnsController.columnOption(columnName, 'ai.prompt', '');
109127
}
110128

111-
public getAIColumnText(columnName: string, key: any): void {
129+
public getAIColumnText(columnName: string, key: unknown): string | undefined {
130+
return this.aiColumnIntegrationController.getAIColumnText(columnName, key as PropertyKey);
131+
}
132+
133+
public aiColumnOptionChanged(
134+
column: Column,
135+
optionName: string,
136+
value: unknown,
137+
): void {
138+
const isPromptOptionName = isPromptOption(optionName, value);
112139

140+
if (isPromptOptionName && column.name) {
141+
this.aiColumnIntegrationController.clearAIColumn(column.name);
142+
}
113143
}
114144

115145
public dispose(): void {
116-
this.dataController.changed.remove(this.dataChangedHandler);
146+
super.dispose();
147+
if (this.aiColumnOptionChangedHandler) {
148+
this.columnsController.aiColumnOptionChanged.remove(this.aiColumnOptionChangedHandler);
149+
}
150+
if (this.dataChangedHandler) {
151+
this.dataController.changed.remove(this.dataChangedHandler);
152+
}
117153
}
118154
}

packages/devextreme/js/__internal/grids/grid_core/ai_column/m_ai_column_integration_controller.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,13 @@ export class AIColumnIntegrationController extends Controller {
7272
return;
7373
}
7474

75-
const keys = Object.keys(data);
76-
const cachedResponse: Record<PropertyKey, string> = useCache
77-
? this.aiColumnCacheController.getCachedResponse(columnName, keys)
78-
: {};
7975
const keyField = this.dataController.key();
76+
let cachedResponse: Record<PropertyKey, string> = {};
77+
if (args.useCache) {
78+
const keys = data.map((item) => item[keyField] as PropertyKey);
79+
cachedResponse = this.aiColumnCacheController.getCachedResponse(columnName, keys);
80+
}
81+
8082
const reducedData = reduceDataCachedKeys(data, cachedResponse, keyField);
8183
const areAllDataCached = Object.keys(reducedData).length === 0;
8284
if (areAllDataCached) {
@@ -129,6 +131,7 @@ export class AIColumnIntegrationController extends Controller {
129131
};
130132

131133
this.executeAction('onAIColumnResponseReceived', args);
134+
this.aiColumnCacheController.setCachedResponse(columnName, finalResponse.data);
132135
this.showResult(
133136
columnName,
134137
finalResponse.data,
@@ -163,6 +166,14 @@ export class AIColumnIntegrationController extends Controller {
163166
this.errorHandlingController?.showToastError(message);
164167
}
165168

169+
public getAIColumnText(columnName: string, key: PropertyKey): string | undefined {
170+
return this.aiColumnCacheController.getCachedString(columnName, key);
171+
}
172+
173+
public clearAIColumn(columnName: string): void {
174+
this.aiColumnCacheController.clearCache(columnName);
175+
}
176+
166177
private getAIIntegration(columnName: string): AIIntegration | null {
167178
if (!columnName) {
168179
errors.log('E1066');

packages/devextreme/js/__internal/grids/grid_core/ai_column/m_ai_column_view.ts

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export const columnHeadersViewExtender = (
2929

3030
private activeDropDownButtonInstance!: DropDownButton | null;
3131

32+
private aiColumnOptionChangedHandler!: (column: Column, optionName: string, value: unknown) => void;
33+
3234
private getDropDownButtonItems(column: Column): Item[] {
3335
return [
3436
{
@@ -152,6 +154,18 @@ export const columnHeadersViewExtender = (
152154
return renderingTemplate;
153155
}
154156

157+
private aiColumnOptionChanged(column: Column, optionName: string, value: unknown): void {
158+
const isPromptOptionName = isPromptOption(optionName, value);
159+
160+
if (isPromptOptionName) {
161+
const visibleIndex = this._columnsController.getVisibleIndex(column.index);
162+
const $headerElement = this.getHeaderElement(visibleIndex);
163+
const dropDownButtonInstance = this.getDropDownButtonInstance($headerElement);
164+
165+
dropDownButtonInstance?.option('items', this.getDropDownButtonItems(column));
166+
}
167+
}
168+
155169
public init(): void {
156170
super.init();
157171
this.aiColumnController = this.getController('aiColumn');
@@ -167,31 +181,9 @@ export const columnHeadersViewExtender = (
167181
*/
168182
this.activeDropDownButtonInstance?.close();
169183
});
170-
}
171-
172-
public optionChanged(args): void {
173-
super.optionChanged(args);
174-
175-
if (args.name !== 'columns') {
176-
return;
177-
}
178-
179-
const column = this._columnsController.getColumnByPath(args.fullName);
180-
181-
if (column?.type !== AI_COLUMN_NAME) {
182-
return;
183-
}
184-
185-
const columnOptionName = this._columnsController.getColumnOptionNameByFullName(args.fullName);
186-
const isPromptOptionName = isPromptOption(columnOptionName, args.value);
187184

188-
if (isPromptOptionName) {
189-
const visibleIndex = this._columnsController.getVisibleIndex(column.index);
190-
const $headerElement = this.getHeaderElement(visibleIndex);
191-
const dropDownButtonInstance = this.getDropDownButtonInstance($headerElement);
192-
193-
dropDownButtonInstance?.option('items', this.getDropDownButtonItems(column));
194-
}
185+
this.aiColumnOptionChangedHandler = this.aiColumnOptionChanged.bind(this);
186+
this._columnsController.aiColumnOptionChanged.add(this.aiColumnOptionChangedHandler);
195187
}
196188

197189
public renderDragCellContent($dragContainer: dxElementWrapper, column: Column): void {
@@ -206,5 +198,9 @@ export const columnHeadersViewExtender = (
206198
public dispose(): void {
207199
super.dispose();
208200
this.activeDropDownButtonInstance = null;
201+
202+
if (this.aiColumnOptionChangedHandler) {
203+
this._columnsController.aiColumnOptionChanged.remove(this.aiColumnOptionChangedHandler);
204+
}
209205
}
210206
};

0 commit comments

Comments
 (0)