@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
|
@@ -0,0 +1,1277 @@
|
|
|
1
|
+
// Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
|
|
2
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { ReactWidget } from '@jupyterlab/apputils';
|
|
4
|
+
import { UUID } from '@lumino/coreutils';
|
|
5
|
+
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js';
|
|
6
|
+
import { NBIAPI, GitHubCopilotLoginStatus } from './api';
|
|
7
|
+
import { BackendMessageType, ContextType, GITHUB_COPILOT_PROVIDER_ID, RequestDataType, ResponseStreamDataType, TelemetryEventType } from './tokens';
|
|
8
|
+
import { MarkdownRenderer } from './markdown-renderer';
|
|
9
|
+
import copySvgstr from '../style/icons/copy.svg';
|
|
10
|
+
import copilotSvgstr from '../style/icons/copilot.svg';
|
|
11
|
+
import copilotWarningSvgstr from '../style/icons/copilot-warning.svg';
|
|
12
|
+
import { VscSend, VscStopCircle, VscEye, VscEyeClosed, VscTriangleRight, VscTriangleDown } from 'react-icons/vsc';
|
|
13
|
+
import { extractLLMGeneratedCode, isDarkTheme } from './utils';
|
|
14
|
+
const OPENAI_COMPATIBLE_CHAT_MODEL_ID = 'openai-compatible-chat-model';
|
|
15
|
+
const LITELLM_COMPATIBLE_CHAT_MODEL_ID = 'litellm-compatible-chat-model';
|
|
16
|
+
const OPENAI_COMPATIBLE_INLINE_COMPLETION_MODEL_ID = 'openai-compatible-inline-completion-model';
|
|
17
|
+
const LITELLM_COMPATIBLE_INLINE_COMPLETION_MODEL_ID = 'litellm-compatible-inline-completion-model';
|
|
18
|
+
export var RunChatCompletionType;
|
|
19
|
+
(function (RunChatCompletionType) {
|
|
20
|
+
RunChatCompletionType[RunChatCompletionType["Chat"] = 0] = "Chat";
|
|
21
|
+
RunChatCompletionType[RunChatCompletionType["ExplainThis"] = 1] = "ExplainThis";
|
|
22
|
+
RunChatCompletionType[RunChatCompletionType["FixThis"] = 2] = "FixThis";
|
|
23
|
+
RunChatCompletionType[RunChatCompletionType["GenerateCode"] = 3] = "GenerateCode";
|
|
24
|
+
RunChatCompletionType[RunChatCompletionType["ExplainThisOutput"] = 4] = "ExplainThisOutput";
|
|
25
|
+
RunChatCompletionType[RunChatCompletionType["TroubleshootThisOutput"] = 5] = "TroubleshootThisOutput";
|
|
26
|
+
})(RunChatCompletionType || (RunChatCompletionType = {}));
|
|
27
|
+
export class ChatSidebar extends ReactWidget {
|
|
28
|
+
constructor(options) {
|
|
29
|
+
super();
|
|
30
|
+
this._options = options;
|
|
31
|
+
this.node.style.height = '100%';
|
|
32
|
+
}
|
|
33
|
+
render() {
|
|
34
|
+
return (React.createElement(SidebarComponent, { getActiveDocumentInfo: this._options.getActiveDocumentInfo, getActiveSelectionContent: this._options.getActiveSelectionContent, getCurrentCellContents: this._options.getCurrentCellContents, openFile: this._options.openFile, getApp: this._options.getApp, getTelemetryEmitter: this._options.getTelemetryEmitter }));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class InlinePromptWidget extends ReactWidget {
|
|
38
|
+
constructor(rect, options) {
|
|
39
|
+
super();
|
|
40
|
+
this.node.classList.add('inline-prompt-widget');
|
|
41
|
+
this.node.style.top = `${rect.top + 32}px`;
|
|
42
|
+
this.node.style.left = `${rect.left}px`;
|
|
43
|
+
this.node.style.width = rect.width + 'px';
|
|
44
|
+
this.node.style.height = '48px';
|
|
45
|
+
this._options = options;
|
|
46
|
+
this.node.addEventListener('focusout', (event) => {
|
|
47
|
+
if (this.node.contains(event.relatedTarget)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
this._options.onRequestCancelled();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
updatePosition(rect) {
|
|
54
|
+
this.node.style.top = `${rect.top + 32}px`;
|
|
55
|
+
this.node.style.left = `${rect.left}px`;
|
|
56
|
+
this.node.style.width = rect.width + 'px';
|
|
57
|
+
}
|
|
58
|
+
_onResponse(response) {
|
|
59
|
+
var _a, _b, _c, _d, _e;
|
|
60
|
+
if (response.type === BackendMessageType.StreamMessage) {
|
|
61
|
+
const delta = (_b = (_a = response.data['choices']) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b['delta'];
|
|
62
|
+
if (!delta) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const responseMessage = (_e = (_d = (_c = response.data['choices']) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d['delta']) === null || _e === void 0 ? void 0 : _e['content'];
|
|
66
|
+
if (!responseMessage) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this._options.onContentStream(responseMessage);
|
|
70
|
+
}
|
|
71
|
+
else if (response.type === BackendMessageType.StreamEnd) {
|
|
72
|
+
this._options.onContentStreamEnd();
|
|
73
|
+
const timeElapsed = (new Date().getTime() - this._requestTime.getTime()) / 1000;
|
|
74
|
+
this._options.telemetryEmitter.emitTelemetryEvent({
|
|
75
|
+
type: TelemetryEventType.InlineChatResponse,
|
|
76
|
+
data: {
|
|
77
|
+
chatModel: {
|
|
78
|
+
provider: NBIAPI.config.chatModel.provider,
|
|
79
|
+
model: NBIAPI.config.chatModel.model
|
|
80
|
+
},
|
|
81
|
+
timeElapsed
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
_onRequestSubmitted(prompt) {
|
|
87
|
+
// code update
|
|
88
|
+
if (this._options.existingCode !== '') {
|
|
89
|
+
this.node.style.height = '300px';
|
|
90
|
+
}
|
|
91
|
+
// save the prompt in case of a rerender
|
|
92
|
+
this._options.prompt = prompt;
|
|
93
|
+
this._options.onRequestSubmitted(prompt);
|
|
94
|
+
this._requestTime = new Date();
|
|
95
|
+
this._options.telemetryEmitter.emitTelemetryEvent({
|
|
96
|
+
type: TelemetryEventType.InlineChatRequest,
|
|
97
|
+
data: {
|
|
98
|
+
chatModel: {
|
|
99
|
+
provider: NBIAPI.config.chatModel.provider,
|
|
100
|
+
model: NBIAPI.config.chatModel.model
|
|
101
|
+
},
|
|
102
|
+
prompt: prompt
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
render() {
|
|
107
|
+
return (React.createElement(InlinePopoverComponent, { prompt: this._options.prompt, existingCode: this._options.existingCode, onRequestSubmitted: this._onRequestSubmitted.bind(this), onRequestCancelled: this._options.onRequestCancelled, onResponseEmit: this._onResponse.bind(this), prefix: this._options.prefix, suffix: this._options.suffix, onUpdatedCodeChange: this._options.onUpdatedCodeChange, onUpdatedCodeAccepted: this._options.onUpdatedCodeAccepted }));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export class GitHubCopilotStatusBarItem extends ReactWidget {
|
|
111
|
+
constructor(options) {
|
|
112
|
+
super();
|
|
113
|
+
this._getApp = options.getApp;
|
|
114
|
+
}
|
|
115
|
+
render() {
|
|
116
|
+
return React.createElement(GitHubCopilotStatusComponent, { getApp: this._getApp });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export class GitHubCopilotLoginDialogBody extends ReactWidget {
|
|
120
|
+
constructor(options) {
|
|
121
|
+
super();
|
|
122
|
+
this._onLoggedIn = options.onLoggedIn;
|
|
123
|
+
}
|
|
124
|
+
render() {
|
|
125
|
+
return (React.createElement(GitHubCopilotLoginDialogBodyComponent, { onLoggedIn: () => this._onLoggedIn() }));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
export class ConfigurationDialogBody extends ReactWidget {
|
|
129
|
+
constructor(options) {
|
|
130
|
+
super();
|
|
131
|
+
this._onSave = options.onSave;
|
|
132
|
+
}
|
|
133
|
+
render() {
|
|
134
|
+
return React.createElement(ConfigurationDialogBodyComponent, { onSave: this._onSave });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const answeredForms = new Map();
|
|
138
|
+
function ChatResponseHTMLFrame(props) {
|
|
139
|
+
const iframSrc = useMemo(() => URL.createObjectURL(new Blob([props.source], { type: 'text/html' })), []);
|
|
140
|
+
return (React.createElement("div", { className: "chat-response-html-frame", key: `key-${props.index}` },
|
|
141
|
+
React.createElement("iframe", { className: "chat-response-html-frame-iframe", height: props.height, sandbox: "allow-scripts", src: iframSrc })));
|
|
142
|
+
}
|
|
143
|
+
function ChatResponse(props) {
|
|
144
|
+
var _a, _b, _c;
|
|
145
|
+
const [renderCount, setRenderCount] = useState(0);
|
|
146
|
+
const msg = props.message;
|
|
147
|
+
const timestamp = msg.date.toLocaleTimeString('en-US', { hour12: false });
|
|
148
|
+
const openNotebook = (event) => {
|
|
149
|
+
const notebookPath = event.target.dataset['ref'];
|
|
150
|
+
props.openFile(notebookPath);
|
|
151
|
+
};
|
|
152
|
+
const markFormConfirmed = (messageId) => {
|
|
153
|
+
answeredForms.set(messageId, 'confirmed');
|
|
154
|
+
};
|
|
155
|
+
const markFormCanceled = (messageId) => {
|
|
156
|
+
answeredForms.set(messageId, 'canceled');
|
|
157
|
+
};
|
|
158
|
+
const runCommand = (commandId, args) => {
|
|
159
|
+
props.getApp().commands.execute(commandId, args);
|
|
160
|
+
};
|
|
161
|
+
// group messages by type
|
|
162
|
+
const groupedContents = [];
|
|
163
|
+
let lastItemType;
|
|
164
|
+
const extractReasoningContent = (item) => {
|
|
165
|
+
let currentContent = item.content;
|
|
166
|
+
if (typeof currentContent !== 'string') {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
let reasoningContent = '';
|
|
170
|
+
let reasoningStartTime = new Date();
|
|
171
|
+
const reasoningEndTime = new Date();
|
|
172
|
+
const startPos = currentContent.indexOf('<think>');
|
|
173
|
+
const hasStart = startPos >= 0;
|
|
174
|
+
reasoningStartTime = new Date(item.created);
|
|
175
|
+
if (hasStart) {
|
|
176
|
+
currentContent = currentContent.substring(startPos + 7);
|
|
177
|
+
}
|
|
178
|
+
const endPos = currentContent.indexOf('</think>');
|
|
179
|
+
const hasEnd = endPos >= 0;
|
|
180
|
+
if (hasEnd) {
|
|
181
|
+
reasoningContent += currentContent.substring(0, endPos);
|
|
182
|
+
currentContent = currentContent.substring(endPos + 8);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
if (hasStart) {
|
|
186
|
+
reasoningContent += currentContent;
|
|
187
|
+
currentContent = '';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
item.content = currentContent;
|
|
191
|
+
item.reasoningContent = reasoningContent;
|
|
192
|
+
item.reasoningFinished = hasEnd;
|
|
193
|
+
item.reasoningTime =
|
|
194
|
+
(reasoningEndTime.getTime() - reasoningStartTime.getTime()) / 1000;
|
|
195
|
+
return hasStart && !hasEnd; // is thinking
|
|
196
|
+
};
|
|
197
|
+
for (let i = 0; i < msg.contents.length; i++) {
|
|
198
|
+
const item = msg.contents[i];
|
|
199
|
+
if (item.type === lastItemType &&
|
|
200
|
+
lastItemType === ResponseStreamDataType.MarkdownPart) {
|
|
201
|
+
const lastItem = groupedContents[groupedContents.length - 1];
|
|
202
|
+
lastItem.content += item.content;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
groupedContents.push(structuredClone(item));
|
|
206
|
+
lastItemType = item.type;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const [thinkingInProgress, setThinkingInProgress] = useState(false);
|
|
210
|
+
for (const item of groupedContents) {
|
|
211
|
+
const isThinking = extractReasoningContent(item);
|
|
212
|
+
if (isThinking && !thinkingInProgress) {
|
|
213
|
+
setThinkingInProgress(true);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
let intervalId = undefined;
|
|
218
|
+
if (thinkingInProgress) {
|
|
219
|
+
intervalId = setInterval(() => {
|
|
220
|
+
setRenderCount(prev => prev + 1);
|
|
221
|
+
setThinkingInProgress(false);
|
|
222
|
+
}, 1000);
|
|
223
|
+
}
|
|
224
|
+
return () => clearInterval(intervalId);
|
|
225
|
+
}, [thinkingInProgress]);
|
|
226
|
+
const onExpandCollapseClick = (event) => {
|
|
227
|
+
const parent = event.currentTarget.parentElement;
|
|
228
|
+
if (parent.classList.contains('expanded')) {
|
|
229
|
+
parent.classList.remove('expanded');
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
parent.classList.add('expanded');
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
return (React.createElement("div", { className: `chat-message chat-message-${msg.from}`, "data-render-count": renderCount },
|
|
236
|
+
React.createElement("div", { className: "chat-message-header" },
|
|
237
|
+
React.createElement("div", { className: "chat-message-from" },
|
|
238
|
+
((_a = msg.participant) === null || _a === void 0 ? void 0 : _a.iconPath) && (React.createElement("div", { className: `chat-message-from-icon ${((_b = msg.participant) === null || _b === void 0 ? void 0 : _b.id) === 'default' ? 'chat-message-from-icon-default' : ''} ${isDarkTheme() ? 'dark' : ''}` },
|
|
239
|
+
React.createElement("img", { src: msg.participant.iconPath }))),
|
|
240
|
+
React.createElement("div", { className: "chat-message-from-title" }, msg.from === 'user' ? 'User' : ((_c = msg.participant) === null || _c === void 0 ? void 0 : _c.name) || 'Copilot'),
|
|
241
|
+
React.createElement("div", { className: "chat-message-from-progress", style: { display: `${props.showGenerating ? 'visible' : 'none'}` } },
|
|
242
|
+
React.createElement("div", { className: "loading-ellipsis" }, "Generating"))),
|
|
243
|
+
React.createElement("div", { className: "chat-message-timestamp" }, timestamp)),
|
|
244
|
+
React.createElement("div", { className: "chat-message-content" },
|
|
245
|
+
groupedContents.map((item, index) => {
|
|
246
|
+
switch (item.type) {
|
|
247
|
+
case ResponseStreamDataType.Markdown:
|
|
248
|
+
case ResponseStreamDataType.MarkdownPart:
|
|
249
|
+
return (React.createElement(React.Fragment, null,
|
|
250
|
+
item.reasoningContent && (React.createElement("div", { className: "chat-reasoning-content" },
|
|
251
|
+
React.createElement("div", { className: "chat-reasoning-content-title", onClick: (event) => onExpandCollapseClick(event) },
|
|
252
|
+
React.createElement(VscTriangleRight, { className: "collapsed-icon" }),
|
|
253
|
+
React.createElement(VscTriangleDown, { className: "expanded-icon" }),
|
|
254
|
+
' ',
|
|
255
|
+
item.reasoningFinished
|
|
256
|
+
? 'Thought'
|
|
257
|
+
: `Thinking (${Math.floor(item.reasoningTime)} s)`),
|
|
258
|
+
React.createElement("div", { className: "chat-reasoning-content-text" },
|
|
259
|
+
React.createElement(MarkdownRenderer, { key: `key-${index}`, getApp: props.getApp, getActiveDocumentInfo: props.getActiveDocumentInfo }, item.reasoningContent)))),
|
|
260
|
+
React.createElement(MarkdownRenderer, { key: `key-${index}`, getApp: props.getApp, getActiveDocumentInfo: props.getActiveDocumentInfo }, item.content)));
|
|
261
|
+
case ResponseStreamDataType.HTMLFrame:
|
|
262
|
+
return (React.createElement(ChatResponseHTMLFrame, { index: index, source: item.content.source, height: item.content.height }));
|
|
263
|
+
case ResponseStreamDataType.Button:
|
|
264
|
+
return (React.createElement("div", { className: "chat-response-button" },
|
|
265
|
+
React.createElement("button", { key: `key-${index}`, className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: () => runCommand(item.content.commandId, item.content.args) },
|
|
266
|
+
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, item.content.title))));
|
|
267
|
+
case ResponseStreamDataType.Anchor:
|
|
268
|
+
return (React.createElement("div", { className: "chat-response-anchor" },
|
|
269
|
+
React.createElement("a", { key: `key-${index}`, href: item.content.uri, target: "_blank" }, item.content.title)));
|
|
270
|
+
case ResponseStreamDataType.Progress:
|
|
271
|
+
// show only if no more message available
|
|
272
|
+
return index === groupedContents.length - 1 ? (React.createElement("div", { key: `key-${index}` },
|
|
273
|
+
"\u2713 ",
|
|
274
|
+
item.content)) : null;
|
|
275
|
+
case ResponseStreamDataType.Confirmation:
|
|
276
|
+
return answeredForms.get(item.id) ===
|
|
277
|
+
'confirmed' ? null : answeredForms.get(item.id) ===
|
|
278
|
+
'canceled' ? (React.createElement("div", null, "\u2716 Canceled")) : (React.createElement("div", { className: "chat-confirmation-form", key: `key-${index}` },
|
|
279
|
+
item.content.title ? (React.createElement("div", null,
|
|
280
|
+
React.createElement("b", null, item.content.title))) : null,
|
|
281
|
+
item.content.message ? (React.createElement("div", null, item.content.message)) : null,
|
|
282
|
+
React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: () => {
|
|
283
|
+
markFormConfirmed(item.id);
|
|
284
|
+
runCommand('notebook-intelligence:chat-user-input', item.content.confirmArgs);
|
|
285
|
+
} },
|
|
286
|
+
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, item.content.confirmLabel)),
|
|
287
|
+
React.createElement("button", { className: "jp-Dialog-button jp-mod-reject jp-mod-styled", onClick: () => {
|
|
288
|
+
markFormCanceled(item.id);
|
|
289
|
+
runCommand('notebook-intelligence:chat-user-input', item.content.cancelArgs);
|
|
290
|
+
} },
|
|
291
|
+
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, item.content.cancelLabel))));
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
}),
|
|
295
|
+
msg.notebookLink && (React.createElement("a", { className: "copilot-generated-notebook-link", "data-ref": msg.notebookLink, onClick: openNotebook }, "open notebook")))));
|
|
296
|
+
}
|
|
297
|
+
async function submitCompletionRequest(request, responseEmitter) {
|
|
298
|
+
switch (request.type) {
|
|
299
|
+
case RunChatCompletionType.Chat:
|
|
300
|
+
return NBIAPI.chatRequest(request.messageId, request.chatId, request.content, request.language || 'python', request.filename || 'Untitled.ipynb', request.additionalContext || [], responseEmitter);
|
|
301
|
+
case RunChatCompletionType.ExplainThis:
|
|
302
|
+
case RunChatCompletionType.FixThis:
|
|
303
|
+
case RunChatCompletionType.ExplainThisOutput:
|
|
304
|
+
case RunChatCompletionType.TroubleshootThisOutput: {
|
|
305
|
+
return NBIAPI.chatRequest(request.messageId, request.chatId, request.content, request.language || 'python', request.filename || 'Untitled.ipynb', [], responseEmitter);
|
|
306
|
+
}
|
|
307
|
+
case RunChatCompletionType.GenerateCode:
|
|
308
|
+
return NBIAPI.generateCode(request.chatId, request.content, request.prefix || '', request.suffix || '', request.existingCode || '', request.language || 'python', request.filename || 'Untitled.ipynb', responseEmitter);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function SidebarComponent(props) {
|
|
312
|
+
const [chatMessages, setChatMessages] = useState([]);
|
|
313
|
+
const [prompt, setPrompt] = useState('');
|
|
314
|
+
const [draftPrompt, setDraftPrompt] = useState('');
|
|
315
|
+
const messagesEndRef = useRef(null);
|
|
316
|
+
const [ghLoginStatus, setGHLoginStatus] = useState(GitHubCopilotLoginStatus.NotLoggedIn);
|
|
317
|
+
const [loginClickCount, _setLoginClickCount] = useState(0);
|
|
318
|
+
const [copilotRequestInProgress, setCopilotRequestInProgress] = useState(false);
|
|
319
|
+
const [showPopover, setShowPopover] = useState(false);
|
|
320
|
+
const [originalPrefixes, setOriginalPrefixes] = useState([]);
|
|
321
|
+
const [prefixSuggestions, setPrefixSuggestions] = useState([]);
|
|
322
|
+
const [selectedPrefixSuggestionIndex, setSelectedPrefixSuggestionIndex] = useState(0);
|
|
323
|
+
const promptInputRef = useRef(null);
|
|
324
|
+
const [promptHistory, setPromptHistory] = useState([]);
|
|
325
|
+
// position on prompt history stack
|
|
326
|
+
const [promptHistoryIndex, setPromptHistoryIndex] = useState(0);
|
|
327
|
+
const [chatId, setChatId] = useState(UUID.uuid4());
|
|
328
|
+
const lastMessageId = useRef('');
|
|
329
|
+
const lastRequestTime = useRef(new Date());
|
|
330
|
+
const [contextOn, setContextOn] = useState(false);
|
|
331
|
+
const [activeDocumentInfo, setActiveDocumentInfo] = useState(null);
|
|
332
|
+
const [currentFileContextTitle, setCurrentFileContextTitle] = useState('');
|
|
333
|
+
const telemetryEmitter = props.getTelemetryEmitter();
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
const prefixes = [];
|
|
336
|
+
const chatParticipants = NBIAPI.config.chatParticipants;
|
|
337
|
+
for (const participant of chatParticipants) {
|
|
338
|
+
const id = participant.id;
|
|
339
|
+
const commands = participant.commands;
|
|
340
|
+
const participantPrefix = id === 'default' ? '' : `@${id}`;
|
|
341
|
+
if (participantPrefix !== '') {
|
|
342
|
+
prefixes.push(participantPrefix);
|
|
343
|
+
}
|
|
344
|
+
const commandPrefix = participantPrefix === '' ? '' : `${participantPrefix} `;
|
|
345
|
+
for (const command of commands) {
|
|
346
|
+
prefixes.push(`${commandPrefix}/${command}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
setOriginalPrefixes(prefixes);
|
|
350
|
+
setPrefixSuggestions(prefixes);
|
|
351
|
+
}, []);
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
const fetchData = () => {
|
|
354
|
+
setGHLoginStatus(NBIAPI.getLoginStatus());
|
|
355
|
+
};
|
|
356
|
+
fetchData();
|
|
357
|
+
const intervalId = setInterval(fetchData, 1000);
|
|
358
|
+
return () => clearInterval(intervalId);
|
|
359
|
+
}, [loginClickCount]);
|
|
360
|
+
useEffect(() => {
|
|
361
|
+
setSelectedPrefixSuggestionIndex(0);
|
|
362
|
+
}, [prefixSuggestions]);
|
|
363
|
+
const onPromptChange = (event) => {
|
|
364
|
+
const newPrompt = event.target.value;
|
|
365
|
+
setPrompt(newPrompt);
|
|
366
|
+
const trimmedPrompt = newPrompt.trimStart();
|
|
367
|
+
if (trimmedPrompt === '@' || trimmedPrompt === '/') {
|
|
368
|
+
setShowPopover(true);
|
|
369
|
+
filterPrefixSuggestions(trimmedPrompt);
|
|
370
|
+
}
|
|
371
|
+
else if (trimmedPrompt.startsWith('@') ||
|
|
372
|
+
trimmedPrompt.startsWith('/') ||
|
|
373
|
+
trimmedPrompt === '') {
|
|
374
|
+
filterPrefixSuggestions(trimmedPrompt);
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
setShowPopover(false);
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
const applyPrefixSuggestion = (prefix) => {
|
|
381
|
+
var _a;
|
|
382
|
+
if (prefix.includes(prompt)) {
|
|
383
|
+
setPrompt(`${prefix} `);
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
setPrompt(`${prefix} ${prompt} `);
|
|
387
|
+
}
|
|
388
|
+
setShowPopover(false);
|
|
389
|
+
(_a = promptInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
390
|
+
setSelectedPrefixSuggestionIndex(0);
|
|
391
|
+
};
|
|
392
|
+
const prefixSuggestionSelected = (event) => {
|
|
393
|
+
const prefix = event.target.dataset['value'];
|
|
394
|
+
applyPrefixSuggestion(prefix);
|
|
395
|
+
};
|
|
396
|
+
const handleSubmitStopChatButtonClick = async () => {
|
|
397
|
+
if (!copilotRequestInProgress) {
|
|
398
|
+
handleUserInputSubmit();
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
handleUserInputCancel();
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
const handleUserInputSubmit = async () => {
|
|
405
|
+
setPromptHistoryIndex(promptHistory.length + 1);
|
|
406
|
+
setPromptHistory([...promptHistory, prompt]);
|
|
407
|
+
setShowPopover(false);
|
|
408
|
+
const promptPrefixParts = [];
|
|
409
|
+
const promptParts = prompt.split(' ');
|
|
410
|
+
if (promptParts.length > 1) {
|
|
411
|
+
for (let i = 0; i < Math.min(promptParts.length, 2); i++) {
|
|
412
|
+
const part = promptParts[i];
|
|
413
|
+
if (part.startsWith('@') || part.startsWith('/')) {
|
|
414
|
+
promptPrefixParts.push(part);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const promptPrefix = promptPrefixParts.length > 0 ? promptPrefixParts.join(' ') + ' ' : '';
|
|
419
|
+
lastMessageId.current = UUID.uuid4();
|
|
420
|
+
lastRequestTime.current = new Date();
|
|
421
|
+
const newList = [
|
|
422
|
+
...chatMessages,
|
|
423
|
+
{
|
|
424
|
+
id: lastMessageId.current,
|
|
425
|
+
date: new Date(),
|
|
426
|
+
from: 'user',
|
|
427
|
+
contents: [
|
|
428
|
+
{
|
|
429
|
+
id: lastMessageId.current,
|
|
430
|
+
type: ResponseStreamDataType.Markdown,
|
|
431
|
+
content: prompt,
|
|
432
|
+
created: new Date()
|
|
433
|
+
}
|
|
434
|
+
]
|
|
435
|
+
}
|
|
436
|
+
];
|
|
437
|
+
setChatMessages(newList);
|
|
438
|
+
if (prompt.startsWith('/clear')) {
|
|
439
|
+
setChatMessages([]);
|
|
440
|
+
setPrompt('');
|
|
441
|
+
resetChatId();
|
|
442
|
+
resetPrefixSuggestions();
|
|
443
|
+
setPromptHistory([]);
|
|
444
|
+
setPromptHistoryIndex(0);
|
|
445
|
+
NBIAPI.sendWebSocketMessage(UUID.uuid4(), RequestDataType.ClearChatHistory, { chatId });
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
setCopilotRequestInProgress(true);
|
|
449
|
+
const activeDocInfo = props.getActiveDocumentInfo();
|
|
450
|
+
const extractedPrompt = prompt;
|
|
451
|
+
const contents = [];
|
|
452
|
+
const app = props.getApp();
|
|
453
|
+
const additionalContext = [];
|
|
454
|
+
if (contextOn && (activeDocumentInfo === null || activeDocumentInfo === void 0 ? void 0 : activeDocumentInfo.filename)) {
|
|
455
|
+
const selection = activeDocumentInfo.selection;
|
|
456
|
+
const textSelected = selection &&
|
|
457
|
+
!(selection.start.line === selection.end.line &&
|
|
458
|
+
selection.start.column === selection.end.column);
|
|
459
|
+
additionalContext.push({
|
|
460
|
+
type: ContextType.CurrentFile,
|
|
461
|
+
content: props.getActiveSelectionContent(),
|
|
462
|
+
currentCellContents: textSelected
|
|
463
|
+
? null
|
|
464
|
+
: props.getCurrentCellContents(),
|
|
465
|
+
filePath: activeDocumentInfo.filePath,
|
|
466
|
+
cellIndex: activeDocumentInfo.activeCellIndex,
|
|
467
|
+
startLine: selection ? selection.start.line + 1 : 1,
|
|
468
|
+
endLine: selection ? selection.end.line + 1 : 1
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
submitCompletionRequest({
|
|
472
|
+
messageId: lastMessageId.current,
|
|
473
|
+
chatId,
|
|
474
|
+
type: RunChatCompletionType.Chat,
|
|
475
|
+
content: extractedPrompt,
|
|
476
|
+
language: activeDocInfo.language,
|
|
477
|
+
filename: activeDocInfo.filename,
|
|
478
|
+
additionalContext
|
|
479
|
+
}, {
|
|
480
|
+
emit: async (response) => {
|
|
481
|
+
var _a, _b, _c, _d, _e;
|
|
482
|
+
if (response.id !== lastMessageId.current) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
let responseMessage = '';
|
|
486
|
+
if (response.type === BackendMessageType.StreamMessage) {
|
|
487
|
+
const delta = (_b = (_a = response.data['choices']) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b['delta'];
|
|
488
|
+
if (!delta) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (delta['nbiContent']) {
|
|
492
|
+
const nbiContent = delta['nbiContent'];
|
|
493
|
+
contents.push({
|
|
494
|
+
id: response.id,
|
|
495
|
+
type: nbiContent.type,
|
|
496
|
+
content: nbiContent.content,
|
|
497
|
+
created: new Date(response.created)
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
responseMessage =
|
|
502
|
+
(_e = (_d = (_c = response.data['choices']) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d['delta']) === null || _e === void 0 ? void 0 : _e['content'];
|
|
503
|
+
if (!responseMessage) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
contents.push({
|
|
507
|
+
id: response.id,
|
|
508
|
+
type: ResponseStreamDataType.MarkdownPart,
|
|
509
|
+
content: responseMessage,
|
|
510
|
+
created: new Date(response.created)
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
else if (response.type === BackendMessageType.StreamEnd) {
|
|
515
|
+
setCopilotRequestInProgress(false);
|
|
516
|
+
const timeElapsed = (new Date().getTime() - lastRequestTime.current.getTime()) / 1000;
|
|
517
|
+
telemetryEmitter.emitTelemetryEvent({
|
|
518
|
+
type: TelemetryEventType.ChatResponse,
|
|
519
|
+
data: {
|
|
520
|
+
chatModel: {
|
|
521
|
+
provider: NBIAPI.config.chatModel.provider,
|
|
522
|
+
model: NBIAPI.config.chatModel.model
|
|
523
|
+
},
|
|
524
|
+
timeElapsed
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
else if (response.type === BackendMessageType.RunUICommand) {
|
|
529
|
+
const messageId = response.id;
|
|
530
|
+
const result = await app.commands.execute(response.data.commandId, response.data.args);
|
|
531
|
+
const data = {
|
|
532
|
+
callback_id: response.data.callback_id,
|
|
533
|
+
result: result || 'void'
|
|
534
|
+
};
|
|
535
|
+
try {
|
|
536
|
+
JSON.stringify(data);
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
data.result = 'Could not serialize the result';
|
|
540
|
+
}
|
|
541
|
+
NBIAPI.sendWebSocketMessage(messageId, RequestDataType.RunUICommandResponse, data);
|
|
542
|
+
}
|
|
543
|
+
setChatMessages([
|
|
544
|
+
...newList,
|
|
545
|
+
{
|
|
546
|
+
id: UUID.uuid4(),
|
|
547
|
+
date: new Date(),
|
|
548
|
+
from: 'copilot',
|
|
549
|
+
contents: contents,
|
|
550
|
+
participant: NBIAPI.config.chatParticipants.find(participant => {
|
|
551
|
+
return participant.id === response.participant;
|
|
552
|
+
})
|
|
553
|
+
}
|
|
554
|
+
]);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
const newPrompt = prompt.startsWith('/settings') ? '' : promptPrefix;
|
|
558
|
+
setPrompt(newPrompt);
|
|
559
|
+
filterPrefixSuggestions(newPrompt);
|
|
560
|
+
telemetryEmitter.emitTelemetryEvent({
|
|
561
|
+
type: TelemetryEventType.ChatRequest,
|
|
562
|
+
data: {
|
|
563
|
+
chatModel: {
|
|
564
|
+
provider: NBIAPI.config.chatModel.provider,
|
|
565
|
+
model: NBIAPI.config.chatModel.model
|
|
566
|
+
},
|
|
567
|
+
prompt: extractedPrompt
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
};
|
|
571
|
+
const handleUserInputCancel = async () => {
|
|
572
|
+
NBIAPI.sendWebSocketMessage(lastMessageId.current, RequestDataType.CancelChatRequest, { chatId });
|
|
573
|
+
lastMessageId.current = '';
|
|
574
|
+
setCopilotRequestInProgress(false);
|
|
575
|
+
};
|
|
576
|
+
const filterPrefixSuggestions = (prmpt) => {
|
|
577
|
+
const userInput = prmpt.trimStart();
|
|
578
|
+
if (userInput === '') {
|
|
579
|
+
setPrefixSuggestions(originalPrefixes);
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
setPrefixSuggestions(originalPrefixes.filter(prefix => prefix.includes(userInput)));
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
const resetPrefixSuggestions = () => {
|
|
586
|
+
setPrefixSuggestions(originalPrefixes);
|
|
587
|
+
setSelectedPrefixSuggestionIndex(0);
|
|
588
|
+
};
|
|
589
|
+
const resetChatId = () => {
|
|
590
|
+
setChatId(UUID.uuid4());
|
|
591
|
+
};
|
|
592
|
+
const onPromptKeyDown = async (event) => {
|
|
593
|
+
if (event.key === 'Enter') {
|
|
594
|
+
event.stopPropagation();
|
|
595
|
+
event.preventDefault();
|
|
596
|
+
if (showPopover) {
|
|
597
|
+
applyPrefixSuggestion(prefixSuggestions[selectedPrefixSuggestionIndex]);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
setSelectedPrefixSuggestionIndex(0);
|
|
601
|
+
handleSubmitStopChatButtonClick();
|
|
602
|
+
}
|
|
603
|
+
else if (event.key === 'Tab') {
|
|
604
|
+
if (showPopover) {
|
|
605
|
+
event.stopPropagation();
|
|
606
|
+
event.preventDefault();
|
|
607
|
+
applyPrefixSuggestion(prefixSuggestions[selectedPrefixSuggestionIndex]);
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
else if (event.key === 'Escape') {
|
|
612
|
+
event.stopPropagation();
|
|
613
|
+
event.preventDefault();
|
|
614
|
+
setShowPopover(false);
|
|
615
|
+
setSelectedPrefixSuggestionIndex(0);
|
|
616
|
+
}
|
|
617
|
+
else if (event.key === 'ArrowUp') {
|
|
618
|
+
event.stopPropagation();
|
|
619
|
+
event.preventDefault();
|
|
620
|
+
if (showPopover) {
|
|
621
|
+
setSelectedPrefixSuggestionIndex((selectedPrefixSuggestionIndex - 1 + prefixSuggestions.length) %
|
|
622
|
+
prefixSuggestions.length);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
setShowPopover(false);
|
|
626
|
+
// first time up key press
|
|
627
|
+
if (promptHistory.length > 0 &&
|
|
628
|
+
promptHistoryIndex === promptHistory.length) {
|
|
629
|
+
setDraftPrompt(prompt);
|
|
630
|
+
}
|
|
631
|
+
if (promptHistory.length > 0 &&
|
|
632
|
+
promptHistoryIndex > 0 &&
|
|
633
|
+
promptHistoryIndex <= promptHistory.length) {
|
|
634
|
+
const prevPrompt = promptHistory[promptHistoryIndex - 1];
|
|
635
|
+
const newIndex = promptHistoryIndex - 1;
|
|
636
|
+
setPrompt(prevPrompt);
|
|
637
|
+
setPromptHistoryIndex(newIndex);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
else if (event.key === 'ArrowDown') {
|
|
641
|
+
event.stopPropagation();
|
|
642
|
+
event.preventDefault();
|
|
643
|
+
if (showPopover) {
|
|
644
|
+
setSelectedPrefixSuggestionIndex((selectedPrefixSuggestionIndex + 1 + prefixSuggestions.length) %
|
|
645
|
+
prefixSuggestions.length);
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
setShowPopover(false);
|
|
649
|
+
if (promptHistory.length > 0 &&
|
|
650
|
+
promptHistoryIndex >= 0 &&
|
|
651
|
+
promptHistoryIndex < promptHistory.length) {
|
|
652
|
+
if (promptHistoryIndex === promptHistory.length - 1) {
|
|
653
|
+
setPrompt(draftPrompt);
|
|
654
|
+
setPromptHistoryIndex(promptHistory.length);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
const prevPrompt = promptHistory[promptHistoryIndex + 1];
|
|
658
|
+
const newIndex = promptHistoryIndex + 1;
|
|
659
|
+
setPrompt(prevPrompt);
|
|
660
|
+
setPromptHistoryIndex(newIndex);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
const scrollMessagesToBottom = () => {
|
|
665
|
+
var _a;
|
|
666
|
+
(_a = messagesEndRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: 'smooth' });
|
|
667
|
+
};
|
|
668
|
+
const handleConfigurationClick = async () => {
|
|
669
|
+
props
|
|
670
|
+
.getApp()
|
|
671
|
+
.commands.execute('notebook-intelligence:open-configuration-dialog');
|
|
672
|
+
};
|
|
673
|
+
const handleLoginClick = async () => {
|
|
674
|
+
props
|
|
675
|
+
.getApp()
|
|
676
|
+
.commands.execute('notebook-intelligence:open-github-copilot-login-dialog');
|
|
677
|
+
};
|
|
678
|
+
useEffect(() => {
|
|
679
|
+
scrollMessagesToBottom();
|
|
680
|
+
}, [chatMessages]);
|
|
681
|
+
const promptRequestHandler = useCallback((eventData) => {
|
|
682
|
+
const request = eventData.detail;
|
|
683
|
+
request.chatId = chatId;
|
|
684
|
+
let message = '';
|
|
685
|
+
switch (request.type) {
|
|
686
|
+
case RunChatCompletionType.ExplainThis:
|
|
687
|
+
message = `Explain this code:\n\`\`\`\n${request.content}\n\`\`\`\n`;
|
|
688
|
+
break;
|
|
689
|
+
case RunChatCompletionType.FixThis:
|
|
690
|
+
message = `Fix this code:\n\`\`\`\n${request.content}\n\`\`\`\n`;
|
|
691
|
+
break;
|
|
692
|
+
case RunChatCompletionType.ExplainThisOutput:
|
|
693
|
+
message = `Explain this notebook cell output: \n\`\`\`\n${request.content}\n\`\`\`\n`;
|
|
694
|
+
break;
|
|
695
|
+
case RunChatCompletionType.TroubleshootThisOutput:
|
|
696
|
+
message = `Troubleshoot errors reported in the notebook cell output: \n\`\`\`\n${request.content}\n\`\`\`\n`;
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
const messageId = UUID.uuid4();
|
|
700
|
+
request.messageId = messageId;
|
|
701
|
+
const newList = [
|
|
702
|
+
...chatMessages,
|
|
703
|
+
{
|
|
704
|
+
id: messageId,
|
|
705
|
+
date: new Date(),
|
|
706
|
+
from: 'user',
|
|
707
|
+
contents: [
|
|
708
|
+
{
|
|
709
|
+
id: messageId,
|
|
710
|
+
type: ResponseStreamDataType.Markdown,
|
|
711
|
+
content: message,
|
|
712
|
+
created: new Date()
|
|
713
|
+
}
|
|
714
|
+
]
|
|
715
|
+
}
|
|
716
|
+
];
|
|
717
|
+
setChatMessages(newList);
|
|
718
|
+
setCopilotRequestInProgress(true);
|
|
719
|
+
const contents = [];
|
|
720
|
+
submitCompletionRequest(request, {
|
|
721
|
+
emit: response => {
|
|
722
|
+
var _a, _b, _c, _d, _e;
|
|
723
|
+
if (response.type === BackendMessageType.StreamMessage) {
|
|
724
|
+
const delta = (_b = (_a = response.data['choices']) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b['delta'];
|
|
725
|
+
if (!delta) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const responseMessage = (_e = (_d = (_c = response.data['choices']) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d['delta']) === null || _e === void 0 ? void 0 : _e['content'];
|
|
729
|
+
if (!responseMessage) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
contents.push({
|
|
733
|
+
id: response.id,
|
|
734
|
+
type: ResponseStreamDataType.MarkdownPart,
|
|
735
|
+
content: responseMessage,
|
|
736
|
+
created: new Date(response.created)
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
else if (response.type === BackendMessageType.StreamEnd) {
|
|
740
|
+
setCopilotRequestInProgress(false);
|
|
741
|
+
}
|
|
742
|
+
const messageId = UUID.uuid4();
|
|
743
|
+
setChatMessages([
|
|
744
|
+
...newList,
|
|
745
|
+
{
|
|
746
|
+
id: messageId,
|
|
747
|
+
date: new Date(),
|
|
748
|
+
from: 'copilot',
|
|
749
|
+
contents: contents,
|
|
750
|
+
participant: NBIAPI.config.chatParticipants.find(participant => {
|
|
751
|
+
return participant.id === response.participant;
|
|
752
|
+
})
|
|
753
|
+
}
|
|
754
|
+
]);
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
}, [chatMessages]);
|
|
758
|
+
useEffect(() => {
|
|
759
|
+
document.addEventListener('copilotSidebar:runPrompt', promptRequestHandler);
|
|
760
|
+
return () => {
|
|
761
|
+
document.removeEventListener('copilotSidebar:runPrompt', promptRequestHandler);
|
|
762
|
+
};
|
|
763
|
+
}, [chatMessages]);
|
|
764
|
+
const activeDocumentChangeHandler = (eventData) => {
|
|
765
|
+
var _a;
|
|
766
|
+
// if file changes reset the context toggle
|
|
767
|
+
if (((_a = eventData.detail.activeDocumentInfo) === null || _a === void 0 ? void 0 : _a.filePath) !==
|
|
768
|
+
(activeDocumentInfo === null || activeDocumentInfo === void 0 ? void 0 : activeDocumentInfo.filePath)) {
|
|
769
|
+
setContextOn(false);
|
|
770
|
+
}
|
|
771
|
+
setActiveDocumentInfo({
|
|
772
|
+
...eventData.detail.activeDocumentInfo,
|
|
773
|
+
...{ activeWidget: null }
|
|
774
|
+
});
|
|
775
|
+
setCurrentFileContextTitle(getActiveDocumentContextTitle(eventData.detail.activeDocumentInfo));
|
|
776
|
+
};
|
|
777
|
+
useEffect(() => {
|
|
778
|
+
document.addEventListener('copilotSidebar:activeDocumentChanged', activeDocumentChangeHandler);
|
|
779
|
+
return () => {
|
|
780
|
+
document.removeEventListener('copilotSidebar:activeDocumentChanged', activeDocumentChangeHandler);
|
|
781
|
+
};
|
|
782
|
+
}, [activeDocumentInfo]);
|
|
783
|
+
const getActiveDocumentContextTitle = (activeDocumentInfo) => {
|
|
784
|
+
if (!(activeDocumentInfo === null || activeDocumentInfo === void 0 ? void 0 : activeDocumentInfo.filename)) {
|
|
785
|
+
return '';
|
|
786
|
+
}
|
|
787
|
+
const wholeFile = !activeDocumentInfo.selection ||
|
|
788
|
+
(activeDocumentInfo.selection.start.line ===
|
|
789
|
+
activeDocumentInfo.selection.end.line &&
|
|
790
|
+
activeDocumentInfo.selection.start.column ===
|
|
791
|
+
activeDocumentInfo.selection.end.column);
|
|
792
|
+
let cellAndLineIndicator = '';
|
|
793
|
+
if (!wholeFile) {
|
|
794
|
+
if (activeDocumentInfo.filename.endsWith('.ipynb')) {
|
|
795
|
+
cellAndLineIndicator = ` · Cell ${activeDocumentInfo.activeCellIndex + 1}`;
|
|
796
|
+
}
|
|
797
|
+
if (activeDocumentInfo.selection.start.line ===
|
|
798
|
+
activeDocumentInfo.selection.end.line) {
|
|
799
|
+
cellAndLineIndicator += `:${activeDocumentInfo.selection.start.line + 1}`;
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
cellAndLineIndicator += `:${activeDocumentInfo.selection.start.line + 1}-${activeDocumentInfo.selection.end.line + 1}`;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return `${activeDocumentInfo.filename}${cellAndLineIndicator}`;
|
|
806
|
+
};
|
|
807
|
+
const nbiConfig = NBIAPI.config;
|
|
808
|
+
const getGHLoginRequired = () => {
|
|
809
|
+
return (nbiConfig.usingGitHubCopilotModel &&
|
|
810
|
+
NBIAPI.getLoginStatus() === GitHubCopilotLoginStatus.NotLoggedIn);
|
|
811
|
+
};
|
|
812
|
+
const getChatEnabled = () => {
|
|
813
|
+
return nbiConfig.chatModel.provider === GITHUB_COPILOT_PROVIDER_ID
|
|
814
|
+
? !getGHLoginRequired()
|
|
815
|
+
: nbiConfig.llmProviders.find(provider => provider.id === nbiConfig.chatModel.provider);
|
|
816
|
+
};
|
|
817
|
+
const [ghLoginRequired, setGHLoginRequired] = useState(getGHLoginRequired());
|
|
818
|
+
const [chatEnabled, setChatEnabled] = useState(getChatEnabled());
|
|
819
|
+
NBIAPI.configChanged.connect(() => {
|
|
820
|
+
setGHLoginRequired(getGHLoginRequired());
|
|
821
|
+
setChatEnabled(getChatEnabled());
|
|
822
|
+
});
|
|
823
|
+
useEffect(() => {
|
|
824
|
+
setGHLoginRequired(getGHLoginRequired());
|
|
825
|
+
setChatEnabled(getChatEnabled());
|
|
826
|
+
}, [ghLoginStatus]);
|
|
827
|
+
return (React.createElement("div", { className: "sidebar" },
|
|
828
|
+
React.createElement("div", { className: "sidebar-header" },
|
|
829
|
+
React.createElement("div", { className: "sidebar-title" }, "Copilot Chat")),
|
|
830
|
+
!chatEnabled && !ghLoginRequired && (React.createElement("div", { className: "sidebar-login-info" },
|
|
831
|
+
"Chat is disabled as you don't have a model configured.",
|
|
832
|
+
React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: handleConfigurationClick },
|
|
833
|
+
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, "Configure models")))),
|
|
834
|
+
ghLoginRequired && (React.createElement("div", { className: "sidebar-login-info" },
|
|
835
|
+
React.createElement("div", null, "You are not logged in to GitHub Copilot. Please login now to activate chat."),
|
|
836
|
+
React.createElement("div", null,
|
|
837
|
+
React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: handleLoginClick },
|
|
838
|
+
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, "Login to GitHub Copilot")),
|
|
839
|
+
React.createElement("button", { className: "jp-Dialog-button jp-mod-reject jp-mod-styled", onClick: handleConfigurationClick },
|
|
840
|
+
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, "Change provider"))))),
|
|
841
|
+
chatEnabled &&
|
|
842
|
+
(chatMessages.length === 0 ? (React.createElement("div", { className: "sidebar-messages" },
|
|
843
|
+
React.createElement("div", { className: "sidebar-greeting" }, "Welcome! How can I assist you today?"))) : (React.createElement("div", { className: "sidebar-messages" },
|
|
844
|
+
chatMessages.map((msg, index) => (React.createElement(ChatResponse, { key: `key-${index}`, message: msg, openFile: props.openFile, getApp: props.getApp, getActiveDocumentInfo: props.getActiveDocumentInfo, showGenerating: index === chatMessages.length - 1 &&
|
|
845
|
+
msg.from === 'copilot' &&
|
|
846
|
+
copilotRequestInProgress }))),
|
|
847
|
+
React.createElement("div", { ref: messagesEndRef })))),
|
|
848
|
+
chatEnabled && (React.createElement("div", { className: `sidebar-user-input ${copilotRequestInProgress ? 'generating' : ''}` },
|
|
849
|
+
React.createElement("textarea", { ref: promptInputRef, rows: 3, onChange: onPromptChange, onKeyDown: onPromptKeyDown, placeholder: "Ask Copilot...", spellCheck: false, value: prompt }),
|
|
850
|
+
(activeDocumentInfo === null || activeDocumentInfo === void 0 ? void 0 : activeDocumentInfo.filename) && (React.createElement("div", { className: "user-input-context-row" },
|
|
851
|
+
React.createElement("div", { className: `user-input-context user-input-context-active-file ${contextOn ? 'on' : 'off'}` },
|
|
852
|
+
React.createElement("div", null, currentFileContextTitle),
|
|
853
|
+
contextOn ? (React.createElement("div", { className: "user-input-context-toggle", onClick: () => setContextOn(!contextOn) },
|
|
854
|
+
React.createElement(VscEye, { title: "Use as context" }))) : (React.createElement("div", { className: "user-input-context-toggle", onClick: () => setContextOn(!contextOn) },
|
|
855
|
+
React.createElement(VscEyeClosed, { title: "Don't use as context" })))))),
|
|
856
|
+
React.createElement("div", { className: "user-input-footer" },
|
|
857
|
+
React.createElement("div", null,
|
|
858
|
+
React.createElement("a", { href: "javascript:void(0)", onClick: () => {
|
|
859
|
+
var _a;
|
|
860
|
+
setShowPopover(true);
|
|
861
|
+
(_a = promptInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
862
|
+
}, title: "Select chat participant" }, "@")),
|
|
863
|
+
React.createElement("div", { style: { flexGrow: 1 } }),
|
|
864
|
+
React.createElement("div", null,
|
|
865
|
+
React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled send-button", onClick: () => handleSubmitStopChatButtonClick(), disabled: prompt.length === 0 && !copilotRequestInProgress }, copilotRequestInProgress ? React.createElement(VscStopCircle, null) : React.createElement(VscSend, null)))),
|
|
866
|
+
showPopover && prefixSuggestions.length > 0 && (React.createElement("div", { className: "user-input-autocomplete" }, prefixSuggestions.map((prefix, index) => (React.createElement("div", { key: `key-${index}`, className: `user-input-autocomplete-item ${index === selectedPrefixSuggestionIndex ? 'selected' : ''}`, "data-value": prefix, onClick: event => prefixSuggestionSelected(event) }, prefix)))))))));
|
|
867
|
+
}
|
|
868
|
+
function InlinePopoverComponent(props) {
|
|
869
|
+
const [modifiedCode, setModifiedCode] = useState('');
|
|
870
|
+
const [promptSubmitted, setPromptSubmitted] = useState(false);
|
|
871
|
+
const originalOnRequestSubmitted = props.onRequestSubmitted;
|
|
872
|
+
const originalOnResponseEmit = props.onResponseEmit;
|
|
873
|
+
const onRequestSubmitted = (prompt) => {
|
|
874
|
+
setModifiedCode('');
|
|
875
|
+
setPromptSubmitted(true);
|
|
876
|
+
originalOnRequestSubmitted(prompt);
|
|
877
|
+
};
|
|
878
|
+
const onResponseEmit = (response) => {
|
|
879
|
+
var _a, _b, _c, _d, _e;
|
|
880
|
+
if (response.type === BackendMessageType.StreamMessage) {
|
|
881
|
+
const delta = (_b = (_a = response.data['choices']) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b['delta'];
|
|
882
|
+
if (!delta) {
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
const responseMessage = (_e = (_d = (_c = response.data['choices']) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d['delta']) === null || _e === void 0 ? void 0 : _e['content'];
|
|
886
|
+
if (!responseMessage) {
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
setModifiedCode((modifiedCode) => modifiedCode + responseMessage);
|
|
890
|
+
}
|
|
891
|
+
else if (response.type === BackendMessageType.StreamEnd) {
|
|
892
|
+
setModifiedCode((modifiedCode) => extractLLMGeneratedCode(modifiedCode));
|
|
893
|
+
}
|
|
894
|
+
originalOnResponseEmit(response);
|
|
895
|
+
};
|
|
896
|
+
return (React.createElement("div", { className: "inline-popover" },
|
|
897
|
+
React.createElement(InlinePromptComponent, { ...props, onRequestSubmitted: onRequestSubmitted, onResponseEmit: onResponseEmit, onUpdatedCodeAccepted: props.onUpdatedCodeAccepted, limitHeight: props.existingCode !== '' && promptSubmitted }),
|
|
898
|
+
props.existingCode !== '' && promptSubmitted && (React.createElement(React.Fragment, null,
|
|
899
|
+
React.createElement(InlineDiffViewerComponent, { ...props, modifiedCode: modifiedCode }),
|
|
900
|
+
React.createElement("div", { className: "inline-popover-footer" },
|
|
901
|
+
React.createElement("div", null,
|
|
902
|
+
React.createElement("button", { className: "jp-Button jp-mod-accept jp-mod-styled jp-mod-small", onClick: () => props.onUpdatedCodeAccepted() }, "Accept")),
|
|
903
|
+
React.createElement("div", null,
|
|
904
|
+
React.createElement("button", { className: "jp-Button jp-mod-reject jp-mod-styled jp-mod-small", onClick: () => props.onRequestCancelled() }, "Cancel")))))));
|
|
905
|
+
}
|
|
906
|
+
function InlineDiffViewerComponent(props) {
|
|
907
|
+
const editorContainerRef = useRef(null);
|
|
908
|
+
const [diffEditor, setDiffEditor] = useState(null);
|
|
909
|
+
useEffect(() => {
|
|
910
|
+
const editorEl = editorContainerRef.current;
|
|
911
|
+
editorEl.className = 'monaco-editor-container';
|
|
912
|
+
const existingModel = monaco.editor.createModel(props.existingCode, 'text/plain');
|
|
913
|
+
const modifiedModel = monaco.editor.createModel(props.modifiedCode, 'text/plain');
|
|
914
|
+
const editor = monaco.editor.createDiffEditor(editorEl, {
|
|
915
|
+
originalEditable: false,
|
|
916
|
+
automaticLayout: true,
|
|
917
|
+
theme: isDarkTheme() ? 'vs-dark' : 'vs'
|
|
918
|
+
});
|
|
919
|
+
editor.setModel({
|
|
920
|
+
original: existingModel,
|
|
921
|
+
modified: modifiedModel
|
|
922
|
+
});
|
|
923
|
+
modifiedModel.onDidChangeContent(() => {
|
|
924
|
+
props.onUpdatedCodeChange(modifiedModel.getValue());
|
|
925
|
+
});
|
|
926
|
+
setDiffEditor(editor);
|
|
927
|
+
}, []);
|
|
928
|
+
useEffect(() => {
|
|
929
|
+
var _a;
|
|
930
|
+
(_a = diffEditor === null || diffEditor === void 0 ? void 0 : diffEditor.getModifiedEditor().getModel()) === null || _a === void 0 ? void 0 : _a.setValue(props.modifiedCode);
|
|
931
|
+
}, [props.modifiedCode]);
|
|
932
|
+
return (React.createElement("div", { ref: editorContainerRef, className: "monaco-editor-container" }));
|
|
933
|
+
}
|
|
934
|
+
function InlinePromptComponent(props) {
|
|
935
|
+
const [prompt, setPrompt] = useState(props.prompt);
|
|
936
|
+
const promptInputRef = useRef(null);
|
|
937
|
+
const [inputSubmitted, setInputSubmitted] = useState(false);
|
|
938
|
+
const onPromptChange = (event) => {
|
|
939
|
+
const newPrompt = event.target.value;
|
|
940
|
+
setPrompt(newPrompt);
|
|
941
|
+
};
|
|
942
|
+
const handleUserInputSubmit = async () => {
|
|
943
|
+
const promptPrefixParts = [];
|
|
944
|
+
const promptParts = prompt.split(' ');
|
|
945
|
+
if (promptParts.length > 1) {
|
|
946
|
+
for (let i = 0; i < Math.min(promptParts.length, 2); i++) {
|
|
947
|
+
const part = promptParts[i];
|
|
948
|
+
if (part.startsWith('@') || part.startsWith('/')) {
|
|
949
|
+
promptPrefixParts.push(part);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
submitCompletionRequest({
|
|
954
|
+
messageId: UUID.uuid4(),
|
|
955
|
+
chatId: UUID.uuid4(),
|
|
956
|
+
type: RunChatCompletionType.GenerateCode,
|
|
957
|
+
content: prompt,
|
|
958
|
+
language: undefined,
|
|
959
|
+
filename: undefined,
|
|
960
|
+
prefix: props.prefix,
|
|
961
|
+
suffix: props.suffix,
|
|
962
|
+
existingCode: props.existingCode
|
|
963
|
+
}, {
|
|
964
|
+
emit: async (response) => {
|
|
965
|
+
props.onResponseEmit(response);
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
setInputSubmitted(true);
|
|
969
|
+
};
|
|
970
|
+
const onPromptKeyDown = async (event) => {
|
|
971
|
+
if (event.key === 'Enter') {
|
|
972
|
+
event.stopPropagation();
|
|
973
|
+
event.preventDefault();
|
|
974
|
+
if (inputSubmitted && (event.metaKey || event.ctrlKey)) {
|
|
975
|
+
props.onUpdatedCodeAccepted();
|
|
976
|
+
}
|
|
977
|
+
else {
|
|
978
|
+
props.onRequestSubmitted(prompt);
|
|
979
|
+
handleUserInputSubmit();
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
else if (event.key === 'Escape') {
|
|
983
|
+
event.stopPropagation();
|
|
984
|
+
event.preventDefault();
|
|
985
|
+
props.onRequestCancelled();
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
useEffect(() => {
|
|
989
|
+
var _a;
|
|
990
|
+
const input = promptInputRef.current;
|
|
991
|
+
if (input) {
|
|
992
|
+
input.select();
|
|
993
|
+
(_a = promptInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
994
|
+
}
|
|
995
|
+
}, []);
|
|
996
|
+
return (React.createElement("div", { className: "inline-prompt-container", style: { height: props.limitHeight ? '40px' : '100%' } },
|
|
997
|
+
React.createElement("textarea", { ref: promptInputRef, rows: 3, onChange: onPromptChange, onKeyDown: onPromptKeyDown, placeholder: "Ask Copilot to generate Python code...", spellCheck: false, value: prompt })));
|
|
998
|
+
}
|
|
999
|
+
function GitHubCopilotStatusComponent(props) {
|
|
1000
|
+
const [ghLoginStatus, setGHLoginStatus] = useState(GitHubCopilotLoginStatus.NotLoggedIn);
|
|
1001
|
+
const [loginClickCount, _setLoginClickCount] = useState(0);
|
|
1002
|
+
useEffect(() => {
|
|
1003
|
+
const fetchData = () => {
|
|
1004
|
+
setGHLoginStatus(NBIAPI.getLoginStatus());
|
|
1005
|
+
};
|
|
1006
|
+
fetchData();
|
|
1007
|
+
const intervalId = setInterval(fetchData, 1000);
|
|
1008
|
+
return () => clearInterval(intervalId);
|
|
1009
|
+
}, [loginClickCount]);
|
|
1010
|
+
const onStatusClick = () => {
|
|
1011
|
+
props
|
|
1012
|
+
.getApp()
|
|
1013
|
+
.commands.execute('notebook-intelligence:open-github-copilot-login-dialog');
|
|
1014
|
+
};
|
|
1015
|
+
return (React.createElement("div", { title: `GitHub Copilot: ${ghLoginStatus === GitHubCopilotLoginStatus.LoggedIn ? 'Logged in' : 'Not logged in'}`, className: "github-copilot-status-bar", onClick: () => onStatusClick(), dangerouslySetInnerHTML: {
|
|
1016
|
+
__html: ghLoginStatus === GitHubCopilotLoginStatus.LoggedIn
|
|
1017
|
+
? copilotSvgstr
|
|
1018
|
+
: copilotWarningSvgstr
|
|
1019
|
+
} }));
|
|
1020
|
+
}
|
|
1021
|
+
function GitHubCopilotLoginDialogBodyComponent(props) {
|
|
1022
|
+
const [ghLoginStatus, setGHLoginStatus] = useState(GitHubCopilotLoginStatus.NotLoggedIn);
|
|
1023
|
+
const [loginClickCount, setLoginClickCount] = useState(0);
|
|
1024
|
+
const [loginClicked, setLoginClicked] = useState(false);
|
|
1025
|
+
const [deviceActivationURL, setDeviceActivationURL] = useState('');
|
|
1026
|
+
const [deviceActivationCode, setDeviceActivationCode] = useState('');
|
|
1027
|
+
useEffect(() => {
|
|
1028
|
+
const fetchData = () => {
|
|
1029
|
+
const status = NBIAPI.getLoginStatus();
|
|
1030
|
+
setGHLoginStatus(status);
|
|
1031
|
+
if (status === GitHubCopilotLoginStatus.LoggedIn && loginClicked) {
|
|
1032
|
+
setTimeout(() => {
|
|
1033
|
+
props.onLoggedIn();
|
|
1034
|
+
}, 1000);
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
fetchData();
|
|
1038
|
+
const intervalId = setInterval(fetchData, 1000);
|
|
1039
|
+
return () => clearInterval(intervalId);
|
|
1040
|
+
}, [loginClickCount]);
|
|
1041
|
+
const handleLoginClick = async () => {
|
|
1042
|
+
const response = await NBIAPI.loginToGitHub();
|
|
1043
|
+
setDeviceActivationURL(response.verificationURI);
|
|
1044
|
+
setDeviceActivationCode(response.userCode);
|
|
1045
|
+
setLoginClickCount(loginClickCount + 1);
|
|
1046
|
+
setLoginClicked(true);
|
|
1047
|
+
};
|
|
1048
|
+
const handleLogoutClick = async () => {
|
|
1049
|
+
await NBIAPI.logoutFromGitHub();
|
|
1050
|
+
setLoginClickCount(loginClickCount + 1);
|
|
1051
|
+
};
|
|
1052
|
+
const loggedIn = ghLoginStatus === GitHubCopilotLoginStatus.LoggedIn;
|
|
1053
|
+
return (React.createElement("div", { className: "github-copilot-login-dialog" },
|
|
1054
|
+
React.createElement("div", { className: "github-copilot-login-status" },
|
|
1055
|
+
React.createElement("h4", null,
|
|
1056
|
+
"Login status:",
|
|
1057
|
+
' ',
|
|
1058
|
+
React.createElement("span", { className: `github-copilot-login-status-text ${loggedIn ? 'logged-in' : ''}` }, loggedIn
|
|
1059
|
+
? 'Logged in'
|
|
1060
|
+
: ghLoginStatus === GitHubCopilotLoginStatus.LoggingIn
|
|
1061
|
+
? 'Logging in...'
|
|
1062
|
+
: ghLoginStatus === GitHubCopilotLoginStatus.ActivatingDevice
|
|
1063
|
+
? 'Activating device...'
|
|
1064
|
+
: ghLoginStatus === GitHubCopilotLoginStatus.NotLoggedIn
|
|
1065
|
+
? 'Not logged in'
|
|
1066
|
+
: 'Unknown'))),
|
|
1067
|
+
ghLoginStatus === GitHubCopilotLoginStatus.NotLoggedIn && (React.createElement(React.Fragment, null,
|
|
1068
|
+
React.createElement("div", null, "Your code and data are directly transferred to GitHub Copilot as needed without storing any copies other than keeping in the process memory."),
|
|
1069
|
+
React.createElement("div", null,
|
|
1070
|
+
React.createElement("a", { href: "https://github.com/features/copilot", target: "_blank" }, "GitHub Copilot"),
|
|
1071
|
+
' ',
|
|
1072
|
+
"requires a subscription and it has a free tier. GitHub Copilot is subject to the",
|
|
1073
|
+
' ',
|
|
1074
|
+
React.createElement("a", { href: "https://docs.github.com/en/site-policy/github-terms/github-terms-for-additional-products-and-features", target: "_blank" }, "GitHub Terms for Additional Products and Features"),
|
|
1075
|
+
"."),
|
|
1076
|
+
React.createElement("div", null,
|
|
1077
|
+
React.createElement("h4", null, "Privacy and terms"),
|
|
1078
|
+
"By using Notebook Intelligence with GitHub Copilot subscription you agree to",
|
|
1079
|
+
' ',
|
|
1080
|
+
React.createElement("a", { href: "https://docs.github.com/en/copilot/responsible-use-of-github-copilot-features/responsible-use-of-github-copilot-chat-in-your-ide", target: "_blank" }, "GitHub Copilot chat terms"),
|
|
1081
|
+
". Review the terms to understand about usage, limitations and ways to improve GitHub Copilot. Please review",
|
|
1082
|
+
' ',
|
|
1083
|
+
React.createElement("a", { href: "https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement", target: "_blank" }, "Privacy Statement"),
|
|
1084
|
+
"."),
|
|
1085
|
+
React.createElement("div", null,
|
|
1086
|
+
React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-reject jp-mod-styled", onClick: handleLoginClick },
|
|
1087
|
+
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, "Login using your GitHub account"))))),
|
|
1088
|
+
loggedIn && (React.createElement("div", null,
|
|
1089
|
+
React.createElement("button", { className: "jp-Dialog-button jp-mod-reject jp-mod-styled", onClick: handleLogoutClick },
|
|
1090
|
+
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, "Logout")))),
|
|
1091
|
+
ghLoginStatus === GitHubCopilotLoginStatus.ActivatingDevice &&
|
|
1092
|
+
deviceActivationURL &&
|
|
1093
|
+
deviceActivationCode && (React.createElement("div", null,
|
|
1094
|
+
React.createElement("div", { className: "copilot-activation-message" },
|
|
1095
|
+
"Copy code",
|
|
1096
|
+
' ',
|
|
1097
|
+
React.createElement("span", { className: "user-code-span", onClick: () => {
|
|
1098
|
+
navigator.clipboard.writeText(deviceActivationCode);
|
|
1099
|
+
return true;
|
|
1100
|
+
} },
|
|
1101
|
+
React.createElement("b", null,
|
|
1102
|
+
deviceActivationCode,
|
|
1103
|
+
' ',
|
|
1104
|
+
React.createElement("span", { className: "copy-icon", dangerouslySetInnerHTML: { __html: copySvgstr } }))),
|
|
1105
|
+
' ',
|
|
1106
|
+
"and enter at",
|
|
1107
|
+
' ',
|
|
1108
|
+
React.createElement("a", { href: deviceActivationURL, target: "_blank" }, deviceActivationURL),
|
|
1109
|
+
' ',
|
|
1110
|
+
"to allow access to GitHub Copilot from this app. Activation could take up to a minute after you enter the code."))),
|
|
1111
|
+
ghLoginStatus === GitHubCopilotLoginStatus.ActivatingDevice && (React.createElement("div", { style: { marginTop: '10px' } },
|
|
1112
|
+
React.createElement("button", { className: "jp-Dialog-button jp-mod-reject jp-mod-styled", onClick: handleLogoutClick },
|
|
1113
|
+
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, "Cancel activation"))))));
|
|
1114
|
+
}
|
|
1115
|
+
function ConfigurationDialogBodyComponent(props) {
|
|
1116
|
+
const nbiConfig = NBIAPI.config;
|
|
1117
|
+
const llmProviders = nbiConfig.llmProviders;
|
|
1118
|
+
const [chatModels, setChatModels] = useState([]);
|
|
1119
|
+
const [inlineCompletionModels, setInlineCompletionModels] = useState([]);
|
|
1120
|
+
const handleSaveClick = async () => {
|
|
1121
|
+
await NBIAPI.setConfig({
|
|
1122
|
+
chat_model: {
|
|
1123
|
+
provider: chatModelProvider,
|
|
1124
|
+
model: chatModel,
|
|
1125
|
+
properties: chatModelProperties
|
|
1126
|
+
},
|
|
1127
|
+
inline_completion_model: {
|
|
1128
|
+
provider: inlineCompletionModelProvider,
|
|
1129
|
+
model: inlineCompletionModel,
|
|
1130
|
+
properties: inlineCompletionModelProperties
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
props.onSave();
|
|
1134
|
+
};
|
|
1135
|
+
const handleRefreshOllamaModelListClick = async () => {
|
|
1136
|
+
await NBIAPI.updateOllamaModelList();
|
|
1137
|
+
updateModelOptionsForProvider(chatModelProvider, 'chat');
|
|
1138
|
+
};
|
|
1139
|
+
const [chatModelProvider, setChatModelProvider] = useState(nbiConfig.chatModel.provider || 'none');
|
|
1140
|
+
const [inlineCompletionModelProvider, setInlineCompletionModelProvider] = useState(nbiConfig.inlineCompletionModel.provider || 'none');
|
|
1141
|
+
const [chatModel, setChatModel] = useState(nbiConfig.chatModel.model);
|
|
1142
|
+
const [chatModelProperties, setChatModelProperties] = useState([]);
|
|
1143
|
+
const [inlineCompletionModelProperties, setInlineCompletionModelProperties] = useState([]);
|
|
1144
|
+
const [inlineCompletionModel, setInlineCompletionModel] = useState(nbiConfig.inlineCompletionModel.model);
|
|
1145
|
+
const updateModelOptionsForProvider = (providerId, modelType) => {
|
|
1146
|
+
if (modelType === 'chat') {
|
|
1147
|
+
setChatModelProvider(providerId);
|
|
1148
|
+
}
|
|
1149
|
+
else {
|
|
1150
|
+
setInlineCompletionModelProvider(providerId);
|
|
1151
|
+
}
|
|
1152
|
+
const models = modelType === 'chat'
|
|
1153
|
+
? nbiConfig.chatModels
|
|
1154
|
+
: nbiConfig.inlineCompletionModels;
|
|
1155
|
+
const selectedModelId = modelType === 'chat'
|
|
1156
|
+
? nbiConfig.chatModel.model
|
|
1157
|
+
: nbiConfig.inlineCompletionModel.model;
|
|
1158
|
+
const providerModels = models.filter((model) => model.provider === providerId);
|
|
1159
|
+
if (modelType === 'chat') {
|
|
1160
|
+
setChatModels(providerModels);
|
|
1161
|
+
}
|
|
1162
|
+
else {
|
|
1163
|
+
setInlineCompletionModels(providerModels);
|
|
1164
|
+
}
|
|
1165
|
+
let selectedModel = providerModels.find((model) => model.id === selectedModelId);
|
|
1166
|
+
if (!selectedModel) {
|
|
1167
|
+
selectedModel = providerModels === null || providerModels === void 0 ? void 0 : providerModels[0];
|
|
1168
|
+
}
|
|
1169
|
+
if (selectedModel) {
|
|
1170
|
+
if (modelType === 'chat') {
|
|
1171
|
+
setChatModel(selectedModel.id);
|
|
1172
|
+
setChatModelProperties(selectedModel.properties);
|
|
1173
|
+
}
|
|
1174
|
+
else {
|
|
1175
|
+
setInlineCompletionModel(selectedModel.id);
|
|
1176
|
+
setInlineCompletionModelProperties(selectedModel.properties);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
else {
|
|
1180
|
+
if (modelType === 'chat') {
|
|
1181
|
+
setChatModelProperties([]);
|
|
1182
|
+
}
|
|
1183
|
+
else {
|
|
1184
|
+
setInlineCompletionModelProperties([]);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
const onModelPropertyChange = (modelType, propertyId, value) => {
|
|
1189
|
+
const modelProperties = modelType === 'chat'
|
|
1190
|
+
? chatModelProperties
|
|
1191
|
+
: inlineCompletionModelProperties;
|
|
1192
|
+
const updatedProperties = modelProperties.map((property) => {
|
|
1193
|
+
if (property.id === propertyId) {
|
|
1194
|
+
return { ...property, value };
|
|
1195
|
+
}
|
|
1196
|
+
return property;
|
|
1197
|
+
});
|
|
1198
|
+
if (modelType === 'chat') {
|
|
1199
|
+
setChatModelProperties(updatedProperties);
|
|
1200
|
+
}
|
|
1201
|
+
else {
|
|
1202
|
+
setInlineCompletionModelProperties(updatedProperties);
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
useEffect(() => {
|
|
1206
|
+
updateModelOptionsForProvider(chatModelProvider, 'chat');
|
|
1207
|
+
updateModelOptionsForProvider(inlineCompletionModelProvider, 'inline-completion');
|
|
1208
|
+
}, []);
|
|
1209
|
+
return (React.createElement("div", { className: "config-dialog" },
|
|
1210
|
+
React.createElement("div", { className: "config-dialog-body" },
|
|
1211
|
+
React.createElement("div", { className: "model-config-section" },
|
|
1212
|
+
React.createElement("div", { className: "model-config-section-header" }, "Chat model"),
|
|
1213
|
+
React.createElement("div", { className: "model-config-section-body" },
|
|
1214
|
+
React.createElement("div", { className: "model-config-section-row" },
|
|
1215
|
+
React.createElement("div", { className: "model-config-section-column" },
|
|
1216
|
+
React.createElement("div", null, "Provider"),
|
|
1217
|
+
React.createElement("div", null,
|
|
1218
|
+
React.createElement("select", { className: "jp-mod-styled", onChange: event => updateModelOptionsForProvider(event.target.value, 'chat') },
|
|
1219
|
+
llmProviders.map((provider, index) => (React.createElement("option", { key: index, value: provider.id, selected: provider.id === chatModelProvider }, provider.name))),
|
|
1220
|
+
React.createElement("option", { key: -1, value: "none", selected: chatModelProvider === 'none' ||
|
|
1221
|
+
!llmProviders.find(provider => provider.id === chatModelProvider) }, "None")))),
|
|
1222
|
+
!['openai-compatible', 'litellm-compatible', 'none'].includes(chatModelProvider) &&
|
|
1223
|
+
chatModels.length > 0 && (React.createElement("div", { className: "model-config-section-column" },
|
|
1224
|
+
React.createElement("div", null, "Model"),
|
|
1225
|
+
![
|
|
1226
|
+
OPENAI_COMPATIBLE_CHAT_MODEL_ID,
|
|
1227
|
+
LITELLM_COMPATIBLE_CHAT_MODEL_ID
|
|
1228
|
+
].includes(chatModel) &&
|
|
1229
|
+
chatModels.length > 0 && (React.createElement("div", null,
|
|
1230
|
+
React.createElement("select", { className: "jp-mod-styled", onChange: event => setChatModel(event.target.value) }, chatModels.map((model, index) => (React.createElement("option", { key: index, value: model.id, selected: model.id === chatModel }, model.name))))))))),
|
|
1231
|
+
React.createElement("div", { className: "model-config-section-row" },
|
|
1232
|
+
React.createElement("div", { className: "model-config-section-column" }, chatModelProvider === 'ollama' && chatModels.length === 0 && (React.createElement("div", { className: "ollama-warning-message" },
|
|
1233
|
+
"No Ollama models found! Make sure",
|
|
1234
|
+
' ',
|
|
1235
|
+
React.createElement("a", { href: "https://ollama.com/", target: "_blank" }, "Ollama"),
|
|
1236
|
+
' ',
|
|
1237
|
+
"is running and models are downloaded to your computer.",
|
|
1238
|
+
' ',
|
|
1239
|
+
React.createElement("a", { href: "javascript:void(0)", onClick: handleRefreshOllamaModelListClick }, "Try again"),
|
|
1240
|
+
' ',
|
|
1241
|
+
"once ready.")))),
|
|
1242
|
+
React.createElement("div", { className: "model-config-section-row" },
|
|
1243
|
+
React.createElement("div", { className: "model-config-section-column" }, chatModelProperties.map((property, index) => (React.createElement("div", { className: "form-field-row", key: index },
|
|
1244
|
+
React.createElement("div", { className: "form-field-description" },
|
|
1245
|
+
property.name,
|
|
1246
|
+
" ",
|
|
1247
|
+
property.optional ? '(optional)' : ''),
|
|
1248
|
+
React.createElement("input", { name: "chat-model-id-input", placeholder: property.description, className: "jp-mod-styled", spellCheck: false, value: property.value, onChange: event => onModelPropertyChange('chat', property.id, event.target.value) })))))))),
|
|
1249
|
+
React.createElement("div", { className: "model-config-section" },
|
|
1250
|
+
React.createElement("div", { className: "model-config-section-header" }, "Auto-complete model"),
|
|
1251
|
+
React.createElement("div", { className: "model-config-section-body" },
|
|
1252
|
+
React.createElement("div", { className: "model-config-section-row" },
|
|
1253
|
+
React.createElement("div", { className: "model-config-section-column" },
|
|
1254
|
+
React.createElement("div", null, "Provider"),
|
|
1255
|
+
React.createElement("div", null,
|
|
1256
|
+
React.createElement("select", { className: "jp-mod-styled", onChange: event => updateModelOptionsForProvider(event.target.value, 'inline-completion') },
|
|
1257
|
+
llmProviders.map((provider, index) => (React.createElement("option", { key: index, value: provider.id, selected: provider.id === inlineCompletionModelProvider }, provider.name))),
|
|
1258
|
+
React.createElement("option", { key: -1, value: "none", selected: inlineCompletionModelProvider === 'none' ||
|
|
1259
|
+
!llmProviders.find(provider => provider.id === inlineCompletionModelProvider) }, "None")))),
|
|
1260
|
+
!['openai-compatible', 'litellm-compatible', 'none'].includes(inlineCompletionModelProvider) && (React.createElement("div", { className: "model-config-section-column" },
|
|
1261
|
+
React.createElement("div", null, "Model"),
|
|
1262
|
+
![
|
|
1263
|
+
OPENAI_COMPATIBLE_INLINE_COMPLETION_MODEL_ID,
|
|
1264
|
+
LITELLM_COMPATIBLE_INLINE_COMPLETION_MODEL_ID
|
|
1265
|
+
].includes(inlineCompletionModel) && (React.createElement("div", null,
|
|
1266
|
+
React.createElement("select", { className: "jp-mod-styled", onChange: event => setInlineCompletionModel(event.target.value) }, inlineCompletionModels.map((model, index) => (React.createElement("option", { key: index, value: model.id, selected: model.id === inlineCompletionModel }, model.name))))))))),
|
|
1267
|
+
React.createElement("div", { className: "model-config-section-row" },
|
|
1268
|
+
React.createElement("div", { className: "model-config-section-column" }, inlineCompletionModelProperties.map((property, index) => (React.createElement("div", { className: "form-field-row", key: index },
|
|
1269
|
+
React.createElement("div", { className: "form-field-description" },
|
|
1270
|
+
property.name,
|
|
1271
|
+
" ",
|
|
1272
|
+
property.optional ? '(optional)' : ''),
|
|
1273
|
+
React.createElement("input", { name: "inline-completion-model-id-input", placeholder: property.description, className: "jp-mod-styled", spellCheck: false, value: property.value, onChange: event => onModelPropertyChange('inline-completion', property.id, event.target.value) }))))))))),
|
|
1274
|
+
React.createElement("div", { className: "config-dialog-footer" },
|
|
1275
|
+
React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: handleSaveClick },
|
|
1276
|
+
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, "Save")))));
|
|
1277
|
+
}
|