@jupyterlite/ai 0.6.0 → 0.6.2

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.
@@ -4,7 +4,6 @@ import {
4
4
  ISettingRegistry
5
5
  } from '@jupyterlab/settingregistry';
6
6
  import { FormComponent, IFormRenderer } from '@jupyterlab/ui-components';
7
- import { ArrayExt } from '@lumino/algorithm';
8
7
  import { JSONExt } from '@lumino/coreutils';
9
8
  import { IChangeEvent } from '@rjsf/core';
10
9
  import type { FieldProps } from '@rjsf/utils';
@@ -13,20 +12,27 @@ import { JSONSchema7 } from 'json-schema';
13
12
  import { ISecretsManager } from 'jupyter-secrets-manager';
14
13
  import React from 'react';
15
14
 
16
- import { getSecretId, SECRETS_NAMESPACE, SettingConnector } from '.';
15
+ import { getSecretId, SettingConnector } from '.';
17
16
  import baseSettings from './base.json';
18
- import { IAIProviderRegistry, IDict } from '../tokens';
17
+ import { IAIProviderRegistry, IDict, PLUGIN_IDS } from '../tokens';
19
18
 
20
19
  const MD_MIME_TYPE = 'text/markdown';
21
20
  const STORAGE_NAME = '@jupyterlite/ai:settings';
22
21
  const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
22
+ const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
23
23
 
24
24
  export const aiSettingsRenderer = (options: {
25
25
  providerRegistry: IAIProviderRegistry;
26
+ secretsToken?: symbol;
26
27
  rmRegistry?: IRenderMimeRegistry;
27
28
  secretsManager?: ISecretsManager;
28
29
  settingConnector?: ISettingConnector;
29
30
  }): IFormRenderer => {
31
+ const { secretsToken } = options;
32
+ delete options.secretsToken;
33
+ if (secretsToken) {
34
+ Private.setToken(secretsToken);
35
+ }
30
36
  return {
31
37
  fieldRenderer: (props: FieldProps) => {
32
38
  props.formContext = { ...props.formContext, ...options };
@@ -63,6 +69,8 @@ export class AiSettings extends React.Component<
63
69
 
64
70
  this._useSecretsManager =
65
71
  (this._settings.get('UseSecretsManager').composite as boolean) ?? true;
72
+ this._hideSecretFields =
73
+ (this._settings.get('HideSecretFields').composite as boolean) ?? true;
66
74
 
67
75
  // Initialize the providers schema.
68
76
  const providerSchema = JSONExt.deepCopy(baseSettings) as any;
@@ -113,7 +121,13 @@ export class AiSettings extends React.Component<
113
121
  const useSecretsManager =
114
122
  (this._settings.get('UseSecretsManager').composite as boolean) ?? true;
115
123
  if (useSecretsManager !== this._useSecretsManager) {
116
- this.updateUseSecretsManager(useSecretsManager);
124
+ this._updateUseSecretsManager(useSecretsManager);
125
+ }
126
+ const hideSecretFields =
127
+ (this._settings.get('HideSecretFields').composite as boolean) ?? true;
128
+ if (hideSecretFields !== this._hideSecretFields) {
129
+ this._hideSecretFields = hideSecretFields;
130
+ this._updateSchema();
117
131
  }
118
132
  });
119
133
  }
@@ -122,33 +136,32 @@ export class AiSettings extends React.Component<
122
136
  if (!this._secretsManager || !this._useSecretsManager) {
123
137
  return;
124
138
  }
125
- // Attach the password inputs to the secrets manager only if they have changed.
126
- const inputs = this._formRef.current?.getElementsByTagName('input') || [];
127
- if (ArrayExt.shallowEqual(inputs, this._formInputs)) {
128
- return;
129
- }
130
139
 
131
- await this._secretsManager.detachAll(SECRETS_NAMESPACE);
132
- this._formInputs = [...inputs];
133
- this._unsavedFields = [];
140
+ // Attach the password inputs to the secrets manager.
141
+ await this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
142
+ const inputs = this._formRef.current?.getElementsByTagName('input') || [];
134
143
  for (let i = 0; i < inputs.length; i++) {
135
144
  if (inputs[i].type.toLowerCase() === 'password') {
136
145
  const label = inputs[i].getAttribute('label');
137
146
  if (label) {
138
147
  const id = getSecretId(this._provider, label);
139
148
  this._secretsManager.attach(
149
+ Private.getToken(),
140
150
  SECRETS_NAMESPACE,
141
151
  id,
142
152
  inputs[i],
143
153
  (value: string) => this._onPasswordUpdated(label, value)
144
154
  );
145
- this._unsavedFields.push(label);
146
155
  }
147
156
  }
148
157
  }
149
- if (this._settingConnector instanceof SettingConnector) {
150
- this._settingConnector.doNotSave = this._unsavedFields;
158
+ }
159
+
160
+ componentWillUnmount(): void {
161
+ if (!this._secretsManager || !this._useSecretsManager) {
162
+ return;
151
163
  }
164
+ this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
152
165
  }
153
166
 
154
167
  /**
@@ -182,26 +195,31 @@ export class AiSettings extends React.Component<
182
195
  saveSettings(value: IDict<any>) {
183
196
  const currentSettings = { ...value };
184
197
  const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) ?? '{}');
185
- this._unsavedFields.forEach(field => delete currentSettings[field]);
198
+ // Do not save secrets in local storage if using the secrets manager.
199
+ if (this._secretsManager && this._useSecretsManager) {
200
+ this._secretFields.forEach(field => delete currentSettings[field]);
201
+ }
186
202
  settings[this._provider] = currentSettings;
187
203
  localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
188
204
  }
189
205
 
190
- private updateUseSecretsManager = (value: boolean) => {
206
+ /**
207
+ * Update the settings whether the secrets manager is used or not.
208
+ *
209
+ * @param value - whether to use the secrets manager or not.
210
+ */
211
+ private _updateUseSecretsManager = (value: boolean) => {
191
212
  this._useSecretsManager = value;
192
213
  if (!value) {
193
214
  // Detach all the password inputs attached to the secrets manager, and save the
194
215
  // current settings to the local storage to save the password.
195
- this._secretsManager?.detachAll(SECRETS_NAMESPACE);
196
- this._formInputs = [];
197
- this._unsavedFields = [];
216
+ this._secretsManager?.detachAll(Private.getToken(), SECRETS_NAMESPACE);
198
217
  if (this._settingConnector instanceof SettingConnector) {
199
218
  this._settingConnector.doNotSave = [];
200
219
  }
201
220
  this.saveSettings(this._currentSettings);
202
221
  } else {
203
- // Remove all the keys stored locally and attach the password inputs to the
204
- // secrets manager.
222
+ // Remove all the keys stored locally.
205
223
  const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
206
224
  Object.keys(settings).forEach(provider => {
207
225
  Object.keys(settings[provider])
@@ -211,6 +229,11 @@ export class AiSettings extends React.Component<
211
229
  });
212
230
  });
213
231
  localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
232
+ // Update the fields not to save in settings.
233
+ if (this._settingConnector instanceof SettingConnector) {
234
+ this._settingConnector.doNotSave = this._secretFields;
235
+ }
236
+ // Attach the password inputs to the secrets manager.
214
237
  this.componentDidUpdate();
215
238
  }
216
239
  this._settings
@@ -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
  */
@@ -238,12 +251,28 @@ export class AiSettings extends React.Component<
238
251
  this._provider
239
252
  );
240
253
 
254
+ this._secretFields = [];
241
255
  if (settingsSchema) {
242
256
  Object.entries(settingsSchema).forEach(([key, value]) => {
257
+ if (key.toLowerCase().includes('key')) {
258
+ this._secretFields.push(key);
259
+ if (this._hideSecretFields) {
260
+ return;
261
+ }
262
+ this._uiSchema[key] = { 'ui:widget': 'password' };
263
+ }
243
264
  schema.properties[key] = value;
244
- this._updateUiSchema(key);
245
265
  });
246
266
  }
267
+
268
+ // Do not save secrets in settings if using the secrets manager.
269
+ if (
270
+ this._secretsManager &&
271
+ this._useSecretsManager &&
272
+ this._settingConnector instanceof SettingConnector
273
+ ) {
274
+ this._settingConnector.doNotSave = this._secretFields;
275
+ }
247
276
  return schema as JSONSchema7;
248
277
  }
249
278
 
@@ -349,6 +378,7 @@ export class AiSettings extends React.Component<
349
378
  private _provider: string;
350
379
  private _providerSchema: JSONSchema7;
351
380
  private _useSecretsManager: boolean;
381
+ private _hideSecretFields: boolean;
352
382
  private _rmRegistry: IRenderMimeRegistry | null;
353
383
  private _secretsManager: ISecretsManager | null;
354
384
  private _settingConnector: ISettingConnector | null;
@@ -356,6 +386,26 @@ export class AiSettings extends React.Component<
356
386
  private _uiSchema: IDict<any> = {};
357
387
  private _settings: ISettingRegistry.ISettings;
358
388
  private _formRef = React.createRef<HTMLDivElement>();
359
- private _unsavedFields: string[] = [];
360
- private _formInputs: HTMLInputElement[] = [];
389
+ private _secretFields: string[] = [];
390
+ }
391
+
392
+ namespace Private {
393
+ /**
394
+ * The token to use with the secrets manager.
395
+ */
396
+ let secretsToken: symbol;
397
+
398
+ /**
399
+ * Set of the token.
400
+ */
401
+ export function setToken(value: symbol): void {
402
+ secretsToken = value;
403
+ }
404
+
405
+ /**
406
+ * get the token.
407
+ */
408
+ export function getToken(): symbol {
409
+ return secretsToken;
410
+ }
361
411
  }
@@ -71,12 +71,11 @@ export class SettingConnector
71
71
 
72
72
  async save(id: string, raw: string): Promise<void> {
73
73
  const settings = json5.parse(raw);
74
+
75
+ // Replace secrets fields with the replacement string.
76
+ // Create the field if it does not exist in settings.
74
77
  this._doNotSave.forEach(field => {
75
- if (
76
- settings['AIprovider'] !== undefined &&
77
- settings['AIprovider'][field] !== undefined &&
78
- settings['AIprovider'][field] !== ''
79
- ) {
78
+ if (settings['AIprovider'] !== undefined) {
80
79
  settings['AIprovider'][field] = SECRETS_REPLACEMENT;
81
80
  }
82
81
  });
@@ -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
  }