@jupyterlite/ai 0.6.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/base-completer.d.ts +22 -5
- package/lib/base-completer.js +14 -1
- package/lib/chat-handler.d.ts +23 -11
- package/lib/chat-handler.js +66 -45
- package/lib/completion-provider.d.ts +2 -2
- package/lib/completion-provider.js +5 -4
- package/lib/components/stop-button.d.ts +0 -1
- package/lib/default-prompts.d.ts +2 -0
- package/lib/default-prompts.js +31 -0
- package/lib/default-providers/Anthropic/completer.d.ts +4 -11
- package/lib/default-providers/Anthropic/completer.js +5 -16
- package/lib/default-providers/ChromeAI/completer.d.ts +4 -11
- package/lib/default-providers/ChromeAI/completer.js +5 -16
- package/lib/default-providers/ChromeAI/instructions.d.ts +4 -0
- package/lib/default-providers/ChromeAI/instructions.js +18 -0
- package/lib/default-providers/ChromeAI/settings-schema.json +0 -3
- package/lib/default-providers/Gemini/completer.d.ts +12 -0
- package/lib/default-providers/Gemini/completer.js +48 -0
- package/lib/default-providers/Gemini/instructions.d.ts +2 -0
- package/lib/default-providers/Gemini/instructions.js +9 -0
- package/lib/default-providers/Gemini/settings-schema.json +64 -0
- package/lib/default-providers/MistralAI/completer.d.ts +10 -13
- package/lib/default-providers/MistralAI/completer.js +42 -52
- package/lib/default-providers/MistralAI/instructions.d.ts +1 -1
- package/lib/default-providers/MistralAI/instructions.js +2 -0
- package/lib/default-providers/Ollama/completer.d.ts +12 -0
- package/lib/default-providers/Ollama/completer.js +43 -0
- package/lib/default-providers/Ollama/instructions.d.ts +2 -0
- package/lib/default-providers/Ollama/instructions.js +70 -0
- package/lib/default-providers/Ollama/settings-schema.json +143 -0
- package/lib/default-providers/OpenAI/completer.d.ts +4 -11
- package/lib/default-providers/OpenAI/completer.js +8 -16
- package/lib/default-providers/OpenAI/settings-schema.json +88 -128
- package/lib/default-providers/WebLLM/completer.d.ts +21 -0
- package/lib/default-providers/WebLLM/completer.js +127 -0
- package/lib/default-providers/WebLLM/instructions.d.ts +6 -0
- package/lib/default-providers/WebLLM/instructions.js +32 -0
- package/lib/default-providers/WebLLM/settings-schema.json +19 -0
- package/lib/default-providers/index.js +127 -8
- package/lib/index.d.ts +3 -2
- package/lib/index.js +80 -36
- package/lib/provider.d.ts +48 -22
- package/lib/provider.js +254 -101
- package/lib/settings/index.d.ts +1 -1
- package/lib/settings/index.js +1 -1
- package/lib/settings/panel.d.ts +151 -14
- package/lib/settings/panel.js +334 -145
- package/lib/settings/textarea.d.ts +2 -0
- package/lib/settings/textarea.js +18 -0
- package/lib/tokens.d.ts +45 -22
- package/lib/tokens.js +2 -1
- package/lib/types/ai-model.d.ts +24 -0
- package/lib/types/ai-model.js +5 -0
- package/package.json +19 -15
- package/schema/chat.json +1 -1
- package/schema/provider-registry.json +8 -8
- package/schema/system-prompts.json +22 -0
- package/src/base-completer.ts +38 -6
- package/src/chat-handler.ts +62 -31
- package/src/completion-provider.ts +3 -3
- package/src/default-prompts.ts +33 -0
- package/src/default-providers/Anthropic/completer.ts +5 -21
- package/src/default-providers/ChromeAI/completer.ts +5 -21
- package/src/default-providers/ChromeAI/instructions.ts +21 -0
- package/src/default-providers/Gemini/completer.ts +61 -0
- package/src/default-providers/Gemini/instructions.ts +9 -0
- package/src/default-providers/MistralAI/completer.ts +47 -65
- package/src/default-providers/MistralAI/instructions.ts +2 -0
- package/src/default-providers/Ollama/completer.ts +54 -0
- package/src/default-providers/Ollama/instructions.ts +70 -0
- package/src/default-providers/OpenAI/completer.ts +8 -21
- package/src/default-providers/WebLLM/completer.ts +151 -0
- package/src/default-providers/WebLLM/instructions.ts +33 -0
- package/src/default-providers/index.ts +158 -18
- package/src/index.ts +108 -40
- package/src/provider.ts +300 -109
- package/src/settings/index.ts +1 -1
- package/src/settings/panel.tsx +463 -101
- package/src/settings/textarea.tsx +33 -0
- package/src/tokens.ts +49 -24
- package/src/types/ai-model.ts +37 -0
- package/src/types/service-worker.d.ts +6 -0
- package/style/base.css +34 -0
- package/lib/settings/settings-connector.d.ts +0 -31
- package/lib/settings/settings-connector.js +0 -61
- package/src/settings/settings-connector.ts +0 -88
package/src/settings/panel.tsx
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
2
|
+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
2
3
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { JSONExt } from '@lumino/coreutils';
|
|
4
|
+
Button,
|
|
5
|
+
FormComponent,
|
|
6
|
+
IFormRenderer
|
|
7
|
+
} from '@jupyterlab/ui-components';
|
|
8
|
+
import { JSONExt, ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
8
9
|
import { IChangeEvent } from '@rjsf/core';
|
|
9
10
|
import type { FieldProps } from '@rjsf/utils';
|
|
10
11
|
import validator from '@rjsf/validator-ajv8';
|
|
@@ -12,21 +13,24 @@ import { JSONSchema7 } from 'json-schema';
|
|
|
12
13
|
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
13
14
|
import React from 'react';
|
|
14
15
|
|
|
15
|
-
import { getSecretId,
|
|
16
|
+
import { getSecretId, SECRETS_REPLACEMENT } from '.';
|
|
16
17
|
import baseSettings from './base.json';
|
|
17
|
-
import { IAIProviderRegistry, IDict, PLUGIN_IDS } from '../tokens';
|
|
18
|
+
import { IAIProviderRegistry, IDict, ModelRole, PLUGIN_IDS } from '../tokens';
|
|
18
19
|
|
|
19
20
|
const MD_MIME_TYPE = 'text/markdown';
|
|
20
|
-
const STORAGE_NAME = '@jupyterlite/ai:settings';
|
|
21
21
|
const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
|
|
22
|
+
const ERROR_CLASS = 'jp-AISettingsError';
|
|
22
23
|
const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
|
|
24
|
+
const STORAGE_KEYS = {
|
|
25
|
+
chat: '@jupyterlite/ai:chat-settings',
|
|
26
|
+
completer: '@jupyterlite/ai:completer-settings'
|
|
27
|
+
};
|
|
23
28
|
|
|
24
29
|
export const aiSettingsRenderer = (options: {
|
|
25
30
|
providerRegistry: IAIProviderRegistry;
|
|
26
31
|
secretsToken?: symbol;
|
|
27
32
|
rmRegistry?: IRenderMimeRegistry;
|
|
28
33
|
secretsManager?: ISecretsManager;
|
|
29
|
-
settingConnector?: ISettingConnector;
|
|
30
34
|
}): IFormRenderer => {
|
|
31
35
|
const { secretsToken } = options;
|
|
32
36
|
delete options.secretsToken;
|
|
@@ -41,36 +45,205 @@ export const aiSettingsRenderer = (options: {
|
|
|
41
45
|
};
|
|
42
46
|
};
|
|
43
47
|
|
|
44
|
-
export interface ISettingsFormStates {
|
|
45
|
-
schema: JSONSchema7;
|
|
46
|
-
instruction: HTMLElement | null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
48
|
const WrappedFormComponent = (props: any): JSX.Element => {
|
|
50
49
|
return <FormComponent {...props} validator={validator} />;
|
|
51
50
|
};
|
|
52
51
|
|
|
53
|
-
export
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
export interface IAiSettings {
|
|
53
|
+
/**
|
|
54
|
+
* Get the local storage settings for a specific role (chat or completer).
|
|
55
|
+
*/
|
|
56
|
+
getLocalStorage(role: ModelRole): IDict<any>;
|
|
57
|
+
/**
|
|
58
|
+
* Set the local storage item for a specific role (chat or completer).
|
|
59
|
+
* If the key is not provider (null) we assume the value should replace the whole
|
|
60
|
+
* local storage for this role.
|
|
61
|
+
*/
|
|
62
|
+
setLocalStorageItem(role: ModelRole, key: string | null, value: any): void;
|
|
63
|
+
/**
|
|
64
|
+
* Get the settings from the registry (jupyterlab settings system) for a given role.
|
|
65
|
+
*/
|
|
66
|
+
getSettingsFromRegistry(role: ModelRole): IDict<any>;
|
|
67
|
+
/**
|
|
68
|
+
* Save the settings to the setting registry.
|
|
69
|
+
*/
|
|
70
|
+
saveSettingsToRegistry(role: ModelRole, settings: IDict<any>): void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class AiSettings
|
|
74
|
+
extends React.Component<FieldProps, AiSettings.states>
|
|
75
|
+
implements IAiSettings
|
|
76
|
+
{
|
|
57
77
|
constructor(props: FieldProps) {
|
|
78
|
+
super(props);
|
|
79
|
+
this._settings = props.formContext.settings;
|
|
80
|
+
const uniqueProvider =
|
|
81
|
+
(this._settings.get('UniqueProvider').composite as boolean) ?? true;
|
|
82
|
+
|
|
83
|
+
this.state = { uniqueProvider };
|
|
84
|
+
|
|
85
|
+
this._settings.changed.connect(this._settingsChanged);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private _settingsChanged = () => {
|
|
89
|
+
const uniqueProvider =
|
|
90
|
+
(this._settings.get('UniqueProvider').composite as boolean) ?? true;
|
|
91
|
+
if (this.state.uniqueProvider === uniqueProvider) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (uniqueProvider) {
|
|
95
|
+
// Copy chat settings to the completer settings if there should be a unique
|
|
96
|
+
// provider for both.
|
|
97
|
+
this.setLocalStorageItem('completer', null, this.getLocalStorage('chat'));
|
|
98
|
+
this.saveSettingsToRegistry(
|
|
99
|
+
'completer',
|
|
100
|
+
this.getSettingsFromRegistry('chat')
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
this.setState({ uniqueProvider });
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get the local storage settings for a specific role (chat or completer).
|
|
108
|
+
*/
|
|
109
|
+
getLocalStorage = (role: ModelRole): IDict<any> => {
|
|
110
|
+
const storageKey = STORAGE_KEYS[role];
|
|
111
|
+
return JSON.parse(localStorage.getItem(storageKey) ?? '{}');
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Set the local storage item for a specific role (chat or completer).
|
|
116
|
+
* If the key is not provider (null) we assume the value should replace the whole
|
|
117
|
+
* local storage for this role.
|
|
118
|
+
*/
|
|
119
|
+
setLocalStorageItem = (
|
|
120
|
+
role: ModelRole,
|
|
121
|
+
key: string | null,
|
|
122
|
+
value: any
|
|
123
|
+
): void => {
|
|
124
|
+
const storageKey = STORAGE_KEYS[role];
|
|
125
|
+
let settings: IDict<any>;
|
|
126
|
+
|
|
127
|
+
if (key !== null) {
|
|
128
|
+
settings = JSON.parse(localStorage.getItem(storageKey) ?? '{}');
|
|
129
|
+
settings[key] = value;
|
|
130
|
+
} else {
|
|
131
|
+
settings = value;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
localStorage.setItem(storageKey, JSON.stringify(settings));
|
|
135
|
+
|
|
136
|
+
// If both chat and completer use the same settings, only the chat settings should
|
|
137
|
+
// be editable for user, so we should duplicate its values to the completer
|
|
138
|
+
// local storage.
|
|
139
|
+
if (this.state.uniqueProvider && role === 'chat') {
|
|
140
|
+
const storageKeyCompleter = STORAGE_KEYS['completer'];
|
|
141
|
+
localStorage.setItem(storageKeyCompleter, JSON.stringify(settings));
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get the settings from the registry (jupyterlab settings system) for a given role.
|
|
147
|
+
*/
|
|
148
|
+
getSettingsFromRegistry = (role: ModelRole): IDict<any> => {
|
|
149
|
+
const settings = this._settings.get('AIproviders')
|
|
150
|
+
.composite as ReadonlyPartialJSONObject;
|
|
151
|
+
return settings && Object.keys(settings).includes(role)
|
|
152
|
+
? (settings[role] as IDict<any>)
|
|
153
|
+
: { provider: 'None' };
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Save the settings to the setting registry.
|
|
158
|
+
*/
|
|
159
|
+
saveSettingsToRegistry = (role: ModelRole, settings: IDict<any>): void => {
|
|
160
|
+
const fullSettings = this._settings.get('AIproviders')
|
|
161
|
+
.composite as IDict<any>;
|
|
162
|
+
fullSettings[role] = { ...settings };
|
|
163
|
+
|
|
164
|
+
// If both chat and completer use the same settings, only the chat settings should
|
|
165
|
+
// be editable for user, so we should duplicate its values to the completer
|
|
166
|
+
// settings.
|
|
167
|
+
if (this.state.uniqueProvider && role === 'chat') {
|
|
168
|
+
fullSettings['completer'] = { ...settings };
|
|
169
|
+
}
|
|
170
|
+
this._settings.set('AIproviders', { ...fullSettings }).catch(console.error);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
render(): JSX.Element {
|
|
174
|
+
return (
|
|
175
|
+
<div>
|
|
176
|
+
<h3>
|
|
177
|
+
{this.state.uniqueProvider
|
|
178
|
+
? 'Chat and completer provider'
|
|
179
|
+
: 'Chat provider'}
|
|
180
|
+
</h3>
|
|
181
|
+
<AiProviderSettings {...this.props} role={'chat'} aiSettings={this} />
|
|
182
|
+
{!this.state.uniqueProvider && (
|
|
183
|
+
<>
|
|
184
|
+
<h3>Completer provider</h3>
|
|
185
|
+
<AiProviderSettings
|
|
186
|
+
{...this.props}
|
|
187
|
+
role={'completer'}
|
|
188
|
+
aiSettings={this}
|
|
189
|
+
/>
|
|
190
|
+
</>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private _settings: ISettingRegistry.ISettings;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The AI settings component namespace.
|
|
201
|
+
*/
|
|
202
|
+
namespace AiSettings {
|
|
203
|
+
/**
|
|
204
|
+
* The AI settings component states.
|
|
205
|
+
*/
|
|
206
|
+
export type states = {
|
|
207
|
+
/**
|
|
208
|
+
* Whether there is only one provider for chat and completion.
|
|
209
|
+
*/
|
|
210
|
+
uniqueProvider: boolean;
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* The provider names object.
|
|
214
|
+
*/
|
|
215
|
+
export type providers = {
|
|
216
|
+
[key in ModelRole]: string;
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* The provider schemas object.
|
|
220
|
+
*/
|
|
221
|
+
export type schemas = {
|
|
222
|
+
[key in ModelRole]: JSONSchema7;
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export class AiProviderSettings extends React.Component<
|
|
227
|
+
AiProviderSettings.props,
|
|
228
|
+
AiProviderSettings.states
|
|
229
|
+
> {
|
|
230
|
+
constructor(props: AiProviderSettings.props) {
|
|
58
231
|
super(props);
|
|
59
232
|
if (!props.formContext.providerRegistry) {
|
|
60
233
|
throw new Error(
|
|
61
234
|
'The provider registry is needed to enable the jupyterlite-ai settings panel'
|
|
62
235
|
);
|
|
63
236
|
}
|
|
237
|
+
this._role = props.role;
|
|
64
238
|
this._providerRegistry = props.formContext.providerRegistry;
|
|
65
239
|
this._rmRegistry = props.formContext.rmRegistry ?? null;
|
|
66
240
|
this._secretsManager = props.formContext.secretsManager ?? null;
|
|
67
|
-
this._settingConnector = props.formContext.settingConnector ?? null;
|
|
68
241
|
this._settings = props.formContext.settings;
|
|
69
242
|
|
|
70
|
-
|
|
243
|
+
const useSecretsManagerSetting =
|
|
71
244
|
(this._settings.get('UseSecretsManager').composite as boolean) ?? true;
|
|
72
|
-
this.
|
|
73
|
-
|
|
245
|
+
this._useSecretsManager =
|
|
246
|
+
useSecretsManagerSetting && this._secretsManager !== null;
|
|
74
247
|
|
|
75
248
|
// Initialize the providers schema.
|
|
76
249
|
const providerSchema = JSONExt.deepCopy(baseSettings) as any;
|
|
@@ -84,11 +257,14 @@ export class AiSettings extends React.Component<
|
|
|
84
257
|
this._providerSchema = providerSchema as JSONSchema7;
|
|
85
258
|
|
|
86
259
|
// Check if there is saved values in local storage, otherwise use the settings from
|
|
87
|
-
// the setting registry (
|
|
88
|
-
const
|
|
260
|
+
// the setting registry (leads to default if there are no user settings).
|
|
261
|
+
const storageKey = STORAGE_KEYS[this._role];
|
|
262
|
+
const storageSettings = localStorage.getItem(storageKey);
|
|
89
263
|
if (storageSettings === null) {
|
|
90
|
-
const labSettings = this.
|
|
91
|
-
|
|
264
|
+
const labSettings = this.props.aiSettings.getSettingsFromRegistry(
|
|
265
|
+
this._role
|
|
266
|
+
);
|
|
267
|
+
if (Object.keys(labSettings).includes('provider')) {
|
|
92
268
|
// Get the provider name.
|
|
93
269
|
const provider = Object.entries(labSettings).find(
|
|
94
270
|
v => v[0] === 'provider'
|
|
@@ -98,38 +274,43 @@ export class AiSettings extends React.Component<
|
|
|
98
274
|
_current: provider
|
|
99
275
|
};
|
|
100
276
|
settings[provider] = labSettings;
|
|
101
|
-
|
|
277
|
+
this.props.aiSettings.setLocalStorageItem(this._role, null, settings);
|
|
102
278
|
}
|
|
103
279
|
}
|
|
104
280
|
|
|
105
281
|
// Initialize the settings from the saved ones.
|
|
106
282
|
this._provider = this.getCurrentProvider();
|
|
107
|
-
this._currentSettings = this.getSettings();
|
|
108
283
|
|
|
109
284
|
// Initialize the schema.
|
|
110
285
|
const schema = this._buildSchema();
|
|
111
|
-
this.state = { schema, instruction: null };
|
|
112
286
|
|
|
287
|
+
// Initialize the current settings.
|
|
288
|
+
const isModified = this._updatedFormData(
|
|
289
|
+
this.getSettingsFromLocalStorage()
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
this.state = {
|
|
293
|
+
schema,
|
|
294
|
+
instruction: null,
|
|
295
|
+
compatibilityError: null,
|
|
296
|
+
isModified: isModified
|
|
297
|
+
};
|
|
113
298
|
this._renderInstruction();
|
|
114
299
|
|
|
300
|
+
this._checkProviderCompatibility();
|
|
301
|
+
|
|
115
302
|
// Update the setting registry.
|
|
116
|
-
this.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
(this._settings.get('HideSecretFields').composite as boolean) ?? true;
|
|
128
|
-
if (hideSecretFields !== this._hideSecretFields) {
|
|
129
|
-
this._hideSecretFields = hideSecretFields;
|
|
130
|
-
this._updateSchema();
|
|
131
|
-
}
|
|
132
|
-
});
|
|
303
|
+
this.saveSettingsToRegistry();
|
|
304
|
+
|
|
305
|
+
this._secretsManager?.fieldVisibilityChanged.connect(
|
|
306
|
+
this._fieldVisibilityChanged
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
this._settings.changed.connect(this._settingsChanged);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
componentDidMount(): void {
|
|
313
|
+
this.componentDidUpdate();
|
|
133
314
|
}
|
|
134
315
|
|
|
135
316
|
async componentDidUpdate(): Promise<void> {
|
|
@@ -158,6 +339,10 @@ export class AiSettings extends React.Component<
|
|
|
158
339
|
}
|
|
159
340
|
|
|
160
341
|
componentWillUnmount(): void {
|
|
342
|
+
this._settings.changed.disconnect(this._settingsChanged);
|
|
343
|
+
this._secretsManager?.fieldVisibilityChanged.disconnect(
|
|
344
|
+
this._fieldVisibilityChanged
|
|
345
|
+
);
|
|
161
346
|
if (!this._secretsManager || !this._useSecretsManager) {
|
|
162
347
|
return;
|
|
163
348
|
}
|
|
@@ -168,7 +353,7 @@ export class AiSettings extends React.Component<
|
|
|
168
353
|
* Get the current provider from the local storage.
|
|
169
354
|
*/
|
|
170
355
|
getCurrentProvider(): string {
|
|
171
|
-
const settings =
|
|
356
|
+
const settings = this.props.aiSettings.getLocalStorage(this._role);
|
|
172
357
|
return settings['_current'] ?? 'None';
|
|
173
358
|
}
|
|
174
359
|
|
|
@@ -176,51 +361,95 @@ export class AiSettings extends React.Component<
|
|
|
176
361
|
* Save the current provider to the local storage.
|
|
177
362
|
*/
|
|
178
363
|
saveCurrentProvider(): void {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
364
|
+
this.props.aiSettings.setLocalStorageItem(
|
|
365
|
+
this._role,
|
|
366
|
+
'_current',
|
|
367
|
+
this._provider
|
|
368
|
+
);
|
|
182
369
|
}
|
|
183
370
|
|
|
184
371
|
/**
|
|
185
|
-
* Get settings from local storage for
|
|
372
|
+
* Get settings from local storage for the current provider provider.
|
|
186
373
|
*/
|
|
187
|
-
|
|
188
|
-
const settings =
|
|
374
|
+
getSettingsFromLocalStorage(): IDict<any> {
|
|
375
|
+
const settings = this.props.aiSettings.getLocalStorage(this._role);
|
|
189
376
|
return settings[this._provider] ?? { provider: this._provider };
|
|
190
377
|
}
|
|
191
378
|
|
|
192
379
|
/**
|
|
193
380
|
* Save settings in local storage for a given provider.
|
|
194
381
|
*/
|
|
195
|
-
|
|
196
|
-
const currentSettings = { ...
|
|
197
|
-
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) ?? '{}');
|
|
382
|
+
saveSettingsToLocalStorage() {
|
|
383
|
+
const currentSettings = { ...this._currentSettings };
|
|
198
384
|
// Do not save secrets in local storage if using the secrets manager.
|
|
199
|
-
if (this.
|
|
385
|
+
if (this._useSecretsManager) {
|
|
200
386
|
this._secretFields.forEach(field => delete currentSettings[field]);
|
|
201
387
|
}
|
|
202
|
-
|
|
203
|
-
|
|
388
|
+
this.props.aiSettings.setLocalStorageItem(
|
|
389
|
+
this._role,
|
|
390
|
+
this._provider,
|
|
391
|
+
currentSettings
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Save the settings to the setting registry.
|
|
397
|
+
*/
|
|
398
|
+
saveSettingsToRegistry(): void {
|
|
399
|
+
const sanitizedSettings = { ...this._currentSettings };
|
|
400
|
+
if (this._useSecretsManager) {
|
|
401
|
+
this._secretFields.forEach(field => {
|
|
402
|
+
sanitizedSettings[field] = SECRETS_REPLACEMENT;
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
this.props.aiSettings.saveSettingsToRegistry(this._role, {
|
|
407
|
+
provider: this._provider,
|
|
408
|
+
...sanitizedSettings
|
|
409
|
+
});
|
|
204
410
|
}
|
|
205
411
|
|
|
412
|
+
/**
|
|
413
|
+
* Triggered when the settings has changed.
|
|
414
|
+
*/
|
|
415
|
+
private _settingsChanged = (settings: ISettingRegistry.ISettings) => {
|
|
416
|
+
this._updateUseSecretsManager(
|
|
417
|
+
(this._settings.get('UseSecretsManager').composite as boolean) ?? true
|
|
418
|
+
);
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Triggered when the secret fields visibility has changed.
|
|
423
|
+
*/
|
|
424
|
+
private _fieldVisibilityChanged = (
|
|
425
|
+
_: ISecretsManager,
|
|
426
|
+
value: boolean
|
|
427
|
+
): void => {
|
|
428
|
+
if (this._useSecretsManager) {
|
|
429
|
+
this._updateSchema();
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
|
|
206
433
|
/**
|
|
207
434
|
* Update the settings whether the secrets manager is used or not.
|
|
208
435
|
*
|
|
209
436
|
* @param value - whether to use the secrets manager or not.
|
|
210
437
|
*/
|
|
211
438
|
private _updateUseSecretsManager = (value: boolean) => {
|
|
439
|
+
// No-op if the value did not change or the secrets manager has not been provided.
|
|
440
|
+
if (value === this._useSecretsManager || this._secretsManager === null) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Update the secrets manager.
|
|
212
445
|
this._useSecretsManager = value;
|
|
213
446
|
if (!value) {
|
|
214
447
|
// Detach all the password inputs attached to the secrets manager, and save the
|
|
215
448
|
// current settings to the local storage to save the password.
|
|
216
|
-
this._secretsManager
|
|
217
|
-
if (this._settingConnector instanceof SettingConnector) {
|
|
218
|
-
this._settingConnector.doNotSave = [];
|
|
219
|
-
}
|
|
220
|
-
this.saveSettings(this._currentSettings);
|
|
449
|
+
this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
221
450
|
} else {
|
|
222
451
|
// Remove all the keys stored locally.
|
|
223
|
-
const settings =
|
|
452
|
+
const settings = this.props.aiSettings.getLocalStorage(this._role);
|
|
224
453
|
Object.keys(settings).forEach(provider => {
|
|
225
454
|
Object.keys(settings[provider])
|
|
226
455
|
.filter(key => key.toLowerCase().includes('key'))
|
|
@@ -228,17 +457,11 @@ export class AiSettings extends React.Component<
|
|
|
228
457
|
delete settings[provider][key];
|
|
229
458
|
});
|
|
230
459
|
});
|
|
231
|
-
|
|
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
|
-
this.componentDidUpdate();
|
|
460
|
+
this.props.aiSettings.setLocalStorageItem(this._role, null, settings);
|
|
238
461
|
}
|
|
239
|
-
this.
|
|
240
|
-
|
|
241
|
-
|
|
462
|
+
this._updateSchema();
|
|
463
|
+
this.saveSettingsToLocalStorage();
|
|
464
|
+
this.saveSettingsToRegistry();
|
|
242
465
|
};
|
|
243
466
|
|
|
244
467
|
/**
|
|
@@ -252,27 +475,30 @@ export class AiSettings extends React.Component<
|
|
|
252
475
|
);
|
|
253
476
|
|
|
254
477
|
this._secretFields = [];
|
|
478
|
+
this._defaultFormData = {};
|
|
255
479
|
if (settingsSchema) {
|
|
256
480
|
Object.entries(settingsSchema).forEach(([key, value]) => {
|
|
257
481
|
if (key.toLowerCase().includes('key')) {
|
|
258
482
|
this._secretFields.push(key);
|
|
259
|
-
|
|
483
|
+
|
|
484
|
+
// If the secrets manager is not used, show the secrets fields.
|
|
485
|
+
// If the secrets manager is used, check if the fields should be visible.
|
|
486
|
+
const showSecretFields =
|
|
487
|
+
!this._useSecretsManager ||
|
|
488
|
+
(this._secretsManager?.secretFieldsVisibility ?? true);
|
|
489
|
+
if (!showSecretFields) {
|
|
260
490
|
return;
|
|
261
491
|
}
|
|
492
|
+
|
|
262
493
|
this._uiSchema[key] = { 'ui:widget': 'password' };
|
|
263
494
|
}
|
|
264
495
|
schema.properties[key] = value;
|
|
496
|
+
if (value.default !== undefined) {
|
|
497
|
+
this._defaultFormData[key] = value.default;
|
|
498
|
+
}
|
|
265
499
|
});
|
|
266
500
|
}
|
|
267
501
|
|
|
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
|
-
}
|
|
276
502
|
return schema as JSONSchema7;
|
|
277
503
|
}
|
|
278
504
|
|
|
@@ -304,7 +530,30 @@ export class AiSettings extends React.Component<
|
|
|
304
530
|
}
|
|
305
531
|
|
|
306
532
|
/**
|
|
307
|
-
*
|
|
533
|
+
* Check for compatibility of the provider with the current environment.
|
|
534
|
+
* If the provider is not compatible, display an error message.
|
|
535
|
+
*/
|
|
536
|
+
private async _checkProviderCompatibility(): Promise<void> {
|
|
537
|
+
const compatibilityCheck = this._providerRegistry.getCompatibilityCheck(
|
|
538
|
+
this._provider
|
|
539
|
+
);
|
|
540
|
+
if (!compatibilityCheck) {
|
|
541
|
+
this.setState({ compatibilityError: null });
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
const error = await compatibilityCheck();
|
|
545
|
+
if (!error) {
|
|
546
|
+
this.setState({ compatibilityError: null });
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const errorDiv = document.createElement('div');
|
|
550
|
+
errorDiv.className = ERROR_CLASS;
|
|
551
|
+
errorDiv.innerHTML = error;
|
|
552
|
+
this.setState({ compatibilityError: error });
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Triggered when the provider has changed, to update the schema and values.
|
|
308
557
|
* Update the Jupyterlab settings accordingly.
|
|
309
558
|
*/
|
|
310
559
|
private _onProviderChanged = (e: IChangeEvent) => {
|
|
@@ -314,12 +563,18 @@ export class AiSettings extends React.Component<
|
|
|
314
563
|
}
|
|
315
564
|
this._provider = provider;
|
|
316
565
|
this.saveCurrentProvider();
|
|
317
|
-
this._currentSettings = this.getSettings();
|
|
318
566
|
this._updateSchema();
|
|
319
567
|
this._renderInstruction();
|
|
320
|
-
this.
|
|
321
|
-
|
|
322
|
-
|
|
568
|
+
this._checkProviderCompatibility();
|
|
569
|
+
|
|
570
|
+
// Initialize the current settings.
|
|
571
|
+
const isModified = this._updatedFormData(
|
|
572
|
+
this.getSettingsFromLocalStorage()
|
|
573
|
+
);
|
|
574
|
+
if (isModified !== this.state.isModified) {
|
|
575
|
+
this.setState({ isModified });
|
|
576
|
+
}
|
|
577
|
+
this.saveSettingsToRegistry();
|
|
323
578
|
};
|
|
324
579
|
|
|
325
580
|
/**
|
|
@@ -328,22 +583,65 @@ export class AiSettings extends React.Component<
|
|
|
328
583
|
*/
|
|
329
584
|
private _onPasswordUpdated = (fieldName: string, value: string) => {
|
|
330
585
|
this._currentSettings[fieldName] = value;
|
|
331
|
-
this.
|
|
332
|
-
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
333
|
-
.catch(console.error);
|
|
586
|
+
this.saveSettingsToRegistry();
|
|
334
587
|
};
|
|
335
588
|
|
|
589
|
+
/**
|
|
590
|
+
* Update the current settings with the new values from the form.
|
|
591
|
+
*
|
|
592
|
+
* @param data - The form data to update.
|
|
593
|
+
* @returns - Boolean whether the form is not the default one.
|
|
594
|
+
*/
|
|
595
|
+
private _updatedFormData(data: IDict): boolean {
|
|
596
|
+
let isModified = false;
|
|
597
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
598
|
+
if (this._defaultFormData[key] !== undefined) {
|
|
599
|
+
if (value === undefined) {
|
|
600
|
+
const schemaProperty = this.state.schema.properties?.[
|
|
601
|
+
key
|
|
602
|
+
] as JSONSchema7;
|
|
603
|
+
if (schemaProperty.type === 'string') {
|
|
604
|
+
data[key] = '';
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (value !== this._defaultFormData[key]) {
|
|
608
|
+
isModified = true;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
this._currentSettings = JSONExt.deepCopy(data);
|
|
613
|
+
return isModified;
|
|
614
|
+
}
|
|
615
|
+
|
|
336
616
|
/**
|
|
337
617
|
* Triggered when the form value has changed, to update the current settings and save
|
|
338
618
|
* it in local storage.
|
|
339
619
|
* Update the Jupyterlab settings accordingly.
|
|
340
620
|
*/
|
|
341
|
-
private
|
|
342
|
-
|
|
343
|
-
this.
|
|
344
|
-
this.
|
|
345
|
-
|
|
346
|
-
|
|
621
|
+
private _onFormChanged = (e: IChangeEvent): void => {
|
|
622
|
+
const { formData } = e;
|
|
623
|
+
const isModified = this._updatedFormData(formData);
|
|
624
|
+
this.saveSettingsToLocalStorage();
|
|
625
|
+
this.saveSettingsToRegistry();
|
|
626
|
+
if (isModified !== this.state.isModified) {
|
|
627
|
+
this.setState({ isModified });
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Handler for the "Restore to defaults" button - clears all
|
|
633
|
+
* modified settings then calls `setFormData` to restore the
|
|
634
|
+
* values.
|
|
635
|
+
*/
|
|
636
|
+
private _reset = async (event: React.MouseEvent): Promise<void> => {
|
|
637
|
+
event.stopPropagation();
|
|
638
|
+
this._currentSettings = {
|
|
639
|
+
...this._currentSettings,
|
|
640
|
+
...this._defaultFormData
|
|
641
|
+
};
|
|
642
|
+
this.saveSettingsToLocalStorage();
|
|
643
|
+
this.saveSettingsToRegistry();
|
|
644
|
+
this.setState({ isModified: false });
|
|
347
645
|
};
|
|
348
646
|
|
|
349
647
|
render(): JSX.Element {
|
|
@@ -353,7 +651,14 @@ export class AiSettings extends React.Component<
|
|
|
353
651
|
formData={{ provider: this._provider }}
|
|
354
652
|
schema={this._providerSchema}
|
|
355
653
|
onChange={this._onProviderChanged}
|
|
654
|
+
idPrefix={`jp-SettingsEditor-${PLUGIN_IDS.providerRegistry}-${this._role}`}
|
|
356
655
|
/>
|
|
656
|
+
{this.state.compatibilityError !== null && (
|
|
657
|
+
<div className={ERROR_CLASS}>
|
|
658
|
+
<i className={'fas fa-exclamation-triangle'}></i>
|
|
659
|
+
<span>{this.state.compatibilityError}</span>
|
|
660
|
+
</div>
|
|
661
|
+
)}
|
|
357
662
|
{this.state.instruction !== null && (
|
|
358
663
|
<details>
|
|
359
664
|
<summary className={INSTRUCTION_CLASS}>Instructions</summary>
|
|
@@ -364,29 +669,86 @@ export class AiSettings extends React.Component<
|
|
|
364
669
|
/>
|
|
365
670
|
</details>
|
|
366
671
|
)}
|
|
672
|
+
<div className="jp-SettingsHeader">
|
|
673
|
+
<h3 title={this._provider}>{this._provider}</h3>
|
|
674
|
+
<div className="jp-SettingsHeader-buttonbar">
|
|
675
|
+
{this.state.isModified && (
|
|
676
|
+
<Button className="jp-RestoreButton" onClick={this._reset}>
|
|
677
|
+
Restore to Defaults
|
|
678
|
+
</Button>
|
|
679
|
+
)}
|
|
680
|
+
</div>
|
|
681
|
+
</div>
|
|
367
682
|
<WrappedFormComponent
|
|
368
683
|
formData={this._currentSettings}
|
|
369
684
|
schema={this.state.schema}
|
|
370
|
-
onChange={this.
|
|
685
|
+
onChange={this._onFormChanged}
|
|
371
686
|
uiSchema={this._uiSchema}
|
|
687
|
+
idPrefix={`jp-SettingsEditor-${PLUGIN_IDS.providerRegistry}-${this._role}`}
|
|
688
|
+
formContext={{
|
|
689
|
+
...this.props.formContext,
|
|
690
|
+
defaultFormData: this._defaultFormData
|
|
691
|
+
}}
|
|
372
692
|
/>
|
|
373
693
|
</div>
|
|
374
694
|
);
|
|
375
695
|
}
|
|
376
696
|
|
|
697
|
+
private _role: ModelRole;
|
|
377
698
|
private _providerRegistry: IAIProviderRegistry;
|
|
378
699
|
private _provider: string;
|
|
379
700
|
private _providerSchema: JSONSchema7;
|
|
380
701
|
private _useSecretsManager: boolean;
|
|
381
|
-
private _hideSecretFields: boolean;
|
|
382
702
|
private _rmRegistry: IRenderMimeRegistry | null;
|
|
383
703
|
private _secretsManager: ISecretsManager | null;
|
|
384
|
-
private _settingConnector: ISettingConnector | null;
|
|
385
704
|
private _currentSettings: IDict<any> = { provider: 'None' };
|
|
386
705
|
private _uiSchema: IDict<any> = {};
|
|
387
706
|
private _settings: ISettingRegistry.ISettings;
|
|
388
707
|
private _formRef = React.createRef<HTMLDivElement>();
|
|
389
708
|
private _secretFields: string[] = [];
|
|
709
|
+
private _defaultFormData: IDict<any> = {};
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* The AI provider settings component namespace.
|
|
714
|
+
*/
|
|
715
|
+
export namespace AiProviderSettings {
|
|
716
|
+
/**
|
|
717
|
+
* The AI provider settings component props.
|
|
718
|
+
*/
|
|
719
|
+
export type props = FieldProps & {
|
|
720
|
+
/**
|
|
721
|
+
* Why this model is used for (chat or completion).
|
|
722
|
+
*/
|
|
723
|
+
role: ModelRole;
|
|
724
|
+
/**
|
|
725
|
+
* The parent component which should handle:
|
|
726
|
+
* - the get/set functions for local storage
|
|
727
|
+
* - save settings using jupyter settings system
|
|
728
|
+
*/
|
|
729
|
+
aiSettings: IAiSettings;
|
|
730
|
+
};
|
|
731
|
+
/**
|
|
732
|
+
* The AI provider settings component states.
|
|
733
|
+
*/
|
|
734
|
+
export type states = {
|
|
735
|
+
/**
|
|
736
|
+
* The schema of the settings.
|
|
737
|
+
*/
|
|
738
|
+
schema: JSONSchema7;
|
|
739
|
+
/**
|
|
740
|
+
* The instructions for this provider.
|
|
741
|
+
*/
|
|
742
|
+
instruction: HTMLElement | null;
|
|
743
|
+
/**
|
|
744
|
+
* An error if the model in not compatible with the current environment.
|
|
745
|
+
*/
|
|
746
|
+
compatibilityError: string | null;
|
|
747
|
+
/**
|
|
748
|
+
* Whether the settings are modified from default or not.
|
|
749
|
+
*/
|
|
750
|
+
isModified?: boolean;
|
|
751
|
+
};
|
|
390
752
|
}
|
|
391
753
|
|
|
392
754
|
namespace Private {
|