Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
dist
coverage
**/*.d.ts
tests
**/__tests__
ui-tests
65 changes: 65 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended'
],
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module'
},
plugins: ['@stylistic', '@typescript-eslint'],
rules: {
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'interface',
format: ['PascalCase'],
custom: {
regex: '^I[A-Z]',
match: true
}
}
],
'@typescript-eslint/no-unused-vars': [
'warn',
{
args: 'none'
}
],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@stylistic/quotes': [
'error',
'single',
{
avoidEscape: true,
allowTemplateLiterals: false
}
],
curly: ['error', 'all'],
eqeqeq: 'error',
'no-restricted-imports': [
'error',
{
paths: [
{
name: '@mui/icons-material',
message:
"Please import icons using path imports, e.g. `import AddIcon from '@mui/icons-material/Add'`"
}
],
patterns: [
{
group: ['@mui/*/*/*'],
message: '3rd level imports in mui are considered private'
}
]
}
],
'prefer-arrow-callback': 'error'
}
};
14 changes: 14 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"singleQuote": true,
"trailingComma": "none",
"arrowParens": "avoid",
"endOfLine": "auto",
"overrides": [
{
"files": "package.json",
"options": {
"tabWidth": 4
}
}
]
}
17 changes: 17 additions & 0 deletions .stylelintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": [
"stylelint-config-recommended",
"stylelint-config-standard",
"stylelint-prettier/recommended"
],
"plugins": [
"stylelint-csstree-validator"
],
"rules": {
"csstree/validator": true,
"property-no-vendor-prefix": null,
"selector-class-pattern": "^([a-z][A-z\\d]*)(-[A-z\\d]+)*$",
"selector-no-vendor-prefix": null,
"value-no-vendor-prefix": null
}
}
98 changes: 6 additions & 92 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,15 @@
"@langchain/mistralai": "^0.1.1",
"@lumino/coreutils": "^2.1.2",
"@lumino/polling": "^2.1.2",
"@lumino/signaling": "^2.1.2"
"@lumino/signaling": "^2.1.2",
"@mui/icons-material": "^5.11.0",
"@mui/material": "^5.11.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@jupyterlab/builder": "^4.0.0",
"@stylistic/eslint-plugin": "^3.0.1",
"@types/json-schema": "^7.0.11",
"@types/react": "^18.0.26",
"@types/react-addons-linked-state-mixin": "^0.14.22",
Expand Down Expand Up @@ -106,96 +111,5 @@
"extension": true,
"outputDir": "jupyterlite_ai/labextension",
"schemaDir": "schema"
},
"eslintIgnore": [
"node_modules",
"dist",
"coverage",
"**/*.d.ts"
],
"eslintConfig": {
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "tsconfig.json",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "interface",
"format": [
"PascalCase"
],
"custom": {
"regex": "^I[A-Z]",
"match": true
}
}
],
"@typescript-eslint/no-unused-vars": [
"warn",
{
"args": "none"
}
],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/quotes": [
"error",
"single",
{
"avoidEscape": true,
"allowTemplateLiterals": false
}
],
"curly": [
"error",
"all"
],
"eqeqeq": "error",
"prefer-arrow-callback": "error"
}
},
"prettier": {
"singleQuote": true,
"trailingComma": "none",
"arrowParens": "avoid",
"endOfLine": "auto",
"overrides": [
{
"files": "package.json",
"options": {
"tabWidth": 4
}
}
]
},
"stylelint": {
"extends": [
"stylelint-config-recommended",
"stylelint-config-standard",
"stylelint-prettier/recommended"
],
"plugins": [
"stylelint-csstree-validator"
],
"rules": {
"csstree/validator": true,
"property-no-vendor-prefix": null,
"selector-class-pattern": "^([a-z][A-z\\d]*)(-[A-z\\d]+)*$",
"selector-no-vendor-prefix": null,
"value-no-vendor-prefix": null
}
}
}
9 changes: 8 additions & 1 deletion src/chat-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,17 @@ export class ChatHandler extends ChatModel {
}

async sendMessage(message: INewMessage): Promise<boolean> {
const body = message.body;
if (body.startsWith('/clear')) {
// TODO: do we need a clear method?
this.messagesDeleted(0, this.messages.length);
this._history.messages = [];
return false;
}
message.id = UUID.uuid4();
const msg: IChatMessage = {
id: message.id,
body: message.body,
body,
sender: { username: 'User' },
time: Date.now(),
type: 'msg'
Expand Down
41 changes: 37 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {
ActiveCellManager,
AutocompletionRegistry,
buildChatSidebar,
buildErrorWidget,
IActiveCellManager
IActiveCellManager,
IAutocompletionCommandsProps,
IAutocompletionRegistry
} from '@jupyter/chat';
import {
JupyterFrontEnd,
Expand All @@ -17,18 +20,47 @@ import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { ChatHandler } from './chat-handler';
import { getSettings } from './llm-models';
import { AIProvider } from './provider';
import { renderSlashCommandOption } from './slash-commands';
import { IAIProvider } from './token';

const autocompletionRegistryPlugin: JupyterFrontEndPlugin<IAutocompletionRegistry> =
{
id: '@jupyterlite/ai:autocompletion-registry',
description: 'Autocompletion registry',
autoStart: true,
provides: IAutocompletionRegistry,
activate: () => {
const autocompletionRegistry = new AutocompletionRegistry();
const options = ['/clear'];
const autocompletionCommands: IAutocompletionCommandsProps = {
opener: '/',
commands: options.map(option => {
return {
id: option.slice(1),
label: option,
description: 'Clear the chat window'
};
}),
props: {
renderOption: renderSlashCommandOption
}
};
autocompletionRegistry.add('jupyterlite-ai', autocompletionCommands);
return autocompletionRegistry;
}
};

const chatPlugin: JupyterFrontEndPlugin<void> = {
id: '@jupyterlite/ai:chat',
description: 'LLM chat extension',
autoStart: true,
requires: [IAIProvider, IRenderMimeRegistry, IAutocompletionRegistry],
optional: [INotebookTracker, ISettingRegistry, IThemeManager],
requires: [IAIProvider, IRenderMimeRegistry],
activate: async (
app: JupyterFrontEnd,
aiProvider: IAIProvider,
rmRegistry: IRenderMimeRegistry,
autocompletionRegistry: IAutocompletionRegistry,
notebookTracker: INotebookTracker | null,
settingsRegistry: ISettingRegistry | null,
themeManager: IThemeManager | null
Expand Down Expand Up @@ -83,7 +115,8 @@ const chatPlugin: JupyterFrontEndPlugin<void> = {
chatWidget = buildChatSidebar({
model: chatHandler,
themeManager,
rmRegistry
rmRegistry,
autocompletionRegistry
});
chatWidget.title.caption = 'Jupyterlite AI Chat';
} catch (e) {
Expand Down Expand Up @@ -156,4 +189,4 @@ const aiProviderPlugin: JupyterFrontEndPlugin<IAIProvider> = {
}
};

export default [chatPlugin, aiProviderPlugin];
export default [chatPlugin, autocompletionRegistryPlugin, aiProviderPlugin];
55 changes: 55 additions & 0 deletions src/slash-commands.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* TODO: reuse from Jupyter AI instead of copying?
* https://github.com/jupyterlab/jupyter-ai/blob/main/packages/jupyter-ai/src/slash-autocompletion.tsx
*/

import { Box, Typography } from '@mui/material';
import { AutocompleteCommand } from '@jupyter/chat';

import HideSource from '@mui/icons-material/HideSource';

import React from 'react';

const DEFAULT_SLASH_COMMAND_ICONS: Record<string, JSX.Element> = {
clear: <HideSource />
};

type SlashCommandOption = AutocompleteCommand & {
id: string;
description: string;
};

/**
* Renders an option shown in the slash command autocomplete.
*/
export function renderSlashCommandOption(
optionProps: React.HTMLAttributes<HTMLLIElement>,
option: SlashCommandOption
): JSX.Element {
const icon =
option.id in DEFAULT_SLASH_COMMAND_ICONS
? DEFAULT_SLASH_COMMAND_ICONS[option.id]
: DEFAULT_SLASH_COMMAND_ICONS.unknown;

return (
<li {...optionProps}>
<Box sx={{ lineHeight: 0, marginRight: 4, opacity: 0.618 }}>{icon}</Box>
<Box sx={{ flexGrow: 1 }}>
<Typography
component="span"
sx={{
fontSize: 'var(--jp-ui-font-size1)'
}}
>
{option.label}
</Typography>
<Typography
component="span"
sx={{ opacity: 0.618, fontSize: 'var(--jp-ui-font-size0)' }}
>
{' — ' + option.description}
</Typography>
</Box>
</li>
);
}
Loading
Loading