@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.
@@ -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/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,8 +152,10 @@ 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));
153
- fullSettings[key] = (secrets === null || secrets === void 0 ? void 0 : secrets.value) || settings[key];
155
+ const secrets = await ((_b = this._secretsManager) === null || _b === void 0 ? void 0 : _b.get(Private.getToken(), SECRETS_NAMESPACE, id));
156
+ if (secrets !== undefined) {
157
+ fullSettings[key] = secrets.value;
158
+ }
154
159
  continue;
155
160
  }
156
161
  fullSettings[key] = settings[key];
@@ -158,7 +163,7 @@ export class AIProviderRegistry {
158
163
  if (((_c = this._currentProvider) === null || _c === void 0 ? void 0 : _c.completer) !== undefined) {
159
164
  try {
160
165
  this._completer = new this._currentProvider.completer({
161
- ...fullSettings
166
+ settings: fullSettings
162
167
  });
163
168
  this._completerError = '';
164
169
  }
@@ -231,3 +236,24 @@ export class AIProviderRegistry {
231
236
  }
232
237
  AIProviderRegistry.updateConfig = updateConfig;
233
238
  })(AIProviderRegistry || (AIProviderRegistry = {}));
239
+ var Private;
240
+ (function (Private) {
241
+ /**
242
+ * The token to use with the secrets manager.
243
+ */
244
+ let secretsToken;
245
+ /**
246
+ * Set of the token.
247
+ */
248
+ function setToken(value) {
249
+ secretsToken = value;
250
+ }
251
+ Private.setToken = setToken;
252
+ /**
253
+ * get the token.
254
+ */
255
+ function getToken() {
256
+ return secretsToken;
257
+ }
258
+ Private.getToken = getToken;
259
+ })(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
  */
@@ -35,12 +37,12 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
35
37
  * Save settings in local storage for a given provider.
36
38
  */
37
39
  saveSettings(value: IDict<any>): void;
38
- private updateUseSecretsManager;
39
40
  /**
40
- * Update the UI schema of the form.
41
- * Currently use to hide API keys.
41
+ * Update the settings whether the secrets manager is used or not.
42
+ *
43
+ * @param value - whether to use the secrets manager or not.
42
44
  */
43
- private _updateUiSchema;
45
+ private _updateUseSecretsManager;
44
46
  /**
45
47
  * Build the schema for a given provider.
46
48
  */
@@ -75,6 +77,7 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
75
77
  private _provider;
76
78
  private _providerSchema;
77
79
  private _useSecretsManager;
80
+ private _hideSecretFields;
78
81
  private _rmRegistry;
79
82
  private _secretsManager;
80
83
  private _settingConnector;
@@ -82,6 +85,5 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
82
85
  private _uiSchema;
83
86
  private _settings;
84
87
  private _formRef;
85
- private _unsavedFields;
86
- private _formInputs;
88
+ private _secretFields;
87
89
  }
@@ -1,14 +1,20 @@
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';
6
- import { getSecretId, SECRETS_NAMESPACE, SettingConnector } from '.';
5
+ import { getSecretId, SettingConnector } from '.';
7
6
  import baseSettings from './base.json';
7
+ import { PLUGIN_IDS } from '../tokens';
8
8
  const MD_MIME_TYPE = 'text/markdown';
9
9
  const STORAGE_NAME = '@jupyterlite/ai:settings';
10
10
  const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
11
+ const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
11
12
  export const aiSettingsRenderer = (options) => {
13
+ const { secretsToken } = options;
14
+ delete options.secretsToken;
15
+ if (secretsToken) {
16
+ Private.setToken(secretsToken);
17
+ }
12
18
  return {
13
19
  fieldRenderer: (props) => {
14
20
  props.formContext = { ...props.formContext, ...options };
@@ -21,25 +27,27 @@ const WrappedFormComponent = (props) => {
21
27
  };
22
28
  export class AiSettings extends React.Component {
23
29
  constructor(props) {
24
- var _a, _b, _c, _d, _e;
30
+ var _a, _b, _c, _d, _e, _f;
25
31
  super(props);
26
- 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) => {
27
38
  var _a;
28
39
  this._useSecretsManager = value;
29
40
  if (!value) {
30
41
  // Detach all the password inputs attached to the secrets manager, and save the
31
42
  // current settings to the local storage to save the password.
32
- (_a = this._secretsManager) === null || _a === void 0 ? void 0 : _a.detachAll(SECRETS_NAMESPACE);
33
- this._formInputs = [];
34
- this._unsavedFields = [];
43
+ (_a = this._secretsManager) === null || _a === void 0 ? void 0 : _a.detachAll(Private.getToken(), SECRETS_NAMESPACE);
35
44
  if (this._settingConnector instanceof SettingConnector) {
36
45
  this._settingConnector.doNotSave = [];
37
46
  }
38
47
  this.saveSettings(this._currentSettings);
39
48
  }
40
49
  else {
41
- // Remove all the keys stored locally and attach the password inputs to the
42
- // secrets manager.
50
+ // Remove all the keys stored locally.
43
51
  const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
44
52
  Object.keys(settings).forEach(provider => {
45
53
  Object.keys(settings[provider])
@@ -49,6 +57,11 @@ export class AiSettings extends React.Component {
49
57
  });
50
58
  });
51
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.
52
65
  this.componentDidUpdate();
53
66
  }
54
67
  this._settings
@@ -98,8 +111,7 @@ export class AiSettings extends React.Component {
98
111
  this._currentSettings = { provider: 'None' };
99
112
  this._uiSchema = {};
100
113
  this._formRef = React.createRef();
101
- this._unsavedFields = [];
102
- this._formInputs = [];
114
+ this._secretFields = [];
103
115
  if (!props.formContext.providerRegistry) {
104
116
  throw new Error('The provider registry is needed to enable the jupyterlite-ai settings panel');
105
117
  }
@@ -110,6 +122,8 @@ export class AiSettings extends React.Component {
110
122
  this._settings = props.formContext.settings;
111
123
  this._useSecretsManager =
112
124
  (_d = this._settings.get('UseSecretsManager').composite) !== null && _d !== void 0 ? _d : true;
125
+ this._hideSecretFields =
126
+ (_e = this._settings.get('HideSecretFields').composite) !== null && _e !== void 0 ? _e : true;
113
127
  // Initialize the providers schema.
114
128
  const providerSchema = JSONExt.deepCopy(baseSettings);
115
129
  providerSchema.properties.provider = {
@@ -127,7 +141,7 @@ export class AiSettings extends React.Component {
127
141
  const labSettings = this._settings.get('AIprovider').composite;
128
142
  if (labSettings && Object.keys(labSettings).includes('provider')) {
129
143
  // Get the provider name.
130
- const provider = (_e = Object.entries(labSettings).find(v => v[0] === 'provider')) === null || _e === void 0 ? void 0 : _e[1];
144
+ const provider = (_f = Object.entries(labSettings).find(v => v[0] === 'provider')) === null || _f === void 0 ? void 0 : _f[1];
131
145
  // Save the settings.
132
146
  const settings = {
133
147
  _current: provider
@@ -148,10 +162,15 @@ export class AiSettings extends React.Component {
148
162
  .set('AIprovider', this._currentSettings)
149
163
  .catch(console.error);
150
164
  this._settings.changed.connect(() => {
151
- var _a;
165
+ var _a, _b;
152
166
  const useSecretsManager = (_a = this._settings.get('UseSecretsManager').composite) !== null && _a !== void 0 ? _a : true;
153
167
  if (useSecretsManager !== this._useSecretsManager) {
154
- this.updateUseSecretsManager(useSecretsManager);
168
+ this._updateUseSecretsManager(useSecretsManager);
169
+ }
170
+ const hideSecretFields = (_b = this._settings.get('HideSecretFields').composite) !== null && _b !== void 0 ? _b : true;
171
+ if (hideSecretFields !== this._hideSecretFields) {
172
+ this._hideSecretFields = hideSecretFields;
173
+ this._updateSchema();
155
174
  }
156
175
  });
157
176
  }
@@ -160,27 +179,24 @@ export class AiSettings extends React.Component {
160
179
  if (!this._secretsManager || !this._useSecretsManager) {
161
180
  return;
162
181
  }
163
- // Attach the password inputs to the secrets manager only if they have changed.
182
+ // Attach the password inputs to the secrets manager.
183
+ await this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
164
184
  const inputs = ((_a = this._formRef.current) === null || _a === void 0 ? void 0 : _a.getElementsByTagName('input')) || [];
165
- if (ArrayExt.shallowEqual(inputs, this._formInputs)) {
166
- return;
167
- }
168
- await this._secretsManager.detachAll(SECRETS_NAMESPACE);
169
- this._formInputs = [...inputs];
170
- this._unsavedFields = [];
171
185
  for (let i = 0; i < inputs.length; i++) {
172
186
  if (inputs[i].type.toLowerCase() === 'password') {
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));
177
- this._unsavedFields.push(label);
190
+ this._secretsManager.attach(Private.getToken(), SECRETS_NAMESPACE, id, inputs[i], (value) => this._onPasswordUpdated(label, value));
178
191
  }
179
192
  }
180
193
  }
181
- if (this._settingConnector instanceof SettingConnector) {
182
- this._settingConnector.doNotSave = this._unsavedFields;
194
+ }
195
+ componentWillUnmount() {
196
+ if (!this._secretsManager || !this._useSecretsManager) {
197
+ return;
183
198
  }
199
+ this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
184
200
  }
185
201
  /**
186
202
  * Get the current provider from the local storage.
@@ -213,19 +229,13 @@ export class AiSettings extends React.Component {
213
229
  var _a;
214
230
  const currentSettings = { ...value };
215
231
  const settings = JSON.parse((_a = localStorage.getItem(STORAGE_NAME)) !== null && _a !== void 0 ? _a : '{}');
216
- 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
+ }
217
236
  settings[this._provider] = currentSettings;
218
237
  localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
219
238
  }
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
239
  /**
230
240
  * Build the schema for a given provider.
231
241
  */
@@ -233,12 +243,25 @@ export class AiSettings extends React.Component {
233
243
  const schema = JSONExt.deepCopy(baseSettings);
234
244
  this._uiSchema = {};
235
245
  const settingsSchema = this._providerRegistry.getSettingsSchema(this._provider);
246
+ this._secretFields = [];
236
247
  if (settingsSchema) {
237
248
  Object.entries(settingsSchema).forEach(([key, value]) => {
249
+ if (key.toLowerCase().includes('key')) {
250
+ this._secretFields.push(key);
251
+ if (this._hideSecretFields) {
252
+ return;
253
+ }
254
+ this._uiSchema[key] = { 'ui:widget': 'password' };
255
+ }
238
256
  schema.properties[key] = value;
239
- this._updateUiSchema(key);
240
257
  });
241
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
+ }
242
265
  return schema;
243
266
  }
244
267
  /**
@@ -275,3 +298,24 @@ export class AiSettings extends React.Component {
275
298
  React.createElement(WrappedFormComponent, { formData: this._currentSettings, schema: this.state.schema, onChange: this._onFormChange, uiSchema: this._uiSchema })));
276
299
  }
277
300
  }
301
+ var Private;
302
+ (function (Private) {
303
+ /**
304
+ * The token to use with the secrets manager.
305
+ */
306
+ let secretsToken;
307
+ /**
308
+ * Set of the token.
309
+ */
310
+ function setToken(value) {
311
+ secretsToken = value;
312
+ }
313
+ Private.setToken = setToken;
314
+ /**
315
+ * get the token.
316
+ */
317
+ function getToken() {
318
+ return secretsToken;
319
+ }
320
+ Private.getToken = getToken;
321
+ })(Private || (Private = {}));
@@ -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
  });
@@ -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
  */