@notebook-intelligence/notebook-intelligence 2.3.2 → 2.4.0
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/README.md +1 -1
- package/lib/api.d.ts +5 -1
- package/lib/api.js +42 -7
- package/lib/chat-sidebar.d.ts +2 -0
- package/lib/chat-sidebar.js +32 -5
- package/lib/index.js +77 -0
- package/lib/tokens.d.ts +2 -1
- package/lib/tokens.js +1 -0
- package/package.json +1 -1
- package/src/api.ts +47 -9
- package/src/chat-sidebar.tsx +73 -6
- package/src/index.ts +102 -1
- package/src/tokens.ts +2 -1
- package/style/base.css +6 -1
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ for the frontend extension.
|
|
|
62
62
|
Notebook Intelligence can remember your GitHub Copilot login so that you don't need to re-login after a JupyterLab or system restart. Please be aware of the security implications of using this feature.
|
|
63
63
|
|
|
64
64
|
> [!CAUTION]
|
|
65
|
-
> If you configure NBI to remember your GitHub Copilot login, it will encrypt the token and store into a data file at `~/.jupyter/nbi-data.json`. You should never share this file with others as they can access your tokens.
|
|
65
|
+
> If you configure NBI to remember your GitHub Copilot login, it will encrypt the token and store into a data file at `~/.jupyter/nbi/user-data.json`. You should never share this file with others as they can access your tokens.
|
|
66
66
|
> Even though the token is encrypted, it is done so by using a default password and that's why it can be decrypted by others. In order to prevent that you can specify a custom password using the environment variable `NBI_GH_ACCESS_TOKEN_PASSWORD`.
|
|
67
67
|
|
|
68
68
|
```bash
|
package/lib/api.d.ts
CHANGED
|
@@ -12,10 +12,11 @@ export interface IDeviceVerificationInfo {
|
|
|
12
12
|
}
|
|
13
13
|
export declare class NBIConfig {
|
|
14
14
|
get userHomeDir(): string;
|
|
15
|
-
get
|
|
15
|
+
get userConfigDir(): string;
|
|
16
16
|
get llmProviders(): [any];
|
|
17
17
|
get chatModels(): [any];
|
|
18
18
|
get inlineCompletionModels(): [any];
|
|
19
|
+
get defaultChatMode(): string;
|
|
19
20
|
get chatModel(): any;
|
|
20
21
|
get inlineCompletionModel(): any;
|
|
21
22
|
get usingGitHubCopilotModel(): boolean;
|
|
@@ -32,6 +33,7 @@ export declare class NBIAPI {
|
|
|
32
33
|
static _messageReceived: Signal<unknown, any>;
|
|
33
34
|
static config: NBIConfig;
|
|
34
35
|
static configChanged: Signal<NBIConfig, void>;
|
|
36
|
+
static githubLoginStatusChanged: Signal<unknown, void>;
|
|
35
37
|
static initialize(): Promise<void>;
|
|
36
38
|
static initializeWebsocket(): Promise<void>;
|
|
37
39
|
static getLoginStatus(): GitHubCopilotLoginStatus;
|
|
@@ -43,6 +45,8 @@ export declare class NBIAPI {
|
|
|
43
45
|
static setConfig(config: any): Promise<void>;
|
|
44
46
|
static updateOllamaModelList(): Promise<void>;
|
|
45
47
|
static reloadMCPServerList(): Promise<any>;
|
|
48
|
+
static getMCPConfigFile(): Promise<any>;
|
|
49
|
+
static setMCPConfigFile(config: any): Promise<any>;
|
|
46
50
|
static chatRequest(messageId: string, chatId: string, prompt: string, language: string, filename: string, additionalContext: IContextItem[], chatMode: string, toolSelections: IToolSelections, responseEmitter: IChatCompletionResponseEmitter): Promise<void>;
|
|
47
51
|
static generateCode(chatId: string, prompt: string, prefix: string, suffix: string, existingCode: string, language: string, filename: string, responseEmitter: IChatCompletionResponseEmitter): Promise<void>;
|
|
48
52
|
static sendChatUserInput(messageId: string, data: any): Promise<void>;
|
package/lib/api.js
CHANGED
|
@@ -5,8 +5,7 @@ import { requestAPI } from './handler';
|
|
|
5
5
|
import { URLExt } from '@jupyterlab/coreutils';
|
|
6
6
|
import { UUID } from '@lumino/coreutils';
|
|
7
7
|
import { Signal } from '@lumino/signaling';
|
|
8
|
-
import { GITHUB_COPILOT_PROVIDER_ID, RequestDataType } from './tokens';
|
|
9
|
-
const LOGIN_STATUS_UPDATE_INTERVAL = 2000;
|
|
8
|
+
import { GITHUB_COPILOT_PROVIDER_ID, RequestDataType, BackendMessageType } from './tokens';
|
|
10
9
|
export var GitHubCopilotLoginStatus;
|
|
11
10
|
(function (GitHubCopilotLoginStatus) {
|
|
12
11
|
GitHubCopilotLoginStatus["NotLoggedIn"] = "NOT_LOGGED_IN";
|
|
@@ -23,8 +22,8 @@ export class NBIConfig {
|
|
|
23
22
|
get userHomeDir() {
|
|
24
23
|
return this.capabilities.user_home_dir;
|
|
25
24
|
}
|
|
26
|
-
get
|
|
27
|
-
return this.capabilities.
|
|
25
|
+
get userConfigDir() {
|
|
26
|
+
return this.capabilities.nbi_user_config_dir;
|
|
28
27
|
}
|
|
29
28
|
get llmProviders() {
|
|
30
29
|
return this.capabilities.llm_providers;
|
|
@@ -35,6 +34,9 @@ export class NBIConfig {
|
|
|
35
34
|
get inlineCompletionModels() {
|
|
36
35
|
return this.capabilities.inline_completion_models;
|
|
37
36
|
}
|
|
37
|
+
get defaultChatMode() {
|
|
38
|
+
return this.capabilities.default_chat_mode;
|
|
39
|
+
}
|
|
38
40
|
get chatModel() {
|
|
39
41
|
return this.capabilities.chat_model;
|
|
40
42
|
}
|
|
@@ -56,10 +58,15 @@ class NBIAPI {
|
|
|
56
58
|
static async initialize() {
|
|
57
59
|
await this.fetchCapabilities();
|
|
58
60
|
this.updateGitHubLoginStatus();
|
|
59
|
-
setInterval(() => {
|
|
60
|
-
this.updateGitHubLoginStatus();
|
|
61
|
-
}, LOGIN_STATUS_UPDATE_INTERVAL);
|
|
62
61
|
NBIAPI.initializeWebsocket();
|
|
62
|
+
this._messageReceived.connect((_, msg) => {
|
|
63
|
+
msg = JSON.parse(msg);
|
|
64
|
+
if (msg.type === BackendMessageType.GitHubCopilotLoginStatusChange) {
|
|
65
|
+
this.updateGitHubLoginStatus().then(() => {
|
|
66
|
+
this.githubLoginStatusChanged.emit();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
});
|
|
63
70
|
}
|
|
64
71
|
static async initializeWebsocket() {
|
|
65
72
|
const serverSettings = ServerConnection.makeSettings();
|
|
@@ -189,6 +196,33 @@ class NBIAPI {
|
|
|
189
196
|
});
|
|
190
197
|
});
|
|
191
198
|
}
|
|
199
|
+
static async getMCPConfigFile() {
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
requestAPI('mcp-config-file', { method: 'GET' })
|
|
202
|
+
.then(async (data) => {
|
|
203
|
+
resolve(data);
|
|
204
|
+
})
|
|
205
|
+
.catch(reason => {
|
|
206
|
+
console.error(`Failed to get MCP config file.\n${reason}`);
|
|
207
|
+
reject(reason);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
static async setMCPConfigFile(config) {
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
requestAPI('mcp-config-file', {
|
|
214
|
+
method: 'POST',
|
|
215
|
+
body: JSON.stringify(config)
|
|
216
|
+
})
|
|
217
|
+
.then(async (data) => {
|
|
218
|
+
resolve(data);
|
|
219
|
+
})
|
|
220
|
+
.catch(reason => {
|
|
221
|
+
console.error(`Failed to set MCP config file.\n${reason}`);
|
|
222
|
+
reject(reason);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
}
|
|
192
226
|
static async chatRequest(messageId, chatId, prompt, language, filename, additionalContext, chatMode, toolSelections, responseEmitter) {
|
|
193
227
|
this._messageReceived.connect((_, msg) => {
|
|
194
228
|
msg = JSON.parse(msg);
|
|
@@ -286,4 +320,5 @@ NBIAPI._deviceVerificationInfo = {
|
|
|
286
320
|
NBIAPI._messageReceived = new Signal(_a);
|
|
287
321
|
NBIAPI.config = new NBIConfig();
|
|
288
322
|
NBIAPI.configChanged = _a.config.changed;
|
|
323
|
+
NBIAPI.githubLoginStatusChanged = new Signal(_a);
|
|
289
324
|
export { NBIAPI };
|
package/lib/chat-sidebar.d.ts
CHANGED
|
@@ -76,7 +76,9 @@ export declare class GitHubCopilotLoginDialogBody extends ReactWidget {
|
|
|
76
76
|
export declare class ConfigurationDialogBody extends ReactWidget {
|
|
77
77
|
constructor(options: {
|
|
78
78
|
onSave: () => void;
|
|
79
|
+
onEditMCPConfigClicked: () => void;
|
|
79
80
|
});
|
|
80
81
|
render(): JSX.Element;
|
|
82
|
+
private _onEditMCPConfigClicked;
|
|
81
83
|
private _onSave;
|
|
82
84
|
}
|
package/lib/chat-sidebar.js
CHANGED
|
@@ -13,6 +13,7 @@ import copilotWarningSvgstr from '../style/icons/copilot-warning.svg';
|
|
|
13
13
|
import { VscSend, VscStopCircle, VscEye, VscEyeClosed, VscTriangleRight, VscTriangleDown, VscWarning, VscSettingsGear, VscPassFilled, VscTools, VscTrash } from 'react-icons/vsc';
|
|
14
14
|
import { MdOutlineCheckBoxOutlineBlank, MdCheckBox } from 'react-icons/md';
|
|
15
15
|
import { extractLLMGeneratedCode, isDarkTheme } from './utils';
|
|
16
|
+
import * as path from 'path';
|
|
16
17
|
const OPENAI_COMPATIBLE_CHAT_MODEL_ID = 'openai-compatible-chat-model';
|
|
17
18
|
const LITELLM_COMPATIBLE_CHAT_MODEL_ID = 'litellm-compatible-chat-model';
|
|
18
19
|
const OPENAI_COMPATIBLE_INLINE_COMPLETION_MODEL_ID = 'openai-compatible-inline-completion-model';
|
|
@@ -130,10 +131,11 @@ export class GitHubCopilotLoginDialogBody extends ReactWidget {
|
|
|
130
131
|
export class ConfigurationDialogBody extends ReactWidget {
|
|
131
132
|
constructor(options) {
|
|
132
133
|
super();
|
|
134
|
+
this._onEditMCPConfigClicked = options.onEditMCPConfigClicked;
|
|
133
135
|
this._onSave = options.onSave;
|
|
134
136
|
}
|
|
135
137
|
render() {
|
|
136
|
-
return React.createElement(ConfigurationDialogBodyComponent, { onSave: this._onSave });
|
|
138
|
+
return (React.createElement(ConfigurationDialogBodyComponent, { onEditMCPConfigClicked: this._onEditMCPConfigClicked, onSave: this._onSave }));
|
|
137
139
|
}
|
|
138
140
|
}
|
|
139
141
|
const answeredForms = new Map();
|
|
@@ -358,7 +360,7 @@ function SidebarComponent(props) {
|
|
|
358
360
|
const [activeDocumentInfo, setActiveDocumentInfo] = useState(null);
|
|
359
361
|
const [currentFileContextTitle, setCurrentFileContextTitle] = useState('');
|
|
360
362
|
const telemetryEmitter = props.getTelemetryEmitter();
|
|
361
|
-
const [chatMode, setChatMode] = useState(
|
|
363
|
+
const [chatMode, setChatMode] = useState(NBIAPI.config.defaultChatMode);
|
|
362
364
|
const [toolSelectionTitle, setToolSelectionTitle] = useState('Tool selection');
|
|
363
365
|
const [selectedToolCount, setSelectedToolCount] = useState(0);
|
|
364
366
|
const [notebookExecuteToolSelected, setNotebookExecuteToolSelected] = useState(false);
|
|
@@ -1508,6 +1510,7 @@ function ConfigurationDialogBodyComponent(props) {
|
|
|
1508
1510
|
const [mcpServerNames, setMcpServerNames] = useState(((_a = nbiConfig.toolConfig.mcpServers) === null || _a === void 0 ? void 0 : _a.map((server) => server.id)) || []);
|
|
1509
1511
|
const handleSaveClick = async () => {
|
|
1510
1512
|
const config = {
|
|
1513
|
+
default_chat_mode: defaultChatMode,
|
|
1511
1514
|
chat_model: {
|
|
1512
1515
|
provider: chatModelProvider,
|
|
1513
1516
|
model: chatModel,
|
|
@@ -1532,6 +1535,7 @@ function ConfigurationDialogBodyComponent(props) {
|
|
|
1532
1535
|
};
|
|
1533
1536
|
const [chatModelProvider, setChatModelProvider] = useState(nbiConfig.chatModel.provider || 'none');
|
|
1534
1537
|
const [inlineCompletionModelProvider, setInlineCompletionModelProvider] = useState(nbiConfig.inlineCompletionModel.provider || 'none');
|
|
1538
|
+
const [defaultChatMode, setDefaultChatMode] = useState(nbiConfig.defaultChatMode);
|
|
1535
1539
|
const [chatModel, setChatModel] = useState(nbiConfig.chatModel.model);
|
|
1536
1540
|
const [chatModelProperties, setChatModelProperties] = useState([]);
|
|
1537
1541
|
const [inlineCompletionModelProperties, setInlineCompletionModelProperties] = useState([]);
|
|
@@ -1608,6 +1612,16 @@ function ConfigurationDialogBodyComponent(props) {
|
|
|
1608
1612
|
}, []);
|
|
1609
1613
|
return (React.createElement("div", { className: "config-dialog" },
|
|
1610
1614
|
React.createElement("div", { className: "config-dialog-body" },
|
|
1615
|
+
React.createElement("div", { className: "model-config-section" },
|
|
1616
|
+
React.createElement("div", { className: "model-config-section-header" }, "Default chat mode"),
|
|
1617
|
+
React.createElement("div", { className: "model-config-section-body" },
|
|
1618
|
+
React.createElement("div", { className: "model-config-section-row" },
|
|
1619
|
+
React.createElement("div", { className: "model-config-section-column" },
|
|
1620
|
+
React.createElement("div", null,
|
|
1621
|
+
React.createElement("select", { className: "jp-mod-styled", value: defaultChatMode, onChange: event => setDefaultChatMode(event.target.value) },
|
|
1622
|
+
React.createElement("option", { value: "ask" }, "Ask"),
|
|
1623
|
+
React.createElement("option", { value: "agent" }, "Agent")))),
|
|
1624
|
+
React.createElement("div", { className: "model-config-section-column" }, " ")))),
|
|
1611
1625
|
React.createElement("div", { className: "model-config-section" },
|
|
1612
1626
|
React.createElement("div", { className: "model-config-section-header" }, "Chat model"),
|
|
1613
1627
|
React.createElement("div", { className: "model-config-section-body" },
|
|
@@ -1689,8 +1703,10 @@ function ConfigurationDialogBodyComponent(props) {
|
|
|
1689
1703
|
"Remember my GitHub Copilot access token")))))),
|
|
1690
1704
|
React.createElement("div", { className: "model-config-section" },
|
|
1691
1705
|
React.createElement("div", { className: "model-config-section-header" },
|
|
1692
|
-
"MCP Servers
|
|
1706
|
+
"MCP Servers (",
|
|
1693
1707
|
mcpServerNames.length,
|
|
1708
|
+
") [",
|
|
1709
|
+
React.createElement("a", { href: "javascript:void(0)", onClick: props.onEditMCPConfigClicked }, "edit"),
|
|
1694
1710
|
"]"),
|
|
1695
1711
|
React.createElement("div", { className: "model-config-section-body" },
|
|
1696
1712
|
React.createElement("div", { className: "model-config-section-row" },
|
|
@@ -1706,10 +1722,21 @@ function ConfigurationDialogBodyComponent(props) {
|
|
|
1706
1722
|
React.createElement("div", { className: "model-config-section-row" },
|
|
1707
1723
|
React.createElement("div", { className: "model-config-section-column" },
|
|
1708
1724
|
React.createElement("span", { className: "user-code-span", onClick: () => {
|
|
1709
|
-
navigator.clipboard.writeText(NBIAPI.config.
|
|
1725
|
+
navigator.clipboard.writeText(path.join(NBIAPI.config.userConfigDir, 'config.json'));
|
|
1726
|
+
return true;
|
|
1727
|
+
} },
|
|
1728
|
+
path.join(NBIAPI.config.userConfigDir, 'config.json'),
|
|
1729
|
+
' ',
|
|
1730
|
+
React.createElement("span", { className: "copy-icon", dangerouslySetInnerHTML: { __html: copySvgstr } }))))),
|
|
1731
|
+
React.createElement("div", { className: "model-config-section-header" }, "MCP config file path"),
|
|
1732
|
+
React.createElement("div", { className: "model-config-section-body" },
|
|
1733
|
+
React.createElement("div", { className: "model-config-section-row" },
|
|
1734
|
+
React.createElement("div", { className: "model-config-section-column" },
|
|
1735
|
+
React.createElement("span", { className: "user-code-span", onClick: () => {
|
|
1736
|
+
navigator.clipboard.writeText(path.join(NBIAPI.config.userConfigDir, 'mcp.json'));
|
|
1710
1737
|
return true;
|
|
1711
1738
|
} },
|
|
1712
|
-
NBIAPI.config.
|
|
1739
|
+
path.join(NBIAPI.config.userConfigDir, 'mcp.json'),
|
|
1713
1740
|
' ',
|
|
1714
1741
|
React.createElement("span", { className: "copy-icon", dangerouslySetInnerHTML: { __html: copySvgstr } }))))))),
|
|
1715
1742
|
React.createElement("div", { className: "config-dialog-footer" },
|
package/lib/index.js
CHANGED
|
@@ -50,6 +50,7 @@ var CommandIDs;
|
|
|
50
50
|
CommandIDs.runCellAtIndex = 'notebook-intelligence:run-cell-at-index';
|
|
51
51
|
CommandIDs.getCurrentFileContent = 'notebook-intelligence:get-current-file-content';
|
|
52
52
|
CommandIDs.setCurrentFileContent = 'notebook-intelligence:set-current-file-content';
|
|
53
|
+
CommandIDs.openMCPConfigEditor = 'notebook-intelligence:open-mcp-config-editor';
|
|
53
54
|
})(CommandIDs || (CommandIDs = {}));
|
|
54
55
|
const DOCUMENT_WATCH_INTERVAL = 1000;
|
|
55
56
|
const MAX_TOKENS = 4096;
|
|
@@ -356,6 +357,67 @@ class TelemetryEmitter {
|
|
|
356
357
|
});
|
|
357
358
|
}
|
|
358
359
|
}
|
|
360
|
+
class MCPConfigEditor {
|
|
361
|
+
constructor(docManager) {
|
|
362
|
+
this._docWidget = null;
|
|
363
|
+
this._tmpMCPConfigFilename = 'nbi.mcp.temp.json';
|
|
364
|
+
this._isOpen = false;
|
|
365
|
+
this._docManager = docManager;
|
|
366
|
+
}
|
|
367
|
+
async open() {
|
|
368
|
+
const contents = new ContentsManager();
|
|
369
|
+
const newJSONFile = await contents.newUntitled({
|
|
370
|
+
ext: '.json'
|
|
371
|
+
});
|
|
372
|
+
const mcpConfig = await NBIAPI.getMCPConfigFile();
|
|
373
|
+
try {
|
|
374
|
+
await contents.delete(this._tmpMCPConfigFilename);
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
// ignore
|
|
378
|
+
}
|
|
379
|
+
await contents.save(newJSONFile.path, {
|
|
380
|
+
content: JSON.stringify(mcpConfig, null, 2),
|
|
381
|
+
format: 'text',
|
|
382
|
+
type: 'file'
|
|
383
|
+
});
|
|
384
|
+
await contents.rename(newJSONFile.path, this._tmpMCPConfigFilename);
|
|
385
|
+
this._docWidget = this._docManager.openOrReveal(this._tmpMCPConfigFilename, 'Editor');
|
|
386
|
+
this._addListeners();
|
|
387
|
+
// tab closed
|
|
388
|
+
this._docWidget.disposed.connect((_, args) => {
|
|
389
|
+
this._removeListeners();
|
|
390
|
+
contents.delete(this._tmpMCPConfigFilename);
|
|
391
|
+
});
|
|
392
|
+
this._isOpen = true;
|
|
393
|
+
}
|
|
394
|
+
close() {
|
|
395
|
+
if (!this._isOpen) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
this._isOpen = false;
|
|
399
|
+
this._docWidget.dispose();
|
|
400
|
+
this._docWidget = null;
|
|
401
|
+
}
|
|
402
|
+
get isOpen() {
|
|
403
|
+
return this._isOpen;
|
|
404
|
+
}
|
|
405
|
+
_addListeners() {
|
|
406
|
+
this._docWidget.context.model.stateChanged.connect(this._onStateChanged, this);
|
|
407
|
+
}
|
|
408
|
+
_removeListeners() {
|
|
409
|
+
this._docWidget.context.model.stateChanged.disconnect(this._onStateChanged, this);
|
|
410
|
+
}
|
|
411
|
+
_onStateChanged(model, args) {
|
|
412
|
+
if (args.name === 'dirty' && args.newValue === false) {
|
|
413
|
+
this._onSave();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
async _onSave() {
|
|
417
|
+
const mcpConfig = this._docWidget.context.model.toJSON();
|
|
418
|
+
await NBIAPI.setMCPConfigFile(mcpConfig);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
359
421
|
/**
|
|
360
422
|
* Initialization data for the @notebook-intelligence/notebook-intelligence extension.
|
|
361
423
|
*/
|
|
@@ -392,6 +454,7 @@ const plugin = {
|
|
|
392
454
|
};
|
|
393
455
|
await NBIAPI.initialize();
|
|
394
456
|
let openPopover = null;
|
|
457
|
+
let mcpConfigEditor = null;
|
|
395
458
|
completionManager.registerInlineProvider(new NBIInlineCompletionProvider(telemetryEmitter));
|
|
396
459
|
if (settingRegistry) {
|
|
397
460
|
settingRegistry
|
|
@@ -866,6 +929,10 @@ const plugin = {
|
|
|
866
929
|
onSave: () => {
|
|
867
930
|
dialog === null || dialog === void 0 ? void 0 : dialog.dispose();
|
|
868
931
|
NBIAPI.fetchCapabilities();
|
|
932
|
+
},
|
|
933
|
+
onEditMCPConfigClicked: () => {
|
|
934
|
+
dialog === null || dialog === void 0 ? void 0 : dialog.dispose();
|
|
935
|
+
app.commands.execute('notebook-intelligence:open-mcp-config-editor');
|
|
869
936
|
}
|
|
870
937
|
});
|
|
871
938
|
dialog = new Dialog({
|
|
@@ -878,6 +945,16 @@ const plugin = {
|
|
|
878
945
|
dialog.launch();
|
|
879
946
|
}
|
|
880
947
|
});
|
|
948
|
+
app.commands.addCommand(CommandIDs.openMCPConfigEditor, {
|
|
949
|
+
label: 'Open MCP Config Editor',
|
|
950
|
+
execute: args => {
|
|
951
|
+
if (mcpConfigEditor && mcpConfigEditor.isOpen) {
|
|
952
|
+
mcpConfigEditor.close();
|
|
953
|
+
}
|
|
954
|
+
mcpConfigEditor = new MCPConfigEditor(docManager);
|
|
955
|
+
mcpConfigEditor.open();
|
|
956
|
+
}
|
|
957
|
+
});
|
|
881
958
|
palette.addItem({
|
|
882
959
|
command: CommandIDs.openConfigurationDialog,
|
|
883
960
|
category: 'Notebook Intelligence'
|
package/lib/tokens.d.ts
CHANGED
|
@@ -25,7 +25,8 @@ export declare enum RequestDataType {
|
|
|
25
25
|
export declare enum BackendMessageType {
|
|
26
26
|
StreamMessage = "stream-message",
|
|
27
27
|
StreamEnd = "stream-end",
|
|
28
|
-
RunUICommand = "run-ui-command"
|
|
28
|
+
RunUICommand = "run-ui-command",
|
|
29
|
+
GitHubCopilotLoginStatusChange = "github-copilot-login-status-change"
|
|
29
30
|
}
|
|
30
31
|
export declare enum ResponseStreamDataType {
|
|
31
32
|
LLMRaw = "llm-raw",
|
package/lib/tokens.js
CHANGED
|
@@ -16,6 +16,7 @@ export var BackendMessageType;
|
|
|
16
16
|
BackendMessageType["StreamMessage"] = "stream-message";
|
|
17
17
|
BackendMessageType["StreamEnd"] = "stream-end";
|
|
18
18
|
BackendMessageType["RunUICommand"] = "run-ui-command";
|
|
19
|
+
BackendMessageType["GitHubCopilotLoginStatusChange"] = "github-copilot-login-status-change";
|
|
19
20
|
})(BackendMessageType || (BackendMessageType = {}));
|
|
20
21
|
export var ResponseStreamDataType;
|
|
21
22
|
(function (ResponseStreamDataType) {
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -12,11 +12,10 @@ import {
|
|
|
12
12
|
IContextItem,
|
|
13
13
|
ITelemetryEvent,
|
|
14
14
|
IToolSelections,
|
|
15
|
-
RequestDataType
|
|
15
|
+
RequestDataType,
|
|
16
|
+
BackendMessageType
|
|
16
17
|
} from './tokens';
|
|
17
18
|
|
|
18
|
-
const LOGIN_STATUS_UPDATE_INTERVAL = 2000;
|
|
19
|
-
|
|
20
19
|
export enum GitHubCopilotLoginStatus {
|
|
21
20
|
NotLoggedIn = 'NOT_LOGGED_IN',
|
|
22
21
|
ActivatingDevice = 'ACTIVATING_DEVICE',
|
|
@@ -34,8 +33,8 @@ export class NBIConfig {
|
|
|
34
33
|
return this.capabilities.user_home_dir;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
get
|
|
38
|
-
return this.capabilities.
|
|
36
|
+
get userConfigDir(): string {
|
|
37
|
+
return this.capabilities.nbi_user_config_dir;
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
get llmProviders(): [any] {
|
|
@@ -50,6 +49,10 @@ export class NBIConfig {
|
|
|
50
49
|
return this.capabilities.inline_completion_models;
|
|
51
50
|
}
|
|
52
51
|
|
|
52
|
+
get defaultChatMode(): string {
|
|
53
|
+
return this.capabilities.default_chat_mode;
|
|
54
|
+
}
|
|
55
|
+
|
|
53
56
|
get chatModel(): any {
|
|
54
57
|
return this.capabilities.chat_model;
|
|
55
58
|
}
|
|
@@ -89,16 +92,22 @@ export class NBIAPI {
|
|
|
89
92
|
static _messageReceived = new Signal<unknown, any>(this);
|
|
90
93
|
static config = new NBIConfig();
|
|
91
94
|
static configChanged = this.config.changed;
|
|
95
|
+
static githubLoginStatusChanged = new Signal<unknown, void>(this);
|
|
92
96
|
|
|
93
97
|
static async initialize() {
|
|
94
98
|
await this.fetchCapabilities();
|
|
95
99
|
this.updateGitHubLoginStatus();
|
|
96
100
|
|
|
97
|
-
setInterval(() => {
|
|
98
|
-
this.updateGitHubLoginStatus();
|
|
99
|
-
}, LOGIN_STATUS_UPDATE_INTERVAL);
|
|
100
|
-
|
|
101
101
|
NBIAPI.initializeWebsocket();
|
|
102
|
+
|
|
103
|
+
this._messageReceived.connect((_, msg) => {
|
|
104
|
+
msg = JSON.parse(msg);
|
|
105
|
+
if (msg.type === BackendMessageType.GitHubCopilotLoginStatusChange) {
|
|
106
|
+
this.updateGitHubLoginStatus().then(() => {
|
|
107
|
+
this.githubLoginStatusChanged.emit();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
});
|
|
102
111
|
}
|
|
103
112
|
|
|
104
113
|
static async initializeWebsocket() {
|
|
@@ -250,6 +259,35 @@ export class NBIAPI {
|
|
|
250
259
|
});
|
|
251
260
|
}
|
|
252
261
|
|
|
262
|
+
static async getMCPConfigFile(): Promise<any> {
|
|
263
|
+
return new Promise<any>((resolve, reject) => {
|
|
264
|
+
requestAPI<any>('mcp-config-file', { method: 'GET' })
|
|
265
|
+
.then(async data => {
|
|
266
|
+
resolve(data);
|
|
267
|
+
})
|
|
268
|
+
.catch(reason => {
|
|
269
|
+
console.error(`Failed to get MCP config file.\n${reason}`);
|
|
270
|
+
reject(reason);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
static async setMCPConfigFile(config: any): Promise<any> {
|
|
276
|
+
return new Promise<any>((resolve, reject) => {
|
|
277
|
+
requestAPI<any>('mcp-config-file', {
|
|
278
|
+
method: 'POST',
|
|
279
|
+
body: JSON.stringify(config)
|
|
280
|
+
})
|
|
281
|
+
.then(async data => {
|
|
282
|
+
resolve(data);
|
|
283
|
+
})
|
|
284
|
+
.catch(reason => {
|
|
285
|
+
console.error(`Failed to set MCP config file.\n${reason}`);
|
|
286
|
+
reject(reason);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
253
291
|
static async chatRequest(
|
|
254
292
|
messageId: string,
|
|
255
293
|
chatId: string,
|
package/src/chat-sidebar.tsx
CHANGED
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
import { MdOutlineCheckBoxOutlineBlank, MdCheckBox } from 'react-icons/md';
|
|
57
57
|
|
|
58
58
|
import { extractLLMGeneratedCode, isDarkTheme } from './utils';
|
|
59
|
+
import * as path from 'path';
|
|
59
60
|
|
|
60
61
|
const OPENAI_COMPATIBLE_CHAT_MODEL_ID = 'openai-compatible-chat-model';
|
|
61
62
|
const LITELLM_COMPATIBLE_CHAT_MODEL_ID = 'litellm-compatible-chat-model';
|
|
@@ -264,16 +265,26 @@ export class GitHubCopilotLoginDialogBody extends ReactWidget {
|
|
|
264
265
|
}
|
|
265
266
|
|
|
266
267
|
export class ConfigurationDialogBody extends ReactWidget {
|
|
267
|
-
constructor(options: {
|
|
268
|
+
constructor(options: {
|
|
269
|
+
onSave: () => void;
|
|
270
|
+
onEditMCPConfigClicked: () => void;
|
|
271
|
+
}) {
|
|
268
272
|
super();
|
|
269
273
|
|
|
274
|
+
this._onEditMCPConfigClicked = options.onEditMCPConfigClicked;
|
|
270
275
|
this._onSave = options.onSave;
|
|
271
276
|
}
|
|
272
277
|
|
|
273
278
|
render(): JSX.Element {
|
|
274
|
-
return
|
|
279
|
+
return (
|
|
280
|
+
<ConfigurationDialogBodyComponent
|
|
281
|
+
onEditMCPConfigClicked={this._onEditMCPConfigClicked}
|
|
282
|
+
onSave={this._onSave}
|
|
283
|
+
/>
|
|
284
|
+
);
|
|
275
285
|
}
|
|
276
286
|
|
|
287
|
+
private _onEditMCPConfigClicked: () => void;
|
|
277
288
|
private _onSave: () => void;
|
|
278
289
|
}
|
|
279
290
|
|
|
@@ -725,7 +736,8 @@ function SidebarComponent(props: any) {
|
|
|
725
736
|
useState<IActiveDocumentInfo | null>(null);
|
|
726
737
|
const [currentFileContextTitle, setCurrentFileContextTitle] = useState('');
|
|
727
738
|
const telemetryEmitter: ITelemetryEmitter = props.getTelemetryEmitter();
|
|
728
|
-
const [chatMode, setChatMode] = useState(
|
|
739
|
+
const [chatMode, setChatMode] = useState(NBIAPI.config.defaultChatMode);
|
|
740
|
+
|
|
729
741
|
const [toolSelectionTitle, setToolSelectionTitle] =
|
|
730
742
|
useState('Tool selection');
|
|
731
743
|
const [selectedToolCount, setSelectedToolCount] = useState(0);
|
|
@@ -2497,6 +2509,7 @@ function ConfigurationDialogBodyComponent(props: any) {
|
|
|
2497
2509
|
|
|
2498
2510
|
const handleSaveClick = async () => {
|
|
2499
2511
|
const config: any = {
|
|
2512
|
+
default_chat_mode: defaultChatMode,
|
|
2500
2513
|
chat_model: {
|
|
2501
2514
|
provider: chatModelProvider,
|
|
2502
2515
|
model: chatModel,
|
|
@@ -2531,6 +2544,9 @@ function ConfigurationDialogBodyComponent(props: any) {
|
|
|
2531
2544
|
);
|
|
2532
2545
|
const [inlineCompletionModelProvider, setInlineCompletionModelProvider] =
|
|
2533
2546
|
useState(nbiConfig.inlineCompletionModel.provider || 'none');
|
|
2547
|
+
const [defaultChatMode, setDefaultChatMode] = useState<string>(
|
|
2548
|
+
nbiConfig.defaultChatMode
|
|
2549
|
+
);
|
|
2534
2550
|
const [chatModel, setChatModel] = useState<string>(nbiConfig.chatModel.model);
|
|
2535
2551
|
const [chatModelProperties, setChatModelProperties] = useState<any[]>([]);
|
|
2536
2552
|
const [inlineCompletionModelProperties, setInlineCompletionModelProperties] =
|
|
@@ -2629,6 +2645,27 @@ function ConfigurationDialogBodyComponent(props: any) {
|
|
|
2629
2645
|
return (
|
|
2630
2646
|
<div className="config-dialog">
|
|
2631
2647
|
<div className="config-dialog-body">
|
|
2648
|
+
<div className="model-config-section">
|
|
2649
|
+
<div className="model-config-section-header">Default chat mode</div>
|
|
2650
|
+
<div className="model-config-section-body">
|
|
2651
|
+
<div className="model-config-section-row">
|
|
2652
|
+
<div className="model-config-section-column">
|
|
2653
|
+
<div>
|
|
2654
|
+
<select
|
|
2655
|
+
className="jp-mod-styled"
|
|
2656
|
+
value={defaultChatMode}
|
|
2657
|
+
onChange={event => setDefaultChatMode(event.target.value)}
|
|
2658
|
+
>
|
|
2659
|
+
<option value="ask">Ask</option>
|
|
2660
|
+
<option value="agent">Agent</option>
|
|
2661
|
+
</select>
|
|
2662
|
+
</div>
|
|
2663
|
+
</div>
|
|
2664
|
+
<div className="model-config-section-column"> </div>
|
|
2665
|
+
</div>
|
|
2666
|
+
</div>
|
|
2667
|
+
</div>
|
|
2668
|
+
|
|
2632
2669
|
<div className="model-config-section">
|
|
2633
2670
|
<div className="model-config-section-header">Chat model</div>
|
|
2634
2671
|
<div className="model-config-section-body">
|
|
@@ -2889,7 +2926,11 @@ function ConfigurationDialogBodyComponent(props: any) {
|
|
|
2889
2926
|
|
|
2890
2927
|
<div className="model-config-section">
|
|
2891
2928
|
<div className="model-config-section-header">
|
|
2892
|
-
MCP Servers
|
|
2929
|
+
MCP Servers ({mcpServerNames.length}) [
|
|
2930
|
+
<a href="javascript:void(0)" onClick={props.onEditMCPConfigClicked}>
|
|
2931
|
+
edit
|
|
2932
|
+
</a>
|
|
2933
|
+
]
|
|
2893
2934
|
</div>
|
|
2894
2935
|
<div className="model-config-section-body">
|
|
2895
2936
|
<div className="model-config-section-row">
|
|
@@ -2927,11 +2968,37 @@ function ConfigurationDialogBodyComponent(props: any) {
|
|
|
2927
2968
|
<span
|
|
2928
2969
|
className="user-code-span"
|
|
2929
2970
|
onClick={() => {
|
|
2930
|
-
navigator.clipboard.writeText(
|
|
2971
|
+
navigator.clipboard.writeText(
|
|
2972
|
+
path.join(NBIAPI.config.userConfigDir, 'config.json')
|
|
2973
|
+
);
|
|
2974
|
+
return true;
|
|
2975
|
+
}}
|
|
2976
|
+
>
|
|
2977
|
+
{path.join(NBIAPI.config.userConfigDir, 'config.json')}{' '}
|
|
2978
|
+
<span
|
|
2979
|
+
className="copy-icon"
|
|
2980
|
+
dangerouslySetInnerHTML={{ __html: copySvgstr }}
|
|
2981
|
+
></span>
|
|
2982
|
+
</span>
|
|
2983
|
+
</div>
|
|
2984
|
+
</div>
|
|
2985
|
+
</div>
|
|
2986
|
+
<div className="model-config-section-header">
|
|
2987
|
+
MCP config file path
|
|
2988
|
+
</div>
|
|
2989
|
+
<div className="model-config-section-body">
|
|
2990
|
+
<div className="model-config-section-row">
|
|
2991
|
+
<div className="model-config-section-column">
|
|
2992
|
+
<span
|
|
2993
|
+
className="user-code-span"
|
|
2994
|
+
onClick={() => {
|
|
2995
|
+
navigator.clipboard.writeText(
|
|
2996
|
+
path.join(NBIAPI.config.userConfigDir, 'mcp.json')
|
|
2997
|
+
);
|
|
2931
2998
|
return true;
|
|
2932
2999
|
}}
|
|
2933
3000
|
>
|
|
2934
|
-
{NBIAPI.config.
|
|
3001
|
+
{path.join(NBIAPI.config.userConfigDir, 'mcp.json')}{' '}
|
|
2935
3002
|
<span
|
|
2936
3003
|
className="copy-icon"
|
|
2937
3004
|
dangerouslySetInnerHTML={{ __html: copySvgstr }}
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from '@jupyterlab/application';
|
|
8
8
|
|
|
9
9
|
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
10
|
-
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
10
|
+
import { DocumentWidget, IDocumentWidget } from '@jupyterlab/docregistry';
|
|
11
11
|
|
|
12
12
|
import { Dialog, ICommandPalette } from '@jupyterlab/apputils';
|
|
13
13
|
import { IMainMenu } from '@jupyterlab/mainmenu';
|
|
@@ -124,6 +124,8 @@ namespace CommandIDs {
|
|
|
124
124
|
'notebook-intelligence:get-current-file-content';
|
|
125
125
|
export const setCurrentFileContent =
|
|
126
126
|
'notebook-intelligence:set-current-file-content';
|
|
127
|
+
export const openMCPConfigEditor =
|
|
128
|
+
'notebook-intelligence:open-mcp-config-editor';
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
const DOCUMENT_WATCH_INTERVAL = 1000;
|
|
@@ -514,6 +516,87 @@ class TelemetryEmitter implements ITelemetryEmitter {
|
|
|
514
516
|
private _listeners: Set<ITelemetryListener> = new Set<ITelemetryListener>();
|
|
515
517
|
}
|
|
516
518
|
|
|
519
|
+
class MCPConfigEditor {
|
|
520
|
+
constructor(docManager: IDocumentManager) {
|
|
521
|
+
this._docManager = docManager;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async open() {
|
|
525
|
+
const contents = new ContentsManager();
|
|
526
|
+
const newJSONFile = await contents.newUntitled({
|
|
527
|
+
ext: '.json'
|
|
528
|
+
});
|
|
529
|
+
const mcpConfig = await NBIAPI.getMCPConfigFile();
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
await contents.delete(this._tmpMCPConfigFilename);
|
|
533
|
+
} catch (error) {
|
|
534
|
+
// ignore
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
await contents.save(newJSONFile.path, {
|
|
538
|
+
content: JSON.stringify(mcpConfig, null, 2),
|
|
539
|
+
format: 'text',
|
|
540
|
+
type: 'file'
|
|
541
|
+
});
|
|
542
|
+
await contents.rename(newJSONFile.path, this._tmpMCPConfigFilename);
|
|
543
|
+
this._docWidget = this._docManager.openOrReveal(
|
|
544
|
+
this._tmpMCPConfigFilename,
|
|
545
|
+
'Editor'
|
|
546
|
+
);
|
|
547
|
+
this._addListeners();
|
|
548
|
+
// tab closed
|
|
549
|
+
this._docWidget.disposed.connect((_, args) => {
|
|
550
|
+
this._removeListeners();
|
|
551
|
+
contents.delete(this._tmpMCPConfigFilename);
|
|
552
|
+
});
|
|
553
|
+
this._isOpen = true;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
close() {
|
|
557
|
+
if (!this._isOpen) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
this._isOpen = false;
|
|
561
|
+
this._docWidget.dispose();
|
|
562
|
+
this._docWidget = null;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
get isOpen(): boolean {
|
|
566
|
+
return this._isOpen;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
private _addListeners() {
|
|
570
|
+
this._docWidget.context.model.stateChanged.connect(
|
|
571
|
+
this._onStateChanged,
|
|
572
|
+
this
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
private _removeListeners() {
|
|
577
|
+
this._docWidget.context.model.stateChanged.disconnect(
|
|
578
|
+
this._onStateChanged,
|
|
579
|
+
this
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
private _onStateChanged(model: any, args: any) {
|
|
584
|
+
if (args.name === 'dirty' && args.newValue === false) {
|
|
585
|
+
this._onSave();
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
private async _onSave() {
|
|
590
|
+
const mcpConfig = this._docWidget.context.model.toJSON();
|
|
591
|
+
await NBIAPI.setMCPConfigFile(mcpConfig);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
private _docManager: IDocumentManager;
|
|
595
|
+
private _docWidget: IDocumentWidget = null;
|
|
596
|
+
private _tmpMCPConfigFilename = 'nbi.mcp.temp.json';
|
|
597
|
+
private _isOpen = false;
|
|
598
|
+
}
|
|
599
|
+
|
|
517
600
|
/**
|
|
518
601
|
* Initialization data for the @notebook-intelligence/notebook-intelligence extension.
|
|
519
602
|
*/
|
|
@@ -567,6 +650,7 @@ const plugin: JupyterFrontEndPlugin<INotebookIntelligence> = {
|
|
|
567
650
|
await NBIAPI.initialize();
|
|
568
651
|
|
|
569
652
|
let openPopover: InlinePromptWidget | null = null;
|
|
653
|
+
let mcpConfigEditor: MCPConfigEditor | null = null;
|
|
570
654
|
|
|
571
655
|
completionManager.registerInlineProvider(
|
|
572
656
|
new NBIInlineCompletionProvider(telemetryEmitter)
|
|
@@ -1152,6 +1236,12 @@ const plugin: JupyterFrontEndPlugin<INotebookIntelligence> = {
|
|
|
1152
1236
|
onSave: () => {
|
|
1153
1237
|
dialog?.dispose();
|
|
1154
1238
|
NBIAPI.fetchCapabilities();
|
|
1239
|
+
},
|
|
1240
|
+
onEditMCPConfigClicked: () => {
|
|
1241
|
+
dialog?.dispose();
|
|
1242
|
+
app.commands.execute(
|
|
1243
|
+
'notebook-intelligence:open-mcp-config-editor'
|
|
1244
|
+
);
|
|
1155
1245
|
}
|
|
1156
1246
|
});
|
|
1157
1247
|
dialog = new Dialog({
|
|
@@ -1166,6 +1256,17 @@ const plugin: JupyterFrontEndPlugin<INotebookIntelligence> = {
|
|
|
1166
1256
|
}
|
|
1167
1257
|
});
|
|
1168
1258
|
|
|
1259
|
+
app.commands.addCommand(CommandIDs.openMCPConfigEditor, {
|
|
1260
|
+
label: 'Open MCP Config Editor',
|
|
1261
|
+
execute: args => {
|
|
1262
|
+
if (mcpConfigEditor && mcpConfigEditor.isOpen) {
|
|
1263
|
+
mcpConfigEditor.close();
|
|
1264
|
+
}
|
|
1265
|
+
mcpConfigEditor = new MCPConfigEditor(docManager);
|
|
1266
|
+
mcpConfigEditor.open();
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1169
1270
|
palette.addItem({
|
|
1170
1271
|
command: CommandIDs.openConfigurationDialog,
|
|
1171
1272
|
category: 'Notebook Intelligence'
|
package/src/tokens.ts
CHANGED
|
@@ -31,7 +31,8 @@ export enum RequestDataType {
|
|
|
31
31
|
export enum BackendMessageType {
|
|
32
32
|
StreamMessage = 'stream-message',
|
|
33
33
|
StreamEnd = 'stream-end',
|
|
34
|
-
RunUICommand = 'run-ui-command'
|
|
34
|
+
RunUICommand = 'run-ui-command',
|
|
35
|
+
GitHubCopilotLoginStatusChange = 'github-copilot-login-status-change'
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
export enum ResponseStreamDataType {
|
package/style/base.css
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
.sidebar-user-input {
|
|
33
|
-
height:
|
|
33
|
+
height: auto;
|
|
34
34
|
padding: 5px;
|
|
35
35
|
display: flex;
|
|
36
36
|
flex-direction: column;
|
|
@@ -67,6 +67,11 @@
|
|
|
67
67
|
border: none;
|
|
68
68
|
resize: none;
|
|
69
69
|
background: none;
|
|
70
|
+
/* stylelint-disable */
|
|
71
|
+
field-sizing: content;
|
|
72
|
+
/* stylelint-enable */
|
|
73
|
+
min-height: 36px;
|
|
74
|
+
max-height: 200px;
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
.sidebar-user-input .user-input-context-row {
|