@jupyterlite/ai 0.6.1 → 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.
@@ -5,7 +5,7 @@ export interface IBaseCompleter {
5
5
  /**
6
6
  * The LLM completer.
7
7
  */
8
- provider: BaseLanguageModel;
8
+ completer: BaseLanguageModel;
9
9
  /**
10
10
  * The completion prompt.
11
11
  */
@@ -3,7 +3,7 @@ import { BaseChatModel } from '@langchain/core/language_models/chat_models';
3
3
  import { BaseCompleter, IBaseCompleter } from '../../base-completer';
4
4
  export declare class AnthropicCompleter implements IBaseCompleter {
5
5
  constructor(options: BaseCompleter.IOptions);
6
- get provider(): BaseChatModel;
6
+ get completer(): BaseChatModel;
7
7
  /**
8
8
  * Getter and setter for the initial prompt.
9
9
  */
@@ -14,6 +14,6 @@ export declare class AnthropicCompleter implements IBaseCompleter {
14
14
  insertText: string;
15
15
  }[];
16
16
  }>;
17
- private _anthropicProvider;
17
+ private _completer;
18
18
  private _prompt;
19
19
  }
@@ -4,10 +4,10 @@ import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
4
4
  export class AnthropicCompleter {
5
5
  constructor(options) {
6
6
  this._prompt = COMPLETION_SYSTEM_PROMPT;
7
- this._anthropicProvider = new ChatAnthropic({ ...options.settings });
7
+ this._completer = new ChatAnthropic({ ...options.settings });
8
8
  }
9
- get provider() {
10
- return this._anthropicProvider;
9
+ get completer() {
10
+ return this._completer;
11
11
  }
12
12
  /**
13
13
  * Getter and setter for the initial prompt.
@@ -28,7 +28,7 @@ export class AnthropicCompleter {
28
28
  new AIMessage(trimmedPrompt)
29
29
  ];
30
30
  try {
31
- const response = await this._anthropicProvider.invoke(messages);
31
+ const response = await this._completer.invoke(messages);
32
32
  const items = [];
33
33
  // Anthropic can return string or complex content, a list of string/images/other.
34
34
  if (typeof response.content === 'string') {
@@ -8,12 +8,12 @@ export declare class ChromeCompleter implements IBaseCompleter {
8
8
  */
9
9
  get prompt(): string;
10
10
  set prompt(value: string);
11
- get provider(): LLM;
11
+ get completer(): LLM;
12
12
  fetch(request: CompletionHandler.IRequest, context: IInlineCompletionContext): Promise<{
13
13
  items: {
14
14
  insertText: string;
15
15
  }[];
16
16
  }>;
17
- private _chromeProvider;
17
+ private _completer;
18
18
  private _prompt;
19
19
  }
@@ -23,7 +23,7 @@ const CODE_BLOCK_END_REGEX = /```$/;
23
23
  export class ChromeCompleter {
24
24
  constructor(options) {
25
25
  this._prompt = COMPLETION_SYSTEM_PROMPT;
26
- this._chromeProvider = new ChromeAI({ ...options.settings });
26
+ this._completer = new ChromeAI({ ...options.settings });
27
27
  }
28
28
  /**
29
29
  * Getter and setter for the initial prompt.
@@ -34,8 +34,8 @@ export class ChromeCompleter {
34
34
  set prompt(value) {
35
35
  this._prompt = value;
36
36
  }
37
- get provider() {
38
- return this._chromeProvider;
37
+ get completer() {
38
+ return this._completer;
39
39
  }
40
40
  async fetch(request, context) {
41
41
  const { text, offset: cursorOffset } = request;
@@ -46,7 +46,7 @@ export class ChromeCompleter {
46
46
  new HumanMessage(trimmedPrompt)
47
47
  ];
48
48
  try {
49
- let response = await this._chromeProvider.invoke(messages);
49
+ let response = await this._completer.invoke(messages);
50
50
  // ChromeAI sometimes returns a string starting with '```',
51
51
  // so process the response to remove the code block delimiters
52
52
  if (CODE_BLOCK_START_REGEX.test(response)) {
@@ -3,7 +3,7 @@ import { BaseChatModel } from '@langchain/core/language_models/chat_models';
3
3
  import { BaseCompleter, IBaseCompleter } from '../../base-completer';
4
4
  export declare class CodestralCompleter implements IBaseCompleter {
5
5
  constructor(options: BaseCompleter.IOptions);
6
- get provider(): BaseChatModel;
6
+ get completer(): BaseChatModel;
7
7
  /**
8
8
  * Getter and setter for the initial prompt.
9
9
  */
@@ -11,6 +11,6 @@ export declare class CodestralCompleter implements IBaseCompleter {
11
11
  set prompt(value: string);
12
12
  fetch(request: CompletionHandler.IRequest, context: IInlineCompletionContext): Promise<any>;
13
13
  private _throttler;
14
- private _mistralProvider;
14
+ private _completer;
15
15
  private _prompt;
16
16
  }
@@ -9,9 +9,9 @@ const INTERVAL = 1000;
9
9
  export class CodestralCompleter {
10
10
  constructor(options) {
11
11
  this._prompt = COMPLETION_SYSTEM_PROMPT;
12
- this._mistralProvider = new ChatMistralAI({ ...options.settings });
12
+ this._completer = new ChatMistralAI({ ...options.settings });
13
13
  this._throttler = new Throttler(async (messages) => {
14
- const response = await this._mistralProvider.invoke(messages);
14
+ const response = await this._completer.invoke(messages);
15
15
  // Extract results of completion request.
16
16
  const items = [];
17
17
  if (typeof response.content === 'string') {
@@ -32,8 +32,8 @@ export class CodestralCompleter {
32
32
  return { items };
33
33
  }, { limit: INTERVAL });
34
34
  }
35
- get provider() {
36
- return this._mistralProvider;
35
+ get completer() {
36
+ return this._completer;
37
37
  }
38
38
  /**
39
39
  * Getter and setter for the initial prompt.
@@ -3,7 +3,7 @@ import { BaseChatModel } from '@langchain/core/language_models/chat_models';
3
3
  import { BaseCompleter, IBaseCompleter } from '../../base-completer';
4
4
  export declare class OpenAICompleter implements IBaseCompleter {
5
5
  constructor(options: BaseCompleter.IOptions);
6
- get provider(): BaseChatModel;
6
+ get completer(): BaseChatModel;
7
7
  /**
8
8
  * Getter and setter for the initial prompt.
9
9
  */
@@ -14,6 +14,6 @@ export declare class OpenAICompleter implements IBaseCompleter {
14
14
  insertText: string;
15
15
  }[];
16
16
  }>;
17
- private _openAIProvider;
17
+ private _completer;
18
18
  private _prompt;
19
19
  }
@@ -4,10 +4,10 @@ import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
4
4
  export class OpenAICompleter {
5
5
  constructor(options) {
6
6
  this._prompt = COMPLETION_SYSTEM_PROMPT;
7
- this._openAIProvider = new ChatOpenAI({ ...options.settings });
7
+ this._completer = new ChatOpenAI({ ...options.settings });
8
8
  }
9
- get provider() {
10
- return this._openAIProvider;
9
+ get completer() {
10
+ return this._completer;
11
11
  }
12
12
  /**
13
13
  * Getter and setter for the initial prompt.
@@ -23,7 +23,7 @@ export class OpenAICompleter {
23
23
  const prompt = text.slice(0, cursorOffset);
24
24
  const messages = [new SystemMessage(this._prompt), new AIMessage(prompt)];
25
25
  try {
26
- const response = await this._openAIProvider.invoke(messages);
26
+ const response = await this._completer.invoke(messages);
27
27
  const items = [];
28
28
  if (typeof response.content === 'string') {
29
29
  items.push({
package/lib/provider.js CHANGED
@@ -153,7 +153,9 @@ export class AIProviderRegistry {
153
153
  if (settings[key] === SECRETS_REPLACEMENT) {
154
154
  const id = getSecretId(name, key);
155
155
  const secrets = await ((_b = this._secretsManager) === null || _b === void 0 ? void 0 : _b.get(Private.getToken(), SECRETS_NAMESPACE, id));
156
- fullSettings[key] = (secrets === null || secrets === void 0 ? void 0 : secrets.value) || settings[key];
156
+ if (secrets !== undefined) {
157
+ fullSettings[key] = secrets.value;
158
+ }
157
159
  continue;
158
160
  }
159
161
  fullSettings[key] = settings[key];
@@ -161,7 +163,7 @@ export class AIProviderRegistry {
161
163
  if (((_c = this._currentProvider) === null || _c === void 0 ? void 0 : _c.completer) !== undefined) {
162
164
  try {
163
165
  this._completer = new this._currentProvider.completer({
164
- ...fullSettings
166
+ settings: fullSettings
165
167
  });
166
168
  this._completerError = '';
167
169
  }
@@ -37,7 +37,12 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
37
37
  * Save settings in local storage for a given provider.
38
38
  */
39
39
  saveSettings(value: IDict<any>): void;
40
- private updateUseSecretsManager;
40
+ /**
41
+ * Update the settings whether the secrets manager is used or not.
42
+ *
43
+ * @param value - whether to use the secrets manager or not.
44
+ */
45
+ private _updateUseSecretsManager;
41
46
  /**
42
47
  * Build the schema for a given provider.
43
48
  */
@@ -80,6 +85,5 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
80
85
  private _uiSchema;
81
86
  private _settings;
82
87
  private _formRef;
83
- private _unsavedFields;
84
- private _formInputs;
88
+ private _secretFields;
85
89
  }
@@ -1,5 +1,4 @@
1
1
  import { FormComponent } from '@jupyterlab/ui-components';
2
- import { ArrayExt } from '@lumino/algorithm';
3
2
  import { JSONExt } from '@lumino/coreutils';
4
3
  import validator from '@rjsf/validator-ajv8';
5
4
  import React from 'react';
@@ -30,23 +29,25 @@ export class AiSettings extends React.Component {
30
29
  constructor(props) {
31
30
  var _a, _b, _c, _d, _e, _f;
32
31
  super(props);
33
- this.updateUseSecretsManager = (value) => {
32
+ /**
33
+ * Update the settings whether the secrets manager is used or not.
34
+ *
35
+ * @param value - whether to use the secrets manager or not.
36
+ */
37
+ this._updateUseSecretsManager = (value) => {
34
38
  var _a;
35
39
  this._useSecretsManager = value;
36
40
  if (!value) {
37
41
  // Detach all the password inputs attached to the secrets manager, and save the
38
42
  // current settings to the local storage to save the password.
39
43
  (_a = this._secretsManager) === null || _a === void 0 ? void 0 : _a.detachAll(Private.getToken(), SECRETS_NAMESPACE);
40
- this._formInputs = [];
41
- this._unsavedFields = [];
42
44
  if (this._settingConnector instanceof SettingConnector) {
43
45
  this._settingConnector.doNotSave = [];
44
46
  }
45
47
  this.saveSettings(this._currentSettings);
46
48
  }
47
49
  else {
48
- // Remove all the keys stored locally and attach the password inputs to the
49
- // secrets manager.
50
+ // Remove all the keys stored locally.
50
51
  const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
51
52
  Object.keys(settings).forEach(provider => {
52
53
  Object.keys(settings[provider])
@@ -56,6 +57,11 @@ export class AiSettings extends React.Component {
56
57
  });
57
58
  });
58
59
  localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
60
+ // Update the fields not to save in settings.
61
+ if (this._settingConnector instanceof SettingConnector) {
62
+ this._settingConnector.doNotSave = this._secretFields;
63
+ }
64
+ // Attach the password inputs to the secrets manager.
59
65
  this.componentDidUpdate();
60
66
  }
61
67
  this._settings
@@ -105,8 +111,7 @@ export class AiSettings extends React.Component {
105
111
  this._currentSettings = { provider: 'None' };
106
112
  this._uiSchema = {};
107
113
  this._formRef = React.createRef();
108
- this._unsavedFields = [];
109
- this._formInputs = [];
114
+ this._secretFields = [];
110
115
  if (!props.formContext.providerRegistry) {
111
116
  throw new Error('The provider registry is needed to enable the jupyterlite-ai settings panel');
112
117
  }
@@ -160,7 +165,7 @@ export class AiSettings extends React.Component {
160
165
  var _a, _b;
161
166
  const useSecretsManager = (_a = this._settings.get('UseSecretsManager').composite) !== null && _a !== void 0 ? _a : true;
162
167
  if (useSecretsManager !== this._useSecretsManager) {
163
- this.updateUseSecretsManager(useSecretsManager);
168
+ this._updateUseSecretsManager(useSecretsManager);
164
169
  }
165
170
  const hideSecretFields = (_b = this._settings.get('HideSecretFields').composite) !== null && _b !== void 0 ? _b : true;
166
171
  if (hideSecretFields !== this._hideSecretFields) {
@@ -174,27 +179,18 @@ export class AiSettings extends React.Component {
174
179
  if (!this._secretsManager || !this._useSecretsManager) {
175
180
  return;
176
181
  }
177
- // Attach the password inputs to the secrets manager only if they have changed.
178
- const inputs = ((_a = this._formRef.current) === null || _a === void 0 ? void 0 : _a.getElementsByTagName('input')) || [];
179
- if (ArrayExt.shallowEqual(inputs, this._formInputs)) {
180
- return;
181
- }
182
+ // Attach the password inputs to the secrets manager.
182
183
  await this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
183
- this._formInputs = [...inputs];
184
- this._unsavedFields = [];
184
+ const inputs = ((_a = this._formRef.current) === null || _a === void 0 ? void 0 : _a.getElementsByTagName('input')) || [];
185
185
  for (let i = 0; i < inputs.length; i++) {
186
186
  if (inputs[i].type.toLowerCase() === 'password') {
187
187
  const label = inputs[i].getAttribute('label');
188
188
  if (label) {
189
189
  const id = getSecretId(this._provider, label);
190
190
  this._secretsManager.attach(Private.getToken(), SECRETS_NAMESPACE, id, inputs[i], (value) => this._onPasswordUpdated(label, value));
191
- this._unsavedFields.push(label);
192
191
  }
193
192
  }
194
193
  }
195
- if (this._settingConnector instanceof SettingConnector) {
196
- this._settingConnector.doNotSave = this._unsavedFields;
197
- }
198
194
  }
199
195
  componentWillUnmount() {
200
196
  if (!this._secretsManager || !this._useSecretsManager) {
@@ -233,7 +229,10 @@ export class AiSettings extends React.Component {
233
229
  var _a;
234
230
  const currentSettings = { ...value };
235
231
  const settings = JSON.parse((_a = localStorage.getItem(STORAGE_NAME)) !== null && _a !== void 0 ? _a : '{}');
236
- this._unsavedFields.forEach(field => delete currentSettings[field]);
232
+ // Do not save secrets in local storage if using the secrets manager.
233
+ if (this._secretsManager && this._useSecretsManager) {
234
+ this._secretFields.forEach(field => delete currentSettings[field]);
235
+ }
237
236
  settings[this._provider] = currentSettings;
238
237
  localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
239
238
  }
@@ -244,9 +243,11 @@ export class AiSettings extends React.Component {
244
243
  const schema = JSONExt.deepCopy(baseSettings);
245
244
  this._uiSchema = {};
246
245
  const settingsSchema = this._providerRegistry.getSettingsSchema(this._provider);
246
+ this._secretFields = [];
247
247
  if (settingsSchema) {
248
248
  Object.entries(settingsSchema).forEach(([key, value]) => {
249
249
  if (key.toLowerCase().includes('key')) {
250
+ this._secretFields.push(key);
250
251
  if (this._hideSecretFields) {
251
252
  return;
252
253
  }
@@ -255,6 +256,12 @@ export class AiSettings extends React.Component {
255
256
  schema.properties[key] = value;
256
257
  });
257
258
  }
259
+ // Do not save secrets in settings if using the secrets manager.
260
+ if (this._secretsManager &&
261
+ this._useSecretsManager &&
262
+ this._settingConnector instanceof SettingConnector) {
263
+ this._settingConnector.doNotSave = this._secretFields;
264
+ }
258
265
  return schema;
259
266
  }
260
267
  /**
@@ -49,10 +49,10 @@ export class SettingConnector extends DataConnector {
49
49
  }
50
50
  async save(id, raw) {
51
51
  const settings = json5.parse(raw);
52
+ // Replace secrets fields with the replacement string.
53
+ // Create the field if it does not exist in settings.
52
54
  this._doNotSave.forEach(field => {
53
- if (settings['AIprovider'] !== undefined &&
54
- settings['AIprovider'][field] !== undefined &&
55
- settings['AIprovider'][field] !== '') {
55
+ if (settings['AIprovider'] !== undefined) {
56
56
  settings['AIprovider'][field] = SECRETS_REPLACEMENT;
57
57
  }
58
58
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupyterlite/ai",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "AI code completions and chat for JupyterLite",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -75,7 +75,7 @@
75
75
  "@lumino/signaling": "^2.1.2",
76
76
  "@mui/icons-material": "^5.11.0",
77
77
  "@mui/material": "^5.11.0",
78
- "@rjsf/core": "^4.2.0",
78
+ "@rjsf/core": "^5.18.4",
79
79
  "@rjsf/utils": "^5.18.4",
80
80
  "@rjsf/validator-ajv8": "^5.18.4",
81
81
  "json5": "^2.2.3",
@@ -9,7 +9,7 @@ export interface IBaseCompleter {
9
9
  /**
10
10
  * The LLM completer.
11
11
  */
12
- provider: BaseLanguageModel;
12
+ completer: BaseLanguageModel;
13
13
 
14
14
  /**
15
15
  * The completion prompt.
@@ -11,11 +11,11 @@ import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
11
11
 
12
12
  export class AnthropicCompleter implements IBaseCompleter {
13
13
  constructor(options: BaseCompleter.IOptions) {
14
- this._anthropicProvider = new ChatAnthropic({ ...options.settings });
14
+ this._completer = new ChatAnthropic({ ...options.settings });
15
15
  }
16
16
 
17
- get provider(): BaseChatModel {
18
- return this._anthropicProvider;
17
+ get completer(): BaseChatModel {
18
+ return this._completer;
19
19
  }
20
20
 
21
21
  /**
@@ -44,7 +44,7 @@ export class AnthropicCompleter implements IBaseCompleter {
44
44
  ];
45
45
 
46
46
  try {
47
- const response = await this._anthropicProvider.invoke(messages);
47
+ const response = await this._completer.invoke(messages);
48
48
  const items = [];
49
49
 
50
50
  // Anthropic can return string or complex content, a list of string/images/other.
@@ -70,6 +70,6 @@ export class AnthropicCompleter implements IBaseCompleter {
70
70
  }
71
71
  }
72
72
 
73
- private _anthropicProvider: ChatAnthropic;
73
+ private _completer: ChatAnthropic;
74
74
  private _prompt: string = COMPLETION_SYSTEM_PROMPT;
75
75
  }
@@ -32,7 +32,7 @@ const CODE_BLOCK_END_REGEX = /```$/;
32
32
 
33
33
  export class ChromeCompleter implements IBaseCompleter {
34
34
  constructor(options: BaseCompleter.IOptions) {
35
- this._chromeProvider = new ChromeAI({ ...options.settings });
35
+ this._completer = new ChromeAI({ ...options.settings });
36
36
  }
37
37
 
38
38
  /**
@@ -45,8 +45,8 @@ export class ChromeCompleter implements IBaseCompleter {
45
45
  this._prompt = value;
46
46
  }
47
47
 
48
- get provider(): LLM {
49
- return this._chromeProvider;
48
+ get completer(): LLM {
49
+ return this._completer;
50
50
  }
51
51
 
52
52
  async fetch(
@@ -64,7 +64,7 @@ export class ChromeCompleter implements IBaseCompleter {
64
64
  ];
65
65
 
66
66
  try {
67
- let response = await this._chromeProvider.invoke(messages);
67
+ let response = await this._completer.invoke(messages);
68
68
 
69
69
  // ChromeAI sometimes returns a string starting with '```',
70
70
  // so process the response to remove the code block delimiters
@@ -84,6 +84,6 @@ export class ChromeCompleter implements IBaseCompleter {
84
84
  }
85
85
  }
86
86
 
87
- private _chromeProvider: ChromeAI;
87
+ private _completer: ChromeAI;
88
88
  private _prompt: string = COMPLETION_SYSTEM_PROMPT;
89
89
  }
@@ -21,10 +21,10 @@ const INTERVAL = 1000;
21
21
 
22
22
  export class CodestralCompleter implements IBaseCompleter {
23
23
  constructor(options: BaseCompleter.IOptions) {
24
- this._mistralProvider = new ChatMistralAI({ ...options.settings });
24
+ this._completer = new ChatMistralAI({ ...options.settings });
25
25
  this._throttler = new Throttler(
26
26
  async (messages: BaseMessage[]) => {
27
- const response = await this._mistralProvider.invoke(messages);
27
+ const response = await this._completer.invoke(messages);
28
28
  // Extract results of completion request.
29
29
  const items = [];
30
30
  if (typeof response.content === 'string') {
@@ -47,8 +47,8 @@ export class CodestralCompleter implements IBaseCompleter {
47
47
  );
48
48
  }
49
49
 
50
- get provider(): BaseChatModel {
51
- return this._mistralProvider;
50
+ get completer(): BaseChatModel {
51
+ return this._completer;
52
52
  }
53
53
 
54
54
  /**
@@ -82,6 +82,6 @@ export class CodestralCompleter implements IBaseCompleter {
82
82
  }
83
83
 
84
84
  private _throttler: Throttler;
85
- private _mistralProvider: ChatMistralAI;
85
+ private _completer: ChatMistralAI;
86
86
  private _prompt: string = COMPLETION_SYSTEM_PROMPT;
87
87
  }
@@ -11,11 +11,11 @@ import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
11
11
 
12
12
  export class OpenAICompleter implements IBaseCompleter {
13
13
  constructor(options: BaseCompleter.IOptions) {
14
- this._openAIProvider = new ChatOpenAI({ ...options.settings });
14
+ this._completer = new ChatOpenAI({ ...options.settings });
15
15
  }
16
16
 
17
- get provider(): BaseChatModel {
18
- return this._openAIProvider;
17
+ get completer(): BaseChatModel {
18
+ return this._completer;
19
19
  }
20
20
 
21
21
  /**
@@ -38,7 +38,7 @@ export class OpenAICompleter implements IBaseCompleter {
38
38
  const messages = [new SystemMessage(this._prompt), new AIMessage(prompt)];
39
39
 
40
40
  try {
41
- const response = await this._openAIProvider.invoke(messages);
41
+ const response = await this._completer.invoke(messages);
42
42
  const items = [];
43
43
  if (typeof response.content === 'string') {
44
44
  items.push({
@@ -62,6 +62,6 @@ export class OpenAICompleter implements IBaseCompleter {
62
62
  }
63
63
  }
64
64
 
65
- private _openAIProvider: ChatOpenAI;
65
+ private _completer: ChatOpenAI;
66
66
  private _prompt: string = COMPLETION_SYSTEM_PROMPT;
67
67
  }
package/src/provider.ts CHANGED
@@ -176,7 +176,9 @@ export class AIProviderRegistry implements IAIProviderRegistry {
176
176
  SECRETS_NAMESPACE,
177
177
  id
178
178
  );
179
- fullSettings[key] = secrets?.value || settings[key];
179
+ if (secrets !== undefined) {
180
+ fullSettings[key] = secrets.value;
181
+ }
180
182
  continue;
181
183
  }
182
184
  fullSettings[key] = settings[key];
@@ -185,7 +187,7 @@ export class AIProviderRegistry implements IAIProviderRegistry {
185
187
  if (this._currentProvider?.completer !== undefined) {
186
188
  try {
187
189
  this._completer = new this._currentProvider.completer({
188
- ...fullSettings
190
+ settings: fullSettings
189
191
  });
190
192
  this._completerError = '';
191
193
  } catch (e: any) {
@@ -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';
@@ -122,7 +121,7 @@ export class AiSettings extends React.Component<
122
121
  const useSecretsManager =
123
122
  (this._settings.get('UseSecretsManager').composite as boolean) ?? true;
124
123
  if (useSecretsManager !== this._useSecretsManager) {
125
- this.updateUseSecretsManager(useSecretsManager);
124
+ this._updateUseSecretsManager(useSecretsManager);
126
125
  }
127
126
  const hideSecretFields =
128
127
  (this._settings.get('HideSecretFields').composite as boolean) ?? true;
@@ -137,15 +136,10 @@ export class AiSettings extends React.Component<
137
136
  if (!this._secretsManager || !this._useSecretsManager) {
138
137
  return;
139
138
  }
140
- // Attach the password inputs to the secrets manager only if they have changed.
141
- const inputs = this._formRef.current?.getElementsByTagName('input') || [];
142
- if (ArrayExt.shallowEqual(inputs, this._formInputs)) {
143
- return;
144
- }
145
139
 
140
+ // Attach the password inputs to the secrets manager.
146
141
  await this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
147
- this._formInputs = [...inputs];
148
- this._unsavedFields = [];
142
+ const inputs = this._formRef.current?.getElementsByTagName('input') || [];
149
143
  for (let i = 0; i < inputs.length; i++) {
150
144
  if (inputs[i].type.toLowerCase() === 'password') {
151
145
  const label = inputs[i].getAttribute('label');
@@ -158,13 +152,9 @@ export class AiSettings extends React.Component<
158
152
  inputs[i],
159
153
  (value: string) => this._onPasswordUpdated(label, value)
160
154
  );
161
- this._unsavedFields.push(label);
162
155
  }
163
156
  }
164
157
  }
165
- if (this._settingConnector instanceof SettingConnector) {
166
- this._settingConnector.doNotSave = this._unsavedFields;
167
- }
168
158
  }
169
159
 
170
160
  componentWillUnmount(): void {
@@ -205,26 +195,31 @@ export class AiSettings extends React.Component<
205
195
  saveSettings(value: IDict<any>) {
206
196
  const currentSettings = { ...value };
207
197
  const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) ?? '{}');
208
- 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
+ }
209
202
  settings[this._provider] = currentSettings;
210
203
  localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
211
204
  }
212
205
 
213
- 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) => {
214
212
  this._useSecretsManager = value;
215
213
  if (!value) {
216
214
  // Detach all the password inputs attached to the secrets manager, and save the
217
215
  // current settings to the local storage to save the password.
218
216
  this._secretsManager?.detachAll(Private.getToken(), SECRETS_NAMESPACE);
219
- this._formInputs = [];
220
- this._unsavedFields = [];
221
217
  if (this._settingConnector instanceof SettingConnector) {
222
218
  this._settingConnector.doNotSave = [];
223
219
  }
224
220
  this.saveSettings(this._currentSettings);
225
221
  } else {
226
- // Remove all the keys stored locally and attach the password inputs to the
227
- // secrets manager.
222
+ // Remove all the keys stored locally.
228
223
  const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
229
224
  Object.keys(settings).forEach(provider => {
230
225
  Object.keys(settings[provider])
@@ -234,6 +229,11 @@ export class AiSettings extends React.Component<
234
229
  });
235
230
  });
236
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.
237
237
  this.componentDidUpdate();
238
238
  }
239
239
  this._settings
@@ -251,9 +251,11 @@ export class AiSettings extends React.Component<
251
251
  this._provider
252
252
  );
253
253
 
254
+ this._secretFields = [];
254
255
  if (settingsSchema) {
255
256
  Object.entries(settingsSchema).forEach(([key, value]) => {
256
257
  if (key.toLowerCase().includes('key')) {
258
+ this._secretFields.push(key);
257
259
  if (this._hideSecretFields) {
258
260
  return;
259
261
  }
@@ -262,6 +264,15 @@ export class AiSettings extends React.Component<
262
264
  schema.properties[key] = value;
263
265
  });
264
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
+ }
265
276
  return schema as JSONSchema7;
266
277
  }
267
278
 
@@ -375,8 +386,7 @@ export class AiSettings extends React.Component<
375
386
  private _uiSchema: IDict<any> = {};
376
387
  private _settings: ISettingRegistry.ISettings;
377
388
  private _formRef = React.createRef<HTMLDivElement>();
378
- private _unsavedFields: string[] = [];
379
- private _formInputs: HTMLInputElement[] = [];
389
+ private _secretFields: string[] = [];
380
390
  }
381
391
 
382
392
  namespace Private {
@@ -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
  });