@notebook-intelligence/notebook-intelligence 1.2.5 → 1.3.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 +166 -76
- package/lib/api.js +10 -0
- package/lib/chat-sidebar.js +16 -13
- package/lib/index.js +4 -2
- package/lib/tokens.d.ts +1 -0
- package/lib/tokens.js +1 -0
- package/package.json +1 -1
- package/src/api.ts +12 -0
- package/src/chat-sidebar.tsx +18 -17
- package/src/index.ts +4 -2
- package/src/tokens.ts +1 -0
- package/style/base.css +22 -0
package/README.md
CHANGED
|
@@ -21,11 +21,19 @@ See blog posts for features and usage.
|
|
|
21
21
|
|
|
22
22
|
<img src="media/copilot-chat.gif" alt="Chat interface" width=600 />
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
## Installation
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
NBI requires JupyterLab >= 4.0.0. To install the extension, run the command below and restart JupyterLab.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
```bash
|
|
29
|
+
pip install notebook-intelligence
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Configuration options
|
|
33
|
+
|
|
34
|
+
### Configuring LLM Provider and models
|
|
35
|
+
|
|
36
|
+
You can configure the model provider and model options using the Notebook Intelligence Settings dialog. You can access this dialog from JupyterLab Settings menu -> Notebook Intelligence Settings, using `/settings` command in NBI Chat or by using the command palette. For more details, see the [blog post](https://notebook-intelligence.github.io/notebook-intelligence/blog/2025/03/05/support-for-any-llm-provider.html).
|
|
29
37
|
|
|
30
38
|
<img src="media/provider-list.png" alt="Settings dialog" width=500 />
|
|
31
39
|
|
|
@@ -35,28 +43,6 @@ This extension is composed of a Python package named `notebook_intelligence`
|
|
|
35
43
|
for the server extension and a NPM package named `@notebook-intelligence/notebook-intelligence`
|
|
36
44
|
for the frontend extension.
|
|
37
45
|
|
|
38
|
-
## Requirements
|
|
39
|
-
|
|
40
|
-
- JupyterLab >= 4.0.0
|
|
41
|
-
|
|
42
|
-
## Install
|
|
43
|
-
|
|
44
|
-
To install the extension, execute:
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
pip install notebook_intelligence
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Uninstall
|
|
51
|
-
|
|
52
|
-
To remove the extension, execute:
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
pip uninstall notebook_intelligence
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## Configuration options
|
|
59
|
-
|
|
60
46
|
### Remembering GitHub Copilot login
|
|
61
47
|
|
|
62
48
|
Notebook Intelligence uses system keyring to store the GitHub access tokens. If your stored access token fails to login (due to expiration or other reasons), you will be prompted to relogin on the UI. If you run into issues with this feature, check the Jupyter server logs and the [keyring package](https://github.com/jaraco/keyring) documentation.
|
|
@@ -73,79 +59,183 @@ Once you set it to remember, it will continue to remember even if you skip `--No
|
|
|
73
59
|
jupyter lab --NotebookIntelligence.github_access_token=forget
|
|
74
60
|
```
|
|
75
61
|
|
|
76
|
-
|
|
62
|
+
### Configuration files
|
|
77
63
|
|
|
78
|
-
|
|
79
|
-
that the server extension is enabled:
|
|
64
|
+
NBI saves configuration at `~/.jupyter/nbi-config.json`. It also supports environment wide base configuration at `<env-prefix>/share/jupyter/nbi-config.json`. Organizations can ship default configuration at this environment wide config path. User's changes will be stored as overrides at `~/.jupyter/nbi-config.json`.
|
|
80
65
|
|
|
81
|
-
|
|
82
|
-
jupyter server extension list
|
|
83
|
-
```
|
|
66
|
+
These config files are used for saving LLM provider, model and MCP configuration. Note that API keys you enter for your custom LLM providers will also be stored in these config files.
|
|
84
67
|
|
|
85
|
-
|
|
86
|
-
|
|
68
|
+
> [!IMPORTANT]
|
|
69
|
+
> Note that updating nbi-config.json manually requires restarting JupyterLab to take effect.
|
|
87
70
|
|
|
88
|
-
|
|
89
|
-
jupyter labextension list
|
|
90
|
-
```
|
|
71
|
+
### Model Context Protocol ([MCP](https://modelcontextprotocol.io)) Support
|
|
91
72
|
|
|
92
|
-
|
|
73
|
+
NBI seamlessly integrates with MCP servers. It supports servers with both Standard Input/Output (stdio) and Server-Sent Events (SSE) transports. The MCP support is limited to server tools at the moment.
|
|
93
74
|
|
|
94
|
-
|
|
75
|
+
You can easily add MCP servers to NBI by editing the configuration file [nbi-config.json](#configuration-files). Simply add a key "mcp" and "mcpServers" under it as shown below.
|
|
95
76
|
|
|
96
|
-
|
|
77
|
+
> [!NOTE]
|
|
78
|
+
> Using MCP servers requires an LLM model with tool calling capabilities. All of the GitHub Copilot models provided in NBI support this feature. If you are using other providers make sure you choose a tool calling capable model.
|
|
97
79
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
`yarn` or `npm` in lieu of `jlpm` below.
|
|
80
|
+
> [!CAUTION]
|
|
81
|
+
> Note that most MCP servers are run on the same computer as your JupyterLab installation and they can make irreversible changes to your computer and/or access private data. Make sure that you only install MCP servers from trusted sources.
|
|
101
82
|
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"chat_model": {
|
|
86
|
+
...
|
|
87
|
+
},
|
|
88
|
+
...<other configuration>,
|
|
89
|
+
|
|
90
|
+
"mcp": {
|
|
91
|
+
"mcpServers": {
|
|
92
|
+
"filesystem": {
|
|
93
|
+
"command": "npx",
|
|
94
|
+
"args": [
|
|
95
|
+
"-y",
|
|
96
|
+
"@modelcontextprotocol/server-filesystem",
|
|
97
|
+
"/Users/mbektas/mcp-test"
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
113
103
|
```
|
|
114
104
|
|
|
115
|
-
|
|
105
|
+
This will automatically create a new chat participant in NBI and you can access it by starting your prompts with `@mcp`. Use `@mcp /info` prompt to get information on the tools provided by the MCP servers you configured. This chat participant will have access all the tools provided by the servers you configure.
|
|
116
106
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
107
|
+
<img src="media/mcp-prompt.png" alt="Settings dialog" width=600 />
|
|
108
|
+
|
|
109
|
+
By default, each tool call to MCP servers will require approval. If you would like to auto approve tools, you can do so by using the `"alwaysAllow": []` configuration key in the nbi-config.json. Simply list the names of tools.
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
"mcpServers": {
|
|
113
|
+
"filesystem": {
|
|
114
|
+
"command": "npx",
|
|
115
|
+
"args": [
|
|
116
|
+
"-y",
|
|
117
|
+
"@modelcontextprotocol/server-filesystem",
|
|
118
|
+
"/Users/mbektas/mcp-test"
|
|
119
|
+
],
|
|
120
|
+
"alwaysAllow": ["list_allowed_directories", "list_directory"]
|
|
121
|
+
},
|
|
122
|
+
}
|
|
122
123
|
```
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
For servers with stdio transport, you can also set additional environment variables by using the `env` key. Environment variables are specified as key value pairs.
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
"mcpServers": {
|
|
129
|
+
"servername": {
|
|
130
|
+
"command": "",
|
|
131
|
+
"args": [],
|
|
132
|
+
"env": {
|
|
133
|
+
"ENV_VAR_NAME": "ENV_VAR_VALUE"
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
```
|
|
125
138
|
|
|
126
|
-
|
|
139
|
+
Below is an example of a server configuration with SSE transport. For SSE transport servers, you can also specify headers to be sent as part of the requests.
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
"mcpServers": {
|
|
143
|
+
"remoterservername": {
|
|
144
|
+
"url": "http://127.0.0.1:8080/sse",
|
|
145
|
+
"headers": {
|
|
146
|
+
"Authorization": "Bearer mysecrettoken"
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
```
|
|
127
151
|
|
|
128
|
-
|
|
129
|
-
|
|
152
|
+
If you have multiple servers configured but you would like to disable some for a while, you can do so by using the `disabled` key. `servername2` will be diabled and not available in `@mcp` chat participant.
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
"mcpServers": {
|
|
156
|
+
"servername1": {
|
|
157
|
+
"command": "",
|
|
158
|
+
"args": [],
|
|
159
|
+
},
|
|
160
|
+
"servername2": {
|
|
161
|
+
"command": "",
|
|
162
|
+
"args": [],
|
|
163
|
+
"disabled": true
|
|
164
|
+
},
|
|
165
|
+
}
|
|
130
166
|
```
|
|
131
167
|
|
|
132
|
-
|
|
168
|
+
#### Grouping MCP servers
|
|
169
|
+
|
|
170
|
+
When you integrate multiple MCP servers to NBI, all of their tools will be available under the same chat participant `@mcp`. However, this may not be ideal in many situations. You may want to group certain servers and their tools based on their functionality. NBI lets you do that easily by configuring MCP chat participants. You can list the servers for each custom participant. If there are any unassigned MCP servers, then they will be used the default `@mcp` chat participant.
|
|
171
|
+
|
|
172
|
+
Below is an example of creating a custom MCP participant. This configuration results in two chat participants `@mcp-fs` with `filesytem` MC server tools and `@mcp` with `servername1` and `servername1` MCP server tools.
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"chat_model": {
|
|
177
|
+
...
|
|
178
|
+
},
|
|
179
|
+
...<other configuration>,
|
|
180
|
+
|
|
181
|
+
"mcp": {
|
|
182
|
+
"mcpServers": {
|
|
183
|
+
"filesystem": {
|
|
184
|
+
"command": "npx",
|
|
185
|
+
"args": [
|
|
186
|
+
"-y",
|
|
187
|
+
"@modelcontextprotocol/server-filesystem",
|
|
188
|
+
"/Users/mbektas/mcp-test"
|
|
189
|
+
]
|
|
190
|
+
},
|
|
191
|
+
"servername1": {
|
|
192
|
+
"command": "",
|
|
193
|
+
"args": [],
|
|
194
|
+
},
|
|
195
|
+
"servername2": {
|
|
196
|
+
"command": "",
|
|
197
|
+
"args": [],
|
|
198
|
+
"disabled": true
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
"participants": {
|
|
202
|
+
"fs": {
|
|
203
|
+
"name": "MCP - File system",
|
|
204
|
+
"servers": ["filesystem"]
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
133
210
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
211
|
+
#### Using NBI tools within MCP chat participants
|
|
212
|
+
|
|
213
|
+
NBI allows you to access built-in tools from an MCP participant. You can do that by adding the list of built in NBI tools to your MCP participant configuration. The built-in tools available to MCP are `create_new_notebook`, `add_markdown_cell_to_notebook`, `add_code_cell_to_notebook`. Below is an example that integrates all these tools to MCP participant `@mcp-fs`.
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
"participants": {
|
|
217
|
+
"fs": {
|
|
218
|
+
"name": "MCP - File system",
|
|
219
|
+
"servers": ["filesystem"],
|
|
220
|
+
"nbiTools": [
|
|
221
|
+
"create_new_notebook",
|
|
222
|
+
"add_markdown_cell_to_notebook",
|
|
223
|
+
"add_code_cell_to_notebook"
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
}
|
|
138
227
|
```
|
|
139
228
|
|
|
140
|
-
|
|
141
|
-
command. To find its location, you can run `jupyter labextension list` to figure out where the `labextensions`
|
|
142
|
-
folder is located. Then you can remove the symlink named `@notebook-intelligence/notebook-intelligence` within that folder.
|
|
229
|
+
This chat participant will allow you to run example prompts like below.
|
|
143
230
|
|
|
144
|
-
|
|
231
|
+
```
|
|
232
|
+
@mcp-fs list the directories I have access to.
|
|
233
|
+
```
|
|
145
234
|
|
|
146
|
-
|
|
235
|
+
```
|
|
236
|
+
@mcp-fs add a code cell which demonstrates ipywidgets Button to this notebook.
|
|
237
|
+
```
|
|
147
238
|
|
|
148
|
-
###
|
|
239
|
+
### Developer documentation
|
|
149
240
|
|
|
150
|
-
|
|
151
|
-
- [B00TK1D/copilot-api for GitHub auth and inline completions](https://github.com/B00TK1D/copilot-api)
|
|
241
|
+
For building locally and contributing see the [developer documentatation](CONTRIBUTING.md).
|
package/lib/api.js
CHANGED
|
@@ -56,6 +56,16 @@ class NBIAPI {
|
|
|
56
56
|
this._webSocket.onmessage = msg => {
|
|
57
57
|
this._messageReceived.emit(msg.data);
|
|
58
58
|
};
|
|
59
|
+
this._webSocket.onerror = msg => {
|
|
60
|
+
console.error(`Websocket error: ${msg}. Closing...`);
|
|
61
|
+
this._webSocket.close();
|
|
62
|
+
};
|
|
63
|
+
this._webSocket.onclose = msg => {
|
|
64
|
+
console.log(`Websocket is closed: ${msg.reason}. Reconnecting...`);
|
|
65
|
+
setTimeout(() => {
|
|
66
|
+
NBIAPI.initializeWebsocket();
|
|
67
|
+
}, 1000);
|
|
68
|
+
};
|
|
59
69
|
}
|
|
60
70
|
static getLoginStatus() {
|
|
61
71
|
return this._loginStatus;
|
package/lib/chat-sidebar.js
CHANGED
|
@@ -149,11 +149,11 @@ function ChatResponse(props) {
|
|
|
149
149
|
const notebookPath = event.target.dataset['ref'];
|
|
150
150
|
props.openFile(notebookPath);
|
|
151
151
|
};
|
|
152
|
-
const markFormConfirmed = (
|
|
153
|
-
answeredForms.set(
|
|
152
|
+
const markFormConfirmed = (contentId) => {
|
|
153
|
+
answeredForms.set(contentId, 'confirmed');
|
|
154
154
|
};
|
|
155
|
-
const markFormCanceled = (
|
|
156
|
-
answeredForms.set(
|
|
155
|
+
const markFormCanceled = (contentId) => {
|
|
156
|
+
answeredForms.set(contentId, 'canceled');
|
|
157
157
|
};
|
|
158
158
|
const runCommand = (commandId, args) => {
|
|
159
159
|
props.getApp().commands.execute(commandId, args);
|
|
@@ -258,15 +258,18 @@ function ChatResponse(props) {
|
|
|
258
258
|
React.createElement("div", { className: "chat-reasoning-content-text" },
|
|
259
259
|
React.createElement(MarkdownRenderer, { key: `key-${index}`, getApp: props.getApp, getActiveDocumentInfo: props.getActiveDocumentInfo }, item.reasoningContent)))),
|
|
260
260
|
React.createElement(MarkdownRenderer, { key: `key-${index}`, getApp: props.getApp, getActiveDocumentInfo: props.getActiveDocumentInfo }, item.content)));
|
|
261
|
+
case ResponseStreamDataType.Image:
|
|
262
|
+
return (React.createElement("div", { className: "chat-response-img", key: `key-${index}` },
|
|
263
|
+
React.createElement("img", { src: item.content })));
|
|
261
264
|
case ResponseStreamDataType.HTMLFrame:
|
|
262
265
|
return (React.createElement(ChatResponseHTMLFrame, { index: index, source: item.content.source, height: item.content.height }));
|
|
263
266
|
case ResponseStreamDataType.Button:
|
|
264
|
-
return (React.createElement("div", { className: "chat-response-button" },
|
|
265
|
-
React.createElement("button", {
|
|
267
|
+
return (React.createElement("div", { className: "chat-response-button", key: `key-${index}` },
|
|
268
|
+
React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: () => runCommand(item.content.commandId, item.content.args) },
|
|
266
269
|
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, item.content.title))));
|
|
267
270
|
case ResponseStreamDataType.Anchor:
|
|
268
|
-
return (React.createElement("div", { className: "chat-response-anchor" },
|
|
269
|
-
React.createElement("a", {
|
|
271
|
+
return (React.createElement("div", { className: "chat-response-anchor", key: `key-${index}` },
|
|
272
|
+
React.createElement("a", { href: item.content.uri, target: "_blank" }, item.content.title)));
|
|
270
273
|
case ResponseStreamDataType.Progress:
|
|
271
274
|
// show only if no more message available
|
|
272
275
|
return index === groupedContents.length - 1 ? (React.createElement("div", { key: `key-${index}` },
|
|
@@ -426,7 +429,7 @@ function SidebarComponent(props) {
|
|
|
426
429
|
from: 'user',
|
|
427
430
|
contents: [
|
|
428
431
|
{
|
|
429
|
-
id:
|
|
432
|
+
id: UUID.uuid4(),
|
|
430
433
|
type: ResponseStreamDataType.Markdown,
|
|
431
434
|
content: prompt,
|
|
432
435
|
created: new Date()
|
|
@@ -491,7 +494,7 @@ function SidebarComponent(props) {
|
|
|
491
494
|
if (delta['nbiContent']) {
|
|
492
495
|
const nbiContent = delta['nbiContent'];
|
|
493
496
|
contents.push({
|
|
494
|
-
id:
|
|
497
|
+
id: UUID.uuid4(),
|
|
495
498
|
type: nbiContent.type,
|
|
496
499
|
content: nbiContent.content,
|
|
497
500
|
created: new Date(response.created)
|
|
@@ -504,7 +507,7 @@ function SidebarComponent(props) {
|
|
|
504
507
|
return;
|
|
505
508
|
}
|
|
506
509
|
contents.push({
|
|
507
|
-
id:
|
|
510
|
+
id: UUID.uuid4(),
|
|
508
511
|
type: ResponseStreamDataType.MarkdownPart,
|
|
509
512
|
content: responseMessage,
|
|
510
513
|
created: new Date(response.created)
|
|
@@ -826,14 +829,14 @@ function SidebarComponent(props) {
|
|
|
826
829
|
}, [ghLoginStatus]);
|
|
827
830
|
return (React.createElement("div", { className: "sidebar" },
|
|
828
831
|
React.createElement("div", { className: "sidebar-header" },
|
|
829
|
-
React.createElement("div", { className: "sidebar-title" }, "
|
|
832
|
+
React.createElement("div", { className: "sidebar-title" }, "Notebook Intelligence")),
|
|
830
833
|
!chatEnabled && !ghLoginRequired && (React.createElement("div", { className: "sidebar-login-info" },
|
|
831
834
|
"Chat is disabled as you don't have a model configured.",
|
|
832
835
|
React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: handleConfigurationClick },
|
|
833
836
|
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, "Configure models")))),
|
|
834
837
|
ghLoginRequired && (React.createElement("div", { className: "sidebar-login-info" },
|
|
835
838
|
React.createElement("div", null, "You are not logged in to GitHub Copilot. Please login now to activate chat."),
|
|
836
|
-
React.createElement("div",
|
|
839
|
+
React.createElement("div", { className: "sidebar-login-buttons" },
|
|
837
840
|
React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: handleLoginClick },
|
|
838
841
|
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, "Login to GitHub Copilot")),
|
|
839
842
|
React.createElement("button", { className: "jp-Dialog-button jp-mod-reject jp-mod-styled", onClick: handleConfigurationClick },
|
package/lib/index.js
CHANGED
|
@@ -160,7 +160,9 @@ class ActiveDocumentWatcher {
|
|
|
160
160
|
}
|
|
161
161
|
else {
|
|
162
162
|
const dw = activeWidget;
|
|
163
|
-
|
|
163
|
+
const content = (_b = (_a = dw === null || dw === void 0 ? void 0 : dw.context) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.toString();
|
|
164
|
+
const maxContext = 0.5 * MAX_TOKENS;
|
|
165
|
+
return content.substring(0, maxContext);
|
|
164
166
|
}
|
|
165
167
|
}
|
|
166
168
|
static getCurrentCellContents() {
|
|
@@ -417,7 +419,7 @@ const plugin = {
|
|
|
417
419
|
};
|
|
418
420
|
const panel = new Panel();
|
|
419
421
|
panel.id = 'notebook-intelligence-tab';
|
|
420
|
-
panel.title.caption = '
|
|
422
|
+
panel.title.caption = 'Notebook Intelligence';
|
|
421
423
|
const sidebarIcon = new LabIcon({
|
|
422
424
|
name: 'ui-components:palette',
|
|
423
425
|
svgstr: sparklesSvgstr
|
package/lib/tokens.d.ts
CHANGED
package/lib/tokens.js
CHANGED
|
@@ -22,6 +22,7 @@ export var ResponseStreamDataType;
|
|
|
22
22
|
ResponseStreamDataType["LLMRaw"] = "llm-raw";
|
|
23
23
|
ResponseStreamDataType["Markdown"] = "markdown";
|
|
24
24
|
ResponseStreamDataType["MarkdownPart"] = "markdown-part";
|
|
25
|
+
ResponseStreamDataType["Image"] = "image";
|
|
25
26
|
ResponseStreamDataType["HTMLFrame"] = "html-frame";
|
|
26
27
|
ResponseStreamDataType["Button"] = "button";
|
|
27
28
|
ResponseStreamDataType["Anchor"] = "anchor";
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -96,6 +96,18 @@ export class NBIAPI {
|
|
|
96
96
|
this._webSocket.onmessage = msg => {
|
|
97
97
|
this._messageReceived.emit(msg.data);
|
|
98
98
|
};
|
|
99
|
+
|
|
100
|
+
this._webSocket.onerror = msg => {
|
|
101
|
+
console.error(`Websocket error: ${msg}. Closing...`);
|
|
102
|
+
this._webSocket.close();
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
this._webSocket.onclose = msg => {
|
|
106
|
+
console.log(`Websocket is closed: ${msg.reason}. Reconnecting...`);
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
NBIAPI.initializeWebsocket();
|
|
109
|
+
}, 1000);
|
|
110
|
+
};
|
|
99
111
|
}
|
|
100
112
|
|
|
101
113
|
static getLoginStatus(): GitHubCopilotLoginStatus {
|
package/src/chat-sidebar.tsx
CHANGED
|
@@ -312,11 +312,11 @@ function ChatResponse(props: any) {
|
|
|
312
312
|
props.openFile(notebookPath);
|
|
313
313
|
};
|
|
314
314
|
|
|
315
|
-
const markFormConfirmed = (
|
|
316
|
-
answeredForms.set(
|
|
315
|
+
const markFormConfirmed = (contentId: string) => {
|
|
316
|
+
answeredForms.set(contentId, 'confirmed');
|
|
317
317
|
};
|
|
318
|
-
const markFormCanceled = (
|
|
319
|
-
answeredForms.set(
|
|
318
|
+
const markFormCanceled = (contentId: string) => {
|
|
319
|
+
answeredForms.set(contentId, 'canceled');
|
|
320
320
|
};
|
|
321
321
|
|
|
322
322
|
const runCommand = (commandId: string, args: any) => {
|
|
@@ -477,6 +477,12 @@ function ChatResponse(props: any) {
|
|
|
477
477
|
</MarkdownRenderer>
|
|
478
478
|
</>
|
|
479
479
|
);
|
|
480
|
+
case ResponseStreamDataType.Image:
|
|
481
|
+
return (
|
|
482
|
+
<div className="chat-response-img" key={`key-${index}`}>
|
|
483
|
+
<img src={item.content} />
|
|
484
|
+
</div>
|
|
485
|
+
);
|
|
480
486
|
case ResponseStreamDataType.HTMLFrame:
|
|
481
487
|
return (
|
|
482
488
|
<ChatResponseHTMLFrame
|
|
@@ -487,9 +493,8 @@ function ChatResponse(props: any) {
|
|
|
487
493
|
);
|
|
488
494
|
case ResponseStreamDataType.Button:
|
|
489
495
|
return (
|
|
490
|
-
<div className="chat-response-button">
|
|
496
|
+
<div className="chat-response-button" key={`key-${index}`}>
|
|
491
497
|
<button
|
|
492
|
-
key={`key-${index}`}
|
|
493
498
|
className="jp-Dialog-button jp-mod-accept jp-mod-styled"
|
|
494
499
|
onClick={() =>
|
|
495
500
|
runCommand(item.content.commandId, item.content.args)
|
|
@@ -503,12 +508,8 @@ function ChatResponse(props: any) {
|
|
|
503
508
|
);
|
|
504
509
|
case ResponseStreamDataType.Anchor:
|
|
505
510
|
return (
|
|
506
|
-
<div className="chat-response-anchor">
|
|
507
|
-
<a
|
|
508
|
-
key={`key-${index}`}
|
|
509
|
-
href={item.content.uri}
|
|
510
|
-
target="_blank"
|
|
511
|
-
>
|
|
511
|
+
<div className="chat-response-anchor" key={`key-${index}`}>
|
|
512
|
+
<a href={item.content.uri} target="_blank">
|
|
512
513
|
{item.content.title}
|
|
513
514
|
</a>
|
|
514
515
|
</div>
|
|
@@ -761,7 +762,7 @@ function SidebarComponent(props: any) {
|
|
|
761
762
|
from: 'user',
|
|
762
763
|
contents: [
|
|
763
764
|
{
|
|
764
|
-
id:
|
|
765
|
+
id: UUID.uuid4(),
|
|
765
766
|
type: ResponseStreamDataType.Markdown,
|
|
766
767
|
content: prompt,
|
|
767
768
|
created: new Date()
|
|
@@ -839,7 +840,7 @@ function SidebarComponent(props: any) {
|
|
|
839
840
|
if (delta['nbiContent']) {
|
|
840
841
|
const nbiContent = delta['nbiContent'];
|
|
841
842
|
contents.push({
|
|
842
|
-
id:
|
|
843
|
+
id: UUID.uuid4(),
|
|
843
844
|
type: nbiContent.type,
|
|
844
845
|
content: nbiContent.content,
|
|
845
846
|
created: new Date(response.created)
|
|
@@ -851,7 +852,7 @@ function SidebarComponent(props: any) {
|
|
|
851
852
|
return;
|
|
852
853
|
}
|
|
853
854
|
contents.push({
|
|
854
|
-
id:
|
|
855
|
+
id: UUID.uuid4(),
|
|
855
856
|
type: ResponseStreamDataType.MarkdownPart,
|
|
856
857
|
content: responseMessage,
|
|
857
858
|
created: new Date(response.created)
|
|
@@ -1253,7 +1254,7 @@ function SidebarComponent(props: any) {
|
|
|
1253
1254
|
return (
|
|
1254
1255
|
<div className="sidebar">
|
|
1255
1256
|
<div className="sidebar-header">
|
|
1256
|
-
<div className="sidebar-title">
|
|
1257
|
+
<div className="sidebar-title">Notebook Intelligence</div>
|
|
1257
1258
|
</div>
|
|
1258
1259
|
{!chatEnabled && !ghLoginRequired && (
|
|
1259
1260
|
<div className="sidebar-login-info">
|
|
@@ -1272,7 +1273,7 @@ function SidebarComponent(props: any) {
|
|
|
1272
1273
|
You are not logged in to GitHub Copilot. Please login now to
|
|
1273
1274
|
activate chat.
|
|
1274
1275
|
</div>
|
|
1275
|
-
<div>
|
|
1276
|
+
<div className="sidebar-login-buttons">
|
|
1276
1277
|
<button
|
|
1277
1278
|
className="jp-Dialog-button jp-mod-accept jp-mod-styled"
|
|
1278
1279
|
onClick={handleLoginClick}
|
package/src/index.ts
CHANGED
|
@@ -250,7 +250,9 @@ class ActiveDocumentWatcher {
|
|
|
250
250
|
}
|
|
251
251
|
} else {
|
|
252
252
|
const dw = activeWidget as DocumentWidget;
|
|
253
|
-
|
|
253
|
+
const content = dw?.context?.model?.toString();
|
|
254
|
+
const maxContext = 0.5 * MAX_TOKENS;
|
|
255
|
+
return content.substring(0, maxContext);
|
|
254
256
|
}
|
|
255
257
|
}
|
|
256
258
|
|
|
@@ -602,7 +604,7 @@ const plugin: JupyterFrontEndPlugin<INotebookIntelligence> = {
|
|
|
602
604
|
|
|
603
605
|
const panel = new Panel();
|
|
604
606
|
panel.id = 'notebook-intelligence-tab';
|
|
605
|
-
panel.title.caption = '
|
|
607
|
+
panel.title.caption = 'Notebook Intelligence';
|
|
606
608
|
const sidebarIcon = new LabIcon({
|
|
607
609
|
name: 'ui-components:palette',
|
|
608
610
|
svgstr: sparklesSvgstr
|
package/src/tokens.ts
CHANGED
package/style/base.css
CHANGED
|
@@ -146,6 +146,10 @@ pre:has(.code-block-header) {
|
|
|
146
146
|
height: 18px;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
.chat-response-img img {
|
|
150
|
+
max-width: 100%;
|
|
151
|
+
}
|
|
152
|
+
|
|
149
153
|
.chat-message-from-icon-default.dark img {
|
|
150
154
|
filter: invert(100%);
|
|
151
155
|
}
|
|
@@ -224,6 +228,17 @@ pre:has(.code-block-header) {
|
|
|
224
228
|
text-decoration: underline;
|
|
225
229
|
}
|
|
226
230
|
|
|
231
|
+
.sidebar-login-buttons {
|
|
232
|
+
display: flex;
|
|
233
|
+
flex-direction: column;
|
|
234
|
+
align-items: center;
|
|
235
|
+
gap: 10px;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.sidebar-login-buttons .jp-Dialog-button.jp-mod-styled {
|
|
239
|
+
margin-right: 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
227
242
|
.sidebar-greeting {
|
|
228
243
|
padding: 5px;
|
|
229
244
|
font-size: 14px;
|
|
@@ -266,6 +281,13 @@ pre:has(.code-block-header) {
|
|
|
266
281
|
line-height: 20px;
|
|
267
282
|
}
|
|
268
283
|
|
|
284
|
+
.chat-confirmation-form button {
|
|
285
|
+
height: 24px;
|
|
286
|
+
line-height: 24px;
|
|
287
|
+
padding: 0 6px;
|
|
288
|
+
margin-top: 2px;
|
|
289
|
+
}
|
|
290
|
+
|
|
269
291
|
.user-input-autocomplete {
|
|
270
292
|
display: flex;
|
|
271
293
|
background-color: var(--jp-layout-color2);
|