Skip to content

AI: Init, add .d.ts, add separate build, add tests #29345

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 92 commits into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
09a9692
AI: Add ai-types
Mar 18, 2025
886913e
feat(types): Add AIProvider
Mar 18, 2025
92308f6
feat(ai): Add PromptManager
Mar 18, 2025
641f715
feat(ai): Add RequestManager
Mar 18, 2025
da1c8af
feat(ai): Add BaseCommand
Mar 18, 2025
80f97c2
feat(ai): Add AI
Mar 18, 2025
1fb7e4a
feat(ai): Add TranslateCommand
Mar 18, 2025
467715f
feat(ai): Update structure && Add AI
Mar 18, 2025
8ca323a
refactor(ai-types): Remove comments
Mar 19, 2025
5069ded
feat(ai && ai-types): Update imports
Mar 19, 2025
899a7a9
feat(ai): Improve typing
Mar 19, 2025
4140b6c
fix(ai): Fix imports
Mar 19, 2025
dc87270
refactor(ai-types)
Mar 19, 2025
d5340f8
feat(types): Add ai-types into dx.all.d.ts
Mar 19, 2025
99a1453
feat(ai.js)
Mar 19, 2025
0f48435
fix(ai.d.ts): Move types to .d.ts && Fix imports && Regenerate all
Mar 19, 2025
82215f8
feat && fix(ai &7 ai-types): Add implements && Fix AI type
Mar 19, 2025
2cc0d63
fiximport()
Mar 19, 2025
78284d2
test(build)
Mar 19, 2025
83395a6
feat(bundle): Add imports
Mar 19, 2025
f60a7e8
refactor
Mar 19, 2025
12b3162
feat(ai: tests): Add first tesrt
Mar 19, 2025
b7dd83a
fix && improve(tests &7 types)
Mar 19, 2025
ab8a654
refactor(npm.js)
Mar 24, 2025
520f241
refactor
Mar 24, 2025
6e33dfb
feat(BaseCommand: tests)
Mar 24, 2025
03723ec
refactor(ai.d.ts): Move AI into packaje
Mar 25, 2025
8c1ecea
feat(regenerate-all)
Mar 25, 2025
f59f3b0
feat(tests): Move AI qunit --> jest
Mar 25, 2025
0213f53
refactor(tests): ai
Mar 25, 2025
ac89bf6
refactor(jest-tests): Move files
Mar 25, 2025
ad2d163
feat(base command tests): Move to jest
Mar 25, 2025
fd0b1d8
feat && refactor(tests && templates && promptManager): Add PromptMana…
Mar 26, 2025
3c81359
feat(base.test): Add tests
Mar 26, 2025
dd57852
feat(requestManager): Add tests
Mar 26, 2025
1b270c3
feat(request_manager.test.ts): Add tests
Mar 27, 2025
04d1142
revert && feat(ai.d.ts): Move some types to ai.d.ts && Add a hidden tag
Mar 27, 2025
740f791
feat(ai.test.ts): Get rid of extra tests && Add new tests
Mar 27, 2025
9f650ec
refactor
Mar 27, 2025
f7d1ebe
refactor(ai.test.ts): Get rid of jest.fn
Mar 27, 2025
8036c52
feat(translate.test.ts): Add tests
Mar 27, 2025
b652d75
refactor(base.test.ts): Get rid of custom PromptManager && RequestMan…
Mar 27, 2025
ab0801d
fix(base.tests.ts): Remove jest.mock
Mar 27, 2025
caf5d6a
revert(Provider): Return Provider && Move it into separate file
Mar 27, 2025
678cded
feat(trnslate.test.ts): Improve tests
Mar 27, 2025
6fd67b6
refactor(translate.test.ts): Remove mocks
Mar 28, 2025
102bd80
feat(comments)
Mar 28, 2025
136a536
refactor(ai.tests.ts): Remove mock
Mar 28, 2025
7977971
refactor(request_manager.test.ts): Remove mock
Mar 28, 2025
df0dc4e
refactor(test_utils): Rename
Mar 28, 2025
29df6cb
feat(ai.d.ts): Remove hidden
Mar 28, 2025
d2d49d4
fix(ai.d.ts): Add publick
Mar 28, 2025
617ee83
skip regenerate
mpreyskurantov Mar 28, 2025
a0ef527
regenerate ai re-exports
mpreyskurantov Mar 28, 2025
d1644c1
fix(imports)
Mar 31, 2025
ff21a38
fix(imports)
Mar 31, 2025
2ec48c0
fix(renaming)
Mar 31, 2025
767b8de
fix(imports): Remove IAI && Add AI class to .d.ts
Mar 31, 2025
fcee671
revert(pg)
Apr 1, 2025
ae47155
fix(AI): Move AI to root && Add manual re-exports to src/index.ts && …
Apr 1, 2025
a203fa4
fix(ai.js): Get rif of default export
Apr 1, 2025
4f3042d
revert && feat(demos)
Apr 2, 2025
747c510
revert(.d.ts): Remove default export
Apr 2, 2025
d4aa58d
feat(AI): Improve AI, Base, Translate with generic type
Apr 2, 2025
92d69d6
revert(chat angular demo)
Apr 2, 2025
338b910
refactor(tests): Remove extra tests
Apr 2, 2025
d98051d
feat(ai): Add Command Store && Add tests
Apr 2, 2025
9f45f06
refactor(renaming)
Apr 3, 2025
4248fa6
refactor(ai.js): Remove extra lines
Apr 3, 2025
cdc91b6
feat(ai.d.ts): Add tags
Apr 4, 2025
e0c60ac
feat(AI): Improve typing
Apr 4, 2025
d730575
refactor(RENAMING): Rename ai to ai_integration && Fix imports
Apr 4, 2025
ff9d72e
refactor(wrappers && ai-folder): Remove old wrappers && Rename ai folder
Apr 4, 2025
0b9a0a0
refactor(RENAMING): Fix import && Rename ai.ts --> ai_integration.ts
Apr 4, 2025
202b7c6
refactor(RENAMING): Fix imports
Apr 4, 2025
cf68ab0
refactor(RENAMING): Rename AI class --> AIIntegration
Apr 4, 2025
a2327f1
refactor(RENAMING): Add re-exports to framework wrappers
Apr 4, 2025
870e737
refactor(RENAMING): Fix jest mock imports
Apr 4, 2025
938a189
refactor(RENAMING): Fix config.bundle files
Apr 4, 2025
c659f79
refactor(RENAMING): Fix import && Rename namespace
Apr 7, 2025
0306b5b
Rename and move js/ai_integration -> js/common/ai-integration
alexslavr Apr 8, 2025
35cc0b9
Regenerate wrappers
alexslavr Apr 8, 2025
0030994
Revert "skip regenerate"
alexslavr Apr 8, 2025
306bd3b
Revert changes in demo systemjs configs
alexslavr Apr 8, 2025
7c63660
refactor(small renaming)
Apr 9, 2025
46d7921
refactor(dx.ai-integration.js): Rename namespace
Apr 9, 2025
18b33f0
refactor
Apr 9, 2025
6c202de
fix(deps): Update eslint-config-devextreme to 1.1.6
Apr 9, 2025
16bdb17
refactor(ai-integration.d.ts): Replace interfaces with types
Apr 10, 2025
fb76701
refactor(ai-integration.d.ts): Rename ResponseParams to Response
Apr 10, 2025
5c6779c
refactor(ai-integration.d.ts): Rename promise to result && Run regene…
Apr 10, 2025
e5213a3
revert && refactor(.d.ts): Remove extra tags && Revert promise name
Apr 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export {
AIIntegration,
AIProvider,
Prompt,
RequestParams,
Response,
} from 'devextreme/common/ai-integration';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"lib": {
"entryFile": "index.ts"
}
}
1 change: 1 addition & 0 deletions packages/devextreme-angular/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export {
VerticalEdge,
} from 'devextreme/common';

export * as AiIntegration from './ai-integration';
export * as Charts from './charts';
export * as Core from './core/index';
export * as Data from './data';
Expand Down
7 changes: 7 additions & 0 deletions packages/devextreme-react/src/common/ai-integration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export {
AIIntegration,
AIProvider,
Prompt,
RequestParams,
Response,
} from "devextreme/common/ai-integration";
1 change: 1 addition & 0 deletions packages/devextreme-react/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export {
VerticalEdge,
} from "devextreme/common";

export * as AiIntegration from "./ai-integration";
export * as Charts from "./charts";
export * as Core from "./core/index";
export * as Data from "./data";
Expand Down
7 changes: 7 additions & 0 deletions packages/devextreme-vue/src/common/ai-integration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export {
AIIntegration,
AIProvider,
Prompt,
RequestParams,
Response,
} from "devextreme/common/ai-integration";
1 change: 1 addition & 0 deletions packages/devextreme-vue/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export {
VerticalEdge,
} from "devextreme/common";

export * as AiIntegration from "./ai-integration";
export * as Charts from "./charts";
export * as Core from "./core/index";
export * as Data from "./data";
Expand Down
7 changes: 5 additions & 2 deletions packages/devextreme/build/gulp/js-bundles.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const namedDebug = lazyPipe()
.pipe(named, (file) => path.basename(file.path, path.extname(file.path)) + '.debug');

const BUNDLES = [
'/bundles/dx.ai-integration.js',
'/bundles/dx.all.js',
'/bundles/dx.web.js',
'/bundles/dx.viz.js'
Expand Down Expand Up @@ -53,8 +54,10 @@ const jsBundlesProd = (src, dist, bundles) => (() =>
);

gulp.task('js-bundles-prod',
jsBundlesProd(ctx.TRANSPILED_PROD_RENOVATION_PATH,
ctx.RESULT_JS_PATH, BUNDLES
jsBundlesProd(
ctx.TRANSPILED_PROD_RENOVATION_PATH,
ctx.RESULT_JS_PATH,
BUNDLES,
)
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import {
beforeEach,
describe,
expect,
it,
jest,
} from '@jest/globals';
import type { AIProvider, RequestCallbacks } from '@js/common/ai-integration';
import { BaseCommand } from '@ts/core/ai_integration/commands/base';
import type { PromptData, PromptTemplateName } from '@ts/core/ai_integration/core/prompt_manager';
import { PromptManager } from '@ts/core/ai_integration/core/prompt_manager';
import { RequestManager } from '@ts/core/ai_integration/core/request_manager';
import { Provider } from '@ts/core/ai_integration/test_utils/provider_mock';

jest.mock('@ts/core/ai_integration/templates/index', () => ({
templates: {
'test-template-name': {
system: 'System test template with {{first}}',
user: 'User test template with {{second}}',
},
},
}));

interface TestCommandParams {
first: string;
second: string;
}

class TestCommand extends BaseCommand<TestCommandParams, string> {
getTemplateName(): PromptTemplateName {
return 'test-template-name' as PromptTemplateName;
}

buildPromptData(params: TestCommandParams): PromptData {
const data = {
system: { first: params?.first },
user: { second: params?.second },
};

return data;
}

parseResult(response: string): string {
return `Parsed result: ${response}`;
}
}

describe('BaseCommand', () => {
let promptManager = null as unknown as PromptManager;
let requestManager = null as unknown as RequestManager;
let command = null as unknown as TestCommand;

const params: TestCommandParams = { first: 'first', second: 'second' };

beforeEach(() => {
const provider: AIProvider = new Provider();

requestManager = new RequestManager(provider);
promptManager = new PromptManager();

command = new TestCommand(promptManager, requestManager);
});

describe('constructor', () => {
it('stores PromptManager and RequestManager correctly', () => {
// @ts-expect-error Access to protected property for a test
expect(command.promptManager).toBe(promptManager);
// @ts-expect-error Access to protected property for a test
expect(command.requestManager).toBe(requestManager);
});
});

describe('execute', () => {
it('getTemplateName returns value correctly', () => {
const spy = jest.spyOn(command, 'getTemplateName');

command.execute(params, {});

expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveReturnedWith('test-template-name');
});

it('buildPromptData receives and returns correct data', () => {
const spy = jest.spyOn(command, 'buildPromptData');

command.execute(params, {});

expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(params);
expect(spy).toHaveReturnedWith({
system: { first: params.first },
user: { second: params.second },
});
});

it('parseResult receives correct value and returns expected result', async () => {
const spy = jest.spyOn(command, 'parseResult');

command.execute(params, {});

await new Promise(process.nextTick);

expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith('AI response');
expect(spy).toHaveReturnedWith('Parsed result: AI response');
});

it('callbacks are called correctly', async () => {
const callbacks = {
onComplete: jest.fn(),
onError: jest.fn(),
onChunk: jest.fn(),
};

command.execute(params, callbacks as RequestCallbacks);

await new Promise(process.nextTick);

expect(callbacks.onComplete).toHaveBeenCalledTimes(1);
expect(callbacks.onError).toHaveBeenCalledTimes(0);
expect(callbacks.onChunk).toHaveBeenCalledTimes(2);
});

it('onComplete is called with parseResult output', async () => {
const callbacks = { onComplete: jest.fn() };

command.execute(params, callbacks as RequestCallbacks);

await new Promise(process.nextTick);

expect(callbacks.onComplete).toHaveBeenCalledWith('Parsed result: AI response');
});

it('calls onError if request fails', async () => {
const originalSendRequest = requestManager.sendRequest;

requestManager.sendRequest = (_, callbacks) => {
callbacks.onError?.(new Error('Test error'));

return (): void => {};
};

try {
const callbacks = {
onError: jest.fn(),
onComplete: jest.fn(),
};

command.execute(params, callbacks as RequestCallbacks);

await new Promise(process.nextTick);

expect(callbacks.onError).toHaveBeenCalledTimes(1);
expect(callbacks.onError).toHaveBeenCalledWith(new Error('Test error'));
expect(callbacks.onComplete).toHaveBeenCalledTimes(0);
} finally {
requestManager.sendRequest = originalSendRequest;
}
});

it('calls onChunk for each chunk and onComplete correctly', () => {
const originalSendRequest = requestManager.sendRequest;

requestManager.sendRequest = (_, callbacks) => {
callbacks.onChunk?.('first');
callbacks.onChunk?.('second');
callbacks.onComplete?.('first second');

return (): void => {};
};

try {
const onChunk = jest.fn();
const onComplete = jest.fn();

command.execute(params, { onChunk, onComplete });

expect(onChunk).toHaveBeenCalledTimes(2);
expect(onChunk).toHaveBeenNthCalledWith(1, 'first');
expect(onChunk).toHaveBeenNthCalledWith(2, 'second');
expect(onComplete).toHaveBeenCalledTimes(1);
expect(onComplete).toHaveBeenNthCalledWith(1, 'Parsed result: first second');
} finally {
requestManager.sendRequest = originalSendRequest;
}
});

it('executes with undefined params without errors', async () => {
const sendRequestSpy = jest.spyOn(requestManager, 'sendRequest');
const onError = jest.fn();

expect(command.execute(undefined as unknown as TestCommandParams, { onError })).not.toThrow();

await new Promise(process.nextTick);

expect(onError).toHaveBeenCalledTimes(0);
expect(sendRequestSpy).toHaveBeenCalledTimes(1);
});

it('executes with partial callbacks without errors', async () => {
const sendRequestSpy = jest.spyOn(requestManager, 'sendRequest');
const callbacks = { onChunk: jest.fn() };

expect(command.execute(params, callbacks)).not.toThrow();

await new Promise(process.nextTick);

expect(callbacks.onChunk).toHaveBeenCalledTimes(2);
expect(sendRequestSpy).toHaveBeenCalledTimes(1);
});

it('executes with undefined callbacks without errors', () => {
const sendRequestSpy = jest.spyOn(requestManager, 'sendRequest');

expect(command.execute(
params,
(undefined as unknown as RequestCallbacks),
)).not.toThrow();

expect(sendRequestSpy).toHaveBeenCalledTimes(1);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { BaseCommandResult, RequestCallbacks } from '@js/common/ai-integration';
import type { PromptData, PromptManager, PromptTemplateName } from '@ts/core/ai_integration/core/prompt_manager';
import type { RequestManager } from '@ts/core/ai_integration/core/request_manager';

export abstract class BaseCommand<TParams, TResult extends BaseCommandResult> {
constructor(
protected promptManager: PromptManager,
protected requestManager: RequestManager,
) {}

public execute(params: TParams, callbacks: RequestCallbacks): () => void {
const templateName = this.getTemplateName();
const data = this.buildPromptData(params);

const prompt = this.promptManager.buildPrompt(templateName, data);

const abort = this.requestManager.sendRequest(prompt, {
onChunk: (chunk) => { callbacks?.onChunk?.(chunk); },
onComplete: (result) => {
const finalResponse = this.parseResult(result);

callbacks?.onComplete?.(finalResponse);
},
onError: (error) => { callbacks?.onError?.(error); },
});

return abort;
}

protected abstract getTemplateName(): PromptTemplateName;
protected abstract buildPromptData(params: TParams): PromptData;
protected abstract parseResult(response: string): TResult;
}
Loading
Loading