@jupyterlite/ai 0.2.0 → 0.4.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/README.md +48 -9
- package/lib/chat-handler.d.ts +15 -3
- package/lib/chat-handler.js +80 -28
- package/lib/completion-provider.d.ts +5 -18
- package/lib/completion-provider.js +8 -34
- package/lib/icons.d.ts +2 -0
- package/lib/icons.js +15 -0
- package/lib/index.d.ts +3 -2
- package/lib/index.js +79 -22
- package/lib/llm-models/anthropic-completer.d.ts +19 -0
- package/lib/llm-models/anthropic-completer.js +57 -0
- package/lib/llm-models/base-completer.d.ts +6 -2
- package/lib/llm-models/chrome-completer.d.ts +19 -0
- package/lib/llm-models/chrome-completer.js +67 -0
- package/lib/llm-models/codestral-completer.d.ts +9 -8
- package/lib/llm-models/codestral-completer.js +37 -54
- package/lib/llm-models/index.d.ts +3 -2
- package/lib/llm-models/index.js +42 -2
- package/lib/llm-models/openai-completer.d.ts +19 -0
- package/lib/llm-models/openai-completer.js +51 -0
- package/lib/provider.d.ts +54 -15
- package/lib/provider.js +123 -41
- package/lib/settings/instructions.d.ts +2 -0
- package/lib/settings/instructions.js +44 -0
- package/lib/settings/panel.d.ts +70 -0
- package/lib/settings/panel.js +190 -0
- package/lib/settings/schemas/_generated/Anthropic.json +70 -0
- package/lib/settings/schemas/_generated/ChromeAI.json +21 -0
- package/lib/settings/schemas/_generated/MistralAI.json +75 -0
- package/lib/settings/schemas/_generated/OpenAI.json +668 -0
- package/lib/settings/schemas/base.json +7 -0
- package/lib/settings/schemas/index.d.ts +3 -0
- package/lib/settings/schemas/index.js +11 -0
- package/lib/slash-commands.d.ts +16 -0
- package/lib/slash-commands.js +25 -0
- package/lib/tokens.d.ts +103 -0
- package/lib/tokens.js +5 -0
- package/package.json +27 -104
- package/schema/chat.json +8 -0
- package/schema/provider-registry.json +17 -0
- package/src/chat-handler.ts +103 -43
- package/src/completion-provider.ts +13 -37
- package/src/icons.ts +18 -0
- package/src/index.ts +101 -24
- package/src/llm-models/anthropic-completer.ts +75 -0
- package/src/llm-models/base-completer.ts +7 -2
- package/src/llm-models/chrome-completer.ts +88 -0
- package/src/llm-models/codestral-completer.ts +43 -69
- package/src/llm-models/index.ts +49 -2
- package/src/llm-models/openai-completer.ts +67 -0
- package/src/llm-models/svg.d.ts +9 -0
- package/src/provider.ts +138 -43
- package/src/settings/instructions.ts +48 -0
- package/src/settings/panel.tsx +257 -0
- package/src/settings/schemas/index.ts +15 -0
- package/src/slash-commands.tsx +55 -0
- package/src/tokens.ts +112 -0
- package/style/base.css +4 -0
- package/style/icons/jupyternaut-lite.svg +7 -0
- package/lib/llm-models/utils.d.ts +0 -15
- package/lib/llm-models/utils.js +0 -29
- package/lib/token.d.ts +0 -13
- package/lib/token.js +0 -2
- package/schema/ai-provider.json +0 -21
- package/src/llm-models/utils.ts +0 -41
- package/src/token.ts +0 -19
package/README.md
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
[](https://github.com/jupyterlite/ai/actions/workflows/build.yml)
|
|
4
4
|
[](https://jupyterlite.github.io/ai/lab/index.html)
|
|
5
5
|
|
|
6
|
-
AI code completions and chat for JupyterLab, Notebook 7 and JupyterLite
|
|
6
|
+
AI code completions and chat for JupyterLab, Notebook 7 and JupyterLite ✨
|
|
7
7
|
|
|
8
|
-
[a screencast showing the
|
|
8
|
+
[a screencast showing the Jupyterlite AI extension in JupyterLite](https://github.com/jupyterlite/ai/assets/591645/855c4e3e-3a63-4868-8052-5c9909922c21)
|
|
9
9
|
|
|
10
10
|
## Requirements
|
|
11
11
|
|
|
@@ -14,12 +14,7 @@ AI code completions and chat for JupyterLab, Notebook 7 and JupyterLite, powered
|
|
|
14
14
|
> To enable more AI providers in JupyterLab and Jupyter Notebook, we recommend using the [Jupyter AI](https://github.com/jupyterlab/jupyter-ai) extension directly.
|
|
15
15
|
> At the moment Jupyter AI is not compatible with JupyterLite, but might be to some extent in the future.
|
|
16
16
|
|
|
17
|
-
- JupyterLab >= 4.
|
|
18
|
-
|
|
19
|
-
> [!WARNING]
|
|
20
|
-
> This extension is still very much experimental. It is not an official MistralAI extension.
|
|
21
|
-
> It is exploring the integration of the MistralAI API with JupyterLab, which can also be used in [JupyterLite](https://jupyterlite.readthedocs.io/).
|
|
22
|
-
> For a more complete AI extension for JupyterLab, see [Jupyter AI](https://github.com/jupyterlab/jupyter-ai).
|
|
17
|
+
- JupyterLab >= 4.4.0a0 or Notebook >= 7.4.0a0
|
|
23
18
|
|
|
24
19
|
## ✨ Try it in your browser ✨
|
|
25
20
|
|
|
@@ -37,13 +32,29 @@ To install the extension, execute:
|
|
|
37
32
|
pip install jupyterlite-ai
|
|
38
33
|
```
|
|
39
34
|
|
|
35
|
+
To install requirements (jupyterlab, jupyterlite and notebook), there is an optional dependencies argument:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install jupyterlite-ai[jupyter]
|
|
39
|
+
```
|
|
40
|
+
|
|
40
41
|
# Usage
|
|
41
42
|
|
|
43
|
+
AI providers typically require using an API key to access their models.
|
|
44
|
+
|
|
45
|
+
The process is different for each provider, so you may refer to their documentation to learn how to generate new API keys, if they are not covered in the sections below.
|
|
46
|
+
|
|
47
|
+
## Using MistralAI
|
|
48
|
+
|
|
49
|
+
> [!WARNING]
|
|
50
|
+
> This extension is still very much experimental. It is not an official MistralAI extension.
|
|
51
|
+
|
|
42
52
|
1. Go to https://console.mistral.ai/api-keys/ and create an API key.
|
|
43
53
|
|
|
44
54
|

|
|
45
55
|
|
|
46
|
-
2. Open the JupyterLab settings and go to the
|
|
56
|
+
2. Open the JupyterLab settings and go to the **Ai providers** section to select the `MistralAI`
|
|
57
|
+
provider and the API key (required).
|
|
47
58
|
|
|
48
59
|

|
|
49
60
|
|
|
@@ -51,6 +62,34 @@ pip install jupyterlite-ai
|
|
|
51
62
|
|
|
52
63
|

|
|
53
64
|
|
|
65
|
+
## Using ChromeAI
|
|
66
|
+
|
|
67
|
+
> [!WARNING]
|
|
68
|
+
> Support for ChromeAI is still experimental and only available in Google Chrome.
|
|
69
|
+
|
|
70
|
+
You can test ChromeAI is enabled in your browser by going to the following URL: https://chromeai.org/
|
|
71
|
+
|
|
72
|
+
Enable the proper flags in Google Chrome.
|
|
73
|
+
|
|
74
|
+
- chrome://flags/#prompt-api-for-gemini-nano
|
|
75
|
+
- Select: `Enabled`
|
|
76
|
+
- chrome://flags/#optimization-guide-on-device-model
|
|
77
|
+
- Select: `Enabled BypassPrefRequirement`
|
|
78
|
+
- chrome://components
|
|
79
|
+
- Click `Check for Update` on Optimization Guide On Device Model to download the model
|
|
80
|
+
- [Optional] chrome://flags/#text-safety-classifier
|
|
81
|
+
|
|
82
|
+

|
|
83
|
+
|
|
84
|
+
Then restart Chrome for these changes to take effect.
|
|
85
|
+
|
|
86
|
+
> [!WARNING]
|
|
87
|
+
> 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).
|
|
88
|
+
> During the download, ChromeAI may not be available via the extension.
|
|
89
|
+
|
|
90
|
+
> [!NOTE]
|
|
91
|
+
> For more information about Chrome Built-in AI: https://developer.chrome.com/docs/ai/get-started
|
|
92
|
+
|
|
54
93
|
## Uninstall
|
|
55
94
|
|
|
56
95
|
To remove the extension, execute:
|
package/lib/chat-handler.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChatModel, IChatHistory, IChatMessage, INewMessage } from '@jupyter/chat';
|
|
2
2
|
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
3
|
-
import {
|
|
3
|
+
import { IAIProviderRegistry } from './tokens';
|
|
4
4
|
export type ConnectionMessage = {
|
|
5
5
|
type: 'connection';
|
|
6
6
|
client_id: string;
|
|
@@ -8,17 +8,29 @@ export type ConnectionMessage = {
|
|
|
8
8
|
export declare class ChatHandler extends ChatModel {
|
|
9
9
|
constructor(options: ChatHandler.IOptions);
|
|
10
10
|
get provider(): BaseChatModel | null;
|
|
11
|
+
/**
|
|
12
|
+
* Getter and setter for the persona name.
|
|
13
|
+
*/
|
|
14
|
+
get personaName(): string;
|
|
15
|
+
set personaName(value: string);
|
|
16
|
+
/**
|
|
17
|
+
* Getter and setter for the initial prompt.
|
|
18
|
+
*/
|
|
19
|
+
get prompt(): string;
|
|
20
|
+
set prompt(value: string);
|
|
11
21
|
sendMessage(message: INewMessage): Promise<boolean>;
|
|
12
22
|
getHistory(): Promise<IChatHistory>;
|
|
13
23
|
dispose(): void;
|
|
14
24
|
messageAdded(message: IChatMessage): void;
|
|
15
|
-
private
|
|
25
|
+
private _providerRegistry;
|
|
26
|
+
private _personaName;
|
|
27
|
+
private _prompt;
|
|
16
28
|
private _errorMessage;
|
|
17
29
|
private _history;
|
|
18
30
|
private _defaultErrorMessage;
|
|
19
31
|
}
|
|
20
32
|
export declare namespace ChatHandler {
|
|
21
33
|
interface IOptions extends ChatModel.IOptions {
|
|
22
|
-
|
|
34
|
+
providerRegistry: IAIProviderRegistry;
|
|
23
35
|
}
|
|
24
36
|
}
|
package/lib/chat-handler.js
CHANGED
|
@@ -3,34 +3,81 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
import { ChatModel } from '@jupyter/chat';
|
|
6
|
-
import { AIMessage, HumanMessage, mergeMessageRuns } from '@langchain/core/messages';
|
|
6
|
+
import { AIMessage, HumanMessage, mergeMessageRuns, SystemMessage } from '@langchain/core/messages';
|
|
7
7
|
import { UUID } from '@lumino/coreutils';
|
|
8
|
-
import {
|
|
8
|
+
import { chatSystemPrompt } from './provider';
|
|
9
|
+
import { jupyternautLiteIcon } from './icons';
|
|
10
|
+
/**
|
|
11
|
+
* The base64 encoded SVG string of the jupyternaut lite icon.
|
|
12
|
+
* Encode so it can be passed as avatar_url to jupyter-chat.
|
|
13
|
+
*/
|
|
14
|
+
const AI_AVATAR_BASE64 = btoa(jupyternautLiteIcon.svgstr);
|
|
15
|
+
const AI_AVATAR = `data:image/svg+xml;base64,${AI_AVATAR_BASE64}`;
|
|
9
16
|
export class ChatHandler extends ChatModel {
|
|
10
17
|
constructor(options) {
|
|
11
18
|
super(options);
|
|
19
|
+
this._personaName = 'AI';
|
|
12
20
|
this._errorMessage = '';
|
|
13
21
|
this._history = { messages: [] };
|
|
14
22
|
this._defaultErrorMessage = 'AI provider not configured';
|
|
15
|
-
this.
|
|
16
|
-
this.
|
|
17
|
-
|
|
23
|
+
this._providerRegistry = options.providerRegistry;
|
|
24
|
+
this._prompt = chatSystemPrompt({
|
|
25
|
+
provider_name: this._providerRegistry.currentName
|
|
26
|
+
});
|
|
27
|
+
this._providerRegistry.providerChanged.connect(() => {
|
|
28
|
+
this._errorMessage = this._providerRegistry.chatError;
|
|
29
|
+
this._prompt = chatSystemPrompt({
|
|
30
|
+
provider_name: this._providerRegistry.currentName
|
|
31
|
+
});
|
|
18
32
|
});
|
|
19
33
|
}
|
|
20
34
|
get provider() {
|
|
21
|
-
return this.
|
|
35
|
+
return this._providerRegistry.currentChatModel;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Getter and setter for the persona name.
|
|
39
|
+
*/
|
|
40
|
+
get personaName() {
|
|
41
|
+
return this._personaName;
|
|
42
|
+
}
|
|
43
|
+
set personaName(value) {
|
|
44
|
+
this.messages.forEach(message => {
|
|
45
|
+
if (message.sender.username === this._personaName) {
|
|
46
|
+
const updated = { ...message };
|
|
47
|
+
updated.sender.username = value;
|
|
48
|
+
this.messageAdded(updated);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
this._personaName = value;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Getter and setter for the initial prompt.
|
|
55
|
+
*/
|
|
56
|
+
get prompt() {
|
|
57
|
+
return this._prompt;
|
|
58
|
+
}
|
|
59
|
+
set prompt(value) {
|
|
60
|
+
this._prompt = value;
|
|
22
61
|
}
|
|
23
62
|
async sendMessage(message) {
|
|
63
|
+
var _a;
|
|
64
|
+
const body = message.body;
|
|
65
|
+
if (body.startsWith('/clear')) {
|
|
66
|
+
// TODO: do we need a clear method?
|
|
67
|
+
this.messagesDeleted(0, this.messages.length);
|
|
68
|
+
this._history.messages = [];
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
24
71
|
message.id = UUID.uuid4();
|
|
25
72
|
const msg = {
|
|
26
73
|
id: message.id,
|
|
27
|
-
body
|
|
74
|
+
body,
|
|
28
75
|
sender: { username: 'User' },
|
|
29
76
|
time: Date.now(),
|
|
30
77
|
type: 'msg'
|
|
31
78
|
};
|
|
32
79
|
this.messageAdded(msg);
|
|
33
|
-
if (this.
|
|
80
|
+
if (this._providerRegistry.currentChatModel === null) {
|
|
34
81
|
const errorMsg = {
|
|
35
82
|
id: UUID.uuid4(),
|
|
36
83
|
body: `**${this._errorMessage ? this._errorMessage : this._defaultErrorMessage}**`,
|
|
@@ -42,30 +89,35 @@ export class ChatHandler extends ChatModel {
|
|
|
42
89
|
return false;
|
|
43
90
|
}
|
|
44
91
|
this._history.messages.push(msg);
|
|
45
|
-
const messages = mergeMessageRuns(this.
|
|
92
|
+
const messages = mergeMessageRuns([new SystemMessage(this._prompt)]);
|
|
93
|
+
messages.push(...this._history.messages.map(msg => {
|
|
46
94
|
if (msg.sender.username === 'User') {
|
|
47
95
|
return new HumanMessage(msg.body);
|
|
48
96
|
}
|
|
49
97
|
return new AIMessage(msg.body);
|
|
50
98
|
}));
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
this.
|
|
99
|
+
const sender = { username: this._personaName, avatar_url: AI_AVATAR };
|
|
100
|
+
this.updateWriters([sender]);
|
|
101
|
+
// create an empty message to be filled by the AI provider
|
|
102
|
+
const botMsg = {
|
|
103
|
+
id: UUID.uuid4(),
|
|
104
|
+
body: '',
|
|
105
|
+
sender,
|
|
106
|
+
time: Date.now(),
|
|
107
|
+
type: 'msg'
|
|
108
|
+
};
|
|
109
|
+
let content = '';
|
|
110
|
+
try {
|
|
111
|
+
for await (const chunk of await this._providerRegistry.currentChatModel.stream(messages)) {
|
|
112
|
+
content += (_a = chunk.content) !== null && _a !== void 0 ? _a : chunk;
|
|
113
|
+
botMsg.body = content;
|
|
114
|
+
this.messageAdded(botMsg);
|
|
115
|
+
}
|
|
64
116
|
this._history.messages.push(botMsg);
|
|
65
117
|
return true;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const error =
|
|
118
|
+
}
|
|
119
|
+
catch (reason) {
|
|
120
|
+
const error = this._providerRegistry.formatErrorMessage(reason);
|
|
69
121
|
const errorMsg = {
|
|
70
122
|
id: UUID.uuid4(),
|
|
71
123
|
body: `**${error}**`,
|
|
@@ -75,10 +127,10 @@ export class ChatHandler extends ChatModel {
|
|
|
75
127
|
};
|
|
76
128
|
this.messageAdded(errorMsg);
|
|
77
129
|
return false;
|
|
78
|
-
}
|
|
79
|
-
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
80
132
|
this.updateWriters([]);
|
|
81
|
-
}
|
|
133
|
+
}
|
|
82
134
|
}
|
|
83
135
|
async getHistory() {
|
|
84
136
|
return this._history;
|
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import { CompletionHandler, IInlineCompletionContext, IInlineCompletionProvider } from '@jupyterlab/completer';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
2
|
+
import { IBaseCompleter } from './llm-models';
|
|
3
|
+
import { IAIProviderRegistry } from './tokens';
|
|
5
4
|
/**
|
|
6
5
|
* The generic completion provider to register to the completion provider manager.
|
|
7
6
|
*/
|
|
8
7
|
export declare class CompletionProvider implements IInlineCompletionProvider {
|
|
9
8
|
readonly identifier = "@jupyterlite/ai";
|
|
10
9
|
constructor(options: CompletionProvider.IOptions);
|
|
11
|
-
/**
|
|
12
|
-
* Set the completer.
|
|
13
|
-
*
|
|
14
|
-
* @param name - the name of the completer.
|
|
15
|
-
* @param settings - The settings associated to the completer.
|
|
16
|
-
*/
|
|
17
|
-
setCompleter(name: string, settings: ReadonlyPartialJSONObject): void;
|
|
18
10
|
/**
|
|
19
11
|
* Get the current completer name.
|
|
20
12
|
*/
|
|
@@ -23,18 +15,13 @@ export declare class CompletionProvider implements IInlineCompletionProvider {
|
|
|
23
15
|
* Get the current completer.
|
|
24
16
|
*/
|
|
25
17
|
get completer(): IBaseCompleter | null;
|
|
26
|
-
/**
|
|
27
|
-
* Get the LLM completer.
|
|
28
|
-
*/
|
|
29
|
-
get llmCompleter(): LLM | null;
|
|
30
18
|
fetch(request: CompletionHandler.IRequest, context: IInlineCompletionContext): Promise<any>;
|
|
31
|
-
private
|
|
19
|
+
private _providerRegistry;
|
|
32
20
|
private _requestCompletion;
|
|
33
|
-
private _completer;
|
|
34
21
|
}
|
|
35
22
|
export declare namespace CompletionProvider {
|
|
36
|
-
interface IOptions
|
|
37
|
-
|
|
23
|
+
interface IOptions {
|
|
24
|
+
providerRegistry: IAIProviderRegistry;
|
|
38
25
|
requestCompletion: () => void;
|
|
39
26
|
}
|
|
40
27
|
}
|
|
@@ -1,57 +1,31 @@
|
|
|
1
|
-
import { getCompleter } from './llm-models';
|
|
2
1
|
/**
|
|
3
2
|
* The generic completion provider to register to the completion provider manager.
|
|
4
3
|
*/
|
|
5
4
|
export class CompletionProvider {
|
|
6
5
|
constructor(options) {
|
|
7
6
|
this.identifier = '@jupyterlite/ai';
|
|
8
|
-
this.
|
|
9
|
-
this._completer = null;
|
|
10
|
-
const { name, settings } = options;
|
|
7
|
+
this._providerRegistry = options.providerRegistry;
|
|
11
8
|
this._requestCompletion = options.requestCompletion;
|
|
12
|
-
this.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* Set the completer.
|
|
16
|
-
*
|
|
17
|
-
* @param name - the name of the completer.
|
|
18
|
-
* @param settings - The settings associated to the completer.
|
|
19
|
-
*/
|
|
20
|
-
setCompleter(name, settings) {
|
|
21
|
-
try {
|
|
22
|
-
this._completer = getCompleter(name, settings);
|
|
23
|
-
if (this._completer) {
|
|
24
|
-
this._completer.requestCompletion = this._requestCompletion;
|
|
9
|
+
this._providerRegistry.providerChanged.connect(() => {
|
|
10
|
+
if (this.completer) {
|
|
11
|
+
this.completer.requestCompletion = this._requestCompletion;
|
|
25
12
|
}
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
catch (e) {
|
|
29
|
-
this._completer = null;
|
|
30
|
-
this._name = 'None';
|
|
31
|
-
throw e;
|
|
32
|
-
}
|
|
13
|
+
});
|
|
33
14
|
}
|
|
34
15
|
/**
|
|
35
16
|
* Get the current completer name.
|
|
36
17
|
*/
|
|
37
18
|
get name() {
|
|
38
|
-
return this.
|
|
19
|
+
return this._providerRegistry.currentName;
|
|
39
20
|
}
|
|
40
21
|
/**
|
|
41
22
|
* Get the current completer.
|
|
42
23
|
*/
|
|
43
24
|
get completer() {
|
|
44
|
-
return this.
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Get the LLM completer.
|
|
48
|
-
*/
|
|
49
|
-
get llmCompleter() {
|
|
50
|
-
var _a;
|
|
51
|
-
return ((_a = this._completer) === null || _a === void 0 ? void 0 : _a.provider) || null;
|
|
25
|
+
return this._providerRegistry.currentCompleter;
|
|
52
26
|
}
|
|
53
27
|
async fetch(request, context) {
|
|
54
28
|
var _a;
|
|
55
|
-
return (_a = this.
|
|
29
|
+
return (_a = this.completer) === null || _a === void 0 ? void 0 : _a.fetch(request, context);
|
|
56
30
|
}
|
|
57
31
|
}
|
package/lib/icons.d.ts
ADDED
package/lib/icons.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
6
|
+
/**
|
|
7
|
+
* This icon is based on the jupyternaut icon from Jupyter AI:
|
|
8
|
+
* https://github.com/jupyterlab/jupyter-ai/blob/main/packages/jupyter-ai/style/icons/jupyternaut.svg
|
|
9
|
+
* With a small tweak for the colors to match the JupyterLite icon.
|
|
10
|
+
*/
|
|
11
|
+
import jupyternautLiteSvg from '../style/icons/jupyternaut-lite.svg';
|
|
12
|
+
export const jupyternautLiteIcon = new LabIcon({
|
|
13
|
+
name: '@jupyterlite/ai:jupyternaut-lite',
|
|
14
|
+
svgstr: jupyternautLiteSvg
|
|
15
|
+
});
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import { IAutocompletionRegistry } from '@jupyter/chat';
|
|
1
2
|
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
2
|
-
import {
|
|
3
|
-
declare const _default: (JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<
|
|
3
|
+
import { IAIProviderRegistry } from './tokens';
|
|
4
|
+
declare const _default: (JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<IAutocompletionRegistry> | JupyterFrontEndPlugin<IAIProviderRegistry>)[];
|
|
4
5
|
export default _default;
|
package/lib/index.js
CHANGED
|
@@ -1,19 +1,49 @@
|
|
|
1
|
-
import { ActiveCellManager, buildChatSidebar, buildErrorWidget } from '@jupyter/chat';
|
|
1
|
+
import { ActiveCellManager, AutocompletionRegistry, buildChatSidebar, buildErrorWidget, IAutocompletionRegistry } 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
|
+
import { IFormRendererRegistry } from '@jupyterlab/ui-components';
|
|
7
8
|
import { ChatHandler } from './chat-handler';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
9
|
+
import { CompletionProvider } from './completion-provider';
|
|
10
|
+
import { AIProviders } from './llm-models';
|
|
11
|
+
import { AIProviderRegistry } from './provider';
|
|
12
|
+
import { aiSettingsRenderer } from './settings/panel';
|
|
13
|
+
import { renderSlashCommandOption } from './slash-commands';
|
|
14
|
+
import { IAIProviderRegistry } from './tokens';
|
|
15
|
+
const autocompletionRegistryPlugin = {
|
|
16
|
+
id: '@jupyterlite/ai:autocompletion-registry',
|
|
17
|
+
description: 'Autocompletion registry',
|
|
18
|
+
autoStart: true,
|
|
19
|
+
provides: IAutocompletionRegistry,
|
|
20
|
+
activate: () => {
|
|
21
|
+
const autocompletionRegistry = new AutocompletionRegistry();
|
|
22
|
+
const options = ['/clear'];
|
|
23
|
+
const autocompletionCommands = {
|
|
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;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
10
40
|
const chatPlugin = {
|
|
11
41
|
id: '@jupyterlite/ai:chat',
|
|
12
42
|
description: 'LLM chat extension',
|
|
13
43
|
autoStart: true,
|
|
44
|
+
requires: [IAIProviderRegistry, IRenderMimeRegistry, IAutocompletionRegistry],
|
|
14
45
|
optional: [INotebookTracker, ISettingRegistry, IThemeManager],
|
|
15
|
-
|
|
16
|
-
activate: async (app, aiProvider, rmRegistry, notebookTracker, settingsRegistry, themeManager) => {
|
|
46
|
+
activate: async (app, providerRegistry, rmRegistry, autocompletionRegistry, notebookTracker, settingsRegistry, themeManager) => {
|
|
17
47
|
let activeCellManager = null;
|
|
18
48
|
if (notebookTracker) {
|
|
19
49
|
activeCellManager = new ActiveCellManager({
|
|
@@ -22,16 +52,20 @@ const chatPlugin = {
|
|
|
22
52
|
});
|
|
23
53
|
}
|
|
24
54
|
const chatHandler = new ChatHandler({
|
|
25
|
-
|
|
26
|
-
activeCellManager
|
|
55
|
+
providerRegistry,
|
|
56
|
+
activeCellManager
|
|
27
57
|
});
|
|
28
58
|
let sendWithShiftEnter = false;
|
|
29
59
|
let enableCodeToolbar = true;
|
|
60
|
+
let personaName = 'AI';
|
|
30
61
|
function loadSetting(setting) {
|
|
31
62
|
sendWithShiftEnter = setting.get('sendWithShiftEnter')
|
|
32
63
|
.composite;
|
|
33
64
|
enableCodeToolbar = setting.get('enableCodeToolbar').composite;
|
|
65
|
+
personaName = setting.get('personaName').composite;
|
|
66
|
+
// set the properties
|
|
34
67
|
chatHandler.config = { sendWithShiftEnter, enableCodeToolbar };
|
|
68
|
+
chatHandler.personaName = personaName;
|
|
35
69
|
}
|
|
36
70
|
Promise.all([app.restored, settingsRegistry === null || settingsRegistry === void 0 ? void 0 : settingsRegistry.load(chatPlugin.id)])
|
|
37
71
|
.then(([, settings]) => {
|
|
@@ -50,9 +84,10 @@ const chatPlugin = {
|
|
|
50
84
|
chatWidget = buildChatSidebar({
|
|
51
85
|
model: chatHandler,
|
|
52
86
|
themeManager,
|
|
53
|
-
rmRegistry
|
|
87
|
+
rmRegistry,
|
|
88
|
+
autocompletionRegistry
|
|
54
89
|
});
|
|
55
|
-
chatWidget.title.caption = '
|
|
90
|
+
chatWidget.title.caption = 'Jupyterlite AI Chat';
|
|
56
91
|
}
|
|
57
92
|
catch (e) {
|
|
58
93
|
chatWidget = buildErrorWidget(themeManager);
|
|
@@ -61,30 +96,52 @@ const chatPlugin = {
|
|
|
61
96
|
console.log('Chat extension initialized');
|
|
62
97
|
}
|
|
63
98
|
};
|
|
64
|
-
const
|
|
65
|
-
id: '@jupyterlite/ai:
|
|
99
|
+
const completerPlugin = {
|
|
100
|
+
id: '@jupyterlite/ai:completer',
|
|
66
101
|
autoStart: true,
|
|
67
|
-
requires: [
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
completionProviderManager: manager,
|
|
102
|
+
requires: [IAIProviderRegistry, ICompletionProviderManager],
|
|
103
|
+
activate: (app, providerRegistry, manager) => {
|
|
104
|
+
const completer = new CompletionProvider({
|
|
105
|
+
providerRegistry,
|
|
72
106
|
requestCompletion: () => app.commands.execute('inline-completer:invoke')
|
|
73
107
|
});
|
|
108
|
+
manager.registerInlineProvider(completer);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const providerRegistryPlugin = {
|
|
112
|
+
id: '@jupyterlite/ai:provider-registry',
|
|
113
|
+
autoStart: true,
|
|
114
|
+
requires: [IFormRendererRegistry, ISettingRegistry],
|
|
115
|
+
optional: [IRenderMimeRegistry],
|
|
116
|
+
provides: IAIProviderRegistry,
|
|
117
|
+
activate: (app, editorRegistry, settingRegistry, rmRegistry) => {
|
|
118
|
+
const providerRegistry = new AIProviderRegistry();
|
|
119
|
+
editorRegistry.addRenderer('@jupyterlite/ai:provider-registry.AIprovider', aiSettingsRenderer({ providerRegistry, rmRegistry }));
|
|
74
120
|
settingRegistry
|
|
75
|
-
.load(
|
|
121
|
+
.load(providerRegistryPlugin.id)
|
|
76
122
|
.then(settings => {
|
|
77
123
|
const updateProvider = () => {
|
|
78
|
-
|
|
79
|
-
|
|
124
|
+
var _a;
|
|
125
|
+
// Update the settings to the AI providers.
|
|
126
|
+
const providerSettings = ((_a = settings.get('AIprovider').composite) !== null && _a !== void 0 ? _a : {
|
|
127
|
+
provider: 'None'
|
|
128
|
+
});
|
|
129
|
+
providerRegistry.setProvider(providerSettings.provider, providerSettings);
|
|
80
130
|
};
|
|
81
131
|
settings.changed.connect(() => updateProvider());
|
|
82
132
|
updateProvider();
|
|
83
133
|
})
|
|
84
134
|
.catch(reason => {
|
|
85
|
-
console.error(`Failed to load settings for ${
|
|
135
|
+
console.error(`Failed to load settings for ${providerRegistryPlugin.id}`, reason);
|
|
86
136
|
});
|
|
87
|
-
|
|
137
|
+
// Initialize the registry with the default providers
|
|
138
|
+
AIProviders.forEach(provider => providerRegistry.add(provider));
|
|
139
|
+
return providerRegistry;
|
|
88
140
|
}
|
|
89
141
|
};
|
|
90
|
-
export default [
|
|
142
|
+
export default [
|
|
143
|
+
providerRegistryPlugin,
|
|
144
|
+
autocompletionRegistryPlugin,
|
|
145
|
+
chatPlugin,
|
|
146
|
+
completerPlugin
|
|
147
|
+
];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CompletionHandler, IInlineCompletionContext } from '@jupyterlab/completer';
|
|
2
|
+
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
3
|
+
import { BaseCompleter, IBaseCompleter } from './base-completer';
|
|
4
|
+
export declare class AnthropicCompleter implements IBaseCompleter {
|
|
5
|
+
constructor(options: BaseCompleter.IOptions);
|
|
6
|
+
get provider(): BaseChatModel;
|
|
7
|
+
/**
|
|
8
|
+
* Getter and setter for the initial prompt.
|
|
9
|
+
*/
|
|
10
|
+
get prompt(): string;
|
|
11
|
+
set prompt(value: string);
|
|
12
|
+
fetch(request: CompletionHandler.IRequest, context: IInlineCompletionContext): Promise<{
|
|
13
|
+
items: {
|
|
14
|
+
insertText: string;
|
|
15
|
+
}[];
|
|
16
|
+
}>;
|
|
17
|
+
private _anthropicProvider;
|
|
18
|
+
private _prompt;
|
|
19
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ChatAnthropic } from '@langchain/anthropic';
|
|
2
|
+
import { AIMessage, SystemMessage } from '@langchain/core/messages';
|
|
3
|
+
import { COMPLETION_SYSTEM_PROMPT } from '../provider';
|
|
4
|
+
export class AnthropicCompleter {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this._prompt = COMPLETION_SYSTEM_PROMPT;
|
|
7
|
+
this._anthropicProvider = new ChatAnthropic({ ...options.settings });
|
|
8
|
+
}
|
|
9
|
+
get provider() {
|
|
10
|
+
return this._anthropicProvider;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Getter and setter for the initial prompt.
|
|
14
|
+
*/
|
|
15
|
+
get prompt() {
|
|
16
|
+
return this._prompt;
|
|
17
|
+
}
|
|
18
|
+
set prompt(value) {
|
|
19
|
+
this._prompt = value;
|
|
20
|
+
}
|
|
21
|
+
async fetch(request, context) {
|
|
22
|
+
const { text, offset: cursorOffset } = request;
|
|
23
|
+
const prompt = text.slice(0, cursorOffset);
|
|
24
|
+
// Anthropic does not allow whitespace at the end of the AIMessage
|
|
25
|
+
const trimmedPrompt = prompt.trim();
|
|
26
|
+
const messages = [
|
|
27
|
+
new SystemMessage(this._prompt),
|
|
28
|
+
new AIMessage(trimmedPrompt)
|
|
29
|
+
];
|
|
30
|
+
try {
|
|
31
|
+
const response = await this._anthropicProvider.invoke(messages);
|
|
32
|
+
const items = [];
|
|
33
|
+
// Anthropic can return string or complex content, a list of string/images/other.
|
|
34
|
+
if (typeof response.content === 'string') {
|
|
35
|
+
items.push({
|
|
36
|
+
insertText: response.content
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
response.content.forEach(content => {
|
|
41
|
+
if (content.type !== 'text') {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
items.push({
|
|
45
|
+
insertText: content.text,
|
|
46
|
+
filterText: prompt.substring(trimmedPrompt.length)
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return { items };
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error('Error fetching completions', error);
|
|
54
|
+
return { items: [] };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|