Skip to content

Commit 1dcfc1f

Browse files
committed
feat: code assistant integration
1 parent b190e24 commit 1dcfc1f

File tree

9 files changed

+224
-88
lines changed

9 files changed

+224
-88
lines changed

package-lock.json

Lines changed: 30 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"@hookform/resolvers": "^3.10.0",
2929
"@reduxjs/toolkit": "^2.5.0",
3030
"@tanstack/react-table": "^8.20.6",
31+
"@ydb-platform/monaco-ghost": "^0.1.7",
3132
"axios": "^1.7.9",
3233
"axios-retry": "^4.5.0",
3334
"colord": "^2.9.3",

src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx

Lines changed: 131 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
import React from 'react';
2+
13
import NiceModal from '@ebay/nice-modal-react';
4+
import {useMonacoGhost} from '@ydb-platform/monaco-ghost';
25
import throttle from 'lodash/throttle';
36
import type Monaco from 'monaco-editor';
47

58
import {MonacoEditor} from '../../../../components/MonacoEditor/MonacoEditor';
9+
import {codeAssistApi} from '../../../../store/reducers/codeAssist/codeAssist';
610
import {
711
goToNextQuery,
812
goToPreviousQuery,
@@ -68,95 +72,135 @@ export function YqlEditor({
6872
window.ydbEditor = undefined;
6973
};
7074

71-
const editorDidMount = (editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco) => {
72-
window.ydbEditor = editor;
73-
const keybindings = getKeyBindings(monaco);
74-
monaco.editor.registerCommand('insertSnippetToEditor', (_asessor, input: string) => {
75-
//suggestController is not properly typed yet in monaco-editor package
76-
const contribution = editor.getContribution<any>('snippetController2');
77-
if (contribution) {
78-
editor.focus();
79-
editor.setValue('');
80-
contribution.insert(input);
81-
}
82-
});
83-
initResizeHandler(editor);
84-
initUserPrompt(editor, getLastQueryText);
85-
editor.focus();
86-
editor.addAction({
87-
id: 'sendQuery',
88-
label: i18n('action.send-query'),
89-
keybindings: [keybindings.sendQuery],
90-
// A precondition for this action.
91-
precondition: undefined,
92-
// A rule to evaluate on top of the precondition in order to dispatch the keybindings.
93-
keybindingContext: undefined,
94-
contextMenuGroupId: CONTEXT_MENU_GROUP_ID,
95-
contextMenuOrder: 1,
96-
// Method that will be executed when the action is triggered.
97-
// @param editor The editor instance is passed in as a convenience
98-
run: () => handleSendQuery(),
99-
});
100-
101-
const canSendSelectedText = editor.createContextKey<boolean>('canSendSelectedText', false);
102-
editor.onDidChangeCursorSelection(({selection, secondarySelections}) => {
103-
const notEmpty =
104-
selection.selectionStartLineNumber !== selection.positionLineNumber ||
105-
selection.selectionStartColumn !== selection.positionColumn;
106-
const hasMultipleSelections = secondarySelections.length > 0;
107-
canSendSelectedText.set(notEmpty && !hasMultipleSelections);
108-
});
109-
editor.addAction({
110-
id: 'sendSelectedQuery',
111-
label: i18n('action.send-selected-query'),
112-
keybindings: [keybindings.sendSelectedQuery],
113-
precondition: 'canSendSelectedText',
114-
contextMenuGroupId: CONTEXT_MENU_GROUP_ID,
115-
contextMenuOrder: 1,
116-
run: (e) => {
117-
const selection = e.getSelection();
118-
const model = e.getModel();
119-
if (selection && model) {
120-
const text = model.getValueInRange({
121-
startLineNumber: selection.getSelectionStart().lineNumber,
122-
startColumn: selection.getSelectionStart().column,
123-
endLineNumber: selection.getPosition().lineNumber,
124-
endColumn: selection.getPosition().column,
125-
});
126-
handleSendExecuteClick(text, true);
75+
const [sendCodeAssistPrompt] = codeAssistApi.useLazyGetCodeAssistSuggestionsQuery();
76+
77+
const {registerMonacoGhost, dispose} = useMonacoGhost({
78+
api: {
79+
getCodeAssistSuggestions: async (prompt) => {
80+
try {
81+
return await sendCodeAssistPrompt(prompt).unwrap();
82+
} catch {
83+
return {items: []};
12784
}
12885
},
129-
});
130-
131-
editor.addAction({
132-
id: 'previous-query',
133-
label: i18n('action.previous-query'),
134-
keybindings: [keybindings.selectPreviousQuery],
135-
contextMenuGroupId: CONTEXT_MENU_GROUP_ID,
136-
contextMenuOrder: 2,
137-
run: () => {
138-
dispatch(goToPreviousQuery());
139-
},
140-
});
141-
editor.addAction({
142-
id: 'next-query',
143-
label: i18n('action.next-query'),
144-
keybindings: [keybindings.selectNextQuery],
145-
contextMenuGroupId: CONTEXT_MENU_GROUP_ID,
146-
contextMenuOrder: 3,
147-
run: () => {
148-
dispatch(goToNextQuery());
149-
},
150-
});
151-
editor.addAction({
152-
id: 'save-query',
153-
label: i18n('action.save-query'),
154-
keybindings: [keybindings.saveQuery],
155-
run: () => {
156-
NiceModal.show(SAVE_QUERY_DIALOG);
157-
},
158-
});
159-
};
86+
},
87+
config: {
88+
language: YQL_LANGUAGE_ID,
89+
},
90+
});
91+
92+
const editorDidMount = React.useCallback(
93+
(editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco) => {
94+
window.ydbEditor = editor;
95+
const keybindings = getKeyBindings(monaco);
96+
monaco.editor.registerCommand('insertSnippetToEditor', (_asessor, input: string) => {
97+
//suggestController is not properly typed yet in monaco-editor package
98+
const contribution = editor.getContribution<any>('snippetController2');
99+
if (contribution) {
100+
editor.focus();
101+
editor.setValue('');
102+
contribution.insert(input);
103+
}
104+
});
105+
106+
if (window.api.codeAssist) {
107+
registerMonacoGhost(editor);
108+
}
109+
initResizeHandler(editor);
110+
initUserPrompt(editor, getLastQueryText);
111+
editor.focus();
112+
editor.addAction({
113+
id: 'sendQuery',
114+
label: i18n('action.send-query'),
115+
keybindings: [keybindings.sendQuery],
116+
// A precondition for this action.
117+
precondition: undefined,
118+
// A rule to evaluate on top of the precondition in order to dispatch the keybindings.
119+
keybindingContext: undefined,
120+
contextMenuGroupId: CONTEXT_MENU_GROUP_ID,
121+
contextMenuOrder: 1,
122+
// Method that will be executed when the action is triggered.
123+
// @param editor The editor instance is passed in as a convenience
124+
run: () => handleSendQuery(),
125+
});
126+
127+
const canSendSelectedText = editor.createContextKey<boolean>(
128+
'canSendSelectedText',
129+
false,
130+
);
131+
editor.onDidChangeCursorSelection(({selection, secondarySelections}) => {
132+
const notEmpty =
133+
selection.selectionStartLineNumber !== selection.positionLineNumber ||
134+
selection.selectionStartColumn !== selection.positionColumn;
135+
const hasMultipleSelections = secondarySelections.length > 0;
136+
canSendSelectedText.set(notEmpty && !hasMultipleSelections);
137+
});
138+
editor.addAction({
139+
id: 'sendSelectedQuery',
140+
label: i18n('action.send-selected-query'),
141+
keybindings: [keybindings.sendSelectedQuery],
142+
precondition: 'canSendSelectedText',
143+
contextMenuGroupId: CONTEXT_MENU_GROUP_ID,
144+
contextMenuOrder: 1,
145+
run: (e) => {
146+
const selection = e.getSelection();
147+
const model = e.getModel();
148+
if (selection && model) {
149+
const text = model.getValueInRange({
150+
startLineNumber: selection.getSelectionStart().lineNumber,
151+
startColumn: selection.getSelectionStart().column,
152+
endLineNumber: selection.getPosition().lineNumber,
153+
endColumn: selection.getPosition().column,
154+
});
155+
handleSendExecuteClick(text, true);
156+
}
157+
},
158+
});
159+
160+
editor.addAction({
161+
id: 'previous-query',
162+
label: i18n('action.previous-query'),
163+
keybindings: [keybindings.selectPreviousQuery],
164+
contextMenuGroupId: CONTEXT_MENU_GROUP_ID,
165+
contextMenuOrder: 2,
166+
run: () => {
167+
dispatch(goToPreviousQuery());
168+
},
169+
});
170+
editor.addAction({
171+
id: 'next-query',
172+
label: i18n('action.next-query'),
173+
keybindings: [keybindings.selectNextQuery],
174+
contextMenuGroupId: CONTEXT_MENU_GROUP_ID,
175+
contextMenuOrder: 3,
176+
run: () => {
177+
dispatch(goToNextQuery());
178+
},
179+
});
180+
editor.addAction({
181+
id: 'save-query',
182+
label: i18n('action.save-query'),
183+
keybindings: [keybindings.saveQuery],
184+
run: () => {
185+
NiceModal.show(SAVE_QUERY_DIALOG);
186+
},
187+
});
188+
189+
return () => {
190+
if (window.api.codeAssist) {
191+
dispose();
192+
}
193+
};
194+
},
195+
[
196+
dispatch,
197+
dispose,
198+
getLastQueryText,
199+
handleSendExecuteClick,
200+
handleSendQuery,
201+
registerMonacoGhost,
202+
],
203+
);
160204

161205
const onChange = (newValue: string) => {
162206
updateErrorsHighlighting();

src/services/api/codeAssist.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type {PromptFile, Suggestions} from '@ydb-platform/monaco-ghost';
2+
3+
import {codeAssistBackend as CODE_ASSISTANT_BACKEND} from '../../store';
4+
5+
import {BaseYdbAPI} from './base';
6+
const ideInfo = {
7+
Ide: 'ydb',
8+
IdeVersion: '1',
9+
PluginFamily: 'ydb',
10+
PluginVersion: '0.2',
11+
};
12+
export class CodeAssistAPI extends BaseYdbAPI {
13+
getPath(path: string) {
14+
return `${CODE_ASSISTANT_BACKEND ?? ''}${path}`;
15+
}
16+
17+
getCodeAssistSuggestions(data: PromptFile[]) {
18+
return this.post<Suggestions>(
19+
this.getPath('/code-assist-suggestion'),
20+
{
21+
Files: data,
22+
ContextCreateType: 1,
23+
IdeInfo: ideInfo,
24+
},
25+
null,
26+
{
27+
concurrentId: 'code-assist-suggestion',
28+
collectRequest: false,
29+
},
30+
);
31+
}
32+
}

src/services/api/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {AxiosRequestConfig} from 'axios';
22

33
import {AuthAPI} from './auth';
4+
import {CodeAssistAPI} from './codeAssist';
45
import {MetaAPI} from './meta';
56
import {OperationAPI} from './operation';
67
import {PDiskAPI} from './pdisk';
@@ -22,11 +23,13 @@ export class YdbEmbeddedAPI {
2223
vdisk: VDiskAPI;
2324
viewer: ViewerAPI;
2425
meta?: MetaAPI;
26+
codeAssist?: CodeAssistAPI;
2527

2628
constructor({config, webVersion}: {config: AxiosRequestConfig; webVersion?: boolean}) {
2729
this.auth = new AuthAPI({config});
2830
if (webVersion) {
2931
this.meta = new MetaAPI({config});
32+
this.codeAssist = new CodeAssistAPI({config});
3033
}
3134
this.operation = new OperationAPI({config});
3235
this.pdisk = new PDiskAPI({config});

src/store/configureStore.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ function _configureStore<
4646
export const webVersion = window.web_version;
4747
export const customBackend = window.custom_backend;
4848
export const metaBackend = window.meta_backend;
49+
export const codeAssistBackend = window.code_assist_backend;
4950

5051
const isSingleClusterMode = `${metaBackend}` === 'undefined';
5152

src/store/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export {
55
configureStore,
66
customBackend,
77
metaBackend,
8+
codeAssistBackend,
89
webVersion,
910
} from './configureStore';
1011
export {rootReducer} from './reducers';
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type {PromptFile, Suggestions} from '@ydb-platform/monaco-ghost';
2+
3+
import {api} from '../api';
4+
5+
export const codeAssistApi = api.injectEndpoints({
6+
endpoints: (builder) => ({
7+
getCodeAssistSuggestions: builder.query<Suggestions, PromptFile[]>({
8+
queryFn: async (prompt: PromptFile[]) => {
9+
try {
10+
if (window.api.codeAssist) {
11+
const data = await window.api.codeAssist.getCodeAssistSuggestions(prompt);
12+
return {data};
13+
} else {
14+
throw new Error('Method is not implemented.');
15+
}
16+
} catch (error) {
17+
return {error};
18+
}
19+
},
20+
providesTags: ['All'],
21+
}),
22+
}),
23+
overrideExisting: 'throw',
24+
});

src/types/window.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ interface Window {
3737
web_version?: boolean;
3838
custom_backend?: string;
3939
meta_backend?: string;
40+
code_assist_backend?: string;
4041

4142
userSettings?: import('../services/settings').SettingsObject;
4243
systemSettings?: import('../services/settings').SettingsObject;

0 commit comments

Comments
 (0)