@jupyterlite/ai 0.2.0 → 0.3.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/_provider-settings/anthropic.json +70 -0
- package/lib/_provider-settings/chromeAI.json +21 -0
- package/lib/_provider-settings/mistralAI.json +75 -0
- package/lib/_provider-settings/openAI.json +668 -0
- package/lib/chat-handler.d.ts +12 -0
- package/lib/chat-handler.js +70 -21
- package/lib/completion-provider.d.ts +3 -3
- package/lib/icons.d.ts +2 -0
- package/lib/icons.js +15 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +61 -6
- 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/openai-completer.d.ts +19 -0
- package/lib/llm-models/openai-completer.js +51 -0
- package/lib/llm-models/utils.d.ts +1 -0
- package/lib/llm-models/utils.js +57 -0
- package/lib/provider.d.ts +11 -0
- package/lib/provider.js +26 -0
- package/lib/slash-commands.d.ts +16 -0
- package/lib/slash-commands.js +25 -0
- package/package.json +23 -104
- package/schema/ai-provider.json +4 -8
- package/schema/chat.json +8 -0
- package/src/chat-handler.ts +91 -34
- package/src/completion-provider.ts +3 -3
- package/src/icons.ts +18 -0
- package/src/index.ts +67 -5
- 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/openai-completer.ts +67 -0
- package/src/llm-models/svg.d.ts +9 -0
- package/src/llm-models/utils.ts +49 -0
- package/src/provider.ts +38 -0
- package/src/slash-commands.tsx +55 -0
- package/style/icons/jupyternaut-lite.svg +7 -0
|
@@ -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 OpenAICompleter 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 _openAIProvider;
|
|
18
|
+
private _prompt;
|
|
19
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AIMessage, SystemMessage } from '@langchain/core/messages';
|
|
2
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
3
|
+
import { COMPLETION_SYSTEM_PROMPT } from '../provider';
|
|
4
|
+
export class OpenAICompleter {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this._prompt = COMPLETION_SYSTEM_PROMPT;
|
|
7
|
+
this._openAIProvider = new ChatOpenAI({ ...options.settings });
|
|
8
|
+
}
|
|
9
|
+
get provider() {
|
|
10
|
+
return this._openAIProvider;
|
|
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
|
+
const messages = [new SystemMessage(this._prompt), new AIMessage(prompt)];
|
|
25
|
+
try {
|
|
26
|
+
const response = await this._openAIProvider.invoke(messages);
|
|
27
|
+
const items = [];
|
|
28
|
+
if (typeof response.content === 'string') {
|
|
29
|
+
items.push({
|
|
30
|
+
insertText: response.content
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
response.content.forEach(content => {
|
|
35
|
+
if (content.type !== 'text') {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
items.push({
|
|
39
|
+
insertText: content.text,
|
|
40
|
+
filterText: prompt.substring(prompt.length)
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return { items };
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error('Error fetching completions', error);
|
|
48
|
+
return { items: [] };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
package/lib/llm-models/utils.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
import { ChatAnthropic } from '@langchain/anthropic';
|
|
2
|
+
import { ChromeAI } from '@langchain/community/experimental/llms/chrome_ai';
|
|
1
3
|
import { ChatMistralAI } from '@langchain/mistralai';
|
|
4
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
5
|
+
import { AnthropicCompleter } from './anthropic-completer';
|
|
2
6
|
import { CodestralCompleter } from './codestral-completer';
|
|
7
|
+
import { ChromeCompleter } from './chrome-completer';
|
|
8
|
+
import { OpenAICompleter } from './openai-completer';
|
|
9
|
+
import chromeAI from '../_provider-settings/chromeAI.json';
|
|
10
|
+
import mistralAI from '../_provider-settings/mistralAI.json';
|
|
11
|
+
import anthropic from '../_provider-settings/anthropic.json';
|
|
12
|
+
import openAI from '../_provider-settings/openAI.json';
|
|
3
13
|
/**
|
|
4
14
|
* Get an LLM completer from the name.
|
|
5
15
|
*/
|
|
@@ -7,6 +17,15 @@ export function getCompleter(name, settings) {
|
|
|
7
17
|
if (name === 'MistralAI') {
|
|
8
18
|
return new CodestralCompleter({ settings });
|
|
9
19
|
}
|
|
20
|
+
else if (name === 'Anthropic') {
|
|
21
|
+
return new AnthropicCompleter({ settings });
|
|
22
|
+
}
|
|
23
|
+
else if (name === 'ChromeAI') {
|
|
24
|
+
return new ChromeCompleter({ settings });
|
|
25
|
+
}
|
|
26
|
+
else if (name === 'OpenAI') {
|
|
27
|
+
return new OpenAICompleter({ settings });
|
|
28
|
+
}
|
|
10
29
|
return null;
|
|
11
30
|
}
|
|
12
31
|
/**
|
|
@@ -16,6 +35,17 @@ export function getChatModel(name, settings) {
|
|
|
16
35
|
if (name === 'MistralAI') {
|
|
17
36
|
return new ChatMistralAI({ ...settings });
|
|
18
37
|
}
|
|
38
|
+
else if (name === 'Anthropic') {
|
|
39
|
+
return new ChatAnthropic({ ...settings });
|
|
40
|
+
}
|
|
41
|
+
else if (name === 'ChromeAI') {
|
|
42
|
+
// TODO: fix
|
|
43
|
+
// @ts-expect-error: missing properties
|
|
44
|
+
return new ChromeAI({ ...settings });
|
|
45
|
+
}
|
|
46
|
+
else if (name === 'OpenAI') {
|
|
47
|
+
return new ChatOpenAI({ ...settings });
|
|
48
|
+
}
|
|
19
49
|
return null;
|
|
20
50
|
}
|
|
21
51
|
/**
|
|
@@ -25,5 +55,32 @@ export function getErrorMessage(name, error) {
|
|
|
25
55
|
if (name === 'MistralAI') {
|
|
26
56
|
return error.message;
|
|
27
57
|
}
|
|
58
|
+
else if (name === 'Anthropic') {
|
|
59
|
+
return error.error.error.message;
|
|
60
|
+
}
|
|
61
|
+
else if (name === 'ChromeAI') {
|
|
62
|
+
return error.message;
|
|
63
|
+
}
|
|
64
|
+
else if (name === 'OpenAI') {
|
|
65
|
+
return error.message;
|
|
66
|
+
}
|
|
28
67
|
return 'Unknown provider';
|
|
29
68
|
}
|
|
69
|
+
/*
|
|
70
|
+
* Get an LLM completer from the name.
|
|
71
|
+
*/
|
|
72
|
+
export function getSettings(name) {
|
|
73
|
+
if (name === 'MistralAI') {
|
|
74
|
+
return mistralAI.properties;
|
|
75
|
+
}
|
|
76
|
+
else if (name === 'Anthropic') {
|
|
77
|
+
return anthropic.properties;
|
|
78
|
+
}
|
|
79
|
+
else if (name === 'ChromeAI') {
|
|
80
|
+
return chromeAI.properties;
|
|
81
|
+
}
|
|
82
|
+
else if (name === 'OpenAI') {
|
|
83
|
+
return openAI.properties;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
package/lib/provider.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { ISignal } from '@lumino/signaling';
|
|
|
5
5
|
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
6
6
|
import { IBaseCompleter } from './llm-models';
|
|
7
7
|
import { IAIProvider } from './token';
|
|
8
|
+
export declare const chatSystemPrompt: (options: AIProvider.IPromptOptions) => string;
|
|
9
|
+
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";
|
|
8
10
|
export declare class AIProvider implements IAIProvider {
|
|
9
11
|
constructor(options: AIProvider.IOptions);
|
|
10
12
|
get name(): string;
|
|
@@ -54,6 +56,15 @@ export declare namespace AIProvider {
|
|
|
54
56
|
*/
|
|
55
57
|
requestCompletion: () => void;
|
|
56
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* The options for the Chat system prompt.
|
|
61
|
+
*/
|
|
62
|
+
interface IPromptOptions {
|
|
63
|
+
/**
|
|
64
|
+
* The provider name.
|
|
65
|
+
*/
|
|
66
|
+
provider_name: string;
|
|
67
|
+
}
|
|
57
68
|
/**
|
|
58
69
|
* This function indicates whether a key is writable in an object.
|
|
59
70
|
* https://stackoverflow.com/questions/54724875/can-we-check-whether-property-is-readonly-in-typescript
|
package/lib/provider.js
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
import { Signal } from '@lumino/signaling';
|
|
2
2
|
import { CompletionProvider } from './completion-provider';
|
|
3
3
|
import { getChatModel } from './llm-models';
|
|
4
|
+
export const chatSystemPrompt = (options) => `
|
|
5
|
+
You are Jupyternaut, a conversational assistant living in JupyterLab to help users.
|
|
6
|
+
You are not a language model, but rather an application built on a foundation model from ${options.provider_name}.
|
|
7
|
+
You are talkative and you provide lots of specific details from the foundation model's context.
|
|
8
|
+
You may use Markdown to format your response.
|
|
9
|
+
If your response includes code, they must be enclosed in Markdown fenced code blocks (with triple backticks before and after).
|
|
10
|
+
If your response includes mathematical notation, they must be expressed in LaTeX markup and enclosed in LaTeX delimiters.
|
|
11
|
+
All dollar quantities (of USD) must be formatted in LaTeX, with the \`$\` symbol escaped by a single backslash \`\\\`.
|
|
12
|
+
- Example prompt: \`If I have \\\\$100 and spend \\\\$20, how much money do I have left?\`
|
|
13
|
+
- **Correct** response: \`You have \\(\\$80\\) remaining.\`
|
|
14
|
+
- **Incorrect** response: \`You have $80 remaining.\`
|
|
15
|
+
If you do not know the answer to a question, answer truthfully by responding that you do not know.
|
|
16
|
+
The following is a friendly conversation between you and a human.
|
|
17
|
+
`;
|
|
18
|
+
export const COMPLETION_SYSTEM_PROMPT = `
|
|
19
|
+
You are an application built to provide helpful code completion suggestions.
|
|
20
|
+
You should only produce code. Keep comments to minimum, use the
|
|
21
|
+
programming language comment syntax. Produce clean code.
|
|
22
|
+
The code is written in JupyterLab, a data analysis and code development
|
|
23
|
+
environment which can execute code extended with additional syntax for
|
|
24
|
+
interactive features, such as magics.
|
|
25
|
+
Only give raw strings back, do not format the response using backticks.
|
|
26
|
+
The output should be a single string, and should correspond to what a human users
|
|
27
|
+
would write.
|
|
28
|
+
Do not include the prompt in the output, only the string that should be appended to the current input.
|
|
29
|
+
`;
|
|
4
30
|
export class AIProvider {
|
|
5
31
|
constructor(options) {
|
|
6
32
|
this._llmChatModel = null;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TODO: reuse from Jupyter AI instead of copying?
|
|
3
|
+
* https://github.com/jupyterlab/jupyter-ai/blob/main/packages/jupyter-ai/src/slash-autocompletion.tsx
|
|
4
|
+
*/
|
|
5
|
+
/// <reference types="react-addons-linked-state-mixin" />
|
|
6
|
+
import { AutocompleteCommand } from '@jupyter/chat';
|
|
7
|
+
import React from 'react';
|
|
8
|
+
type SlashCommandOption = AutocompleteCommand & {
|
|
9
|
+
id: string;
|
|
10
|
+
description: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Renders an option shown in the slash command autocomplete.
|
|
14
|
+
*/
|
|
15
|
+
export declare function renderSlashCommandOption(optionProps: React.HTMLAttributes<HTMLLIElement>, option: SlashCommandOption): JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TODO: reuse from Jupyter AI instead of copying?
|
|
3
|
+
* https://github.com/jupyterlab/jupyter-ai/blob/main/packages/jupyter-ai/src/slash-autocompletion.tsx
|
|
4
|
+
*/
|
|
5
|
+
import { Box, Typography } from '@mui/material';
|
|
6
|
+
import HideSource from '@mui/icons-material/HideSource';
|
|
7
|
+
import React from 'react';
|
|
8
|
+
const DEFAULT_SLASH_COMMAND_ICONS = {
|
|
9
|
+
clear: React.createElement(HideSource, null)
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Renders an option shown in the slash command autocomplete.
|
|
13
|
+
*/
|
|
14
|
+
export function renderSlashCommandOption(optionProps, option) {
|
|
15
|
+
const icon = option.id in DEFAULT_SLASH_COMMAND_ICONS
|
|
16
|
+
? DEFAULT_SLASH_COMMAND_ICONS[option.id]
|
|
17
|
+
: DEFAULT_SLASH_COMMAND_ICONS.unknown;
|
|
18
|
+
return (React.createElement("li", { ...optionProps },
|
|
19
|
+
React.createElement(Box, { sx: { lineHeight: 0, marginRight: 4, opacity: 0.618 } }, icon),
|
|
20
|
+
React.createElement(Box, { sx: { flexGrow: 1 } },
|
|
21
|
+
React.createElement(Typography, { component: "span", sx: {
|
|
22
|
+
fontSize: 'var(--jp-ui-font-size1)'
|
|
23
|
+
} }, option.label),
|
|
24
|
+
React.createElement(Typography, { component: "span", sx: { opacity: 0.618, fontSize: 'var(--jp-ui-font-size0)' } }, ' — ' + option.description))));
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlite/ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "AI code completions and chat for JupyterLite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -27,8 +27,9 @@
|
|
|
27
27
|
"url": "https://github.com/jupyterlite/ai.git"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
|
-
"build": "jlpm build:lib && jlpm build:labextension:dev",
|
|
31
|
-
"build:
|
|
30
|
+
"build": "node ./scripts/settings-generator.js && jlpm build:lib && jlpm build:labextension:dev",
|
|
31
|
+
"build:dev": "jlpm build:lib && jlpm build:labextension:dev",
|
|
32
|
+
"build:prod": "node ./scripts/settings-generator.js && jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
|
|
32
33
|
"build:labextension": "jupyter labextension build .",
|
|
33
34
|
"build:labextension:dev": "jupyter labextension build --development True .",
|
|
34
35
|
"build:lib": "tsc --sourceMap",
|
|
@@ -53,21 +54,29 @@
|
|
|
53
54
|
"watch:labextension": "jupyter labextension watch ."
|
|
54
55
|
},
|
|
55
56
|
"dependencies": {
|
|
56
|
-
"@jupyter/chat": "^0.
|
|
57
|
-
"@jupyterlab/application": "^4.
|
|
58
|
-
"@jupyterlab/apputils": "^4.
|
|
59
|
-
"@jupyterlab/completer": "^4.
|
|
60
|
-
"@jupyterlab/notebook": "^4.
|
|
61
|
-
"@jupyterlab/rendermime": "^4.
|
|
62
|
-
"@jupyterlab/settingregistry": "^4.
|
|
63
|
-
"@langchain/
|
|
57
|
+
"@jupyter/chat": "^0.7.1",
|
|
58
|
+
"@jupyterlab/application": "^4.4.0-alpha.0",
|
|
59
|
+
"@jupyterlab/apputils": "^4.5.0-alpha.0",
|
|
60
|
+
"@jupyterlab/completer": "^4.4.0-alpha.0",
|
|
61
|
+
"@jupyterlab/notebook": "^4.4.0-alpha.0",
|
|
62
|
+
"@jupyterlab/rendermime": "^4.4.0-alpha.0",
|
|
63
|
+
"@jupyterlab/settingregistry": "^4.4.0-alpha.0",
|
|
64
|
+
"@langchain/anthropic": "^0.3.9",
|
|
65
|
+
"@langchain/community": "^0.3.31",
|
|
66
|
+
"@langchain/core": "^0.3.40",
|
|
64
67
|
"@langchain/mistralai": "^0.1.1",
|
|
68
|
+
"@langchain/openai": "^0.4.4",
|
|
65
69
|
"@lumino/coreutils": "^2.1.2",
|
|
66
70
|
"@lumino/polling": "^2.1.2",
|
|
67
|
-
"@lumino/signaling": "^2.1.2"
|
|
71
|
+
"@lumino/signaling": "^2.1.2",
|
|
72
|
+
"@mui/icons-material": "^5.11.0",
|
|
73
|
+
"@mui/material": "^5.11.0",
|
|
74
|
+
"react": "^18.2.0",
|
|
75
|
+
"react-dom": "^18.2.0"
|
|
68
76
|
},
|
|
69
77
|
"devDependencies": {
|
|
70
78
|
"@jupyterlab/builder": "^4.0.0",
|
|
79
|
+
"@stylistic/eslint-plugin": "^3.0.1",
|
|
71
80
|
"@types/json-schema": "^7.0.11",
|
|
72
81
|
"@types/react": "^18.0.26",
|
|
73
82
|
"@types/react-addons-linked-state-mixin": "^0.14.22",
|
|
@@ -87,7 +96,8 @@
|
|
|
87
96
|
"stylelint-config-standard": "^34.0.0",
|
|
88
97
|
"stylelint-csstree-validator": "^3.0.0",
|
|
89
98
|
"stylelint-prettier": "^4.0.0",
|
|
90
|
-
"
|
|
99
|
+
"ts-json-schema-generator": "^2.3.0",
|
|
100
|
+
"typescript": "~5.1.6",
|
|
91
101
|
"yjs": "^13.5.0"
|
|
92
102
|
},
|
|
93
103
|
"sideEffects": [
|
|
@@ -102,96 +112,5 @@
|
|
|
102
112
|
"extension": true,
|
|
103
113
|
"outputDir": "jupyterlite_ai/labextension",
|
|
104
114
|
"schemaDir": "schema"
|
|
105
|
-
},
|
|
106
|
-
"eslintIgnore": [
|
|
107
|
-
"node_modules",
|
|
108
|
-
"dist",
|
|
109
|
-
"coverage",
|
|
110
|
-
"**/*.d.ts"
|
|
111
|
-
],
|
|
112
|
-
"eslintConfig": {
|
|
113
|
-
"extends": [
|
|
114
|
-
"eslint:recommended",
|
|
115
|
-
"plugin:@typescript-eslint/eslint-recommended",
|
|
116
|
-
"plugin:@typescript-eslint/recommended",
|
|
117
|
-
"plugin:prettier/recommended"
|
|
118
|
-
],
|
|
119
|
-
"parser": "@typescript-eslint/parser",
|
|
120
|
-
"parserOptions": {
|
|
121
|
-
"project": "tsconfig.json",
|
|
122
|
-
"sourceType": "module"
|
|
123
|
-
},
|
|
124
|
-
"plugins": [
|
|
125
|
-
"@typescript-eslint"
|
|
126
|
-
],
|
|
127
|
-
"rules": {
|
|
128
|
-
"@typescript-eslint/naming-convention": [
|
|
129
|
-
"error",
|
|
130
|
-
{
|
|
131
|
-
"selector": "interface",
|
|
132
|
-
"format": [
|
|
133
|
-
"PascalCase"
|
|
134
|
-
],
|
|
135
|
-
"custom": {
|
|
136
|
-
"regex": "^I[A-Z]",
|
|
137
|
-
"match": true
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
],
|
|
141
|
-
"@typescript-eslint/no-unused-vars": [
|
|
142
|
-
"warn",
|
|
143
|
-
{
|
|
144
|
-
"args": "none"
|
|
145
|
-
}
|
|
146
|
-
],
|
|
147
|
-
"@typescript-eslint/no-explicit-any": "off",
|
|
148
|
-
"@typescript-eslint/no-namespace": "off",
|
|
149
|
-
"@typescript-eslint/no-use-before-define": "off",
|
|
150
|
-
"@typescript-eslint/quotes": [
|
|
151
|
-
"error",
|
|
152
|
-
"single",
|
|
153
|
-
{
|
|
154
|
-
"avoidEscape": true,
|
|
155
|
-
"allowTemplateLiterals": false
|
|
156
|
-
}
|
|
157
|
-
],
|
|
158
|
-
"curly": [
|
|
159
|
-
"error",
|
|
160
|
-
"all"
|
|
161
|
-
],
|
|
162
|
-
"eqeqeq": "error",
|
|
163
|
-
"prefer-arrow-callback": "error"
|
|
164
|
-
}
|
|
165
|
-
},
|
|
166
|
-
"prettier": {
|
|
167
|
-
"singleQuote": true,
|
|
168
|
-
"trailingComma": "none",
|
|
169
|
-
"arrowParens": "avoid",
|
|
170
|
-
"endOfLine": "auto",
|
|
171
|
-
"overrides": [
|
|
172
|
-
{
|
|
173
|
-
"files": "package.json",
|
|
174
|
-
"options": {
|
|
175
|
-
"tabWidth": 4
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
]
|
|
179
|
-
},
|
|
180
|
-
"stylelint": {
|
|
181
|
-
"extends": [
|
|
182
|
-
"stylelint-config-recommended",
|
|
183
|
-
"stylelint-config-standard",
|
|
184
|
-
"stylelint-prettier/recommended"
|
|
185
|
-
],
|
|
186
|
-
"plugins": [
|
|
187
|
-
"stylelint-csstree-validator"
|
|
188
|
-
],
|
|
189
|
-
"rules": {
|
|
190
|
-
"csstree/validator": true,
|
|
191
|
-
"property-no-vendor-prefix": null,
|
|
192
|
-
"selector-class-pattern": "^([a-z][A-z\\d]*)(-[A-z\\d]+)*$",
|
|
193
|
-
"selector-no-vendor-prefix": null,
|
|
194
|
-
"value-no-vendor-prefix": null
|
|
195
|
-
}
|
|
196
115
|
}
|
|
197
116
|
}
|
package/schema/ai-provider.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"title": "AI provider",
|
|
3
3
|
"description": "Provider settings",
|
|
4
|
+
"jupyter.lab.setting-icon": "@jupyterlite/ai:jupyternaut-lite",
|
|
5
|
+
"jupyter.lab.setting-icon-label": "JupyterLite AI Chat",
|
|
4
6
|
"type": "object",
|
|
5
7
|
"properties": {
|
|
6
8
|
"provider": {
|
|
@@ -8,14 +10,8 @@
|
|
|
8
10
|
"title": "The AI provider",
|
|
9
11
|
"description": "The AI provider to use for chat and completion",
|
|
10
12
|
"default": "None",
|
|
11
|
-
"enum": ["None", "MistralAI"]
|
|
12
|
-
},
|
|
13
|
-
"apiKey": {
|
|
14
|
-
"type": "string",
|
|
15
|
-
"title": "The Codestral API key",
|
|
16
|
-
"description": "The API key to use for Codestral",
|
|
17
|
-
"default": ""
|
|
13
|
+
"enum": ["None", "Anthropic", "ChromeAI", "MistralAI", "OpenAI"]
|
|
18
14
|
}
|
|
19
15
|
},
|
|
20
|
-
"additionalProperties":
|
|
16
|
+
"additionalProperties": true
|
|
21
17
|
}
|
package/schema/chat.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"title": "Chat configuration",
|
|
3
3
|
"description": "Configuration for the chat panel",
|
|
4
|
+
"jupyter.lab.setting-icon": "jupyter-chat::chat",
|
|
5
|
+
"jupyter.lab.setting-icon-label": "Jupyter Chat",
|
|
4
6
|
"type": "object",
|
|
5
7
|
"properties": {
|
|
6
8
|
"sendWithShiftEnter": {
|
|
@@ -14,6 +16,12 @@
|
|
|
14
16
|
"type": "boolean",
|
|
15
17
|
"default": true,
|
|
16
18
|
"readOnly": false
|
|
19
|
+
},
|
|
20
|
+
"personaName": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"title": "AI persona name",
|
|
23
|
+
"description": "The name of the AI persona",
|
|
24
|
+
"default": "Jupyternaut"
|
|
17
25
|
}
|
|
18
26
|
},
|
|
19
27
|
"additionalProperties": false
|
package/src/chat-handler.ts
CHANGED
|
@@ -13,11 +13,21 @@ import type { BaseChatModel } from '@langchain/core/language_models/chat_models'
|
|
|
13
13
|
import {
|
|
14
14
|
AIMessage,
|
|
15
15
|
HumanMessage,
|
|
16
|
-
mergeMessageRuns
|
|
16
|
+
mergeMessageRuns,
|
|
17
|
+
SystemMessage
|
|
17
18
|
} from '@langchain/core/messages';
|
|
18
19
|
import { UUID } from '@lumino/coreutils';
|
|
19
20
|
import { getErrorMessage } from './llm-models';
|
|
21
|
+
import { chatSystemPrompt } from './provider';
|
|
20
22
|
import { IAIProvider } from './token';
|
|
23
|
+
import { jupyternautLiteIcon } from './icons';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The base64 encoded SVG string of the jupyternaut lite icon.
|
|
27
|
+
* Encode so it can be passed as avatar_url to jupyter-chat.
|
|
28
|
+
*/
|
|
29
|
+
const AI_AVATAR_BASE64 = btoa(jupyternautLiteIcon.svgstr);
|
|
30
|
+
const AI_AVATAR = `data:image/svg+xml;base64,${AI_AVATAR_BASE64}`;
|
|
21
31
|
|
|
22
32
|
export type ConnectionMessage = {
|
|
23
33
|
type: 'connection';
|
|
@@ -28,8 +38,11 @@ export class ChatHandler extends ChatModel {
|
|
|
28
38
|
constructor(options: ChatHandler.IOptions) {
|
|
29
39
|
super(options);
|
|
30
40
|
this._aiProvider = options.aiProvider;
|
|
41
|
+
this._prompt = chatSystemPrompt({ provider_name: this._aiProvider.name });
|
|
42
|
+
|
|
31
43
|
this._aiProvider.modelChange.connect(() => {
|
|
32
44
|
this._errorMessage = this._aiProvider.chatError;
|
|
45
|
+
this._prompt = chatSystemPrompt({ provider_name: this._aiProvider.name });
|
|
33
46
|
});
|
|
34
47
|
}
|
|
35
48
|
|
|
@@ -37,11 +50,45 @@ export class ChatHandler extends ChatModel {
|
|
|
37
50
|
return this._aiProvider.chatModel;
|
|
38
51
|
}
|
|
39
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Getter and setter for the persona name.
|
|
55
|
+
*/
|
|
56
|
+
get personaName(): string {
|
|
57
|
+
return this._personaName;
|
|
58
|
+
}
|
|
59
|
+
set personaName(value: string) {
|
|
60
|
+
this.messages.forEach(message => {
|
|
61
|
+
if (message.sender.username === this._personaName) {
|
|
62
|
+
const updated: IChatMessage = { ...message };
|
|
63
|
+
updated.sender.username = value;
|
|
64
|
+
this.messageAdded(updated);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
this._personaName = value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Getter and setter for the initial prompt.
|
|
72
|
+
*/
|
|
73
|
+
get prompt(): string {
|
|
74
|
+
return this._prompt;
|
|
75
|
+
}
|
|
76
|
+
set prompt(value: string) {
|
|
77
|
+
this._prompt = value;
|
|
78
|
+
}
|
|
79
|
+
|
|
40
80
|
async sendMessage(message: INewMessage): Promise<boolean> {
|
|
81
|
+
const body = message.body;
|
|
82
|
+
if (body.startsWith('/clear')) {
|
|
83
|
+
// TODO: do we need a clear method?
|
|
84
|
+
this.messagesDeleted(0, this.messages.length);
|
|
85
|
+
this._history.messages = [];
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
41
88
|
message.id = UUID.uuid4();
|
|
42
89
|
const msg: IChatMessage = {
|
|
43
90
|
id: message.id,
|
|
44
|
-
body
|
|
91
|
+
body,
|
|
45
92
|
sender: { username: 'User' },
|
|
46
93
|
time: Date.now(),
|
|
47
94
|
type: 'msg'
|
|
@@ -62,8 +109,9 @@ export class ChatHandler extends ChatModel {
|
|
|
62
109
|
|
|
63
110
|
this._history.messages.push(msg);
|
|
64
111
|
|
|
65
|
-
const messages = mergeMessageRuns(
|
|
66
|
-
|
|
112
|
+
const messages = mergeMessageRuns([new SystemMessage(this._prompt)]);
|
|
113
|
+
messages.push(
|
|
114
|
+
...this._history.messages.map(msg => {
|
|
67
115
|
if (msg.sender.username === 'User') {
|
|
68
116
|
return new HumanMessage(msg.body);
|
|
69
117
|
}
|
|
@@ -71,37 +119,44 @@ export class ChatHandler extends ChatModel {
|
|
|
71
119
|
})
|
|
72
120
|
);
|
|
73
121
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
122
|
+
const sender = { username: this._personaName, avatar_url: AI_AVATAR };
|
|
123
|
+
this.updateWriters([sender]);
|
|
124
|
+
|
|
125
|
+
// create an empty message to be filled by the AI provider
|
|
126
|
+
const botMsg: IChatMessage = {
|
|
127
|
+
id: UUID.uuid4(),
|
|
128
|
+
body: '',
|
|
129
|
+
sender,
|
|
130
|
+
time: Date.now(),
|
|
131
|
+
type: 'msg'
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
let content = '';
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
for await (const chunk of await this._aiProvider.chatModel.stream(
|
|
138
|
+
messages
|
|
139
|
+
)) {
|
|
140
|
+
content += chunk.content ?? chunk;
|
|
141
|
+
botMsg.body = content;
|
|
86
142
|
this.messageAdded(botMsg);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
.
|
|
103
|
-
|
|
104
|
-
});
|
|
143
|
+
}
|
|
144
|
+
this._history.messages.push(botMsg);
|
|
145
|
+
return true;
|
|
146
|
+
} catch (reason) {
|
|
147
|
+
const error = getErrorMessage(this._aiProvider.name, reason);
|
|
148
|
+
const errorMsg: IChatMessage = {
|
|
149
|
+
id: UUID.uuid4(),
|
|
150
|
+
body: `**${error}**`,
|
|
151
|
+
sender: { username: 'ERROR' },
|
|
152
|
+
time: Date.now(),
|
|
153
|
+
type: 'msg'
|
|
154
|
+
};
|
|
155
|
+
this.messageAdded(errorMsg);
|
|
156
|
+
return false;
|
|
157
|
+
} finally {
|
|
158
|
+
this.updateWriters([]);
|
|
159
|
+
}
|
|
105
160
|
}
|
|
106
161
|
|
|
107
162
|
async getHistory(): Promise<IChatHistory> {
|
|
@@ -117,6 +172,8 @@ export class ChatHandler extends ChatModel {
|
|
|
117
172
|
}
|
|
118
173
|
|
|
119
174
|
private _aiProvider: IAIProvider;
|
|
175
|
+
private _personaName = 'AI';
|
|
176
|
+
private _prompt: string;
|
|
120
177
|
private _errorMessage: string = '';
|
|
121
178
|
private _history: IChatHistory = { messages: [] };
|
|
122
179
|
private _defaultErrorMessage = 'AI provider not configured';
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
IInlineCompletionContext,
|
|
4
4
|
IInlineCompletionProvider
|
|
5
5
|
} from '@jupyterlab/completer';
|
|
6
|
-
import {
|
|
6
|
+
import { BaseLanguageModel } from '@langchain/core/language_models/base';
|
|
7
|
+
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
7
8
|
|
|
8
9
|
import { getCompleter, IBaseCompleter, BaseCompleter } from './llm-models';
|
|
9
|
-
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* The generic completion provider to register to the completion provider manager.
|
|
@@ -57,7 +57,7 @@ export class CompletionProvider implements IInlineCompletionProvider {
|
|
|
57
57
|
/**
|
|
58
58
|
* Get the LLM completer.
|
|
59
59
|
*/
|
|
60
|
-
get llmCompleter():
|
|
60
|
+
get llmCompleter(): BaseLanguageModel | null {
|
|
61
61
|
return this._completer?.provider || null;
|
|
62
62
|
}
|
|
63
63
|
|