@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
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import ChromeAI from './_generated/ChromeAI.json';
|
|
2
|
+
import MistralAI from './_generated/MistralAI.json';
|
|
3
|
+
import Anthropic from './_generated/Anthropic.json';
|
|
4
|
+
import OpenAI from './_generated/OpenAI.json';
|
|
5
|
+
const ProviderSettings = {
|
|
6
|
+
ChromeAI,
|
|
7
|
+
MistralAI,
|
|
8
|
+
Anthropic,
|
|
9
|
+
OpenAI
|
|
10
|
+
};
|
|
11
|
+
export { ProviderSettings };
|
|
@@ -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/lib/tokens.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
2
|
+
import { ReadonlyPartialJSONObject, Token } from '@lumino/coreutils';
|
|
3
|
+
import { ISignal } from '@lumino/signaling';
|
|
4
|
+
import { JSONSchema7 } from 'json-schema';
|
|
5
|
+
import { IBaseCompleter } from './llm-models';
|
|
6
|
+
export interface IDict<T = any> {
|
|
7
|
+
[key: string]: T;
|
|
8
|
+
}
|
|
9
|
+
export interface IType<T> {
|
|
10
|
+
new (...args: any[]): T;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The provider interface.
|
|
14
|
+
*/
|
|
15
|
+
export interface IAIProvider {
|
|
16
|
+
/**
|
|
17
|
+
* The name of the provider.
|
|
18
|
+
*/
|
|
19
|
+
name: string;
|
|
20
|
+
/**
|
|
21
|
+
* The chat model class to use.
|
|
22
|
+
*/
|
|
23
|
+
chatModel?: IType<BaseChatModel>;
|
|
24
|
+
/**
|
|
25
|
+
* The completer class to use.
|
|
26
|
+
*/
|
|
27
|
+
completer?: IType<IBaseCompleter>;
|
|
28
|
+
/**
|
|
29
|
+
* the settings schema for the provider.
|
|
30
|
+
*/
|
|
31
|
+
settingsSchema?: any;
|
|
32
|
+
/**
|
|
33
|
+
* The instructions to be displayed in the settings, as helper to use the provider.
|
|
34
|
+
* A markdown renderer is used to render the instructions.
|
|
35
|
+
*/
|
|
36
|
+
instructions?: string;
|
|
37
|
+
/**
|
|
38
|
+
* A function that extract the error message from the provider API error.
|
|
39
|
+
* Default to `(error) => error.message`.
|
|
40
|
+
*/
|
|
41
|
+
errorMessage?: (error: any) => string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The provider registry interface.
|
|
45
|
+
*/
|
|
46
|
+
export interface IAIProviderRegistry {
|
|
47
|
+
/**
|
|
48
|
+
* Get the list of provider names.
|
|
49
|
+
*/
|
|
50
|
+
readonly providers: string[];
|
|
51
|
+
/**
|
|
52
|
+
* Add a new provider.
|
|
53
|
+
*/
|
|
54
|
+
add(provider: IAIProvider): void;
|
|
55
|
+
/**
|
|
56
|
+
* Get the current provider name.
|
|
57
|
+
*/
|
|
58
|
+
currentName: string;
|
|
59
|
+
/**
|
|
60
|
+
* Get the current completer of the completion provider.
|
|
61
|
+
*/
|
|
62
|
+
currentCompleter: IBaseCompleter | null;
|
|
63
|
+
/**
|
|
64
|
+
* Get the current llm chat model.
|
|
65
|
+
*/
|
|
66
|
+
currentChatModel: BaseChatModel | null;
|
|
67
|
+
/**
|
|
68
|
+
* Get the settings schema of a given provider.
|
|
69
|
+
*/
|
|
70
|
+
getSettingsSchema(provider: string): JSONSchema7;
|
|
71
|
+
/**
|
|
72
|
+
* Get the instructions of a given provider.
|
|
73
|
+
*/
|
|
74
|
+
getInstructions(provider: string): string | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Format an error message from the current provider.
|
|
77
|
+
*/
|
|
78
|
+
formatErrorMessage(error: any): string;
|
|
79
|
+
/**
|
|
80
|
+
* Set the providers (chat model and completer).
|
|
81
|
+
* Creates the providers if the name has changed, otherwise only updates their config.
|
|
82
|
+
*
|
|
83
|
+
* @param name - the name of the provider to use.
|
|
84
|
+
* @param settings - the settings for the models.
|
|
85
|
+
*/
|
|
86
|
+
setProvider(name: string, settings: ReadonlyPartialJSONObject): void;
|
|
87
|
+
/**
|
|
88
|
+
* A signal emitting when the provider or its settings has changed.
|
|
89
|
+
*/
|
|
90
|
+
readonly providerChanged: ISignal<IAIProviderRegistry, void>;
|
|
91
|
+
/**
|
|
92
|
+
* Get the current chat error;
|
|
93
|
+
*/
|
|
94
|
+
readonly chatError: string;
|
|
95
|
+
/**
|
|
96
|
+
* get the current completer error.
|
|
97
|
+
*/
|
|
98
|
+
readonly completerError: string;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The provider registry token.
|
|
102
|
+
*/
|
|
103
|
+
export declare const IAIProviderRegistry: Token<IAIProviderRegistry>;
|
package/lib/tokens.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlite/ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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,33 @@
|
|
|
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
|
-
"@
|
|
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
|
+
"@jupyterlab/ui-components": "^4.4.0-alpha.0",
|
|
65
|
+
"@langchain/anthropic": "^0.3.9",
|
|
66
|
+
"@langchain/community": "^0.3.31",
|
|
67
|
+
"@langchain/core": "^0.3.40",
|
|
64
68
|
"@langchain/mistralai": "^0.1.1",
|
|
69
|
+
"@langchain/openai": "^0.4.4",
|
|
65
70
|
"@lumino/coreutils": "^2.1.2",
|
|
66
71
|
"@lumino/polling": "^2.1.2",
|
|
67
|
-
"@lumino/signaling": "^2.1.2"
|
|
72
|
+
"@lumino/signaling": "^2.1.2",
|
|
73
|
+
"@mui/icons-material": "^5.11.0",
|
|
74
|
+
"@mui/material": "^5.11.0",
|
|
75
|
+
"@rjsf/core": "^4.2.0",
|
|
76
|
+
"@rjsf/utils": "^5.18.4",
|
|
77
|
+
"@rjsf/validator-ajv8": "^5.18.4",
|
|
78
|
+
"react": "^18.2.0",
|
|
79
|
+
"react-dom": "^18.2.0"
|
|
68
80
|
},
|
|
69
81
|
"devDependencies": {
|
|
70
82
|
"@jupyterlab/builder": "^4.0.0",
|
|
83
|
+
"@stylistic/eslint-plugin": "^3.0.1",
|
|
71
84
|
"@types/json-schema": "^7.0.11",
|
|
72
85
|
"@types/react": "^18.0.26",
|
|
73
86
|
"@types/react-addons-linked-state-mixin": "^0.14.22",
|
|
@@ -87,7 +100,8 @@
|
|
|
87
100
|
"stylelint-config-standard": "^34.0.0",
|
|
88
101
|
"stylelint-csstree-validator": "^3.0.0",
|
|
89
102
|
"stylelint-prettier": "^4.0.0",
|
|
90
|
-
"
|
|
103
|
+
"ts-json-schema-generator": "^2.3.0",
|
|
104
|
+
"typescript": "~5.1.6",
|
|
91
105
|
"yjs": "^13.5.0"
|
|
92
106
|
},
|
|
93
107
|
"sideEffects": [
|
|
@@ -102,96 +116,5 @@
|
|
|
102
116
|
"extension": true,
|
|
103
117
|
"outputDir": "jupyterlite_ai/labextension",
|
|
104
118
|
"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
119
|
}
|
|
197
120
|
}
|
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
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "AI provider",
|
|
3
|
+
"description": "Provider registry settings",
|
|
4
|
+
"jupyter.lab.setting-icon": "@jupyterlite/ai:jupyternaut-lite",
|
|
5
|
+
"jupyter.lab.setting-icon-label": "JupyterLite AI Chat",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"AIprovider": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"title": "AI provider",
|
|
11
|
+
"description": "The AI provider configuration",
|
|
12
|
+
"default": {},
|
|
13
|
+
"additionalProperties": true
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"additionalProperties": false
|
|
17
|
+
}
|
package/src/chat-handler.ts
CHANGED
|
@@ -13,11 +13,20 @@ 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
|
-
import {
|
|
20
|
-
import {
|
|
20
|
+
import { chatSystemPrompt } from './provider';
|
|
21
|
+
import { IAIProviderRegistry } from './tokens';
|
|
22
|
+
import { jupyternautLiteIcon } from './icons';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The base64 encoded SVG string of the jupyternaut lite icon.
|
|
26
|
+
* Encode so it can be passed as avatar_url to jupyter-chat.
|
|
27
|
+
*/
|
|
28
|
+
const AI_AVATAR_BASE64 = btoa(jupyternautLiteIcon.svgstr);
|
|
29
|
+
const AI_AVATAR = `data:image/svg+xml;base64,${AI_AVATAR_BASE64}`;
|
|
21
30
|
|
|
22
31
|
export type ConnectionMessage = {
|
|
23
32
|
type: 'connection';
|
|
@@ -27,28 +36,69 @@ export type ConnectionMessage = {
|
|
|
27
36
|
export class ChatHandler extends ChatModel {
|
|
28
37
|
constructor(options: ChatHandler.IOptions) {
|
|
29
38
|
super(options);
|
|
30
|
-
this.
|
|
31
|
-
this.
|
|
32
|
-
|
|
39
|
+
this._providerRegistry = options.providerRegistry;
|
|
40
|
+
this._prompt = chatSystemPrompt({
|
|
41
|
+
provider_name: this._providerRegistry.currentName
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
this._providerRegistry.providerChanged.connect(() => {
|
|
45
|
+
this._errorMessage = this._providerRegistry.chatError;
|
|
46
|
+
this._prompt = chatSystemPrompt({
|
|
47
|
+
provider_name: this._providerRegistry.currentName
|
|
48
|
+
});
|
|
33
49
|
});
|
|
34
50
|
}
|
|
35
51
|
|
|
36
52
|
get provider(): BaseChatModel | null {
|
|
37
|
-
return this.
|
|
53
|
+
return this._providerRegistry.currentChatModel;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Getter and setter for the persona name.
|
|
58
|
+
*/
|
|
59
|
+
get personaName(): string {
|
|
60
|
+
return this._personaName;
|
|
61
|
+
}
|
|
62
|
+
set personaName(value: string) {
|
|
63
|
+
this.messages.forEach(message => {
|
|
64
|
+
if (message.sender.username === this._personaName) {
|
|
65
|
+
const updated: IChatMessage = { ...message };
|
|
66
|
+
updated.sender.username = value;
|
|
67
|
+
this.messageAdded(updated);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
this._personaName = value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Getter and setter for the initial prompt.
|
|
75
|
+
*/
|
|
76
|
+
get prompt(): string {
|
|
77
|
+
return this._prompt;
|
|
78
|
+
}
|
|
79
|
+
set prompt(value: string) {
|
|
80
|
+
this._prompt = value;
|
|
38
81
|
}
|
|
39
82
|
|
|
40
83
|
async sendMessage(message: INewMessage): Promise<boolean> {
|
|
84
|
+
const body = message.body;
|
|
85
|
+
if (body.startsWith('/clear')) {
|
|
86
|
+
// TODO: do we need a clear method?
|
|
87
|
+
this.messagesDeleted(0, this.messages.length);
|
|
88
|
+
this._history.messages = [];
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
41
91
|
message.id = UUID.uuid4();
|
|
42
92
|
const msg: IChatMessage = {
|
|
43
93
|
id: message.id,
|
|
44
|
-
body
|
|
94
|
+
body,
|
|
45
95
|
sender: { username: 'User' },
|
|
46
96
|
time: Date.now(),
|
|
47
97
|
type: 'msg'
|
|
48
98
|
};
|
|
49
99
|
this.messageAdded(msg);
|
|
50
100
|
|
|
51
|
-
if (this.
|
|
101
|
+
if (this._providerRegistry.currentChatModel === null) {
|
|
52
102
|
const errorMsg: IChatMessage = {
|
|
53
103
|
id: UUID.uuid4(),
|
|
54
104
|
body: `**${this._errorMessage ? this._errorMessage : this._defaultErrorMessage}**`,
|
|
@@ -62,8 +112,9 @@ export class ChatHandler extends ChatModel {
|
|
|
62
112
|
|
|
63
113
|
this._history.messages.push(msg);
|
|
64
114
|
|
|
65
|
-
const messages = mergeMessageRuns(
|
|
66
|
-
|
|
115
|
+
const messages = mergeMessageRuns([new SystemMessage(this._prompt)]);
|
|
116
|
+
messages.push(
|
|
117
|
+
...this._history.messages.map(msg => {
|
|
67
118
|
if (msg.sender.username === 'User') {
|
|
68
119
|
return new HumanMessage(msg.body);
|
|
69
120
|
}
|
|
@@ -71,37 +122,44 @@ export class ChatHandler extends ChatModel {
|
|
|
71
122
|
})
|
|
72
123
|
);
|
|
73
124
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
125
|
+
const sender = { username: this._personaName, avatar_url: AI_AVATAR };
|
|
126
|
+
this.updateWriters([sender]);
|
|
127
|
+
|
|
128
|
+
// create an empty message to be filled by the AI provider
|
|
129
|
+
const botMsg: IChatMessage = {
|
|
130
|
+
id: UUID.uuid4(),
|
|
131
|
+
body: '',
|
|
132
|
+
sender,
|
|
133
|
+
time: Date.now(),
|
|
134
|
+
type: 'msg'
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
let content = '';
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
for await (const chunk of await this._providerRegistry.currentChatModel.stream(
|
|
141
|
+
messages
|
|
142
|
+
)) {
|
|
143
|
+
content += chunk.content ?? chunk;
|
|
144
|
+
botMsg.body = content;
|
|
86
145
|
this.messageAdded(botMsg);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
.
|
|
103
|
-
|
|
104
|
-
});
|
|
146
|
+
}
|
|
147
|
+
this._history.messages.push(botMsg);
|
|
148
|
+
return true;
|
|
149
|
+
} catch (reason) {
|
|
150
|
+
const error = this._providerRegistry.formatErrorMessage(reason);
|
|
151
|
+
const errorMsg: IChatMessage = {
|
|
152
|
+
id: UUID.uuid4(),
|
|
153
|
+
body: `**${error}**`,
|
|
154
|
+
sender: { username: 'ERROR' },
|
|
155
|
+
time: Date.now(),
|
|
156
|
+
type: 'msg'
|
|
157
|
+
};
|
|
158
|
+
this.messageAdded(errorMsg);
|
|
159
|
+
return false;
|
|
160
|
+
} finally {
|
|
161
|
+
this.updateWriters([]);
|
|
162
|
+
}
|
|
105
163
|
}
|
|
106
164
|
|
|
107
165
|
async getHistory(): Promise<IChatHistory> {
|
|
@@ -116,7 +174,9 @@ export class ChatHandler extends ChatModel {
|
|
|
116
174
|
super.messageAdded(message);
|
|
117
175
|
}
|
|
118
176
|
|
|
119
|
-
private
|
|
177
|
+
private _providerRegistry: IAIProviderRegistry;
|
|
178
|
+
private _personaName = 'AI';
|
|
179
|
+
private _prompt: string;
|
|
120
180
|
private _errorMessage: string = '';
|
|
121
181
|
private _history: IChatHistory = { messages: [] };
|
|
122
182
|
private _defaultErrorMessage = 'AI provider not configured';
|
|
@@ -124,6 +184,6 @@ export class ChatHandler extends ChatModel {
|
|
|
124
184
|
|
|
125
185
|
export namespace ChatHandler {
|
|
126
186
|
export interface IOptions extends ChatModel.IOptions {
|
|
127
|
-
|
|
187
|
+
providerRegistry: IAIProviderRegistry;
|
|
128
188
|
}
|
|
129
189
|
}
|