Skip to content

Commit a24893d

Browse files
committed
feat: code assistant
1 parent 14b96e7 commit a24893d

File tree

10 files changed

+531
-0
lines changed

10 files changed

+531
-0
lines changed

public/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
window.web_version = !'%REACT_APP_BACKEND%';
2626
window.custom_backend = '%NODE_ENV%' === 'development' && '%REACT_APP_BACKEND%';
2727
window.meta_backend = '%REACT_APP_META_BACKEND%'
28+
window.code_assistant_backend = '%REACT_APP_CODE_ASSISTANT_BACKEND%'
2829
</script>
2930
</head>
3031
<body>

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
import {useChangedQuerySettings} from '../../../../utils/hooks/useChangedQuerySettings';
4343
import {useLastQueryExecutionSettings} from '../../../../utils/hooks/useLastQueryExecutionSettings';
4444
import {YQL_LANGUAGE_ID} from '../../../../utils/monaco/constats';
45+
import {inlineCompletionProviderInstance} from '../../../../utils/monaco/yql/ydb.inlineCompletionProvider';
4546
import {QUERY_ACTIONS} from '../../../../utils/query';
4647
import type {InitialPaneState} from '../../utils/paneVisibilityToggleHelpers';
4748
import {
@@ -215,6 +216,23 @@ export default function QueryEditor(props: QueryEditorProps) {
215216
contribution.insert(input);
216217
}
217218
});
219+
220+
if (window.api.codeAssistant) {
221+
monaco.editor.registerCommand('acceptCodeAssistCompletion', (_accessor, ...args) => {
222+
const data = args[0] ?? {};
223+
if (!data || typeof data !== 'object') {
224+
return;
225+
}
226+
const {requestId, suggestionText} = data;
227+
if (requestId && suggestionText) {
228+
inlineCompletionProviderInstance.handleAccept({requestId, suggestionText});
229+
}
230+
});
231+
232+
monaco.editor.registerCommand('declineCodeAssistCompletion', () => {
233+
inlineCompletionProviderInstance.commandDiscard();
234+
});
235+
}
218236
initResizeHandler(editor);
219237
initUserPrompt(editor, getLastQueryText);
220238
editor.focus();

src/services/api/codeAssistant.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import {codeAssistantBackend as CODE_ASSISTANT_BACKEND} from '../../store';
2+
import type {
3+
PromptFile,
4+
Suggestions,
5+
TelemetryEvent,
6+
TelemetryOpenTabs,
7+
} from '../../types/api/codeAssistant';
8+
9+
import {BaseYdbAPI} from './base';
10+
11+
const ideInfo = {
12+
Ide: 'yql',
13+
IdeVersion: '1',
14+
PluginFamily: 'yql',
15+
PluginVersion: '0.2',
16+
};
17+
18+
export class CodeAssistAPI extends BaseYdbAPI {
19+
getPath(path: string) {
20+
return `${CODE_ASSISTANT_BACKEND ?? ''}${path}`;
21+
}
22+
23+
getCodeAssistSuggestions(data: PromptFile[]): Promise<Suggestions> {
24+
return this.post<Suggestions>(
25+
this.getPath('/code-assist-suggestion'),
26+
{
27+
Files: data,
28+
ContextCreateType: 1,
29+
IdeInfo: ideInfo,
30+
},
31+
null,
32+
{
33+
concurrentId: 'code-assist-suggestion',
34+
collectRequest: false,
35+
},
36+
);
37+
}
38+
39+
sendCodeAssistTelemetry(data: TelemetryEvent): Promise<unknown> {
40+
return this.post(this.getPath('/code-assist-telemetry'), data, null, {
41+
concurrentId: 'code-assist-telemetry',
42+
collectRequest: false,
43+
});
44+
}
45+
46+
sendCodeAssistOpenTabs(data: TelemetryOpenTabs): Promise<unknown> {
47+
return this.post(
48+
this.getPath('/code-assist-telemetry'),
49+
{OpenTabs: {Tabs: data, IdeInfo: ideInfo}},
50+
null,
51+
{
52+
concurrentId: 'code-assist-telemetry',
53+
collectRequest: false,
54+
},
55+
);
56+
}
57+
}

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 './codeAssistant';
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+
codeAssistant?: 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.codeAssistant = 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 codeAssistantBackend = window.code_assistant_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
@@ -6,6 +6,7 @@ export {
66
customBackend,
77
metaBackend,
88
webVersion,
9+
codeAssistantBackend,
910
} from './configureStore';
1011
export {rootReducer} from './reducers';
1112

src/types/api/codeAssistant.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
type IdeInfo = {
2+
Ide: string;
3+
IdeVersion: string;
4+
PluginFamily: string;
5+
PluginVersion: string;
6+
};
7+
8+
export interface Prompt {
9+
Files: PromptFile[];
10+
ContextCreateType: ContextCreateType;
11+
ForceSuggest?: boolean;
12+
IdeInfo: IdeInfo;
13+
}
14+
15+
export interface PromptPosition {
16+
Ln: number;
17+
Col: number;
18+
}
19+
20+
export interface PromptFragment {
21+
Text: string;
22+
Start: PromptPosition;
23+
End: PromptPosition;
24+
}
25+
26+
export interface PromptFile {
27+
Path: string;
28+
Fragments: PromptFragment[];
29+
Cursor: PromptPosition;
30+
}
31+
32+
export type ContextCreateType = 1;
33+
34+
export interface Suggestions {
35+
Suggests: Suggestion[];
36+
RequestId: string;
37+
}
38+
39+
export type DiscardReason = 'OnCancel';
40+
41+
export interface Suggestion {
42+
Text: string;
43+
}
44+
45+
export interface AcceptSuggestionEvent {
46+
Accepted: {
47+
RequestId: string;
48+
Timestamp: number;
49+
AcceptedText: string;
50+
ConvertedText: string;
51+
};
52+
}
53+
export interface DiscardSuggestionEvent {
54+
Discarded: {
55+
RequestId: string;
56+
Timestamp: number;
57+
DiscardReason: 'OnCancel';
58+
DiscardedText: string;
59+
CacheHitCount: number;
60+
};
61+
}
62+
export interface IgnoreSuggestionEvent {
63+
Ignored: {
64+
RequestId: string;
65+
Timestamp: number;
66+
IgnoredText: string;
67+
};
68+
}
69+
70+
export type TelemetryEvent = AcceptSuggestionEvent | DiscardSuggestionEvent | IgnoreSuggestionEvent;
71+
72+
type OpenTab = {
73+
FileName: string;
74+
Text: string;
75+
};
76+
77+
export type TelemetryOpenTabs = OpenTab[];

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_assistant_backend?: string;
4041

4142
userSettings?: import('../services/settings').SettingsObject;
4243
systemSettings?: import('../services/settings').SettingsObject;
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import type Monaco from 'monaco-editor';
2+
import {v4} from 'uuid';
3+
4+
import type {DiscardReason, PromptFile, TelemetryEvent} from '../../types/api/codeAssistant';
5+
6+
const limitBeforeCursor = 8_000;
7+
const limitAfterCursor = 1_000;
8+
9+
const sessionId = v4();
10+
11+
export function getPromptFileContent(
12+
model: Monaco.editor.ITextModel,
13+
position: Monaco.Position,
14+
): PromptFile[] | undefined {
15+
const linesContent = model.getLinesContent();
16+
const prevTextInCurrentLine = linesContent[position.lineNumber - 1].slice(
17+
0,
18+
position.column - 1,
19+
);
20+
const postTextInCurrentLine = linesContent[position.lineNumber - 1].slice(position.column - 1);
21+
const prevText = linesContent
22+
.slice(0, position.lineNumber - 1)
23+
.concat([prevTextInCurrentLine])
24+
.join('\n');
25+
const postText = [postTextInCurrentLine]
26+
.concat(linesContent.slice(position.lineNumber))
27+
.join('\n');
28+
const cursorPostion = {Ln: position.lineNumber, Col: position.column};
29+
30+
const fragments = [];
31+
if (prevText) {
32+
fragments.push({
33+
Text:
34+
prevText.length > limitBeforeCursor
35+
? prevText.slice(prevText.length - limitBeforeCursor)
36+
: prevText,
37+
Start: {Ln: 1, Col: 1},
38+
End: cursorPostion,
39+
});
40+
}
41+
if (postText) {
42+
fragments.push({
43+
Text: postText.slice(0, limitAfterCursor),
44+
Start: cursorPostion,
45+
End: {
46+
Ln: linesContent.length,
47+
Col: linesContent[linesContent.length - 1].length,
48+
},
49+
});
50+
}
51+
52+
return fragments.length
53+
? [
54+
{
55+
Fragments: fragments,
56+
Cursor: cursorPostion,
57+
Path: `${sessionId}/query.yql`,
58+
},
59+
]
60+
: undefined;
61+
}
62+
63+
interface SendCodeAssistTelemetry {
64+
requestId: string;
65+
}
66+
67+
interface SendCodeAssistTelemetryAccept extends SendCodeAssistTelemetry {
68+
type: 'accept';
69+
70+
acceptedText: string;
71+
}
72+
interface SendCodeAssistTelemetryDecline extends SendCodeAssistTelemetry {
73+
type: 'decline';
74+
suggestionText: string;
75+
reason: DiscardReason;
76+
hitCount: number;
77+
}
78+
interface SendCodeAssistTelemetryIgnore extends SendCodeAssistTelemetry {
79+
type: 'ignore';
80+
suggestionText: string;
81+
}
82+
type SendCodeAssistTelemetryProps =
83+
| SendCodeAssistTelemetryAccept
84+
| SendCodeAssistTelemetryDecline
85+
| SendCodeAssistTelemetryIgnore;
86+
87+
function isAcceptType(data: SendCodeAssistTelemetryProps): data is SendCodeAssistTelemetryAccept {
88+
return data.type === 'accept';
89+
}
90+
function isDeclineType(data: SendCodeAssistTelemetryProps): data is SendCodeAssistTelemetryDecline {
91+
return data.type === 'decline';
92+
}
93+
function isIgnoreType(data: SendCodeAssistTelemetryProps): data is SendCodeAssistTelemetryIgnore {
94+
return data.type === 'ignore';
95+
}
96+
97+
export function sendCodeAssistTelemetry(props: SendCodeAssistTelemetryProps) {
98+
let data: TelemetryEvent | null = null;
99+
const timestamp = Date.now();
100+
if (isAcceptType(props)) {
101+
const {requestId, acceptedText} = props;
102+
data = {
103+
Accepted: {
104+
RequestId: requestId,
105+
Timestamp: timestamp,
106+
AcceptedText: acceptedText,
107+
ConvertedText: acceptedText,
108+
},
109+
};
110+
} else if (isDeclineType(props)) {
111+
const {requestId, suggestionText, reason, hitCount} = props;
112+
data = {
113+
Discarded: {
114+
RequestId: requestId,
115+
Timestamp: timestamp,
116+
DiscardReason: reason,
117+
DiscardedText: suggestionText,
118+
CacheHitCount: hitCount,
119+
},
120+
};
121+
} else if (isIgnoreType(props)) {
122+
const {requestId, suggestionText} = props;
123+
data = {
124+
Ignored: {
125+
RequestId: requestId,
126+
Timestamp: timestamp,
127+
IgnoredText: suggestionText,
128+
},
129+
};
130+
}
131+
if (data) {
132+
window.api.codeAssistant?.sendCodeAssistTelemetry(data);
133+
}
134+
}

0 commit comments

Comments
 (0)