@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/lib/settings/panel.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { FormComponent } from '@jupyterlab/ui-components';
|
|
1
|
+
import { Button, FormComponent } from '@jupyterlab/ui-components';
|
|
2
2
|
import { JSONExt } from '@lumino/coreutils';
|
|
3
3
|
import validator from '@rjsf/validator-ajv8';
|
|
4
4
|
import React from 'react';
|
|
5
|
-
import { getSecretId,
|
|
5
|
+
import { getSecretId, SECRETS_REPLACEMENT } from '.';
|
|
6
6
|
import baseSettings from './base.json';
|
|
7
7
|
import { PLUGIN_IDS } from '../tokens';
|
|
8
8
|
const MD_MIME_TYPE = 'text/markdown';
|
|
9
|
-
const STORAGE_NAME = '@jupyterlite/ai:settings';
|
|
10
9
|
const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
|
|
10
|
+
const ERROR_CLASS = 'jp-AISettingsError';
|
|
11
11
|
const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
|
|
12
|
+
const STORAGE_KEYS = {
|
|
13
|
+
chat: '@jupyterlite/ai:chat-settings',
|
|
14
|
+
completer: '@jupyterlite/ai:completer-settings'
|
|
15
|
+
};
|
|
12
16
|
export const aiSettingsRenderer = (options) => {
|
|
13
17
|
const { secretsToken } = options;
|
|
14
18
|
delete options.secretsToken;
|
|
@@ -27,103 +31,107 @@ const WrappedFormComponent = (props) => {
|
|
|
27
31
|
};
|
|
28
32
|
export class AiSettings extends React.Component {
|
|
29
33
|
constructor(props) {
|
|
30
|
-
var _a, _b, _c, _d, _e, _f;
|
|
31
34
|
super(props);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
35
|
+
this._settings = props.formContext.settings;
|
|
36
|
+
const uniqueProvider = this._settings.get('UniqueProvider').composite ?? true;
|
|
37
|
+
this.state = { uniqueProvider };
|
|
38
|
+
this._settings.changed.connect(this._settingsChanged);
|
|
39
|
+
}
|
|
40
|
+
_settingsChanged = () => {
|
|
41
|
+
const uniqueProvider = this._settings.get('UniqueProvider').composite ?? true;
|
|
42
|
+
if (this.state.uniqueProvider === uniqueProvider) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (uniqueProvider) {
|
|
46
|
+
// Copy chat settings to the completer settings if there should be a unique
|
|
47
|
+
// provider for both.
|
|
48
|
+
this.setLocalStorageItem('completer', null, this.getLocalStorage('chat'));
|
|
49
|
+
this.saveSettingsToRegistry('completer', this.getSettingsFromRegistry('chat'));
|
|
50
|
+
}
|
|
51
|
+
this.setState({ uniqueProvider });
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Get the local storage settings for a specific role (chat or completer).
|
|
55
|
+
*/
|
|
56
|
+
getLocalStorage = (role) => {
|
|
57
|
+
const storageKey = STORAGE_KEYS[role];
|
|
58
|
+
return JSON.parse(localStorage.getItem(storageKey) ?? '{}');
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Set the local storage item for a specific role (chat or completer).
|
|
62
|
+
* If the key is not provider (null) we assume the value should replace the whole
|
|
63
|
+
* local storage for this role.
|
|
64
|
+
*/
|
|
65
|
+
setLocalStorageItem = (role, key, value) => {
|
|
66
|
+
const storageKey = STORAGE_KEYS[role];
|
|
67
|
+
let settings;
|
|
68
|
+
if (key !== null) {
|
|
69
|
+
settings = JSON.parse(localStorage.getItem(storageKey) ?? '{}');
|
|
70
|
+
settings[key] = value;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
settings = value;
|
|
74
|
+
}
|
|
75
|
+
localStorage.setItem(storageKey, JSON.stringify(settings));
|
|
76
|
+
// If both chat and completer use the same settings, only the chat settings should
|
|
77
|
+
// be editable for user, so we should duplicate its values to the completer
|
|
78
|
+
// local storage.
|
|
79
|
+
if (this.state.uniqueProvider && role === 'chat') {
|
|
80
|
+
const storageKeyCompleter = STORAGE_KEYS['completer'];
|
|
81
|
+
localStorage.setItem(storageKeyCompleter, JSON.stringify(settings));
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Get the settings from the registry (jupyterlab settings system) for a given role.
|
|
86
|
+
*/
|
|
87
|
+
getSettingsFromRegistry = (role) => {
|
|
88
|
+
const settings = this._settings.get('AIproviders')
|
|
89
|
+
.composite;
|
|
90
|
+
return settings && Object.keys(settings).includes(role)
|
|
91
|
+
? settings[role]
|
|
92
|
+
: { provider: 'None' };
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Save the settings to the setting registry.
|
|
96
|
+
*/
|
|
97
|
+
saveSettingsToRegistry = (role, settings) => {
|
|
98
|
+
const fullSettings = this._settings.get('AIproviders')
|
|
99
|
+
.composite;
|
|
100
|
+
fullSettings[role] = { ...settings };
|
|
101
|
+
// If both chat and completer use the same settings, only the chat settings should
|
|
102
|
+
// be editable for user, so we should duplicate its values to the completer
|
|
103
|
+
// settings.
|
|
104
|
+
if (this.state.uniqueProvider && role === 'chat') {
|
|
105
|
+
fullSettings['completer'] = { ...settings };
|
|
106
|
+
}
|
|
107
|
+
this._settings.set('AIproviders', { ...fullSettings }).catch(console.error);
|
|
108
|
+
};
|
|
109
|
+
render() {
|
|
110
|
+
return (React.createElement("div", null,
|
|
111
|
+
React.createElement("h3", null, this.state.uniqueProvider
|
|
112
|
+
? 'Chat and completer provider'
|
|
113
|
+
: 'Chat provider'),
|
|
114
|
+
React.createElement(AiProviderSettings, { ...this.props, role: 'chat', aiSettings: this }),
|
|
115
|
+
!this.state.uniqueProvider && (React.createElement(React.Fragment, null,
|
|
116
|
+
React.createElement("h3", null, "Completer provider"),
|
|
117
|
+
React.createElement(AiProviderSettings, { ...this.props, role: 'completer', aiSettings: this })))));
|
|
118
|
+
}
|
|
119
|
+
_settings;
|
|
120
|
+
}
|
|
121
|
+
export class AiProviderSettings extends React.Component {
|
|
122
|
+
constructor(props) {
|
|
123
|
+
super(props);
|
|
115
124
|
if (!props.formContext.providerRegistry) {
|
|
116
125
|
throw new Error('The provider registry is needed to enable the jupyterlite-ai settings panel');
|
|
117
126
|
}
|
|
127
|
+
this._role = props.role;
|
|
118
128
|
this._providerRegistry = props.formContext.providerRegistry;
|
|
119
|
-
this._rmRegistry =
|
|
120
|
-
this._secretsManager =
|
|
121
|
-
this._settingConnector = (_c = props.formContext.settingConnector) !== null && _c !== void 0 ? _c : null;
|
|
129
|
+
this._rmRegistry = props.formContext.rmRegistry ?? null;
|
|
130
|
+
this._secretsManager = props.formContext.secretsManager ?? null;
|
|
122
131
|
this._settings = props.formContext.settings;
|
|
132
|
+
const useSecretsManagerSetting = this._settings.get('UseSecretsManager').composite ?? true;
|
|
123
133
|
this._useSecretsManager =
|
|
124
|
-
|
|
125
|
-
this._hideSecretFields =
|
|
126
|
-
(_e = this._settings.get('HideSecretFields').composite) !== null && _e !== void 0 ? _e : true;
|
|
134
|
+
useSecretsManagerSetting && this._secretsManager !== null;
|
|
127
135
|
// Initialize the providers schema.
|
|
128
136
|
const providerSchema = JSONExt.deepCopy(baseSettings);
|
|
129
137
|
providerSchema.properties.provider = {
|
|
@@ -135,53 +143,51 @@ export class AiSettings extends React.Component {
|
|
|
135
143
|
};
|
|
136
144
|
this._providerSchema = providerSchema;
|
|
137
145
|
// Check if there is saved values in local storage, otherwise use the settings from
|
|
138
|
-
// the setting registry (
|
|
139
|
-
const
|
|
146
|
+
// the setting registry (leads to default if there are no user settings).
|
|
147
|
+
const storageKey = STORAGE_KEYS[this._role];
|
|
148
|
+
const storageSettings = localStorage.getItem(storageKey);
|
|
140
149
|
if (storageSettings === null) {
|
|
141
|
-
const labSettings = this.
|
|
142
|
-
if (
|
|
150
|
+
const labSettings = this.props.aiSettings.getSettingsFromRegistry(this._role);
|
|
151
|
+
if (Object.keys(labSettings).includes('provider')) {
|
|
143
152
|
// Get the provider name.
|
|
144
|
-
const provider =
|
|
153
|
+
const provider = Object.entries(labSettings).find(v => v[0] === 'provider')?.[1];
|
|
145
154
|
// Save the settings.
|
|
146
155
|
const settings = {
|
|
147
156
|
_current: provider
|
|
148
157
|
};
|
|
149
158
|
settings[provider] = labSettings;
|
|
150
|
-
|
|
159
|
+
this.props.aiSettings.setLocalStorageItem(this._role, null, settings);
|
|
151
160
|
}
|
|
152
161
|
}
|
|
153
162
|
// Initialize the settings from the saved ones.
|
|
154
163
|
this._provider = this.getCurrentProvider();
|
|
155
|
-
this._currentSettings = this.getSettings();
|
|
156
164
|
// Initialize the schema.
|
|
157
165
|
const schema = this._buildSchema();
|
|
158
|
-
|
|
166
|
+
// Initialize the current settings.
|
|
167
|
+
const isModified = this._updatedFormData(this.getSettingsFromLocalStorage());
|
|
168
|
+
this.state = {
|
|
169
|
+
schema,
|
|
170
|
+
instruction: null,
|
|
171
|
+
compatibilityError: null,
|
|
172
|
+
isModified: isModified
|
|
173
|
+
};
|
|
159
174
|
this._renderInstruction();
|
|
175
|
+
this._checkProviderCompatibility();
|
|
160
176
|
// Update the setting registry.
|
|
161
|
-
this.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (useSecretsManager !== this._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();
|
|
174
|
-
}
|
|
175
|
-
});
|
|
177
|
+
this.saveSettingsToRegistry();
|
|
178
|
+
this._secretsManager?.fieldVisibilityChanged.connect(this._fieldVisibilityChanged);
|
|
179
|
+
this._settings.changed.connect(this._settingsChanged);
|
|
180
|
+
}
|
|
181
|
+
componentDidMount() {
|
|
182
|
+
this.componentDidUpdate();
|
|
176
183
|
}
|
|
177
184
|
async componentDidUpdate() {
|
|
178
|
-
var _a;
|
|
179
185
|
if (!this._secretsManager || !this._useSecretsManager) {
|
|
180
186
|
return;
|
|
181
187
|
}
|
|
182
188
|
// Attach the password inputs to the secrets manager.
|
|
183
189
|
await this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
184
|
-
const inputs =
|
|
190
|
+
const inputs = this._formRef.current?.getElementsByTagName('input') || [];
|
|
185
191
|
for (let i = 0; i < inputs.length; i++) {
|
|
186
192
|
if (inputs[i].type.toLowerCase() === 'password') {
|
|
187
193
|
const label = inputs[i].getAttribute('label');
|
|
@@ -193,6 +199,8 @@ export class AiSettings extends React.Component {
|
|
|
193
199
|
}
|
|
194
200
|
}
|
|
195
201
|
componentWillUnmount() {
|
|
202
|
+
this._settings.changed.disconnect(this._settingsChanged);
|
|
203
|
+
this._secretsManager?.fieldVisibilityChanged.disconnect(this._fieldVisibilityChanged);
|
|
196
204
|
if (!this._secretsManager || !this._useSecretsManager) {
|
|
197
205
|
return;
|
|
198
206
|
}
|
|
@@ -202,40 +210,95 @@ export class AiSettings extends React.Component {
|
|
|
202
210
|
* Get the current provider from the local storage.
|
|
203
211
|
*/
|
|
204
212
|
getCurrentProvider() {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return (_a = settings['_current']) !== null && _a !== void 0 ? _a : 'None';
|
|
213
|
+
const settings = this.props.aiSettings.getLocalStorage(this._role);
|
|
214
|
+
return settings['_current'] ?? 'None';
|
|
208
215
|
}
|
|
209
216
|
/**
|
|
210
217
|
* Save the current provider to the local storage.
|
|
211
218
|
*/
|
|
212
219
|
saveCurrentProvider() {
|
|
213
|
-
|
|
214
|
-
settings['_current'] = this._provider;
|
|
215
|
-
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
220
|
+
this.props.aiSettings.setLocalStorageItem(this._role, '_current', this._provider);
|
|
216
221
|
}
|
|
217
222
|
/**
|
|
218
|
-
* Get settings from local storage for
|
|
223
|
+
* Get settings from local storage for the current provider provider.
|
|
219
224
|
*/
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
return (_a = settings[this._provider]) !== null && _a !== void 0 ? _a : { provider: this._provider };
|
|
225
|
+
getSettingsFromLocalStorage() {
|
|
226
|
+
const settings = this.props.aiSettings.getLocalStorage(this._role);
|
|
227
|
+
return settings[this._provider] ?? { provider: this._provider };
|
|
224
228
|
}
|
|
225
229
|
/**
|
|
226
230
|
* Save settings in local storage for a given provider.
|
|
227
231
|
*/
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const currentSettings = { ...value };
|
|
231
|
-
const settings = JSON.parse((_a = localStorage.getItem(STORAGE_NAME)) !== null && _a !== void 0 ? _a : '{}');
|
|
232
|
+
saveSettingsToLocalStorage() {
|
|
233
|
+
const currentSettings = { ...this._currentSettings };
|
|
232
234
|
// Do not save secrets in local storage if using the secrets manager.
|
|
233
|
-
if (this.
|
|
235
|
+
if (this._useSecretsManager) {
|
|
234
236
|
this._secretFields.forEach(field => delete currentSettings[field]);
|
|
235
237
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
+
this.props.aiSettings.setLocalStorageItem(this._role, this._provider, currentSettings);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Save the settings to the setting registry.
|
|
242
|
+
*/
|
|
243
|
+
saveSettingsToRegistry() {
|
|
244
|
+
const sanitizedSettings = { ...this._currentSettings };
|
|
245
|
+
if (this._useSecretsManager) {
|
|
246
|
+
this._secretFields.forEach(field => {
|
|
247
|
+
sanitizedSettings[field] = SECRETS_REPLACEMENT;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
this.props.aiSettings.saveSettingsToRegistry(this._role, {
|
|
251
|
+
provider: this._provider,
|
|
252
|
+
...sanitizedSettings
|
|
253
|
+
});
|
|
238
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Triggered when the settings has changed.
|
|
257
|
+
*/
|
|
258
|
+
_settingsChanged = (settings) => {
|
|
259
|
+
this._updateUseSecretsManager(this._settings.get('UseSecretsManager').composite ?? true);
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Triggered when the secret fields visibility has changed.
|
|
263
|
+
*/
|
|
264
|
+
_fieldVisibilityChanged = (_, value) => {
|
|
265
|
+
if (this._useSecretsManager) {
|
|
266
|
+
this._updateSchema();
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
/**
|
|
270
|
+
* Update the settings whether the secrets manager is used or not.
|
|
271
|
+
*
|
|
272
|
+
* @param value - whether to use the secrets manager or not.
|
|
273
|
+
*/
|
|
274
|
+
_updateUseSecretsManager = (value) => {
|
|
275
|
+
// No-op if the value did not change or the secrets manager has not been provided.
|
|
276
|
+
if (value === this._useSecretsManager || this._secretsManager === null) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
// Update the secrets manager.
|
|
280
|
+
this._useSecretsManager = value;
|
|
281
|
+
if (!value) {
|
|
282
|
+
// Detach all the password inputs attached to the secrets manager, and save the
|
|
283
|
+
// current settings to the local storage to save the password.
|
|
284
|
+
this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
// Remove all the keys stored locally.
|
|
288
|
+
const settings = this.props.aiSettings.getLocalStorage(this._role);
|
|
289
|
+
Object.keys(settings).forEach(provider => {
|
|
290
|
+
Object.keys(settings[provider])
|
|
291
|
+
.filter(key => key.toLowerCase().includes('key'))
|
|
292
|
+
.forEach(key => {
|
|
293
|
+
delete settings[provider][key];
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
this.props.aiSettings.setLocalStorageItem(this._role, null, settings);
|
|
297
|
+
}
|
|
298
|
+
this._updateSchema();
|
|
299
|
+
this.saveSettingsToLocalStorage();
|
|
300
|
+
this.saveSettingsToRegistry();
|
|
301
|
+
};
|
|
239
302
|
/**
|
|
240
303
|
* Build the schema for a given provider.
|
|
241
304
|
*/
|
|
@@ -244,24 +307,26 @@ export class AiSettings extends React.Component {
|
|
|
244
307
|
this._uiSchema = {};
|
|
245
308
|
const settingsSchema = this._providerRegistry.getSettingsSchema(this._provider);
|
|
246
309
|
this._secretFields = [];
|
|
310
|
+
this._defaultFormData = {};
|
|
247
311
|
if (settingsSchema) {
|
|
248
312
|
Object.entries(settingsSchema).forEach(([key, value]) => {
|
|
249
313
|
if (key.toLowerCase().includes('key')) {
|
|
250
314
|
this._secretFields.push(key);
|
|
251
|
-
|
|
315
|
+
// If the secrets manager is not used, show the secrets fields.
|
|
316
|
+
// If the secrets manager is used, check if the fields should be visible.
|
|
317
|
+
const showSecretFields = !this._useSecretsManager ||
|
|
318
|
+
(this._secretsManager?.secretFieldsVisibility ?? true);
|
|
319
|
+
if (!showSecretFields) {
|
|
252
320
|
return;
|
|
253
321
|
}
|
|
254
322
|
this._uiSchema[key] = { 'ui:widget': 'password' };
|
|
255
323
|
}
|
|
256
324
|
schema.properties[key] = value;
|
|
325
|
+
if (value.default !== undefined) {
|
|
326
|
+
this._defaultFormData[key] = value.default;
|
|
327
|
+
}
|
|
257
328
|
});
|
|
258
329
|
}
|
|
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
|
-
}
|
|
265
330
|
return schema;
|
|
266
331
|
}
|
|
267
332
|
/**
|
|
@@ -289,14 +354,138 @@ export class AiSettings extends React.Component {
|
|
|
289
354
|
await renderer.renderModel(model);
|
|
290
355
|
this.setState({ instruction: renderer.node });
|
|
291
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* Check for compatibility of the provider with the current environment.
|
|
359
|
+
* If the provider is not compatible, display an error message.
|
|
360
|
+
*/
|
|
361
|
+
async _checkProviderCompatibility() {
|
|
362
|
+
const compatibilityCheck = this._providerRegistry.getCompatibilityCheck(this._provider);
|
|
363
|
+
if (!compatibilityCheck) {
|
|
364
|
+
this.setState({ compatibilityError: null });
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const error = await compatibilityCheck();
|
|
368
|
+
if (!error) {
|
|
369
|
+
this.setState({ compatibilityError: null });
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const errorDiv = document.createElement('div');
|
|
373
|
+
errorDiv.className = ERROR_CLASS;
|
|
374
|
+
errorDiv.innerHTML = error;
|
|
375
|
+
this.setState({ compatibilityError: error });
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Triggered when the provider has changed, to update the schema and values.
|
|
379
|
+
* Update the Jupyterlab settings accordingly.
|
|
380
|
+
*/
|
|
381
|
+
_onProviderChanged = (e) => {
|
|
382
|
+
const provider = e.formData.provider;
|
|
383
|
+
if (provider === this._currentSettings.provider) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
this._provider = provider;
|
|
387
|
+
this.saveCurrentProvider();
|
|
388
|
+
this._updateSchema();
|
|
389
|
+
this._renderInstruction();
|
|
390
|
+
this._checkProviderCompatibility();
|
|
391
|
+
// Initialize the current settings.
|
|
392
|
+
const isModified = this._updatedFormData(this.getSettingsFromLocalStorage());
|
|
393
|
+
if (isModified !== this.state.isModified) {
|
|
394
|
+
this.setState({ isModified });
|
|
395
|
+
}
|
|
396
|
+
this.saveSettingsToRegistry();
|
|
397
|
+
};
|
|
398
|
+
/**
|
|
399
|
+
* Callback function called when the password input has been programmatically updated
|
|
400
|
+
* with the secret manager.
|
|
401
|
+
*/
|
|
402
|
+
_onPasswordUpdated = (fieldName, value) => {
|
|
403
|
+
this._currentSettings[fieldName] = value;
|
|
404
|
+
this.saveSettingsToRegistry();
|
|
405
|
+
};
|
|
406
|
+
/**
|
|
407
|
+
* Update the current settings with the new values from the form.
|
|
408
|
+
*
|
|
409
|
+
* @param data - The form data to update.
|
|
410
|
+
* @returns - Boolean whether the form is not the default one.
|
|
411
|
+
*/
|
|
412
|
+
_updatedFormData(data) {
|
|
413
|
+
let isModified = false;
|
|
414
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
415
|
+
if (this._defaultFormData[key] !== undefined) {
|
|
416
|
+
if (value === undefined) {
|
|
417
|
+
const schemaProperty = this.state.schema.properties?.[key];
|
|
418
|
+
if (schemaProperty.type === 'string') {
|
|
419
|
+
data[key] = '';
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (value !== this._defaultFormData[key]) {
|
|
423
|
+
isModified = true;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
this._currentSettings = JSONExt.deepCopy(data);
|
|
428
|
+
return isModified;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Triggered when the form value has changed, to update the current settings and save
|
|
432
|
+
* it in local storage.
|
|
433
|
+
* Update the Jupyterlab settings accordingly.
|
|
434
|
+
*/
|
|
435
|
+
_onFormChanged = (e) => {
|
|
436
|
+
const { formData } = e;
|
|
437
|
+
const isModified = this._updatedFormData(formData);
|
|
438
|
+
this.saveSettingsToLocalStorage();
|
|
439
|
+
this.saveSettingsToRegistry();
|
|
440
|
+
if (isModified !== this.state.isModified) {
|
|
441
|
+
this.setState({ isModified });
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
/**
|
|
445
|
+
* Handler for the "Restore to defaults" button - clears all
|
|
446
|
+
* modified settings then calls `setFormData` to restore the
|
|
447
|
+
* values.
|
|
448
|
+
*/
|
|
449
|
+
_reset = async (event) => {
|
|
450
|
+
event.stopPropagation();
|
|
451
|
+
this._currentSettings = {
|
|
452
|
+
...this._currentSettings,
|
|
453
|
+
...this._defaultFormData
|
|
454
|
+
};
|
|
455
|
+
this.saveSettingsToLocalStorage();
|
|
456
|
+
this.saveSettingsToRegistry();
|
|
457
|
+
this.setState({ isModified: false });
|
|
458
|
+
};
|
|
292
459
|
render() {
|
|
293
460
|
return (React.createElement("div", { ref: this._formRef },
|
|
294
|
-
React.createElement(WrappedFormComponent, { formData: { provider: this._provider }, schema: this._providerSchema, onChange: this._onProviderChanged }),
|
|
461
|
+
React.createElement(WrappedFormComponent, { formData: { provider: this._provider }, schema: this._providerSchema, onChange: this._onProviderChanged, idPrefix: `jp-SettingsEditor-${PLUGIN_IDS.providerRegistry}-${this._role}` }),
|
|
462
|
+
this.state.compatibilityError !== null && (React.createElement("div", { className: ERROR_CLASS },
|
|
463
|
+
React.createElement("i", { className: 'fas fa-exclamation-triangle' }),
|
|
464
|
+
React.createElement("span", null, this.state.compatibilityError))),
|
|
295
465
|
this.state.instruction !== null && (React.createElement("details", null,
|
|
296
466
|
React.createElement("summary", { className: INSTRUCTION_CLASS }, "Instructions"),
|
|
297
467
|
React.createElement("span", { ref: node => node && node.replaceChildren(this.state.instruction) }))),
|
|
298
|
-
React.createElement(
|
|
468
|
+
React.createElement("div", { className: "jp-SettingsHeader" },
|
|
469
|
+
React.createElement("h3", { title: this._provider }, this._provider),
|
|
470
|
+
React.createElement("div", { className: "jp-SettingsHeader-buttonbar" }, this.state.isModified && (React.createElement(Button, { className: "jp-RestoreButton", onClick: this._reset }, "Restore to Defaults")))),
|
|
471
|
+
React.createElement(WrappedFormComponent, { formData: this._currentSettings, schema: this.state.schema, onChange: this._onFormChanged, uiSchema: this._uiSchema, idPrefix: `jp-SettingsEditor-${PLUGIN_IDS.providerRegistry}-${this._role}`, formContext: {
|
|
472
|
+
...this.props.formContext,
|
|
473
|
+
defaultFormData: this._defaultFormData
|
|
474
|
+
} })));
|
|
299
475
|
}
|
|
476
|
+
_role;
|
|
477
|
+
_providerRegistry;
|
|
478
|
+
_provider;
|
|
479
|
+
_providerSchema;
|
|
480
|
+
_useSecretsManager;
|
|
481
|
+
_rmRegistry;
|
|
482
|
+
_secretsManager;
|
|
483
|
+
_currentSettings = { provider: 'None' };
|
|
484
|
+
_uiSchema = {};
|
|
485
|
+
_settings;
|
|
486
|
+
_formRef = React.createRef();
|
|
487
|
+
_secretFields = [];
|
|
488
|
+
_defaultFormData = {};
|
|
300
489
|
}
|
|
301
490
|
var Private;
|
|
302
491
|
(function (Private) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
const TEXTAREA_CLASS = 'jp-AISettingsTextArea';
|
|
3
|
+
export const textArea = {
|
|
4
|
+
fieldRenderer: (props) => {
|
|
5
|
+
const settings = props.formContext.settings;
|
|
6
|
+
const schema = settings.schema.properties?.[props.name];
|
|
7
|
+
const [formData, setFormData] = useState(props.formData);
|
|
8
|
+
settings.changed.connect(() => {
|
|
9
|
+
setFormData(settings.get(props.name).composite);
|
|
10
|
+
});
|
|
11
|
+
const onChange = (event) => {
|
|
12
|
+
settings.set(props.name, event.target.value);
|
|
13
|
+
};
|
|
14
|
+
return (React.createElement(React.Fragment, null,
|
|
15
|
+
schema?.title && (React.createElement("h3", { className: "jp-FormGroup-fieldLabel jp-FormGroup-contentItem" }, schema.title)),
|
|
16
|
+
React.createElement("textarea", { className: TEXTAREA_CLASS, onChange: onChange }, formData)));
|
|
17
|
+
}
|
|
18
|
+
};
|