@jupyterlite/ai 0.4.0 → 0.5.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 +7 -1
- package/lib/chat-handler.js +29 -0
- package/lib/completion-provider.d.ts +1 -1
- package/lib/{llm-models/anthropic-completer.d.ts → default-providers/Anthropic/completer.d.ts} +1 -1
- package/lib/{llm-models/anthropic-completer.js → default-providers/Anthropic/completer.js} +1 -1
- package/lib/{llm-models/chrome-completer.d.ts → default-providers/ChromeAI/completer.d.ts} +1 -1
- package/lib/{llm-models/chrome-completer.js → default-providers/ChromeAI/completer.js} +1 -1
- package/lib/default-providers/ChromeAI/instructions.d.ts +2 -0
- package/lib/default-providers/ChromeAI/instructions.js +24 -0
- package/lib/{llm-models/codestral-completer.d.ts → default-providers/MistralAI/completer.d.ts} +1 -1
- package/lib/{llm-models/codestral-completer.js → default-providers/MistralAI/completer.js} +1 -1
- package/lib/default-providers/MistralAI/instructions.d.ts +2 -0
- package/lib/default-providers/MistralAI/instructions.js +16 -0
- package/lib/{llm-models/openai-completer.d.ts → default-providers/OpenAI/completer.d.ts} +1 -1
- package/lib/{llm-models/openai-completer.js → default-providers/OpenAI/completer.js} +1 -1
- package/lib/default-providers/index.d.ts +2 -0
- package/lib/default-providers/index.js +60 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +17 -32
- package/lib/provider.d.ts +1 -1
- package/lib/settings/panel.d.ts +14 -0
- package/lib/settings/panel.js +82 -5
- package/lib/tokens.d.ts +1 -1
- package/package.json +8 -5
- package/schema/provider-registry.json +6 -0
- package/src/chat-handler.ts +34 -0
- package/src/completion-provider.ts +1 -1
- package/src/{llm-models/anthropic-completer.ts → default-providers/Anthropic/completer.ts} +2 -2
- package/src/{llm-models/chrome-completer.ts → default-providers/ChromeAI/completer.ts} +3 -2
- package/src/default-providers/ChromeAI/instructions.ts +24 -0
- package/src/{llm-models/codestral-completer.ts → default-providers/MistralAI/completer.ts} +2 -2
- package/src/default-providers/MistralAI/instructions.ts +16 -0
- package/src/{llm-models/openai-completer.ts → default-providers/OpenAI/completer.ts} +2 -2
- package/src/default-providers/index.ts +71 -0
- package/src/index.ts +25 -42
- package/src/provider.ts +1 -1
- package/src/settings/panel.tsx +93 -4
- package/src/tokens.ts +1 -1
- package/lib/llm-models/index.d.ts +0 -4
- package/lib/llm-models/index.js +0 -43
- package/lib/settings/instructions.d.ts +0 -2
- package/lib/settings/instructions.js +0 -44
- package/lib/settings/schemas/index.d.ts +0 -3
- package/lib/settings/schemas/index.js +0 -11
- package/lib/slash-commands.d.ts +0 -16
- package/lib/slash-commands.js +0 -25
- package/src/llm-models/index.ts +0 -50
- package/src/settings/instructions.ts +0 -48
- package/src/settings/schemas/index.ts +0 -15
- package/src/slash-commands.tsx +0 -55
- /package/lib/{llm-models/base-completer.d.ts → base-completer.d.ts} +0 -0
- /package/lib/{llm-models/base-completer.js → base-completer.js} +0 -0
- /package/lib/{settings/schemas/_generated/Anthropic.json → default-providers/Anthropic/settings-schema.json} +0 -0
- /package/lib/{settings/schemas/_generated/ChromeAI.json → default-providers/ChromeAI/settings-schema.json} +0 -0
- /package/lib/{settings/schemas/_generated/MistralAI.json → default-providers/MistralAI/settings-schema.json} +0 -0
- /package/lib/{settings/schemas/_generated/OpenAI.json → default-providers/OpenAI/settings-schema.json} +0 -0
- /package/lib/settings/{schemas/base.json → base.json} +0 -0
- /package/src/{llm-models/base-completer.ts → base-completer.ts} +0 -0
- /package/src/{llm-models/svg.d.ts → global.d.ts} +0 -0
package/lib/chat-handler.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChatModel, IChatHistory, IChatMessage, INewMessage } from '@jupyter/chat';
|
|
1
|
+
import { ChatCommand, ChatModel, IChatCommandProvider, IChatHistory, IChatMessage, IInputModel, INewMessage } from '@jupyter/chat';
|
|
2
2
|
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
3
3
|
import { IAIProviderRegistry } from './tokens';
|
|
4
4
|
export type ConnectionMessage = {
|
|
@@ -33,4 +33,10 @@ export declare namespace ChatHandler {
|
|
|
33
33
|
interface IOptions extends ChatModel.IOptions {
|
|
34
34
|
providerRegistry: IAIProviderRegistry;
|
|
35
35
|
}
|
|
36
|
+
class ClearCommandProvider implements IChatCommandProvider {
|
|
37
|
+
id: string;
|
|
38
|
+
private _slash_commands;
|
|
39
|
+
getChatCommands(inputModel: IInputModel): Promise<ChatCommand[]>;
|
|
40
|
+
handleChatCommand(command: ChatCommand, inputModel: IInputModel): Promise<void>;
|
|
41
|
+
}
|
|
36
42
|
}
|
package/lib/chat-handler.js
CHANGED
|
@@ -142,3 +142,32 @@ export class ChatHandler extends ChatModel {
|
|
|
142
142
|
super.messageAdded(message);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
+
(function (ChatHandler) {
|
|
146
|
+
class ClearCommandProvider {
|
|
147
|
+
constructor() {
|
|
148
|
+
this.id = '@jupyterlite/ai:clear-commands';
|
|
149
|
+
this._slash_commands = [
|
|
150
|
+
{
|
|
151
|
+
name: '/clear',
|
|
152
|
+
providerId: this.id,
|
|
153
|
+
replaceWith: '/clear',
|
|
154
|
+
description: 'Clear the chat'
|
|
155
|
+
}
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
async getChatCommands(inputModel) {
|
|
159
|
+
var _a, _b;
|
|
160
|
+
const match = (_b = (_a = inputModel.currentWord) === null || _a === void 0 ? void 0 : _a.match(/^\/\w*/)) === null || _b === void 0 ? void 0 : _b[0];
|
|
161
|
+
if (!match) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
const commands = this._slash_commands.filter(cmd => cmd.name.startsWith(match));
|
|
165
|
+
return commands;
|
|
166
|
+
}
|
|
167
|
+
async handleChatCommand(command, inputModel) {
|
|
168
|
+
// no handling needed because `replaceWith` is set in each command.
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
ChatHandler.ClearCommandProvider = ClearCommandProvider;
|
|
173
|
+
})(ChatHandler || (ChatHandler = {}));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CompletionHandler, IInlineCompletionContext, IInlineCompletionProvider } from '@jupyterlab/completer';
|
|
2
|
-
import { IBaseCompleter } from './
|
|
2
|
+
import { IBaseCompleter } from './base-completer';
|
|
3
3
|
import { IAIProviderRegistry } from './tokens';
|
|
4
4
|
/**
|
|
5
5
|
* The generic completion provider to register to the completion provider manager.
|
package/lib/{llm-models/anthropic-completer.d.ts → default-providers/Anthropic/completer.d.ts}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CompletionHandler, IInlineCompletionContext } from '@jupyterlab/completer';
|
|
2
2
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
3
|
-
import { BaseCompleter, IBaseCompleter } from '
|
|
3
|
+
import { BaseCompleter, IBaseCompleter } from '../../base-completer';
|
|
4
4
|
export declare class AnthropicCompleter implements IBaseCompleter {
|
|
5
5
|
constructor(options: BaseCompleter.IOptions);
|
|
6
6
|
get provider(): BaseChatModel;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChatAnthropic } from '@langchain/anthropic';
|
|
2
2
|
import { AIMessage, SystemMessage } from '@langchain/core/messages';
|
|
3
|
-
import { COMPLETION_SYSTEM_PROMPT } from '
|
|
3
|
+
import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
|
|
4
4
|
export class AnthropicCompleter {
|
|
5
5
|
constructor(options) {
|
|
6
6
|
this._prompt = COMPLETION_SYSTEM_PROMPT;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CompletionHandler, IInlineCompletionContext } from '@jupyterlab/completer';
|
|
2
2
|
import { LLM } from '@langchain/core/language_models/llms';
|
|
3
|
-
import { BaseCompleter, IBaseCompleter } from '
|
|
3
|
+
import { BaseCompleter, IBaseCompleter } from '../../base-completer';
|
|
4
4
|
export declare class ChromeCompleter implements IBaseCompleter {
|
|
5
5
|
constructor(options: BaseCompleter.IOptions);
|
|
6
6
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChromeAI } from '@langchain/community/experimental/llms/chrome_ai';
|
|
2
2
|
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
|
|
3
|
-
import { COMPLETION_SYSTEM_PROMPT } from '
|
|
3
|
+
import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
|
|
4
4
|
/**
|
|
5
5
|
* Regular expression to match the '```' string at the start of a string.
|
|
6
6
|
* So the completions returned by the LLM can still be kept after removing the code block formatting.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
declare const _default: "\n<i class=\"fas fa-exclamation-triangle\"></i> Support for ChromeAI is still experimental and only available in Google Chrome.\n\nYou can test ChromeAI is enabled in your browser by going to the following URL: <https://chromeai.org/>\n\nEnable the proper flags in Google Chrome.\n\n- chrome://flags/#prompt-api-for-gemini-nano\n - Select: `Enabled`\n- chrome://flags/#optimization-guide-on-device-model\n - Select: `Enabled BypassPrefRequirement`\n- chrome://components\n - Click `Check for Update` on Optimization Guide On Device Model to download the model\n- [Optional] chrome://flags/#text-safety-classifier\n\n<img src=\"https://github.com/user-attachments/assets/d48f46cc-52ee-4ce5-9eaf-c763cdbee04c\" alt=\"A screenshot showing how to enable the ChromeAI flag in Google Chrome\" width=\"500px\">\n\nThen restart Chrome for these changes to take effect.\n\n<i class=\"fas fa-exclamation-triangle\"></i> On first use, Chrome will download the on-device model, which can be as large as 22GB (according to their docs and at the time of writing).\nDuring the download, ChromeAI may not be available via the extension.\n\n<i class=\"fa fa-info-circle\" aria-hidden=\"true\"></i> For more information about Chrome Built-in AI: <https://developer.chrome.com/docs/ai/get-started>\n";
|
|
2
|
+
export default _default;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export default `
|
|
2
|
+
<i class="fas fa-exclamation-triangle"></i> Support for ChromeAI is still experimental and only available in Google Chrome.
|
|
3
|
+
|
|
4
|
+
You can test ChromeAI is enabled in your browser by going to the following URL: <https://chromeai.org/>
|
|
5
|
+
|
|
6
|
+
Enable the proper flags in Google Chrome.
|
|
7
|
+
|
|
8
|
+
- chrome://flags/#prompt-api-for-gemini-nano
|
|
9
|
+
- Select: \`Enabled\`
|
|
10
|
+
- chrome://flags/#optimization-guide-on-device-model
|
|
11
|
+
- Select: \`Enabled BypassPrefRequirement\`
|
|
12
|
+
- chrome://components
|
|
13
|
+
- Click \`Check for Update\` on Optimization Guide On Device Model to download the model
|
|
14
|
+
- [Optional] chrome://flags/#text-safety-classifier
|
|
15
|
+
|
|
16
|
+
<img src="https://github.com/user-attachments/assets/d48f46cc-52ee-4ce5-9eaf-c763cdbee04c" alt="A screenshot showing how to enable the ChromeAI flag in Google Chrome" width="500px">
|
|
17
|
+
|
|
18
|
+
Then restart Chrome for these changes to take effect.
|
|
19
|
+
|
|
20
|
+
<i class="fas fa-exclamation-triangle"></i> On first use, Chrome will download the on-device model, which can be as large as 22GB (according to their docs and at the time of writing).
|
|
21
|
+
During the download, ChromeAI may not be available via the extension.
|
|
22
|
+
|
|
23
|
+
<i class="fa fa-info-circle" aria-hidden="true"></i> For more information about Chrome Built-in AI: <https://developer.chrome.com/docs/ai/get-started>
|
|
24
|
+
`;
|
package/lib/{llm-models/codestral-completer.d.ts → default-providers/MistralAI/completer.d.ts}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CompletionHandler, IInlineCompletionContext } from '@jupyterlab/completer';
|
|
2
2
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
3
|
-
import { BaseCompleter, IBaseCompleter } from '
|
|
3
|
+
import { BaseCompleter, IBaseCompleter } from '../../base-completer';
|
|
4
4
|
export declare class CodestralCompleter implements IBaseCompleter {
|
|
5
5
|
constructor(options: BaseCompleter.IOptions);
|
|
6
6
|
get provider(): BaseChatModel;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
|
|
2
2
|
import { ChatMistralAI } from '@langchain/mistralai';
|
|
3
3
|
import { Throttler } from '@lumino/polling';
|
|
4
|
-
import { COMPLETION_SYSTEM_PROMPT } from '
|
|
4
|
+
import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
|
|
5
5
|
/**
|
|
6
6
|
* The Mistral API has a rate limit of 1 request per second
|
|
7
7
|
*/
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
declare const _default: "\n<i class=\"fas fa-exclamation-triangle\"></i> This extension is still very much experimental. It is not an official MistralAI extension.\n\n1. Go to <https://console.mistral.ai/api-keys/> and create an API key.\n\n <img src=\"https://raw.githubusercontent.com/jupyterlite/ai/refs/heads/main/img/1-api-key.png\" alt=\"Screenshot showing how to create an API key\" width=\"500px\">\n\n2. Open the JupyterLab settings and go to the **Ai providers** section to select the `MistralAI`\n provider and the API key (required).\n\n <img src=\"https://raw.githubusercontent.com/jupyterlite/ai/refs/heads/main/img/2-jupyterlab-settings.png\" alt=\"Screenshot showing how to add the API key to the settings\" width=\"500px\">\n\n3. Open the chat, or use the inline completer\n\n <img src=\"https://raw.githubusercontent.com/jupyterlite/ai/refs/heads/main/img/3-usage.png\" alt=\"Screenshot showing how to use the chat\" width=\"500px\">\n";
|
|
2
|
+
export default _default;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default `
|
|
2
|
+
<i class="fas fa-exclamation-triangle"></i> This extension is still very much experimental. It is not an official MistralAI extension.
|
|
3
|
+
|
|
4
|
+
1. Go to <https://console.mistral.ai/api-keys/> and create an API key.
|
|
5
|
+
|
|
6
|
+
<img src="https://raw.githubusercontent.com/jupyterlite/ai/refs/heads/main/img/1-api-key.png" alt="Screenshot showing how to create an API key" width="500px">
|
|
7
|
+
|
|
8
|
+
2. Open the JupyterLab settings and go to the **Ai providers** section to select the \`MistralAI\`
|
|
9
|
+
provider and the API key (required).
|
|
10
|
+
|
|
11
|
+
<img src="https://raw.githubusercontent.com/jupyterlite/ai/refs/heads/main/img/2-jupyterlab-settings.png" alt="Screenshot showing how to add the API key to the settings" width="500px">
|
|
12
|
+
|
|
13
|
+
3. Open the chat, or use the inline completer
|
|
14
|
+
|
|
15
|
+
<img src="https://raw.githubusercontent.com/jupyterlite/ai/refs/heads/main/img/3-usage.png" alt="Screenshot showing how to use the chat" width="500px">
|
|
16
|
+
`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CompletionHandler, IInlineCompletionContext } from '@jupyterlab/completer';
|
|
2
2
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
3
|
-
import { BaseCompleter, IBaseCompleter } from '
|
|
3
|
+
import { BaseCompleter, IBaseCompleter } from '../../base-completer';
|
|
4
4
|
export declare class OpenAICompleter implements IBaseCompleter {
|
|
5
5
|
constructor(options: BaseCompleter.IOptions);
|
|
6
6
|
get provider(): BaseChatModel;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AIMessage, SystemMessage } from '@langchain/core/messages';
|
|
2
2
|
import { ChatOpenAI } from '@langchain/openai';
|
|
3
|
-
import { COMPLETION_SYSTEM_PROMPT } from '
|
|
3
|
+
import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
|
|
4
4
|
export class OpenAICompleter {
|
|
5
5
|
constructor(options) {
|
|
6
6
|
this._prompt = COMPLETION_SYSTEM_PROMPT;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ChatAnthropic } from '@langchain/anthropic';
|
|
2
|
+
import { ChromeAI } from '@langchain/community/experimental/llms/chrome_ai';
|
|
3
|
+
import { ChatMistralAI } from '@langchain/mistralai';
|
|
4
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
5
|
+
import { IAIProviderRegistry } from '../tokens';
|
|
6
|
+
// Import completers
|
|
7
|
+
import { AnthropicCompleter } from './Anthropic/completer';
|
|
8
|
+
import { ChromeCompleter } from './ChromeAI/completer';
|
|
9
|
+
import { CodestralCompleter } from './MistralAI/completer';
|
|
10
|
+
import { OpenAICompleter } from './OpenAI/completer';
|
|
11
|
+
// Import Settings
|
|
12
|
+
import AnthropicSettings from './Anthropic/settings-schema.json';
|
|
13
|
+
import ChromeAISettings from './ChromeAI/settings-schema.json';
|
|
14
|
+
import MistralAISettings from './MistralAI/settings-schema.json';
|
|
15
|
+
import OpenAISettings from './OpenAI/settings-schema.json';
|
|
16
|
+
// Import instructions
|
|
17
|
+
import ChromeAIInstructions from './ChromeAI/instructions';
|
|
18
|
+
import MistralAIInstructions from './MistralAI/instructions';
|
|
19
|
+
// Build the AIProvider list
|
|
20
|
+
const AIProviders = [
|
|
21
|
+
{
|
|
22
|
+
name: 'Anthropic',
|
|
23
|
+
chatModel: ChatAnthropic,
|
|
24
|
+
completer: AnthropicCompleter,
|
|
25
|
+
settingsSchema: AnthropicSettings,
|
|
26
|
+
errorMessage: (error) => error.error.error.message
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'ChromeAI',
|
|
30
|
+
// TODO: fix
|
|
31
|
+
// @ts-expect-error: missing properties
|
|
32
|
+
chatModel: ChromeAI,
|
|
33
|
+
completer: ChromeCompleter,
|
|
34
|
+
instructions: ChromeAIInstructions,
|
|
35
|
+
settingsSchema: ChromeAISettings
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'MistralAI',
|
|
39
|
+
chatModel: ChatMistralAI,
|
|
40
|
+
completer: CodestralCompleter,
|
|
41
|
+
instructions: MistralAIInstructions,
|
|
42
|
+
settingsSchema: MistralAISettings
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'OpenAI',
|
|
46
|
+
chatModel: ChatOpenAI,
|
|
47
|
+
completer: OpenAICompleter,
|
|
48
|
+
settingsSchema: OpenAISettings
|
|
49
|
+
}
|
|
50
|
+
];
|
|
51
|
+
export const defaultProviderPlugins = AIProviders.map(provider => {
|
|
52
|
+
return {
|
|
53
|
+
id: `@jupyterlite/ai:${provider.name}`,
|
|
54
|
+
autoStart: true,
|
|
55
|
+
requires: [IAIProviderRegistry],
|
|
56
|
+
activate: (app, registry) => {
|
|
57
|
+
registry.add(provider);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
});
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IChatCommandRegistry } from '@jupyter/chat';
|
|
2
2
|
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
3
3
|
import { IAIProviderRegistry } from './tokens';
|
|
4
|
-
declare const _default: (JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<
|
|
4
|
+
declare const _default: (JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<IChatCommandRegistry> | JupyterFrontEndPlugin<IAIProviderRegistry>)[];
|
|
5
5
|
export default _default;
|
package/lib/index.js
CHANGED
|
@@ -1,49 +1,35 @@
|
|
|
1
|
-
import { ActiveCellManager,
|
|
1
|
+
import { ActiveCellManager, buildChatSidebar, buildErrorWidget, ChatCommandRegistry, IChatCommandRegistry } 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
6
|
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
7
7
|
import { IFormRendererRegistry } from '@jupyterlab/ui-components';
|
|
8
|
+
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
8
9
|
import { ChatHandler } from './chat-handler';
|
|
9
10
|
import { CompletionProvider } from './completion-provider';
|
|
10
|
-
import {
|
|
11
|
+
import { defaultProviderPlugins } from './default-providers';
|
|
11
12
|
import { AIProviderRegistry } from './provider';
|
|
12
13
|
import { aiSettingsRenderer } from './settings/panel';
|
|
13
|
-
import { renderSlashCommandOption } from './slash-commands';
|
|
14
14
|
import { IAIProviderRegistry } from './tokens';
|
|
15
|
-
const
|
|
15
|
+
const chatCommandRegistryPlugin = {
|
|
16
16
|
id: '@jupyterlite/ai:autocompletion-registry',
|
|
17
17
|
description: 'Autocompletion registry',
|
|
18
18
|
autoStart: true,
|
|
19
|
-
provides:
|
|
19
|
+
provides: IChatCommandRegistry,
|
|
20
20
|
activate: () => {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
opener: '/',
|
|
25
|
-
commands: options.map(option => {
|
|
26
|
-
return {
|
|
27
|
-
id: option.slice(1),
|
|
28
|
-
label: option,
|
|
29
|
-
description: 'Clear the chat window'
|
|
30
|
-
};
|
|
31
|
-
}),
|
|
32
|
-
props: {
|
|
33
|
-
renderOption: renderSlashCommandOption
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
autocompletionRegistry.add('jupyterlite-ai', autocompletionCommands);
|
|
37
|
-
return autocompletionRegistry;
|
|
21
|
+
const registry = new ChatCommandRegistry();
|
|
22
|
+
registry.addProvider(new ChatHandler.ClearCommandProvider());
|
|
23
|
+
return registry;
|
|
38
24
|
}
|
|
39
25
|
};
|
|
40
26
|
const chatPlugin = {
|
|
41
27
|
id: '@jupyterlite/ai:chat',
|
|
42
28
|
description: 'LLM chat extension',
|
|
43
29
|
autoStart: true,
|
|
44
|
-
requires: [IAIProviderRegistry, IRenderMimeRegistry,
|
|
30
|
+
requires: [IAIProviderRegistry, IRenderMimeRegistry, IChatCommandRegistry],
|
|
45
31
|
optional: [INotebookTracker, ISettingRegistry, IThemeManager],
|
|
46
|
-
activate: async (app, providerRegistry, rmRegistry,
|
|
32
|
+
activate: async (app, providerRegistry, rmRegistry, chatCommandRegistry, notebookTracker, settingsRegistry, themeManager) => {
|
|
47
33
|
let activeCellManager = null;
|
|
48
34
|
if (notebookTracker) {
|
|
49
35
|
activeCellManager = new ActiveCellManager({
|
|
@@ -85,7 +71,7 @@ const chatPlugin = {
|
|
|
85
71
|
model: chatHandler,
|
|
86
72
|
themeManager,
|
|
87
73
|
rmRegistry,
|
|
88
|
-
|
|
74
|
+
chatCommandRegistry
|
|
89
75
|
});
|
|
90
76
|
chatWidget.title.caption = 'Jupyterlite AI Chat';
|
|
91
77
|
}
|
|
@@ -112,11 +98,11 @@ const providerRegistryPlugin = {
|
|
|
112
98
|
id: '@jupyterlite/ai:provider-registry',
|
|
113
99
|
autoStart: true,
|
|
114
100
|
requires: [IFormRendererRegistry, ISettingRegistry],
|
|
115
|
-
optional: [IRenderMimeRegistry],
|
|
101
|
+
optional: [IRenderMimeRegistry, ISecretsManager],
|
|
116
102
|
provides: IAIProviderRegistry,
|
|
117
|
-
activate: (app, editorRegistry, settingRegistry, rmRegistry) => {
|
|
103
|
+
activate: (app, editorRegistry, settingRegistry, rmRegistry, secretsManager) => {
|
|
118
104
|
const providerRegistry = new AIProviderRegistry();
|
|
119
|
-
editorRegistry.addRenderer('@jupyterlite/ai:provider-registry.AIprovider', aiSettingsRenderer({ providerRegistry, rmRegistry }));
|
|
105
|
+
editorRegistry.addRenderer('@jupyterlite/ai:provider-registry.AIprovider', aiSettingsRenderer({ providerRegistry, rmRegistry, secretsManager }));
|
|
120
106
|
settingRegistry
|
|
121
107
|
.load(providerRegistryPlugin.id)
|
|
122
108
|
.then(settings => {
|
|
@@ -134,14 +120,13 @@ const providerRegistryPlugin = {
|
|
|
134
120
|
.catch(reason => {
|
|
135
121
|
console.error(`Failed to load settings for ${providerRegistryPlugin.id}`, reason);
|
|
136
122
|
});
|
|
137
|
-
// Initialize the registry with the default providers
|
|
138
|
-
AIProviders.forEach(provider => providerRegistry.add(provider));
|
|
139
123
|
return providerRegistry;
|
|
140
124
|
}
|
|
141
125
|
};
|
|
142
126
|
export default [
|
|
143
127
|
providerRegistryPlugin,
|
|
144
|
-
|
|
128
|
+
chatCommandRegistryPlugin,
|
|
145
129
|
chatPlugin,
|
|
146
|
-
completerPlugin
|
|
130
|
+
completerPlugin,
|
|
131
|
+
...defaultProviderPlugins
|
|
147
132
|
];
|
package/lib/provider.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { BaseLanguageModel } from '@langchain/core/language_models/base';
|
|
|
3
3
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
4
4
|
import { ISignal } from '@lumino/signaling';
|
|
5
5
|
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
6
|
-
import { IBaseCompleter } from './
|
|
6
|
+
import { IBaseCompleter } from './base-completer';
|
|
7
7
|
import { IAIProvider, IAIProviderRegistry } from './tokens';
|
|
8
8
|
import { JSONSchema7 } from 'json-schema';
|
|
9
9
|
export declare const chatSystemPrompt: (options: AIProviderRegistry.IPromptOptions) => string;
|
package/lib/settings/panel.d.ts
CHANGED
|
@@ -2,11 +2,13 @@ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
|
2
2
|
import { IFormRenderer } from '@jupyterlab/ui-components';
|
|
3
3
|
import type { FieldProps } from '@rjsf/utils';
|
|
4
4
|
import { JSONSchema7 } from 'json-schema';
|
|
5
|
+
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
5
6
|
import React from 'react';
|
|
6
7
|
import { IAIProviderRegistry, IDict } from '../tokens';
|
|
7
8
|
export declare const aiSettingsRenderer: (options: {
|
|
8
9
|
providerRegistry: IAIProviderRegistry;
|
|
9
10
|
rmRegistry?: IRenderMimeRegistry;
|
|
11
|
+
secretsManager?: ISecretsManager;
|
|
10
12
|
}) => IFormRenderer;
|
|
11
13
|
export interface ISettingsFormStates {
|
|
12
14
|
schema: JSONSchema7;
|
|
@@ -14,6 +16,7 @@ export interface ISettingsFormStates {
|
|
|
14
16
|
}
|
|
15
17
|
export declare class AiSettings extends React.Component<FieldProps, ISettingsFormStates> {
|
|
16
18
|
constructor(props: FieldProps);
|
|
19
|
+
componentDidUpdate(): Promise<void>;
|
|
17
20
|
/**
|
|
18
21
|
* Get the current provider from the local storage.
|
|
19
22
|
*/
|
|
@@ -30,6 +33,7 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
|
|
|
30
33
|
* Save settings in local storage for a given provider.
|
|
31
34
|
*/
|
|
32
35
|
saveSettings(value: IDict<any>): void;
|
|
36
|
+
private updateUseSecretsManager;
|
|
33
37
|
/**
|
|
34
38
|
* Update the UI schema of the form.
|
|
35
39
|
* Currently use to hide API keys.
|
|
@@ -53,6 +57,11 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
|
|
|
53
57
|
* Update the Jupyterlab settings accordingly.
|
|
54
58
|
*/
|
|
55
59
|
private _onProviderChanged;
|
|
60
|
+
/**
|
|
61
|
+
* Callback function called when the password input has been programmatically updated
|
|
62
|
+
* with the secret manager.
|
|
63
|
+
*/
|
|
64
|
+
private _onPasswordUpdated;
|
|
56
65
|
/**
|
|
57
66
|
* Triggered when the form value has changed, to update the current settings and save
|
|
58
67
|
* it in local storage.
|
|
@@ -63,8 +72,13 @@ export declare class AiSettings extends React.Component<FieldProps, ISettingsFor
|
|
|
63
72
|
private _providerRegistry;
|
|
64
73
|
private _provider;
|
|
65
74
|
private _providerSchema;
|
|
75
|
+
private _useSecretsManager;
|
|
66
76
|
private _rmRegistry;
|
|
77
|
+
private _secretsManager;
|
|
67
78
|
private _currentSettings;
|
|
68
79
|
private _uiSchema;
|
|
69
80
|
private _settings;
|
|
81
|
+
private _formRef;
|
|
82
|
+
private _unsavedFields;
|
|
83
|
+
private _formInputs;
|
|
70
84
|
}
|
package/lib/settings/panel.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { FormComponent } from '@jupyterlab/ui-components';
|
|
2
|
+
import { ArrayExt } from '@lumino/algorithm';
|
|
2
3
|
import { JSONExt } from '@lumino/coreutils';
|
|
3
4
|
import validator from '@rjsf/validator-ajv8';
|
|
4
5
|
import React from 'react';
|
|
5
|
-
import baseSettings from './
|
|
6
|
+
import baseSettings from './base.json';
|
|
7
|
+
const SECRETS_NAMESPACE = '@jupyterlite/ai';
|
|
6
8
|
const MD_MIME_TYPE = 'text/markdown';
|
|
7
9
|
const STORAGE_NAME = '@jupyterlite/ai:settings';
|
|
8
10
|
const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
|
|
@@ -19,8 +21,34 @@ const WrappedFormComponent = (props) => {
|
|
|
19
21
|
};
|
|
20
22
|
export class AiSettings extends React.Component {
|
|
21
23
|
constructor(props) {
|
|
22
|
-
var _a, _b;
|
|
24
|
+
var _a, _b, _c, _d;
|
|
23
25
|
super(props);
|
|
26
|
+
this.updateUseSecretsManager = (value) => {
|
|
27
|
+
var _a;
|
|
28
|
+
this._useSecretsManager = value;
|
|
29
|
+
if (!value) {
|
|
30
|
+
// Detach all the password inputs attached to the secrets manager, and save the
|
|
31
|
+
// current settings to the local storage to save the password.
|
|
32
|
+
(_a = this._secretsManager) === null || _a === void 0 ? void 0 : _a.detachAll(SECRETS_NAMESPACE);
|
|
33
|
+
this._formInputs = [];
|
|
34
|
+
this._unsavedFields = [];
|
|
35
|
+
this.saveSettings(this._currentSettings);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
// Remove all the keys stored locally and attach the password inputs to the
|
|
39
|
+
// secrets manager.
|
|
40
|
+
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
|
|
41
|
+
Object.keys(settings).forEach(provider => {
|
|
42
|
+
Object.keys(settings[provider])
|
|
43
|
+
.filter(key => key.toLowerCase().includes('key'))
|
|
44
|
+
.forEach(key => {
|
|
45
|
+
delete settings[provider][key];
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
49
|
+
this.componentDidUpdate();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
24
52
|
/**
|
|
25
53
|
* Triggered when the provider hes changed, to update the schema and values.
|
|
26
54
|
* Update the Jupyterlab settings accordingly.
|
|
@@ -39,6 +67,16 @@ export class AiSettings extends React.Component {
|
|
|
39
67
|
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
40
68
|
.catch(console.error);
|
|
41
69
|
};
|
|
70
|
+
/**
|
|
71
|
+
* Callback function called when the password input has been programmatically updated
|
|
72
|
+
* with the secret manager.
|
|
73
|
+
*/
|
|
74
|
+
this._onPasswordUpdated = (fieldName, value) => {
|
|
75
|
+
this._currentSettings[fieldName] = value;
|
|
76
|
+
this._settings
|
|
77
|
+
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
78
|
+
.catch(console.error);
|
|
79
|
+
};
|
|
42
80
|
/**
|
|
43
81
|
* Triggered when the form value has changed, to update the current settings and save
|
|
44
82
|
* it in local storage.
|
|
@@ -53,12 +91,18 @@ export class AiSettings extends React.Component {
|
|
|
53
91
|
};
|
|
54
92
|
this._currentSettings = { provider: 'None' };
|
|
55
93
|
this._uiSchema = {};
|
|
94
|
+
this._formRef = React.createRef();
|
|
95
|
+
this._unsavedFields = [];
|
|
96
|
+
this._formInputs = [];
|
|
56
97
|
if (!props.formContext.providerRegistry) {
|
|
57
98
|
throw new Error('The provider registry is needed to enable the jupyterlite-ai settings panel');
|
|
58
99
|
}
|
|
59
100
|
this._providerRegistry = props.formContext.providerRegistry;
|
|
60
101
|
this._rmRegistry = (_a = props.formContext.rmRegistry) !== null && _a !== void 0 ? _a : null;
|
|
102
|
+
this._secretsManager = (_b = props.formContext.secretsManager) !== null && _b !== void 0 ? _b : null;
|
|
61
103
|
this._settings = props.formContext.settings;
|
|
104
|
+
this._useSecretsManager =
|
|
105
|
+
(_c = this._settings.get('UseSecretsManager').composite) !== null && _c !== void 0 ? _c : true;
|
|
62
106
|
// Initialize the providers schema.
|
|
63
107
|
const providerSchema = JSONExt.deepCopy(baseSettings);
|
|
64
108
|
providerSchema.properties.provider = {
|
|
@@ -76,7 +120,7 @@ export class AiSettings extends React.Component {
|
|
|
76
120
|
const labSettings = this._settings.get('AIprovider').composite;
|
|
77
121
|
if (labSettings && Object.keys(labSettings).includes('provider')) {
|
|
78
122
|
// Get the provider name.
|
|
79
|
-
const provider = (
|
|
123
|
+
const provider = (_d = Object.entries(labSettings).find(v => v[0] === 'provider')) === null || _d === void 0 ? void 0 : _d[1];
|
|
80
124
|
// Save the settings.
|
|
81
125
|
const settings = {
|
|
82
126
|
_current: provider
|
|
@@ -96,6 +140,37 @@ export class AiSettings extends React.Component {
|
|
|
96
140
|
this._settings
|
|
97
141
|
.set('AIprovider', this._currentSettings)
|
|
98
142
|
.catch(console.error);
|
|
143
|
+
this._settings.changed.connect(() => {
|
|
144
|
+
var _a;
|
|
145
|
+
const useSecretsManager = (_a = this._settings.get('UseSecretsManager').composite) !== null && _a !== void 0 ? _a : true;
|
|
146
|
+
if (useSecretsManager !== this._useSecretsManager) {
|
|
147
|
+
this.updateUseSecretsManager(useSecretsManager);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
async componentDidUpdate() {
|
|
152
|
+
var _a;
|
|
153
|
+
if (!this._secretsManager || !this._useSecretsManager) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// Attach the password inputs to the secrets manager only if they have changed.
|
|
157
|
+
const inputs = ((_a = this._formRef.current) === null || _a === void 0 ? void 0 : _a.getElementsByTagName('input')) || [];
|
|
158
|
+
if (ArrayExt.shallowEqual(inputs, this._formInputs)) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
await this._secretsManager.detachAll(SECRETS_NAMESPACE);
|
|
162
|
+
this._formInputs = [...inputs];
|
|
163
|
+
this._unsavedFields = [];
|
|
164
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
165
|
+
if (inputs[i].type.toLowerCase() === 'password') {
|
|
166
|
+
const label = inputs[i].getAttribute('label');
|
|
167
|
+
if (label) {
|
|
168
|
+
const id = `${this._provider}-${label}`;
|
|
169
|
+
this._secretsManager.attach(SECRETS_NAMESPACE, id, inputs[i], (value) => this._onPasswordUpdated(label, value));
|
|
170
|
+
this._unsavedFields.push(label);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
99
174
|
}
|
|
100
175
|
/**
|
|
101
176
|
* Get the current provider from the local storage.
|
|
@@ -126,8 +201,10 @@ export class AiSettings extends React.Component {
|
|
|
126
201
|
*/
|
|
127
202
|
saveSettings(value) {
|
|
128
203
|
var _a;
|
|
204
|
+
const currentSettings = { ...value };
|
|
129
205
|
const settings = JSON.parse((_a = localStorage.getItem(STORAGE_NAME)) !== null && _a !== void 0 ? _a : '{}');
|
|
130
|
-
|
|
206
|
+
this._unsavedFields.forEach(field => delete currentSettings[field]);
|
|
207
|
+
settings[this._provider] = currentSettings;
|
|
131
208
|
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
132
209
|
}
|
|
133
210
|
/**
|
|
@@ -180,7 +257,7 @@ export class AiSettings extends React.Component {
|
|
|
180
257
|
this.setState({ instruction: renderer.node });
|
|
181
258
|
}
|
|
182
259
|
render() {
|
|
183
|
-
return (React.createElement(
|
|
260
|
+
return (React.createElement("div", { ref: this._formRef },
|
|
184
261
|
React.createElement(WrappedFormComponent, { formData: { provider: this._provider }, schema: this._providerSchema, onChange: this._onProviderChanged }),
|
|
185
262
|
this.state.instruction !== null && (React.createElement("details", null,
|
|
186
263
|
React.createElement("summary", { className: INSTRUCTION_CLASS }, "Instructions"),
|
package/lib/tokens.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
|
2
2
|
import { ReadonlyPartialJSONObject, Token } from '@lumino/coreutils';
|
|
3
3
|
import { ISignal } from '@lumino/signaling';
|
|
4
4
|
import { JSONSchema7 } from 'json-schema';
|
|
5
|
-
import { IBaseCompleter } from './
|
|
5
|
+
import { IBaseCompleter } from './base-completer';
|
|
6
6
|
export interface IDict<T = any> {
|
|
7
7
|
[key: string]: T;
|
|
8
8
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlite/ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "AI code completions and chat for JupyterLite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"license": "BSD-3-Clause",
|
|
15
15
|
"author": "JupyterLite Contributors",
|
|
16
16
|
"files": [
|
|
17
|
-
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
|
|
17
|
+
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf,md}",
|
|
18
18
|
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
|
|
19
19
|
"src/**/*.{ts,tsx}",
|
|
20
20
|
"schema/*.json"
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"url": "https://github.com/jupyterlite/ai.git"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
|
-
"build": "
|
|
30
|
+
"build": "jlpm build:lib && jlpm build:labextension:dev",
|
|
31
31
|
"build:dev": "jlpm build:lib && jlpm build:labextension:dev",
|
|
32
|
-
"build:prod": "
|
|
32
|
+
"build:prod": "jlpm settings:build && jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
|
|
33
33
|
"build:labextension": "jupyter labextension build .",
|
|
34
34
|
"build:labextension:dev": "jupyter labextension build --development True .",
|
|
35
35
|
"build:lib": "tsc --sourceMap",
|
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
"prettier": "jlpm prettier:base --write --list-different",
|
|
48
48
|
"prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
|
|
49
49
|
"prettier:check": "jlpm prettier:base --check",
|
|
50
|
+
"settings:build": "node ./scripts/settings-checker.js --generate",
|
|
51
|
+
"settings:check": "node ./scripts/settings-checker.js",
|
|
50
52
|
"stylelint": "jlpm stylelint:check --fix",
|
|
51
53
|
"stylelint:check": "stylelint --cache \"style/**/*.css\"",
|
|
52
54
|
"watch": "run-p watch:src watch:labextension",
|
|
@@ -54,7 +56,7 @@
|
|
|
54
56
|
"watch:labextension": "jupyter labextension watch ."
|
|
55
57
|
},
|
|
56
58
|
"dependencies": {
|
|
57
|
-
"@jupyter/chat": "^0.
|
|
59
|
+
"@jupyter/chat": "^0.8.1",
|
|
58
60
|
"@jupyterlab/application": "^4.4.0-alpha.0",
|
|
59
61
|
"@jupyterlab/apputils": "^4.5.0-alpha.0",
|
|
60
62
|
"@jupyterlab/completer": "^4.4.0-alpha.0",
|
|
@@ -75,6 +77,7 @@
|
|
|
75
77
|
"@rjsf/core": "^4.2.0",
|
|
76
78
|
"@rjsf/utils": "^5.18.4",
|
|
77
79
|
"@rjsf/validator-ajv8": "^5.18.4",
|
|
80
|
+
"jupyter-secrets-manager": "^0.1.1",
|
|
78
81
|
"react": "^18.2.0",
|
|
79
82
|
"react-dom": "^18.2.0"
|
|
80
83
|
},
|