@jupyterlite/ai 0.5.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 +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 +45 -9
- 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 +3 -0
- package/lib/settings/panel.js +15 -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 +15 -3
- package/package.json +9 -5
- 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 +57 -12
- package/src/provider.ts +57 -14
- package/src/settings/index.ts +3 -0
- package/src/settings/panel.tsx +18 -3
- package/src/settings/settings-connector.ts +89 -0
- package/src/settings/utils.ts +6 -0
- package/src/tokens.ts +16 -3
package/lib/chat-handler.d.ts
CHANGED
|
@@ -22,12 +22,14 @@ export declare class ChatHandler extends ChatModel {
|
|
|
22
22
|
getHistory(): Promise<IChatHistory>;
|
|
23
23
|
dispose(): void;
|
|
24
24
|
messageAdded(message: IChatMessage): void;
|
|
25
|
+
stopStreaming(): void;
|
|
25
26
|
private _providerRegistry;
|
|
26
27
|
private _personaName;
|
|
27
28
|
private _prompt;
|
|
28
29
|
private _errorMessage;
|
|
29
30
|
private _history;
|
|
30
31
|
private _defaultErrorMessage;
|
|
32
|
+
private _controller;
|
|
31
33
|
}
|
|
32
34
|
export declare namespace ChatHandler {
|
|
33
35
|
interface IOptions extends ChatModel.IOptions {
|
package/lib/chat-handler.js
CHANGED
|
@@ -20,6 +20,7 @@ export class ChatHandler extends ChatModel {
|
|
|
20
20
|
this._errorMessage = '';
|
|
21
21
|
this._history = { messages: [] };
|
|
22
22
|
this._defaultErrorMessage = 'AI provider not configured';
|
|
23
|
+
this._controller = null;
|
|
23
24
|
this._providerRegistry = options.providerRegistry;
|
|
24
25
|
this._prompt = chatSystemPrompt({
|
|
25
26
|
provider_name: this._providerRegistry.currentName
|
|
@@ -107,8 +108,9 @@ export class ChatHandler extends ChatModel {
|
|
|
107
108
|
type: 'msg'
|
|
108
109
|
};
|
|
109
110
|
let content = '';
|
|
111
|
+
this._controller = new AbortController();
|
|
110
112
|
try {
|
|
111
|
-
for await (const chunk of await this._providerRegistry.currentChatModel.stream(messages)) {
|
|
113
|
+
for await (const chunk of await this._providerRegistry.currentChatModel.stream(messages, { signal: this._controller.signal })) {
|
|
112
114
|
content += (_a = chunk.content) !== null && _a !== void 0 ? _a : chunk;
|
|
113
115
|
botMsg.body = content;
|
|
114
116
|
this.messageAdded(botMsg);
|
|
@@ -130,6 +132,7 @@ export class ChatHandler extends ChatModel {
|
|
|
130
132
|
}
|
|
131
133
|
finally {
|
|
132
134
|
this.updateWriters([]);
|
|
135
|
+
this._controller = null;
|
|
133
136
|
}
|
|
134
137
|
}
|
|
135
138
|
async getHistory() {
|
|
@@ -141,6 +144,10 @@ export class ChatHandler extends ChatModel {
|
|
|
141
144
|
messageAdded(message) {
|
|
142
145
|
super.messageAdded(message);
|
|
143
146
|
}
|
|
147
|
+
stopStreaming() {
|
|
148
|
+
var _a;
|
|
149
|
+
(_a = this._controller) === null || _a === void 0 ? void 0 : _a.abort();
|
|
150
|
+
}
|
|
144
151
|
}
|
|
145
152
|
(function (ChatHandler) {
|
|
146
153
|
class ClearCommandProvider {
|
|
@@ -21,7 +21,14 @@ export declare class CompletionProvider implements IInlineCompletionProvider {
|
|
|
21
21
|
}
|
|
22
22
|
export declare namespace CompletionProvider {
|
|
23
23
|
interface IOptions {
|
|
24
|
+
/**
|
|
25
|
+
* The registry where the completion provider belongs.
|
|
26
|
+
*/
|
|
24
27
|
providerRegistry: IAIProviderRegistry;
|
|
28
|
+
/**
|
|
29
|
+
* The request completion commands, can be useful if a provider needs to request
|
|
30
|
+
* the completion by itself.
|
|
31
|
+
*/
|
|
25
32
|
requestCompletion: () => void;
|
|
26
33
|
}
|
|
27
34
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { InputToolbarRegistry } from '@jupyter/chat';
|
|
3
|
+
/**
|
|
4
|
+
* Properties of the stop button.
|
|
5
|
+
*/
|
|
6
|
+
export interface IStopButtonProps extends InputToolbarRegistry.IToolbarItemProps {
|
|
7
|
+
/**
|
|
8
|
+
* The function to stop streaming.
|
|
9
|
+
*/
|
|
10
|
+
stopStreaming: () => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The stop button.
|
|
14
|
+
*/
|
|
15
|
+
export declare function StopButton(props: IStopButtonProps): JSX.Element;
|
|
16
|
+
/**
|
|
17
|
+
* factory returning the toolbar item.
|
|
18
|
+
*/
|
|
19
|
+
export declare function stopItem(stopStreaming: () => void): InputToolbarRegistry.IToolbarItem;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import StopIcon from '@mui/icons-material/Stop';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { TooltippedButton } from '@jupyter/chat';
|
|
8
|
+
/**
|
|
9
|
+
* The stop button.
|
|
10
|
+
*/
|
|
11
|
+
export function StopButton(props) {
|
|
12
|
+
const tooltip = 'Stop streaming';
|
|
13
|
+
return (React.createElement(TooltippedButton, { onClick: props.stopStreaming, tooltip: tooltip, buttonProps: {
|
|
14
|
+
size: 'small',
|
|
15
|
+
variant: 'contained',
|
|
16
|
+
title: tooltip
|
|
17
|
+
} },
|
|
18
|
+
React.createElement(StopIcon, null)));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* factory returning the toolbar item.
|
|
22
|
+
*/
|
|
23
|
+
export function stopItem(stopStreaming) {
|
|
24
|
+
return {
|
|
25
|
+
element: (props) => {
|
|
26
|
+
const stopProps = { ...props, stopStreaming };
|
|
27
|
+
return StopButton(stopProps);
|
|
28
|
+
},
|
|
29
|
+
position: 50,
|
|
30
|
+
hidden: true /* hidden by default */
|
|
31
|
+
};
|
|
32
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IChatCommandRegistry } from '@jupyter/chat';
|
|
2
2
|
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
3
|
+
import { ISettingConnector } from '@jupyterlab/settingregistry';
|
|
3
4
|
import { IAIProviderRegistry } from './tokens';
|
|
4
|
-
declare const _default: (JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<IChatCommandRegistry> | JupyterFrontEndPlugin<IAIProviderRegistry>)[];
|
|
5
|
+
declare const _default: (JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<IChatCommandRegistry> | JupyterFrontEndPlugin<IAIProviderRegistry> | JupyterFrontEndPlugin<ISettingConnector>)[];
|
|
5
6
|
export default _default;
|
package/lib/index.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { ActiveCellManager, buildChatSidebar, buildErrorWidget, ChatCommandRegistry, IChatCommandRegistry } from '@jupyter/chat';
|
|
1
|
+
import { ActiveCellManager, buildChatSidebar, buildErrorWidget, ChatCommandRegistry, IChatCommandRegistry, InputToolbarRegistry } from '@jupyter/chat';
|
|
2
2
|
import { IThemeManager } from '@jupyterlab/apputils';
|
|
3
3
|
import { ICompletionProviderManager } from '@jupyterlab/completer';
|
|
4
4
|
import { INotebookTracker } from '@jupyterlab/notebook';
|
|
5
5
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
6
|
-
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
6
|
+
import { ISettingConnector, ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
7
7
|
import { IFormRendererRegistry } from '@jupyterlab/ui-components';
|
|
8
8
|
import { ISecretsManager } 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
|
-
import { aiSettingsRenderer } from './settings
|
|
13
|
+
import { aiSettingsRenderer, SettingConnector } from './settings';
|
|
14
14
|
import { IAIProviderRegistry } from './tokens';
|
|
15
|
+
import { stopItem } from './components/stop-button';
|
|
15
16
|
const chatCommandRegistryPlugin = {
|
|
16
17
|
id: '@jupyterlite/ai:autocompletion-registry',
|
|
17
18
|
description: 'Autocompletion registry',
|
|
@@ -66,12 +67,26 @@ const chatPlugin = {
|
|
|
66
67
|
console.error(`Something went wrong when reading the settings.\n${reason}`);
|
|
67
68
|
});
|
|
68
69
|
let chatWidget = null;
|
|
70
|
+
const inputToolbarRegistry = InputToolbarRegistry.defaultToolbarRegistry();
|
|
71
|
+
const stopButton = stopItem(() => chatHandler.stopStreaming());
|
|
72
|
+
inputToolbarRegistry.addItem('stop', stopButton);
|
|
73
|
+
chatHandler.writersChanged.connect((_, users) => {
|
|
74
|
+
if (users.filter(user => user.username === chatHandler.personaName).length) {
|
|
75
|
+
inputToolbarRegistry.hide('send');
|
|
76
|
+
inputToolbarRegistry.show('stop');
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
inputToolbarRegistry.hide('stop');
|
|
80
|
+
inputToolbarRegistry.show('send');
|
|
81
|
+
}
|
|
82
|
+
});
|
|
69
83
|
try {
|
|
70
84
|
chatWidget = buildChatSidebar({
|
|
71
85
|
model: chatHandler,
|
|
72
86
|
themeManager,
|
|
73
87
|
rmRegistry,
|
|
74
|
-
chatCommandRegistry
|
|
88
|
+
chatCommandRegistry,
|
|
89
|
+
inputToolbarRegistry
|
|
75
90
|
});
|
|
76
91
|
chatWidget.title.caption = 'Jupyterlite AI Chat';
|
|
77
92
|
}
|
|
@@ -98,11 +113,16 @@ const providerRegistryPlugin = {
|
|
|
98
113
|
id: '@jupyterlite/ai:provider-registry',
|
|
99
114
|
autoStart: true,
|
|
100
115
|
requires: [IFormRendererRegistry, ISettingRegistry],
|
|
101
|
-
optional: [IRenderMimeRegistry, ISecretsManager],
|
|
116
|
+
optional: [IRenderMimeRegistry, ISecretsManager, ISettingConnector],
|
|
102
117
|
provides: IAIProviderRegistry,
|
|
103
|
-
activate: (app, editorRegistry, settingRegistry, rmRegistry, secretsManager) => {
|
|
104
|
-
const providerRegistry = new AIProviderRegistry();
|
|
105
|
-
editorRegistry.addRenderer('@jupyterlite/ai:provider-registry.AIprovider', aiSettingsRenderer({
|
|
118
|
+
activate: (app, editorRegistry, settingRegistry, rmRegistry, secretsManager, settingConnector) => {
|
|
119
|
+
const providerRegistry = new AIProviderRegistry({ secretsManager });
|
|
120
|
+
editorRegistry.addRenderer('@jupyterlite/ai:provider-registry.AIprovider', aiSettingsRenderer({
|
|
121
|
+
providerRegistry,
|
|
122
|
+
rmRegistry,
|
|
123
|
+
secretsManager,
|
|
124
|
+
settingConnector
|
|
125
|
+
}));
|
|
106
126
|
settingRegistry
|
|
107
127
|
.load(providerRegistryPlugin.id)
|
|
108
128
|
.then(settings => {
|
|
@@ -112,7 +132,10 @@ const providerRegistryPlugin = {
|
|
|
112
132
|
const providerSettings = ((_a = settings.get('AIprovider').composite) !== null && _a !== void 0 ? _a : {
|
|
113
133
|
provider: 'None'
|
|
114
134
|
});
|
|
115
|
-
providerRegistry.setProvider(
|
|
135
|
+
providerRegistry.setProvider({
|
|
136
|
+
name: providerSettings.provider,
|
|
137
|
+
settings: providerSettings
|
|
138
|
+
});
|
|
116
139
|
};
|
|
117
140
|
settings.changed.connect(() => updateProvider());
|
|
118
141
|
updateProvider();
|
|
@@ -123,10 +146,23 @@ const providerRegistryPlugin = {
|
|
|
123
146
|
return providerRegistry;
|
|
124
147
|
}
|
|
125
148
|
};
|
|
149
|
+
/**
|
|
150
|
+
* Provides the settings connector as a separate plugin to allow for alternative
|
|
151
|
+
* implementations that may want to fetch settings from a different source or
|
|
152
|
+
* endpoint.
|
|
153
|
+
*/
|
|
154
|
+
const settingsConnector = {
|
|
155
|
+
id: '@jupyterlite/ai:settings-connector',
|
|
156
|
+
description: 'Provides a settings connector which does not save passwords.',
|
|
157
|
+
autoStart: true,
|
|
158
|
+
provides: ISettingConnector,
|
|
159
|
+
activate: (app) => new SettingConnector(app.serviceManager.settings)
|
|
160
|
+
};
|
|
126
161
|
export default [
|
|
127
162
|
providerRegistryPlugin,
|
|
128
163
|
chatCommandRegistryPlugin,
|
|
129
164
|
chatPlugin,
|
|
130
165
|
completerPlugin,
|
|
166
|
+
settingsConnector,
|
|
131
167
|
...defaultProviderPlugins
|
|
132
168
|
];
|
package/lib/provider.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { ICompletionProviderManager } from '@jupyterlab/completer';
|
|
2
1
|
import { BaseLanguageModel } from '@langchain/core/language_models/base';
|
|
3
2
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
4
3
|
import { ISignal } from '@lumino/signaling';
|
|
5
4
|
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
6
|
-
import { IBaseCompleter } from './base-completer';
|
|
7
|
-
import { IAIProvider, IAIProviderRegistry } from './tokens';
|
|
8
5
|
import { JSONSchema7 } from 'json-schema';
|
|
6
|
+
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
7
|
+
import { IBaseCompleter } from './base-completer';
|
|
8
|
+
import { IAIProvider, IAIProviderRegistry, ISetProviderOptions } from './tokens';
|
|
9
9
|
export declare const chatSystemPrompt: (options: AIProviderRegistry.IPromptOptions) => string;
|
|
10
10
|
export declare const COMPLETION_SYSTEM_PROMPT = "\nYou are an application built to provide helpful code completion suggestions.\nYou should only produce code. Keep comments to minimum, use the\nprogramming language comment syntax. Produce clean code.\nThe code is written in JupyterLab, a data analysis and code development\nenvironment which can execute code extended with additional syntax for\ninteractive features, such as magics.\nOnly give raw strings back, do not format the response using backticks.\nThe output should be a single string, and should correspond to what a human users\nwould write.\nDo not include the prompt in the output, only the string that should be appended to the current input.\n";
|
|
11
11
|
export declare class AIProviderRegistry implements IAIProviderRegistry {
|
|
12
|
+
/**
|
|
13
|
+
* The constructor of the provider registry.
|
|
14
|
+
*/
|
|
15
|
+
constructor(options: AIProviderRegistry.IOptions);
|
|
12
16
|
/**
|
|
13
17
|
* Get the list of provider names.
|
|
14
18
|
*/
|
|
@@ -53,14 +57,14 @@ export declare class AIProviderRegistry implements IAIProviderRegistry {
|
|
|
53
57
|
* Set the providers (chat model and completer).
|
|
54
58
|
* Creates the providers if the name has changed, otherwise only updates their config.
|
|
55
59
|
*
|
|
56
|
-
* @param
|
|
57
|
-
* @param settings - the settings for the models.
|
|
60
|
+
* @param options - An object with the name and the settings of the provider to use.
|
|
58
61
|
*/
|
|
59
|
-
setProvider(
|
|
62
|
+
setProvider(options: ISetProviderOptions): Promise<void>;
|
|
60
63
|
/**
|
|
61
64
|
* A signal emitting when the provider or its settings has changed.
|
|
62
65
|
*/
|
|
63
66
|
get providerChanged(): ISignal<IAIProviderRegistry, void>;
|
|
67
|
+
private _secretsManager;
|
|
64
68
|
private _currentProvider;
|
|
65
69
|
private _completer;
|
|
66
70
|
private _chatModel;
|
|
@@ -69,6 +73,7 @@ export declare class AIProviderRegistry implements IAIProviderRegistry {
|
|
|
69
73
|
private _chatError;
|
|
70
74
|
private _completerError;
|
|
71
75
|
private _providers;
|
|
76
|
+
private _deferredProvider;
|
|
72
77
|
}
|
|
73
78
|
export declare namespace AIProviderRegistry {
|
|
74
79
|
/**
|
|
@@ -76,13 +81,9 @@ export declare namespace AIProviderRegistry {
|
|
|
76
81
|
*/
|
|
77
82
|
interface IOptions {
|
|
78
83
|
/**
|
|
79
|
-
* The
|
|
80
|
-
*/
|
|
81
|
-
completionProviderManager: ICompletionProviderManager;
|
|
82
|
-
/**
|
|
83
|
-
* The application commands registry.
|
|
84
|
+
* The secrets manager used in the application.
|
|
84
85
|
*/
|
|
85
|
-
|
|
86
|
+
secretsManager?: ISecretsManager;
|
|
86
87
|
}
|
|
87
88
|
/**
|
|
88
89
|
* The options for the Chat system prompt.
|
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,4 +1,5 @@
|
|
|
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';
|
|
@@ -9,6 +10,7 @@ export declare const aiSettingsRenderer: (options: {
|
|
|
9
10
|
providerRegistry: IAIProviderRegistry;
|
|
10
11
|
rmRegistry?: IRenderMimeRegistry;
|
|
11
12
|
secretsManager?: ISecretsManager;
|
|
13
|
+
settingConnector?: ISettingConnector;
|
|
12
14
|
}) => IFormRenderer;
|
|
13
15
|
export interface ISettingsFormStates {
|
|
14
16
|
schema: JSONSchema7;
|
|
@@ -75,6 +77,7 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
|
|
|
75
77
|
private _useSecretsManager;
|
|
76
78
|
private _rmRegistry;
|
|
77
79
|
private _secretsManager;
|
|
80
|
+
private _settingConnector;
|
|
78
81
|
private _currentSettings;
|
|
79
82
|
private _uiSchema;
|
|
80
83
|
private _settings;
|
package/lib/settings/panel.js
CHANGED
|
@@ -3,8 +3,8 @@ 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, SECRETS_NAMESPACE, SettingConnector } from '.';
|
|
6
7
|
import baseSettings from './base.json';
|
|
7
|
-
const SECRETS_NAMESPACE = '@jupyterlite/ai';
|
|
8
8
|
const MD_MIME_TYPE = 'text/markdown';
|
|
9
9
|
const STORAGE_NAME = '@jupyterlite/ai:settings';
|
|
10
10
|
const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
|
|
@@ -21,7 +21,7 @@ const WrappedFormComponent = (props) => {
|
|
|
21
21
|
};
|
|
22
22
|
export class AiSettings extends React.Component {
|
|
23
23
|
constructor(props) {
|
|
24
|
-
var _a, _b, _c, _d;
|
|
24
|
+
var _a, _b, _c, _d, _e;
|
|
25
25
|
super(props);
|
|
26
26
|
this.updateUseSecretsManager = (value) => {
|
|
27
27
|
var _a;
|
|
@@ -32,6 +32,9 @@ export class AiSettings extends React.Component {
|
|
|
32
32
|
(_a = this._secretsManager) === null || _a === void 0 ? void 0 : _a.detachAll(SECRETS_NAMESPACE);
|
|
33
33
|
this._formInputs = [];
|
|
34
34
|
this._unsavedFields = [];
|
|
35
|
+
if (this._settingConnector instanceof SettingConnector) {
|
|
36
|
+
this._settingConnector.doNotSave = [];
|
|
37
|
+
}
|
|
35
38
|
this.saveSettings(this._currentSettings);
|
|
36
39
|
}
|
|
37
40
|
else {
|
|
@@ -48,6 +51,9 @@ export class AiSettings extends React.Component {
|
|
|
48
51
|
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
49
52
|
this.componentDidUpdate();
|
|
50
53
|
}
|
|
54
|
+
this._settings
|
|
55
|
+
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
56
|
+
.catch(console.error);
|
|
51
57
|
};
|
|
52
58
|
/**
|
|
53
59
|
* Triggered when the provider hes changed, to update the schema and values.
|
|
@@ -100,9 +106,10 @@ export class AiSettings extends React.Component {
|
|
|
100
106
|
this._providerRegistry = props.formContext.providerRegistry;
|
|
101
107
|
this._rmRegistry = (_a = props.formContext.rmRegistry) !== null && _a !== void 0 ? _a : null;
|
|
102
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;
|
|
103
110
|
this._settings = props.formContext.settings;
|
|
104
111
|
this._useSecretsManager =
|
|
105
|
-
(
|
|
112
|
+
(_d = this._settings.get('UseSecretsManager').composite) !== null && _d !== void 0 ? _d : true;
|
|
106
113
|
// Initialize the providers schema.
|
|
107
114
|
const providerSchema = JSONExt.deepCopy(baseSettings);
|
|
108
115
|
providerSchema.properties.provider = {
|
|
@@ -120,7 +127,7 @@ export class AiSettings extends React.Component {
|
|
|
120
127
|
const labSettings = this._settings.get('AIprovider').composite;
|
|
121
128
|
if (labSettings && Object.keys(labSettings).includes('provider')) {
|
|
122
129
|
// Get the provider name.
|
|
123
|
-
const provider = (
|
|
130
|
+
const provider = (_e = Object.entries(labSettings).find(v => v[0] === 'provider')) === null || _e === void 0 ? void 0 : _e[1];
|
|
124
131
|
// Save the settings.
|
|
125
132
|
const settings = {
|
|
126
133
|
_current: provider
|
|
@@ -165,12 +172,15 @@ export class AiSettings extends React.Component {
|
|
|
165
172
|
if (inputs[i].type.toLowerCase() === 'password') {
|
|
166
173
|
const label = inputs[i].getAttribute('label');
|
|
167
174
|
if (label) {
|
|
168
|
-
const id =
|
|
175
|
+
const id = getSecretId(this._provider, label);
|
|
169
176
|
this._secretsManager.attach(SECRETS_NAMESPACE, id, inputs[i], (value) => this._onPasswordUpdated(label, value));
|
|
170
177
|
this._unsavedFields.push(label);
|
|
171
178
|
}
|
|
172
179
|
}
|
|
173
180
|
}
|
|
181
|
+
if (this._settingConnector instanceof SettingConnector) {
|
|
182
|
+
this._settingConnector.doNotSave = this._unsavedFields;
|
|
183
|
+
}
|
|
174
184
|
}
|
|
175
185
|
/**
|
|
176
186
|
* Get the current provider from the local storage.
|
|
@@ -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
|
@@ -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",
|
|
@@ -56,13 +56,13 @@
|
|
|
56
56
|
"watch:labextension": "jupyter labextension watch ."
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@jupyter/chat": "^0.
|
|
59
|
+
"@jupyter/chat": "^0.9.0",
|
|
60
60
|
"@jupyterlab/application": "^4.4.0-alpha.0",
|
|
61
61
|
"@jupyterlab/apputils": "^4.5.0-alpha.0",
|
|
62
62
|
"@jupyterlab/completer": "^4.4.0-alpha.0",
|
|
63
63
|
"@jupyterlab/notebook": "^4.4.0-alpha.0",
|
|
64
64
|
"@jupyterlab/rendermime": "^4.4.0-alpha.0",
|
|
65
|
-
"@jupyterlab/settingregistry": "^4.4.0-
|
|
65
|
+
"@jupyterlab/settingregistry": "^4.4.0-beta.1",
|
|
66
66
|
"@jupyterlab/ui-components": "^4.4.0-alpha.0",
|
|
67
67
|
"@langchain/anthropic": "^0.3.9",
|
|
68
68
|
"@langchain/community": "^0.3.31",
|
|
@@ -77,7 +77,8 @@
|
|
|
77
77
|
"@rjsf/core": "^4.2.0",
|
|
78
78
|
"@rjsf/utils": "^5.18.4",
|
|
79
79
|
"@rjsf/validator-ajv8": "^5.18.4",
|
|
80
|
-
"
|
|
80
|
+
"json5": "^2.2.3",
|
|
81
|
+
"jupyter-secrets-manager": "^0.2.0",
|
|
81
82
|
"react": "^18.2.0",
|
|
82
83
|
"react-dom": "^18.2.0"
|
|
83
84
|
},
|
|
@@ -118,6 +119,9 @@
|
|
|
118
119
|
"jupyterlab": {
|
|
119
120
|
"extension": true,
|
|
120
121
|
"outputDir": "jupyterlite_ai/labextension",
|
|
121
|
-
"schemaDir": "schema"
|
|
122
|
+
"schemaDir": "schema",
|
|
123
|
+
"disabledExtensions": [
|
|
124
|
+
"@jupyterlab/apputils-extension:settings-connector"
|
|
125
|
+
]
|
|
122
126
|
}
|
|
123
127
|
}
|
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
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
buildErrorWidget,
|
|
5
5
|
ChatCommandRegistry,
|
|
6
6
|
IActiveCellManager,
|
|
7
|
-
IChatCommandRegistry
|
|
7
|
+
IChatCommandRegistry,
|
|
8
|
+
InputToolbarRegistry
|
|
8
9
|
} from '@jupyter/chat';
|
|
9
10
|
import {
|
|
10
11
|
JupyterFrontEnd,
|
|
@@ -14,7 +15,10 @@ import { ReactWidget, IThemeManager } from '@jupyterlab/apputils';
|
|
|
14
15
|
import { ICompletionProviderManager } from '@jupyterlab/completer';
|
|
15
16
|
import { INotebookTracker } from '@jupyterlab/notebook';
|
|
16
17
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
ISettingConnector,
|
|
20
|
+
ISettingRegistry
|
|
21
|
+
} from '@jupyterlab/settingregistry';
|
|
18
22
|
import { IFormRendererRegistry } from '@jupyterlab/ui-components';
|
|
19
23
|
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
20
24
|
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
@@ -23,8 +27,9 @@ import { ChatHandler } from './chat-handler';
|
|
|
23
27
|
import { CompletionProvider } from './completion-provider';
|
|
24
28
|
import { defaultProviderPlugins } from './default-providers';
|
|
25
29
|
import { AIProviderRegistry } from './provider';
|
|
26
|
-
import { aiSettingsRenderer } from './settings
|
|
30
|
+
import { aiSettingsRenderer, SettingConnector } from './settings';
|
|
27
31
|
import { IAIProviderRegistry } from './tokens';
|
|
32
|
+
import { stopItem } from './components/stop-button';
|
|
28
33
|
|
|
29
34
|
const chatCommandRegistryPlugin: JupyterFrontEndPlugin<IChatCommandRegistry> = {
|
|
30
35
|
id: '@jupyterlite/ai:autocompletion-registry',
|
|
@@ -99,12 +104,30 @@ const chatPlugin: JupyterFrontEndPlugin<void> = {
|
|
|
99
104
|
});
|
|
100
105
|
|
|
101
106
|
let chatWidget: ReactWidget | null = null;
|
|
107
|
+
|
|
108
|
+
const inputToolbarRegistry = InputToolbarRegistry.defaultToolbarRegistry();
|
|
109
|
+
const stopButton = stopItem(() => chatHandler.stopStreaming());
|
|
110
|
+
inputToolbarRegistry.addItem('stop', stopButton);
|
|
111
|
+
|
|
112
|
+
chatHandler.writersChanged.connect((_, users) => {
|
|
113
|
+
if (
|
|
114
|
+
users.filter(user => user.username === chatHandler.personaName).length
|
|
115
|
+
) {
|
|
116
|
+
inputToolbarRegistry.hide('send');
|
|
117
|
+
inputToolbarRegistry.show('stop');
|
|
118
|
+
} else {
|
|
119
|
+
inputToolbarRegistry.hide('stop');
|
|
120
|
+
inputToolbarRegistry.show('send');
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
102
124
|
try {
|
|
103
125
|
chatWidget = buildChatSidebar({
|
|
104
126
|
model: chatHandler,
|
|
105
127
|
themeManager,
|
|
106
128
|
rmRegistry,
|
|
107
|
-
chatCommandRegistry
|
|
129
|
+
chatCommandRegistry,
|
|
130
|
+
inputToolbarRegistry
|
|
108
131
|
});
|
|
109
132
|
chatWidget.title.caption = 'Jupyterlite AI Chat';
|
|
110
133
|
} catch (e) {
|
|
@@ -138,21 +161,28 @@ const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
|
|
|
138
161
|
id: '@jupyterlite/ai:provider-registry',
|
|
139
162
|
autoStart: true,
|
|
140
163
|
requires: [IFormRendererRegistry, ISettingRegistry],
|
|
141
|
-
optional: [IRenderMimeRegistry, ISecretsManager],
|
|
164
|
+
optional: [IRenderMimeRegistry, ISecretsManager, ISettingConnector],
|
|
142
165
|
provides: IAIProviderRegistry,
|
|
143
166
|
activate: (
|
|
144
167
|
app: JupyterFrontEnd,
|
|
145
168
|
editorRegistry: IFormRendererRegistry,
|
|
146
169
|
settingRegistry: ISettingRegistry,
|
|
147
170
|
rmRegistry?: IRenderMimeRegistry,
|
|
148
|
-
secretsManager?: ISecretsManager
|
|
171
|
+
secretsManager?: ISecretsManager,
|
|
172
|
+
settingConnector?: ISettingConnector
|
|
149
173
|
): IAIProviderRegistry => {
|
|
150
|
-
const providerRegistry = new AIProviderRegistry();
|
|
174
|
+
const providerRegistry = new AIProviderRegistry({ secretsManager });
|
|
151
175
|
|
|
152
176
|
editorRegistry.addRenderer(
|
|
153
177
|
'@jupyterlite/ai:provider-registry.AIprovider',
|
|
154
|
-
aiSettingsRenderer({
|
|
178
|
+
aiSettingsRenderer({
|
|
179
|
+
providerRegistry,
|
|
180
|
+
rmRegistry,
|
|
181
|
+
secretsManager,
|
|
182
|
+
settingConnector
|
|
183
|
+
})
|
|
155
184
|
);
|
|
185
|
+
|
|
156
186
|
settingRegistry
|
|
157
187
|
.load(providerRegistryPlugin.id)
|
|
158
188
|
.then(settings => {
|
|
@@ -161,10 +191,10 @@ const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
|
|
|
161
191
|
const providerSettings = (settings.get('AIprovider').composite ?? {
|
|
162
192
|
provider: 'None'
|
|
163
193
|
}) as ReadonlyPartialJSONObject;
|
|
164
|
-
providerRegistry.setProvider(
|
|
165
|
-
providerSettings.provider as string,
|
|
166
|
-
providerSettings
|
|
167
|
-
);
|
|
194
|
+
providerRegistry.setProvider({
|
|
195
|
+
name: providerSettings.provider as string,
|
|
196
|
+
settings: providerSettings
|
|
197
|
+
});
|
|
168
198
|
};
|
|
169
199
|
|
|
170
200
|
settings.changed.connect(() => updateProvider());
|
|
@@ -181,10 +211,25 @@ const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
|
|
|
181
211
|
}
|
|
182
212
|
};
|
|
183
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Provides the settings connector as a separate plugin to allow for alternative
|
|
216
|
+
* implementations that may want to fetch settings from a different source or
|
|
217
|
+
* endpoint.
|
|
218
|
+
*/
|
|
219
|
+
const settingsConnector: JupyterFrontEndPlugin<ISettingConnector> = {
|
|
220
|
+
id: '@jupyterlite/ai:settings-connector',
|
|
221
|
+
description: 'Provides a settings connector which does not save passwords.',
|
|
222
|
+
autoStart: true,
|
|
223
|
+
provides: ISettingConnector,
|
|
224
|
+
activate: (app: JupyterFrontEnd) =>
|
|
225
|
+
new SettingConnector(app.serviceManager.settings)
|
|
226
|
+
};
|
|
227
|
+
|
|
184
228
|
export default [
|
|
185
229
|
providerRegistryPlugin,
|
|
186
230
|
chatCommandRegistryPlugin,
|
|
187
231
|
chatPlugin,
|
|
188
232
|
completerPlugin,
|
|
233
|
+
settingsConnector,
|
|
189
234
|
...defaultProviderPlugins
|
|
190
235
|
];
|
package/src/provider.ts
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
import { ICompletionProviderManager } from '@jupyterlab/completer';
|
|
2
1
|
import { BaseLanguageModel } from '@langchain/core/language_models/base';
|
|
3
2
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
4
3
|
import { ISignal, Signal } from '@lumino/signaling';
|
|
5
4
|
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
5
|
+
import { JSONSchema7 } from 'json-schema';
|
|
6
|
+
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
6
7
|
|
|
7
8
|
import { IBaseCompleter } from './base-completer';
|
|
8
|
-
import {
|
|
9
|
-
|
|
9
|
+
import {
|
|
10
|
+
getSecretId,
|
|
11
|
+
SECRETS_NAMESPACE,
|
|
12
|
+
SECRETS_REPLACEMENT
|
|
13
|
+
} from './settings';
|
|
14
|
+
import {
|
|
15
|
+
IAIProvider,
|
|
16
|
+
IAIProviderRegistry,
|
|
17
|
+
IDict,
|
|
18
|
+
ISetProviderOptions
|
|
19
|
+
} from './tokens';
|
|
10
20
|
|
|
11
21
|
export const chatSystemPrompt = (
|
|
12
22
|
options: AIProviderRegistry.IPromptOptions
|
|
@@ -39,6 +49,13 @@ Do not include the prompt in the output, only the string that should be appended
|
|
|
39
49
|
`;
|
|
40
50
|
|
|
41
51
|
export class AIProviderRegistry implements IAIProviderRegistry {
|
|
52
|
+
/**
|
|
53
|
+
* The constructor of the provider registry.
|
|
54
|
+
*/
|
|
55
|
+
constructor(options: AIProviderRegistry.IOptions) {
|
|
56
|
+
this._secretsManager = options.secretsManager || null;
|
|
57
|
+
}
|
|
58
|
+
|
|
42
59
|
/**
|
|
43
60
|
* Get the list of provider names.
|
|
44
61
|
*/
|
|
@@ -56,6 +73,11 @@ export class AIProviderRegistry implements IAIProviderRegistry {
|
|
|
56
73
|
);
|
|
57
74
|
}
|
|
58
75
|
this._providers.set(provider.name, provider);
|
|
76
|
+
|
|
77
|
+
// Set the provider if the loading has been deferred.
|
|
78
|
+
if (provider.name === this._deferredProvider?.name) {
|
|
79
|
+
this.setProvider(this._deferredProvider);
|
|
80
|
+
}
|
|
59
81
|
}
|
|
60
82
|
|
|
61
83
|
/**
|
|
@@ -131,15 +153,36 @@ export class AIProviderRegistry implements IAIProviderRegistry {
|
|
|
131
153
|
* Set the providers (chat model and completer).
|
|
132
154
|
* Creates the providers if the name has changed, otherwise only updates their config.
|
|
133
155
|
*
|
|
134
|
-
* @param
|
|
135
|
-
* @param settings - the settings for the models.
|
|
156
|
+
* @param options - An object with the name and the settings of the provider to use.
|
|
136
157
|
*/
|
|
137
|
-
setProvider(
|
|
158
|
+
async setProvider(options: ISetProviderOptions): Promise<void> {
|
|
159
|
+
const { name, settings } = options;
|
|
138
160
|
this._currentProvider = this._providers.get(name) ?? null;
|
|
161
|
+
if (this._currentProvider === null) {
|
|
162
|
+
// The current provider may not be loaded when the settings are first loaded.
|
|
163
|
+
// Let's defer the provider loading.
|
|
164
|
+
this._deferredProvider = options;
|
|
165
|
+
} else {
|
|
166
|
+
this._deferredProvider = null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Build a new settings object containing the secrets.
|
|
170
|
+
const fullSettings: IDict = {};
|
|
171
|
+
for (const key of Object.keys(settings)) {
|
|
172
|
+
if (settings[key] === SECRETS_REPLACEMENT) {
|
|
173
|
+
const id = getSecretId(name, key);
|
|
174
|
+
const secrets = await this._secretsManager?.get(SECRETS_NAMESPACE, id);
|
|
175
|
+
fullSettings[key] = secrets?.value || settings[key];
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
fullSettings[key] = settings[key];
|
|
179
|
+
}
|
|
139
180
|
|
|
140
181
|
if (this._currentProvider?.completer !== undefined) {
|
|
141
182
|
try {
|
|
142
|
-
this._completer = new this._currentProvider.completer({
|
|
183
|
+
this._completer = new this._currentProvider.completer({
|
|
184
|
+
...fullSettings
|
|
185
|
+
});
|
|
143
186
|
this._completerError = '';
|
|
144
187
|
} catch (e: any) {
|
|
145
188
|
this._completerError = e.message;
|
|
@@ -150,7 +193,9 @@ export class AIProviderRegistry implements IAIProviderRegistry {
|
|
|
150
193
|
|
|
151
194
|
if (this._currentProvider?.chatModel !== undefined) {
|
|
152
195
|
try {
|
|
153
|
-
this._chatModel = new this._currentProvider.chatModel({
|
|
196
|
+
this._chatModel = new this._currentProvider.chatModel({
|
|
197
|
+
...fullSettings
|
|
198
|
+
});
|
|
154
199
|
this._chatError = '';
|
|
155
200
|
} catch (e: any) {
|
|
156
201
|
this._chatError = e.message;
|
|
@@ -170,6 +215,7 @@ export class AIProviderRegistry implements IAIProviderRegistry {
|
|
|
170
215
|
return this._providerChanged;
|
|
171
216
|
}
|
|
172
217
|
|
|
218
|
+
private _secretsManager: ISecretsManager | null;
|
|
173
219
|
private _currentProvider: IAIProvider | null = null;
|
|
174
220
|
private _completer: IBaseCompleter | null = null;
|
|
175
221
|
private _chatModel: BaseChatModel | null = null;
|
|
@@ -178,6 +224,7 @@ export class AIProviderRegistry implements IAIProviderRegistry {
|
|
|
178
224
|
private _chatError: string = '';
|
|
179
225
|
private _completerError: string = '';
|
|
180
226
|
private _providers = new Map<string, IAIProvider>();
|
|
227
|
+
private _deferredProvider: ISetProviderOptions | null = null;
|
|
181
228
|
}
|
|
182
229
|
|
|
183
230
|
export namespace AIProviderRegistry {
|
|
@@ -186,13 +233,9 @@ export namespace AIProviderRegistry {
|
|
|
186
233
|
*/
|
|
187
234
|
export interface IOptions {
|
|
188
235
|
/**
|
|
189
|
-
* The
|
|
190
|
-
*/
|
|
191
|
-
completionProviderManager: ICompletionProviderManager;
|
|
192
|
-
/**
|
|
193
|
-
* The application commands registry.
|
|
236
|
+
* The secrets manager used in the application.
|
|
194
237
|
*/
|
|
195
|
-
|
|
238
|
+
secretsManager?: ISecretsManager;
|
|
196
239
|
}
|
|
197
240
|
|
|
198
241
|
/**
|
package/src/settings/panel.tsx
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ISettingConnector,
|
|
4
|
+
ISettingRegistry
|
|
5
|
+
} from '@jupyterlab/settingregistry';
|
|
3
6
|
import { FormComponent, IFormRenderer } from '@jupyterlab/ui-components';
|
|
4
7
|
import { ArrayExt } from '@lumino/algorithm';
|
|
5
8
|
import { JSONExt } from '@lumino/coreutils';
|
|
@@ -10,10 +13,10 @@ import { JSONSchema7 } from 'json-schema';
|
|
|
10
13
|
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
11
14
|
import React from 'react';
|
|
12
15
|
|
|
16
|
+
import { getSecretId, SECRETS_NAMESPACE, SettingConnector } from '.';
|
|
13
17
|
import baseSettings from './base.json';
|
|
14
18
|
import { IAIProviderRegistry, IDict } from '../tokens';
|
|
15
19
|
|
|
16
|
-
const SECRETS_NAMESPACE = '@jupyterlite/ai';
|
|
17
20
|
const MD_MIME_TYPE = 'text/markdown';
|
|
18
21
|
const STORAGE_NAME = '@jupyterlite/ai:settings';
|
|
19
22
|
const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
|
|
@@ -22,6 +25,7 @@ export const aiSettingsRenderer = (options: {
|
|
|
22
25
|
providerRegistry: IAIProviderRegistry;
|
|
23
26
|
rmRegistry?: IRenderMimeRegistry;
|
|
24
27
|
secretsManager?: ISecretsManager;
|
|
28
|
+
settingConnector?: ISettingConnector;
|
|
25
29
|
}): IFormRenderer => {
|
|
26
30
|
return {
|
|
27
31
|
fieldRenderer: (props: FieldProps) => {
|
|
@@ -54,6 +58,7 @@ export class AiSettings extends React.Component<
|
|
|
54
58
|
this._providerRegistry = props.formContext.providerRegistry;
|
|
55
59
|
this._rmRegistry = props.formContext.rmRegistry ?? null;
|
|
56
60
|
this._secretsManager = props.formContext.secretsManager ?? null;
|
|
61
|
+
this._settingConnector = props.formContext.settingConnector ?? null;
|
|
57
62
|
this._settings = props.formContext.settings;
|
|
58
63
|
|
|
59
64
|
this._useSecretsManager =
|
|
@@ -130,7 +135,7 @@ export class AiSettings extends React.Component<
|
|
|
130
135
|
if (inputs[i].type.toLowerCase() === 'password') {
|
|
131
136
|
const label = inputs[i].getAttribute('label');
|
|
132
137
|
if (label) {
|
|
133
|
-
const id =
|
|
138
|
+
const id = getSecretId(this._provider, label);
|
|
134
139
|
this._secretsManager.attach(
|
|
135
140
|
SECRETS_NAMESPACE,
|
|
136
141
|
id,
|
|
@@ -141,6 +146,9 @@ export class AiSettings extends React.Component<
|
|
|
141
146
|
}
|
|
142
147
|
}
|
|
143
148
|
}
|
|
149
|
+
if (this._settingConnector instanceof SettingConnector) {
|
|
150
|
+
this._settingConnector.doNotSave = this._unsavedFields;
|
|
151
|
+
}
|
|
144
152
|
}
|
|
145
153
|
|
|
146
154
|
/**
|
|
@@ -187,6 +195,9 @@ export class AiSettings extends React.Component<
|
|
|
187
195
|
this._secretsManager?.detachAll(SECRETS_NAMESPACE);
|
|
188
196
|
this._formInputs = [];
|
|
189
197
|
this._unsavedFields = [];
|
|
198
|
+
if (this._settingConnector instanceof SettingConnector) {
|
|
199
|
+
this._settingConnector.doNotSave = [];
|
|
200
|
+
}
|
|
190
201
|
this.saveSettings(this._currentSettings);
|
|
191
202
|
} else {
|
|
192
203
|
// Remove all the keys stored locally and attach the password inputs to the
|
|
@@ -202,6 +213,9 @@ export class AiSettings extends React.Component<
|
|
|
202
213
|
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
203
214
|
this.componentDidUpdate();
|
|
204
215
|
}
|
|
216
|
+
this._settings
|
|
217
|
+
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
218
|
+
.catch(console.error);
|
|
205
219
|
};
|
|
206
220
|
|
|
207
221
|
/**
|
|
@@ -337,6 +351,7 @@ export class AiSettings extends React.Component<
|
|
|
337
351
|
private _useSecretsManager: boolean;
|
|
338
352
|
private _rmRegistry: IRenderMimeRegistry | null;
|
|
339
353
|
private _secretsManager: ISecretsManager | null;
|
|
354
|
+
private _settingConnector: ISettingConnector | null;
|
|
340
355
|
private _currentSettings: IDict<any> = { provider: 'None' };
|
|
341
356
|
private _uiSchema: IDict<any> = {};
|
|
342
357
|
private _settings: ISettingRegistry.ISettings;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { PageConfig } from '@jupyterlab/coreutils';
|
|
2
|
+
import {
|
|
3
|
+
ISettingConnector,
|
|
4
|
+
ISettingRegistry
|
|
5
|
+
} from '@jupyterlab/settingregistry';
|
|
6
|
+
import { DataConnector, IDataConnector } from '@jupyterlab/statedb';
|
|
7
|
+
import { Throttler } from '@lumino/polling';
|
|
8
|
+
import * as json5 from 'json5';
|
|
9
|
+
|
|
10
|
+
import { SECRETS_REPLACEMENT } from '.';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A data connector for fetching settings.
|
|
14
|
+
*
|
|
15
|
+
* #### Notes
|
|
16
|
+
* This connector adds a query parameter to the base services setting manager.
|
|
17
|
+
*/
|
|
18
|
+
export class SettingConnector
|
|
19
|
+
extends DataConnector<ISettingRegistry.IPlugin, string>
|
|
20
|
+
implements ISettingConnector
|
|
21
|
+
{
|
|
22
|
+
constructor(connector: IDataConnector<ISettingRegistry.IPlugin, string>) {
|
|
23
|
+
super();
|
|
24
|
+
this._connector = connector;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
set doNotSave(fields: string[]) {
|
|
28
|
+
this._doNotSave = [...fields];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Fetch settings for a plugin.
|
|
33
|
+
* @param id - The plugin ID
|
|
34
|
+
*
|
|
35
|
+
* #### Notes
|
|
36
|
+
* The REST API requests are throttled at one request per plugin per 100ms.
|
|
37
|
+
*/
|
|
38
|
+
fetch(id: string): Promise<ISettingRegistry.IPlugin | undefined> {
|
|
39
|
+
const throttlers = this._throttlers;
|
|
40
|
+
if (!(id in throttlers)) {
|
|
41
|
+
throttlers[id] = new Throttler(() => this._connector.fetch(id), 100);
|
|
42
|
+
}
|
|
43
|
+
return throttlers[id].invoke();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async list(query: 'ids'): Promise<{ ids: string[] }>;
|
|
47
|
+
async list(
|
|
48
|
+
query: 'active' | 'all'
|
|
49
|
+
): Promise<{ ids: string[]; values: ISettingRegistry.IPlugin[] }>;
|
|
50
|
+
async list(
|
|
51
|
+
query: 'active' | 'all' | 'ids' = 'all'
|
|
52
|
+
): Promise<{ ids: string[]; values?: ISettingRegistry.IPlugin[] }> {
|
|
53
|
+
const { isDisabled } = PageConfig.Extension;
|
|
54
|
+
const { ids, values } = await this._connector.list(
|
|
55
|
+
query === 'ids' ? 'ids' : undefined
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (query === 'all') {
|
|
59
|
+
return { ids, values };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (query === 'ids') {
|
|
63
|
+
return { ids };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
ids: ids.filter(id => !isDisabled(id)),
|
|
68
|
+
values: values.filter(({ id }) => !isDisabled(id))
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async save(id: string, raw: string): Promise<void> {
|
|
73
|
+
const settings = json5.parse(raw);
|
|
74
|
+
this._doNotSave.forEach(field => {
|
|
75
|
+
if (
|
|
76
|
+
settings['AIprovider'] !== undefined &&
|
|
77
|
+
settings['AIprovider'][field] !== undefined &&
|
|
78
|
+
settings['AIprovider'][field] !== ''
|
|
79
|
+
) {
|
|
80
|
+
settings['AIprovider'][field] = SECRETS_REPLACEMENT;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
await this._connector.save(id, json5.stringify(settings, null, 2));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private _connector: IDataConnector<ISettingRegistry.IPlugin, string>;
|
|
87
|
+
private _doNotSave: string[] = [];
|
|
88
|
+
private _throttlers: { [key: string]: Throttler } = Object.create(null);
|
|
89
|
+
}
|
package/src/tokens.ts
CHANGED
|
@@ -85,10 +85,9 @@ export interface IAIProviderRegistry {
|
|
|
85
85
|
* Set the providers (chat model and completer).
|
|
86
86
|
* Creates the providers if the name has changed, otherwise only updates their config.
|
|
87
87
|
*
|
|
88
|
-
* @param
|
|
89
|
-
* @param settings - the settings for the models.
|
|
88
|
+
* @param options - an object with the name and the settings of the provider to use.
|
|
90
89
|
*/
|
|
91
|
-
setProvider(
|
|
90
|
+
setProvider(options: ISetProviderOptions): void;
|
|
92
91
|
/**
|
|
93
92
|
* A signal emitting when the provider or its settings has changed.
|
|
94
93
|
*/
|
|
@@ -103,6 +102,20 @@ export interface IAIProviderRegistry {
|
|
|
103
102
|
readonly completerError: string;
|
|
104
103
|
}
|
|
105
104
|
|
|
105
|
+
/**
|
|
106
|
+
* The set provider options.
|
|
107
|
+
*/
|
|
108
|
+
export interface ISetProviderOptions {
|
|
109
|
+
/**
|
|
110
|
+
* The name of the provider.
|
|
111
|
+
*/
|
|
112
|
+
name: string;
|
|
113
|
+
/**
|
|
114
|
+
* The settings of the provider.
|
|
115
|
+
*/
|
|
116
|
+
settings: ReadonlyPartialJSONObject;
|
|
117
|
+
}
|
|
118
|
+
|
|
106
119
|
/**
|
|
107
120
|
* The provider registry token.
|
|
108
121
|
*/
|