├── .copier-answers.yml ├── .github └── workflows │ ├── binder-on-pr.yml │ ├── build.yml │ ├── check-release.yml │ ├── enforce-label.yml │ ├── prep-release.yml │ └── publish-release.yml ├── .gitignore ├── .prettierignore ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── RELEASE.md ├── binder ├── environment.yml └── postBuild ├── install.json ├── jupyter-config └── server-config │ └── jupyterlab_transformers_completer.json ├── jupyterlab_transformers_completer └── __init__.py ├── package.json ├── pyproject.toml ├── setup.py ├── src ├── index.ts ├── models.ts ├── types.ts ├── utils.ts └── worker.ts ├── style ├── base.css ├── index.css └── index.js ├── tsconfig.json └── yarn.lock /.copier-answers.yml: -------------------------------------------------------------------------------- 1 | # Changes here will be overwritten by Copier; NEVER EDIT MANUALLY 2 | _commit: v4.2.1 3 | _src_path: https://github.com/jupyterlab/extension-template 4 | author_email: mkrassowski@quansight.com 5 | author_name: Michał Krassowski 6 | data_format: string 7 | file_extension: '' 8 | has_binder: true 9 | has_settings: false 10 | kind: frontend 11 | labextension_name: '@jupyterlab/transformers-completer' 12 | mimetype: '' 13 | mimetype_name: '' 14 | project_short_description: A JupyterLab extension. 15 | python_name: jupyterlab-transformers-completer 16 | repository: https://github.com/krassowski/jupyterlab-transformers-completer 17 | test: false 18 | viewer_name: '' 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/binder-on-pr.yml: -------------------------------------------------------------------------------- 1 | name: Binder Badge 2 | on: 3 | pull_request_target: 4 | types: [opened] 5 | 6 | jobs: 7 | binder: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | pull-requests: write 11 | steps: 12 | - uses: jupyterlab/maintainer-tools/.github/actions/binder-link@v1 13 | with: 14 | github_token: ${{ secrets.github_token }} 15 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: main 6 | pull_request: 7 | branches: '*' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | with: 17 | submodules: true 18 | 19 | - name: Base Setup 20 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 21 | 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install -U --pre "jupyterlab>=4.1.0,<5" 25 | 26 | - name: Lint the extension 27 | run: | 28 | set -eux 29 | jlpm 30 | jlpm run lint:check 31 | 32 | - name: Build the extension 33 | run: | 34 | set -eux 35 | python -m pip install .[test] 36 | jupyter server extension list 37 | jupyter server extension list 2>&1 | grep -ie "jupyterlab_transformers_completer.*OK" 38 | 39 | jupyter labextension list 40 | jupyter labextension list 2>&1 | grep -ie "@jupyterlab/transformers-completer.*OK" 41 | python -m jupyterlab.browser_check 42 | 43 | - name: Package the extension 44 | run: | 45 | set -eux 46 | 47 | pip install build 48 | python -m build 49 | pip uninstall -y "jupyterlab-transformers-completer" jupyterlab 50 | 51 | - name: Upload extension packages 52 | uses: actions/upload-artifact@v3 53 | with: 54 | name: extension-artifacts 55 | path: dist/jupyterlab_transformers_completer* 56 | if-no-files-found: error 57 | 58 | test_isolated: 59 | needs: build 60 | runs-on: ubuntu-latest 61 | 62 | steps: 63 | - name: Install Python 64 | uses: actions/setup-python@v4 65 | with: 66 | python-version: '3.9' 67 | architecture: 'x64' 68 | - uses: actions/download-artifact@v3 69 | with: 70 | name: extension-artifacts 71 | - name: Install and Test 72 | run: | 73 | set -eux 74 | # Remove NodeJS, twice to take care of system and locally installed node versions. 75 | sudo rm -rf $(which node) 76 | sudo rm -rf $(which node) 77 | 78 | pip install --pre "jupyterlab>=4.1.0,<5" jupyterlab_transformers_completer*.whl 79 | 80 | jupyter server extension list 81 | jupyter server extension list 2>&1 | grep -ie "jupyterlab_transformers_completer.*OK" 82 | jupyter labextension list 83 | jupyter labextension list 2>&1 | grep -ie "@jupyterlab/transformers-completer.*OK" 84 | python -m jupyterlab.browser_check --no-browser-test 85 | 86 | 87 | check_links: 88 | name: Check Links 89 | runs-on: ubuntu-latest 90 | timeout-minutes: 15 91 | steps: 92 | - uses: actions/checkout@v3 93 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 94 | - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1 95 | -------------------------------------------------------------------------------- /.github/workflows/check-release.yml: -------------------------------------------------------------------------------- 1 | name: Check Release 2 | on: 3 | push: 4 | branches: ["main"] 5 | pull_request: 6 | branches: ["*"] 7 | 8 | jobs: 9 | check_release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | - name: Base Setup 15 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 16 | - name: Check Release 17 | uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 18 | with: 19 | 20 | token: ${{ secrets.GITHUB_TOKEN }} 21 | 22 | - name: Upload Distributions 23 | uses: actions/upload-artifact@v3 24 | with: 25 | name: jupyterlab-transformers-completer-releaser-dist-${{ github.run_number }} 26 | path: .jupyter_releaser_checkout/dist 27 | -------------------------------------------------------------------------------- /.github/workflows/enforce-label.yml: -------------------------------------------------------------------------------- 1 | name: Enforce PR label 2 | 3 | on: 4 | pull_request: 5 | types: [labeled, unlabeled, opened, edited, synchronize] 6 | jobs: 7 | enforce-label: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | pull-requests: write 11 | steps: 12 | - name: enforce-triage-label 13 | uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1 14 | -------------------------------------------------------------------------------- /.github/workflows/prep-release.yml: -------------------------------------------------------------------------------- 1 | name: "Step 1: Prep Release" 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version_spec: 6 | description: "New Version Specifier" 7 | default: "next" 8 | required: false 9 | branch: 10 | description: "The branch to target" 11 | required: false 12 | post_version_spec: 13 | description: "Post Version Specifier" 14 | required: false 15 | since: 16 | description: "Use PRs with activity since this date or git reference" 17 | required: false 18 | since_last_stable: 19 | description: "Use PRs with activity since the last stable git tag" 20 | required: false 21 | type: boolean 22 | jobs: 23 | prep_release: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 27 | 28 | - name: Prep Release 29 | id: prep-release 30 | uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 31 | with: 32 | token: ${{ secrets.ADMIN_GITHUB_TOKEN }} 33 | version_spec: ${{ github.event.inputs.version_spec }} 34 | post_version_spec: ${{ github.event.inputs.post_version_spec }} 35 | branch: ${{ github.event.inputs.branch }} 36 | since: ${{ github.event.inputs.since }} 37 | since_last_stable: ${{ github.event.inputs.since_last_stable }} 38 | 39 | - name: "** Next Step **" 40 | run: | 41 | echo "Optional): Review Draft Release: ${{ steps.prep-release.outputs.release_url }}" 42 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: "Step 2: Publish Release" 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | branch: 6 | description: "The target branch" 7 | required: false 8 | release_url: 9 | description: "The URL of the draft GitHub release" 10 | required: false 11 | steps_to_skip: 12 | description: "Comma separated list of steps to skip" 13 | required: false 14 | 15 | jobs: 16 | publish_release: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | # This is useful if you want to use PyPI trusted publisher 20 | # and NPM provenance 21 | id-token: write 22 | steps: 23 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 24 | 25 | - name: Populate Release 26 | id: populate-release 27 | uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 28 | with: 29 | token: ${{ secrets.ADMIN_GITHUB_TOKEN }} 30 | branch: ${{ github.event.inputs.branch }} 31 | release_url: ${{ github.event.inputs.release_url }} 32 | steps_to_skip: ${{ github.event.inputs.steps_to_skip }} 33 | 34 | - name: Finalize Release 35 | id: finalize-release 36 | env: 37 | # The following are needed if you use legacy PyPI set up 38 | # PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} 39 | # PYPI_TOKEN_MAP: ${{ secrets.PYPI_TOKEN_MAP }} 40 | # TWINE_USERNAME: __token__ 41 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 42 | uses: jupyter-server/jupyter-releaser/.github/actions/finalize-release@v2 43 | with: 44 | token: ${{ secrets.ADMIN_GITHUB_TOKEN }} 45 | release_url: ${{ steps.populate-release.outputs.release_url }} 46 | 47 | - name: "** Next Step **" 48 | if: ${{ success() }} 49 | run: | 50 | echo "Verify the final release" 51 | echo ${{ steps.finalize-release.outputs.release_url }} 52 | 53 | - name: "** Failure Message **" 54 | if: ${{ failure() }} 55 | run: | 56 | echo "Failed to Publish the Draft Release Url:" 57 | echo ${{ steps.populate-release.outputs.release_url }} 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle.* 2 | lib/ 3 | node_modules/ 4 | *.log 5 | .eslintcache 6 | .stylelintcache 7 | *.egg-info/ 8 | .ipynb_checkpoints 9 | *.tsbuildinfo 10 | jupyterlab_transformers_completer/labextension 11 | # Version file is handled by hatchling 12 | jupyterlab_transformers_completer/_version.py 13 | .jupyter/ 14 | 15 | # Created by https://www.gitignore.io/api/python 16 | # Edit at https://www.gitignore.io/?templates=python 17 | 18 | ### Python ### 19 | # Byte-compiled / optimized / DLL files 20 | __pycache__/ 21 | *.py[cod] 22 | *$py.class 23 | 24 | # C extensions 25 | *.so 26 | 27 | # Distribution / packaging 28 | .Python 29 | build/ 30 | develop-eggs/ 31 | dist/ 32 | downloads/ 33 | eggs/ 34 | .eggs/ 35 | lib/ 36 | lib64/ 37 | parts/ 38 | sdist/ 39 | var/ 40 | wheels/ 41 | pip-wheel-metadata/ 42 | share/python-wheels/ 43 | .installed.cfg 44 | *.egg 45 | MANIFEST 46 | 47 | # PyInstaller 48 | # Usually these files are written by a python script from a template 49 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 50 | *.manifest 51 | *.spec 52 | 53 | # Installer logs 54 | pip-log.txt 55 | pip-delete-this-directory.txt 56 | 57 | # Unit test / coverage reports 58 | htmlcov/ 59 | .tox/ 60 | .nox/ 61 | .coverage 62 | .coverage.* 63 | .cache 64 | nosetests.xml 65 | coverage/ 66 | coverage.xml 67 | *.cover 68 | .hypothesis/ 69 | .pytest_cache/ 70 | 71 | # Translations 72 | *.mo 73 | *.pot 74 | 75 | # Scrapy stuff: 76 | .scrapy 77 | 78 | # Sphinx documentation 79 | docs/_build/ 80 | 81 | # PyBuilder 82 | target/ 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # celery beat schedule file 88 | celerybeat-schedule 89 | 90 | # SageMath parsed files 91 | *.sage.py 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # Mr Developer 101 | .mr.developer.cfg 102 | .project 103 | .pydevproject 104 | 105 | # mkdocs documentation 106 | /site 107 | 108 | # mypy 109 | .mypy_cache/ 110 | .dmypy.json 111 | dmypy.json 112 | 113 | # Pyre type checker 114 | .pyre/ 115 | 116 | # End of https://www.gitignore.io/api/python 117 | 118 | # OSX files 119 | .DS_Store 120 | 121 | # Yarn cache 122 | .yarn/ 123 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | **/lib 4 | **/package.json 5 | !/package.json 6 | jupyterlab-transformers-completer 7 | jupyterlab 8 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Michał Krassowski 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jupyterlab-transformers-completer 2 | 3 | [![Extension status](https://img.shields.io/badge/status-experimental-red 'not ready to be used')](https://jupyterlab-contrib.github.io/) 4 | [![Build](https://github.com/krassowski/jupyterlab-transformers-completer/actions/workflows/build.yml/badge.svg)](https://github.com/krassowski/jupyterlab-transformers-completer/actions/workflows/build.yml) 5 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/krassowski/jupyterlab-transformers-completer/main?urlpath=lab) 6 | 7 | Inline completion provider using `transformers.js` for JupyterLab 8 | 9 | This extension is aimed for developers of JupyterLab extensions (and advanced JupyterLab users) to explore the integration of the inline completions API added in JupyterLab 4.1. 10 | 11 | All models linked from this demonstration run exclusively **in your browser**, and are: 12 | 13 | - order of magnitudes smaller than the state-of-the-art models, 14 | - producing correspondingly lower accuracy of suggestions/answers. 15 | 16 | These models are not vetted for accuracy nor propriety and should not be deployed without further validation. 17 | 18 | ![demo-transformers](https://github.com/krassowski/jupyterlab-transformers-completer/assets/5832902/c81ca9c1-925d-498d-8650-520f8a570f99) 19 | 20 | ### Usage 21 | 22 | 1. Go to Settings → Inline Completer → choose the models for code (in code cells and scripts) and text (in markdown cells and plain files) generation. 23 | 2. The models will be downloaded, compiled, and cached in your browser as indicated by pop-up notifications in bottom right corner. 24 | 3. Start typing a few words in the code cell or Markdown cell and observe the suggestions; hover over to see shortcuts. 25 | 4. Adjust model configuration in settings as needed; in particular increasing the repetition penalty, adjusting temperature and top _k_ is recommended. 26 | 27 | ### Known issues 28 | 29 | - Sometimes it is required to go to settings after installation and modify settings to trigger model download and compilation 30 | - Sometimes the browser will cache a faulty (e.g not fully downloaded) file resulting in Syntax Error when parsing; you can try in an incognito/private mode to confirm that this is transient and clear browser cache to remove such file. 31 | 32 | ## Requirements 33 | 34 | - JupyterLab >= 4.1.0 or Jupyter Notebook >= 7.1.0 35 | - A browser supporting: 36 | - [`SharedArrayBuffer`](https://caniuse.com/sharedarraybuffer) 37 | - [Web Workers](https://caniuse.com/webworkers) 38 | - Dynamic import for workers (behind `dom.workers.modules.enabled` in Firefox) 39 | - (optional, for faster inference) [WebGPU](https://caniuse.com/webgpu) (behind `dom.webgpu.enabled` in Firefox) 40 | - `jupyter-server` to enable additional headers (`jupyverse` and `jupyterlite` not tested yet) 41 | 42 | When this extension is enabled, the server will return additional headers, 43 | which will prevent fetching external resources, for example the extension logos 44 | from GitHub will no longer load in the extension panel. 45 | 46 | The additional headers are used to enable synchronous communication with WebWorker via `SharedArrayBuffer`: 47 | 48 | ```http 49 | Cross-Origin-Opener-Policy: same-origin, 50 | Cross-Origin-Embedder-Policy: require-corp 51 | ``` 52 | 53 | ## Install 54 | 55 | To install the extension, execute: 56 | 57 | ```bash 58 | pip install -U 'jupyterlab>=4.1.0' jupyterlab-transformers-completer 59 | ``` 60 | 61 | ## Uninstall 62 | 63 | To remove the extension, execute: 64 | 65 | ```bash 66 | pip uninstall jupyterlab-transformers-completer 67 | ``` 68 | 69 | ## Contributing 70 | 71 | ### Development install 72 | 73 | Note: You will need NodeJS to build the extension package. 74 | 75 | The `jlpm` command is JupyterLab's pinned version of 76 | [yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use 77 | `yarn` or `npm` in lieu of `jlpm` below. 78 | 79 | ```bash 80 | # Clone the repo to your local environment 81 | # Change directory to the jupyterlab-transformers-completer directory 82 | # Install package in development mode 83 | pip install -e "." 84 | # Link your development version of the extension with JupyterLab 85 | jupyter labextension develop . --overwrite 86 | # Rebuild extension Typescript source after making changes 87 | jlpm build 88 | ``` 89 | 90 | You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension. 91 | 92 | ```bash 93 | # Watch the source directory in one terminal, automatically rebuilding when needed 94 | jlpm watch 95 | # Run JupyterLab in another terminal 96 | jupyter lab 97 | ``` 98 | 99 | With the watch command running, every saved change will immediately be built locally and available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt). 100 | 101 | By default, the `jlpm build` command generates the source maps for this extension to make it easier to debug using the browser dev tools. To also generate source maps for the JupyterLab core extensions, you can run the following command: 102 | 103 | ```bash 104 | jupyter lab build --minimize=False 105 | ``` 106 | 107 | ### Development uninstall 108 | 109 | ```bash 110 | pip uninstall jupyterlab-transformers-completer 111 | ``` 112 | 113 | In development mode, you will also need to remove the symlink created by `jupyter labextension develop` 114 | command. To find its location, you can run `jupyter labextension list` to figure out where the `labextensions` 115 | folder is located. Then you can remove the symlink named `@jupyterlab/transformers-completer` within that folder. 116 | 117 | ### Packaging the extension 118 | 119 | See [RELEASE](RELEASE.md) 120 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Making a new release of jupyterlab-transformers-completer 2 | 3 | The extension can be published to `PyPI` and `npm` manually or using the [Jupyter Releaser](https://github.com/jupyter-server/jupyter_releaser). 4 | 5 | ## Manual release 6 | 7 | ### Python package 8 | 9 | This extension can be distributed as Python packages. All of the Python 10 | packaging instructions are in the `pyproject.toml` file to wrap your extension in a 11 | Python package. Before generating a package, you first need to install some tools: 12 | 13 | ```bash 14 | pip install build twine hatch 15 | ``` 16 | 17 | Bump the version using `hatch`. By default this will create a tag. 18 | See the docs on [hatch-nodejs-version](https://github.com/agoose77/hatch-nodejs-version#semver) for details. 19 | 20 | ```bash 21 | hatch version 22 | ``` 23 | 24 | Make sure to clean up all the development files before building the package: 25 | 26 | ```bash 27 | jlpm clean:all 28 | ``` 29 | 30 | You could also clean up the local git repository: 31 | 32 | ```bash 33 | git clean -dfX 34 | ``` 35 | 36 | To create a Python source package (`.tar.gz`) and the binary package (`.whl`) in the `dist/` directory, do: 37 | 38 | ```bash 39 | python -m build 40 | ``` 41 | 42 | > `python setup.py sdist bdist_wheel` is deprecated and will not work for this package. 43 | 44 | Then to upload the package to PyPI, do: 45 | 46 | ```bash 47 | twine upload dist/* 48 | ``` 49 | 50 | ### NPM package 51 | 52 | To publish the frontend part of the extension as a NPM package, do: 53 | 54 | ```bash 55 | npm login 56 | npm publish --access public 57 | ``` 58 | 59 | ## Automated releases with the Jupyter Releaser 60 | 61 | The extension repository should already be compatible with the Jupyter Releaser. 62 | 63 | Check out the [workflow documentation](https://jupyter-releaser.readthedocs.io/en/latest/get_started/making_release_from_repo.html) for more information. 64 | 65 | Here is a summary of the steps to cut a new release: 66 | 67 | - Add tokens to the [Github Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) in the repository: 68 | - `ADMIN_GITHUB_TOKEN` (with "public_repo" and "repo:status" permissions); see the [documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) 69 | - `NPM_TOKEN` (with "automation" permission); see the [documentation](https://docs.npmjs.com/creating-and-viewing-access-tokens) 70 | - Set up PyPI 71 | 72 |
Using PyPI trusted publisher (modern way) 73 | 74 | - Set up your PyPI project by [adding a trusted publisher](https://docs.pypi.org/trusted-publishers/adding-a-publisher/) 75 | - The _workflow name_ is `publish-release.yml` and the _environment_ should be left blank. 76 | - Ensure the publish release job as `permissions`: `id-token : write` (see the [documentation](https://docs.pypi.org/trusted-publishers/using-a-publisher/)) 77 | 78 |
79 | 80 |
Using PyPI token (legacy way) 81 | 82 | - If the repo generates PyPI release(s), create a scoped PyPI [token](https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/#saving-credentials-on-github). We recommend using a scoped token for security reasons. 83 | 84 | - You can store the token as `PYPI_TOKEN` in your fork's `Secrets`. 85 | 86 | - Advanced usage: if you are releasing multiple repos, you can create a secret named `PYPI_TOKEN_MAP` instead of `PYPI_TOKEN` that is formatted as follows: 87 | 88 | ```text 89 | owner1/repo1,token1 90 | owner2/repo2,token2 91 | ``` 92 | 93 | If you have multiple Python packages in the same repository, you can point to them as follows: 94 | 95 | ```text 96 | owner1/repo1/path/to/package1,token1 97 | owner1/repo1/path/to/package2,token2 98 | ``` 99 | 100 |
101 | 102 | - Go to the Actions panel 103 | - Run the "Step 1: Prep Release" workflow 104 | - Check the draft changelog 105 | - Run the "Step 2: Publish Release" workflow 106 | 107 | ## Publishing to `conda-forge` 108 | 109 | If the package is not on conda forge yet, check the documentation to learn how to add it: https://conda-forge.org/docs/maintainer/adding_pkgs.html 110 | 111 | Otherwise a bot should pick up the new version publish to PyPI, and open a new PR on the feedstock repository automatically. 112 | -------------------------------------------------------------------------------- /binder/environment.yml: -------------------------------------------------------------------------------- 1 | # a mybinder.org-ready environment for demoing jupyterlab-transformers-completer 2 | # this environment may also be used locally on Linux/MacOS/Windows, e.g. 3 | # 4 | # conda env update --file binder/environment.yml 5 | # conda activate jupyterlab-transformers-completer-demo 6 | # 7 | name: jupyterlab-transformers-completer-demo 8 | 9 | channels: 10 | - conda-forge 11 | 12 | dependencies: 13 | # runtime dependencies 14 | - python >=3.10,<3.11.0a0 15 | - jupyterlab >=4.2.2,<5 16 | # lab dev 17 | # https://github.com/conda-forge/hatch-feedstock/pull/22#issuecomment-2106302514 18 | - hatch <1.10.0 19 | - build 20 | # labextension build dependencies 21 | - nodejs >=20,<21 22 | - pip 23 | - wheel 24 | # additional packages for demos 25 | # - ipywidgets 26 | -------------------------------------------------------------------------------- /binder/postBuild: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ perform a development install of jupyterlab-transformers-completer 3 | 4 | On Binder, this will run _after_ the environment has been fully created from 5 | the environment.yml in this directory. 6 | 7 | This script should also run locally on Linux/MacOS/Windows: 8 | 9 | python3 binder/postBuild 10 | """ 11 | import subprocess 12 | import sys 13 | from pathlib import Path 14 | import os 15 | 16 | ROOT = Path.cwd() 17 | 18 | def _(*args, **kwargs): 19 | """ Run a command, echoing the args 20 | 21 | fails hard if something goes wrong 22 | """ 23 | print("\n\t", " ".join(args), "\n") 24 | return_code = subprocess.call(args, **kwargs) 25 | if return_code != 0: 26 | print("\nERROR", return_code, " ".join(args)) 27 | sys.exit(return_code) 28 | 29 | # verify the environment is self-consistent before even starting 30 | _(sys.executable, "-m", "pip", "check") 31 | 32 | # install jupyterlab pre-release 33 | _(sys.executable, "-m", "pip", "install", "-U", "--pre", "jupyterlab>=4.1.0,<5") 34 | 35 | # install the labextension 36 | _(sys.executable, "-m", "pip", "install", "-e", ".") 37 | _(sys.executable, "-m", "jupyter", "labextension", "develop", "--overwrite", ".") 38 | 39 | # verify the environment the extension didn't break anything 40 | _(sys.executable, "-m", "pip", "check") 41 | 42 | # list the extensions 43 | _("jupyter", "server", "extension", "list") 44 | 45 | # initially list installed extensions to determine if there are any surprises 46 | _("jupyter", "labextension", "list") 47 | 48 | 49 | print("JupyterLab with jupyterlab-transformers-completer is ready to run with:\n") 50 | print("\tjupyter lab\n") 51 | -------------------------------------------------------------------------------- /install.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageManager": "python", 3 | "packageName": "jupyterlab-transformers-completer", 4 | "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package jupyterlab-transformers-completer" 5 | } 6 | -------------------------------------------------------------------------------- /jupyter-config/server-config/jupyterlab_transformers_completer.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServerApp": { 3 | "jpserver_extensions": { 4 | "jupyterlab_transformers_completer": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jupyterlab_transformers_completer/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | from ._version import __version__ 3 | except ImportError: 4 | # Fallback when using the package in dev mode without installing 5 | # in editable mode with pip. It is highly recommended to install 6 | # the package from a stable release or in editable mode: https://pip.pypa.io/en/stable/topics/local-project-installs/#editable-installs 7 | import warnings 8 | warnings.warn("Importing 'jupyterlab-transformers-completer' outside a proper installation.") 9 | __version__ = "dev" 10 | 11 | 12 | def _jupyter_labextension_paths(): 13 | return [{ 14 | "src": "labextension", 15 | "dest": "@jupyterlab/transformers-completer" 16 | }] 17 | 18 | 19 | def _jupyter_server_extension_points(): 20 | return [{ 21 | "module": "jupyterlab_transformers_completer" 22 | }] 23 | 24 | 25 | def _load_jupyter_server_extension(server_app): 26 | """ 27 | Parameters 28 | ---------- 29 | server_app: jupyterlab.labapp.LabApp 30 | JupyterLab application instance 31 | """ 32 | if "headers" not in server_app.web_app.settings: 33 | server_app.web_app.settings["headers"] = {} 34 | server_app.web_app.settings["headers"].update({ 35 | # Allow access to `SharedArrayBuffer`. 36 | "Cross-Origin-Opener-Policy": "same-origin", 37 | "Cross-Origin-Embedder-Policy": "require-corp", 38 | }) 39 | name = "@jupyterlab/transformers-completer" 40 | server_app.log.info(f"Registered {name} server extension") 41 | 42 | 43 | # For backward compatibility with notebook server - useful for Binder/JupyterHub 44 | load_jupyter_server_extension = _load_jupyter_server_extension 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jupyterlab/transformers-completer", 3 | "version": "0.3.0", 4 | "description": "Inline completion provider using tranformers.js for JupyterLab.", 5 | "keywords": [ 6 | "jupyter", 7 | "jupyterlab", 8 | "jupyterlab-extension" 9 | ], 10 | "homepage": "https://github.com/krassowski/jupyterlab-transformers-completer", 11 | "bugs": { 12 | "url": "https://github.com/krassowski/jupyterlab-transformers-completer/issues" 13 | }, 14 | "license": "BSD-3-Clause", 15 | "author": { 16 | "name": "Michał Krassowski", 17 | "email": "mkrassowski@quansight.com" 18 | }, 19 | "files": [ 20 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", 21 | "style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}" 22 | ], 23 | "main": "lib/index.js", 24 | "types": "lib/index.d.ts", 25 | "style": "style/index.css", 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/krassowski/jupyterlab-transformers-completer.git" 29 | }, 30 | "scripts": { 31 | "build": "jlpm build:lib && jlpm build:labextension:dev", 32 | "build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension", 33 | "build:labextension": "jupyter labextension build .", 34 | "build:labextension:dev": "jupyter labextension build --development True .", 35 | "build:lib": "tsc --sourceMap", 36 | "build:lib:prod": "tsc", 37 | "clean": "jlpm clean:lib", 38 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo", 39 | "clean:lintcache": "rimraf .eslintcache .stylelintcache", 40 | "clean:labextension": "rimraf jupyterlab-transformers-completer/labextension jupyterlab-transformers-completer/_version.py", 41 | "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache", 42 | "eslint": "jlpm eslint:check --fix", 43 | "eslint:check": "eslint . --cache --ext .ts,.tsx", 44 | "install:extension": "jlpm build", 45 | "lint": "jlpm stylelint && jlpm prettier && jlpm eslint", 46 | "lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check", 47 | "prettier": "jlpm prettier:base --write --list-different", 48 | "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", 49 | "prettier:check": "jlpm prettier:base --check", 50 | "stylelint": "jlpm stylelint:check --fix", 51 | "stylelint:check": "stylelint --cache \"style/**/*.css\"", 52 | "watch": "run-p watch:src watch:labextension", 53 | "watch:src": "tsc -w --sourceMap", 54 | "watch:labextension": "jupyter labextension watch ." 55 | }, 56 | "packageManager": "yarn@3.5.0", 57 | "dependencies": { 58 | "@jupyterlab/application": "^4.1.0", 59 | "@jupyterlab/completer": "^4.1.0", 60 | "@xenova/transformers": "~2.17.1" 61 | }, 62 | "devDependencies": { 63 | "@jupyterlab/builder": "^4.1.0", 64 | "@types/json-schema": "^7.0.11", 65 | "@types/react": "^18.0.26", 66 | "@typescript-eslint/eslint-plugin": "^6.1.0", 67 | "@typescript-eslint/parser": "^6.1.0", 68 | "css-loader": "^6.7.1", 69 | "eslint": "^8.36.0", 70 | "eslint-config-prettier": "^8.8.0", 71 | "eslint-plugin-prettier": "^5.0.0", 72 | "npm-run-all": "^4.1.5", 73 | "prettier": "^3.0.0", 74 | "rimraf": "^5.0.1", 75 | "source-map-loader": "^1.0.2", 76 | "style-loader": "^3.3.1", 77 | "stylelint": "^15.10.1", 78 | "stylelint-config-recommended": "^13.0.0", 79 | "stylelint-config-standard": "^34.0.0", 80 | "stylelint-csstree-validator": "^3.0.0", 81 | "stylelint-prettier": "^4.0.0", 82 | "typescript": "~5.0.2", 83 | "yjs": "^13.5.0" 84 | }, 85 | "sideEffects": [ 86 | "style/*.css", 87 | "style/index.js" 88 | ], 89 | "styleModule": "style/index.js", 90 | "publishConfig": { 91 | "access": "public" 92 | }, 93 | "jupyterlab": { 94 | "extension": true, 95 | "outputDir": "jupyterlab_transformers_completer/labextension" 96 | }, 97 | "eslintIgnore": [ 98 | "node_modules", 99 | "dist", 100 | "coverage", 101 | "jupyterlab", 102 | "**/*.d.ts" 103 | ], 104 | "eslintConfig": { 105 | "extends": [ 106 | "eslint:recommended", 107 | "plugin:@typescript-eslint/eslint-recommended", 108 | "plugin:@typescript-eslint/recommended", 109 | "plugin:prettier/recommended" 110 | ], 111 | "parser": "@typescript-eslint/parser", 112 | "parserOptions": { 113 | "project": "tsconfig.json", 114 | "sourceType": "module" 115 | }, 116 | "plugins": [ 117 | "@typescript-eslint" 118 | ], 119 | "rules": { 120 | "@typescript-eslint/naming-convention": [ 121 | "error", 122 | { 123 | "selector": "interface", 124 | "format": [ 125 | "PascalCase" 126 | ], 127 | "custom": { 128 | "regex": "^I[A-Z]", 129 | "match": true 130 | } 131 | } 132 | ], 133 | "@typescript-eslint/no-unused-vars": [ 134 | "warn", 135 | { 136 | "args": "none" 137 | } 138 | ], 139 | "@typescript-eslint/no-explicit-any": "off", 140 | "@typescript-eslint/no-namespace": "off", 141 | "@typescript-eslint/no-use-before-define": "off", 142 | "@typescript-eslint/quotes": [ 143 | "error", 144 | "single", 145 | { 146 | "avoidEscape": true, 147 | "allowTemplateLiterals": false 148 | } 149 | ], 150 | "curly": [ 151 | "error", 152 | "all" 153 | ], 154 | "eqeqeq": "error", 155 | "prefer-arrow-callback": "error" 156 | } 157 | }, 158 | "prettier": { 159 | "singleQuote": true, 160 | "trailingComma": "none", 161 | "arrowParens": "avoid", 162 | "endOfLine": "auto", 163 | "overrides": [ 164 | { 165 | "files": "package.json", 166 | "options": { 167 | "tabWidth": 4 168 | } 169 | } 170 | ] 171 | }, 172 | "stylelint": { 173 | "extends": [ 174 | "stylelint-config-recommended", 175 | "stylelint-config-standard", 176 | "stylelint-prettier/recommended" 177 | ], 178 | "plugins": [ 179 | "stylelint-csstree-validator" 180 | ], 181 | "rules": { 182 | "csstree/validator": true, 183 | "property-no-vendor-prefix": null, 184 | "selector-no-vendor-prefix": null, 185 | "value-no-vendor-prefix": null 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "hatchling>=1.5.0", 4 | "jupyterlab>=4.1.0,<5", 5 | "hatch-nodejs-version>=0.3.2" 6 | ] 7 | build-backend = "hatchling.build" 8 | 9 | [project] 10 | name = "jupyterlab-transformers-completer" 11 | readme = "README.md" 12 | license = { file = "LICENSE" } 13 | requires-python = ">=3.8" 14 | classifiers = [ 15 | "Framework :: Jupyter", 16 | "Framework :: Jupyter :: JupyterLab", 17 | "Framework :: Jupyter :: JupyterLab :: 4", 18 | "Framework :: Jupyter :: JupyterLab :: Extensions", 19 | "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt", 20 | "License :: OSI Approved :: BSD License", 21 | "Programming Language :: Python", 22 | "Programming Language :: Python :: 3", 23 | "Programming Language :: Python :: 3.8", 24 | "Programming Language :: Python :: 3.9", 25 | "Programming Language :: Python :: 3.10", 26 | "Programming Language :: Python :: 3.11", 27 | ] 28 | dependencies = [ 29 | "jupyterlab_server>=2.25.2", 30 | "jupyterlab>=4.1.0,<5" 31 | ] 32 | dynamic = ["version", "description", "authors", "urls", "keywords"] 33 | 34 | [tool.hatch.version] 35 | source = "nodejs" 36 | 37 | [tool.hatch.metadata.hooks.nodejs] 38 | fields = ["description", "authors", "urls"] 39 | 40 | [tool.hatch.build.targets.sdist] 41 | artifacts = ["jupyterlab_transformers_completer/labextension"] 42 | exclude = [".github", "binder"] 43 | 44 | [tool.hatch.build.targets.wheel.shared-data] 45 | "jupyterlab_transformers_completer/labextension" = "share/jupyter/labextensions/@jupyterlab/transformers-completer" 46 | "install.json" = "share/jupyter/labextensions/@jupyterlab/transformers-completer/install.json" 47 | "jupyter-config/server-config" = "etc/jupyter/jupyter_server_config.d" 48 | 49 | [tool.hatch.build.hooks.version] 50 | path = "jupyterlab_transformers_completer/_version.py" 51 | 52 | [tool.hatch.build.hooks.jupyter-builder] 53 | dependencies = ["hatch-jupyter-builder>=0.5"] 54 | build-function = "hatch_jupyter_builder.npm_builder" 55 | ensured-targets = [ 56 | "jupyterlab_transformers_completer/labextension/static/style.js", 57 | "jupyterlab_transformers_completer/labextension/package.json", 58 | ] 59 | skip-if-exists = ["jupyterlab_transformers_completer/labextension/static/style.js"] 60 | 61 | [tool.hatch.build.hooks.jupyter-builder.build-kwargs] 62 | build_cmd = "build:prod" 63 | npm = ["jlpm"] 64 | 65 | [tool.hatch.build.hooks.jupyter-builder.editable-build-kwargs] 66 | build_cmd = "install:extension" 67 | npm = ["jlpm"] 68 | source_dir = "src" 69 | build_dir = "jupyterlab_transformers_completer/labextension" 70 | 71 | [tool.jupyter-releaser.options] 72 | version_cmd = "hatch version" 73 | 74 | [tool.jupyter-releaser.hooks] 75 | before-build-npm = [ 76 | "python -m pip install 'jupyterlab>=4.1.0,<5'", 77 | "jlpm", 78 | "jlpm build:prod" 79 | ] 80 | before-build-python = ["jlpm clean:all"] 81 | 82 | [tool.check-wheel-contents] 83 | ignore = ["W002"] 84 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | __import__("setuptools").setup() 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | JupyterFrontEnd, 3 | JupyterFrontEndPlugin 4 | } from '@jupyterlab/application'; 5 | import { 6 | ICompletionProviderManager, 7 | IInlineCompletionProvider, 8 | IInlineCompletionContext, 9 | CompletionHandler, 10 | IInlineCompletionList, 11 | IInlineCompletionItem 12 | } from '@jupyterlab/completer'; 13 | import type { ISettingRegistry } from '@jupyterlab/settingregistry'; 14 | import { Notification, showErrorMessage } from '@jupyterlab/apputils'; 15 | import { JSONValue, PromiseDelegate } from '@lumino/coreutils'; 16 | import type { 17 | ClientMessage, 18 | WorkerMessage, 19 | IModelSettings, 20 | ITransformersSettings 21 | } from './types'; 22 | import { formatFileSize } from './utils'; 23 | import { IModelInfo, codeModels, textModels } from './models'; 24 | 25 | interface ISettings extends IModelSettings, ITransformersSettings { 26 | codeModel: string; 27 | textModel: string; 28 | maxContextWindow: number; 29 | } 30 | 31 | const DEFAULT_SETTINGS: ISettings = { 32 | codeModel: 'Xenova/tiny_starcoder_py', 33 | textModel: 'Xenova/gpt2', 34 | temperature: 0.5, 35 | doSample: false, 36 | topK: 5, 37 | maxNewTokens: 50, 38 | generateN: 2, 39 | maxContextWindow: 512, 40 | diversityPenalty: 1, 41 | repetitionPenalty: 1, 42 | allowLocalModels: false, 43 | allowRemoteModels: true, 44 | remoteHost: 'https://huggingface.co/', 45 | localModelPath: '/models/' 46 | }; 47 | 48 | class TransformersInlineProvider implements IInlineCompletionProvider { 49 | readonly identifier = '@krassowski/inline-completer'; 50 | readonly name = 'Transformers powered completions'; 51 | 52 | constructor(protected options: TransformersInlineProvider.IOptions) { 53 | try { 54 | SharedArrayBuffer; 55 | } catch (e) { 56 | showErrorMessage( 57 | 'SharedArrayBuffer not available', 58 | 'Server extension enabling `same-origin` and `require-corp` headers is required for jupyterlab-transformers-completer to access `SharedArrayBuffer` which is used to synchronously communicate with the language model WebWorker.' 59 | ); 60 | } 61 | const buffer = new SharedArrayBuffer(1024); 62 | this._sharedArray = new Int32Array(buffer); 63 | options.worker.addEventListener( 64 | 'message', 65 | this._onMessageReceived.bind(this) 66 | ); 67 | this._workerStarted.promise.then(() => { 68 | this._postMessage({ 69 | action: 'initializeBuffer', 70 | buffer: buffer 71 | }); 72 | }); 73 | } 74 | 75 | get schema(): ISettingRegistry.IProperty { 76 | return { 77 | properties: { 78 | codeModel: { 79 | title: 'Code model', 80 | description: 'Model used in code cells and code files.', 81 | oneOf: [ 82 | { const: 'none', title: 'No model' }, 83 | ...codeModels.map(this._formatModelOptions) 84 | ], 85 | type: 'string' 86 | }, 87 | textModel: { 88 | title: 'Text model', 89 | description: 90 | 'Model used in Markdown (cells and files) and plain text files.', 91 | oneOf: [ 92 | { const: 'none', title: 'No model' }, 93 | ...textModels.map(this._formatModelOptions) 94 | ], 95 | type: 'string' 96 | }, 97 | // TODO temperature and friends should be per-model 98 | temperature: { 99 | minimum: 0, 100 | maximum: 1, 101 | type: 'number', 102 | title: 'Temperature', 103 | description: 'The value used to module the next token probabilities.' 104 | }, 105 | doSample: { 106 | type: 'boolean', 107 | description: 'Whether to use sampling; use greedy decoding otherwise.' 108 | }, 109 | topK: { 110 | minimum: 0, 111 | maximum: 50, 112 | type: 'number', 113 | title: 'Top k', 114 | description: 115 | 'The number of highest probability vocabulary tokens to keep for top-k-filtering.' 116 | }, 117 | maxNewTokens: { 118 | minimum: 1, 119 | maximum: 512, 120 | type: 'number', 121 | title: 'Tokens limit', 122 | description: 'Maximum number of new tokens.' 123 | }, 124 | generateN: { 125 | minimum: 1, 126 | type: 'number', 127 | title: 'Candidates', 128 | description: 'How many completion candidates should be generated.' 129 | }, 130 | diversityPenalty: { 131 | type: 'number', 132 | title: 'Diversity penalty', 133 | description: '1.0 means not penalty.' 134 | }, 135 | repetitionPenalty: { 136 | type: 'number', 137 | title: 'Repetition penalty', 138 | description: '1.0 means not penalty.' 139 | }, 140 | // TODO: characters are a poor proxy for number of tokens when whitespace are many (though a strictly conservative one). 141 | // Words could be better but can be over-optimistic - one word can be several tokens). 142 | maxContextWindow: { 143 | title: 'Context window', 144 | minimum: 1, 145 | type: 'number', 146 | description: 147 | 'At most how many characters should be provided to the model. Smaller context results in faster generation at a cost of less accurate suggestions.' 148 | }, 149 | allowLocalModels: { 150 | type: 'boolean', 151 | title: 'Check Local Model Database', 152 | default: false, 153 | description: 154 | 'Whether to allow downloading models from local database of models. If set to false (default) the models will be downloaded from remote host.' 155 | }, 156 | allowRemoteModels: { 157 | type: 'boolean', 158 | title: 'Check Remote Model Database', 159 | default: true, 160 | description: 161 | 'Whether to allow downloading models from remote database of models.' 162 | }, 163 | localModelPath: { 164 | type: 'string', 165 | title: 'Local Model Database Path', 166 | default: '/models/' 167 | }, 168 | remoteHost: { 169 | type: 'string', 170 | title: 'Remote Host', 171 | description: 'The remote host from which to download the models.', 172 | default: 'https://huggingface.co/' 173 | } 174 | }, 175 | default: DEFAULT_SETTINGS as any 176 | }; 177 | } 178 | 179 | async configure(settings: { [property: string]: JSONValue }): Promise { 180 | this._settings = settings as any as ISettings; 181 | await this._workerStarted.promise; 182 | this._postMessage({ 183 | action: 'configure', 184 | allowLocalModels: this._settings.allowLocalModels, 185 | allowRemoteModels: this._settings.allowRemoteModels, 186 | localModelPath: this._settings.localModelPath, 187 | remoteHost: this._settings.remoteHost 188 | }); 189 | this._switchModel(this._settings.codeModel, 'code'); 190 | this._switchModel(this._settings.textModel, 'text'); 191 | } 192 | 193 | async fetch( 194 | request: CompletionHandler.IRequest, 195 | context: IInlineCompletionContext 196 | ): Promise> { 197 | const textMimeTypes = [ 198 | 'text/x-ipythongfm', 199 | 'text/x-markdown', 200 | 'text/plain', 201 | 'text/x-rst', 202 | 'text/x-latex', 203 | 'text/x-rsrc' 204 | ]; 205 | const isText = textMimeTypes.includes(request.mimeType!); 206 | // TODO add a setting to only invoke on text if explicitly asked (triggerKind = invoke) 207 | const model = isText ? this._settings.textModel : this._settings.codeModel; 208 | 209 | await this._ready[model].promise; 210 | this._abortPrevious(); 211 | this._streamPromises = new Map(); 212 | 213 | const prefix = this._prefixFromRequest(request); 214 | const items: IInlineCompletionItem[] = []; 215 | const idTokens = []; 216 | for (let i = 0; i < this._settings.generateN; i++) { 217 | const token = 'T' + ++this._tokenCounter; 218 | idTokens.push(token); 219 | items.push({ 220 | insertText: '', 221 | isIncomplete: true, 222 | token: token 223 | }); 224 | } 225 | this._postMessage({ 226 | model, 227 | text: prefix, 228 | maxNewTokens: this._settings.maxNewTokens, 229 | temperature: this._settings.temperature, 230 | topK: this._settings.topK, 231 | doSample: this._settings.doSample, 232 | generateN: this._settings.generateN, 233 | repetitionPenalty: this._settings.repetitionPenalty, 234 | diversityPenalty: this._settings.diversityPenalty, 235 | idTokens, 236 | action: 'generate', 237 | counter: this._currentGeneration 238 | }); 239 | return { items }; 240 | } 241 | 242 | /** 243 | * Stream a reply for completion identified by given `token`. 244 | */ 245 | async *stream(token: string) { 246 | let done = false; 247 | while (!done) { 248 | const delegate = new PromiseDelegate(); 249 | this._streamPromises.set(token, delegate); 250 | const promise = delegate.promise; 251 | yield promise; 252 | done = (await promise).done; 253 | } 254 | } 255 | 256 | /** 257 | * Handle message from the web worker. 258 | */ 259 | private _onMessageReceived(event: MessageEvent) { 260 | const data = event.data; 261 | switch (data.status) { 262 | case 'worker-started': 263 | this._msgWorkerStarted(data as WorkerMessage.IWorkerStarted); 264 | break; 265 | case 'initiate': 266 | this._msgInitiate(data as WorkerMessage.IInitiate); 267 | break; 268 | case 'progress': 269 | this._msgProgress(data as WorkerMessage.IProgress); 270 | break; 271 | case 'done': 272 | this._msgDone(data as WorkerMessage.IDone); 273 | break; 274 | case 'ready': 275 | this._msgReady(data as WorkerMessage.IReady); 276 | break; 277 | case 'update': 278 | this._msgUpdate(data as WorkerMessage.IUpdate); 279 | break; 280 | case 'complete': 281 | this._msgComplete(data as WorkerMessage.IComplete); 282 | break; 283 | case 'interrupted': 284 | this._msgInterrupted(data as WorkerMessage.IGenerationError); 285 | break; 286 | case 'exception': 287 | this._msgException(data as WorkerMessage.IGenerationError); 288 | break; 289 | } 290 | } 291 | 292 | private _msgWorkerStarted(_data: WorkerMessage.IWorkerStarted) { 293 | this._workerStarted.resolve(undefined); 294 | } 295 | 296 | private _msgInitiate(data: WorkerMessage.IInitiate) { 297 | this._ready[data.model] = new PromiseDelegate(); 298 | const message = `Loading ${data.model}: ${data.file}`; 299 | if (this._loadingNotifications[data.model]) { 300 | Notification.update({ 301 | id: this._loadingNotifications[data.model], 302 | message, 303 | autoClose: false 304 | }); 305 | } else { 306 | this._loadingNotifications[data.model] = Notification.emit( 307 | message, 308 | 'in-progress', 309 | { autoClose: false } 310 | ); 311 | } 312 | } 313 | 314 | private _msgProgress(data: WorkerMessage.IProgress) { 315 | Notification.update({ 316 | id: this._loadingNotifications[data.model], 317 | message: `Loading ${data.model}: ${data.file} ${Math.round( 318 | data.progress 319 | )}% (${formatFileSize(data.loaded, 1)}/${formatFileSize(data.total)})`, 320 | type: 'in-progress', 321 | autoClose: false, 322 | progress: data.progress / 100 323 | }); 324 | } 325 | 326 | private _msgDone(data: WorkerMessage.IDone) { 327 | Notification.update({ 328 | id: this._loadingNotifications[data.model], 329 | message: `Loaded ${data.file} for ${data.model}, compiling...`, 330 | type: 'success', 331 | autoClose: false 332 | }); 333 | } 334 | 335 | private _msgReady(data: WorkerMessage.IReady) { 336 | Notification.dismiss(this._loadingNotifications[data.model]); 337 | this._ready[data.model].resolve(void 0); 338 | } 339 | 340 | private _msgUpdate(data: WorkerMessage.IUpdate) { 341 | this._tickWorker(); 342 | const token = data.idToken; 343 | const delegate = this._streamPromises.get(token); 344 | if (!delegate) { 345 | console.warn('Completion updated but stream absent'); 346 | } else { 347 | delegate.resolve({ 348 | done: false, 349 | response: { 350 | insertText: data.output 351 | } 352 | }); 353 | } 354 | } 355 | 356 | private _msgComplete(data: WorkerMessage.IComplete) { 357 | const token = data.idToken; 358 | const delegate = this._streamPromises.get(token); 359 | if (!delegate) { 360 | console.warn('Completion done but stream absent'); 361 | } else { 362 | delegate.resolve({ 363 | done: true, 364 | response: { 365 | insertText: data.output 366 | } 367 | }); 368 | } 369 | this._streamPromises.delete(token); 370 | } 371 | 372 | private _msgInterrupted(data: WorkerMessage.IGenerationError) { 373 | // handle interruption 374 | for (const token of data.idTokens) { 375 | const delegate = this._streamPromises.get(token); 376 | if (delegate) { 377 | delegate.reject(null); 378 | } 379 | this._streamPromises.delete(token); 380 | } 381 | } 382 | 383 | private _msgException(data: WorkerMessage.IGenerationError) { 384 | Notification.error(`Worker error: ${data.error?.message}`); 385 | console.error(data); 386 | } 387 | 388 | /** 389 | * Summarise model for display in user settings. 390 | */ 391 | private _formatModelOptions(model: IModelInfo) { 392 | const modelName = model.repo.replace('Xenova/', ''); 393 | return { 394 | const: model.repo, 395 | title: `${modelName} (${model.licence})` 396 | }; 397 | } 398 | 399 | /** 400 | * Send a tick to the worker with number of current generation counter. 401 | */ 402 | private _tickWorker() { 403 | Atomics.store(this._sharedArray, 0, this._currentGeneration); 404 | Atomics.notify(this._sharedArray, 0, 1); 405 | } 406 | 407 | /** 408 | * Communicate to the worker that previous suggestion no longer needs to be generated. 409 | */ 410 | private _abortPrevious() { 411 | this._currentGeneration++; 412 | this._tickWorker(); 413 | } 414 | 415 | /** 416 | * Extract prefix from request, accounting for context window limit. 417 | */ 418 | private _prefixFromRequest(request: CompletionHandler.IRequest): string { 419 | const textBefore = request.text.slice(0, request.offset); 420 | const prefix = textBefore.slice( 421 | -Math.min(this._settings.maxContextWindow, textBefore.length) 422 | ); 423 | return prefix; 424 | } 425 | 426 | /** 427 | * A type-guarded shorthand to post message to the worker. 428 | */ 429 | private _postMessage(message: ClientMessage.Message) { 430 | this.options.worker.postMessage(message); 431 | } 432 | 433 | /** 434 | * Switch generative model for given `type` of content. 435 | */ 436 | private _switchModel(newModel: string, type: 'code' | 'text') { 437 | const oldModel = this._currentModels[type]; 438 | if (oldModel === newModel) { 439 | return; 440 | } 441 | if (oldModel) { 442 | this._postMessage({ 443 | action: 'disposeModel', 444 | model: oldModel 445 | }); 446 | } 447 | if (newModel !== 'none') { 448 | this._postMessage({ 449 | action: 'initializeModel', 450 | model: newModel 451 | }); 452 | } 453 | this._currentModels[type] = newModel; 454 | } 455 | 456 | private _currentGeneration = 0; 457 | private _currentModels: { 458 | code?: string; 459 | text?: string; 460 | } = {}; 461 | private _loadingNotifications: Record = {}; 462 | private _ready: Record> = {}; 463 | private _settings: ISettings = DEFAULT_SETTINGS; 464 | private _sharedArray: Int32Array; 465 | private _streamPromises: Map> = new Map(); 466 | private _tokenCounter = 0; 467 | private _workerStarted = new PromiseDelegate(); 468 | } 469 | 470 | namespace TransformersInlineProvider { 471 | export interface IOptions { 472 | worker: Worker; 473 | } 474 | } 475 | 476 | interface IStream { 477 | done: boolean; 478 | response: IInlineCompletionItem; 479 | } 480 | 481 | /** 482 | * Initialization data for the @jupyterlab/transformers-completer extension. 483 | */ 484 | const plugin: JupyterFrontEndPlugin = { 485 | id: '@jupyterlab/transformers-completer:plugin', 486 | description: 'An in-browser AI completion provider for JupyterLab.', 487 | requires: [ICompletionProviderManager], 488 | autoStart: true, 489 | activate: ( 490 | app: JupyterFrontEnd, 491 | providerManager: ICompletionProviderManager 492 | ) => { 493 | const worker = new Worker(new URL('./worker.js', import.meta.url)); 494 | const provider = new TransformersInlineProvider({ worker }); 495 | providerManager.registerInlineProvider(provider); 496 | } 497 | }; 498 | 499 | export default plugin; 500 | -------------------------------------------------------------------------------- /src/models.ts: -------------------------------------------------------------------------------- 1 | export interface IModelInfo { 2 | repo: string; 3 | licence: string; 4 | humanEval?: number; 5 | } 6 | 7 | /** 8 | * To update the list of models, compare with: 9 | * https://huggingface.co/models?pipeline_tag=text-generation&library=transformers.js 10 | */ 11 | export const codeModels: IModelInfo[] = [ 12 | { 13 | repo: 'Xenova/tiny_starcoder_py', 14 | licence: 'bigcode-openrail-m', 15 | humanEval: 7.84 16 | }, 17 | { 18 | repo: 'Xenova/codegen-350M-mono', 19 | licence: 'bsd-3-clause' 20 | }, 21 | { 22 | repo: 'Xenova/codegen-350M-multi', 23 | licence: 'bsd-3-clause' 24 | }, 25 | { 26 | repo: 'Xenova/starcoderbase-1b-sft', 27 | licence: '???', 28 | humanEval: 39 29 | }, 30 | { 31 | repo: 'Xenova/WizardCoder-1B-V1.0', 32 | licence: 'bigcode-openrail-m', 33 | humanEval: 23.8 34 | }, 35 | { 36 | repo: 'Xenova/J-350M', 37 | licence: 'bsd-3-clause' 38 | } 39 | ]; 40 | 41 | export const textModels: IModelInfo[] = [ 42 | { 43 | repo: 'Xenova/gpt2', 44 | licence: 'mit' 45 | }, 46 | { 47 | repo: 'Xenova/TinyLLama-v0', 48 | licence: 'apache-2.0' 49 | }, 50 | { 51 | repo: 'Xenova/dlite-v2-774m', 52 | licence: 'apache-2.0' 53 | }, 54 | { 55 | repo: 'Xenova/LaMini-GPT-124M', 56 | licence: 'cc-by-nc-4.0' 57 | }, 58 | { 59 | repo: 'Xenova/LaMini-Cerebras-111M', 60 | licence: 'cc-by-nc-4.0' 61 | }, 62 | { 63 | repo: 'Xenova/LaMini-Cerebras-256M', 64 | licence: 'cc-by-nc-4.0' 65 | }, 66 | { 67 | repo: 'Xenova/LaMini-Cerebras-590M', 68 | licence: 'cc-by-nc-4.0' 69 | }, 70 | { 71 | repo: 'Xenova/opt-125m', 72 | licence: 'other' 73 | }, 74 | { 75 | repo: 'Xenova/pythia-70m-deduped', 76 | licence: 'apache-2.0' 77 | }, 78 | { 79 | repo: 'Xenova/distilgpt2', 80 | licence: 'apache-2.0' 81 | }, 82 | { 83 | repo: 'Xenova/llama-160m', 84 | licence: 'other' 85 | }, 86 | { 87 | repo: 'Xenova/Qwen1.5-0.5B-Chat', 88 | licence: 'tongyi-qianwen-research' 89 | }, 90 | { 91 | repo: 'Xenova/bloom-560m', 92 | licence: 'bigscience-bloom-rail-1.0' 93 | } 94 | ]; 95 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { env } from '@xenova/transformers'; 2 | import type * as transformersModuleNamespace from '@xenova/transformers'; 3 | type MutableEnvironment = { 4 | -readonly [K in keyof typeof transformersModuleNamespace.env]: (typeof transformersModuleNamespace.env)[K]; 5 | }; 6 | export type transformersModule = { 7 | env: MutableEnvironment; 8 | pipeline: typeof transformersModuleNamespace.pipeline; 9 | }; 10 | 11 | export interface IModelSettings { 12 | temperature: number; 13 | maxNewTokens: number; 14 | topK: number; 15 | doSample: boolean; 16 | repetitionPenalty: number; 17 | diversityPenalty: number; 18 | // this a default for `num_return_sequences`, `num_beams` and `num_beam_groups`. 19 | generateN: number; 20 | } 21 | 22 | export interface ITransformersSettings { 23 | allowLocalModels: (typeof env)['allowLocalModels']; 24 | allowRemoteModels: (typeof env)['allowRemoteModels']; 25 | remoteHost: (typeof env)['remoteHost']; 26 | localModelPath: (typeof env)['localModelPath']; 27 | } 28 | 29 | export namespace ClientMessage { 30 | export interface IConfigure extends ITransformersSettings { 31 | action: 'configure'; 32 | } 33 | export interface IInitializeBuffer { 34 | action: 'initializeBuffer'; 35 | buffer: SharedArrayBuffer; 36 | } 37 | export interface IInitializeModel { 38 | action: 'initializeModel'; 39 | model: string; 40 | } 41 | export interface IDisposeModel { 42 | action: 'disposeModel'; 43 | model: string; 44 | } 45 | export interface IGenerate extends IModelSettings { 46 | action: 'generate'; 47 | model: string; 48 | idTokens: string[]; 49 | text: string; 50 | counter: number; 51 | } 52 | export type Message = 53 | | IConfigure 54 | | IInitializeBuffer 55 | | IInitializeModel 56 | | IDisposeModel 57 | | IGenerate; 58 | } 59 | 60 | export namespace WorkerMessage { 61 | export interface IWorkerStarted { 62 | status: 'worker-started'; 63 | } 64 | interface IModelLoadingMessage { 65 | model: string; 66 | file: string; 67 | } 68 | export interface IInitiate extends IModelLoadingMessage { 69 | status: 'initiate'; 70 | } 71 | export interface IProgress extends IModelLoadingMessage { 72 | status: 'progress'; 73 | loaded: number; 74 | total: number; 75 | progress: number; 76 | } 77 | export interface IDone extends IModelLoadingMessage { 78 | status: 'done'; 79 | } 80 | export interface IReady extends IModelLoadingMessage { 81 | status: 'ready'; 82 | } 83 | interface ICompletionMessage { 84 | idToken: string; 85 | output: string; 86 | } 87 | export interface IUpdate extends ICompletionMessage { 88 | status: 'update'; 89 | } 90 | export interface IComplete extends ICompletionMessage { 91 | status: 'complete'; 92 | } 93 | export interface IGenerationError { 94 | idTokens: string[]; 95 | error?: { 96 | message: string; 97 | }; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | /** 5 | * Format bytes to human readable string. 6 | */ 7 | export function formatFileSize( 8 | bytes: number, 9 | decimalPoint: number = 2, 10 | k: number = 1024 11 | ): string { 12 | // https://www.codexworld.com/how-to/convert-file-size-bytes-kb-mb-gb-javascript/ 13 | if (bytes === 0) { 14 | return '0 B'; 15 | } 16 | const dm = decimalPoint; 17 | const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 18 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 19 | if (i >= 0 && i < sizes.length) { 20 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; 21 | } else { 22 | return String(bytes); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/worker.ts: -------------------------------------------------------------------------------- 1 | import type { Pipeline } from '@xenova/transformers'; 2 | import type { 3 | transformersModule, 4 | ClientMessage as Message, 5 | WorkerMessage 6 | } from './types'; 7 | 8 | const transformers = await require('@xenova/transformers/dist/transformers'); 9 | 10 | class Worker { 11 | async handleMessage(event: MessageEvent) { 12 | const data = event.data; 13 | switch (data.action) { 14 | case 'generate': 15 | return this._generate(data as Message.IGenerate); 16 | case 'configure': 17 | return this._configure(data as Message.IConfigure); 18 | case 'initializeBuffer': 19 | return this._initializeBuffer(data as Message.IInitializeBuffer); 20 | case 'initializeModel': 21 | return this._initializeModel(data as Message.IInitializeModel); 22 | case 'disposeModel': 23 | return this._disposeModel(data as Message.IDisposeModel); 24 | default: 25 | console.error('Unhandled message', event); 26 | break; 27 | } 28 | } 29 | 30 | private async _generate(data: Message.IGenerate) { 31 | const { model: modelName, text, idTokens, counter: startCounter } = data; 32 | 33 | const sharedArray = this._sharedArray; 34 | if (sharedArray === null) { 35 | throw Error( 36 | 'Cannot generate before `initializeBuffer` message got processed' 37 | ); 38 | } 39 | const model = this._initializeModel({ model: modelName }); 40 | const generator = await model.instance; 41 | 42 | const generationCounter = sharedArray[0]; 43 | if (generationCounter !== startCounter) { 44 | console.log('Skipping generation because new request was sent since'); 45 | return; 46 | } 47 | 48 | let output = []; 49 | try { 50 | // see https://huggingface.co/docs/transformers.js/main/en/api/utils/generation#module_utils/generation.GenerationConfig 51 | output = await generator(text, { 52 | max_new_tokens: data.maxNewTokens, 53 | temperature: data.temperature, 54 | top_k: data.topK, 55 | do_sample: data.doSample, 56 | num_beams: data.generateN, 57 | num_return_sequences: data.generateN, 58 | repetition_penalty: data.repetitionPenalty, 59 | diversity_penalty: data.diversityPenalty, 60 | // make the alternatives more diverse 61 | num_beam_groups: data.generateN, 62 | callback_function: (x: any) => { 63 | const generationCounter = sharedArray[0]; 64 | if (generationCounter !== startCounter) { 65 | // TODO: use `stopping_condition` once available, see 66 | // https://github.com/xenova/transformers.js/issues/341 67 | throw Error('Execution interrupted'); 68 | } 69 | 70 | for (let i = 0; i < x.length; i++) { 71 | const output = generator.tokenizer.decode(x[i].output_token_ids, { 72 | skip_special_tokens: true 73 | }); 74 | self.postMessage({ 75 | status: 'update', 76 | output: output.substring(text.length), 77 | idToken: idTokens[i] 78 | } as WorkerMessage.IUpdate); 79 | } 80 | } 81 | }); 82 | } catch (e: unknown) { 83 | const errorData = { 84 | error: { 85 | message: (e as Error).message 86 | }, 87 | idTokens 88 | }; 89 | if ((e as Error).message === 'Execution interrupted') { 90 | self.postMessage({ 91 | status: 'interrupted', 92 | ...errorData 93 | } as WorkerMessage.IGenerationError); 94 | } else { 95 | self.postMessage({ 96 | status: 'exception', 97 | ...errorData 98 | } as WorkerMessage.IGenerationError); 99 | } 100 | } 101 | 102 | for (let i = 0; i < output.length; i++) { 103 | self.postMessage({ 104 | status: 'complete', 105 | output: output[i].generated_text.substring(text.length), 106 | idToken: idTokens[i] 107 | } as WorkerMessage.IComplete); 108 | } 109 | } 110 | 111 | private _initializeModel(data: { model: string }): CompletionModel { 112 | let model = this._completionModels.get(data.model); 113 | if (model) { 114 | return model; 115 | } 116 | model = new CompletionModel({ 117 | model: data.model, 118 | onLoadingProgress: (progress: any) => { 119 | self.postMessage({ 120 | ...progress, 121 | model: data.model 122 | } as WorkerMessage.IProgress); 123 | } 124 | }); 125 | this._completionModels.set(data.model, model); 126 | return model; 127 | } 128 | 129 | private _configure(data: Message.IConfigure) { 130 | // Allow to download the model from the hub. 131 | (transformers as transformersModule).env.allowLocalModels = 132 | data.allowLocalModels; 133 | (transformers as transformersModule).env.allowRemoteModels = 134 | data.allowRemoteModels; 135 | (transformers as transformersModule).env.localModelPath = 136 | data.localModelPath; 137 | (transformers as transformersModule).env.remoteHost = data.remoteHost; 138 | } 139 | 140 | private _initializeBuffer(data: Message.IInitializeBuffer) { 141 | this._sharedArray = new Int32Array(data.buffer); 142 | } 143 | 144 | private _disposeModel(data: Message.IDisposeModel) { 145 | const model = this._completionModels.get(data.model); 146 | if (!model) { 147 | return; 148 | } 149 | this._completionModels.delete(data.model); 150 | return model.dispose(); 151 | } 152 | 153 | private _sharedArray: Int32Array | null = null; 154 | private _completionModels: Map = new Map(); 155 | } 156 | 157 | class CompletionModel { 158 | constructor(options: CompletionModel.IOptions) { 159 | this._model = options.model; 160 | this._instance = transformers.pipeline(this._task, this._model, { 161 | progress_callback: (progress: any) => { 162 | options.onLoadingProgress(progress); 163 | } 164 | }); 165 | } 166 | 167 | get instance() { 168 | return this._instance; 169 | } 170 | 171 | async dispose() { 172 | (await this._instance).dispose(); 173 | } 174 | 175 | private _instance: Promise; 176 | private _task = 'text-generation'; 177 | private _model: string; 178 | } 179 | 180 | namespace CompletionModel { 181 | export interface IOptions { 182 | model: string; 183 | onLoadingProgress: (progress: any) => void; 184 | } 185 | } 186 | 187 | export const worker = new Worker(); 188 | self.addEventListener('message', worker.handleMessage.bind(worker)); 189 | self.postMessage({ status: 'worker-started' } as WorkerMessage.IWorkerStarted); 190 | -------------------------------------------------------------------------------- /style/base.css: -------------------------------------------------------------------------------- 1 | /* 2 | See the JupyterLab Developer Guide for useful CSS Patterns: 3 | 4 | https://jupyterlab.readthedocs.io/en/stable/developer/css.html 5 | */ 6 | -------------------------------------------------------------------------------- /style/index.css: -------------------------------------------------------------------------------- 1 | @import url('base.css'); 2 | -------------------------------------------------------------------------------- /style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "composite": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "incremental": true, 8 | "jsx": "react", 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "noEmitOnError": true, 12 | "noImplicitAny": true, 13 | "noUnusedLocals": true, 14 | "preserveWatchOutput": true, 15 | "resolveJsonModule": true, 16 | "outDir": "lib", 17 | "rootDir": "src", 18 | "strict": true, 19 | "strictNullChecks": true, 20 | "skipLibCheck": true, 21 | "target": "ES2018" 22 | }, 23 | "include": ["src/*"] 24 | } 25 | --------------------------------------------------------------------------------