matthoffner commited on
Commit
b5cf8f3
·
1 Parent(s): 44a417c

Create copilot

Browse files
Files changed (1) hide show
  1. app/editor/copilot +244 -0
app/editor/copilot ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
2
+ import { fetchSSE } from './fetch-sse';
3
+
4
+ interface llmParams {
5
+ model?: string;
6
+ temperature?: number;
7
+ max_tokens?: number;
8
+ top_p?: number;
9
+ frequency_penalty?: number;
10
+ presence_penalty?: number;
11
+ stop?: Array<string>;
12
+ }
13
+
14
+ type CursorStyle =
15
+ | 'line'
16
+ | 'block'
17
+ | 'underline'
18
+ | 'line-thin'
19
+ | 'block-outline'
20
+ | 'underline-thin';
21
+
22
+ export interface Config {
23
+ llmKey?: string;
24
+ llmUrl?: string;
25
+ llmParams?: llmParams;
26
+ customCompletionFunction?: (code: string) => Promise<string>;
27
+ maxCodeLinesTollm?: number;
28
+ cursorStyleLoading?: CursorStyle;
29
+ cursorStyleNormal?: CursorStyle;
30
+ assistantMessage?: string;
31
+ }
32
+
33
+ export const defaultllmParams: llmParams = {
34
+ model: '',
35
+ temperature: 0,
36
+ max_tokens: 64,
37
+ top_p: 1.0,
38
+ frequency_penalty: 0.0,
39
+ presence_penalty: 0.0,
40
+ };
41
+
42
+ export const defaultConfig: Config = {
43
+ llmKey: '',
44
+ llmUrl: 'https://matthoffner-llama-cpp-fast-api.hf.space',
45
+ llmParams: defaultllmParams,
46
+ cursorStyleLoading: 'underline',
47
+ cursorStyleNormal: 'line',
48
+ assistantMessage: '',
49
+ };
50
+
51
+ function minimizeWhitespace(code:string) {
52
+ return code
53
+ .split('\n')
54
+ .map((line:string) => line.trim())
55
+ .join('\n');
56
+ }
57
+
58
+ async function fetchCompletionFromllm(
59
+ code: string,
60
+ config: Config,
61
+ controller: AbortController,
62
+ handleInsertion: (text: string) => void
63
+ ): Promise<void> {
64
+ const handleMessage = (message: string) => {
65
+ handleInsertion(message);
66
+ };
67
+
68
+ let text = ''
69
+
70
+ return new Promise(async (resolve, reject) => {
71
+ await fetchSSE(`${config.llmUrl}/v1/chat/completions`, {
72
+ method: 'POST',
73
+ headers: {
74
+ 'Content-Type': 'application/json',
75
+ authorization: `Bearer ${config.llmKey}`,
76
+ },
77
+ body: JSON.stringify({
78
+ stream: true,
79
+ messages: [
80
+ {role: "assistant", content: "You are a helpful assistant, complete the following code"},
81
+ {role: "user", content: config.assistantMessage + '\n' + minimizeWhitespace(code)},
82
+ ],
83
+ ...config.llmParams,
84
+ }),
85
+ signal: controller.signal,
86
+ onMessage: (data) => {
87
+ let lastResponse;
88
+ if (data === "[DONE]") {
89
+ text = text.trim();
90
+ return resolve();
91
+ }
92
+ try {
93
+ const response = JSON.parse(data);
94
+ if ((lastResponse = response == null ? void 0 : response.choices) == null ? void 0 : lastResponse.length) {
95
+ text += response.choices[0].delta.content || '';
96
+ handleMessage == null ? void 0 : handleMessage(text);
97
+ }
98
+ } catch (err) {
99
+ console.warn("llm stream SEE event unexpected error", err);
100
+ return reject(err);
101
+ }
102
+ },
103
+ onError: (error: any) => {
104
+ console.error(error);
105
+ }
106
+ });
107
+ })
108
+ }
109
+
110
+ const handleCompletion = async (
111
+ editor: monaco.editor.IStandaloneCodeEditor,
112
+ config: Config,
113
+ controller: AbortController,
114
+ cursorStyleLoading: () => void,
115
+ cursorStyleNormal: () => void
116
+ ) => {
117
+ const currentPosition = editor.getPosition();
118
+ if (!currentPosition) {
119
+ return;
120
+ }
121
+ const currentLineNumber = currentPosition.lineNumber;
122
+ const startLineNumber = !config.maxCodeLinesTollm
123
+ ? 1
124
+ : Math.max(1, currentLineNumber - config.maxCodeLinesTollm);
125
+ const endLineNumber = currentLineNumber;
126
+ const code = editor
127
+ .getModel()!
128
+ .getLinesContent()
129
+ .slice(startLineNumber - 1, endLineNumber)
130
+ .join('\n');
131
+
132
+ cursorStyleLoading();
133
+
134
+
135
+ let lastText = ''
136
+ const handleInsertion = (text: string) => {
137
+ const position = editor.getPosition();
138
+ if (!position) {
139
+ return;
140
+ }
141
+ const offset = editor.getModel()?.getOffsetAt(position);
142
+ if (!offset) {
143
+ return;
144
+ }
145
+
146
+ const edits = [
147
+ {
148
+ range: {
149
+ startLineNumber: position.lineNumber,
150
+ startColumn: position.column,
151
+ endLineNumber: position.lineNumber,
152
+ endColumn: position.column,
153
+ },
154
+ text: text.slice(lastText.length),
155
+ },
156
+ ];
157
+
158
+ lastText = text
159
+ editor.executeEdits('', edits);
160
+ };
161
+
162
+
163
+ try {
164
+ let newCode = '';
165
+ if (config.customCompletionFunction) {
166
+ newCode = await config.customCompletionFunction(code);
167
+ handleInsertion(newCode);
168
+ } else {
169
+ await fetchCompletionFromllm(code, config, controller, handleInsertion);
170
+ }
171
+ cursorStyleNormal();
172
+ } catch (error) {
173
+ cursorStyleNormal();
174
+ console.error('MonacoEditorCopilot error:', error);
175
+ }
176
+ };
177
+
178
+ const MonacoEditorCopilot = (
179
+ editor: monaco.editor.IStandaloneCodeEditor,
180
+ config: Config
181
+ ) => {
182
+ const mergedConfig: Config = {
183
+ ...defaultConfig,
184
+ ...config,
185
+ llmParams: { ...defaultllmParams, ...config.llmParams },
186
+ };
187
+
188
+ const cursorStyleLoading = () => {
189
+ editor.updateOptions({ cursorStyle: mergedConfig.cursorStyleLoading });
190
+ };
191
+
192
+ const cursorStyleNormal = () => {
193
+ editor.updateOptions({ cursorStyle: mergedConfig.cursorStyleNormal });
194
+ };
195
+
196
+ cursorStyleNormal();
197
+
198
+ let controller: AbortController | null = null;
199
+
200
+ const cancel = () => {
201
+ if (controller) {
202
+ controller.abort();
203
+ }
204
+ cursorStyleNormal();
205
+ }
206
+
207
+ const keyDownHandler = editor.onKeyDown(cancel);
208
+ const mouseDownHandler = editor.onMouseDown(cancel);
209
+
210
+ let copilotAction: monaco.editor.IActionDescriptor | null = {
211
+ id: 'copilot-completion',
212
+ label: 'Trigger Copilot Completion',
213
+ keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyB],
214
+ contextMenuGroupId: 'navigation',
215
+ contextMenuOrder: 1.5,
216
+ run: async () => {
217
+ controller = new AbortController();
218
+ await handleCompletion(
219
+ editor,
220
+ mergedConfig,
221
+ controller,
222
+ cursorStyleLoading,
223
+ cursorStyleNormal
224
+ );
225
+ },
226
+ };
227
+
228
+ editor.addAction(copilotAction);
229
+
230
+ const dispose = () => {
231
+ keyDownHandler.dispose();
232
+ mouseDownHandler.dispose();
233
+ if (copilotAction) {
234
+ copilotAction.run = async () => {
235
+ console.warn('Copilot functionality has been disabled');
236
+ };
237
+ copilotAction = null;
238
+ }
239
+ };
240
+
241
+ return dispose;
242
+ };
243
+
244
+ export default MonacoEditorCopilot;