@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.
@@ -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
+ }