@jupyterlite/ai 0.6.0 → 0.6.1
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/lib/index.js +15 -11
- package/lib/provider.d.ts +4 -0
- package/lib/provider.js +26 -2
- package/lib/settings/panel.d.ts +3 -5
- package/lib/settings/panel.js +54 -17
- package/lib/settings/utils.d.ts +0 -1
- package/lib/settings/utils.js +0 -1
- package/lib/tokens.d.ts +7 -0
- package/lib/tokens.js +7 -0
- package/package.json +11 -10
- package/schema/provider-registry.json +6 -0
- package/src/index.ts +62 -57
- package/src/provider.ts +36 -7
- package/src/settings/panel.tsx +55 -15
- package/src/settings/utils.ts +0 -1
- package/src/tokens.ts +8 -0
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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({
|
|
120
|
-
|
|
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:
|
|
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,
|
|
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,7 +152,7 @@ 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));
|
|
155
|
+
const secrets = await ((_b = this._secretsManager) === null || _b === void 0 ? void 0 : _b.get(Private.getToken(), SECRETS_NAMESPACE, id));
|
|
153
156
|
fullSettings[key] = (secrets === null || secrets === void 0 ? void 0 : secrets.value) || settings[key];
|
|
154
157
|
continue;
|
|
155
158
|
}
|
|
@@ -231,3 +234,24 @@ export class AIProviderRegistry {
|
|
|
231
234
|
}
|
|
232
235
|
AIProviderRegistry.updateConfig = updateConfig;
|
|
233
236
|
})(AIProviderRegistry || (AIProviderRegistry = {}));
|
|
237
|
+
var Private;
|
|
238
|
+
(function (Private) {
|
|
239
|
+
/**
|
|
240
|
+
* The token to use with the secrets manager.
|
|
241
|
+
*/
|
|
242
|
+
let secretsToken;
|
|
243
|
+
/**
|
|
244
|
+
* Set of the token.
|
|
245
|
+
*/
|
|
246
|
+
function setToken(value) {
|
|
247
|
+
secretsToken = value;
|
|
248
|
+
}
|
|
249
|
+
Private.setToken = setToken;
|
|
250
|
+
/**
|
|
251
|
+
* get the token.
|
|
252
|
+
*/
|
|
253
|
+
function getToken() {
|
|
254
|
+
return secretsToken;
|
|
255
|
+
}
|
|
256
|
+
Private.getToken = getToken;
|
|
257
|
+
})(Private || (Private = {}));
|
package/lib/settings/panel.d.ts
CHANGED
|
@@ -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
|
*/
|
|
@@ -36,11 +38,6 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
|
|
|
36
38
|
*/
|
|
37
39
|
saveSettings(value: IDict<any>): void;
|
|
38
40
|
private updateUseSecretsManager;
|
|
39
|
-
/**
|
|
40
|
-
* Update the UI schema of the form.
|
|
41
|
-
* Currently use to hide API keys.
|
|
42
|
-
*/
|
|
43
|
-
private _updateUiSchema;
|
|
44
41
|
/**
|
|
45
42
|
* Build the schema for a given provider.
|
|
46
43
|
*/
|
|
@@ -75,6 +72,7 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
|
|
|
75
72
|
private _provider;
|
|
76
73
|
private _providerSchema;
|
|
77
74
|
private _useSecretsManager;
|
|
75
|
+
private _hideSecretFields;
|
|
78
76
|
private _rmRegistry;
|
|
79
77
|
private _secretsManager;
|
|
80
78
|
private _settingConnector;
|
package/lib/settings/panel.js
CHANGED
|
@@ -3,12 +3,19 @@ import { ArrayExt } from '@lumino/algorithm';
|
|
|
3
3
|
import { JSONExt } from '@lumino/coreutils';
|
|
4
4
|
import validator from '@rjsf/validator-ajv8';
|
|
5
5
|
import React from 'react';
|
|
6
|
-
import { getSecretId,
|
|
6
|
+
import { getSecretId, SettingConnector } from '.';
|
|
7
7
|
import baseSettings from './base.json';
|
|
8
|
+
import { PLUGIN_IDS } from '../tokens';
|
|
8
9
|
const MD_MIME_TYPE = 'text/markdown';
|
|
9
10
|
const STORAGE_NAME = '@jupyterlite/ai:settings';
|
|
10
11
|
const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
|
|
12
|
+
const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
|
|
11
13
|
export const aiSettingsRenderer = (options) => {
|
|
14
|
+
const { secretsToken } = options;
|
|
15
|
+
delete options.secretsToken;
|
|
16
|
+
if (secretsToken) {
|
|
17
|
+
Private.setToken(secretsToken);
|
|
18
|
+
}
|
|
12
19
|
return {
|
|
13
20
|
fieldRenderer: (props) => {
|
|
14
21
|
props.formContext = { ...props.formContext, ...options };
|
|
@@ -21,7 +28,7 @@ const WrappedFormComponent = (props) => {
|
|
|
21
28
|
};
|
|
22
29
|
export class AiSettings extends React.Component {
|
|
23
30
|
constructor(props) {
|
|
24
|
-
var _a, _b, _c, _d, _e;
|
|
31
|
+
var _a, _b, _c, _d, _e, _f;
|
|
25
32
|
super(props);
|
|
26
33
|
this.updateUseSecretsManager = (value) => {
|
|
27
34
|
var _a;
|
|
@@ -29,7 +36,7 @@ export class AiSettings extends React.Component {
|
|
|
29
36
|
if (!value) {
|
|
30
37
|
// Detach all the password inputs attached to the secrets manager, and save the
|
|
31
38
|
// current settings to the local storage to save the password.
|
|
32
|
-
(_a = this._secretsManager) === null || _a === void 0 ? void 0 : _a.detachAll(SECRETS_NAMESPACE);
|
|
39
|
+
(_a = this._secretsManager) === null || _a === void 0 ? void 0 : _a.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
33
40
|
this._formInputs = [];
|
|
34
41
|
this._unsavedFields = [];
|
|
35
42
|
if (this._settingConnector instanceof SettingConnector) {
|
|
@@ -110,6 +117,8 @@ export class AiSettings extends React.Component {
|
|
|
110
117
|
this._settings = props.formContext.settings;
|
|
111
118
|
this._useSecretsManager =
|
|
112
119
|
(_d = this._settings.get('UseSecretsManager').composite) !== null && _d !== void 0 ? _d : true;
|
|
120
|
+
this._hideSecretFields =
|
|
121
|
+
(_e = this._settings.get('HideSecretFields').composite) !== null && _e !== void 0 ? _e : true;
|
|
113
122
|
// Initialize the providers schema.
|
|
114
123
|
const providerSchema = JSONExt.deepCopy(baseSettings);
|
|
115
124
|
providerSchema.properties.provider = {
|
|
@@ -127,7 +136,7 @@ export class AiSettings extends React.Component {
|
|
|
127
136
|
const labSettings = this._settings.get('AIprovider').composite;
|
|
128
137
|
if (labSettings && Object.keys(labSettings).includes('provider')) {
|
|
129
138
|
// Get the provider name.
|
|
130
|
-
const provider = (
|
|
139
|
+
const provider = (_f = Object.entries(labSettings).find(v => v[0] === 'provider')) === null || _f === void 0 ? void 0 : _f[1];
|
|
131
140
|
// Save the settings.
|
|
132
141
|
const settings = {
|
|
133
142
|
_current: provider
|
|
@@ -148,11 +157,16 @@ export class AiSettings extends React.Component {
|
|
|
148
157
|
.set('AIprovider', this._currentSettings)
|
|
149
158
|
.catch(console.error);
|
|
150
159
|
this._settings.changed.connect(() => {
|
|
151
|
-
var _a;
|
|
160
|
+
var _a, _b;
|
|
152
161
|
const useSecretsManager = (_a = this._settings.get('UseSecretsManager').composite) !== null && _a !== void 0 ? _a : true;
|
|
153
162
|
if (useSecretsManager !== this._useSecretsManager) {
|
|
154
163
|
this.updateUseSecretsManager(useSecretsManager);
|
|
155
164
|
}
|
|
165
|
+
const hideSecretFields = (_b = this._settings.get('HideSecretFields').composite) !== null && _b !== void 0 ? _b : true;
|
|
166
|
+
if (hideSecretFields !== this._hideSecretFields) {
|
|
167
|
+
this._hideSecretFields = hideSecretFields;
|
|
168
|
+
this._updateSchema();
|
|
169
|
+
}
|
|
156
170
|
});
|
|
157
171
|
}
|
|
158
172
|
async componentDidUpdate() {
|
|
@@ -165,7 +179,7 @@ export class AiSettings extends React.Component {
|
|
|
165
179
|
if (ArrayExt.shallowEqual(inputs, this._formInputs)) {
|
|
166
180
|
return;
|
|
167
181
|
}
|
|
168
|
-
await this._secretsManager.detachAll(SECRETS_NAMESPACE);
|
|
182
|
+
await this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
169
183
|
this._formInputs = [...inputs];
|
|
170
184
|
this._unsavedFields = [];
|
|
171
185
|
for (let i = 0; i < inputs.length; i++) {
|
|
@@ -173,7 +187,7 @@ export class AiSettings extends React.Component {
|
|
|
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));
|
|
190
|
+
this._secretsManager.attach(Private.getToken(), SECRETS_NAMESPACE, id, inputs[i], (value) => this._onPasswordUpdated(label, value));
|
|
177
191
|
this._unsavedFields.push(label);
|
|
178
192
|
}
|
|
179
193
|
}
|
|
@@ -182,6 +196,12 @@ export class AiSettings extends React.Component {
|
|
|
182
196
|
this._settingConnector.doNotSave = this._unsavedFields;
|
|
183
197
|
}
|
|
184
198
|
}
|
|
199
|
+
componentWillUnmount() {
|
|
200
|
+
if (!this._secretsManager || !this._useSecretsManager) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
204
|
+
}
|
|
185
205
|
/**
|
|
186
206
|
* Get the current provider from the local storage.
|
|
187
207
|
*/
|
|
@@ -217,15 +237,6 @@ export class AiSettings extends React.Component {
|
|
|
217
237
|
settings[this._provider] = currentSettings;
|
|
218
238
|
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
219
239
|
}
|
|
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
240
|
/**
|
|
230
241
|
* Build the schema for a given provider.
|
|
231
242
|
*/
|
|
@@ -235,8 +246,13 @@ export class AiSettings extends React.Component {
|
|
|
235
246
|
const settingsSchema = this._providerRegistry.getSettingsSchema(this._provider);
|
|
236
247
|
if (settingsSchema) {
|
|
237
248
|
Object.entries(settingsSchema).forEach(([key, value]) => {
|
|
249
|
+
if (key.toLowerCase().includes('key')) {
|
|
250
|
+
if (this._hideSecretFields) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
this._uiSchema[key] = { 'ui:widget': 'password' };
|
|
254
|
+
}
|
|
238
255
|
schema.properties[key] = value;
|
|
239
|
-
this._updateUiSchema(key);
|
|
240
256
|
});
|
|
241
257
|
}
|
|
242
258
|
return schema;
|
|
@@ -275,3 +291,24 @@ export class AiSettings extends React.Component {
|
|
|
275
291
|
React.createElement(WrappedFormComponent, { formData: this._currentSettings, schema: this.state.schema, onChange: this._onFormChange, uiSchema: this._uiSchema })));
|
|
276
292
|
}
|
|
277
293
|
}
|
|
294
|
+
var Private;
|
|
295
|
+
(function (Private) {
|
|
296
|
+
/**
|
|
297
|
+
* The token to use with the secrets manager.
|
|
298
|
+
*/
|
|
299
|
+
let secretsToken;
|
|
300
|
+
/**
|
|
301
|
+
* Set of the token.
|
|
302
|
+
*/
|
|
303
|
+
function setToken(value) {
|
|
304
|
+
secretsToken = value;
|
|
305
|
+
}
|
|
306
|
+
Private.setToken = setToken;
|
|
307
|
+
/**
|
|
308
|
+
* get the token.
|
|
309
|
+
*/
|
|
310
|
+
function getToken() {
|
|
311
|
+
return secretsToken;
|
|
312
|
+
}
|
|
313
|
+
Private.getToken = getToken;
|
|
314
|
+
})(Private || (Private = {}));
|
package/lib/settings/utils.d.ts
CHANGED
package/lib/settings/utils.js
CHANGED
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
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlite/ai",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "AI code completions and chat for JupyterLite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -57,13 +57,14 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@jupyter/chat": "^0.9.0",
|
|
60
|
-
"@jupyterlab/application": "^4.4.0
|
|
61
|
-
"@jupyterlab/apputils": "^4.5.0
|
|
62
|
-
"@jupyterlab/completer": "^4.4.0
|
|
63
|
-
"@jupyterlab/
|
|
64
|
-
"@jupyterlab/
|
|
65
|
-
"@jupyterlab/
|
|
66
|
-
"@jupyterlab/
|
|
60
|
+
"@jupyterlab/application": "^4.4.0",
|
|
61
|
+
"@jupyterlab/apputils": "^4.5.0",
|
|
62
|
+
"@jupyterlab/completer": "^4.4.0",
|
|
63
|
+
"@jupyterlab/coreutils": "^6.4.0",
|
|
64
|
+
"@jupyterlab/notebook": "^4.4.0",
|
|
65
|
+
"@jupyterlab/rendermime": "^4.4.0",
|
|
66
|
+
"@jupyterlab/settingregistry": "^4.4.0",
|
|
67
|
+
"@jupyterlab/ui-components": "^4.4.0",
|
|
67
68
|
"@langchain/anthropic": "^0.3.9",
|
|
68
69
|
"@langchain/community": "^0.3.31",
|
|
69
70
|
"@langchain/core": "^0.3.40",
|
|
@@ -78,12 +79,12 @@
|
|
|
78
79
|
"@rjsf/utils": "^5.18.4",
|
|
79
80
|
"@rjsf/validator-ajv8": "^5.18.4",
|
|
80
81
|
"json5": "^2.2.3",
|
|
81
|
-
"jupyter-secrets-manager": "^0.
|
|
82
|
+
"jupyter-secrets-manager": "^0.3.0",
|
|
82
83
|
"react": "^18.2.0",
|
|
83
84
|
"react-dom": "^18.2.0"
|
|
84
85
|
},
|
|
85
86
|
"devDependencies": {
|
|
86
|
-
"@jupyterlab/builder": "^4.
|
|
87
|
+
"@jupyterlab/builder": "^4.4.0",
|
|
87
88
|
"@stylistic/eslint-plugin": "^3.0.1",
|
|
88
89
|
"@types/json-schema": "^7.0.11",
|
|
89
90
|
"@types/react": "^18.0.26",
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
"description": "Whether to use or not the secrets manager. If not, secrets will be stored in the browser (local storage)",
|
|
12
12
|
"default": true
|
|
13
13
|
},
|
|
14
|
+
"HideSecretFields": {
|
|
15
|
+
"type": "boolean",
|
|
16
|
+
"title": "Hide secret fields",
|
|
17
|
+
"description": "Whether to hide the secret fields in the UI or not",
|
|
18
|
+
"default": true
|
|
19
|
+
},
|
|
14
20
|
"AIprovider": {
|
|
15
21
|
"type": "object",
|
|
16
22
|
"title": "AI provider",
|
package/src/index.ts
CHANGED
|
@@ -21,18 +21,18 @@ import {
|
|
|
21
21
|
} from '@jupyterlab/settingregistry';
|
|
22
22
|
import { IFormRendererRegistry } from '@jupyterlab/ui-components';
|
|
23
23
|
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
24
|
-
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
24
|
+
import { ISecretsManager, SecretsManager } from 'jupyter-secrets-manager';
|
|
25
25
|
|
|
26
26
|
import { ChatHandler } from './chat-handler';
|
|
27
27
|
import { CompletionProvider } from './completion-provider';
|
|
28
28
|
import { defaultProviderPlugins } from './default-providers';
|
|
29
29
|
import { AIProviderRegistry } from './provider';
|
|
30
30
|
import { aiSettingsRenderer, SettingConnector } from './settings';
|
|
31
|
-
import { IAIProviderRegistry } from './tokens';
|
|
31
|
+
import { IAIProviderRegistry, PLUGIN_IDS } from './tokens';
|
|
32
32
|
import { stopItem } from './components/stop-button';
|
|
33
33
|
|
|
34
34
|
const chatCommandRegistryPlugin: JupyterFrontEndPlugin<IChatCommandRegistry> = {
|
|
35
|
-
id:
|
|
35
|
+
id: PLUGIN_IDS.chatCommandRegistry,
|
|
36
36
|
description: 'Autocompletion registry',
|
|
37
37
|
autoStart: true,
|
|
38
38
|
provides: IChatCommandRegistry,
|
|
@@ -44,7 +44,7 @@ const chatCommandRegistryPlugin: JupyterFrontEndPlugin<IChatCommandRegistry> = {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
const chatPlugin: JupyterFrontEndPlugin<void> = {
|
|
47
|
-
id:
|
|
47
|
+
id: PLUGIN_IDS.chat,
|
|
48
48
|
description: 'LLM chat extension',
|
|
49
49
|
autoStart: true,
|
|
50
50
|
requires: [IAIProviderRegistry, IRenderMimeRegistry, IChatCommandRegistry],
|
|
@@ -141,7 +141,7 @@ const chatPlugin: JupyterFrontEndPlugin<void> = {
|
|
|
141
141
|
};
|
|
142
142
|
|
|
143
143
|
const completerPlugin: JupyterFrontEndPlugin<void> = {
|
|
144
|
-
id:
|
|
144
|
+
id: PLUGIN_IDS.completer,
|
|
145
145
|
autoStart: true,
|
|
146
146
|
requires: [IAIProviderRegistry, ICompletionProviderManager],
|
|
147
147
|
activate: (
|
|
@@ -157,59 +157,64 @@ const completerPlugin: JupyterFrontEndPlugin<void> = {
|
|
|
157
157
|
}
|
|
158
158
|
};
|
|
159
159
|
|
|
160
|
-
const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> =
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
aiSettingsRenderer({
|
|
179
|
-
providerRegistry,
|
|
180
|
-
rmRegistry,
|
|
181
|
-
secretsManager,
|
|
182
|
-
settingConnector
|
|
183
|
-
})
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
settingRegistry
|
|
187
|
-
.load(providerRegistryPlugin.id)
|
|
188
|
-
.then(settings => {
|
|
189
|
-
const updateProvider = () => {
|
|
190
|
-
// Update the settings to the AI providers.
|
|
191
|
-
const providerSettings = (settings.get('AIprovider').composite ?? {
|
|
192
|
-
provider: 'None'
|
|
193
|
-
}) as ReadonlyPartialJSONObject;
|
|
194
|
-
providerRegistry.setProvider({
|
|
195
|
-
name: providerSettings.provider as string,
|
|
196
|
-
settings: providerSettings
|
|
197
|
-
});
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
settings.changed.connect(() => updateProvider());
|
|
201
|
-
updateProvider();
|
|
202
|
-
})
|
|
203
|
-
.catch(reason => {
|
|
204
|
-
console.error(
|
|
205
|
-
`Failed to load settings for ${providerRegistryPlugin.id}`,
|
|
206
|
-
reason
|
|
207
|
-
);
|
|
160
|
+
const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> =
|
|
161
|
+
SecretsManager.sign(PLUGIN_IDS.providerRegistry, token => ({
|
|
162
|
+
id: PLUGIN_IDS.providerRegistry,
|
|
163
|
+
autoStart: true,
|
|
164
|
+
requires: [IFormRendererRegistry, ISettingRegistry],
|
|
165
|
+
optional: [IRenderMimeRegistry, ISecretsManager, ISettingConnector],
|
|
166
|
+
provides: IAIProviderRegistry,
|
|
167
|
+
activate: (
|
|
168
|
+
app: JupyterFrontEnd,
|
|
169
|
+
editorRegistry: IFormRendererRegistry,
|
|
170
|
+
settingRegistry: ISettingRegistry,
|
|
171
|
+
rmRegistry?: IRenderMimeRegistry,
|
|
172
|
+
secretsManager?: ISecretsManager,
|
|
173
|
+
settingConnector?: ISettingConnector
|
|
174
|
+
): IAIProviderRegistry => {
|
|
175
|
+
const providerRegistry = new AIProviderRegistry({
|
|
176
|
+
token,
|
|
177
|
+
secretsManager
|
|
208
178
|
});
|
|
209
179
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
180
|
+
editorRegistry.addRenderer(
|
|
181
|
+
`${PLUGIN_IDS.providerRegistry}.AIprovider`,
|
|
182
|
+
aiSettingsRenderer({
|
|
183
|
+
providerRegistry,
|
|
184
|
+
secretsToken: token,
|
|
185
|
+
rmRegistry,
|
|
186
|
+
secretsManager,
|
|
187
|
+
settingConnector
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
settingRegistry
|
|
192
|
+
.load(providerRegistryPlugin.id)
|
|
193
|
+
.then(settings => {
|
|
194
|
+
const updateProvider = () => {
|
|
195
|
+
// Update the settings to the AI providers.
|
|
196
|
+
const providerSettings = (settings.get('AIprovider').composite ?? {
|
|
197
|
+
provider: 'None'
|
|
198
|
+
}) as ReadonlyPartialJSONObject;
|
|
199
|
+
providerRegistry.setProvider({
|
|
200
|
+
name: providerSettings.provider as string,
|
|
201
|
+
settings: providerSettings
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
settings.changed.connect(() => updateProvider());
|
|
206
|
+
updateProvider();
|
|
207
|
+
})
|
|
208
|
+
.catch(reason => {
|
|
209
|
+
console.error(
|
|
210
|
+
`Failed to load settings for ${providerRegistryPlugin.id}`,
|
|
211
|
+
reason
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return providerRegistry;
|
|
216
|
+
}
|
|
217
|
+
}));
|
|
213
218
|
|
|
214
219
|
/**
|
|
215
220
|
* Provides the settings connector as a separate plugin to allow for alternative
|
|
@@ -217,7 +222,7 @@ const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
|
|
|
217
222
|
* endpoint.
|
|
218
223
|
*/
|
|
219
224
|
const settingsConnector: JupyterFrontEndPlugin<ISettingConnector> = {
|
|
220
|
-
id:
|
|
225
|
+
id: PLUGIN_IDS.settingsConnector,
|
|
221
226
|
description: 'Provides a settings connector which does not save passwords.',
|
|
222
227
|
autoStart: true,
|
|
223
228
|
provides: ISettingConnector,
|
package/src/provider.ts
CHANGED
|
@@ -6,18 +6,17 @@ import { JSONSchema7 } from 'json-schema';
|
|
|
6
6
|
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
7
7
|
|
|
8
8
|
import { IBaseCompleter } from './base-completer';
|
|
9
|
-
import {
|
|
10
|
-
getSecretId,
|
|
11
|
-
SECRETS_NAMESPACE,
|
|
12
|
-
SECRETS_REPLACEMENT
|
|
13
|
-
} from './settings';
|
|
9
|
+
import { getSecretId, SECRETS_REPLACEMENT } from './settings';
|
|
14
10
|
import {
|
|
15
11
|
IAIProvider,
|
|
16
12
|
IAIProviderRegistry,
|
|
17
13
|
IDict,
|
|
18
|
-
ISetProviderOptions
|
|
14
|
+
ISetProviderOptions,
|
|
15
|
+
PLUGIN_IDS
|
|
19
16
|
} from './tokens';
|
|
20
17
|
|
|
18
|
+
const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
|
|
19
|
+
|
|
21
20
|
export const chatSystemPrompt = (
|
|
22
21
|
options: AIProviderRegistry.IPromptOptions
|
|
23
22
|
) => `
|
|
@@ -54,6 +53,7 @@ export class AIProviderRegistry implements IAIProviderRegistry {
|
|
|
54
53
|
*/
|
|
55
54
|
constructor(options: AIProviderRegistry.IOptions) {
|
|
56
55
|
this._secretsManager = options.secretsManager || null;
|
|
56
|
+
Private.setToken(options.token);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
@@ -171,7 +171,11 @@ export class AIProviderRegistry implements IAIProviderRegistry {
|
|
|
171
171
|
for (const key of Object.keys(settings)) {
|
|
172
172
|
if (settings[key] === SECRETS_REPLACEMENT) {
|
|
173
173
|
const id = getSecretId(name, key);
|
|
174
|
-
const secrets = await this._secretsManager?.get(
|
|
174
|
+
const secrets = await this._secretsManager?.get(
|
|
175
|
+
Private.getToken(),
|
|
176
|
+
SECRETS_NAMESPACE,
|
|
177
|
+
id
|
|
178
|
+
);
|
|
175
179
|
fullSettings[key] = secrets?.value || settings[key];
|
|
176
180
|
continue;
|
|
177
181
|
}
|
|
@@ -236,6 +240,10 @@ export namespace AIProviderRegistry {
|
|
|
236
240
|
* The secrets manager used in the application.
|
|
237
241
|
*/
|
|
238
242
|
secretsManager?: ISecretsManager;
|
|
243
|
+
/**
|
|
244
|
+
* The token used to request the secrets manager.
|
|
245
|
+
*/
|
|
246
|
+
token: symbol;
|
|
239
247
|
}
|
|
240
248
|
|
|
241
249
|
/**
|
|
@@ -290,3 +298,24 @@ export namespace AIProviderRegistry {
|
|
|
290
298
|
});
|
|
291
299
|
}
|
|
292
300
|
}
|
|
301
|
+
|
|
302
|
+
namespace Private {
|
|
303
|
+
/**
|
|
304
|
+
* The token to use with the secrets manager.
|
|
305
|
+
*/
|
|
306
|
+
let secretsToken: symbol;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Set of the token.
|
|
310
|
+
*/
|
|
311
|
+
export function setToken(value: symbol): void {
|
|
312
|
+
secretsToken = value;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* get the token.
|
|
317
|
+
*/
|
|
318
|
+
export function getToken(): symbol {
|
|
319
|
+
return secretsToken;
|
|
320
|
+
}
|
|
321
|
+
}
|
package/src/settings/panel.tsx
CHANGED
|
@@ -13,20 +13,27 @@ import { JSONSchema7 } from 'json-schema';
|
|
|
13
13
|
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
14
14
|
import React from 'react';
|
|
15
15
|
|
|
16
|
-
import { getSecretId,
|
|
16
|
+
import { getSecretId, SettingConnector } from '.';
|
|
17
17
|
import baseSettings from './base.json';
|
|
18
|
-
import { IAIProviderRegistry, IDict } from '../tokens';
|
|
18
|
+
import { IAIProviderRegistry, IDict, PLUGIN_IDS } from '../tokens';
|
|
19
19
|
|
|
20
20
|
const MD_MIME_TYPE = 'text/markdown';
|
|
21
21
|
const STORAGE_NAME = '@jupyterlite/ai:settings';
|
|
22
22
|
const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
|
|
23
|
+
const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
|
|
23
24
|
|
|
24
25
|
export const aiSettingsRenderer = (options: {
|
|
25
26
|
providerRegistry: IAIProviderRegistry;
|
|
27
|
+
secretsToken?: symbol;
|
|
26
28
|
rmRegistry?: IRenderMimeRegistry;
|
|
27
29
|
secretsManager?: ISecretsManager;
|
|
28
30
|
settingConnector?: ISettingConnector;
|
|
29
31
|
}): IFormRenderer => {
|
|
32
|
+
const { secretsToken } = options;
|
|
33
|
+
delete options.secretsToken;
|
|
34
|
+
if (secretsToken) {
|
|
35
|
+
Private.setToken(secretsToken);
|
|
36
|
+
}
|
|
30
37
|
return {
|
|
31
38
|
fieldRenderer: (props: FieldProps) => {
|
|
32
39
|
props.formContext = { ...props.formContext, ...options };
|
|
@@ -63,6 +70,8 @@ export class AiSettings extends React.Component<
|
|
|
63
70
|
|
|
64
71
|
this._useSecretsManager =
|
|
65
72
|
(this._settings.get('UseSecretsManager').composite as boolean) ?? true;
|
|
73
|
+
this._hideSecretFields =
|
|
74
|
+
(this._settings.get('HideSecretFields').composite as boolean) ?? true;
|
|
66
75
|
|
|
67
76
|
// Initialize the providers schema.
|
|
68
77
|
const providerSchema = JSONExt.deepCopy(baseSettings) as any;
|
|
@@ -115,6 +124,12 @@ export class AiSettings extends React.Component<
|
|
|
115
124
|
if (useSecretsManager !== this._useSecretsManager) {
|
|
116
125
|
this.updateUseSecretsManager(useSecretsManager);
|
|
117
126
|
}
|
|
127
|
+
const hideSecretFields =
|
|
128
|
+
(this._settings.get('HideSecretFields').composite as boolean) ?? true;
|
|
129
|
+
if (hideSecretFields !== this._hideSecretFields) {
|
|
130
|
+
this._hideSecretFields = hideSecretFields;
|
|
131
|
+
this._updateSchema();
|
|
132
|
+
}
|
|
118
133
|
});
|
|
119
134
|
}
|
|
120
135
|
|
|
@@ -128,7 +143,7 @@ export class AiSettings extends React.Component<
|
|
|
128
143
|
return;
|
|
129
144
|
}
|
|
130
145
|
|
|
131
|
-
await this._secretsManager.detachAll(SECRETS_NAMESPACE);
|
|
146
|
+
await this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
132
147
|
this._formInputs = [...inputs];
|
|
133
148
|
this._unsavedFields = [];
|
|
134
149
|
for (let i = 0; i < inputs.length; i++) {
|
|
@@ -137,6 +152,7 @@ export class AiSettings extends React.Component<
|
|
|
137
152
|
if (label) {
|
|
138
153
|
const id = getSecretId(this._provider, label);
|
|
139
154
|
this._secretsManager.attach(
|
|
155
|
+
Private.getToken(),
|
|
140
156
|
SECRETS_NAMESPACE,
|
|
141
157
|
id,
|
|
142
158
|
inputs[i],
|
|
@@ -151,6 +167,13 @@ export class AiSettings extends React.Component<
|
|
|
151
167
|
}
|
|
152
168
|
}
|
|
153
169
|
|
|
170
|
+
componentWillUnmount(): void {
|
|
171
|
+
if (!this._secretsManager || !this._useSecretsManager) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
175
|
+
}
|
|
176
|
+
|
|
154
177
|
/**
|
|
155
178
|
* Get the current provider from the local storage.
|
|
156
179
|
*/
|
|
@@ -192,7 +215,7 @@ export class AiSettings extends React.Component<
|
|
|
192
215
|
if (!value) {
|
|
193
216
|
// Detach all the password inputs attached to the secrets manager, and save the
|
|
194
217
|
// current settings to the local storage to save the password.
|
|
195
|
-
this._secretsManager?.detachAll(SECRETS_NAMESPACE);
|
|
218
|
+
this._secretsManager?.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
196
219
|
this._formInputs = [];
|
|
197
220
|
this._unsavedFields = [];
|
|
198
221
|
if (this._settingConnector instanceof SettingConnector) {
|
|
@@ -218,16 +241,6 @@ export class AiSettings extends React.Component<
|
|
|
218
241
|
.catch(console.error);
|
|
219
242
|
};
|
|
220
243
|
|
|
221
|
-
/**
|
|
222
|
-
* Update the UI schema of the form.
|
|
223
|
-
* Currently use to hide API keys.
|
|
224
|
-
*/
|
|
225
|
-
private _updateUiSchema(key: string) {
|
|
226
|
-
if (key.toLowerCase().includes('key')) {
|
|
227
|
-
this._uiSchema[key] = { 'ui:widget': 'password' };
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
244
|
/**
|
|
232
245
|
* Build the schema for a given provider.
|
|
233
246
|
*/
|
|
@@ -240,8 +253,13 @@ export class AiSettings extends React.Component<
|
|
|
240
253
|
|
|
241
254
|
if (settingsSchema) {
|
|
242
255
|
Object.entries(settingsSchema).forEach(([key, value]) => {
|
|
256
|
+
if (key.toLowerCase().includes('key')) {
|
|
257
|
+
if (this._hideSecretFields) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
this._uiSchema[key] = { 'ui:widget': 'password' };
|
|
261
|
+
}
|
|
243
262
|
schema.properties[key] = value;
|
|
244
|
-
this._updateUiSchema(key);
|
|
245
263
|
});
|
|
246
264
|
}
|
|
247
265
|
return schema as JSONSchema7;
|
|
@@ -349,6 +367,7 @@ export class AiSettings extends React.Component<
|
|
|
349
367
|
private _provider: string;
|
|
350
368
|
private _providerSchema: JSONSchema7;
|
|
351
369
|
private _useSecretsManager: boolean;
|
|
370
|
+
private _hideSecretFields: boolean;
|
|
352
371
|
private _rmRegistry: IRenderMimeRegistry | null;
|
|
353
372
|
private _secretsManager: ISecretsManager | null;
|
|
354
373
|
private _settingConnector: ISettingConnector | null;
|
|
@@ -359,3 +378,24 @@ export class AiSettings extends React.Component<
|
|
|
359
378
|
private _unsavedFields: string[] = [];
|
|
360
379
|
private _formInputs: HTMLInputElement[] = [];
|
|
361
380
|
}
|
|
381
|
+
|
|
382
|
+
namespace Private {
|
|
383
|
+
/**
|
|
384
|
+
* The token to use with the secrets manager.
|
|
385
|
+
*/
|
|
386
|
+
let secretsToken: symbol;
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Set of the token.
|
|
390
|
+
*/
|
|
391
|
+
export function setToken(value: symbol): void {
|
|
392
|
+
secretsToken = value;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* get the token.
|
|
397
|
+
*/
|
|
398
|
+
export function getToken(): symbol {
|
|
399
|
+
return secretsToken;
|
|
400
|
+
}
|
|
401
|
+
}
|
package/src/settings/utils.ts
CHANGED
package/src/tokens.ts
CHANGED
|
@@ -5,6 +5,14 @@ import { JSONSchema7 } from 'json-schema';
|
|
|
5
5
|
|
|
6
6
|
import { IBaseCompleter } from './base-completer';
|
|
7
7
|
|
|
8
|
+
export const PLUGIN_IDS = {
|
|
9
|
+
chat: '@jupyterlite/ai:chat',
|
|
10
|
+
chatCommandRegistry: '@jupyterlite/ai:autocompletion-registry',
|
|
11
|
+
completer: '@jupyterlite/ai:completer',
|
|
12
|
+
providerRegistry: '@jupyterlite/ai:provider-registry',
|
|
13
|
+
settingsConnector: '@jupyterlite/ai:settings-connector'
|
|
14
|
+
};
|
|
15
|
+
|
|
8
16
|
export interface IDict<T = any> {
|
|
9
17
|
[key: string]: T;
|
|
10
18
|
}
|