Skip to content

Commit 4e96727

Browse files
authored
Merge pull request #84 from easyops-cn/steve/copilot
feat(): monaco editor copilot
2 parents b68fefa + d4bc041 commit 4e96727

File tree

21 files changed

+1097
-132
lines changed

21 files changed

+1097
-132
lines changed

bricks/vs/build.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export default {
2424
rootMode: "upward",
2525
},
2626
},
27+
{
28+
test: /\.txt$/,
29+
type: "asset/source",
30+
},
2731
],
2832
plugins: [
2933
new MonacoWebpackPlugin({

bricks/vs/docs/code-editor.md

Lines changed: 52 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,55 @@ children:
8888
8989
```yaml preview
9090
- brick: vs.code-editor
91+
context:
92+
- name: commonMarkers
93+
value:
94+
- token: PATH
95+
message: 这是 PATH
96+
level: hit
97+
- token: QUERY
98+
message: 这是 QUERY
99+
level: info
100+
- token: ANCHOR
101+
message: 这是 ANCHOR
102+
level: warn
103+
- token: STATE
104+
message: 这里不能写 STATE
105+
level: error
106+
code:
107+
value: 详情地址
108+
target: https://brick-next.js.org/docs/concepts/context
109+
- token: TPL
110+
level: warn
111+
message: 不允许写入TPL
112+
- name: commonLibs
113+
value:
114+
- filePath: base.d.ts
115+
content: |
116+
declare namespace CTX {
117+
const pageTitle: string;
118+
const name: string;
119+
const a;
120+
const b;
121+
};
122+
declare namespace FN {
123+
function getPageDetail();
124+
function getInstance();
125+
};
126+
declare namespace PATH {
127+
const instanceId: string;
128+
const name: string;
129+
};
130+
declare namespace QUERY {
131+
const activeId: string;
132+
}
91133
events:
92134
token.click:
93135
- action: console.log
94136
properties:
95137
language: brick_next_yaml
96138
value: |
97-
basicUsige:
139+
basicUsage:
98140
keyword:
99141
Expression:
100142
expression1: <% CTX.work %>
@@ -176,7 +218,7 @@ children:
176218
%>
177219
test7: |
178220
<% "track context", CTX.name
179-
exporession16:
221+
expression16:
180222
- <% CTX.a %>
181223
- CTX.b
182224
- <% CTX.c %>
@@ -198,25 +240,6 @@ children:
198240
links:
199241
- CTX
200242
- FN
201-
markers:
202-
- token: PATH
203-
message: “这是 PATH”
204-
level: hit
205-
- token: QUERY
206-
message: “这是 QUERY”
207-
level: info
208-
- token: ANCHOR
209-
message: “这是 ANCHOR“
210-
level: warn
211-
- token: STATE
212-
message: "这里不能写 STATE"
213-
level: error
214-
code:
215-
value: "详情地址"
216-
target: https://brick-next.js.org/docs/concepts/context
217-
- token: TPL
218-
level: warn
219-
message: 不允许写入TPL
220243
completers:
221244
- label: buttonName
222245
detail: string
@@ -230,26 +253,8 @@ children:
230253
completers:
231254
- label: a
232255
- label: b
233-
extraLibs:
234-
- filePath: base.d.ts
235-
content: |
236-
declare namespace CTX {
237-
const pageTitle: string;
238-
const name: string;
239-
const a;
240-
const b;
241-
};
242-
declare namespace FN {
243-
function getPageDetail();
244-
function getInstance();
245-
};
246-
declare namespace PATH {
247-
const instanceId: string;
248-
const name: string;
249-
};
250-
declare namespace QUERY {
251-
const activeId: string;
252-
}
256+
markers: <% CTX.commonMarkers %>
257+
extraLibs: <% CTX.commonLibs %>
253258
- brick: vs.code-editor
254259
events:
255260
highlight.click:
@@ -264,6 +269,8 @@ children:
264269
links:
265270
- CTX
266271
- FN
272+
markers: <% CTX.commonMarkers %>
273+
extraLibs: <% CTX.commonLibs %>
267274
- brick: vs.code-editor
268275
events:
269276
highlight.click:
@@ -276,6 +283,8 @@ children:
276283
links:
277284
- CTX
278285
- FN
286+
markers: <% CTX.commonMarkers %>
287+
extraLibs: <% CTX.commonLibs %>
279288
- brick: vs.code-editor
280289
events:
281290
highlight.click:
@@ -289,6 +298,8 @@ children:
289298
links:
290299
- CTX
291300
- FN
301+
markers: <% CTX.commonMarkers %>
302+
extraLibs: <% CTX.commonLibs %>
292303
```
293304
294305
### Show CTX.DS

bricks/vs/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,17 @@
5858
},
5959
"dependencies": {
6060
"@babel/types": "^7.22.5",
61+
"@next-api-sdk/llm-sdk": "^1.1.3",
6162
"@next-core/cook": "^2.5.7",
6263
"@next-core/element": "^1.2.17",
6364
"@next-core/monaco-contributions": "^0.3.14",
6465
"@next-core/react-element": "^1.0.36",
6566
"@next-core/react-runtime": "^1.7.11",
67+
"@next-core/runtime": "^1.59.4",
6668
"@next-core/theme": "^1.5.4",
6769
"@next-core/utils": "^1.7.32",
6870
"@next-shared/form": "^0.7.10",
71+
"@next-shared/monaco-copilot": "^0.0.0",
6972
"@next-shared/spell-check": "^0.1.1",
7073
"comlink": "^4.4.2",
7174
"monaco-editor": "^0.50.0",

bricks/vs/src/code-editor/index.spec.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ import { CodeEditor } from "./index.js";
66
import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js";
77

88
jest.mock("@next-core/theme", () => ({}));
9+
jest.mock("@next-core/runtime", () => ({
10+
getRuntime() {
11+
return null;
12+
},
13+
getV2RuntimeFromDll() {
14+
return null;
15+
},
16+
}));
917
jest.mock("@next-core/react-runtime");
1018
jest.mock("./workers/yamlLinter.js", () => ({}));
1119
jest.mock("./workers/spellCheckRemoteWorker.js", () => ({}));

bricks/vs/src/code-editor/index.tsx

Lines changed: 103 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { EventEmitter, createDecorators } from "@next-core/element";
1010
import { unwrapProvider } from "@next-core/utils/general";
1111
import { wrapBrick } from "@next-core/react-element";
1212
import { useCurrentTheme } from "@next-core/react-runtime";
13+
import { getRuntime } from "@next-core/runtime";
14+
import { HttpAbortError } from "@next-core/http";
1315
import { FormItemElementBase } from "@next-shared/form";
1416
import type { FormItem, FormItemProps } from "@next-bricks/form/form-item";
1517
import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js";
@@ -38,7 +40,6 @@ import {
3840
getEmbeddedJavascriptUri,
3941
getBrickYamlBuiltInDeclare,
4042
} from "./utils/jsSuggestInBrickYaml.js";
41-
import { addExtraLibs } from "./utils/addExtraLibs.js";
4243
import type { EoTooltip, ToolTipProps } from "@next-bricks/basic/tooltip";
4344
import type {
4445
GeneralIcon,
@@ -49,6 +50,11 @@ import { K, NS, locales } from "./i18n.js";
4950
import type { copyToClipboard as _copyToClipboard } from "@next-bricks/basic/data-providers/copy-to-clipboard";
5051
import type { showNotification as _showNotification } from "@next-bricks/basic/data-providers/show-notification/show-notification";
5152
import classNames from "classnames";
53+
import {
54+
MonacoCopilotProvider,
55+
SUPPORTED_LANGUAGES,
56+
} from "@next-shared/monaco-copilot";
57+
import { AiopsBaseApi_openaiChat } from "@next-api-sdk/llm-sdk";
5258
import "./index.css";
5359
import { EmbeddedModelContext } from "./utils/embeddedModelState.js";
5460
import { PlaceholderContentWidget } from "./widget/Placeholder.js";
@@ -62,6 +68,12 @@ import {
6268
celCommonCompletionProviderFactory,
6369
celSpecificCompletionProviderFactory,
6470
} from "./utils/celCompletionProvider.js";
71+
import {
72+
disposeEditor,
73+
isCurrentEditor,
74+
switchEditor,
75+
} from "./utils/EditorService.js";
76+
import { setExtraLibs } from "./utils/setExtraLibs.js";
6577

6678
initializeReactI18n(NS, locales);
6779

@@ -89,6 +101,47 @@ for (const lang of CEL_FAMILY) {
89101
);
90102
}
91103

104+
// istanbul ignore next
105+
const { enabled: copilotEnabled, ...copilotOptions } =
106+
(getRuntime()?.getMiscSettings().globalMonacoEditorCopilotOptions ?? {}) as {
107+
enabled?: boolean;
108+
model?: string;
109+
debounce?: number;
110+
timeout?: number;
111+
};
112+
113+
// istanbul ignore next
114+
if (copilotEnabled) {
115+
monaco.languages.registerInlineCompletionsProvider(
116+
SUPPORTED_LANGUAGES,
117+
new MonacoCopilotProvider({
118+
...copilotOptions,
119+
async request({ model, temperature, system, prompt, signal }) {
120+
const response = await AiopsBaseApi_openaiChat(
121+
{
122+
model,
123+
temperature,
124+
enableSensitiveWordsFilter: false,
125+
stream: false,
126+
messages: [
127+
...(system ? [{ role: "system", content: system }] : []),
128+
{ role: "user", content: prompt },
129+
],
130+
},
131+
{
132+
signal,
133+
interceptorParams: {
134+
ignoreLoadingBar: true,
135+
},
136+
}
137+
);
138+
return response.choices?.[0]?.message?.content?.trim();
139+
},
140+
HttpAbortError,
141+
})
142+
);
143+
}
144+
92145
const { defineElement, property, event } = createDecorators();
93146

94147
const WrappedFormItem = wrapBrick<FormItem, FormItemProps>("eo-form-item");
@@ -712,19 +765,6 @@ export function CodeEditorComponent({
712765
const languageDefaults =
713766
language === "typescript" ? "typescriptDefaults" : "javascriptDefaults";
714767

715-
useEffect(() => {
716-
const libs: ExtraLib[] = extraLibs ?? [];
717-
718-
const disposables = addExtraLibs(libs, {
719-
languageDefaults,
720-
});
721-
return () => {
722-
for (const item of disposables) {
723-
item.dispose();
724-
}
725-
};
726-
}, [extraLibs, language, languageDefaults]);
727-
728768
useEffect(() => {
729769
if (
730770
language === "javascript" ||
@@ -741,6 +781,55 @@ export function CodeEditorComponent({
741781
}
742782
}, [language, domLibsEnabled, languageDefaults]);
743783

784+
useEffect(() => {
785+
const editor = editorRef.current;
786+
if (!editor) {
787+
return;
788+
}
789+
return () => {
790+
disposeEditor(editor);
791+
};
792+
}, []);
793+
794+
useEffect(() => {
795+
const editor = editorRef.current;
796+
if (!editor) {
797+
return;
798+
}
799+
800+
const setupLanguageService = () => {
801+
setExtraLibs(extraLibs, { languageDefaults });
802+
803+
if (
804+
language === "javascript" ||
805+
language === "typescript" ||
806+
language === "brick_next_yaml"
807+
) {
808+
monaco.languages.typescript[languageDefaults].setCompilerOptions({
809+
allowNonTsExtensions: true,
810+
lib: domLibsEnabled ? undefined : ["esnext"],
811+
target: monaco.languages.typescript.ScriptTarget.ESNext,
812+
moduleResolution:
813+
monaco.languages.typescript.ModuleResolutionKind.NodeJs,
814+
});
815+
}
816+
};
817+
818+
if (isCurrentEditor(editor)) {
819+
setupLanguageService();
820+
}
821+
822+
const disposable = editor.onDidFocusEditorWidget(() => {
823+
// Set language service for this particular editor instance
824+
if (switchEditor(editor)) {
825+
setupLanguageService();
826+
}
827+
});
828+
return () => {
829+
disposable.dispose();
830+
};
831+
}, [domLibsEnabled, extraLibs, language, languageDefaults]);
832+
744833
useEffect(() => {
745834
const editor = editorRef.current;
746835
if (language === "brick_next_yaml" && editor) {
@@ -971,13 +1060,6 @@ export function CodeEditorComponent({
9711060
}
9721061
}, [expanded]);
9731062

974-
useEffect(() => {
975-
return () => {
976-
editorRef.current?.getModel()?.dispose();
977-
editorRef.current?.dispose();
978-
};
979-
}, []);
980-
9811063
useEffect(() => {
9821064
if (!editorRef.current && !placeholder) return;
9831065
const placeholderWidget = new PlaceholderContentWidget(

0 commit comments

Comments
 (0)