@jupyterlite/ai 0.6.0 → 0.6.1

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/lib/index.js CHANGED
@@ -5,16 +5,16 @@ import { INotebookTracker } from '@jupyterlab/notebook';
5
5
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
6
6
  import { ISettingConnector, ISettingRegistry } from '@jupyterlab/settingregistry';
7
7
  import { IFormRendererRegistry } from '@jupyterlab/ui-components';
8
- import { ISecretsManager } from 'jupyter-secrets-manager';
8
+ import { ISecretsManager, SecretsManager } from 'jupyter-secrets-manager';
9
9
  import { ChatHandler } from './chat-handler';
10
10
  import { CompletionProvider } from './completion-provider';
11
11
  import { defaultProviderPlugins } from './default-providers';
12
12
  import { AIProviderRegistry } from './provider';
13
13
  import { aiSettingsRenderer, SettingConnector } from './settings';
14
- import { IAIProviderRegistry } from './tokens';
14
+ import { IAIProviderRegistry, PLUGIN_IDS } from './tokens';
15
15
  import { stopItem } from './components/stop-button';
16
16
  const chatCommandRegistryPlugin = {
17
- id: '@jupyterlite/ai:autocompletion-registry',
17
+ id: PLUGIN_IDS.chatCommandRegistry,
18
18
  description: 'Autocompletion registry',
19
19
  autoStart: true,
20
20
  provides: IChatCommandRegistry,
@@ -25,7 +25,7 @@ const chatCommandRegistryPlugin = {
25
25
  }
26
26
  };
27
27
  const chatPlugin = {
28
- id: '@jupyterlite/ai:chat',
28
+ id: PLUGIN_IDS.chat,
29
29
  description: 'LLM chat extension',
30
30
  autoStart: true,
31
31
  requires: [IAIProviderRegistry, IRenderMimeRegistry, IChatCommandRegistry],
@@ -98,7 +98,7 @@ const chatPlugin = {
98
98
  }
99
99
  };
100
100
  const completerPlugin = {
101
- id: '@jupyterlite/ai:completer',
101
+ id: PLUGIN_IDS.completer,
102
102
  autoStart: true,
103
103
  requires: [IAIProviderRegistry, ICompletionProviderManager],
104
104
  activate: (app, providerRegistry, manager) => {
@@ -109,16 +109,20 @@ const completerPlugin = {
109
109
  manager.registerInlineProvider(completer);
110
110
  }
111
111
  };
112
- const providerRegistryPlugin = {
113
- id: '@jupyterlite/ai:provider-registry',
112
+ const providerRegistryPlugin = SecretsManager.sign(PLUGIN_IDS.providerRegistry, token => ({
113
+ id: PLUGIN_IDS.providerRegistry,
114
114
  autoStart: true,
115
115
  requires: [IFormRendererRegistry, ISettingRegistry],
116
116
  optional: [IRenderMimeRegistry, ISecretsManager, ISettingConnector],
117
117
  provides: IAIProviderRegistry,
118
118
  activate: (app, editorRegistry, settingRegistry, rmRegistry, secretsManager, settingConnector) => {
119
- const providerRegistry = new AIProviderRegistry({ secretsManager });
120
- editorRegistry.addRenderer('@jupyterlite/ai:provider-registry.AIprovider', aiSettingsRenderer({
119
+ const providerRegistry = new AIProviderRegistry({
120
+ token,
121
+ secretsManager
122
+ });
123
+ editorRegistry.addRenderer(`${PLUGIN_IDS.providerRegistry}.AIprovider`, aiSettingsRenderer({
121
124
  providerRegistry,
125
+ secretsToken: token,
122
126
  rmRegistry,
123
127
  secretsManager,
124
128
  settingConnector
@@ -145,14 +149,14 @@ const providerRegistryPlugin = {
145
149
  });
146
150
  return providerRegistry;
147
151
  }
148
- };
152
+ }));
149
153
  /**
150
154
  * Provides the settings connector as a separate plugin to allow for alternative
151
155
  * implementations that may want to fetch settings from a different source or
152
156
  * endpoint.
153
157
  */
154
158
  const settingsConnector = {
155
- id: '@jupyterlite/ai:settings-connector',
159
+ id: PLUGIN_IDS.settingsConnector,
156
160
  description: 'Provides a settings connector which does not save passwords.',
157
161
  autoStart: true,
158
162
  provides: ISettingConnector,
package/lib/provider.d.ts CHANGED
@@ -84,6 +84,10 @@ export declare namespace AIProviderRegistry {
84
84
  * The secrets manager used in the application.
85
85
  */
86
86
  secretsManager?: ISecretsManager;
87
+ /**
88
+ * The token used to request the secrets manager.
89
+ */
90
+ token: symbol;
87
91
  }
88
92
  /**
89
93
  * The options for the Chat system prompt.
package/lib/provider.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { Signal } from '@lumino/signaling';
2
- import { getSecretId, SECRETS_NAMESPACE, SECRETS_REPLACEMENT } from './settings';
2
+ import { getSecretId, SECRETS_REPLACEMENT } from './settings';
3
+ import { PLUGIN_IDS } from './tokens';
4
+ const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
3
5
  export const chatSystemPrompt = (options) => `
4
6
  You are Jupyternaut, a conversational assistant living in JupyterLab to help users.
5
7
  You are not a language model, but rather an application built on a foundation model from ${options.provider_name}.
@@ -41,6 +43,7 @@ export class AIProviderRegistry {
41
43
  this._providers = new Map();
42
44
  this._deferredProvider = null;
43
45
  this._secretsManager = options.secretsManager || null;
46
+ Private.setToken(options.token);
44
47
  }
45
48
  /**
46
49
  * Get the list of provider names.
@@ -149,7 +152,7 @@ export class AIProviderRegistry {
149
152
  for (const key of Object.keys(settings)) {
150
153
  if (settings[key] === SECRETS_REPLACEMENT) {
151
154
  const id = getSecretId(name, key);
152
- const secrets = await ((_b = this._secretsManager) === null || _b === void 0 ? void 0 : _b.get(SECRETS_NAMESPACE, id));
155
+ const secrets = await ((_b = this._secretsManager) === null || _b === void 0 ? void 0 : _b.get(Private.getToken(), SECRETS_NAMESPACE, id));
153
156
  fullSettings[key] = (secrets === null || secrets === void 0 ? void 0 : secrets.value) || settings[key];
154
157
  continue;
155
158
  }
@@ -231,3 +234,24 @@ export class AIProviderRegistry {
231
234
  }
232
235
  AIProviderRegistry.updateConfig = updateConfig;
233
236
  })(AIProviderRegistry || (AIProviderRegistry = {}));
237
+ var Private;
238
+ (function (Private) {
239
+ /**
240
+ * The token to use with the secrets manager.
241
+ */
242
+ let secretsToken;
243
+ /**
244
+ * Set of the token.
245
+ */
246
+ function setToken(value) {
247
+ secretsToken = value;
248
+ }
249
+ Private.setToken = setToken;
250
+ /**
251
+ * get the token.
252
+ */
253
+ function getToken() {
254
+ return secretsToken;
255
+ }
256
+ Private.getToken = getToken;
257
+ })(Private || (Private = {}));
@@ -8,6 +8,7 @@ import React from 'react';
8
8
  import { IAIProviderRegistry, IDict } from '../tokens';
9
9
  export declare const aiSettingsRenderer: (options: {
10
10
  providerRegistry: IAIProviderRegistry;
11
+ secretsToken?: symbol;
11
12
  rmRegistry?: IRenderMimeRegistry;
12
13
  secretsManager?: ISecretsManager;
13
14
  settingConnector?: ISettingConnector;
@@ -19,6 +20,7 @@ export interface ISettingsFormStates {
19
20
  export declare class AiSettings extends React.Component<FieldProps, ISettingsFormStates> {
20
21
  constructor(props: FieldProps);
21
22
  componentDidUpdate(): Promise<void>;
23
+ componentWillUnmount(): void;
22
24
  /**
23
25
  * Get the current provider from the local storage.
24
26
  */
@@ -36,11 +38,6 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
36
38
  */
37
39
  saveSettings(value: IDict<any>): void;
38
40
  private updateUseSecretsManager;
39
- /**
40
- * Update the UI schema of the form.
41
- * Currently use to hide API keys.
42
- */
43
- private _updateUiSchema;
44
41
  /**
45
42
  * Build the schema for a given provider.
46
43
  */
@@ -75,6 +72,7 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
75
72
  private _provider;
76
73
  private _providerSchema;
77
74
  private _useSecretsManager;
75
+ private _hideSecretFields;
78
76
  private _rmRegistry;
79
77
  private _secretsManager;
80
78
  private _settingConnector;
@@ -3,12 +3,19 @@ import { ArrayExt } from '@lumino/algorithm';
3
3
  import { JSONExt } from '@lumino/coreutils';
4
4
  import validator from '@rjsf/validator-ajv8';
5
5
  import React from 'react';
6
- import { getSecretId, SECRETS_NAMESPACE, SettingConnector } from '.';
6
+ import { getSecretId, SettingConnector } from '.';
7
7
  import baseSettings from './base.json';
8
+ import { PLUGIN_IDS } from '../tokens';
8
9
  const MD_MIME_TYPE = 'text/markdown';
9
10
  const STORAGE_NAME = '@jupyterlite/ai:settings';
10
11
  const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
12
+ const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
11
13
  export const aiSettingsRenderer = (options) => {
14
+ const { secretsToken } = options;
15
+ delete options.secretsToken;
16
+ if (secretsToken) {
17
+ Private.setToken(secretsToken);
18
+ }
12
19
  return {
13
20
  fieldRenderer: (props) => {
14
21
  props.formContext = { ...props.formContext, ...options };
@@ -21,7 +28,7 @@ const WrappedFormComponent = (props) => {
21
28
  };
22
29
  export class AiSettings extends React.Component {
23
30
  constructor(props) {
24
- var _a, _b, _c, _d, _e;
31
+ var _a, _b, _c, _d, _e, _f;
25
32
  super(props);
26
33
  this.updateUseSecretsManager = (value) => {
27
34
  var _a;
@@ -29,7 +36,7 @@ export class AiSettings extends React.Component {
29
36
  if (!value) {
30
37
  // Detach all the password inputs attached to the secrets manager, and save the
31
38
  // current settings to the local storage to save the password.
32
- (_a = this._secretsManager) === null || _a === void 0 ? void 0 : _a.detachAll(SECRETS_NAMESPACE);
39
+ (_a = this._secretsManager) === null || _a === void 0 ? void 0 : _a.detachAll(Private.getToken(), SECRETS_NAMESPACE);
33
40
  this._formInputs = [];
34
41
  this._unsavedFields = [];
35
42
  if (this._settingConnector instanceof SettingConnector) {
@@ -110,6 +117,8 @@ export class AiSettings extends React.Component {
110
117
  this._settings = props.formContext.settings;
111
118
  this._useSecretsManager =
112
119
  (_d = this._settings.get('UseSecretsManager').composite) !== null && _d !== void 0 ? _d : true;
120
+ this._hideSecretFields =
121
+ (_e = this._settings.get('HideSecretFields').composite) !== null && _e !== void 0 ? _e : true;
113
122
  // Initialize the providers schema.
114
123
  const providerSchema = JSONExt.deepCopy(baseSettings);
115
124
  providerSchema.properties.provider = {
@@ -127,7 +136,7 @@ export class AiSettings extends React.Component {
127
136
  const labSettings = this._settings.get('AIprovider').composite;
128
137
  if (labSettings && Object.keys(labSettings).includes('provider')) {
129
138
  // Get the provider name.
130
- const provider = (_e = Object.entries(labSettings).find(v => v[0] === 'provider')) === null || _e === void 0 ? void 0 : _e[1];
139
+ const provider = (_f = Object.entries(labSettings).find(v => v[0] === 'provider')) === null || _f === void 0 ? void 0 : _f[1];
131
140
  // Save the settings.
132
141
  const settings = {
133
142
  _current: provider
@@ -148,11 +157,16 @@ export class AiSettings extends React.Component {
148
157
  .set('AIprovider', this._currentSettings)
149
158
  .catch(console.error);
150
159
  this._settings.changed.connect(() => {
151
- var _a;
160
+ var _a, _b;
152
161
  const useSecretsManager = (_a = this._settings.get('UseSecretsManager').composite) !== null && _a !== void 0 ? _a : true;
153
162
  if (useSecretsManager !== this._useSecretsManager) {
154
163
  this.updateUseSecretsManager(useSecretsManager);
155
164
  }
165
+ const hideSecretFields = (_b = this._settings.get('HideSecretFields').composite) !== null && _b !== void 0 ? _b : true;
166
+ if (hideSecretFields !== this._hideSecretFields) {
167
+ this._hideSecretFields = hideSecretFields;
168
+ this._updateSchema();
169
+ }
156
170
  });
157
171
  }
158
172
  async componentDidUpdate() {
@@ -165,7 +179,7 @@ export class AiSettings extends React.Component {
165
179
  if (ArrayExt.shallowEqual(inputs, this._formInputs)) {
166
180
  return;
167
181
  }
168
- await this._secretsManager.detachAll(SECRETS_NAMESPACE);
182
+ await this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
169
183
  this._formInputs = [...inputs];
170
184
  this._unsavedFields = [];
171
185
  for (let i = 0; i < inputs.length; i++) {
@@ -173,7 +187,7 @@ export class AiSettings extends React.Component {
173
187
  const label = inputs[i].getAttribute('label');
174
188
  if (label) {
175
189
  const id = getSecretId(this._provider, label);
176
- this._secretsManager.attach(SECRETS_NAMESPACE, id, inputs[i], (value) => this._onPasswordUpdated(label, value));
190
+ this._secretsManager.attach(Private.getToken(), SECRETS_NAMESPACE, id, inputs[i], (value) => this._onPasswordUpdated(label, value));
177
191
  this._unsavedFields.push(label);
178
192
  }
179
193
  }
@@ -182,6 +196,12 @@ export class AiSettings extends React.Component {
182
196
  this._settingConnector.doNotSave = this._unsavedFields;
183
197
  }
184
198
  }
199
+ componentWillUnmount() {
200
+ if (!this._secretsManager || !this._useSecretsManager) {
201
+ return;
202
+ }
203
+ this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
204
+ }
185
205
  /**
186
206
  * Get the current provider from the local storage.
187
207
  */
@@ -217,15 +237,6 @@ export class AiSettings extends React.Component {
217
237
  settings[this._provider] = currentSettings;
218
238
  localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
219
239
  }
220
- /**
221
- * Update the UI schema of the form.
222
- * Currently use to hide API keys.
223
- */
224
- _updateUiSchema(key) {
225
- if (key.toLowerCase().includes('key')) {
226
- this._uiSchema[key] = { 'ui:widget': 'password' };
227
- }
228
- }
229
240
  /**
230
241
  * Build the schema for a given provider.
231
242
  */
@@ -235,8 +246,13 @@ export class AiSettings extends React.Component {
235
246
  const settingsSchema = this._providerRegistry.getSettingsSchema(this._provider);
236
247
  if (settingsSchema) {
237
248
  Object.entries(settingsSchema).forEach(([key, value]) => {
249
+ if (key.toLowerCase().includes('key')) {
250
+ if (this._hideSecretFields) {
251
+ return;
252
+ }
253
+ this._uiSchema[key] = { 'ui:widget': 'password' };
254
+ }
238
255
  schema.properties[key] = value;
239
- this._updateUiSchema(key);
240
256
  });
241
257
  }
242
258
  return schema;
@@ -275,3 +291,24 @@ export class AiSettings extends React.Component {
275
291
  React.createElement(WrappedFormComponent, { formData: this._currentSettings, schema: this.state.schema, onChange: this._onFormChange, uiSchema: this._uiSchema })));
276
292
  }
277
293
  }
294
+ var Private;
295
+ (function (Private) {
296
+ /**
297
+ * The token to use with the secrets manager.
298
+ */
299
+ let secretsToken;
300
+ /**
301
+ * Set of the token.
302
+ */
303
+ function setToken(value) {
304
+ secretsToken = value;
305
+ }
306
+ Private.setToken = setToken;
307
+ /**
308
+ * get the token.
309
+ */
310
+ function getToken() {
311
+ return secretsToken;
312
+ }
313
+ Private.getToken = getToken;
314
+ })(Private || (Private = {}));
@@ -1,3 +1,2 @@
1
- export declare const SECRETS_NAMESPACE = "@jupyterlite/ai";
2
1
  export declare const SECRETS_REPLACEMENT = "***";
3
2
  export declare function getSecretId(provider: string, label: string): string;
@@ -1,4 +1,3 @@
1
- export const SECRETS_NAMESPACE = '@jupyterlite/ai';
2
1
  export const SECRETS_REPLACEMENT = '***';
3
2
  export function getSecretId(provider, label) {
4
3
  return `${provider}-${label}`;
package/lib/tokens.d.ts CHANGED
@@ -3,6 +3,13 @@ import { ReadonlyPartialJSONObject, Token } from '@lumino/coreutils';
3
3
  import { ISignal } from '@lumino/signaling';
4
4
  import { JSONSchema7 } from 'json-schema';
5
5
  import { IBaseCompleter } from './base-completer';
6
+ export declare const PLUGIN_IDS: {
7
+ chat: string;
8
+ chatCommandRegistry: string;
9
+ completer: string;
10
+ providerRegistry: string;
11
+ settingsConnector: string;
12
+ };
6
13
  export interface IDict<T = any> {
7
14
  [key: string]: T;
8
15
  }
package/lib/tokens.js CHANGED
@@ -1,4 +1,11 @@
1
1
  import { Token } from '@lumino/coreutils';
2
+ export const PLUGIN_IDS = {
3
+ chat: '@jupyterlite/ai:chat',
4
+ chatCommandRegistry: '@jupyterlite/ai:autocompletion-registry',
5
+ completer: '@jupyterlite/ai:completer',
6
+ providerRegistry: '@jupyterlite/ai:provider-registry',
7
+ settingsConnector: '@jupyterlite/ai:settings-connector'
8
+ };
2
9
  /**
3
10
  * The provider registry token.
4
11
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupyterlite/ai",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "AI code completions and chat for JupyterLite",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -57,13 +57,14 @@
57
57
  },
58
58
  "dependencies": {
59
59
  "@jupyter/chat": "^0.9.0",
60
- "@jupyterlab/application": "^4.4.0-alpha.0",
61
- "@jupyterlab/apputils": "^4.5.0-alpha.0",
62
- "@jupyterlab/completer": "^4.4.0-alpha.0",
63
- "@jupyterlab/notebook": "^4.4.0-alpha.0",
64
- "@jupyterlab/rendermime": "^4.4.0-alpha.0",
65
- "@jupyterlab/settingregistry": "^4.4.0-beta.1",
66
- "@jupyterlab/ui-components": "^4.4.0-alpha.0",
60
+ "@jupyterlab/application": "^4.4.0",
61
+ "@jupyterlab/apputils": "^4.5.0",
62
+ "@jupyterlab/completer": "^4.4.0",
63
+ "@jupyterlab/coreutils": "^6.4.0",
64
+ "@jupyterlab/notebook": "^4.4.0",
65
+ "@jupyterlab/rendermime": "^4.4.0",
66
+ "@jupyterlab/settingregistry": "^4.4.0",
67
+ "@jupyterlab/ui-components": "^4.4.0",
67
68
  "@langchain/anthropic": "^0.3.9",
68
69
  "@langchain/community": "^0.3.31",
69
70
  "@langchain/core": "^0.3.40",
@@ -78,12 +79,12 @@
78
79
  "@rjsf/utils": "^5.18.4",
79
80
  "@rjsf/validator-ajv8": "^5.18.4",
80
81
  "json5": "^2.2.3",
81
- "jupyter-secrets-manager": "^0.2.0",
82
+ "jupyter-secrets-manager": "^0.3.0",
82
83
  "react": "^18.2.0",
83
84
  "react-dom": "^18.2.0"
84
85
  },
85
86
  "devDependencies": {
86
- "@jupyterlab/builder": "^4.0.0",
87
+ "@jupyterlab/builder": "^4.4.0",
87
88
  "@stylistic/eslint-plugin": "^3.0.1",
88
89
  "@types/json-schema": "^7.0.11",
89
90
  "@types/react": "^18.0.26",
@@ -11,6 +11,12 @@
11
11
  "description": "Whether to use or not the secrets manager. If not, secrets will be stored in the browser (local storage)",
12
12
  "default": true
13
13
  },
14
+ "HideSecretFields": {
15
+ "type": "boolean",
16
+ "title": "Hide secret fields",
17
+ "description": "Whether to hide the secret fields in the UI or not",
18
+ "default": true
19
+ },
14
20
  "AIprovider": {
15
21
  "type": "object",
16
22
  "title": "AI provider",
package/src/index.ts CHANGED
@@ -21,18 +21,18 @@ import {
21
21
  } from '@jupyterlab/settingregistry';
22
22
  import { IFormRendererRegistry } from '@jupyterlab/ui-components';
23
23
  import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
24
- import { ISecretsManager } from 'jupyter-secrets-manager';
24
+ import { ISecretsManager, SecretsManager } from 'jupyter-secrets-manager';
25
25
 
26
26
  import { ChatHandler } from './chat-handler';
27
27
  import { CompletionProvider } from './completion-provider';
28
28
  import { defaultProviderPlugins } from './default-providers';
29
29
  import { AIProviderRegistry } from './provider';
30
30
  import { aiSettingsRenderer, SettingConnector } from './settings';
31
- import { IAIProviderRegistry } from './tokens';
31
+ import { IAIProviderRegistry, PLUGIN_IDS } from './tokens';
32
32
  import { stopItem } from './components/stop-button';
33
33
 
34
34
  const chatCommandRegistryPlugin: JupyterFrontEndPlugin<IChatCommandRegistry> = {
35
- id: '@jupyterlite/ai:autocompletion-registry',
35
+ id: PLUGIN_IDS.chatCommandRegistry,
36
36
  description: 'Autocompletion registry',
37
37
  autoStart: true,
38
38
  provides: IChatCommandRegistry,
@@ -44,7 +44,7 @@ const chatCommandRegistryPlugin: JupyterFrontEndPlugin<IChatCommandRegistry> = {
44
44
  };
45
45
 
46
46
  const chatPlugin: JupyterFrontEndPlugin<void> = {
47
- id: '@jupyterlite/ai:chat',
47
+ id: PLUGIN_IDS.chat,
48
48
  description: 'LLM chat extension',
49
49
  autoStart: true,
50
50
  requires: [IAIProviderRegistry, IRenderMimeRegistry, IChatCommandRegistry],
@@ -141,7 +141,7 @@ const chatPlugin: JupyterFrontEndPlugin<void> = {
141
141
  };
142
142
 
143
143
  const completerPlugin: JupyterFrontEndPlugin<void> = {
144
- id: '@jupyterlite/ai:completer',
144
+ id: PLUGIN_IDS.completer,
145
145
  autoStart: true,
146
146
  requires: [IAIProviderRegistry, ICompletionProviderManager],
147
147
  activate: (
@@ -157,59 +157,64 @@ const completerPlugin: JupyterFrontEndPlugin<void> = {
157
157
  }
158
158
  };
159
159
 
160
- const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
161
- id: '@jupyterlite/ai:provider-registry',
162
- autoStart: true,
163
- requires: [IFormRendererRegistry, ISettingRegistry],
164
- optional: [IRenderMimeRegistry, ISecretsManager, ISettingConnector],
165
- provides: IAIProviderRegistry,
166
- activate: (
167
- app: JupyterFrontEnd,
168
- editorRegistry: IFormRendererRegistry,
169
- settingRegistry: ISettingRegistry,
170
- rmRegistry?: IRenderMimeRegistry,
171
- secretsManager?: ISecretsManager,
172
- settingConnector?: ISettingConnector
173
- ): IAIProviderRegistry => {
174
- const providerRegistry = new AIProviderRegistry({ secretsManager });
175
-
176
- editorRegistry.addRenderer(
177
- '@jupyterlite/ai:provider-registry.AIprovider',
178
- aiSettingsRenderer({
179
- providerRegistry,
180
- rmRegistry,
181
- secretsManager,
182
- settingConnector
183
- })
184
- );
185
-
186
- settingRegistry
187
- .load(providerRegistryPlugin.id)
188
- .then(settings => {
189
- const updateProvider = () => {
190
- // Update the settings to the AI providers.
191
- const providerSettings = (settings.get('AIprovider').composite ?? {
192
- provider: 'None'
193
- }) as ReadonlyPartialJSONObject;
194
- providerRegistry.setProvider({
195
- name: providerSettings.provider as string,
196
- settings: providerSettings
197
- });
198
- };
199
-
200
- settings.changed.connect(() => updateProvider());
201
- updateProvider();
202
- })
203
- .catch(reason => {
204
- console.error(
205
- `Failed to load settings for ${providerRegistryPlugin.id}`,
206
- reason
207
- );
160
+ const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> =
161
+ SecretsManager.sign(PLUGIN_IDS.providerRegistry, token => ({
162
+ id: PLUGIN_IDS.providerRegistry,
163
+ autoStart: true,
164
+ requires: [IFormRendererRegistry, ISettingRegistry],
165
+ optional: [IRenderMimeRegistry, ISecretsManager, ISettingConnector],
166
+ provides: IAIProviderRegistry,
167
+ activate: (
168
+ app: JupyterFrontEnd,
169
+ editorRegistry: IFormRendererRegistry,
170
+ settingRegistry: ISettingRegistry,
171
+ rmRegistry?: IRenderMimeRegistry,
172
+ secretsManager?: ISecretsManager,
173
+ settingConnector?: ISettingConnector
174
+ ): IAIProviderRegistry => {
175
+ const providerRegistry = new AIProviderRegistry({
176
+ token,
177
+ secretsManager
208
178
  });
209
179
 
210
- return providerRegistry;
211
- }
212
- };
180
+ editorRegistry.addRenderer(
181
+ `${PLUGIN_IDS.providerRegistry}.AIprovider`,
182
+ aiSettingsRenderer({
183
+ providerRegistry,
184
+ secretsToken: token,
185
+ rmRegistry,
186
+ secretsManager,
187
+ settingConnector
188
+ })
189
+ );
190
+
191
+ settingRegistry
192
+ .load(providerRegistryPlugin.id)
193
+ .then(settings => {
194
+ const updateProvider = () => {
195
+ // Update the settings to the AI providers.
196
+ const providerSettings = (settings.get('AIprovider').composite ?? {
197
+ provider: 'None'
198
+ }) as ReadonlyPartialJSONObject;
199
+ providerRegistry.setProvider({
200
+ name: providerSettings.provider as string,
201
+ settings: providerSettings
202
+ });
203
+ };
204
+
205
+ settings.changed.connect(() => updateProvider());
206
+ updateProvider();
207
+ })
208
+ .catch(reason => {
209
+ console.error(
210
+ `Failed to load settings for ${providerRegistryPlugin.id}`,
211
+ reason
212
+ );
213
+ });
214
+
215
+ return providerRegistry;
216
+ }
217
+ }));
213
218
 
214
219
  /**
215
220
  * Provides the settings connector as a separate plugin to allow for alternative
@@ -217,7 +222,7 @@ const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
217
222
  * endpoint.
218
223
  */
219
224
  const settingsConnector: JupyterFrontEndPlugin<ISettingConnector> = {
220
- id: '@jupyterlite/ai:settings-connector',
225
+ id: PLUGIN_IDS.settingsConnector,
221
226
  description: 'Provides a settings connector which does not save passwords.',
222
227
  autoStart: true,
223
228
  provides: ISettingConnector,
package/src/provider.ts CHANGED
@@ -6,18 +6,17 @@ import { JSONSchema7 } from 'json-schema';
6
6
  import { ISecretsManager } from 'jupyter-secrets-manager';
7
7
 
8
8
  import { IBaseCompleter } from './base-completer';
9
- import {
10
- getSecretId,
11
- SECRETS_NAMESPACE,
12
- SECRETS_REPLACEMENT
13
- } from './settings';
9
+ import { getSecretId, SECRETS_REPLACEMENT } from './settings';
14
10
  import {
15
11
  IAIProvider,
16
12
  IAIProviderRegistry,
17
13
  IDict,
18
- ISetProviderOptions
14
+ ISetProviderOptions,
15
+ PLUGIN_IDS
19
16
  } from './tokens';
20
17
 
18
+ const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
19
+
21
20
  export const chatSystemPrompt = (
22
21
  options: AIProviderRegistry.IPromptOptions
23
22
  ) => `
@@ -54,6 +53,7 @@ export class AIProviderRegistry implements IAIProviderRegistry {
54
53
  */
55
54
  constructor(options: AIProviderRegistry.IOptions) {
56
55
  this._secretsManager = options.secretsManager || null;
56
+ Private.setToken(options.token);
57
57
  }
58
58
 
59
59
  /**
@@ -171,7 +171,11 @@ export class AIProviderRegistry implements IAIProviderRegistry {
171
171
  for (const key of Object.keys(settings)) {
172
172
  if (settings[key] === SECRETS_REPLACEMENT) {
173
173
  const id = getSecretId(name, key);
174
- const secrets = await this._secretsManager?.get(SECRETS_NAMESPACE, id);
174
+ const secrets = await this._secretsManager?.get(
175
+ Private.getToken(),
176
+ SECRETS_NAMESPACE,
177
+ id
178
+ );
175
179
  fullSettings[key] = secrets?.value || settings[key];
176
180
  continue;
177
181
  }
@@ -236,6 +240,10 @@ export namespace AIProviderRegistry {
236
240
  * The secrets manager used in the application.
237
241
  */
238
242
  secretsManager?: ISecretsManager;
243
+ /**
244
+ * The token used to request the secrets manager.
245
+ */
246
+ token: symbol;
239
247
  }
240
248
 
241
249
  /**
@@ -290,3 +298,24 @@ export namespace AIProviderRegistry {
290
298
  });
291
299
  }
292
300
  }
301
+
302
+ namespace Private {
303
+ /**
304
+ * The token to use with the secrets manager.
305
+ */
306
+ let secretsToken: symbol;
307
+
308
+ /**
309
+ * Set of the token.
310
+ */
311
+ export function setToken(value: symbol): void {
312
+ secretsToken = value;
313
+ }
314
+
315
+ /**
316
+ * get the token.
317
+ */
318
+ export function getToken(): symbol {
319
+ return secretsToken;
320
+ }
321
+ }
@@ -13,20 +13,27 @@ import { JSONSchema7 } from 'json-schema';
13
13
  import { ISecretsManager } from 'jupyter-secrets-manager';
14
14
  import React from 'react';
15
15
 
16
- import { getSecretId, SECRETS_NAMESPACE, SettingConnector } from '.';
16
+ import { getSecretId, SettingConnector } from '.';
17
17
  import baseSettings from './base.json';
18
- import { IAIProviderRegistry, IDict } from '../tokens';
18
+ import { IAIProviderRegistry, IDict, PLUGIN_IDS } from '../tokens';
19
19
 
20
20
  const MD_MIME_TYPE = 'text/markdown';
21
21
  const STORAGE_NAME = '@jupyterlite/ai:settings';
22
22
  const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
23
+ const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
23
24
 
24
25
  export const aiSettingsRenderer = (options: {
25
26
  providerRegistry: IAIProviderRegistry;
27
+ secretsToken?: symbol;
26
28
  rmRegistry?: IRenderMimeRegistry;
27
29
  secretsManager?: ISecretsManager;
28
30
  settingConnector?: ISettingConnector;
29
31
  }): IFormRenderer => {
32
+ const { secretsToken } = options;
33
+ delete options.secretsToken;
34
+ if (secretsToken) {
35
+ Private.setToken(secretsToken);
36
+ }
30
37
  return {
31
38
  fieldRenderer: (props: FieldProps) => {
32
39
  props.formContext = { ...props.formContext, ...options };
@@ -63,6 +70,8 @@ export class AiSettings extends React.Component<
63
70
 
64
71
  this._useSecretsManager =
65
72
  (this._settings.get('UseSecretsManager').composite as boolean) ?? true;
73
+ this._hideSecretFields =
74
+ (this._settings.get('HideSecretFields').composite as boolean) ?? true;
66
75
 
67
76
  // Initialize the providers schema.
68
77
  const providerSchema = JSONExt.deepCopy(baseSettings) as any;
@@ -115,6 +124,12 @@ export class AiSettings extends React.Component<
115
124
  if (useSecretsManager !== this._useSecretsManager) {
116
125
  this.updateUseSecretsManager(useSecretsManager);
117
126
  }
127
+ const hideSecretFields =
128
+ (this._settings.get('HideSecretFields').composite as boolean) ?? true;
129
+ if (hideSecretFields !== this._hideSecretFields) {
130
+ this._hideSecretFields = hideSecretFields;
131
+ this._updateSchema();
132
+ }
118
133
  });
119
134
  }
120
135
 
@@ -128,7 +143,7 @@ export class AiSettings extends React.Component<
128
143
  return;
129
144
  }
130
145
 
131
- await this._secretsManager.detachAll(SECRETS_NAMESPACE);
146
+ await this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
132
147
  this._formInputs = [...inputs];
133
148
  this._unsavedFields = [];
134
149
  for (let i = 0; i < inputs.length; i++) {
@@ -137,6 +152,7 @@ export class AiSettings extends React.Component<
137
152
  if (label) {
138
153
  const id = getSecretId(this._provider, label);
139
154
  this._secretsManager.attach(
155
+ Private.getToken(),
140
156
  SECRETS_NAMESPACE,
141
157
  id,
142
158
  inputs[i],
@@ -151,6 +167,13 @@ export class AiSettings extends React.Component<
151
167
  }
152
168
  }
153
169
 
170
+ componentWillUnmount(): void {
171
+ if (!this._secretsManager || !this._useSecretsManager) {
172
+ return;
173
+ }
174
+ this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
175
+ }
176
+
154
177
  /**
155
178
  * Get the current provider from the local storage.
156
179
  */
@@ -192,7 +215,7 @@ export class AiSettings extends React.Component<
192
215
  if (!value) {
193
216
  // Detach all the password inputs attached to the secrets manager, and save the
194
217
  // current settings to the local storage to save the password.
195
- this._secretsManager?.detachAll(SECRETS_NAMESPACE);
218
+ this._secretsManager?.detachAll(Private.getToken(), SECRETS_NAMESPACE);
196
219
  this._formInputs = [];
197
220
  this._unsavedFields = [];
198
221
  if (this._settingConnector instanceof SettingConnector) {
@@ -218,16 +241,6 @@ export class AiSettings extends React.Component<
218
241
  .catch(console.error);
219
242
  };
220
243
 
221
- /**
222
- * Update the UI schema of the form.
223
- * Currently use to hide API keys.
224
- */
225
- private _updateUiSchema(key: string) {
226
- if (key.toLowerCase().includes('key')) {
227
- this._uiSchema[key] = { 'ui:widget': 'password' };
228
- }
229
- }
230
-
231
244
  /**
232
245
  * Build the schema for a given provider.
233
246
  */
@@ -240,8 +253,13 @@ export class AiSettings extends React.Component<
240
253
 
241
254
  if (settingsSchema) {
242
255
  Object.entries(settingsSchema).forEach(([key, value]) => {
256
+ if (key.toLowerCase().includes('key')) {
257
+ if (this._hideSecretFields) {
258
+ return;
259
+ }
260
+ this._uiSchema[key] = { 'ui:widget': 'password' };
261
+ }
243
262
  schema.properties[key] = value;
244
- this._updateUiSchema(key);
245
263
  });
246
264
  }
247
265
  return schema as JSONSchema7;
@@ -349,6 +367,7 @@ export class AiSettings extends React.Component<
349
367
  private _provider: string;
350
368
  private _providerSchema: JSONSchema7;
351
369
  private _useSecretsManager: boolean;
370
+ private _hideSecretFields: boolean;
352
371
  private _rmRegistry: IRenderMimeRegistry | null;
353
372
  private _secretsManager: ISecretsManager | null;
354
373
  private _settingConnector: ISettingConnector | null;
@@ -359,3 +378,24 @@ export class AiSettings extends React.Component<
359
378
  private _unsavedFields: string[] = [];
360
379
  private _formInputs: HTMLInputElement[] = [];
361
380
  }
381
+
382
+ namespace Private {
383
+ /**
384
+ * The token to use with the secrets manager.
385
+ */
386
+ let secretsToken: symbol;
387
+
388
+ /**
389
+ * Set of the token.
390
+ */
391
+ export function setToken(value: symbol): void {
392
+ secretsToken = value;
393
+ }
394
+
395
+ /**
396
+ * get the token.
397
+ */
398
+ export function getToken(): symbol {
399
+ return secretsToken;
400
+ }
401
+ }
@@ -1,4 +1,3 @@
1
- export const SECRETS_NAMESPACE = '@jupyterlite/ai';
2
1
  export const SECRETS_REPLACEMENT = '***';
3
2
 
4
3
  export function getSecretId(provider: string, label: string) {
package/src/tokens.ts CHANGED
@@ -5,6 +5,14 @@ import { JSONSchema7 } from 'json-schema';
5
5
 
6
6
  import { IBaseCompleter } from './base-completer';
7
7
 
8
+ export const PLUGIN_IDS = {
9
+ chat: '@jupyterlite/ai:chat',
10
+ chatCommandRegistry: '@jupyterlite/ai:autocompletion-registry',
11
+ completer: '@jupyterlite/ai:completer',
12
+ providerRegistry: '@jupyterlite/ai:provider-registry',
13
+ settingsConnector: '@jupyterlite/ai:settings-connector'
14
+ };
15
+
8
16
  export interface IDict<T = any> {
9
17
  [key: string]: T;
10
18
  }