@notebook-intelligence/notebook-intelligence 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +151 -0
- package/lib/api.d.ts +47 -0
- package/lib/api.js +246 -0
- package/lib/chat-sidebar.d.ts +80 -0
- package/lib/chat-sidebar.js +1277 -0
- package/lib/handler.d.ts +8 -0
- package/lib/handler.js +36 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +1078 -0
- package/lib/markdown-renderer.d.ts +10 -0
- package/lib/markdown-renderer.js +60 -0
- package/lib/tokens.d.ts +93 -0
- package/lib/tokens.js +51 -0
- package/lib/utils.d.ts +17 -0
- package/lib/utils.js +163 -0
- package/package.json +219 -0
- package/schema/plugin.json +42 -0
- package/src/api.ts +333 -0
- package/src/chat-sidebar.tsx +2171 -0
- package/src/handler.ts +51 -0
- package/src/index.ts +1398 -0
- package/src/markdown-renderer.tsx +140 -0
- package/src/svg.d.ts +4 -0
- package/src/tokens.ts +114 -0
- package/src/utils.ts +204 -0
- package/style/base.css +590 -0
- package/style/icons/copilot-warning.svg +1 -0
- package/style/icons/copilot.svg +1 -0
- package/style/icons/copy.svg +1 -0
- package/style/icons/sparkles.svg +1 -0
- package/style/index.css +1 -0
- package/style/index.js +1 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,1398 @@
|
|
|
1
|
+
// Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
JupyterFrontEnd,
|
|
5
|
+
JupyterFrontEndPlugin,
|
|
6
|
+
JupyterLab
|
|
7
|
+
} from '@jupyterlab/application';
|
|
8
|
+
|
|
9
|
+
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
10
|
+
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
11
|
+
|
|
12
|
+
import { Dialog, ICommandPalette } from '@jupyterlab/apputils';
|
|
13
|
+
import { IMainMenu } from '@jupyterlab/mainmenu';
|
|
14
|
+
|
|
15
|
+
import { IEditorLanguageRegistry } from '@jupyterlab/codemirror';
|
|
16
|
+
|
|
17
|
+
import { CodeCell } from '@jupyterlab/cells';
|
|
18
|
+
import { ISharedNotebook } from '@jupyter/ydoc';
|
|
19
|
+
|
|
20
|
+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
CompletionHandler,
|
|
24
|
+
ICompletionProviderManager,
|
|
25
|
+
IInlineCompletionContext,
|
|
26
|
+
IInlineCompletionItem,
|
|
27
|
+
IInlineCompletionList,
|
|
28
|
+
IInlineCompletionProvider
|
|
29
|
+
} from '@jupyterlab/completer';
|
|
30
|
+
|
|
31
|
+
import { NotebookPanel } from '@jupyterlab/notebook';
|
|
32
|
+
import { CodeEditor } from '@jupyterlab/codeeditor';
|
|
33
|
+
import { FileEditorWidget } from '@jupyterlab/fileeditor';
|
|
34
|
+
|
|
35
|
+
import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
|
|
36
|
+
|
|
37
|
+
import { ContentsManager, KernelSpecManager } from '@jupyterlab/services';
|
|
38
|
+
|
|
39
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
40
|
+
|
|
41
|
+
import { Menu, Panel, Widget } from '@lumino/widgets';
|
|
42
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
43
|
+
import { IStatusBar } from '@jupyterlab/statusbar';
|
|
44
|
+
|
|
45
|
+
import {
|
|
46
|
+
ChatSidebar,
|
|
47
|
+
ConfigurationDialogBody,
|
|
48
|
+
GitHubCopilotLoginDialogBody,
|
|
49
|
+
GitHubCopilotStatusBarItem,
|
|
50
|
+
InlinePromptWidget,
|
|
51
|
+
RunChatCompletionType
|
|
52
|
+
} from './chat-sidebar';
|
|
53
|
+
import { NBIAPI, GitHubCopilotLoginStatus } from './api';
|
|
54
|
+
import {
|
|
55
|
+
BackendMessageType,
|
|
56
|
+
GITHUB_COPILOT_PROVIDER_ID,
|
|
57
|
+
IActiveDocumentInfo,
|
|
58
|
+
ICellContents,
|
|
59
|
+
INotebookIntelligence,
|
|
60
|
+
ITelemetryEmitter,
|
|
61
|
+
ITelemetryEvent,
|
|
62
|
+
ITelemetryListener,
|
|
63
|
+
RequestDataType,
|
|
64
|
+
TelemetryEventType
|
|
65
|
+
} from './tokens';
|
|
66
|
+
import sparklesSvgstr from '../style/icons/sparkles.svg';
|
|
67
|
+
import copilotSvgstr from '../style/icons/copilot.svg';
|
|
68
|
+
|
|
69
|
+
import {
|
|
70
|
+
applyCodeToSelectionInEditor,
|
|
71
|
+
cellOutputAsText,
|
|
72
|
+
compareSelections,
|
|
73
|
+
extractLLMGeneratedCode,
|
|
74
|
+
getSelectionInEditor,
|
|
75
|
+
getTokenCount,
|
|
76
|
+
getWholeNotebookContent,
|
|
77
|
+
isSelectionEmpty,
|
|
78
|
+
markdownToComment,
|
|
79
|
+
waitForDuration
|
|
80
|
+
} from './utils';
|
|
81
|
+
import { UUID } from '@lumino/coreutils';
|
|
82
|
+
|
|
83
|
+
namespace CommandIDs {
|
|
84
|
+
export const chatuserInput = 'notebook-intelligence:chat-user-input';
|
|
85
|
+
export const insertAtCursor = 'notebook-intelligence:insert-at-cursor';
|
|
86
|
+
export const addCodeAsNewCell = 'notebook-intelligence:add-code-as-new-cell';
|
|
87
|
+
export const createNewFile = 'notebook-intelligence:create-new-file';
|
|
88
|
+
export const createNewNotebookFromPython =
|
|
89
|
+
'notebook-intelligence:create-new-notebook-from-py';
|
|
90
|
+
export const addCodeCellToNotebook =
|
|
91
|
+
'notebook-intelligence:add-code-cell-to-notebook';
|
|
92
|
+
export const addMarkdownCellToNotebook =
|
|
93
|
+
'notebook-intelligence:add-markdown-cell-to-notebook';
|
|
94
|
+
export const editorGenerateCode =
|
|
95
|
+
'notebook-intelligence:editor-generate-code';
|
|
96
|
+
export const editorExplainThisCode =
|
|
97
|
+
'notebook-intelligence:editor-explain-this-code';
|
|
98
|
+
export const editorFixThisCode = 'notebook-intelligence:editor-fix-this-code';
|
|
99
|
+
export const editorExplainThisOutput =
|
|
100
|
+
'notebook-intelligence:editor-explain-this-output';
|
|
101
|
+
export const editorTroubleshootThisOutput =
|
|
102
|
+
'notebook-intelligence:editor-troubleshoot-this-output';
|
|
103
|
+
export const openGitHubCopilotLoginDialog =
|
|
104
|
+
'notebook-intelligence:open-github-copilot-login-dialog';
|
|
105
|
+
export const openConfigurationDialog =
|
|
106
|
+
'notebook-intelligence:open-configuration-dialog';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const DOCUMENT_WATCH_INTERVAL = 1000;
|
|
110
|
+
const MAX_TOKENS = 4096;
|
|
111
|
+
const githubCopilotIcon = new LabIcon({
|
|
112
|
+
name: 'notebook-intelligence:github-copilot-icon',
|
|
113
|
+
svgstr: copilotSvgstr
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const sparkleIcon = new LabIcon({
|
|
117
|
+
name: 'notebook-intelligence:sparkles-icon',
|
|
118
|
+
svgstr: sparklesSvgstr
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const emptyNotebookContent: any = {
|
|
122
|
+
cells: [],
|
|
123
|
+
metadata: {},
|
|
124
|
+
nbformat: 4,
|
|
125
|
+
nbformat_minor: 5
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const BACKEND_TELEMETRY_LISTENER_NAME = 'backend-telemetry-listener';
|
|
129
|
+
|
|
130
|
+
class ActiveDocumentWatcher {
|
|
131
|
+
static initialize(
|
|
132
|
+
app: JupyterLab,
|
|
133
|
+
languageRegistry: IEditorLanguageRegistry
|
|
134
|
+
) {
|
|
135
|
+
ActiveDocumentWatcher._languageRegistry = languageRegistry;
|
|
136
|
+
|
|
137
|
+
app.shell.currentChanged?.connect((_sender, args) => {
|
|
138
|
+
ActiveDocumentWatcher.watchDocument(args.newValue);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
ActiveDocumentWatcher.activeDocumentInfo.activeWidget =
|
|
142
|
+
app.shell.currentWidget;
|
|
143
|
+
ActiveDocumentWatcher.handleWatchDocument();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static watchDocument(widget: Widget) {
|
|
147
|
+
if (ActiveDocumentWatcher.activeDocumentInfo.activeWidget === widget) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
clearInterval(ActiveDocumentWatcher._watchTimer);
|
|
151
|
+
ActiveDocumentWatcher.activeDocumentInfo.activeWidget = widget;
|
|
152
|
+
|
|
153
|
+
ActiveDocumentWatcher._watchTimer = setInterval(() => {
|
|
154
|
+
ActiveDocumentWatcher.handleWatchDocument();
|
|
155
|
+
}, DOCUMENT_WATCH_INTERVAL);
|
|
156
|
+
|
|
157
|
+
ActiveDocumentWatcher.handleWatchDocument();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static handleWatchDocument() {
|
|
161
|
+
const activeDocumentInfo = ActiveDocumentWatcher.activeDocumentInfo;
|
|
162
|
+
const previousDocumentInfo = {
|
|
163
|
+
...activeDocumentInfo,
|
|
164
|
+
...{ activeWidget: null }
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const activeWidget = activeDocumentInfo.activeWidget;
|
|
168
|
+
if (activeWidget instanceof NotebookPanel) {
|
|
169
|
+
const np = activeWidget as NotebookPanel;
|
|
170
|
+
activeDocumentInfo.filename = np.sessionContext.name;
|
|
171
|
+
activeDocumentInfo.filePath = np.sessionContext.path;
|
|
172
|
+
activeDocumentInfo.language =
|
|
173
|
+
(np.model?.sharedModel?.metadata?.kernelspec?.language as string) ||
|
|
174
|
+
'python';
|
|
175
|
+
const { activeCellIndex, activeCell } = np.content;
|
|
176
|
+
activeDocumentInfo.activeCellIndex = activeCellIndex;
|
|
177
|
+
activeDocumentInfo.selection = activeCell?.editor?.getSelection();
|
|
178
|
+
} else if (activeWidget) {
|
|
179
|
+
const dw = activeWidget as DocumentWidget;
|
|
180
|
+
const contentsModel = dw.context?.contentsModel;
|
|
181
|
+
if (contentsModel?.format === 'text') {
|
|
182
|
+
const fileName = contentsModel.name;
|
|
183
|
+
const filePath = contentsModel.path;
|
|
184
|
+
const language =
|
|
185
|
+
ActiveDocumentWatcher._languageRegistry.findByMIME(
|
|
186
|
+
contentsModel.mimetype
|
|
187
|
+
) || ActiveDocumentWatcher._languageRegistry.findByFileName(fileName);
|
|
188
|
+
activeDocumentInfo.language = language?.name || 'unknown';
|
|
189
|
+
activeDocumentInfo.filename = fileName;
|
|
190
|
+
activeDocumentInfo.filePath = filePath;
|
|
191
|
+
if (activeWidget instanceof FileEditorWidget) {
|
|
192
|
+
const fe = activeWidget as FileEditorWidget;
|
|
193
|
+
activeDocumentInfo.selection = fe.content.editor?.getSelection();
|
|
194
|
+
} else {
|
|
195
|
+
activeDocumentInfo.selection = undefined;
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
activeDocumentInfo.filename = '';
|
|
199
|
+
activeDocumentInfo.filePath = '';
|
|
200
|
+
activeDocumentInfo.language = '';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (
|
|
205
|
+
ActiveDocumentWatcher.documentInfoChanged(
|
|
206
|
+
previousDocumentInfo,
|
|
207
|
+
activeDocumentInfo
|
|
208
|
+
)
|
|
209
|
+
) {
|
|
210
|
+
ActiveDocumentWatcher.fireActiveDocumentChangedEvent();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private static documentInfoChanged(
|
|
215
|
+
lhs: IActiveDocumentInfo,
|
|
216
|
+
rhs: IActiveDocumentInfo
|
|
217
|
+
): boolean {
|
|
218
|
+
if (!lhs || !rhs) {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
lhs.filename !== rhs.filename ||
|
|
224
|
+
lhs.filePath !== rhs.filePath ||
|
|
225
|
+
lhs.language !== rhs.language ||
|
|
226
|
+
lhs.activeCellIndex !== rhs.activeCellIndex ||
|
|
227
|
+
!compareSelections(lhs.selection, rhs.selection)
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
static getActiveSelectionContent(): string {
|
|
232
|
+
const activeDocumentInfo = ActiveDocumentWatcher.activeDocumentInfo;
|
|
233
|
+
const activeWidget = activeDocumentInfo.activeWidget;
|
|
234
|
+
|
|
235
|
+
if (activeWidget instanceof NotebookPanel) {
|
|
236
|
+
const np = activeWidget as NotebookPanel;
|
|
237
|
+
const editor = np.content.activeCell.editor;
|
|
238
|
+
if (isSelectionEmpty(editor.getSelection())) {
|
|
239
|
+
return getWholeNotebookContent(np);
|
|
240
|
+
} else {
|
|
241
|
+
return getSelectionInEditor(editor);
|
|
242
|
+
}
|
|
243
|
+
} else if (activeWidget instanceof FileEditorWidget) {
|
|
244
|
+
const fe = activeWidget as FileEditorWidget;
|
|
245
|
+
const editor = fe.content.editor;
|
|
246
|
+
if (isSelectionEmpty(editor.getSelection())) {
|
|
247
|
+
return editor.model.sharedModel.getSource();
|
|
248
|
+
} else {
|
|
249
|
+
return getSelectionInEditor(editor);
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
const dw = activeWidget as DocumentWidget;
|
|
253
|
+
return dw?.context?.model?.toString();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
static getCurrentCellContents(): ICellContents {
|
|
258
|
+
const activeDocumentInfo = ActiveDocumentWatcher.activeDocumentInfo;
|
|
259
|
+
const activeWidget = activeDocumentInfo.activeWidget;
|
|
260
|
+
|
|
261
|
+
if (activeWidget instanceof NotebookPanel) {
|
|
262
|
+
const np = activeWidget as NotebookPanel;
|
|
263
|
+
const activeCell = np.content.activeCell;
|
|
264
|
+
const input = activeCell.model.sharedModel.source.trim();
|
|
265
|
+
let output = '';
|
|
266
|
+
if (activeCell instanceof CodeCell) {
|
|
267
|
+
output = cellOutputAsText(np.content.activeCell as CodeCell);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return { input, output };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
static fireActiveDocumentChangedEvent() {
|
|
277
|
+
document.dispatchEvent(
|
|
278
|
+
new CustomEvent('copilotSidebar:activeDocumentChanged', {
|
|
279
|
+
detail: {
|
|
280
|
+
activeDocumentInfo: ActiveDocumentWatcher.activeDocumentInfo
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
static activeDocumentInfo: IActiveDocumentInfo = {
|
|
287
|
+
language: 'python',
|
|
288
|
+
filename: 'nb-doesnt-exist.ipynb',
|
|
289
|
+
filePath: 'nb-doesnt-exist.ipynb',
|
|
290
|
+
activeWidget: null,
|
|
291
|
+
activeCellIndex: -1,
|
|
292
|
+
selection: null
|
|
293
|
+
};
|
|
294
|
+
private static _watchTimer: any;
|
|
295
|
+
private static _languageRegistry: IEditorLanguageRegistry;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
class NBIInlineCompletionProvider
|
|
299
|
+
implements IInlineCompletionProvider<IInlineCompletionItem>
|
|
300
|
+
{
|
|
301
|
+
constructor(telemetryEmitter: TelemetryEmitter) {
|
|
302
|
+
this._telemetryEmitter = telemetryEmitter;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
get schema(): ISettingRegistry.IProperty {
|
|
306
|
+
return {
|
|
307
|
+
default: {
|
|
308
|
+
debouncerDelay: 200,
|
|
309
|
+
timeout: 15000
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
fetch(
|
|
315
|
+
request: CompletionHandler.IRequest,
|
|
316
|
+
context: IInlineCompletionContext
|
|
317
|
+
): Promise<IInlineCompletionList<IInlineCompletionItem>> {
|
|
318
|
+
let preContent = '';
|
|
319
|
+
let postContent = '';
|
|
320
|
+
const preCursor = request.text.substring(0, request.offset);
|
|
321
|
+
const postCursor = request.text.substring(request.offset);
|
|
322
|
+
let language = ActiveDocumentWatcher.activeDocumentInfo.language;
|
|
323
|
+
|
|
324
|
+
let editorType = 'file-editor';
|
|
325
|
+
|
|
326
|
+
if (context.widget instanceof NotebookPanel) {
|
|
327
|
+
editorType = 'notebook';
|
|
328
|
+
const activeCell = context.widget.content.activeCell;
|
|
329
|
+
if (activeCell.model.sharedModel.cell_type === 'markdown') {
|
|
330
|
+
language = 'markdown';
|
|
331
|
+
}
|
|
332
|
+
let activeCellReached = false;
|
|
333
|
+
|
|
334
|
+
for (const cell of context.widget.content.widgets) {
|
|
335
|
+
const cellModel = cell.model.sharedModel;
|
|
336
|
+
if (cell === activeCell) {
|
|
337
|
+
activeCellReached = true;
|
|
338
|
+
} else if (!activeCellReached) {
|
|
339
|
+
if (cellModel.cell_type === 'code') {
|
|
340
|
+
preContent += cellModel.source + '\n';
|
|
341
|
+
} else if (cellModel.cell_type === 'markdown') {
|
|
342
|
+
preContent += markdownToComment(cellModel.source) + '\n';
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
if (cellModel.cell_type === 'code') {
|
|
346
|
+
postContent += cellModel.source + '\n';
|
|
347
|
+
} else if (cellModel.cell_type === 'markdown') {
|
|
348
|
+
postContent += markdownToComment(cellModel.source) + '\n';
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const nbiConfig = NBIAPI.config;
|
|
355
|
+
const inlineCompletionsEnabled =
|
|
356
|
+
nbiConfig.inlineCompletionModel.provider === GITHUB_COPILOT_PROVIDER_ID
|
|
357
|
+
? NBIAPI.getLoginStatus() === GitHubCopilotLoginStatus.LoggedIn
|
|
358
|
+
: nbiConfig.inlineCompletionModel.provider !== 'none';
|
|
359
|
+
|
|
360
|
+
this._telemetryEmitter.emitTelemetryEvent({
|
|
361
|
+
type: TelemetryEventType.InlineCompletionRequest,
|
|
362
|
+
data: {
|
|
363
|
+
inlineCompletionModel: {
|
|
364
|
+
provider: NBIAPI.config.inlineCompletionModel.provider,
|
|
365
|
+
model: NBIAPI.config.inlineCompletionModel.model
|
|
366
|
+
},
|
|
367
|
+
editorType
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
return new Promise((resolve, reject) => {
|
|
372
|
+
const items: IInlineCompletionItem[] = [];
|
|
373
|
+
|
|
374
|
+
if (!inlineCompletionsEnabled) {
|
|
375
|
+
resolve({ items });
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (this._lastRequestInfo) {
|
|
380
|
+
NBIAPI.sendWebSocketMessage(
|
|
381
|
+
this._lastRequestInfo.messageId,
|
|
382
|
+
RequestDataType.CancelInlineCompletionRequest,
|
|
383
|
+
{ chatId: this._lastRequestInfo.chatId }
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const messageId = UUID.uuid4();
|
|
388
|
+
const chatId = UUID.uuid4();
|
|
389
|
+
this._lastRequestInfo = { chatId, messageId, requestTime: new Date() };
|
|
390
|
+
|
|
391
|
+
NBIAPI.inlineCompletionsRequest(
|
|
392
|
+
chatId,
|
|
393
|
+
messageId,
|
|
394
|
+
preContent + preCursor,
|
|
395
|
+
postCursor + postContent,
|
|
396
|
+
language,
|
|
397
|
+
ActiveDocumentWatcher.activeDocumentInfo.filename,
|
|
398
|
+
{
|
|
399
|
+
emit: (response: any) => {
|
|
400
|
+
if (
|
|
401
|
+
response.type === BackendMessageType.StreamMessage &&
|
|
402
|
+
response.id === this._lastRequestInfo.messageId
|
|
403
|
+
) {
|
|
404
|
+
items.push({
|
|
405
|
+
insertText: response.data.completions
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const timeElapsed =
|
|
409
|
+
(new Date().getTime() -
|
|
410
|
+
this._lastRequestInfo.requestTime.getTime()) /
|
|
411
|
+
1000;
|
|
412
|
+
this._telemetryEmitter.emitTelemetryEvent({
|
|
413
|
+
type: TelemetryEventType.InlineCompletionResponse,
|
|
414
|
+
data: {
|
|
415
|
+
inlineCompletionModel: {
|
|
416
|
+
provider: NBIAPI.config.inlineCompletionModel.provider,
|
|
417
|
+
model: NBIAPI.config.inlineCompletionModel.model
|
|
418
|
+
},
|
|
419
|
+
timeElapsed
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
resolve({ items });
|
|
424
|
+
} else {
|
|
425
|
+
reject();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
get name(): string {
|
|
434
|
+
return 'Notebook Intelligence';
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
get identifier(): string {
|
|
438
|
+
return '@notebook-intelligence/notebook-intelligence';
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
get icon(): LabIcon.ILabIcon {
|
|
442
|
+
return NBIAPI.config.usingGitHubCopilotModel
|
|
443
|
+
? githubCopilotIcon
|
|
444
|
+
: sparkleIcon;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private _lastRequestInfo: {
|
|
448
|
+
chatId: string;
|
|
449
|
+
messageId: string;
|
|
450
|
+
requestTime: Date;
|
|
451
|
+
} = null;
|
|
452
|
+
private _telemetryEmitter: TelemetryEmitter;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
class TelemetryEmitter implements ITelemetryEmitter {
|
|
456
|
+
registerTelemetryListener(listener: ITelemetryListener) {
|
|
457
|
+
const listenerName = listener.name;
|
|
458
|
+
|
|
459
|
+
if (listenerName !== BACKEND_TELEMETRY_LISTENER_NAME) {
|
|
460
|
+
console.warn(
|
|
461
|
+
`Notebook Intelligence telemetry listener '${listenerName}' registered. Make sure it is from a trusted source.`
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
let listenerAlreadyExists = false;
|
|
466
|
+
this._listeners.forEach(existingListener => {
|
|
467
|
+
if (existingListener.name === listenerName) {
|
|
468
|
+
listenerAlreadyExists = true;
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
if (listenerAlreadyExists) {
|
|
473
|
+
console.error(
|
|
474
|
+
`Notebook Intelligence telemetry listener '${listenerName}' already exists!`
|
|
475
|
+
);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
this._listeners.add(listener);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
unregisterTelemetryListener(listener: ITelemetryListener) {
|
|
483
|
+
this._listeners.delete(listener);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
emitTelemetryEvent(event: ITelemetryEvent) {
|
|
487
|
+
this._listeners.forEach(listener => {
|
|
488
|
+
listener.onTelemetryEvent(event);
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
private _listeners: Set<ITelemetryListener> = new Set<ITelemetryListener>();
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Initialization data for the @notebook-intelligence/notebook-intelligence extension.
|
|
497
|
+
*/
|
|
498
|
+
const plugin: JupyterFrontEndPlugin<INotebookIntelligence> = {
|
|
499
|
+
id: '@notebook-intelligence/notebook-intelligence:plugin',
|
|
500
|
+
description: 'Notebook Intelligence',
|
|
501
|
+
autoStart: true,
|
|
502
|
+
requires: [
|
|
503
|
+
ICompletionProviderManager,
|
|
504
|
+
IDocumentManager,
|
|
505
|
+
IDefaultFileBrowser,
|
|
506
|
+
IEditorLanguageRegistry,
|
|
507
|
+
ICommandPalette,
|
|
508
|
+
IMainMenu
|
|
509
|
+
],
|
|
510
|
+
optional: [ISettingRegistry, IStatusBar],
|
|
511
|
+
provides: INotebookIntelligence,
|
|
512
|
+
activate: async (
|
|
513
|
+
app: JupyterFrontEnd,
|
|
514
|
+
completionManager: ICompletionProviderManager,
|
|
515
|
+
docManager: IDocumentManager,
|
|
516
|
+
defaultBrowser: IDefaultFileBrowser,
|
|
517
|
+
languageRegistry: IEditorLanguageRegistry,
|
|
518
|
+
palette: ICommandPalette,
|
|
519
|
+
mainMenu: IMainMenu,
|
|
520
|
+
settingRegistry: ISettingRegistry | null,
|
|
521
|
+
statusBar: IStatusBar | null
|
|
522
|
+
) => {
|
|
523
|
+
console.log(
|
|
524
|
+
'JupyterLab extension @notebook-intelligence/notebook-intelligence is activated!'
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
const telemetryEmitter = new TelemetryEmitter();
|
|
528
|
+
|
|
529
|
+
telemetryEmitter.registerTelemetryListener({
|
|
530
|
+
name: BACKEND_TELEMETRY_LISTENER_NAME,
|
|
531
|
+
onTelemetryEvent: event => {
|
|
532
|
+
NBIAPI.emitTelemetryEvent(event);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
const extensionService: INotebookIntelligence = {
|
|
537
|
+
registerTelemetryListener: (listener: ITelemetryListener) => {
|
|
538
|
+
telemetryEmitter.registerTelemetryListener(listener);
|
|
539
|
+
},
|
|
540
|
+
unregisterTelemetryListener: (listener: ITelemetryListener) => {
|
|
541
|
+
telemetryEmitter.unregisterTelemetryListener(listener);
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
await NBIAPI.initialize();
|
|
546
|
+
|
|
547
|
+
let openPopover: InlinePromptWidget | null = null;
|
|
548
|
+
|
|
549
|
+
completionManager.registerInlineProvider(
|
|
550
|
+
new NBIInlineCompletionProvider(telemetryEmitter)
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
if (settingRegistry) {
|
|
554
|
+
settingRegistry
|
|
555
|
+
.load(plugin.id)
|
|
556
|
+
.then(settings => {
|
|
557
|
+
//
|
|
558
|
+
})
|
|
559
|
+
.catch(reason => {
|
|
560
|
+
console.error(
|
|
561
|
+
'Failed to load settings for @notebook-intelligence/notebook-intelligence.',
|
|
562
|
+
reason
|
|
563
|
+
);
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const waitForFileToBeActive = async (
|
|
568
|
+
filePath: string
|
|
569
|
+
): Promise<boolean> => {
|
|
570
|
+
const isNotebook = filePath.endsWith('.ipynb');
|
|
571
|
+
|
|
572
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
573
|
+
const checkIfActive = () => {
|
|
574
|
+
const activeFilePath =
|
|
575
|
+
ActiveDocumentWatcher.activeDocumentInfo.filePath;
|
|
576
|
+
const filePathToCheck = filePath;
|
|
577
|
+
const currentWidget = app.shell.currentWidget;
|
|
578
|
+
if (
|
|
579
|
+
activeFilePath === filePathToCheck &&
|
|
580
|
+
((isNotebook &&
|
|
581
|
+
currentWidget instanceof NotebookPanel &&
|
|
582
|
+
currentWidget.content.activeCell &&
|
|
583
|
+
currentWidget.content.activeCell.node.contains(
|
|
584
|
+
document.activeElement
|
|
585
|
+
)) ||
|
|
586
|
+
(!isNotebook &&
|
|
587
|
+
currentWidget instanceof FileEditorWidget &&
|
|
588
|
+
currentWidget.content.editor.hasFocus()))
|
|
589
|
+
) {
|
|
590
|
+
resolve(true);
|
|
591
|
+
} else {
|
|
592
|
+
setTimeout(checkIfActive, 200);
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
checkIfActive();
|
|
596
|
+
|
|
597
|
+
waitForDuration(10000).then(() => {
|
|
598
|
+
resolve(false);
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
const panel = new Panel();
|
|
604
|
+
panel.id = 'notebook-intelligence-tab';
|
|
605
|
+
panel.title.caption = 'Copilot Chat';
|
|
606
|
+
const sidebarIcon = new LabIcon({
|
|
607
|
+
name: 'ui-components:palette',
|
|
608
|
+
svgstr: sparklesSvgstr
|
|
609
|
+
});
|
|
610
|
+
panel.title.icon = sidebarIcon;
|
|
611
|
+
const sidebar = new ChatSidebar({
|
|
612
|
+
getActiveDocumentInfo: (): IActiveDocumentInfo => {
|
|
613
|
+
return ActiveDocumentWatcher.activeDocumentInfo;
|
|
614
|
+
},
|
|
615
|
+
getActiveSelectionContent: (): string => {
|
|
616
|
+
return ActiveDocumentWatcher.getActiveSelectionContent();
|
|
617
|
+
},
|
|
618
|
+
getCurrentCellContents: (): ICellContents => {
|
|
619
|
+
return ActiveDocumentWatcher.getCurrentCellContents();
|
|
620
|
+
},
|
|
621
|
+
openFile: (path: string) => {
|
|
622
|
+
docManager.openOrReveal(path);
|
|
623
|
+
},
|
|
624
|
+
getApp(): JupyterFrontEnd {
|
|
625
|
+
return app;
|
|
626
|
+
},
|
|
627
|
+
getTelemetryEmitter(): ITelemetryEmitter {
|
|
628
|
+
return telemetryEmitter;
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
panel.addWidget(sidebar);
|
|
632
|
+
app.shell.add(panel, 'left', { rank: 1000 });
|
|
633
|
+
app.shell.activateById(panel.id);
|
|
634
|
+
|
|
635
|
+
app.commands.addCommand(CommandIDs.chatuserInput, {
|
|
636
|
+
execute: args => {
|
|
637
|
+
NBIAPI.sendChatUserInput(args.id as string, args.data);
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
app.commands.addCommand(CommandIDs.insertAtCursor, {
|
|
642
|
+
execute: args => {
|
|
643
|
+
const currentWidget = app.shell.currentWidget;
|
|
644
|
+
if (currentWidget instanceof NotebookPanel) {
|
|
645
|
+
const activeCell = currentWidget.content.activeCell;
|
|
646
|
+
if (activeCell) {
|
|
647
|
+
applyCodeToSelectionInEditor(
|
|
648
|
+
activeCell.editor,
|
|
649
|
+
args.code as string
|
|
650
|
+
);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
} else if (currentWidget instanceof FileEditorWidget) {
|
|
654
|
+
applyCodeToSelectionInEditor(
|
|
655
|
+
currentWidget.content.editor,
|
|
656
|
+
args.code as string
|
|
657
|
+
);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
app.commands.execute('apputils:notify', {
|
|
662
|
+
message:
|
|
663
|
+
'Failed to insert at cursor. Open a notebook or file to insert the code.',
|
|
664
|
+
type: 'error',
|
|
665
|
+
options: { autoClose: true }
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
app.commands.addCommand(CommandIDs.addCodeAsNewCell, {
|
|
671
|
+
execute: args => {
|
|
672
|
+
const currentWidget = app.shell.currentWidget;
|
|
673
|
+
if (currentWidget instanceof NotebookPanel) {
|
|
674
|
+
let activeCellIndex = currentWidget.content.activeCellIndex;
|
|
675
|
+
activeCellIndex =
|
|
676
|
+
activeCellIndex === -1
|
|
677
|
+
? currentWidget.content.widgets.length
|
|
678
|
+
: activeCellIndex + 1;
|
|
679
|
+
|
|
680
|
+
currentWidget.model?.sharedModel.insertCell(activeCellIndex, {
|
|
681
|
+
cell_type: 'code',
|
|
682
|
+
metadata: { trusted: true },
|
|
683
|
+
source: args.code as string
|
|
684
|
+
});
|
|
685
|
+
currentWidget.content.activeCellIndex = activeCellIndex;
|
|
686
|
+
} else {
|
|
687
|
+
app.commands.execute('apputils:notify', {
|
|
688
|
+
message: 'Open a notebook to insert the code as new cell',
|
|
689
|
+
type: 'error',
|
|
690
|
+
options: { autoClose: true }
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
app.commands.addCommand(CommandIDs.createNewFile, {
|
|
697
|
+
execute: async args => {
|
|
698
|
+
const contents = new ContentsManager();
|
|
699
|
+
const newPyFile = await contents.newUntitled({
|
|
700
|
+
ext: '.py',
|
|
701
|
+
path: defaultBrowser?.model.path
|
|
702
|
+
});
|
|
703
|
+
contents.save(newPyFile.path, {
|
|
704
|
+
content: extractLLMGeneratedCode(args.code as string),
|
|
705
|
+
format: 'text',
|
|
706
|
+
type: 'file'
|
|
707
|
+
});
|
|
708
|
+
docManager.openOrReveal(newPyFile.path);
|
|
709
|
+
|
|
710
|
+
await waitForFileToBeActive(newPyFile.path);
|
|
711
|
+
|
|
712
|
+
return newPyFile;
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
app.commands.addCommand(CommandIDs.createNewNotebookFromPython, {
|
|
717
|
+
execute: async args => {
|
|
718
|
+
let pythonKernelSpec = null;
|
|
719
|
+
const contents = new ContentsManager();
|
|
720
|
+
const kernels = new KernelSpecManager();
|
|
721
|
+
await kernels.ready;
|
|
722
|
+
const kernelspecs = kernels.specs?.kernelspecs;
|
|
723
|
+
if (kernelspecs) {
|
|
724
|
+
for (const key in kernelspecs) {
|
|
725
|
+
const kernelspec = kernelspecs[key];
|
|
726
|
+
if (kernelspec?.language === 'python') {
|
|
727
|
+
pythonKernelSpec = kernelspec;
|
|
728
|
+
break;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const newNBFile = await contents.newUntitled({
|
|
734
|
+
ext: '.ipynb',
|
|
735
|
+
path: defaultBrowser?.model.path
|
|
736
|
+
});
|
|
737
|
+
const nbFileContent = structuredClone(emptyNotebookContent);
|
|
738
|
+
if (pythonKernelSpec) {
|
|
739
|
+
nbFileContent.metadata = {
|
|
740
|
+
kernelspec: {
|
|
741
|
+
language: 'python',
|
|
742
|
+
name: pythonKernelSpec.name,
|
|
743
|
+
display_name: pythonKernelSpec.display_name
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
if (args.code) {
|
|
749
|
+
nbFileContent.cells.push({
|
|
750
|
+
cell_type: 'code',
|
|
751
|
+
metadata: { trusted: true },
|
|
752
|
+
source: [args.code as string],
|
|
753
|
+
outputs: []
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
contents.save(newNBFile.path, {
|
|
758
|
+
content: nbFileContent,
|
|
759
|
+
format: 'json',
|
|
760
|
+
type: 'notebook'
|
|
761
|
+
});
|
|
762
|
+
docManager.openOrReveal(newNBFile.path);
|
|
763
|
+
|
|
764
|
+
await waitForFileToBeActive(newNBFile.path);
|
|
765
|
+
|
|
766
|
+
return newNBFile;
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
const isNewEmptyNotebook = (model: ISharedNotebook) => {
|
|
771
|
+
return (
|
|
772
|
+
model.cells.length === 1 &&
|
|
773
|
+
model.cells[0].cell_type === 'code' &&
|
|
774
|
+
model.cells[0].source === ''
|
|
775
|
+
);
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
const githubLoginRequired = () => {
|
|
779
|
+
return (
|
|
780
|
+
NBIAPI.config.usingGitHubCopilotModel &&
|
|
781
|
+
NBIAPI.getLoginStatus() === GitHubCopilotLoginStatus.NotLoggedIn
|
|
782
|
+
);
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
const isChatEnabled = (): boolean => {
|
|
786
|
+
return NBIAPI.config.chatModel.provider === GITHUB_COPILOT_PROVIDER_ID
|
|
787
|
+
? !githubLoginRequired()
|
|
788
|
+
: NBIAPI.config.chatModel.provider !== 'none';
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
const isActiveCellCodeCell = (): boolean => {
|
|
792
|
+
if (!(app.shell.currentWidget instanceof NotebookPanel)) {
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
const np = app.shell.currentWidget as NotebookPanel;
|
|
796
|
+
const activeCell = np.content.activeCell;
|
|
797
|
+
return activeCell instanceof CodeCell;
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
const isCurrentWidgetFileEditor = (): boolean => {
|
|
801
|
+
return app.shell.currentWidget instanceof FileEditorWidget;
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
const addCellToNotebook = (
|
|
805
|
+
filePath: string,
|
|
806
|
+
cellType: 'code' | 'markdown',
|
|
807
|
+
source: string
|
|
808
|
+
): boolean => {
|
|
809
|
+
const currentWidget = app.shell.currentWidget;
|
|
810
|
+
const notebookOpen =
|
|
811
|
+
currentWidget instanceof NotebookPanel &&
|
|
812
|
+
currentWidget.sessionContext.path === filePath &&
|
|
813
|
+
currentWidget.model;
|
|
814
|
+
if (!notebookOpen) {
|
|
815
|
+
app.commands.execute('apputils:notify', {
|
|
816
|
+
message: `Failed to access the notebook: ${filePath}`,
|
|
817
|
+
type: 'error',
|
|
818
|
+
options: { autoClose: true }
|
|
819
|
+
});
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const model = currentWidget.model.sharedModel;
|
|
824
|
+
|
|
825
|
+
const newCellIndex = isNewEmptyNotebook(model)
|
|
826
|
+
? 0
|
|
827
|
+
: model.cells.length - 1;
|
|
828
|
+
model.insertCell(newCellIndex, {
|
|
829
|
+
cell_type: cellType,
|
|
830
|
+
metadata: { trusted: true },
|
|
831
|
+
source
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
return true;
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
app.commands.addCommand(CommandIDs.addCodeCellToNotebook, {
|
|
838
|
+
execute: args => {
|
|
839
|
+
return addCellToNotebook(
|
|
840
|
+
args.path as string,
|
|
841
|
+
'code',
|
|
842
|
+
args.code as string
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
app.commands.addCommand(CommandIDs.addMarkdownCellToNotebook, {
|
|
848
|
+
execute: args => {
|
|
849
|
+
return addCellToNotebook(
|
|
850
|
+
args.path as string,
|
|
851
|
+
'markdown',
|
|
852
|
+
args.markdown as string
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
app.commands.addCommand(CommandIDs.openGitHubCopilotLoginDialog, {
|
|
858
|
+
execute: args => {
|
|
859
|
+
let dialog: Dialog<unknown> | null = null;
|
|
860
|
+
const dialogBody = new GitHubCopilotLoginDialogBody({
|
|
861
|
+
onLoggedIn: () => dialog?.dispose()
|
|
862
|
+
});
|
|
863
|
+
dialog = new Dialog({
|
|
864
|
+
title: 'GitHub Copilot Status',
|
|
865
|
+
hasClose: true,
|
|
866
|
+
body: dialogBody,
|
|
867
|
+
buttons: []
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
dialog.launch();
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
app.commands.addCommand(CommandIDs.openConfigurationDialog, {
|
|
875
|
+
label: 'Notebook Intelligence Settings',
|
|
876
|
+
execute: args => {
|
|
877
|
+
let dialog: Dialog<unknown> | null = null;
|
|
878
|
+
const dialogBody = new ConfigurationDialogBody({
|
|
879
|
+
onSave: () => {
|
|
880
|
+
dialog?.dispose();
|
|
881
|
+
NBIAPI.fetchCapabilities();
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
dialog = new Dialog({
|
|
885
|
+
title: 'Notebook Intelligence Settings',
|
|
886
|
+
hasClose: true,
|
|
887
|
+
body: dialogBody,
|
|
888
|
+
buttons: []
|
|
889
|
+
});
|
|
890
|
+
dialog.node.classList.add('config-dialog-container');
|
|
891
|
+
|
|
892
|
+
dialog.launch();
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
palette.addItem({
|
|
897
|
+
command: CommandIDs.openConfigurationDialog,
|
|
898
|
+
category: 'Notebook Intelligence'
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
mainMenu.settingsMenu.addGroup([
|
|
902
|
+
{
|
|
903
|
+
command: CommandIDs.openConfigurationDialog
|
|
904
|
+
}
|
|
905
|
+
]);
|
|
906
|
+
|
|
907
|
+
const getPrefixAndSuffixForActiveCell = (): {
|
|
908
|
+
prefix: string;
|
|
909
|
+
suffix: string;
|
|
910
|
+
} => {
|
|
911
|
+
let prefix = '';
|
|
912
|
+
let suffix = '';
|
|
913
|
+
const currentWidget = app.shell.currentWidget;
|
|
914
|
+
if (
|
|
915
|
+
!(
|
|
916
|
+
currentWidget instanceof NotebookPanel &&
|
|
917
|
+
currentWidget.content.activeCell
|
|
918
|
+
)
|
|
919
|
+
) {
|
|
920
|
+
return { prefix, suffix };
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const activeCellIndex = currentWidget.content.activeCellIndex;
|
|
924
|
+
const numCells = currentWidget.content.widgets.length;
|
|
925
|
+
const maxContext = 0.7 * MAX_TOKENS;
|
|
926
|
+
|
|
927
|
+
for (let d = 1; d < numCells; ++d) {
|
|
928
|
+
const above = activeCellIndex - d;
|
|
929
|
+
const below = activeCellIndex + d;
|
|
930
|
+
if (
|
|
931
|
+
(above < 0 && below >= numCells) ||
|
|
932
|
+
getTokenCount(`${prefix} ${suffix}`) >= maxContext
|
|
933
|
+
) {
|
|
934
|
+
break;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (above >= 0) {
|
|
938
|
+
const aboveCell = currentWidget.content.widgets[above];
|
|
939
|
+
const cellModel = aboveCell.model.sharedModel;
|
|
940
|
+
|
|
941
|
+
if (cellModel.cell_type === 'code') {
|
|
942
|
+
prefix = cellModel.source + '\n' + prefix;
|
|
943
|
+
} else if (cellModel.cell_type === 'markdown') {
|
|
944
|
+
prefix = markdownToComment(cellModel.source) + '\n' + prefix;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
if (below < numCells) {
|
|
949
|
+
const belowCell = currentWidget.content.widgets[below];
|
|
950
|
+
const cellModel = belowCell.model.sharedModel;
|
|
951
|
+
|
|
952
|
+
if (cellModel.cell_type === 'code') {
|
|
953
|
+
suffix += cellModel.source + '\n';
|
|
954
|
+
} else if (cellModel.cell_type === 'markdown') {
|
|
955
|
+
suffix += markdownToComment(cellModel.source) + '\n';
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
return { prefix, suffix };
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
const getPrefixAndSuffixForFileEditor = (): {
|
|
964
|
+
prefix: string;
|
|
965
|
+
suffix: string;
|
|
966
|
+
} => {
|
|
967
|
+
let prefix = '';
|
|
968
|
+
let suffix = '';
|
|
969
|
+
const currentWidget = app.shell.currentWidget;
|
|
970
|
+
if (!(currentWidget instanceof FileEditorWidget)) {
|
|
971
|
+
return { prefix, suffix };
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const fe = currentWidget as FileEditorWidget;
|
|
975
|
+
|
|
976
|
+
const cursor = fe.content.editor.getCursorPosition();
|
|
977
|
+
const offset = fe.content.editor.getOffsetAt(cursor);
|
|
978
|
+
const source = fe.content.editor.model.sharedModel.getSource();
|
|
979
|
+
prefix = source.substring(0, offset);
|
|
980
|
+
suffix = source.substring(offset);
|
|
981
|
+
|
|
982
|
+
return { prefix, suffix };
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
const generateCodeForCellOrFileEditor = () => {
|
|
986
|
+
const isCodeCell = isActiveCellCodeCell();
|
|
987
|
+
const currentWidget = app.shell.currentWidget;
|
|
988
|
+
let editor: CodeEditor.IEditor;
|
|
989
|
+
let codeInput: HTMLElement = null;
|
|
990
|
+
let scrollEl: HTMLElement = null;
|
|
991
|
+
if (isCodeCell) {
|
|
992
|
+
const np = currentWidget as NotebookPanel;
|
|
993
|
+
const activeCell = np.content.activeCell;
|
|
994
|
+
codeInput = activeCell.node.querySelector('.jp-InputArea-editor');
|
|
995
|
+
if (!codeInput) {
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
scrollEl = np.node.querySelector('.jp-WindowedPanel-outer');
|
|
999
|
+
editor = activeCell.editor;
|
|
1000
|
+
} else {
|
|
1001
|
+
const fe = currentWidget as FileEditorWidget;
|
|
1002
|
+
codeInput = fe.node;
|
|
1003
|
+
scrollEl = fe.node.querySelector('.cm-scroller');
|
|
1004
|
+
editor = fe.content.editor;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const getRectAtCursor = (): DOMRect => {
|
|
1008
|
+
const selection = editor.getSelection();
|
|
1009
|
+
const line = Math.min(selection.end.line + 1, editor.lineCount - 1);
|
|
1010
|
+
const coords = editor.getCoordinateForPosition({
|
|
1011
|
+
line,
|
|
1012
|
+
column: selection.end.column
|
|
1013
|
+
});
|
|
1014
|
+
const editorRect = codeInput.getBoundingClientRect();
|
|
1015
|
+
if (!coords) {
|
|
1016
|
+
return editorRect;
|
|
1017
|
+
}
|
|
1018
|
+
const yOffset = 30;
|
|
1019
|
+
const rect: DOMRect = new DOMRect(
|
|
1020
|
+
editorRect.left,
|
|
1021
|
+
coords.top - yOffset,
|
|
1022
|
+
editorRect.right - editorRect.left,
|
|
1023
|
+
coords.bottom - coords.top
|
|
1024
|
+
);
|
|
1025
|
+
return rect;
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
const rect = getRectAtCursor();
|
|
1029
|
+
|
|
1030
|
+
const updatePopoverPosition = () => {
|
|
1031
|
+
if (openPopover !== null) {
|
|
1032
|
+
const rect = getRectAtCursor();
|
|
1033
|
+
openPopover.updatePosition(rect);
|
|
1034
|
+
}
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1037
|
+
const inputResizeObserver = new ResizeObserver(updatePopoverPosition);
|
|
1038
|
+
|
|
1039
|
+
const addPositionListeners = () => {
|
|
1040
|
+
inputResizeObserver.observe(codeInput);
|
|
1041
|
+
if (scrollEl) {
|
|
1042
|
+
scrollEl.addEventListener('scroll', updatePopoverPosition);
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
const removePositionListeners = () => {
|
|
1047
|
+
inputResizeObserver.unobserve(codeInput);
|
|
1048
|
+
if (scrollEl) {
|
|
1049
|
+
scrollEl.removeEventListener('scroll', updatePopoverPosition);
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
const removePopover = () => {
|
|
1054
|
+
if (openPopover !== null) {
|
|
1055
|
+
removePositionListeners();
|
|
1056
|
+
openPopover = null;
|
|
1057
|
+
Widget.detach(inlinePrompt);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
if (isCodeCell) {
|
|
1061
|
+
codeInput?.classList.remove('generating');
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
let userPrompt = '';
|
|
1066
|
+
let existingCode = '';
|
|
1067
|
+
let generatedContent = '';
|
|
1068
|
+
|
|
1069
|
+
let prefix = '',
|
|
1070
|
+
suffix = '';
|
|
1071
|
+
if (isCodeCell) {
|
|
1072
|
+
const ps = getPrefixAndSuffixForActiveCell();
|
|
1073
|
+
prefix = ps.prefix;
|
|
1074
|
+
suffix = ps.suffix;
|
|
1075
|
+
} else {
|
|
1076
|
+
const ps = getPrefixAndSuffixForFileEditor();
|
|
1077
|
+
prefix = ps.prefix;
|
|
1078
|
+
suffix = ps.suffix;
|
|
1079
|
+
}
|
|
1080
|
+
const selection = editor.getSelection();
|
|
1081
|
+
|
|
1082
|
+
const startOffset = editor.getOffsetAt(selection.start);
|
|
1083
|
+
const endOffset = editor.getOffsetAt(selection.end);
|
|
1084
|
+
const source = editor.model.sharedModel.getSource();
|
|
1085
|
+
|
|
1086
|
+
if (isCodeCell) {
|
|
1087
|
+
prefix += '\n' + source.substring(0, startOffset);
|
|
1088
|
+
existingCode = source.substring(startOffset, endOffset);
|
|
1089
|
+
suffix = source.substring(endOffset) + '\n' + suffix;
|
|
1090
|
+
} else {
|
|
1091
|
+
existingCode = source.substring(startOffset, endOffset);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
const applyGeneratedCode = () => {
|
|
1095
|
+
generatedContent = extractLLMGeneratedCode(generatedContent);
|
|
1096
|
+
applyCodeToSelectionInEditor(editor, generatedContent);
|
|
1097
|
+
generatedContent = '';
|
|
1098
|
+
removePopover();
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
removePopover();
|
|
1102
|
+
|
|
1103
|
+
const inlinePrompt = new InlinePromptWidget(rect, {
|
|
1104
|
+
prompt: userPrompt,
|
|
1105
|
+
existingCode,
|
|
1106
|
+
prefix: prefix,
|
|
1107
|
+
suffix: suffix,
|
|
1108
|
+
onRequestSubmitted: (prompt: string) => {
|
|
1109
|
+
userPrompt = prompt;
|
|
1110
|
+
generatedContent = '';
|
|
1111
|
+
if (existingCode !== '') {
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
removePopover();
|
|
1115
|
+
if (isCodeCell) {
|
|
1116
|
+
codeInput?.classList.add('generating');
|
|
1117
|
+
}
|
|
1118
|
+
},
|
|
1119
|
+
onRequestCancelled: () => {
|
|
1120
|
+
removePopover();
|
|
1121
|
+
editor.focus();
|
|
1122
|
+
},
|
|
1123
|
+
onContentStream: (content: string) => {
|
|
1124
|
+
if (existingCode !== '') {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
generatedContent += content;
|
|
1128
|
+
},
|
|
1129
|
+
onContentStreamEnd: () => {
|
|
1130
|
+
if (existingCode !== '') {
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
applyGeneratedCode();
|
|
1134
|
+
editor.focus();
|
|
1135
|
+
},
|
|
1136
|
+
onUpdatedCodeChange: (content: string) => {
|
|
1137
|
+
generatedContent = content;
|
|
1138
|
+
},
|
|
1139
|
+
onUpdatedCodeAccepted: () => {
|
|
1140
|
+
applyGeneratedCode();
|
|
1141
|
+
editor.focus();
|
|
1142
|
+
},
|
|
1143
|
+
telemetryEmitter: telemetryEmitter
|
|
1144
|
+
});
|
|
1145
|
+
openPopover = inlinePrompt;
|
|
1146
|
+
addPositionListeners();
|
|
1147
|
+
Widget.attach(inlinePrompt, document.body);
|
|
1148
|
+
|
|
1149
|
+
telemetryEmitter.emitTelemetryEvent({
|
|
1150
|
+
type: TelemetryEventType.GenerateCodeRequest,
|
|
1151
|
+
data: {
|
|
1152
|
+
chatModel: {
|
|
1153
|
+
provider: NBIAPI.config.chatModel.provider,
|
|
1154
|
+
model: NBIAPI.config.chatModel.model
|
|
1155
|
+
},
|
|
1156
|
+
editorType: isCodeCell ? 'notebook' : 'file-editor'
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1161
|
+
const generateCellCodeCommand: CommandRegistry.ICommandOptions = {
|
|
1162
|
+
execute: args => {
|
|
1163
|
+
generateCodeForCellOrFileEditor();
|
|
1164
|
+
},
|
|
1165
|
+
label: 'Generate code',
|
|
1166
|
+
isEnabled: () =>
|
|
1167
|
+
isChatEnabled() &&
|
|
1168
|
+
(isActiveCellCodeCell() || isCurrentWidgetFileEditor())
|
|
1169
|
+
};
|
|
1170
|
+
app.commands.addCommand(
|
|
1171
|
+
CommandIDs.editorGenerateCode,
|
|
1172
|
+
generateCellCodeCommand
|
|
1173
|
+
);
|
|
1174
|
+
|
|
1175
|
+
const copilotMenuCommands = new CommandRegistry();
|
|
1176
|
+
copilotMenuCommands.addCommand(
|
|
1177
|
+
CommandIDs.editorGenerateCode,
|
|
1178
|
+
generateCellCodeCommand
|
|
1179
|
+
);
|
|
1180
|
+
copilotMenuCommands.addCommand(CommandIDs.editorExplainThisCode, {
|
|
1181
|
+
execute: () => {
|
|
1182
|
+
const np = app.shell.currentWidget as NotebookPanel;
|
|
1183
|
+
const activeCell = np.content.activeCell;
|
|
1184
|
+
const content = activeCell?.model.sharedModel.source || '';
|
|
1185
|
+
document.dispatchEvent(
|
|
1186
|
+
new CustomEvent('copilotSidebar:runPrompt', {
|
|
1187
|
+
detail: {
|
|
1188
|
+
type: RunChatCompletionType.ExplainThis,
|
|
1189
|
+
content,
|
|
1190
|
+
language: ActiveDocumentWatcher.activeDocumentInfo.language,
|
|
1191
|
+
filename: ActiveDocumentWatcher.activeDocumentInfo.filename
|
|
1192
|
+
}
|
|
1193
|
+
})
|
|
1194
|
+
);
|
|
1195
|
+
|
|
1196
|
+
app.commands.execute('tabsmenu:activate-by-id', { id: panel.id });
|
|
1197
|
+
|
|
1198
|
+
telemetryEmitter.emitTelemetryEvent({
|
|
1199
|
+
type: TelemetryEventType.ExplainThisRequest,
|
|
1200
|
+
data: {
|
|
1201
|
+
chatModel: {
|
|
1202
|
+
provider: NBIAPI.config.chatModel.provider,
|
|
1203
|
+
model: NBIAPI.config.chatModel.model
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
},
|
|
1208
|
+
label: 'Explain code',
|
|
1209
|
+
isEnabled: () => isChatEnabled() && isActiveCellCodeCell()
|
|
1210
|
+
});
|
|
1211
|
+
copilotMenuCommands.addCommand(CommandIDs.editorFixThisCode, {
|
|
1212
|
+
execute: () => {
|
|
1213
|
+
const np = app.shell.currentWidget as NotebookPanel;
|
|
1214
|
+
const activeCell = np.content.activeCell;
|
|
1215
|
+
const content = activeCell?.model.sharedModel.source || '';
|
|
1216
|
+
document.dispatchEvent(
|
|
1217
|
+
new CustomEvent('copilotSidebar:runPrompt', {
|
|
1218
|
+
detail: {
|
|
1219
|
+
type: RunChatCompletionType.FixThis,
|
|
1220
|
+
content,
|
|
1221
|
+
language: ActiveDocumentWatcher.activeDocumentInfo.language,
|
|
1222
|
+
filename: ActiveDocumentWatcher.activeDocumentInfo.filename
|
|
1223
|
+
}
|
|
1224
|
+
})
|
|
1225
|
+
);
|
|
1226
|
+
|
|
1227
|
+
app.commands.execute('tabsmenu:activate-by-id', { id: panel.id });
|
|
1228
|
+
|
|
1229
|
+
telemetryEmitter.emitTelemetryEvent({
|
|
1230
|
+
type: TelemetryEventType.FixThisCodeRequest,
|
|
1231
|
+
data: {
|
|
1232
|
+
chatModel: {
|
|
1233
|
+
provider: NBIAPI.config.chatModel.provider,
|
|
1234
|
+
model: NBIAPI.config.chatModel.model
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
},
|
|
1239
|
+
label: 'Fix code',
|
|
1240
|
+
isEnabled: () => isChatEnabled() && isActiveCellCodeCell()
|
|
1241
|
+
});
|
|
1242
|
+
copilotMenuCommands.addCommand(CommandIDs.editorExplainThisOutput, {
|
|
1243
|
+
execute: () => {
|
|
1244
|
+
const np = app.shell.currentWidget as NotebookPanel;
|
|
1245
|
+
const activeCell = np.content.activeCell;
|
|
1246
|
+
if (!(activeCell instanceof CodeCell)) {
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
const content = cellOutputAsText(activeCell as CodeCell);
|
|
1250
|
+
document.dispatchEvent(
|
|
1251
|
+
new CustomEvent('copilotSidebar:runPrompt', {
|
|
1252
|
+
detail: {
|
|
1253
|
+
type: RunChatCompletionType.ExplainThisOutput,
|
|
1254
|
+
content,
|
|
1255
|
+
language: ActiveDocumentWatcher.activeDocumentInfo.language,
|
|
1256
|
+
filename: ActiveDocumentWatcher.activeDocumentInfo.filename
|
|
1257
|
+
}
|
|
1258
|
+
})
|
|
1259
|
+
);
|
|
1260
|
+
|
|
1261
|
+
app.commands.execute('tabsmenu:activate-by-id', { id: panel.id });
|
|
1262
|
+
|
|
1263
|
+
telemetryEmitter.emitTelemetryEvent({
|
|
1264
|
+
type: TelemetryEventType.ExplainThisOutputRequest,
|
|
1265
|
+
data: {
|
|
1266
|
+
chatModel: {
|
|
1267
|
+
provider: NBIAPI.config.chatModel.provider,
|
|
1268
|
+
model: NBIAPI.config.chatModel.model
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
});
|
|
1272
|
+
},
|
|
1273
|
+
label: 'Explain output',
|
|
1274
|
+
isEnabled: () => {
|
|
1275
|
+
if (
|
|
1276
|
+
!(isChatEnabled() && app.shell.currentWidget instanceof NotebookPanel)
|
|
1277
|
+
) {
|
|
1278
|
+
return false;
|
|
1279
|
+
}
|
|
1280
|
+
const np = app.shell.currentWidget as NotebookPanel;
|
|
1281
|
+
const activeCell = np.content.activeCell;
|
|
1282
|
+
if (!(activeCell instanceof CodeCell)) {
|
|
1283
|
+
return false;
|
|
1284
|
+
}
|
|
1285
|
+
const outputs = activeCell.outputArea.model.toJSON();
|
|
1286
|
+
return Array.isArray(outputs) && outputs.length > 0;
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
copilotMenuCommands.addCommand(CommandIDs.editorTroubleshootThisOutput, {
|
|
1290
|
+
execute: () => {
|
|
1291
|
+
const np = app.shell.currentWidget as NotebookPanel;
|
|
1292
|
+
const activeCell = np.content.activeCell;
|
|
1293
|
+
if (!(activeCell instanceof CodeCell)) {
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
const content = cellOutputAsText(activeCell as CodeCell);
|
|
1297
|
+
document.dispatchEvent(
|
|
1298
|
+
new CustomEvent('copilotSidebar:runPrompt', {
|
|
1299
|
+
detail: {
|
|
1300
|
+
type: RunChatCompletionType.TroubleshootThisOutput,
|
|
1301
|
+
content,
|
|
1302
|
+
language: ActiveDocumentWatcher.activeDocumentInfo.language,
|
|
1303
|
+
filename: ActiveDocumentWatcher.activeDocumentInfo.filename
|
|
1304
|
+
}
|
|
1305
|
+
})
|
|
1306
|
+
);
|
|
1307
|
+
|
|
1308
|
+
app.commands.execute('tabsmenu:activate-by-id', { id: panel.id });
|
|
1309
|
+
|
|
1310
|
+
telemetryEmitter.emitTelemetryEvent({
|
|
1311
|
+
type: TelemetryEventType.TroubleshootThisOutputRequest,
|
|
1312
|
+
data: {
|
|
1313
|
+
chatModel: {
|
|
1314
|
+
provider: NBIAPI.config.chatModel.provider,
|
|
1315
|
+
model: NBIAPI.config.chatModel.model
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
});
|
|
1319
|
+
},
|
|
1320
|
+
label: 'Troubleshoot errors in output',
|
|
1321
|
+
isEnabled: () => {
|
|
1322
|
+
if (
|
|
1323
|
+
!(isChatEnabled() && app.shell.currentWidget instanceof NotebookPanel)
|
|
1324
|
+
) {
|
|
1325
|
+
return false;
|
|
1326
|
+
}
|
|
1327
|
+
const np = app.shell.currentWidget as NotebookPanel;
|
|
1328
|
+
const activeCell = np.content.activeCell;
|
|
1329
|
+
if (!(activeCell instanceof CodeCell)) {
|
|
1330
|
+
return false;
|
|
1331
|
+
}
|
|
1332
|
+
const outputs = activeCell.outputArea.model.toJSON();
|
|
1333
|
+
return (
|
|
1334
|
+
Array.isArray(outputs) &&
|
|
1335
|
+
outputs.length > 0 &&
|
|
1336
|
+
outputs.some(output => output.output_type === 'error')
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
const copilotContextMenu = new Menu({ commands: copilotMenuCommands });
|
|
1342
|
+
copilotContextMenu.id = 'notebook-intelligence:editor-context-menu';
|
|
1343
|
+
copilotContextMenu.title.label = 'Copilot';
|
|
1344
|
+
copilotContextMenu.title.icon = sidebarIcon;
|
|
1345
|
+
copilotContextMenu.addItem({ command: CommandIDs.editorGenerateCode });
|
|
1346
|
+
copilotContextMenu.addItem({ command: CommandIDs.editorExplainThisCode });
|
|
1347
|
+
copilotContextMenu.addItem({ command: CommandIDs.editorFixThisCode });
|
|
1348
|
+
copilotContextMenu.addItem({ command: CommandIDs.editorExplainThisOutput });
|
|
1349
|
+
copilotContextMenu.addItem({
|
|
1350
|
+
command: CommandIDs.editorTroubleshootThisOutput
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
app.contextMenu.addItem({
|
|
1354
|
+
type: 'submenu',
|
|
1355
|
+
submenu: copilotContextMenu,
|
|
1356
|
+
selector: '.jp-Editor',
|
|
1357
|
+
rank: 1
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1360
|
+
app.contextMenu.addItem({
|
|
1361
|
+
type: 'submenu',
|
|
1362
|
+
submenu: copilotContextMenu,
|
|
1363
|
+
selector: '.jp-OutputArea-child',
|
|
1364
|
+
rank: 1
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
if (statusBar) {
|
|
1368
|
+
const githubCopilotStatusBarItem = new GitHubCopilotStatusBarItem({
|
|
1369
|
+
getApp: () => app
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
statusBar.registerStatusItem(
|
|
1373
|
+
'notebook-intelligence:github-copilot-status',
|
|
1374
|
+
{
|
|
1375
|
+
item: githubCopilotStatusBarItem,
|
|
1376
|
+
align: 'right',
|
|
1377
|
+
rank: 100,
|
|
1378
|
+
isActive: () => NBIAPI.config.usingGitHubCopilotModel
|
|
1379
|
+
}
|
|
1380
|
+
);
|
|
1381
|
+
|
|
1382
|
+
NBIAPI.configChanged.connect(() => {
|
|
1383
|
+
if (NBIAPI.config.usingGitHubCopilotModel) {
|
|
1384
|
+
githubCopilotStatusBarItem.show();
|
|
1385
|
+
} else {
|
|
1386
|
+
githubCopilotStatusBarItem.hide();
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
const jlabApp = app as JupyterLab;
|
|
1392
|
+
ActiveDocumentWatcher.initialize(jlabApp, languageRegistry);
|
|
1393
|
+
|
|
1394
|
+
return extensionService;
|
|
1395
|
+
}
|
|
1396
|
+
};
|
|
1397
|
+
|
|
1398
|
+
export default plugin;
|