@notebook-intelligence/notebook-intelligence 1.3.1 → 1.3.3

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
@@ -45,19 +45,21 @@ for the frontend extension.
45
45
 
46
46
  ### Remembering GitHub Copilot login
47
47
 
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.
48
+ 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.
49
49
 
50
- To let Notebook Intelligence remember your GitHub access token after you logged in:
50
+ > [!CAUTION]
51
+ > 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.
52
+ > 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`.
51
53
 
52
54
  ```bash
53
- jupyter lab --NotebookIntelligence.github_access_token=remember
55
+ NBI_GH_ACCESS_TOKEN_PASSWORD=my_custom_password
54
56
  ```
55
57
 
56
- Once you set it to remember, it will continue to remember even if you skip `--NotebookIntelligence.github_access_token` at following launches. In order to forget the GitHub access token stored:
58
+ To let Notebook Intelligence remember your GitHub access token, go to Notebook Intelligence Settings dialog and check the option `Remember my GitHub Copilot access token` as shown below.
57
59
 
58
- ```bash
59
- jupyter lab --NotebookIntelligence.github_access_token=forget
60
- ```
60
+ <img src="media/remember-gh-access-token.png" alt="Remember access token" width=500 />
61
+
62
+ If your stored access token fails to login (due to expiration or other reasons), you will be prompted to relogin on the UI.
61
63
 
62
64
  ### Configuration files
63
65
 
package/lib/api.d.ts CHANGED
@@ -17,6 +17,7 @@ export declare class NBIConfig {
17
17
  get chatModel(): any;
18
18
  get inlineCompletionModel(): any;
19
19
  get usingGitHubCopilotModel(): boolean;
20
+ get storeGitHubAccessToken(): boolean;
20
21
  capabilities: any;
21
22
  chatParticipants: IChatParticipant[];
22
23
  changed: Signal<this, void>;
package/lib/api.js CHANGED
@@ -39,6 +39,9 @@ export class NBIConfig {
39
39
  return (this.chatModel.provider === GITHUB_COPILOT_PROVIDER_ID ||
40
40
  this.inlineCompletionModel.provider === GITHUB_COPILOT_PROVIDER_ID);
41
41
  }
42
+ get storeGitHubAccessToken() {
43
+ return this.capabilities.store_github_access_token === true;
44
+ }
42
45
  }
43
46
  class NBIAPI {
44
47
  static async initialize() {
@@ -9,7 +9,7 @@ import { MarkdownRenderer } from './markdown-renderer';
9
9
  import copySvgstr from '../style/icons/copy.svg';
10
10
  import copilotSvgstr from '../style/icons/copilot.svg';
11
11
  import copilotWarningSvgstr from '../style/icons/copilot-warning.svg';
12
- import { VscSend, VscStopCircle, VscEye, VscEyeClosed, VscTriangleRight, VscTriangleDown } from 'react-icons/vsc';
12
+ import { VscSend, VscStopCircle, VscEye, VscEyeClosed, VscTriangleRight, VscTriangleDown, VscWarning } from 'react-icons/vsc';
13
13
  import { extractLLMGeneratedCode, isDarkTheme } from './utils';
14
14
  const OPENAI_COMPATIBLE_CHAT_MODEL_ID = 'openai-compatible-chat-model';
15
15
  const LITELLM_COMPATIBLE_CHAT_MODEL_ID = 'litellm-compatible-chat-model';
@@ -151,9 +151,11 @@ function ChatResponse(props) {
151
151
  };
152
152
  const markFormConfirmed = (contentId) => {
153
153
  answeredForms.set(contentId, 'confirmed');
154
+ setRenderCount(prev => prev + 1);
154
155
  };
155
156
  const markFormCanceled = (contentId) => {
156
157
  answeredForms.set(contentId, 'canceled');
158
+ setRenderCount(prev => prev + 1);
157
159
  };
158
160
  const runCommand = (commandId, args) => {
159
161
  props.getApp().commands.execute(commandId, args);
@@ -272,7 +274,7 @@ function ChatResponse(props) {
272
274
  React.createElement("a", { href: item.content.uri, target: "_blank" }, item.content.title)));
273
275
  case ResponseStreamDataType.Progress:
274
276
  // show only if no more message available
275
- return index === groupedContents.length - 1 ? (React.createElement("div", { key: `key-${index}` },
277
+ return index === groupedContents.length - 1 ? (React.createElement("div", { className: "chat-response-progress", key: `key-${index}` },
276
278
  "\u2713 ",
277
279
  item.content)) : null;
278
280
  case ResponseStreamDataType.Confirmation:
@@ -1121,7 +1123,7 @@ function ConfigurationDialogBodyComponent(props) {
1121
1123
  const [chatModels, setChatModels] = useState([]);
1122
1124
  const [inlineCompletionModels, setInlineCompletionModels] = useState([]);
1123
1125
  const handleSaveClick = async () => {
1124
- await NBIAPI.setConfig({
1126
+ const config = {
1125
1127
  chat_model: {
1126
1128
  provider: chatModelProvider,
1127
1129
  model: chatModel,
@@ -1132,7 +1134,12 @@ function ConfigurationDialogBodyComponent(props) {
1132
1134
  model: inlineCompletionModel,
1133
1135
  properties: inlineCompletionModelProperties
1134
1136
  }
1135
- });
1137
+ };
1138
+ if (chatModelProvider === 'github-copilot' ||
1139
+ inlineCompletionModelProvider === 'github-copilot') {
1140
+ config.store_github_access_token = storeGitHubAccessToken;
1141
+ }
1142
+ await NBIAPI.setConfig(config);
1136
1143
  props.onSave();
1137
1144
  };
1138
1145
  const handleRefreshOllamaModelListClick = async () => {
@@ -1145,6 +1152,7 @@ function ConfigurationDialogBodyComponent(props) {
1145
1152
  const [chatModelProperties, setChatModelProperties] = useState([]);
1146
1153
  const [inlineCompletionModelProperties, setInlineCompletionModelProperties] = useState([]);
1147
1154
  const [inlineCompletionModel, setInlineCompletionModel] = useState(nbiConfig.inlineCompletionModel.model);
1155
+ const [storeGitHubAccessToken, setStoreGitHubAccessToken] = useState(nbiConfig.storeGitHubAccessToken);
1148
1156
  const updateModelOptionsForProvider = (providerId, modelType) => {
1149
1157
  if (modelType === 'chat') {
1150
1158
  setChatModelProvider(providerId);
@@ -1273,7 +1281,23 @@ function ConfigurationDialogBodyComponent(props) {
1273
1281
  property.name,
1274
1282
  " ",
1275
1283
  property.optional ? '(optional)' : ''),
1276
- 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) }))))))))),
1284
+ 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) })))))))),
1285
+ (chatModelProvider === 'github-copilot' ||
1286
+ inlineCompletionModelProvider === 'github-copilot') && (React.createElement("div", { className: "model-config-section" },
1287
+ React.createElement("div", { className: "model-config-section-header access-token-config-header" },
1288
+ "GitHub Copilot login",
1289
+ ' ',
1290
+ React.createElement("a", { href: "https://github.com/notebook-intelligence/notebook-intelligence/blob/main/README.md#remembering-github-copilot-login", target: "_blank" },
1291
+ ' ',
1292
+ React.createElement(VscWarning, { className: "access-token-warning", title: "Click to learn more about security implications" }))),
1293
+ React.createElement("div", { className: "model-config-section-body" },
1294
+ React.createElement("div", { className: "model-config-section-row" },
1295
+ React.createElement("div", { className: "model-config-section-column" },
1296
+ React.createElement("label", null,
1297
+ React.createElement("input", { type: "checkbox", checked: storeGitHubAccessToken, onChange: event => {
1298
+ setStoreGitHubAccessToken(event.target.checked);
1299
+ } }),
1300
+ "Remember my GitHub Copilot access token"))))))),
1277
1301
  React.createElement("div", { className: "config-dialog-footer" },
1278
1302
  React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: handleSaveClick },
1279
1303
  React.createElement("div", { className: "jp-Dialog-buttonLabel" }, "Save")))));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notebook-intelligence/notebook-intelligence",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "AI coding assistant for JupyterLab",
5
5
  "keywords": [
6
6
  "AI",
package/src/api.ts CHANGED
@@ -56,6 +56,10 @@ export class NBIConfig {
56
56
  );
57
57
  }
58
58
 
59
+ get storeGitHubAccessToken(): boolean {
60
+ return this.capabilities.store_github_access_token === true;
61
+ }
62
+
59
63
  capabilities: any = {};
60
64
  chatParticipants: IChatParticipant[] = [];
61
65
 
@@ -41,7 +41,8 @@ import {
41
41
  VscEye,
42
42
  VscEyeClosed,
43
43
  VscTriangleRight,
44
- VscTriangleDown
44
+ VscTriangleDown,
45
+ VscWarning
45
46
  } from 'react-icons/vsc';
46
47
  import { extractLLMGeneratedCode, isDarkTheme } from './utils';
47
48
 
@@ -314,9 +315,11 @@ function ChatResponse(props: any) {
314
315
 
315
316
  const markFormConfirmed = (contentId: string) => {
316
317
  answeredForms.set(contentId, 'confirmed');
318
+ setRenderCount(prev => prev + 1);
317
319
  };
318
320
  const markFormCanceled = (contentId: string) => {
319
321
  answeredForms.set(contentId, 'canceled');
322
+ setRenderCount(prev => prev + 1);
320
323
  };
321
324
 
322
325
  const runCommand = (commandId: string, args: any) => {
@@ -517,7 +520,9 @@ function ChatResponse(props: any) {
517
520
  case ResponseStreamDataType.Progress:
518
521
  // show only if no more message available
519
522
  return index === groupedContents.length - 1 ? (
520
- <div key={`key-${index}`}>&#x2713; {item.content}</div>
523
+ <div className="chat-response-progress" key={`key-${index}`}>
524
+ &#x2713; {item.content}
525
+ </div>
521
526
  ) : null;
522
527
  case ResponseStreamDataType.Confirmation:
523
528
  return answeredForms.get(item.id) ===
@@ -1819,7 +1824,7 @@ function ConfigurationDialogBodyComponent(props: any) {
1819
1824
  const [inlineCompletionModels, setInlineCompletionModels] = useState([]);
1820
1825
 
1821
1826
  const handleSaveClick = async () => {
1822
- await NBIAPI.setConfig({
1827
+ const config: any = {
1823
1828
  chat_model: {
1824
1829
  provider: chatModelProvider,
1825
1830
  model: chatModel,
@@ -1830,7 +1835,16 @@ function ConfigurationDialogBodyComponent(props: any) {
1830
1835
  model: inlineCompletionModel,
1831
1836
  properties: inlineCompletionModelProperties
1832
1837
  }
1833
- });
1838
+ };
1839
+
1840
+ if (
1841
+ chatModelProvider === 'github-copilot' ||
1842
+ inlineCompletionModelProvider === 'github-copilot'
1843
+ ) {
1844
+ config.store_github_access_token = storeGitHubAccessToken;
1845
+ }
1846
+
1847
+ await NBIAPI.setConfig(config);
1834
1848
 
1835
1849
  props.onSave();
1836
1850
  };
@@ -1852,6 +1866,9 @@ function ConfigurationDialogBodyComponent(props: any) {
1852
1866
  const [inlineCompletionModel, setInlineCompletionModel] = useState(
1853
1867
  nbiConfig.inlineCompletionModel.model
1854
1868
  );
1869
+ const [storeGitHubAccessToken, setStoreGitHubAccessToken] = useState(
1870
+ nbiConfig.storeGitHubAccessToken
1871
+ );
1855
1872
 
1856
1873
  const updateModelOptionsForProvider = (
1857
1874
  providerId: string,
@@ -2157,6 +2174,41 @@ function ConfigurationDialogBodyComponent(props: any) {
2157
2174
  </div>
2158
2175
  </div>
2159
2176
  </div>
2177
+
2178
+ {(chatModelProvider === 'github-copilot' ||
2179
+ inlineCompletionModelProvider === 'github-copilot') && (
2180
+ <div className="model-config-section">
2181
+ <div className="model-config-section-header access-token-config-header">
2182
+ GitHub Copilot login{' '}
2183
+ <a
2184
+ href="https://github.com/notebook-intelligence/notebook-intelligence/blob/main/README.md#remembering-github-copilot-login"
2185
+ target="_blank"
2186
+ >
2187
+ {' '}
2188
+ <VscWarning
2189
+ className="access-token-warning"
2190
+ title="Click to learn more about security implications"
2191
+ />
2192
+ </a>
2193
+ </div>
2194
+ <div className="model-config-section-body">
2195
+ <div className="model-config-section-row">
2196
+ <div className="model-config-section-column">
2197
+ <label>
2198
+ <input
2199
+ type="checkbox"
2200
+ checked={storeGitHubAccessToken}
2201
+ onChange={event => {
2202
+ setStoreGitHubAccessToken(event.target.checked);
2203
+ }}
2204
+ />
2205
+ Remember my GitHub Copilot access token
2206
+ </label>
2207
+ </div>
2208
+ </div>
2209
+ </div>
2210
+ </div>
2211
+ )}
2160
2212
  </div>
2161
2213
 
2162
2214
  <div className="config-dialog-footer">
package/style/base.css CHANGED
@@ -275,6 +275,10 @@ pre:has(.code-block-header) {
275
275
  margin: 5px 0;
276
276
  }
277
277
 
278
+ .chat-response-progress {
279
+ margin: 5px 0;
280
+ }
281
+
278
282
  .chat-confirmation-form {
279
283
  border: 1px solid var(--jp-border-color1);
280
284
  padding: 5px;
@@ -610,3 +614,15 @@ body[data-jp-theme-light='false'] .inline-popover {
610
614
  .chat-reasoning-content.expanded .collapsed-icon {
611
615
  display: none;
612
616
  }
617
+
618
+ .access-token-config-header {
619
+ display: flex;
620
+ align-items: center;
621
+ gap: 10px;
622
+ }
623
+
624
+ svg.access-token-warning {
625
+ color: var(--jp-warn-color0);
626
+ width: 18px;
627
+ height: 18px;
628
+ }