@jupyterlite/ai 0.4.0 → 0.6.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/lib/chat-handler.d.ts +9 -1
- package/lib/chat-handler.js +37 -1
- package/lib/completion-provider.d.ts +8 -1
- package/lib/components/stop-button.d.ts +19 -0
- package/lib/components/stop-button.js +32 -0
- package/lib/{llm-models/anthropic-completer.d.ts → default-providers/Anthropic/completer.d.ts} +1 -1
- package/lib/{llm-models/anthropic-completer.js → default-providers/Anthropic/completer.js} +1 -1
- package/lib/{llm-models/chrome-completer.d.ts → default-providers/ChromeAI/completer.d.ts} +1 -1
- package/lib/{llm-models/chrome-completer.js → default-providers/ChromeAI/completer.js} +1 -1
- package/lib/default-providers/ChromeAI/instructions.d.ts +2 -0
- package/lib/default-providers/ChromeAI/instructions.js +24 -0
- package/lib/{llm-models/codestral-completer.d.ts → default-providers/MistralAI/completer.d.ts} +1 -1
- package/lib/{llm-models/codestral-completer.js → default-providers/MistralAI/completer.js} +1 -1
- package/lib/default-providers/MistralAI/instructions.d.ts +2 -0
- package/lib/default-providers/MistralAI/instructions.js +16 -0
- package/lib/{llm-models/openai-completer.d.ts → default-providers/OpenAI/completer.d.ts} +1 -1
- package/lib/{llm-models/openai-completer.js → default-providers/OpenAI/completer.js} +1 -1
- package/lib/default-providers/index.d.ts +2 -0
- package/lib/default-providers/index.js +60 -0
- package/lib/index.d.ts +3 -2
- package/lib/index.js +57 -36
- package/lib/provider.d.ts +13 -12
- package/lib/provider.js +43 -9
- package/lib/settings/index.d.ts +3 -0
- package/lib/settings/index.js +3 -0
- package/lib/settings/panel.d.ts +17 -0
- package/lib/settings/panel.js +92 -5
- package/lib/settings/settings-connector.d.ts +31 -0
- package/lib/settings/settings-connector.js +61 -0
- package/lib/settings/utils.d.ts +3 -0
- package/lib/settings/utils.js +5 -0
- package/lib/tokens.d.ts +16 -4
- package/package.json +14 -7
- package/schema/provider-registry.json +6 -0
- package/src/chat-handler.ts +43 -1
- package/src/completion-provider.ts +8 -1
- package/src/components/stop-button.tsx +56 -0
- package/src/{llm-models/anthropic-completer.ts → default-providers/Anthropic/completer.ts} +2 -2
- package/src/{llm-models/chrome-completer.ts → default-providers/ChromeAI/completer.ts} +3 -2
- package/src/default-providers/ChromeAI/instructions.ts +24 -0
- package/src/{llm-models/codestral-completer.ts → default-providers/MistralAI/completer.ts} +2 -2
- package/src/default-providers/MistralAI/instructions.ts +16 -0
- package/src/{llm-models/openai-completer.ts → default-providers/OpenAI/completer.ts} +2 -2
- package/src/default-providers/index.ts +71 -0
- package/src/index.ts +77 -49
- package/src/provider.ts +58 -15
- package/src/settings/index.ts +3 -0
- package/src/settings/panel.tsx +109 -5
- package/src/settings/settings-connector.ts +89 -0
- package/src/settings/utils.ts +6 -0
- package/src/tokens.ts +17 -4
- package/lib/llm-models/index.d.ts +0 -4
- package/lib/llm-models/index.js +0 -43
- package/lib/settings/instructions.d.ts +0 -2
- package/lib/settings/instructions.js +0 -44
- package/lib/settings/schemas/index.d.ts +0 -3
- package/lib/settings/schemas/index.js +0 -11
- package/lib/slash-commands.d.ts +0 -16
- package/lib/slash-commands.js +0 -25
- package/src/llm-models/index.ts +0 -50
- package/src/settings/instructions.ts +0 -48
- package/src/settings/schemas/index.ts +0 -15
- package/src/slash-commands.tsx +0 -55
- /package/lib/{llm-models/base-completer.d.ts → base-completer.d.ts} +0 -0
- /package/lib/{llm-models/base-completer.js → base-completer.js} +0 -0
- /package/lib/{settings/schemas/_generated/Anthropic.json → default-providers/Anthropic/settings-schema.json} +0 -0
- /package/lib/{settings/schemas/_generated/ChromeAI.json → default-providers/ChromeAI/settings-schema.json} +0 -0
- /package/lib/{settings/schemas/_generated/MistralAI.json → default-providers/MistralAI/settings-schema.json} +0 -0
- /package/lib/{settings/schemas/_generated/OpenAI.json → default-providers/OpenAI/settings-schema.json} +0 -0
- /package/lib/settings/{schemas/base.json → base.json} +0 -0
- /package/src/{llm-models/base-completer.ts → base-completer.ts} +0 -0
- /package/src/{llm-models/svg.d.ts → global.d.ts} +0 -0
package/lib/provider.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Signal } from '@lumino/signaling';
|
|
2
|
+
import { getSecretId, SECRETS_NAMESPACE, SECRETS_REPLACEMENT } from './settings';
|
|
2
3
|
export const chatSystemPrompt = (options) => `
|
|
3
4
|
You are Jupyternaut, a conversational assistant living in JupyterLab to help users.
|
|
4
5
|
You are not a language model, but rather an application built on a foundation model from ${options.provider_name}.
|
|
@@ -26,7 +27,10 @@ would write.
|
|
|
26
27
|
Do not include the prompt in the output, only the string that should be appended to the current input.
|
|
27
28
|
`;
|
|
28
29
|
export class AIProviderRegistry {
|
|
29
|
-
|
|
30
|
+
/**
|
|
31
|
+
* The constructor of the provider registry.
|
|
32
|
+
*/
|
|
33
|
+
constructor(options) {
|
|
30
34
|
this._currentProvider = null;
|
|
31
35
|
this._completer = null;
|
|
32
36
|
this._chatModel = null;
|
|
@@ -35,6 +39,8 @@ export class AIProviderRegistry {
|
|
|
35
39
|
this._chatError = '';
|
|
36
40
|
this._completerError = '';
|
|
37
41
|
this._providers = new Map();
|
|
42
|
+
this._deferredProvider = null;
|
|
43
|
+
this._secretsManager = options.secretsManager || null;
|
|
38
44
|
}
|
|
39
45
|
/**
|
|
40
46
|
* Get the list of provider names.
|
|
@@ -46,10 +52,15 @@ export class AIProviderRegistry {
|
|
|
46
52
|
* Add a new provider.
|
|
47
53
|
*/
|
|
48
54
|
add(provider) {
|
|
55
|
+
var _a;
|
|
49
56
|
if (this._providers.has(provider.name)) {
|
|
50
57
|
throw new Error(`A AI provider named '${provider.name}' is already registered`);
|
|
51
58
|
}
|
|
52
59
|
this._providers.set(provider.name, provider);
|
|
60
|
+
// Set the provider if the loading has been deferred.
|
|
61
|
+
if (provider.name === ((_a = this._deferredProvider) === null || _a === void 0 ? void 0 : _a.name)) {
|
|
62
|
+
this.setProvider(this._deferredProvider);
|
|
63
|
+
}
|
|
53
64
|
}
|
|
54
65
|
/**
|
|
55
66
|
* Get the current provider name.
|
|
@@ -119,15 +130,36 @@ export class AIProviderRegistry {
|
|
|
119
130
|
* Set the providers (chat model and completer).
|
|
120
131
|
* Creates the providers if the name has changed, otherwise only updates their config.
|
|
121
132
|
*
|
|
122
|
-
* @param
|
|
123
|
-
* @param settings - the settings for the models.
|
|
133
|
+
* @param options - An object with the name and the settings of the provider to use.
|
|
124
134
|
*/
|
|
125
|
-
setProvider(
|
|
126
|
-
var _a, _b, _c;
|
|
135
|
+
async setProvider(options) {
|
|
136
|
+
var _a, _b, _c, _d;
|
|
137
|
+
const { name, settings } = options;
|
|
127
138
|
this._currentProvider = (_a = this._providers.get(name)) !== null && _a !== void 0 ? _a : null;
|
|
128
|
-
if (
|
|
139
|
+
if (this._currentProvider === null) {
|
|
140
|
+
// The current provider may not be loaded when the settings are first loaded.
|
|
141
|
+
// Let's defer the provider loading.
|
|
142
|
+
this._deferredProvider = options;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
this._deferredProvider = null;
|
|
146
|
+
}
|
|
147
|
+
// Build a new settings object containing the secrets.
|
|
148
|
+
const fullSettings = {};
|
|
149
|
+
for (const key of Object.keys(settings)) {
|
|
150
|
+
if (settings[key] === SECRETS_REPLACEMENT) {
|
|
151
|
+
const id = getSecretId(name, key);
|
|
152
|
+
const secrets = await ((_b = this._secretsManager) === null || _b === void 0 ? void 0 : _b.get(SECRETS_NAMESPACE, id));
|
|
153
|
+
fullSettings[key] = (secrets === null || secrets === void 0 ? void 0 : secrets.value) || settings[key];
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
fullSettings[key] = settings[key];
|
|
157
|
+
}
|
|
158
|
+
if (((_c = this._currentProvider) === null || _c === void 0 ? void 0 : _c.completer) !== undefined) {
|
|
129
159
|
try {
|
|
130
|
-
this._completer = new this._currentProvider.completer({
|
|
160
|
+
this._completer = new this._currentProvider.completer({
|
|
161
|
+
...fullSettings
|
|
162
|
+
});
|
|
131
163
|
this._completerError = '';
|
|
132
164
|
}
|
|
133
165
|
catch (e) {
|
|
@@ -137,9 +169,11 @@ export class AIProviderRegistry {
|
|
|
137
169
|
else {
|
|
138
170
|
this._completer = null;
|
|
139
171
|
}
|
|
140
|
-
if (((
|
|
172
|
+
if (((_d = this._currentProvider) === null || _d === void 0 ? void 0 : _d.chatModel) !== undefined) {
|
|
141
173
|
try {
|
|
142
|
-
this._chatModel = new this._currentProvider.chatModel({
|
|
174
|
+
this._chatModel = new this._currentProvider.chatModel({
|
|
175
|
+
...fullSettings
|
|
176
|
+
});
|
|
143
177
|
this._chatError = '';
|
|
144
178
|
}
|
|
145
179
|
catch (e) {
|
package/lib/settings/panel.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
2
|
+
import { ISettingConnector } from '@jupyterlab/settingregistry';
|
|
2
3
|
import { IFormRenderer } from '@jupyterlab/ui-components';
|
|
3
4
|
import type { FieldProps } from '@rjsf/utils';
|
|
4
5
|
import { JSONSchema7 } from 'json-schema';
|
|
6
|
+
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
5
7
|
import React from 'react';
|
|
6
8
|
import { IAIProviderRegistry, IDict } from '../tokens';
|
|
7
9
|
export declare const aiSettingsRenderer: (options: {
|
|
8
10
|
providerRegistry: IAIProviderRegistry;
|
|
9
11
|
rmRegistry?: IRenderMimeRegistry;
|
|
12
|
+
secretsManager?: ISecretsManager;
|
|
13
|
+
settingConnector?: ISettingConnector;
|
|
10
14
|
}) => IFormRenderer;
|
|
11
15
|
export interface ISettingsFormStates {
|
|
12
16
|
schema: JSONSchema7;
|
|
@@ -14,6 +18,7 @@ export interface ISettingsFormStates {
|
|
|
14
18
|
}
|
|
15
19
|
export declare class AiSettings extends React.Component<FieldProps, ISettingsFormStates> {
|
|
16
20
|
constructor(props: FieldProps);
|
|
21
|
+
componentDidUpdate(): Promise<void>;
|
|
17
22
|
/**
|
|
18
23
|
* Get the current provider from the local storage.
|
|
19
24
|
*/
|
|
@@ -30,6 +35,7 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
|
|
|
30
35
|
* Save settings in local storage for a given provider.
|
|
31
36
|
*/
|
|
32
37
|
saveSettings(value: IDict<any>): void;
|
|
38
|
+
private updateUseSecretsManager;
|
|
33
39
|
/**
|
|
34
40
|
* Update the UI schema of the form.
|
|
35
41
|
* Currently use to hide API keys.
|
|
@@ -53,6 +59,11 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
|
|
|
53
59
|
* Update the Jupyterlab settings accordingly.
|
|
54
60
|
*/
|
|
55
61
|
private _onProviderChanged;
|
|
62
|
+
/**
|
|
63
|
+
* Callback function called when the password input has been programmatically updated
|
|
64
|
+
* with the secret manager.
|
|
65
|
+
*/
|
|
66
|
+
private _onPasswordUpdated;
|
|
56
67
|
/**
|
|
57
68
|
* Triggered when the form value has changed, to update the current settings and save
|
|
58
69
|
* it in local storage.
|
|
@@ -63,8 +74,14 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
|
|
|
63
74
|
private _providerRegistry;
|
|
64
75
|
private _provider;
|
|
65
76
|
private _providerSchema;
|
|
77
|
+
private _useSecretsManager;
|
|
66
78
|
private _rmRegistry;
|
|
79
|
+
private _secretsManager;
|
|
80
|
+
private _settingConnector;
|
|
67
81
|
private _currentSettings;
|
|
68
82
|
private _uiSchema;
|
|
69
83
|
private _settings;
|
|
84
|
+
private _formRef;
|
|
85
|
+
private _unsavedFields;
|
|
86
|
+
private _formInputs;
|
|
70
87
|
}
|
package/lib/settings/panel.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { FormComponent } from '@jupyterlab/ui-components';
|
|
2
|
+
import { ArrayExt } from '@lumino/algorithm';
|
|
2
3
|
import { JSONExt } from '@lumino/coreutils';
|
|
3
4
|
import validator from '@rjsf/validator-ajv8';
|
|
4
5
|
import React from 'react';
|
|
5
|
-
import
|
|
6
|
+
import { getSecretId, SECRETS_NAMESPACE, SettingConnector } from '.';
|
|
7
|
+
import baseSettings from './base.json';
|
|
6
8
|
const MD_MIME_TYPE = 'text/markdown';
|
|
7
9
|
const STORAGE_NAME = '@jupyterlite/ai:settings';
|
|
8
10
|
const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
|
|
@@ -19,8 +21,40 @@ const WrappedFormComponent = (props) => {
|
|
|
19
21
|
};
|
|
20
22
|
export class AiSettings extends React.Component {
|
|
21
23
|
constructor(props) {
|
|
22
|
-
var _a, _b;
|
|
24
|
+
var _a, _b, _c, _d, _e;
|
|
23
25
|
super(props);
|
|
26
|
+
this.updateUseSecretsManager = (value) => {
|
|
27
|
+
var _a;
|
|
28
|
+
this._useSecretsManager = value;
|
|
29
|
+
if (!value) {
|
|
30
|
+
// Detach all the password inputs attached to the secrets manager, and save the
|
|
31
|
+
// current settings to the local storage to save the password.
|
|
32
|
+
(_a = this._secretsManager) === null || _a === void 0 ? void 0 : _a.detachAll(SECRETS_NAMESPACE);
|
|
33
|
+
this._formInputs = [];
|
|
34
|
+
this._unsavedFields = [];
|
|
35
|
+
if (this._settingConnector instanceof SettingConnector) {
|
|
36
|
+
this._settingConnector.doNotSave = [];
|
|
37
|
+
}
|
|
38
|
+
this.saveSettings(this._currentSettings);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Remove all the keys stored locally and attach the password inputs to the
|
|
42
|
+
// secrets manager.
|
|
43
|
+
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
|
|
44
|
+
Object.keys(settings).forEach(provider => {
|
|
45
|
+
Object.keys(settings[provider])
|
|
46
|
+
.filter(key => key.toLowerCase().includes('key'))
|
|
47
|
+
.forEach(key => {
|
|
48
|
+
delete settings[provider][key];
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
52
|
+
this.componentDidUpdate();
|
|
53
|
+
}
|
|
54
|
+
this._settings
|
|
55
|
+
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
56
|
+
.catch(console.error);
|
|
57
|
+
};
|
|
24
58
|
/**
|
|
25
59
|
* Triggered when the provider hes changed, to update the schema and values.
|
|
26
60
|
* Update the Jupyterlab settings accordingly.
|
|
@@ -39,6 +73,16 @@ export class AiSettings extends React.Component {
|
|
|
39
73
|
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
40
74
|
.catch(console.error);
|
|
41
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Callback function called when the password input has been programmatically updated
|
|
78
|
+
* with the secret manager.
|
|
79
|
+
*/
|
|
80
|
+
this._onPasswordUpdated = (fieldName, value) => {
|
|
81
|
+
this._currentSettings[fieldName] = value;
|
|
82
|
+
this._settings
|
|
83
|
+
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
84
|
+
.catch(console.error);
|
|
85
|
+
};
|
|
42
86
|
/**
|
|
43
87
|
* Triggered when the form value has changed, to update the current settings and save
|
|
44
88
|
* it in local storage.
|
|
@@ -53,12 +97,19 @@ export class AiSettings extends React.Component {
|
|
|
53
97
|
};
|
|
54
98
|
this._currentSettings = { provider: 'None' };
|
|
55
99
|
this._uiSchema = {};
|
|
100
|
+
this._formRef = React.createRef();
|
|
101
|
+
this._unsavedFields = [];
|
|
102
|
+
this._formInputs = [];
|
|
56
103
|
if (!props.formContext.providerRegistry) {
|
|
57
104
|
throw new Error('The provider registry is needed to enable the jupyterlite-ai settings panel');
|
|
58
105
|
}
|
|
59
106
|
this._providerRegistry = props.formContext.providerRegistry;
|
|
60
107
|
this._rmRegistry = (_a = props.formContext.rmRegistry) !== null && _a !== void 0 ? _a : null;
|
|
108
|
+
this._secretsManager = (_b = props.formContext.secretsManager) !== null && _b !== void 0 ? _b : null;
|
|
109
|
+
this._settingConnector = (_c = props.formContext.settingConnector) !== null && _c !== void 0 ? _c : null;
|
|
61
110
|
this._settings = props.formContext.settings;
|
|
111
|
+
this._useSecretsManager =
|
|
112
|
+
(_d = this._settings.get('UseSecretsManager').composite) !== null && _d !== void 0 ? _d : true;
|
|
62
113
|
// Initialize the providers schema.
|
|
63
114
|
const providerSchema = JSONExt.deepCopy(baseSettings);
|
|
64
115
|
providerSchema.properties.provider = {
|
|
@@ -76,7 +127,7 @@ export class AiSettings extends React.Component {
|
|
|
76
127
|
const labSettings = this._settings.get('AIprovider').composite;
|
|
77
128
|
if (labSettings && Object.keys(labSettings).includes('provider')) {
|
|
78
129
|
// Get the provider name.
|
|
79
|
-
const provider = (
|
|
130
|
+
const provider = (_e = Object.entries(labSettings).find(v => v[0] === 'provider')) === null || _e === void 0 ? void 0 : _e[1];
|
|
80
131
|
// Save the settings.
|
|
81
132
|
const settings = {
|
|
82
133
|
_current: provider
|
|
@@ -96,6 +147,40 @@ export class AiSettings extends React.Component {
|
|
|
96
147
|
this._settings
|
|
97
148
|
.set('AIprovider', this._currentSettings)
|
|
98
149
|
.catch(console.error);
|
|
150
|
+
this._settings.changed.connect(() => {
|
|
151
|
+
var _a;
|
|
152
|
+
const useSecretsManager = (_a = this._settings.get('UseSecretsManager').composite) !== null && _a !== void 0 ? _a : true;
|
|
153
|
+
if (useSecretsManager !== this._useSecretsManager) {
|
|
154
|
+
this.updateUseSecretsManager(useSecretsManager);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
async componentDidUpdate() {
|
|
159
|
+
var _a;
|
|
160
|
+
if (!this._secretsManager || !this._useSecretsManager) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// Attach the password inputs to the secrets manager only if they have changed.
|
|
164
|
+
const inputs = ((_a = this._formRef.current) === null || _a === void 0 ? void 0 : _a.getElementsByTagName('input')) || [];
|
|
165
|
+
if (ArrayExt.shallowEqual(inputs, this._formInputs)) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
await this._secretsManager.detachAll(SECRETS_NAMESPACE);
|
|
169
|
+
this._formInputs = [...inputs];
|
|
170
|
+
this._unsavedFields = [];
|
|
171
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
172
|
+
if (inputs[i].type.toLowerCase() === 'password') {
|
|
173
|
+
const label = inputs[i].getAttribute('label');
|
|
174
|
+
if (label) {
|
|
175
|
+
const id = getSecretId(this._provider, label);
|
|
176
|
+
this._secretsManager.attach(SECRETS_NAMESPACE, id, inputs[i], (value) => this._onPasswordUpdated(label, value));
|
|
177
|
+
this._unsavedFields.push(label);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (this._settingConnector instanceof SettingConnector) {
|
|
182
|
+
this._settingConnector.doNotSave = this._unsavedFields;
|
|
183
|
+
}
|
|
99
184
|
}
|
|
100
185
|
/**
|
|
101
186
|
* Get the current provider from the local storage.
|
|
@@ -126,8 +211,10 @@ export class AiSettings extends React.Component {
|
|
|
126
211
|
*/
|
|
127
212
|
saveSettings(value) {
|
|
128
213
|
var _a;
|
|
214
|
+
const currentSettings = { ...value };
|
|
129
215
|
const settings = JSON.parse((_a = localStorage.getItem(STORAGE_NAME)) !== null && _a !== void 0 ? _a : '{}');
|
|
130
|
-
|
|
216
|
+
this._unsavedFields.forEach(field => delete currentSettings[field]);
|
|
217
|
+
settings[this._provider] = currentSettings;
|
|
131
218
|
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
132
219
|
}
|
|
133
220
|
/**
|
|
@@ -180,7 +267,7 @@ export class AiSettings extends React.Component {
|
|
|
180
267
|
this.setState({ instruction: renderer.node });
|
|
181
268
|
}
|
|
182
269
|
render() {
|
|
183
|
-
return (React.createElement(
|
|
270
|
+
return (React.createElement("div", { ref: this._formRef },
|
|
184
271
|
React.createElement(WrappedFormComponent, { formData: { provider: this._provider }, schema: this._providerSchema, onChange: this._onProviderChanged }),
|
|
185
272
|
this.state.instruction !== null && (React.createElement("details", null,
|
|
186
273
|
React.createElement("summary", { className: INSTRUCTION_CLASS }, "Instructions"),
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ISettingConnector, ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
2
|
+
import { DataConnector, IDataConnector } from '@jupyterlab/statedb';
|
|
3
|
+
/**
|
|
4
|
+
* A data connector for fetching settings.
|
|
5
|
+
*
|
|
6
|
+
* #### Notes
|
|
7
|
+
* This connector adds a query parameter to the base services setting manager.
|
|
8
|
+
*/
|
|
9
|
+
export declare class SettingConnector extends DataConnector<ISettingRegistry.IPlugin, string> implements ISettingConnector {
|
|
10
|
+
constructor(connector: IDataConnector<ISettingRegistry.IPlugin, string>);
|
|
11
|
+
set doNotSave(fields: string[]);
|
|
12
|
+
/**
|
|
13
|
+
* Fetch settings for a plugin.
|
|
14
|
+
* @param id - The plugin ID
|
|
15
|
+
*
|
|
16
|
+
* #### Notes
|
|
17
|
+
* The REST API requests are throttled at one request per plugin per 100ms.
|
|
18
|
+
*/
|
|
19
|
+
fetch(id: string): Promise<ISettingRegistry.IPlugin | undefined>;
|
|
20
|
+
list(query: 'ids'): Promise<{
|
|
21
|
+
ids: string[];
|
|
22
|
+
}>;
|
|
23
|
+
list(query: 'active' | 'all'): Promise<{
|
|
24
|
+
ids: string[];
|
|
25
|
+
values: ISettingRegistry.IPlugin[];
|
|
26
|
+
}>;
|
|
27
|
+
save(id: string, raw: string): Promise<void>;
|
|
28
|
+
private _connector;
|
|
29
|
+
private _doNotSave;
|
|
30
|
+
private _throttlers;
|
|
31
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { PageConfig } from '@jupyterlab/coreutils';
|
|
2
|
+
import { DataConnector } from '@jupyterlab/statedb';
|
|
3
|
+
import { Throttler } from '@lumino/polling';
|
|
4
|
+
import * as json5 from 'json5';
|
|
5
|
+
import { SECRETS_REPLACEMENT } from '.';
|
|
6
|
+
/**
|
|
7
|
+
* A data connector for fetching settings.
|
|
8
|
+
*
|
|
9
|
+
* #### Notes
|
|
10
|
+
* This connector adds a query parameter to the base services setting manager.
|
|
11
|
+
*/
|
|
12
|
+
export class SettingConnector extends DataConnector {
|
|
13
|
+
constructor(connector) {
|
|
14
|
+
super();
|
|
15
|
+
this._doNotSave = [];
|
|
16
|
+
this._throttlers = Object.create(null);
|
|
17
|
+
this._connector = connector;
|
|
18
|
+
}
|
|
19
|
+
set doNotSave(fields) {
|
|
20
|
+
this._doNotSave = [...fields];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Fetch settings for a plugin.
|
|
24
|
+
* @param id - The plugin ID
|
|
25
|
+
*
|
|
26
|
+
* #### Notes
|
|
27
|
+
* The REST API requests are throttled at one request per plugin per 100ms.
|
|
28
|
+
*/
|
|
29
|
+
fetch(id) {
|
|
30
|
+
const throttlers = this._throttlers;
|
|
31
|
+
if (!(id in throttlers)) {
|
|
32
|
+
throttlers[id] = new Throttler(() => this._connector.fetch(id), 100);
|
|
33
|
+
}
|
|
34
|
+
return throttlers[id].invoke();
|
|
35
|
+
}
|
|
36
|
+
async list(query = 'all') {
|
|
37
|
+
const { isDisabled } = PageConfig.Extension;
|
|
38
|
+
const { ids, values } = await this._connector.list(query === 'ids' ? 'ids' : undefined);
|
|
39
|
+
if (query === 'all') {
|
|
40
|
+
return { ids, values };
|
|
41
|
+
}
|
|
42
|
+
if (query === 'ids') {
|
|
43
|
+
return { ids };
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
ids: ids.filter(id => !isDisabled(id)),
|
|
47
|
+
values: values.filter(({ id }) => !isDisabled(id))
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async save(id, raw) {
|
|
51
|
+
const settings = json5.parse(raw);
|
|
52
|
+
this._doNotSave.forEach(field => {
|
|
53
|
+
if (settings['AIprovider'] !== undefined &&
|
|
54
|
+
settings['AIprovider'][field] !== undefined &&
|
|
55
|
+
settings['AIprovider'][field] !== '') {
|
|
56
|
+
settings['AIprovider'][field] = SECRETS_REPLACEMENT;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
await this._connector.save(id, json5.stringify(settings, null, 2));
|
|
60
|
+
}
|
|
61
|
+
}
|
package/lib/tokens.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
|
2
2
|
import { ReadonlyPartialJSONObject, Token } from '@lumino/coreutils';
|
|
3
3
|
import { ISignal } from '@lumino/signaling';
|
|
4
4
|
import { JSONSchema7 } from 'json-schema';
|
|
5
|
-
import { IBaseCompleter } from './
|
|
5
|
+
import { IBaseCompleter } from './base-completer';
|
|
6
6
|
export interface IDict<T = any> {
|
|
7
7
|
[key: string]: T;
|
|
8
8
|
}
|
|
@@ -80,10 +80,9 @@ export interface IAIProviderRegistry {
|
|
|
80
80
|
* Set the providers (chat model and completer).
|
|
81
81
|
* Creates the providers if the name has changed, otherwise only updates their config.
|
|
82
82
|
*
|
|
83
|
-
* @param
|
|
84
|
-
* @param settings - the settings for the models.
|
|
83
|
+
* @param options - an object with the name and the settings of the provider to use.
|
|
85
84
|
*/
|
|
86
|
-
setProvider(
|
|
85
|
+
setProvider(options: ISetProviderOptions): void;
|
|
87
86
|
/**
|
|
88
87
|
* A signal emitting when the provider or its settings has changed.
|
|
89
88
|
*/
|
|
@@ -97,6 +96,19 @@ export interface IAIProviderRegistry {
|
|
|
97
96
|
*/
|
|
98
97
|
readonly completerError: string;
|
|
99
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* The set provider options.
|
|
101
|
+
*/
|
|
102
|
+
export interface ISetProviderOptions {
|
|
103
|
+
/**
|
|
104
|
+
* The name of the provider.
|
|
105
|
+
*/
|
|
106
|
+
name: string;
|
|
107
|
+
/**
|
|
108
|
+
* The settings of the provider.
|
|
109
|
+
*/
|
|
110
|
+
settings: ReadonlyPartialJSONObject;
|
|
111
|
+
}
|
|
100
112
|
/**
|
|
101
113
|
* The provider registry token.
|
|
102
114
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlite/ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "AI code completions and chat for JupyterLite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"license": "BSD-3-Clause",
|
|
15
15
|
"author": "JupyterLite Contributors",
|
|
16
16
|
"files": [
|
|
17
|
-
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
|
|
17
|
+
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf,md}",
|
|
18
18
|
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
|
|
19
19
|
"src/**/*.{ts,tsx}",
|
|
20
20
|
"schema/*.json"
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"url": "https://github.com/jupyterlite/ai.git"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
|
-
"build": "
|
|
30
|
+
"build": "jlpm build:lib && jlpm build:labextension:dev",
|
|
31
31
|
"build:dev": "jlpm build:lib && jlpm build:labextension:dev",
|
|
32
|
-
"build:prod": "
|
|
32
|
+
"build:prod": "jlpm settings:build && jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
|
|
33
33
|
"build:labextension": "jupyter labextension build .",
|
|
34
34
|
"build:labextension:dev": "jupyter labextension build --development True .",
|
|
35
35
|
"build:lib": "tsc --sourceMap",
|
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
"prettier": "jlpm prettier:base --write --list-different",
|
|
48
48
|
"prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
|
|
49
49
|
"prettier:check": "jlpm prettier:base --check",
|
|
50
|
+
"settings:build": "node ./scripts/settings-checker.js --generate",
|
|
51
|
+
"settings:check": "node ./scripts/settings-checker.js",
|
|
50
52
|
"stylelint": "jlpm stylelint:check --fix",
|
|
51
53
|
"stylelint:check": "stylelint --cache \"style/**/*.css\"",
|
|
52
54
|
"watch": "run-p watch:src watch:labextension",
|
|
@@ -54,13 +56,13 @@
|
|
|
54
56
|
"watch:labextension": "jupyter labextension watch ."
|
|
55
57
|
},
|
|
56
58
|
"dependencies": {
|
|
57
|
-
"@jupyter/chat": "^0.
|
|
59
|
+
"@jupyter/chat": "^0.9.0",
|
|
58
60
|
"@jupyterlab/application": "^4.4.0-alpha.0",
|
|
59
61
|
"@jupyterlab/apputils": "^4.5.0-alpha.0",
|
|
60
62
|
"@jupyterlab/completer": "^4.4.0-alpha.0",
|
|
61
63
|
"@jupyterlab/notebook": "^4.4.0-alpha.0",
|
|
62
64
|
"@jupyterlab/rendermime": "^4.4.0-alpha.0",
|
|
63
|
-
"@jupyterlab/settingregistry": "^4.4.0-
|
|
65
|
+
"@jupyterlab/settingregistry": "^4.4.0-beta.1",
|
|
64
66
|
"@jupyterlab/ui-components": "^4.4.0-alpha.0",
|
|
65
67
|
"@langchain/anthropic": "^0.3.9",
|
|
66
68
|
"@langchain/community": "^0.3.31",
|
|
@@ -75,6 +77,8 @@
|
|
|
75
77
|
"@rjsf/core": "^4.2.0",
|
|
76
78
|
"@rjsf/utils": "^5.18.4",
|
|
77
79
|
"@rjsf/validator-ajv8": "^5.18.4",
|
|
80
|
+
"json5": "^2.2.3",
|
|
81
|
+
"jupyter-secrets-manager": "^0.2.0",
|
|
78
82
|
"react": "^18.2.0",
|
|
79
83
|
"react-dom": "^18.2.0"
|
|
80
84
|
},
|
|
@@ -115,6 +119,9 @@
|
|
|
115
119
|
"jupyterlab": {
|
|
116
120
|
"extension": true,
|
|
117
121
|
"outputDir": "jupyterlite_ai/labextension",
|
|
118
|
-
"schemaDir": "schema"
|
|
122
|
+
"schemaDir": "schema",
|
|
123
|
+
"disabledExtensions": [
|
|
124
|
+
"@jupyterlab/apputils-extension:settings-connector"
|
|
125
|
+
]
|
|
119
126
|
}
|
|
120
127
|
}
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
"jupyter.lab.setting-icon-label": "JupyterLite AI Chat",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"properties": {
|
|
8
|
+
"UseSecretsManager": {
|
|
9
|
+
"type": "boolean",
|
|
10
|
+
"title": "Use secrets manager",
|
|
11
|
+
"description": "Whether to use or not the secrets manager. If not, secrets will be stored in the browser (local storage)",
|
|
12
|
+
"default": true
|
|
13
|
+
},
|
|
8
14
|
"AIprovider": {
|
|
9
15
|
"type": "object",
|
|
10
16
|
"title": "AI provider",
|
package/src/chat-handler.ts
CHANGED
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
ChatCommand,
|
|
7
8
|
ChatModel,
|
|
9
|
+
IChatCommandProvider,
|
|
8
10
|
IChatHistory,
|
|
9
11
|
IChatMessage,
|
|
12
|
+
IInputModel,
|
|
10
13
|
INewMessage
|
|
11
14
|
} from '@jupyter/chat';
|
|
12
15
|
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
@@ -136,9 +139,11 @@ export class ChatHandler extends ChatModel {
|
|
|
136
139
|
|
|
137
140
|
let content = '';
|
|
138
141
|
|
|
142
|
+
this._controller = new AbortController();
|
|
139
143
|
try {
|
|
140
144
|
for await (const chunk of await this._providerRegistry.currentChatModel.stream(
|
|
141
|
-
messages
|
|
145
|
+
messages,
|
|
146
|
+
{ signal: this._controller.signal }
|
|
142
147
|
)) {
|
|
143
148
|
content += chunk.content ?? chunk;
|
|
144
149
|
botMsg.body = content;
|
|
@@ -159,6 +164,7 @@ export class ChatHandler extends ChatModel {
|
|
|
159
164
|
return false;
|
|
160
165
|
} finally {
|
|
161
166
|
this.updateWriters([]);
|
|
167
|
+
this._controller = null;
|
|
162
168
|
}
|
|
163
169
|
}
|
|
164
170
|
|
|
@@ -174,16 +180,52 @@ export class ChatHandler extends ChatModel {
|
|
|
174
180
|
super.messageAdded(message);
|
|
175
181
|
}
|
|
176
182
|
|
|
183
|
+
stopStreaming(): void {
|
|
184
|
+
this._controller?.abort();
|
|
185
|
+
}
|
|
186
|
+
|
|
177
187
|
private _providerRegistry: IAIProviderRegistry;
|
|
178
188
|
private _personaName = 'AI';
|
|
179
189
|
private _prompt: string;
|
|
180
190
|
private _errorMessage: string = '';
|
|
181
191
|
private _history: IChatHistory = { messages: [] };
|
|
182
192
|
private _defaultErrorMessage = 'AI provider not configured';
|
|
193
|
+
private _controller: AbortController | null = null;
|
|
183
194
|
}
|
|
184
195
|
|
|
185
196
|
export namespace ChatHandler {
|
|
186
197
|
export interface IOptions extends ChatModel.IOptions {
|
|
187
198
|
providerRegistry: IAIProviderRegistry;
|
|
188
199
|
}
|
|
200
|
+
|
|
201
|
+
export class ClearCommandProvider implements IChatCommandProvider {
|
|
202
|
+
public id: string = '@jupyterlite/ai:clear-commands';
|
|
203
|
+
private _slash_commands: ChatCommand[] = [
|
|
204
|
+
{
|
|
205
|
+
name: '/clear',
|
|
206
|
+
providerId: this.id,
|
|
207
|
+
replaceWith: '/clear',
|
|
208
|
+
description: 'Clear the chat'
|
|
209
|
+
}
|
|
210
|
+
];
|
|
211
|
+
async getChatCommands(inputModel: IInputModel) {
|
|
212
|
+
const match = inputModel.currentWord?.match(/^\/\w*/)?.[0];
|
|
213
|
+
if (!match) {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const commands = this._slash_commands.filter(cmd =>
|
|
218
|
+
cmd.name.startsWith(match)
|
|
219
|
+
);
|
|
220
|
+
return commands;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async handleChatCommand(
|
|
224
|
+
command: ChatCommand,
|
|
225
|
+
inputModel: IInputModel
|
|
226
|
+
): Promise<void> {
|
|
227
|
+
// no handling needed because `replaceWith` is set in each command.
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
189
231
|
}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
IInlineCompletionProvider
|
|
5
5
|
} from '@jupyterlab/completer';
|
|
6
6
|
|
|
7
|
-
import { IBaseCompleter } from './
|
|
7
|
+
import { IBaseCompleter } from './base-completer';
|
|
8
8
|
import { IAIProviderRegistry } from './tokens';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -51,7 +51,14 @@ export class CompletionProvider implements IInlineCompletionProvider {
|
|
|
51
51
|
|
|
52
52
|
export namespace CompletionProvider {
|
|
53
53
|
export interface IOptions {
|
|
54
|
+
/**
|
|
55
|
+
* The registry where the completion provider belongs.
|
|
56
|
+
*/
|
|
54
57
|
providerRegistry: IAIProviderRegistry;
|
|
58
|
+
/**
|
|
59
|
+
* The request completion commands, can be useful if a provider needs to request
|
|
60
|
+
* the completion by itself.
|
|
61
|
+
*/
|
|
55
62
|
requestCompletion: () => void;
|
|
56
63
|
}
|
|
57
64
|
}
|