@notebook-intelligence/notebook-intelligence 1.2.4 → 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 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
- [![Github Actions Status](https://github.com/notebook-intelligence/notebook-intelligence/workflows/Build/badge.svg)](https://github.com/notebook-intelligence/notebook-intelligence/actions/workflows/build.yml)
24
+ ## Installation
25
25
 
26
- ## Configuring LLM Provider and models
26
+ NBI requires JupyterLab >= 4.0.0. To install the extension, run the command below and restart JupyterLab.
27
27
 
28
- 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 Copilot 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).
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
- ## Troubleshoot
62
+ ### Configuration files
77
63
 
78
- If you are seeing the frontend extension, but it is not working, check
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
- ```bash
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
- If the server extension is installed and enabled, but you are not seeing
86
- the frontend extension, check the frontend extension is installed:
68
+ > [!IMPORTANT]
69
+ > Note that updating nbi-config.json manually requires restarting JupyterLab to take effect.
87
70
 
88
- ```bash
89
- jupyter labextension list
90
- ```
71
+ ### Model Context Protocol ([MCP](https://modelcontextprotocol.io)) Support
91
72
 
92
- ## Contributing
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
- ### Development install
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
- Note: You will need NodeJS to build the extension package.
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
- The `jlpm` command is JupyterLab's pinned version of
99
- [yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use
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
- ```bash
103
- # Clone the repo to your local environment
104
- # Change directory to the notebook_intelligence directory
105
- # Install package in development mode
106
- pip install -e "."
107
- # Link your development version of the extension with JupyterLab
108
- jupyter labextension develop . --overwrite
109
- # Server extension must be manually installed in develop mode
110
- jupyter server extension enable notebook_intelligence
111
- # Rebuild extension Typescript source after making changes
112
- jlpm build
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
- You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension.
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
- ```bash
118
- # Watch the source directory in one terminal, automatically rebuilding when needed
119
- jlpm watch
120
- # Run JupyterLab in another terminal
121
- jupyter lab
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
- With the watch command running, every saved change will immediately be built locally and available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt).
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
- By default, the `jlpm build` command generates the source maps for this extension to make it easier to debug using the browser dev tools. To also generate source maps for the JupyterLab core extensions, you can run the following command:
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
- ```bash
129
- jupyter lab build --minimize=False
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
- ### Development uninstall
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
- ```bash
135
- # Server extension must be manually disabled in develop mode
136
- jupyter server extension disable notebook_intelligence
137
- pip uninstall notebook_intelligence
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
- In development mode, you will also need to remove the symlink created by `jupyter labextension develop`
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
- ### Packaging the extension
231
+ ```
232
+ @mcp-fs list the directories I have access to.
233
+ ```
145
234
 
146
- See [RELEASE](RELEASE.md)
235
+ ```
236
+ @mcp-fs add a code cell which demonstrates ipywidgets Button to this notebook.
237
+ ```
147
238
 
148
- ### Resources I used as reference
239
+ ### Developer documentation
149
240
 
150
- - [Copilot Internals blog post](https://thakkarparth007.github.io/copilot-explorer/posts/copilot-internals.html)
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;
@@ -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 = (messageId) => {
153
- answeredForms.set(messageId, 'confirmed');
152
+ const markFormConfirmed = (contentId) => {
153
+ answeredForms.set(contentId, 'confirmed');
154
154
  };
155
- const markFormCanceled = (messageId) => {
156
- answeredForms.set(messageId, 'canceled');
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", { key: `key-${index}`, className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: () => runCommand(item.content.commandId, item.content.args) },
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", { key: `key-${index}`, href: item.content.uri, target: "_blank" }, item.content.title)));
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: lastMessageId.current,
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: response.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: response.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" }, "Copilot Chat")),
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", null,
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
- return (_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();
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 = 'Copilot Chat';
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
@@ -31,6 +31,7 @@ export declare enum ResponseStreamDataType {
31
31
  LLMRaw = "llm-raw",
32
32
  Markdown = "markdown",
33
33
  MarkdownPart = "markdown-part",
34
+ Image = "image",
34
35
  HTMLFrame = "html-frame",
35
36
  Button = "button",
36
37
  Anchor = "anchor",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notebook-intelligence/notebook-intelligence",
3
- "version": "1.2.4",
3
+ "version": "1.3.0",
4
4
  "description": "AI coding assistant for JupyterLab",
5
5
  "keywords": [
6
6
  "AI",
@@ -123,7 +123,12 @@
123
123
  "extension": true,
124
124
  "outputDir": "notebook_intelligence/labextension",
125
125
  "schemaDir": "schema",
126
- "webpackConfig": "./webpack.config.js"
126
+ "webpackConfig": "./webpack.config.js",
127
+ "sharedPackages": {
128
+ "@notebook-intelligence/notebook-intelligence": {
129
+ "singleton": true
130
+ }
131
+ }
127
132
  },
128
133
  "eslintIgnore": [
129
134
  "node_modules",
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 {
@@ -312,11 +312,11 @@ function ChatResponse(props: any) {
312
312
  props.openFile(notebookPath);
313
313
  };
314
314
 
315
- const markFormConfirmed = (messageId: string) => {
316
- answeredForms.set(messageId, 'confirmed');
315
+ const markFormConfirmed = (contentId: string) => {
316
+ answeredForms.set(contentId, 'confirmed');
317
317
  };
318
- const markFormCanceled = (messageId: string) => {
319
- answeredForms.set(messageId, 'canceled');
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: lastMessageId.current,
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: response.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: response.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">Copilot Chat</div>
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
- return dw?.context?.model?.toString();
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 = 'Copilot Chat';
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
@@ -38,6 +38,7 @@ export enum ResponseStreamDataType {
38
38
  LLMRaw = 'llm-raw',
39
39
  Markdown = 'markdown',
40
40
  MarkdownPart = 'markdown-part',
41
+ Image = 'image',
41
42
  HTMLFrame = 'html-frame',
42
43
  Button = 'button',
43
44
  Anchor = 'anchor',
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);