@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.
@@ -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 {
@@ -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,19 +1,20 @@
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
- import { ISecretsManager } from 'jupyter-secrets-manager';
8
+ import { ISecretsManager, SecretsManager } from 'jupyter-secrets-manager';
9
9
  import { ChatHandler } from './chat-handler';
10
10
  import { CompletionProvider } from './completion-provider';
11
11
  import { defaultProviderPlugins } from './default-providers';
12
12
  import { AIProviderRegistry } from './provider';
13
- import { aiSettingsRenderer } from './settings/panel';
14
- import { IAIProviderRegistry } from './tokens';
13
+ import { aiSettingsRenderer, SettingConnector } from './settings';
14
+ import { IAIProviderRegistry, PLUGIN_IDS } from './tokens';
15
+ import { stopItem } from './components/stop-button';
15
16
  const chatCommandRegistryPlugin = {
16
- id: '@jupyterlite/ai:autocompletion-registry',
17
+ id: PLUGIN_IDS.chatCommandRegistry,
17
18
  description: 'Autocompletion registry',
18
19
  autoStart: true,
19
20
  provides: IChatCommandRegistry,
@@ -24,7 +25,7 @@ const chatCommandRegistryPlugin = {
24
25
  }
25
26
  };
26
27
  const chatPlugin = {
27
- id: '@jupyterlite/ai:chat',
28
+ id: PLUGIN_IDS.chat,
28
29
  description: 'LLM chat extension',
29
30
  autoStart: true,
30
31
  requires: [IAIProviderRegistry, IRenderMimeRegistry, IChatCommandRegistry],
@@ -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
  }
@@ -83,7 +98,7 @@ const chatPlugin = {
83
98
  }
84
99
  };
85
100
  const completerPlugin = {
86
- id: '@jupyterlite/ai:completer',
101
+ id: PLUGIN_IDS.completer,
87
102
  autoStart: true,
88
103
  requires: [IAIProviderRegistry, ICompletionProviderManager],
89
104
  activate: (app, providerRegistry, manager) => {
@@ -94,15 +109,24 @@ const completerPlugin = {
94
109
  manager.registerInlineProvider(completer);
95
110
  }
96
111
  };
97
- const providerRegistryPlugin = {
98
- id: '@jupyterlite/ai:provider-registry',
112
+ const providerRegistryPlugin = SecretsManager.sign(PLUGIN_IDS.providerRegistry, token => ({
113
+ id: PLUGIN_IDS.providerRegistry,
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({ providerRegistry, rmRegistry, secretsManager }));
118
+ activate: (app, editorRegistry, settingRegistry, rmRegistry, secretsManager, settingConnector) => {
119
+ const providerRegistry = new AIProviderRegistry({
120
+ token,
121
+ secretsManager
122
+ });
123
+ editorRegistry.addRenderer(`${PLUGIN_IDS.providerRegistry}.AIprovider`, aiSettingsRenderer({
124
+ providerRegistry,
125
+ secretsToken: token,
126
+ rmRegistry,
127
+ secretsManager,
128
+ settingConnector
129
+ }));
106
130
  settingRegistry
107
131
  .load(providerRegistryPlugin.id)
108
132
  .then(settings => {
@@ -112,7 +136,10 @@ const providerRegistryPlugin = {
112
136
  const providerSettings = ((_a = settings.get('AIprovider').composite) !== null && _a !== void 0 ? _a : {
113
137
  provider: 'None'
114
138
  });
115
- providerRegistry.setProvider(providerSettings.provider, providerSettings);
139
+ providerRegistry.setProvider({
140
+ name: providerSettings.provider,
141
+ settings: providerSettings
142
+ });
116
143
  };
117
144
  settings.changed.connect(() => updateProvider());
118
145
  updateProvider();
@@ -122,11 +149,24 @@ const providerRegistryPlugin = {
122
149
  });
123
150
  return providerRegistry;
124
151
  }
152
+ }));
153
+ /**
154
+ * Provides the settings connector as a separate plugin to allow for alternative
155
+ * implementations that may want to fetch settings from a different source or
156
+ * endpoint.
157
+ */
158
+ const settingsConnector = {
159
+ id: PLUGIN_IDS.settingsConnector,
160
+ description: 'Provides a settings connector which does not save passwords.',
161
+ autoStart: true,
162
+ provides: ISettingConnector,
163
+ activate: (app) => new SettingConnector(app.serviceManager.settings)
125
164
  };
126
165
  export default [
127
166
  providerRegistryPlugin,
128
167
  chatCommandRegistryPlugin,
129
168
  chatPlugin,
130
169
  completerPlugin,
170
+ settingsConnector,
131
171
  ...defaultProviderPlugins
132
172
  ];
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 name - the name of the provider to use.
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(name: string, settings: ReadonlyPartialJSONObject): void;
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,13 @@ export declare namespace AIProviderRegistry {
76
81
  */
77
82
  interface IOptions {
78
83
  /**
79
- * The completion provider manager in which register the LLM completer.
84
+ * The secrets manager used in the application.
80
85
  */
81
- completionProviderManager: ICompletionProviderManager;
86
+ secretsManager?: ISecretsManager;
82
87
  /**
83
- * The application commands registry.
88
+ * The token used to request the secrets manager.
84
89
  */
85
- requestCompletion: () => void;
90
+ token: symbol;
86
91
  }
87
92
  /**
88
93
  * The options for the Chat system prompt.
package/lib/provider.js CHANGED
@@ -1,4 +1,7 @@
1
1
  import { Signal } from '@lumino/signaling';
2
+ import { getSecretId, SECRETS_REPLACEMENT } from './settings';
3
+ import { PLUGIN_IDS } from './tokens';
4
+ const SECRETS_NAMESPACE = PLUGIN_IDS.providerRegistry;
2
5
  export const chatSystemPrompt = (options) => `
3
6
  You are Jupyternaut, a conversational assistant living in JupyterLab to help users.
4
7
  You are not a language model, but rather an application built on a foundation model from ${options.provider_name}.
@@ -26,7 +29,10 @@ would write.
26
29
  Do not include the prompt in the output, only the string that should be appended to the current input.
27
30
  `;
28
31
  export class AIProviderRegistry {
29
- constructor() {
32
+ /**
33
+ * The constructor of the provider registry.
34
+ */
35
+ constructor(options) {
30
36
  this._currentProvider = null;
31
37
  this._completer = null;
32
38
  this._chatModel = null;
@@ -35,6 +41,9 @@ export class AIProviderRegistry {
35
41
  this._chatError = '';
36
42
  this._completerError = '';
37
43
  this._providers = new Map();
44
+ this._deferredProvider = null;
45
+ this._secretsManager = options.secretsManager || null;
46
+ Private.setToken(options.token);
38
47
  }
39
48
  /**
40
49
  * Get the list of provider names.
@@ -46,10 +55,15 @@ export class AIProviderRegistry {
46
55
  * Add a new provider.
47
56
  */
48
57
  add(provider) {
58
+ var _a;
49
59
  if (this._providers.has(provider.name)) {
50
60
  throw new Error(`A AI provider named '${provider.name}' is already registered`);
51
61
  }
52
62
  this._providers.set(provider.name, provider);
63
+ // Set the provider if the loading has been deferred.
64
+ if (provider.name === ((_a = this._deferredProvider) === null || _a === void 0 ? void 0 : _a.name)) {
65
+ this.setProvider(this._deferredProvider);
66
+ }
53
67
  }
54
68
  /**
55
69
  * Get the current provider name.
@@ -119,15 +133,36 @@ export class AIProviderRegistry {
119
133
  * Set the providers (chat model and completer).
120
134
  * Creates the providers if the name has changed, otherwise only updates their config.
121
135
  *
122
- * @param name - the name of the provider to use.
123
- * @param settings - the settings for the models.
136
+ * @param options - An object with the name and the settings of the provider to use.
124
137
  */
125
- setProvider(name, settings) {
126
- var _a, _b, _c;
138
+ async setProvider(options) {
139
+ var _a, _b, _c, _d;
140
+ const { name, settings } = options;
127
141
  this._currentProvider = (_a = this._providers.get(name)) !== null && _a !== void 0 ? _a : null;
128
- if (((_b = this._currentProvider) === null || _b === void 0 ? void 0 : _b.completer) !== undefined) {
142
+ if (this._currentProvider === null) {
143
+ // The current provider may not be loaded when the settings are first loaded.
144
+ // Let's defer the provider loading.
145
+ this._deferredProvider = options;
146
+ }
147
+ else {
148
+ this._deferredProvider = null;
149
+ }
150
+ // Build a new settings object containing the secrets.
151
+ const fullSettings = {};
152
+ for (const key of Object.keys(settings)) {
153
+ if (settings[key] === SECRETS_REPLACEMENT) {
154
+ const id = getSecretId(name, key);
155
+ const secrets = await ((_b = this._secretsManager) === null || _b === void 0 ? void 0 : _b.get(Private.getToken(), SECRETS_NAMESPACE, id));
156
+ fullSettings[key] = (secrets === null || secrets === void 0 ? void 0 : secrets.value) || settings[key];
157
+ continue;
158
+ }
159
+ fullSettings[key] = settings[key];
160
+ }
161
+ if (((_c = this._currentProvider) === null || _c === void 0 ? void 0 : _c.completer) !== undefined) {
129
162
  try {
130
- this._completer = new this._currentProvider.completer({ ...settings });
163
+ this._completer = new this._currentProvider.completer({
164
+ ...fullSettings
165
+ });
131
166
  this._completerError = '';
132
167
  }
133
168
  catch (e) {
@@ -137,9 +172,11 @@ export class AIProviderRegistry {
137
172
  else {
138
173
  this._completer = null;
139
174
  }
140
- if (((_c = this._currentProvider) === null || _c === void 0 ? void 0 : _c.chatModel) !== undefined) {
175
+ if (((_d = this._currentProvider) === null || _d === void 0 ? void 0 : _d.chatModel) !== undefined) {
141
176
  try {
142
- this._chatModel = new this._currentProvider.chatModel({ ...settings });
177
+ this._chatModel = new this._currentProvider.chatModel({
178
+ ...fullSettings
179
+ });
143
180
  this._chatError = '';
144
181
  }
145
182
  catch (e) {
@@ -197,3 +234,24 @@ export class AIProviderRegistry {
197
234
  }
198
235
  AIProviderRegistry.updateConfig = updateConfig;
199
236
  })(AIProviderRegistry || (AIProviderRegistry = {}));
237
+ var Private;
238
+ (function (Private) {
239
+ /**
240
+ * The token to use with the secrets manager.
241
+ */
242
+ let secretsToken;
243
+ /**
244
+ * Set of the token.
245
+ */
246
+ function setToken(value) {
247
+ secretsToken = value;
248
+ }
249
+ Private.setToken = setToken;
250
+ /**
251
+ * get the token.
252
+ */
253
+ function getToken() {
254
+ return secretsToken;
255
+ }
256
+ Private.getToken = getToken;
257
+ })(Private || (Private = {}));
@@ -0,0 +1,3 @@
1
+ export * from './panel';
2
+ export * from './settings-connector';
3
+ export * from './utils';
@@ -0,0 +1,3 @@
1
+ export * from './panel';
2
+ export * from './settings-connector';
3
+ export * from './utils';
@@ -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';
@@ -7,8 +8,10 @@ import React from 'react';
7
8
  import { IAIProviderRegistry, IDict } from '../tokens';
8
9
  export declare const aiSettingsRenderer: (options: {
9
10
  providerRegistry: IAIProviderRegistry;
11
+ secretsToken?: symbol;
10
12
  rmRegistry?: IRenderMimeRegistry;
11
13
  secretsManager?: ISecretsManager;
14
+ settingConnector?: ISettingConnector;
12
15
  }) => IFormRenderer;
13
16
  export interface ISettingsFormStates {
14
17
  schema: JSONSchema7;
@@ -17,6 +20,7 @@ export interface ISettingsFormStates {
17
20
  export declare class AiSettings extends React.Component<FieldProps, ISettingsFormStates> {
18
21
  constructor(props: FieldProps);
19
22
  componentDidUpdate(): Promise<void>;
23
+ componentWillUnmount(): void;
20
24
  /**
21
25
  * Get the current provider from the local storage.
22
26
  */
@@ -34,11 +38,6 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
34
38
  */
35
39
  saveSettings(value: IDict<any>): void;
36
40
  private updateUseSecretsManager;
37
- /**
38
- * Update the UI schema of the form.
39
- * Currently use to hide API keys.
40
- */
41
- private _updateUiSchema;
42
41
  /**
43
42
  * Build the schema for a given provider.
44
43
  */
@@ -73,8 +72,10 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
73
72
  private _provider;
74
73
  private _providerSchema;
75
74
  private _useSecretsManager;
75
+ private _hideSecretFields;
76
76
  private _rmRegistry;
77
77
  private _secretsManager;
78
+ private _settingConnector;
78
79
  private _currentSettings;
79
80
  private _uiSchema;
80
81
  private _settings;