@jupyterlite/ai 0.5.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/chat-handler.d.ts +2 -0
- package/lib/chat-handler.js +8 -1
- package/lib/completion-provider.d.ts +7 -0
- package/lib/components/stop-button.d.ts +19 -0
- package/lib/components/stop-button.js +32 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +56 -16
- package/lib/provider.d.ts +15 -10
- package/lib/provider.js +67 -9
- package/lib/settings/index.d.ts +3 -0
- package/lib/settings/index.js +3 -0
- package/lib/settings/panel.d.ts +6 -5
- package/lib/settings/panel.js +66 -19
- package/lib/settings/settings-connector.d.ts +31 -0
- package/lib/settings/settings-connector.js +61 -0
- package/lib/settings/utils.d.ts +2 -0
- package/lib/settings/utils.js +4 -0
- package/lib/tokens.d.ts +22 -3
- package/lib/tokens.js +7 -0
- package/package.json +17 -12
- package/schema/provider-registry.json +6 -0
- package/src/chat-handler.ts +9 -1
- package/src/completion-provider.ts +7 -0
- package/src/components/stop-button.tsx +56 -0
- package/src/index.ts +102 -52
- package/src/provider.ts +84 -12
- package/src/settings/index.ts +3 -0
- package/src/settings/panel.tsx +72 -17
- package/src/settings/settings-connector.ts +89 -0
- package/src/settings/utils.ts +5 -0
- package/src/tokens.ts +24 -3
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, SettingConnector } from '.';
|
|
6
7
|
import baseSettings from './base.json';
|
|
7
|
-
|
|
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;
|
|
31
|
+
var _a, _b, _c, _d, _e, _f;
|
|
25
32
|
super(props);
|
|
26
33
|
this.updateUseSecretsManager = (value) => {
|
|
27
34
|
var _a;
|
|
@@ -29,9 +36,12 @@ 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 = [];
|
|
42
|
+
if (this._settingConnector instanceof SettingConnector) {
|
|
43
|
+
this._settingConnector.doNotSave = [];
|
|
44
|
+
}
|
|
35
45
|
this.saveSettings(this._currentSettings);
|
|
36
46
|
}
|
|
37
47
|
else {
|
|
@@ -48,6 +58,9 @@ export class AiSettings extends React.Component {
|
|
|
48
58
|
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
49
59
|
this.componentDidUpdate();
|
|
50
60
|
}
|
|
61
|
+
this._settings
|
|
62
|
+
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
63
|
+
.catch(console.error);
|
|
51
64
|
};
|
|
52
65
|
/**
|
|
53
66
|
* Triggered when the provider hes changed, to update the schema and values.
|
|
@@ -100,9 +113,12 @@ export class AiSettings extends React.Component {
|
|
|
100
113
|
this._providerRegistry = props.formContext.providerRegistry;
|
|
101
114
|
this._rmRegistry = (_a = props.formContext.rmRegistry) !== null && _a !== void 0 ? _a : null;
|
|
102
115
|
this._secretsManager = (_b = props.formContext.secretsManager) !== null && _b !== void 0 ? _b : null;
|
|
116
|
+
this._settingConnector = (_c = props.formContext.settingConnector) !== null && _c !== void 0 ? _c : null;
|
|
103
117
|
this._settings = props.formContext.settings;
|
|
104
118
|
this._useSecretsManager =
|
|
105
|
-
(
|
|
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;
|
|
106
122
|
// Initialize the providers schema.
|
|
107
123
|
const providerSchema = JSONExt.deepCopy(baseSettings);
|
|
108
124
|
providerSchema.properties.provider = {
|
|
@@ -120,7 +136,7 @@ export class AiSettings extends React.Component {
|
|
|
120
136
|
const labSettings = this._settings.get('AIprovider').composite;
|
|
121
137
|
if (labSettings && Object.keys(labSettings).includes('provider')) {
|
|
122
138
|
// Get the provider name.
|
|
123
|
-
const provider = (
|
|
139
|
+
const provider = (_f = Object.entries(labSettings).find(v => v[0] === 'provider')) === null || _f === void 0 ? void 0 : _f[1];
|
|
124
140
|
// Save the settings.
|
|
125
141
|
const settings = {
|
|
126
142
|
_current: provider
|
|
@@ -141,11 +157,16 @@ export class AiSettings extends React.Component {
|
|
|
141
157
|
.set('AIprovider', this._currentSettings)
|
|
142
158
|
.catch(console.error);
|
|
143
159
|
this._settings.changed.connect(() => {
|
|
144
|
-
var _a;
|
|
160
|
+
var _a, _b;
|
|
145
161
|
const useSecretsManager = (_a = this._settings.get('UseSecretsManager').composite) !== null && _a !== void 0 ? _a : true;
|
|
146
162
|
if (useSecretsManager !== this._useSecretsManager) {
|
|
147
163
|
this.updateUseSecretsManager(useSecretsManager);
|
|
148
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
|
+
}
|
|
149
170
|
});
|
|
150
171
|
}
|
|
151
172
|
async componentDidUpdate() {
|
|
@@ -158,19 +179,28 @@ export class AiSettings extends React.Component {
|
|
|
158
179
|
if (ArrayExt.shallowEqual(inputs, this._formInputs)) {
|
|
159
180
|
return;
|
|
160
181
|
}
|
|
161
|
-
await this._secretsManager.detachAll(SECRETS_NAMESPACE);
|
|
182
|
+
await this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
162
183
|
this._formInputs = [...inputs];
|
|
163
184
|
this._unsavedFields = [];
|
|
164
185
|
for (let i = 0; i < inputs.length; i++) {
|
|
165
186
|
if (inputs[i].type.toLowerCase() === 'password') {
|
|
166
187
|
const label = inputs[i].getAttribute('label');
|
|
167
188
|
if (label) {
|
|
168
|
-
const id =
|
|
169
|
-
this._secretsManager.attach(SECRETS_NAMESPACE, id, inputs[i], (value) => this._onPasswordUpdated(label, value));
|
|
189
|
+
const id = getSecretId(this._provider, label);
|
|
190
|
+
this._secretsManager.attach(Private.getToken(), SECRETS_NAMESPACE, id, inputs[i], (value) => this._onPasswordUpdated(label, value));
|
|
170
191
|
this._unsavedFields.push(label);
|
|
171
192
|
}
|
|
172
193
|
}
|
|
173
194
|
}
|
|
195
|
+
if (this._settingConnector instanceof SettingConnector) {
|
|
196
|
+
this._settingConnector.doNotSave = this._unsavedFields;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
componentWillUnmount() {
|
|
200
|
+
if (!this._secretsManager || !this._useSecretsManager) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
this._secretsManager.detachAll(Private.getToken(), SECRETS_NAMESPACE);
|
|
174
204
|
}
|
|
175
205
|
/**
|
|
176
206
|
* Get the current provider from the local storage.
|
|
@@ -207,15 +237,6 @@ export class AiSettings extends React.Component {
|
|
|
207
237
|
settings[this._provider] = currentSettings;
|
|
208
238
|
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
209
239
|
}
|
|
210
|
-
/**
|
|
211
|
-
* Update the UI schema of the form.
|
|
212
|
-
* Currently use to hide API keys.
|
|
213
|
-
*/
|
|
214
|
-
_updateUiSchema(key) {
|
|
215
|
-
if (key.toLowerCase().includes('key')) {
|
|
216
|
-
this._uiSchema[key] = { 'ui:widget': 'password' };
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
240
|
/**
|
|
220
241
|
* Build the schema for a given provider.
|
|
221
242
|
*/
|
|
@@ -225,8 +246,13 @@ export class AiSettings extends React.Component {
|
|
|
225
246
|
const settingsSchema = this._providerRegistry.getSettingsSchema(this._provider);
|
|
226
247
|
if (settingsSchema) {
|
|
227
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
|
+
}
|
|
228
255
|
schema.properties[key] = value;
|
|
229
|
-
this._updateUiSchema(key);
|
|
230
256
|
});
|
|
231
257
|
}
|
|
232
258
|
return schema;
|
|
@@ -265,3 +291,24 @@ export class AiSettings extends React.Component {
|
|
|
265
291
|
React.createElement(WrappedFormComponent, { formData: this._currentSettings, schema: this.state.schema, onChange: this._onFormChange, uiSchema: this._uiSchema })));
|
|
266
292
|
}
|
|
267
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 = {}));
|
|
@@ -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
|
@@ -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
|
}
|
|
@@ -80,10 +87,9 @@ export interface IAIProviderRegistry {
|
|
|
80
87
|
* Set the providers (chat model and completer).
|
|
81
88
|
* Creates the providers if the name has changed, otherwise only updates their config.
|
|
82
89
|
*
|
|
83
|
-
* @param
|
|
84
|
-
* @param settings - the settings for the models.
|
|
90
|
+
* @param options - an object with the name and the settings of the provider to use.
|
|
85
91
|
*/
|
|
86
|
-
setProvider(
|
|
92
|
+
setProvider(options: ISetProviderOptions): void;
|
|
87
93
|
/**
|
|
88
94
|
* A signal emitting when the provider or its settings has changed.
|
|
89
95
|
*/
|
|
@@ -97,6 +103,19 @@ export interface IAIProviderRegistry {
|
|
|
97
103
|
*/
|
|
98
104
|
readonly completerError: string;
|
|
99
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* The set provider options.
|
|
108
|
+
*/
|
|
109
|
+
export interface ISetProviderOptions {
|
|
110
|
+
/**
|
|
111
|
+
* The name of the provider.
|
|
112
|
+
*/
|
|
113
|
+
name: string;
|
|
114
|
+
/**
|
|
115
|
+
* The settings of the provider.
|
|
116
|
+
*/
|
|
117
|
+
settings: ReadonlyPartialJSONObject;
|
|
118
|
+
}
|
|
100
119
|
/**
|
|
101
120
|
* The provider registry token.
|
|
102
121
|
*/
|
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.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "AI code completions and chat for JupyterLite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -56,14 +56,15 @@
|
|
|
56
56
|
"watch:labextension": "jupyter labextension watch ."
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@jupyter/chat": "^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/
|
|
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/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",
|
|
@@ -77,12 +78,13 @@
|
|
|
77
78
|
"@rjsf/core": "^4.2.0",
|
|
78
79
|
"@rjsf/utils": "^5.18.4",
|
|
79
80
|
"@rjsf/validator-ajv8": "^5.18.4",
|
|
80
|
-
"
|
|
81
|
+
"json5": "^2.2.3",
|
|
82
|
+
"jupyter-secrets-manager": "^0.3.0",
|
|
81
83
|
"react": "^18.2.0",
|
|
82
84
|
"react-dom": "^18.2.0"
|
|
83
85
|
},
|
|
84
86
|
"devDependencies": {
|
|
85
|
-
"@jupyterlab/builder": "^4.
|
|
87
|
+
"@jupyterlab/builder": "^4.4.0",
|
|
86
88
|
"@stylistic/eslint-plugin": "^3.0.1",
|
|
87
89
|
"@types/json-schema": "^7.0.11",
|
|
88
90
|
"@types/react": "^18.0.26",
|
|
@@ -118,6 +120,9 @@
|
|
|
118
120
|
"jupyterlab": {
|
|
119
121
|
"extension": true,
|
|
120
122
|
"outputDir": "jupyterlite_ai/labextension",
|
|
121
|
-
"schemaDir": "schema"
|
|
123
|
+
"schemaDir": "schema",
|
|
124
|
+
"disabledExtensions": [
|
|
125
|
+
"@jupyterlab/apputils-extension:settings-connector"
|
|
126
|
+
]
|
|
122
127
|
}
|
|
123
128
|
}
|
|
@@ -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/chat-handler.ts
CHANGED
|
@@ -139,9 +139,11 @@ export class ChatHandler extends ChatModel {
|
|
|
139
139
|
|
|
140
140
|
let content = '';
|
|
141
141
|
|
|
142
|
+
this._controller = new AbortController();
|
|
142
143
|
try {
|
|
143
144
|
for await (const chunk of await this._providerRegistry.currentChatModel.stream(
|
|
144
|
-
messages
|
|
145
|
+
messages,
|
|
146
|
+
{ signal: this._controller.signal }
|
|
145
147
|
)) {
|
|
146
148
|
content += chunk.content ?? chunk;
|
|
147
149
|
botMsg.body = content;
|
|
@@ -162,6 +164,7 @@ export class ChatHandler extends ChatModel {
|
|
|
162
164
|
return false;
|
|
163
165
|
} finally {
|
|
164
166
|
this.updateWriters([]);
|
|
167
|
+
this._controller = null;
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
170
|
|
|
@@ -177,12 +180,17 @@ export class ChatHandler extends ChatModel {
|
|
|
177
180
|
super.messageAdded(message);
|
|
178
181
|
}
|
|
179
182
|
|
|
183
|
+
stopStreaming(): void {
|
|
184
|
+
this._controller?.abort();
|
|
185
|
+
}
|
|
186
|
+
|
|
180
187
|
private _providerRegistry: IAIProviderRegistry;
|
|
181
188
|
private _personaName = 'AI';
|
|
182
189
|
private _prompt: string;
|
|
183
190
|
private _errorMessage: string = '';
|
|
184
191
|
private _history: IChatHistory = { messages: [] };
|
|
185
192
|
private _defaultErrorMessage = 'AI provider not configured';
|
|
193
|
+
private _controller: AbortController | null = null;
|
|
186
194
|
}
|
|
187
195
|
|
|
188
196
|
export namespace ChatHandler {
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import StopIcon from '@mui/icons-material/Stop';
|
|
7
|
+
import React from 'react';
|
|
8
|
+
|
|
9
|
+
import { InputToolbarRegistry, TooltippedButton } from '@jupyter/chat';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Properties of the stop button.
|
|
13
|
+
*/
|
|
14
|
+
export interface IStopButtonProps
|
|
15
|
+
extends InputToolbarRegistry.IToolbarItemProps {
|
|
16
|
+
/**
|
|
17
|
+
* The function to stop streaming.
|
|
18
|
+
*/
|
|
19
|
+
stopStreaming: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The stop button.
|
|
24
|
+
*/
|
|
25
|
+
export function StopButton(props: IStopButtonProps): JSX.Element {
|
|
26
|
+
const tooltip = 'Stop streaming';
|
|
27
|
+
return (
|
|
28
|
+
<TooltippedButton
|
|
29
|
+
onClick={props.stopStreaming}
|
|
30
|
+
tooltip={tooltip}
|
|
31
|
+
buttonProps={{
|
|
32
|
+
size: 'small',
|
|
33
|
+
variant: 'contained',
|
|
34
|
+
title: tooltip
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
<StopIcon />
|
|
38
|
+
</TooltippedButton>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* factory returning the toolbar item.
|
|
44
|
+
*/
|
|
45
|
+
export function stopItem(
|
|
46
|
+
stopStreaming: () => void
|
|
47
|
+
): InputToolbarRegistry.IToolbarItem {
|
|
48
|
+
return {
|
|
49
|
+
element: (props: InputToolbarRegistry.IToolbarItemProps) => {
|
|
50
|
+
const stopProps: IStopButtonProps = { ...props, stopStreaming };
|
|
51
|
+
return StopButton(stopProps);
|
|
52
|
+
},
|
|
53
|
+
position: 50,
|
|
54
|
+
hidden: true /* hidden by default */
|
|
55
|
+
};
|
|
56
|
+
}
|