From 4cad367ab550120c0dc223c8ae95a9d61dd6126f Mon Sep 17 00:00:00 2001 From: GutZuFusss Date: Wed, 16 Aug 2023 09:30:44 +0200 Subject: [PATCH] feat(contrib): ClojureScript UI (#89) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: aarnphm-ec2-dev <29749331+aarnphm@users.noreply.github.com> --- .gitattributes | 13 +- .github/CODEOWNERS | 2 + .github/actions/create_release_and_archive.sh | 2 + .github/actions/release.sh | 7 +- .github/workflows/clojure-frontend.yml | 114 + .github/workflows/compile-pypi.yml | 2 +- .github/workflows/create-releases.yml | 5 +- .pre-commit-config.yaml | 13 +- DEVELOPMENT.md | 7 +- README.md | 11 +- changelog.d/89.feature.md | 5 + clean.sh | 1 + contrib/clojure/.dockerignore | 7 + contrib/clojure/.gitignore | 17 + contrib/clojure/Dockerfile | 32 + contrib/clojure/LICENSE.md | 1 + contrib/clojure/README.md | 53 + contrib/clojure/deps.edn | 14 + contrib/clojure/package.json | 80 + contrib/clojure/pnpm-lock.yaml | 3400 +++++++++++++++++ contrib/clojure/postcss.config.js | 7 + contrib/clojure/public/index.html | 14 + .../clojure/public/static/atom-one-light.css | 75 + contrib/clojure/public/static/logo-dark.svg | 19 + contrib/clojure/public/static/logo-light.svg | 12 + contrib/clojure/shadow-cljs.edn | 9 + contrib/clojure/src/css/tailwind.css | 96 + contrib/clojure/src/generated/README.md | 4 + .../src/main/openllm/api/components.cljs | 30 + .../clojure/src/main/openllm/api/http.cljs | 43 + .../src/main/openllm/api/indexed_db/core.cljs | 338 ++ .../src/main/openllm/api/log4cljs/core.cljs | 44 + .../src/main/openllm/api/persistence.cljs | 121 + contrib/clojure/src/main/openllm/app.cljs | 54 + contrib/clojure/src/main/openllm/build.clj | 41 + .../src/main/openllm/components/chat/db.cljs | 45 + .../main/openllm/components/chat/events.cljs | 122 + .../main/openllm/components/chat/subs.cljs | 49 + .../main/openllm/components/chat/views.cljs | 147 + .../main/openllm/components/common/views.cljs | 12 + .../src/main/openllm/components/db.cljs | 31 + .../main/openllm/components/nav_bar/db.cljs | 36 + .../openllm/components/nav_bar/events.cljs | 47 + .../main/openllm/components/nav_bar/subs.cljs | 15 + .../openllm/components/nav_bar/views.cljs | 87 + .../openllm/components/playground/db.cljs | 41 + .../openllm/components/playground/events.cljs | 49 + .../openllm/components/playground/subs.cljs | 21 + .../openllm/components/playground/views.cljs | 105 + .../main/openllm/components/side_bar/db.cljs | 41 + .../openllm/components/side_bar/events.cljs | 11 + .../components/side_bar/model_params/db.cljs | 108 + .../side_bar/model_params/events.cljs | 16 + .../side_bar/model_params/subs.cljs | 40 + .../side_bar/model_params/views.cljs | 104 + .../side_bar/model_selection/db.cljs | 66 + .../side_bar/model_selection/events.cljs | 83 + .../side_bar/model_selection/subs.cljs | 50 + .../side_bar/model_selection/views.cljs | 29 + .../openllm/components/side_bar/subs.cljs | 37 + .../openllm/components/side_bar/views.cljs | 73 + .../src/main/openllm/components/subs.cljs | 37 + contrib/clojure/src/main/openllm/db.cljs | 67 + contrib/clojure/src/main/openllm/events.cljs | 70 + contrib/clojure/src/main/openllm/subs.cljs | 17 + contrib/clojure/src/main/openllm/util.cljs | 19 + contrib/clojure/src/main/openllm/views.cljs | 43 + contrib/clojure/tailwind.config.js | 49 + hatch.toml | 28 +- openllm-node/package.json | 2 +- .../ADDING_NEW_MODEL.md | 14 +- openllm-python/src/openllm/cli/_factory.py | 24 +- openllm-python/src/openllm/cli/_sdk.py | 8 +- package.json | 5 +- ...le-latest => assert-model-table-latest.py} | 0 tools/{lock-actions => lock-actions.sh} | 0 tools/run-clojure-ui.sh | 30 + tools/{sync-readme => sync-readme.sh} | 0 78 files changed, 6531 insertions(+), 40 deletions(-) create mode 100644 .github/workflows/clojure-frontend.yml create mode 100644 changelog.d/89.feature.md create mode 100644 contrib/clojure/.dockerignore create mode 100644 contrib/clojure/.gitignore create mode 100644 contrib/clojure/Dockerfile create mode 120000 contrib/clojure/LICENSE.md create mode 100644 contrib/clojure/README.md create mode 100644 contrib/clojure/deps.edn create mode 100644 contrib/clojure/package.json create mode 100644 contrib/clojure/pnpm-lock.yaml create mode 100644 contrib/clojure/postcss.config.js create mode 100644 contrib/clojure/public/index.html create mode 100644 contrib/clojure/public/static/atom-one-light.css create mode 100644 contrib/clojure/public/static/logo-dark.svg create mode 100644 contrib/clojure/public/static/logo-light.svg create mode 100644 contrib/clojure/shadow-cljs.edn create mode 100644 contrib/clojure/src/css/tailwind.css create mode 100644 contrib/clojure/src/generated/README.md create mode 100644 contrib/clojure/src/main/openllm/api/components.cljs create mode 100644 contrib/clojure/src/main/openllm/api/http.cljs create mode 100644 contrib/clojure/src/main/openllm/api/indexed_db/core.cljs create mode 100644 contrib/clojure/src/main/openllm/api/log4cljs/core.cljs create mode 100644 contrib/clojure/src/main/openllm/api/persistence.cljs create mode 100644 contrib/clojure/src/main/openllm/app.cljs create mode 100644 contrib/clojure/src/main/openllm/build.clj create mode 100644 contrib/clojure/src/main/openllm/components/chat/db.cljs create mode 100644 contrib/clojure/src/main/openllm/components/chat/events.cljs create mode 100644 contrib/clojure/src/main/openllm/components/chat/subs.cljs create mode 100644 contrib/clojure/src/main/openllm/components/chat/views.cljs create mode 100644 contrib/clojure/src/main/openllm/components/common/views.cljs create mode 100644 contrib/clojure/src/main/openllm/components/db.cljs create mode 100644 contrib/clojure/src/main/openllm/components/nav_bar/db.cljs create mode 100644 contrib/clojure/src/main/openllm/components/nav_bar/events.cljs create mode 100644 contrib/clojure/src/main/openllm/components/nav_bar/subs.cljs create mode 100644 contrib/clojure/src/main/openllm/components/nav_bar/views.cljs create mode 100644 contrib/clojure/src/main/openllm/components/playground/db.cljs create mode 100644 contrib/clojure/src/main/openllm/components/playground/events.cljs create mode 100644 contrib/clojure/src/main/openllm/components/playground/subs.cljs create mode 100644 contrib/clojure/src/main/openllm/components/playground/views.cljs create mode 100644 contrib/clojure/src/main/openllm/components/side_bar/db.cljs create mode 100644 contrib/clojure/src/main/openllm/components/side_bar/events.cljs create mode 100644 contrib/clojure/src/main/openllm/components/side_bar/model_params/db.cljs create mode 100644 contrib/clojure/src/main/openllm/components/side_bar/model_params/events.cljs create mode 100644 contrib/clojure/src/main/openllm/components/side_bar/model_params/subs.cljs create mode 100644 contrib/clojure/src/main/openllm/components/side_bar/model_params/views.cljs create mode 100644 contrib/clojure/src/main/openllm/components/side_bar/model_selection/db.cljs create mode 100644 contrib/clojure/src/main/openllm/components/side_bar/model_selection/events.cljs create mode 100644 contrib/clojure/src/main/openllm/components/side_bar/model_selection/subs.cljs create mode 100644 contrib/clojure/src/main/openllm/components/side_bar/model_selection/views.cljs create mode 100644 contrib/clojure/src/main/openllm/components/side_bar/subs.cljs create mode 100644 contrib/clojure/src/main/openllm/components/side_bar/views.cljs create mode 100644 contrib/clojure/src/main/openllm/components/subs.cljs create mode 100644 contrib/clojure/src/main/openllm/db.cljs create mode 100644 contrib/clojure/src/main/openllm/events.cljs create mode 100644 contrib/clojure/src/main/openllm/subs.cljs create mode 100644 contrib/clojure/src/main/openllm/util.cljs create mode 100644 contrib/clojure/src/main/openllm/views.cljs create mode 100644 contrib/clojure/tailwind.config.js rename ADDING_NEW_MODEL.md => openllm-python/ADDING_NEW_MODEL.md (86%) rename tools/{assert-model-table-latest => assert-model-table-latest.py} (100%) rename tools/{lock-actions => lock-actions.sh} (100%) create mode 100755 tools/run-clojure-ui.sh rename tools/{sync-readme => sync-readme.sh} (100%) diff --git a/.gitattributes b/.gitattributes index 226718d4..dc274c61 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,9 +1,18 @@ +# Clojure UI +contrib/clojure/pnpm-lock.yaml linguist-generated=true +contrib/clojure/src/generated/** linguist-generated=true + +# Python core openllm-python/tests/models/__snapshots__/* linguist-generated=true -typings/**/*.pyi linguist-generated=true -Formula/openllm.rb linguist-generated=true openllm-python/src/openllm/utils/dummy_*.py linguist-generated=true openllm-python/src/openllm/models/__init__.py linguist-generated=true openllm-python/README.md linguist-generated=true + +# Others +typings/**/*.pyi linguist-generated=true +Formula/openllm.rb linguist-generated=true + + * text=auto eol=lf # Needed for setuptools-scm-git-archive .git_archival.txt export-subst diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3c804aa1..bf55a501 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1,3 @@ * @aarnphm + +contrib/clojure @GutZuFusss diff --git a/.github/actions/create_release_and_archive.sh b/.github/actions/create_release_and_archive.sh index 51e98f18..adfc327f 100755 --- a/.github/actions/create_release_and_archive.sh +++ b/.github/actions/create_release_and_archive.sh @@ -28,6 +28,8 @@ To start a LLM: \`\`\`python -m openllm start opt\`\`\` To run OpenLLM within a container environment (requires GPUs): \`\`\`docker run --gpus all -it -P ghcr.io/bentoml/openllm:${TAG} start opt\`\`\` +To run OpenLLM Clojure UI (community-maintained): \`\`\`docker run -p 8420:80 ghcr.io/bentoml/openllm-ui-clojure:${TAG}\`\`\` + Find more information about this release in the [CHANGELOG.md](https://github.com/bentoml/OpenLLM/blob/main/CHANGELOG.md) EOF diff --git a/.github/actions/release.sh b/.github/actions/release.sh index c6525d5a..0cfa8aa0 100755 --- a/.github/actions/release.sh +++ b/.github/actions/release.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env bash + set -e # Function to print script usage @@ -50,8 +52,11 @@ release_package() { pushd openllm-node &>/dev/null jq --arg release_version "${version}" '.version = $release_version' < package.json > package.json.tmp && mv package.json.tmp package.json popd &>/dev/null + pushd contrib/clojure &>/dev/null + jq --arg release_version "${version}" '.version = $release_version' < package.json > package.json.tmp && mv package.json.tmp package.json + popd &>/dev/null towncrier build --yes --version "${version}" - git add CHANGELOG.md changelog.d package.json openllm-node/package.json + git add CHANGELOG.md changelog.d package.json openllm-node/package.json contrib/clojure/package.json git commit -S -sm "infra: prepare for release ${version} [generated] [skip ci]" git push origin main echo "Releasing tag ${version}..." && git tag -a "v${version}" -sm "Release ${version} [generated by GitHub Actions]" diff --git a/.github/workflows/clojure-frontend.yml b/.github/workflows/clojure-frontend.yml new file mode 100644 index 00000000..2e282aec --- /dev/null +++ b/.github/workflows/clojure-frontend.yml @@ -0,0 +1,114 @@ +name: Build Clojure UI +on: + workflow_dispatch: + push: + branches: [main] + tags: + - "*" + pull_request: + branches: [main] + paths: + - 'contrib/clojure/**' +# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#defaultsrun +defaults: + run: + shell: bash --noprofile --norc -exo pipefail {0} +jobs: + build-and-push-clojure-ui: + name: Build and push Clojure UI image + runs-on: ubuntu-latest + concurrency: + group: ${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + permissions: + contents: write + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + security-events: write + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # ratchet:actions/checkout@v3 + with: + fetch-depth: 1 + - name: Inject slug/short variables + uses: rlespinasse/github-slug-action@102b1a064a9b145e56556e22b18b19c624538d94 # ratchet:rlespinasse/github-slug-action@v4.4.1 + - name: Set up QEMU + uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # ratchet:docker/setup-qemu-action@v2.2.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4c0219f9ac95b02789c1075625400b2acbff50b1 # ratchet:docker/setup-buildx-action@v2.9.1 + with: + install: true + driver-opts: | + image=moby/buildkit:master + network=host + - name: Install cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 # ratchet:sigstore/cosign-installer@v3.1.1 + with: + cosign-release: 'v2.1.1' + - name: Login to GitHub Container Registry + uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # ratchet:docker/login-action@v2.2.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata tags and labels on PRs + if: github.event_name == 'pull_request' + id: meta-pr + uses: docker/metadata-action@818d4b7b91585d195f67373fd9cb0332e31a7175 # ratchet:docker/metadata-action@v4.6.0 + with: + images: ghcr.io/bentoml/openllm-ui-clojure + tags: type=raw,value=sha-${{ env.GITHUB_SHA_SHORT }} + - name: Extract metadata tags and labels for main, release or tag + if: github.event_name != 'pull_request' + id: meta + uses: docker/metadata-action@818d4b7b91585d195f67373fd9cb0332e31a7175 # ratchet:docker/metadata-action@v4.6.0 + with: + flavor: latest=auto + images: ghcr.io/bentoml/openllm-ui-clojure + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} + type=raw,value=sha-${{ env.GITHUB_SHA_SHORT }} + - name: Build and push Clojure UI image + id: build-and-push + uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # ratchet:docker/build-push-action@v4 + with: + context: contrib/clojure + file: contrib/clojure/Dockerfile + push: true + build-args: | + GIT_SHA=${{ env.GITHUB_SHA }} + DOCKER_LABEL=sha-${{ env.GITHUB_SHA_SHORT }} + tags: ${{ steps.meta.outputs.tags || steps.meta-pr.outputs.tags }} + labels: ${{ steps.meta.outputs.labels || steps.meta-pr.outputs.labels }} + - name: Sign the released image + if: ${{ github.event_name != 'pull_request' }} + env: + COSIGN_EXPERIMENTAL: "true" + run: echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build-and-push.outputs.digest }} + - name: Run Trivy in GitHub SBOM mode and submit results to Dependency Graph + uses: aquasecurity/trivy-action@559eb1224e654a86c844a795e6702a0742c60c72 # ratchet:aquasecurity/trivy-action@master + if: ${{ github.event_name != 'pull_request' }} + with: + image-ref: 'ghcr.io/bentoml/openllm-ui-clojure:sha-${{ env.GITHUB_SHA_SHORT }}' + format: 'github' + output: 'dependency-results.sbom.json' + github-pat: ${{ secrets.UI_GITHUB_TOKEN }} + scanners: 'vuln' + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@559eb1224e654a86c844a795e6702a0742c60c72 # ratchet:aquasecurity/trivy-action@master + if: ${{ github.event_name != 'pull_request' }} + with: + image-ref: 'ghcr.io/bentoml/openllm-ui-clojure:sha-${{ env.GITHUB_SHA_SHORT }}' + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL' + scanners: 'vuln' + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@a09933a12a80f87b87005513f0abb1494c27a716 # ratchet:github/codeql-action/upload-sarif@v2 + if: ${{ github.event_name != 'pull_request' }} + with: + sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/compile-pypi.yml b/.github/workflows/compile-pypi.yml index a8d248a7..f133b2aa 100644 --- a/.github/workflows/compile-pypi.yml +++ b/.github/workflows/compile-pypi.yml @@ -134,7 +134,7 @@ jobs: push-nightly: name: Push nightly wheels if: >- - !github.event.repository.fork && (github.event_name == 'push' && startsWith(github.ref, 'refs/main') + !github.event.repository.fork && (github.event_name == 'push' && startsWith(github.ref, 'refs/main')) runs-on: ubuntu-latest permissions: id-token: write diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml index 0d358a33..4641e367 100644 --- a/.github/workflows/create-releases.yml +++ b/.github/workflows/create-releases.yml @@ -169,7 +169,10 @@ jobs: pushd openllm-node &>/dev/null jq --arg release_version "${DEV_VERSION}" '.version = $release_version' < package.json > package.json.tmp && mv package.json.tmp package.json popd &>/dev/null - git add package.json openllm-node/package.json && git commit -S -sm "infra: bump to dev version of ${DEV_VERSION} [generated] [skip ci]" + pushd contrib/clojure &>/dev/null + jq --arg release_version "${version}" '.version = $release_version' < package.json > package.json.tmp && mv package.json.tmp package.json + popd &>/dev/null + git add package.json openllm-node/package.json contrib/clojure/package.json && git commit -S -sm "infra: bump to dev version of ${DEV_VERSION} [generated] [skip ci]" git push origin HEAD:main binary-distribution: if: github.repository_owner == 'bentoml' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1957cac8..953108e3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ ci: autoupdate_schedule: weekly - skip: [check-models-table-update, changelog-dry-run, pyright, yapf, mypy, sync-readme] + skip: [check-models-table-update, changelog-dry-run, pyright, yapf, mypy, sync-readme, clj-kondo] autofix_commit_msg: "ci: auto fixes from pre-commit.ci\n\nFor more information, see https://pre-commit.ci" autoupdate_commit_msg: 'ci: pre-commit autoupdate [pre-commit.ci]' default_language_version: @@ -19,7 +19,7 @@ repos: - id: editorconfig-checker verbose: true - repo: https://github.com/econchick/interrogate - rev: 1.5.0 + rev: 1e74611fc5296b0572b6bb11b480d43242c4ec49 hooks: - id: interrogate verbose: true @@ -52,6 +52,11 @@ repos: - types-tabulate - types-PyYAML - types-protobuf + - repo: https://github.com/vincentjames501/pre-commit-clojure + rev: v2.0.3 + hooks: + - id: clj-kondo + verbose: true - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: @@ -78,13 +83,13 @@ repos: additional_dependencies: ['yapf==0.40.0'] - id: check-models-table-update name: check if table in README.md is up-to-date - entry: ./tools/assert-model-table-latest + entry: ./tools/assert-model-table-latest.py language: script verbose: true files: README.md - id: sync-readme name: sync readme with python core library - entry: ./tools/sync-readme + entry: ./tools/sync-readme.sh language: script verbose: true files: README.md diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 81d45e04..617688bf 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -190,7 +190,11 @@ See [STYLE.md](STYLE.md) for our style guide. ## Working with OpenLLM's CI/CD -After you change or update any CI related under `.github`, run `./tools/lock-actions` to lock the action version. +After you change or update any CI related under `.github`, run `bash tools/lock-actions.sh` to lock the action version. + +## UI + +See [ClojureScript UI's README.md](/contrib/clojure/README.md) for more information. ## Install from git archive install @@ -198,7 +202,6 @@ After you change or update any CI related under `.github`, run `./tools/lock-act pip install 'https://github.com/bentoml/OpenLLM/archive/main.tar.gz#subdirectory=openllm-python' ``` - ## Releasing a New Version To release a new version, use `./tools/run-release-action`. It requires `gh`, diff --git a/README.md b/README.md index 1327dd53..073c438d 100644 --- a/README.md +++ b/README.md @@ -493,7 +493,7 @@ openllm build opt --model-id facebook/opt-6.7b --adapter-id ... OpenLLM encourages contributions by welcoming users to incorporate their custom LLMs into the ecosystem. Check out -[Adding a New Model Guide](https://github.com/bentoml/OpenLLM/blob/main/ADDING_NEW_MODEL.md) +[Adding a New Model Guide](https://github.com/bentoml/OpenLLM/blob/main/openllm-python/ADDING_NEW_MODEL.md) to see how you can do it yourself. ### Embeddings @@ -535,6 +535,15 @@ client.embed("I like to eat apples") > Currently, the following model family supports embeddings: Llama, T5 > (Flan-T5, FastChat, etc.), ChatGLM +### Playground and Chat UI + +The following UIs are currently available for OpenLLM: + +| UI | Owner | Type | Progress | +|-----------------------------------------------------------------------------------|-----------------------------------------------|----------------------|----------| +| [Clojure](https://github.com/bentoml/OpenLLM/blob/main/contrib/clojure/README.md) | [@ GutZuFusss](https://github.com/GutZuFusss) | Community-maintained | 🔧 | +| TS | BentoML Team | | 🚧 | + ## ⚙️ Integrations OpenLLM is not just a standalone product; it's a building block designed to diff --git a/changelog.d/89.feature.md b/changelog.d/89.feature.md new file mode 100644 index 00000000..e7f46faf --- /dev/null +++ b/changelog.d/89.feature.md @@ -0,0 +1,5 @@ +OpenLLM now include a community-maintained ClojureScript UI, Thanks @GutZuFusss + +See [this README.md](/contrib/clojure/README.md) for more information + +OpenLLM will also include a `--cors` to enable start with cors enabled. diff --git a/clean.sh b/clean.sh index 302c0bb2..87e2039a 100644 --- a/clean.sh +++ b/clean.sh @@ -2,3 +2,4 @@ GIT_ROOT="$(git rev-parse --show-toplevel)" cd "$GIT_ROOT" || exit 1 find . -type f -iname "*.so" -exec rm -f {} \; +find . -type d -name "node_modules" -exec rm -rf "{}" \; diff --git a/contrib/clojure/.dockerignore b/contrib/clojure/.dockerignore new file mode 100644 index 00000000..def04d9c --- /dev/null +++ b/contrib/clojure/.dockerignore @@ -0,0 +1,7 @@ +/.clj-kondo +/.lsp +/*.iml +/.cpcache +/.vscode +/.idea +node_modules/ diff --git a/contrib/clojure/.gitignore b/contrib/clojure/.gitignore new file mode 100644 index 00000000..cdd1b172 --- /dev/null +++ b/contrib/clojure/.gitignore @@ -0,0 +1,17 @@ +**/public/js/** +**/public/css/** +/.clj-kondo +/.lsp +/*.iml +/.cpcache +/.vscode +/.idea + +/node_modules +**/deps.clj.jar + +/target +/.shadow-cljs +/.nrepl-port +**/output.calva-repl +/src/generated/**.json diff --git a/contrib/clojure/Dockerfile b/contrib/clojure/Dockerfile new file mode 100644 index 00000000..e604cf45 --- /dev/null +++ b/contrib/clojure/Dockerfile @@ -0,0 +1,32 @@ +# syntax=docker/dockerfile-upstream:master +FROM node:19-slim as builder + +LABEL org.opencontainers.image.source="https://github.com/bentoml/OpenLLM" +LABEL org.opencontainers.image.authors="Leon Ikinger (GutZuFusss) " +LABEL org.opencontainers.image.licenses="Apache-2.0" + +ENV DEBIAN_FRONTEND=noninteractive + +RUN --mount=type=cache,target=/var/cache/apt \ + --mount=type=cache,target=/var/lib/apt \ + apt-get update && apt-get install -y --no-install-recommends \ + build-essential openjdk-17-jdk curl git npm bash python3 python3-pip gcc python3-dev \ + && rm -rf /var/lib/apt/lists/* +RUN curl -fsSL https://github.com/clojure/brew-install/releases/latest/download/posix-install.sh | bash - +RUN pip3 install openllm + +WORKDIR /usr/src + +RUN npm install -g pnpm + +COPY ./package.json . + +ENV PATH /usr/src/node_modules/.bin:$PATH + +COPY . . + +RUN pnpm install && pnpm run release + +FROM nginx:alpine as base + +COPY --from=builder /usr/src/public/ /usr/share/nginx/html diff --git a/contrib/clojure/LICENSE.md b/contrib/clojure/LICENSE.md new file mode 120000 index 00000000..f0608a63 --- /dev/null +++ b/contrib/clojure/LICENSE.md @@ -0,0 +1 @@ +../../LICENSE.md \ No newline at end of file diff --git a/contrib/clojure/README.md b/contrib/clojure/README.md new file mode 100644 index 00000000..a3924ed4 --- /dev/null +++ b/contrib/clojure/README.md @@ -0,0 +1,53 @@ +# OpenLLM ClojureScript UI + +This is the ClojureScript UI for OpenLLM. It is built using [reagent](http://reagent-project.github.io/), [reframe](https://github.com/day8/re-frame), [Tailwind CSS](https://tailwindcss.com/), and [shadow-cljs](https://shadow-cljs.github.io/docs/UsersGuide.html) + +We distribute the UI via the docker image `ghcr.io/bentoml/openllm-ui-clojure:sha-`. + +```bash +docker run --rm -p 8420:80 ghcr.io/bentoml/openllm-ui-clojure:0.2.25 +``` + +> [!NOTE] +> Currently, the LLMServer must be also running to use with the UI. +> Start any model server with `openllm start --cors`. For more information, see `openllm start -h` + +## Developement Build + +> [!IMPORTANT] +> Make sure to have [Node.js](https://nodejs.org/en/) (v18.16.1) + +```bash +hatch run ui:clojure +``` + +Access the UI at http://localhost:8420 + + +### Connecting a REPL + +The REPL is the most important tool for developing Clojure applications. + +It is highly recommended to use *Visual Studio Code* + *Calva* for development of this project. Being able to evaluate code directly in the editor is a huge productivity boost and rich comments not only are an excellent way to document code, there are also a lot of useful ones in this codebase, i.e. for clearing persistent storage, dispatching various events and more. + +That being said, if you prefer to use a different editor, the information on how to connect a REPL to the running shadow-cljs instance can be found in the [REPL section](https://shadow-cljs.github.io/docs/UsersGuide.html#_repl_2) of the shadow-cljs documentation. The nREPL port is printed to the console when starting the watch build and can also be found in the `.shadow-cljs/nrepl.port` file. + +### VS Code + Calva: + +To connect a REPL to the running shadow-cljs instance using *VS Code* + *Calva*, open the command palette (`Ctrl+Shift+P`) and search for "Calva: Connect to a running REPL server in the project" or use the shortcut `Ctrl+Shift+C & Ctrl+Shift+C`. + +Then, select "shadow-cljs" from the dropdown menu. Another dropdown menu will appear, select ":app" from that menu. You should now be connected to the running shadow-cljs instance. + +> [!NOTE] +> Here is a list of keyboard shortcuts for Calva: +> * `Ctrl+Shift+C & Enter` - Load/Evaluate current namespace & load it's dependencies +> * `Ctrl+Enter` - Evaluate current form +> * `Ctrl+Alt+C & C` - Evaluate current form into a comment + +For more information on Calva, please consider reading the [Calva documentation](https://calva.io/finding-commands/). + +### Source maps + +Important to have a sane way to understand what is going on in the browser console. + +Please refer to your browsers documentation on how to enable source maps. For Chrome, simply open the dev tools (`Ctrl+Shift+I`) and click the settings icon in the top right corner. Under "Sources" check the "Enable JavaScript source maps" checkbox, you should now correctly see the source code references in the browser console. diff --git a/contrib/clojure/deps.edn b/contrib/clojure/deps.edn new file mode 100644 index 00000000..4366c780 --- /dev/null +++ b/contrib/clojure/deps.edn @@ -0,0 +1,14 @@ +{:paths + ["src/dev" + "src/main" + "src/test"] + + :aliases + {:dev + {:extra-deps + {thheller/shadow-cljs {:mvn/version "2.25.2"} + reagent/reagent {:mvn/version "1.2.0"} + re-frame/re-frame {:mvn/version "1.3.0"} + day8.re-frame/http-fx {:mvn/version "0.2.4"} + superstructor/re-highlight {:mvn/version "2.0.2"} + arttuka/reagent-material-ui {:mvn/version "5.11.12-0"}}}}} diff --git a/contrib/clojure/package.json b/contrib/clojure/package.json new file mode 100644 index 00000000..9a8cb56f --- /dev/null +++ b/contrib/clojure/package.json @@ -0,0 +1,80 @@ +{ + "name": "openllm-clojure-ui", + "version": "0.2.24", + "description": "OpenLLM Clojure UI", + "repository": { + "url": "git@github.com:bentoml/OpenLLM.git", + "type": "git" + }, + "keywords": [ + "openllm", + "llm", + "llmops", + "llama", + "transformers", + "gptq", + "mha", + "flash_attention" + ], + "license": "Apache-2.0", + "author": "Leon Ikinger (GutZuFusss) ", + "scripts": { + "shadow:watch": "shadow-cljs watch app", + "shadow:release": "shadow-cljs release app", + "postcss:build": "cross-env postcss src/css/tailwind.css -o ./public/css/main.css --verbose", + "postcss:watch": "cross-env postcss src/css/tailwind.css -o ./public/css/main.css --verbose -w --poll 200", + "postcss:release": "cross-env NODE_ENV=production postcss src/css/tailwind.css -o ./public/css/main.css --verbose", + "dev": "run-p -l *:watch", + "release": "run-s *:release" + }, + "devDependencies": { + "@types/react-transition-group": "^4.4.6", + "autoprefixer": "^10.4.12", + "cssnano": "^6.0.0", + "npm-run-all": "^4.1.5", + "postcss": "^8.4.23", + "postcss-cli": "^10.1.0", + "shadow-cljs": "2.25.2", + "tailwindcss": "^3.3.2" + }, + "dependencies": { + "@babel/runtime": "^7.22.10", + "@emotion/cache": "^11.11.0", + "@emotion/hash": "^0.9.1", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/memoize": "^0.8.1", + "@emotion/react": "^11.10.6", + "@emotion/serialize": "^1.1.2", + "@emotion/sheet": "^1.2.2", + "@emotion/styled": "^11.10.6", + "@emotion/unitless": "^0.8.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "@mui/base": "5.0.0-alpha.120", + "@mui/icons-material": "5.11.16", + "@mui/material": "5.11.12", + "@mui/private-theming": "^5.14.5", + "@mui/styled-engine": "^5.13.2", + "@mui/system": "^5.14.5", + "@mui/utils": "^5.14.5", + "@mui/x-data-grid": "6.0.0", + "@mui/x-date-pickers": "6.0.0", + "@popperjs/core": "^2.11.8", + "@tailwindcss/forms": "^0.5.3", + "clsx": "^2.0.0", + "create-react-class": "15.7.0", + "cross-env": "^7.0.3", + "dom-helpers": "^5.2.1", + "highlight.js": "11.5.1", + "hoist-non-react-statics": "^3.3.2", + "object-assign": "^4.1.1", + "prop-types": "^15.8.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-is": "^18.2.0", + "react-transition-group": "^4.4.5", + "scheduler": "^0.23.0", + "stylis": "^4.3.0" + } +} diff --git a/contrib/clojure/pnpm-lock.yaml b/contrib/clojure/pnpm-lock.yaml new file mode 100644 index 00000000..30a73729 --- /dev/null +++ b/contrib/clojure/pnpm-lock.yaml @@ -0,0 +1,3400 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + '@babel/runtime': + specifier: ^7.22.10 + version: 7.22.10 + '@emotion/cache': + specifier: ^11.11.0 + version: 11.11.0 + '@emotion/hash': + specifier: ^0.9.1 + version: 0.9.1 + '@emotion/is-prop-valid': + specifier: ^1.2.1 + version: 1.2.1 + '@emotion/memoize': + specifier: ^0.8.1 + version: 0.8.1 + '@emotion/react': + specifier: ^11.10.6 + version: 11.10.6(react@18.2.0) + '@emotion/serialize': + specifier: ^1.1.2 + version: 1.1.2 + '@emotion/sheet': + specifier: ^1.2.2 + version: 1.2.2 + '@emotion/styled': + specifier: ^11.10.6 + version: 11.10.6(@emotion/react@11.10.6)(react@18.2.0) + '@emotion/unitless': + specifier: ^0.8.1 + version: 0.8.1 + '@emotion/use-insertion-effect-with-fallbacks': + specifier: ^1.0.1 + version: 1.0.1(react@18.2.0) + '@emotion/utils': + specifier: ^1.2.1 + version: 1.2.1 + '@emotion/weak-memoize': + specifier: ^0.3.1 + version: 0.3.1 + '@mui/base': + specifier: 5.0.0-alpha.120 + version: 5.0.0-alpha.120(react-dom@18.2.0)(react@18.2.0) + '@mui/icons-material': + specifier: 5.11.16 + version: 5.11.16(@mui/material@5.11.12)(react@18.2.0) + '@mui/material': + specifier: 5.11.12 + version: 5.11.12(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react-dom@18.2.0)(react@18.2.0) + '@mui/private-theming': + specifier: ^5.14.5 + version: 5.14.5(react@18.2.0) + '@mui/styled-engine': + specifier: ^5.13.2 + version: 5.13.2(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0) + '@mui/system': + specifier: ^5.14.5 + version: 5.14.5(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0) + '@mui/utils': + specifier: ^5.14.5 + version: 5.14.5(react@18.2.0) + '@mui/x-data-grid': + specifier: 6.0.0 + version: 6.0.0(@mui/material@5.11.12)(@mui/system@5.14.5)(react-dom@18.2.0)(react@18.2.0) + '@mui/x-date-pickers': + specifier: 6.0.0 + version: 6.0.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@mui/material@5.11.12)(@mui/system@5.14.5)(react-dom@18.2.0)(react@18.2.0) + '@popperjs/core': + specifier: ^2.11.8 + version: 2.11.8 + '@tailwindcss/forms': + specifier: ^0.5.3 + version: 0.5.3(tailwindcss@3.3.2) + clsx: + specifier: ^2.0.0 + version: 2.0.0 + create-react-class: + specifier: 15.7.0 + version: 15.7.0 + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + dom-helpers: + specifier: ^5.2.1 + version: 5.2.1 + highlight.js: + specifier: 11.5.1 + version: 11.5.1 + hoist-non-react-statics: + specifier: ^3.3.2 + version: 3.3.2 + object-assign: + specifier: ^4.1.1 + version: 4.1.1 + prop-types: + specifier: ^15.8.1 + version: 15.8.1 + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + react-is: + specifier: ^18.2.0 + version: 18.2.0 + react-transition-group: + specifier: ^4.4.5 + version: 4.4.5(react-dom@18.2.0)(react@18.2.0) + scheduler: + specifier: ^0.23.0 + version: 0.23.0 + stylis: + specifier: ^4.3.0 + version: 4.3.0 + +devDependencies: + '@types/react-transition-group': + specifier: ^4.4.6 + version: 4.4.6 + autoprefixer: + specifier: ^10.4.12 + version: 10.4.12(postcss@8.4.23) + cssnano: + specifier: ^6.0.0 + version: 6.0.0(postcss@8.4.23) + npm-run-all: + specifier: ^4.1.5 + version: 4.1.5 + postcss: + specifier: ^8.4.23 + version: 8.4.23 + postcss-cli: + specifier: ^10.1.0 + version: 10.1.0(postcss@8.4.23) + shadow-cljs: + specifier: 2.25.2 + version: 2.25.2 + tailwindcss: + specifier: ^3.3.2 + version: 3.3.2 + +packages: + + /@alloc/quick-lru@5.2.0: + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + /@babel/code-frame@7.22.10: + resolution: {integrity: sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.22.10 + chalk: 2.4.2 + dev: false + + /@babel/helper-module-imports@7.22.5: + resolution: {integrity: sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.10 + dev: false + + /@babel/helper-string-parser@7.22.5: + resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/helper-validator-identifier@7.22.5: + resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/highlight@7.22.10: + resolution: {integrity: sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.22.5 + chalk: 2.4.2 + js-tokens: 4.0.0 + dev: false + + /@babel/runtime@7.22.10: + resolution: {integrity: sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.14.0 + dev: false + + /@babel/types@7.22.10: + resolution: {integrity: sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.22.5 + '@babel/helper-validator-identifier': 7.22.5 + to-fast-properties: 2.0.0 + dev: false + + /@date-io/core@2.17.0: + resolution: {integrity: sha512-+EQE8xZhRM/hsY0CDTVyayMDDY5ihc4MqXCrPxooKw19yAzUIC6uUqsZeaOFNL9YKTNxYKrJP5DFgE8o5xRCOw==} + dev: false + + /@date-io/date-fns-jalali@2.17.0: + resolution: {integrity: sha512-KpnYctxafW9lgILD6fE1qsacT3EnNkii9fIDiIAJ+9hXL0GcobQUB3KrTAsEQjMKSMP1WhsvwgJ0JNRdW5Zc8g==} + peerDependencies: + date-fns-jalali: ^2.13.0-0 + peerDependenciesMeta: + date-fns-jalali: + optional: true + dependencies: + '@date-io/core': 2.17.0 + dev: false + + /@date-io/date-fns@2.17.0: + resolution: {integrity: sha512-L0hWZ/mTpy3Gx/xXJ5tq5CzHo0L7ry6KEO9/w/JWiFWFLZgiNVo3ex92gOl3zmzjHqY/3Ev+5sehAr8UnGLEng==} + peerDependencies: + date-fns: ^2.0.0 + peerDependenciesMeta: + date-fns: + optional: true + dependencies: + '@date-io/core': 2.17.0 + dev: false + + /@date-io/dayjs@2.17.0: + resolution: {integrity: sha512-Iq1wjY5XzBh0lheFA0it6Dsyv94e8mTiNR8vuTai+KopxDkreL3YjwTmZHxkgB7/vd0RMIACStzVgWvPATnDCA==} + peerDependencies: + dayjs: ^1.8.17 + peerDependenciesMeta: + dayjs: + optional: true + dependencies: + '@date-io/core': 2.17.0 + dev: false + + /@date-io/hijri@2.17.0: + resolution: {integrity: sha512-mR1nbHQ2HPISOfFEO/fvtOsjL34iVdMUDK4dEAs/QAblPgukadLQp3Mb/5tDooIl9NhYIhT+yoO6mRtFp9cX2A==} + peerDependencies: + moment-hijri: ^2.1.2 + peerDependenciesMeta: + moment-hijri: + optional: true + dependencies: + '@date-io/moment': 2.17.0 + transitivePeerDependencies: + - moment + dev: false + + /@date-io/jalaali@2.17.0: + resolution: {integrity: sha512-DPBJ6dfBQOmTVQOsbdkmR//zBZShCpeyEmxybDJJbZt+uj7bp3Vp3TXzgRdYqwhIPD/u3JC7llznBsbK9erFdA==} + peerDependencies: + moment-jalaali: ^0.7.4 || ^0.8.0 || ^0.9.0 + peerDependenciesMeta: + moment-jalaali: + optional: true + dependencies: + '@date-io/moment': 2.17.0 + transitivePeerDependencies: + - moment + dev: false + + /@date-io/luxon@2.17.0: + resolution: {integrity: sha512-l712Vdm/uTddD2XWt9TlQloZUiTiRQtY5TCOG45MQ/8u0tu8M17BD6QYHar/3OrnkGybALAMPzCy1r5D7+0HBg==} + peerDependencies: + luxon: ^1.21.3 || ^2.x || ^3.x + peerDependenciesMeta: + luxon: + optional: true + dependencies: + '@date-io/core': 2.17.0 + dev: false + + /@date-io/moment@2.17.0: + resolution: {integrity: sha512-e4nb4CDZU4k0WRVhz1Wvl7d+hFsedObSauDHKtZwU9kt7gdYEAzKgnrSCTHsEaXrDumdrkCYTeZ0Tmyk7uV4tw==} + peerDependencies: + moment: ^2.24.0 + peerDependenciesMeta: + moment: + optional: true + dependencies: + '@date-io/core': 2.17.0 + dev: false + + /@emotion/babel-plugin@11.11.0: + resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} + dependencies: + '@babel/helper-module-imports': 7.22.5 + '@babel/runtime': 7.22.10 + '@emotion/hash': 0.9.1 + '@emotion/memoize': 0.8.1 + '@emotion/serialize': 1.1.2 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + dev: false + + /@emotion/cache@11.11.0: + resolution: {integrity: sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==} + dependencies: + '@emotion/memoize': 0.8.1 + '@emotion/sheet': 1.2.2 + '@emotion/utils': 1.2.1 + '@emotion/weak-memoize': 0.3.1 + stylis: 4.2.0 + dev: false + + /@emotion/hash@0.9.1: + resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==} + dev: false + + /@emotion/is-prop-valid@1.2.1: + resolution: {integrity: sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==} + dependencies: + '@emotion/memoize': 0.8.1 + dev: false + + /@emotion/memoize@0.8.1: + resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} + dev: false + + /@emotion/react@11.10.6(react@18.2.0): + resolution: {integrity: sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@emotion/babel-plugin': 11.11.0 + '@emotion/cache': 11.11.0 + '@emotion/serialize': 1.1.2 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) + '@emotion/utils': 1.2.1 + '@emotion/weak-memoize': 0.3.1 + hoist-non-react-statics: 3.3.2 + react: 18.2.0 + dev: false + + /@emotion/serialize@1.1.2: + resolution: {integrity: sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==} + dependencies: + '@emotion/hash': 0.9.1 + '@emotion/memoize': 0.8.1 + '@emotion/unitless': 0.8.1 + '@emotion/utils': 1.2.1 + csstype: 3.1.2 + dev: false + + /@emotion/sheet@1.2.2: + resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==} + dev: false + + /@emotion/styled@11.10.6(@emotion/react@11.10.6)(react@18.2.0): + resolution: {integrity: sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==} + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@emotion/babel-plugin': 11.11.0 + '@emotion/is-prop-valid': 1.2.1 + '@emotion/react': 11.10.6(react@18.2.0) + '@emotion/serialize': 1.1.2 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) + '@emotion/utils': 1.2.1 + react: 18.2.0 + dev: false + + /@emotion/unitless@0.8.1: + resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} + dev: false + + /@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0): + resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + dev: false + + /@emotion/utils@1.2.1: + resolution: {integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==} + dev: false + + /@emotion/weak-memoize@0.3.1: + resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==} + dev: false + + /@jridgewell/gen-mapping@0.3.3: + resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.19 + + /@jridgewell/resolve-uri@3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + engines: {node: '>=6.0.0'} + + /@jridgewell/set-array@1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + /@jridgewell/trace-mapping@0.3.19: + resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + + /@mui/base@5.0.0-alpha.119(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-XA5zhlYfXi67u613eIF0xRmktkatx6ERy3h+PwrMN5IcWFbgiL1guz8VpdXON+GWb8+G7B8t5oqTFIaCqaSAeA==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@emotion/is-prop-valid': 1.2.1 + '@mui/types': 7.2.4 + '@mui/utils': 5.14.5(react@18.2.0) + '@popperjs/core': 2.11.8 + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 18.2.0 + dev: false + + /@mui/base@5.0.0-alpha.120(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UoIXLjbl8ghK7OSD1dYzHIj79sx9v5S2J7vYeuhxUS0QR0FwGZ3WLHd31TQ2CT2faPX/AXsHQeFn93wKSnjPUQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@emotion/is-prop-valid': 1.2.1 + '@mui/types': 7.2.4 + '@mui/utils': 5.14.5(react@18.2.0) + '@popperjs/core': 2.11.8 + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 18.2.0 + dev: false + + /@mui/core-downloads-tracker@5.14.5: + resolution: {integrity: sha512-+wpGH1USwPcKMFPMvXqYPC6fEvhxM3FzxC8lyDiNK/imLyyJ6y2DPb1Oue7OGIKJWBmYBqrWWtfovrxd1aJHTA==} + dev: false + + /@mui/icons-material@5.11.16(@mui/material@5.11.12)(react@18.2.0): + resolution: {integrity: sha512-oKkx9z9Kwg40NtcIajF9uOXhxiyTZrrm9nmIJ4UjkU2IdHpd4QVLbCc/5hZN/y0C6qzi2Zlxyr9TGddQx2vx2A==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@mui/material': ^5.0.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@mui/material': 5.11.12(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + dev: false + + /@mui/material@5.11.12(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-M6BiIeJjySeEzWeiFJQ9pIjJy6mx5mHPWeMT99wjQdAmA2GxCQhE9A0fh6jQP4jMmYzxhOIhjsGcp0vSdpseXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@emotion/react': 11.10.6(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(react@18.2.0) + '@mui/base': 5.0.0-alpha.119(react-dom@18.2.0)(react@18.2.0) + '@mui/core-downloads-tracker': 5.14.5 + '@mui/system': 5.14.5(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0) + '@mui/types': 7.2.4 + '@mui/utils': 5.14.5(react@18.2.0) + '@types/react-transition-group': 4.4.6 + clsx: 1.2.1 + csstype: 3.1.2 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 18.2.0 + react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) + dev: false + + /@mui/private-theming@5.14.5(react@18.2.0): + resolution: {integrity: sha512-cC4C5RrpXpDaaZyH9QwmPhRLgz+f2SYbOty3cPkk4qPSOSfif2ZEcDD9HTENKDDd9deB+xkPKzzZhi8cxIx8Ig==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@mui/utils': 5.14.5(react@18.2.0) + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/styled-engine@5.13.2(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0): + resolution: {integrity: sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@emotion/cache': 11.11.0 + '@emotion/react': 11.10.6(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(react@18.2.0) + csstype: 3.1.2 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/system@5.14.5(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0): + resolution: {integrity: sha512-mextXZHDeGcR7E1kx43TRARrVXy+gI4wzpUgNv7MqZs1dvTVXQGVeAT6ydj9d6FUqHBPMNLGV/21vJOrpqsL+w==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@emotion/react': 11.10.6(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(react@18.2.0) + '@mui/private-theming': 5.14.5(react@18.2.0) + '@mui/styled-engine': 5.13.2(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0) + '@mui/types': 7.2.4 + '@mui/utils': 5.14.5(react@18.2.0) + clsx: 2.0.0 + csstype: 3.1.2 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/types@7.2.4: + resolution: {integrity: sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==} + peerDependencies: + '@types/react': '*' + peerDependenciesMeta: + '@types/react': + optional: true + dev: false + + /@mui/utils@5.14.5(react@18.2.0): + resolution: {integrity: sha512-6Hzw63VR9C5xYv+CbjndoRLU6Gntal8rJ5W+GUzkyHrGWIyYPWZPa6AevnyGioySNETATe1H9oXS8f/7qgIHJA==} + engines: {node: '>=12.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 + dependencies: + '@babel/runtime': 7.22.10 + '@types/prop-types': 15.7.5 + '@types/react-is': 18.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-is: 18.2.0 + dev: false + + /@mui/x-data-grid@6.0.0(@mui/material@5.11.12)(@mui/system@5.14.5)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-jkvDbe8OKOh/4YH7DnM20X6TbDerfaeVCOa6J+PXfDk/GDoNkcSAwIYtn15+hCY3SNWXuTXmJNiNTHxwxLfYBQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@mui/material': ^5.4.1 + '@mui/system': ^5.4.1 + react: ^17.0.2 || ^18.0.0 + react-dom: ^17.0.2 || ^18.0.0 + dependencies: + '@babel/runtime': 7.22.10 + '@mui/material': 5.11.12(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react-dom@18.2.0)(react@18.2.0) + '@mui/system': 5.14.5(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0) + '@mui/utils': 5.14.5(react@18.2.0) + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + reselect: 4.1.8 + dev: false + + /@mui/x-date-pickers@6.0.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@mui/material@5.11.12)(@mui/system@5.14.5)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-6kraEcal5oBM/14CyJteNYcr0BqVD3qy4fX83dQrruRf2B3j8evzCVPZiT7T+Qdrx3AvbUS2wQdGP+9BcHByhA==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.9.0 + '@emotion/styled': ^11.8.1 + '@mui/material': ^5.4.1 + '@mui/system': ^5.4.1 + date-fns: ^2.25.0 + date-fns-jalali: ^2.13.0-0 + dayjs: ^1.10.7 + luxon: ^3.0.2 + moment: ^2.29.4 + moment-hijri: ^2.1.2 + moment-jalaali: ^0.7.4 || ^0.8.0 || ^0.9.0 + react: ^17.0.2 || ^18.0.0 + react-dom: ^17.0.2 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + date-fns: + optional: true + date-fns-jalali: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + moment-hijri: + optional: true + moment-jalaali: + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@date-io/core': 2.17.0 + '@date-io/date-fns': 2.17.0 + '@date-io/date-fns-jalali': 2.17.0 + '@date-io/dayjs': 2.17.0 + '@date-io/hijri': 2.17.0 + '@date-io/jalaali': 2.17.0 + '@date-io/luxon': 2.17.0 + '@date-io/moment': 2.17.0 + '@emotion/react': 11.10.6(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(react@18.2.0) + '@mui/material': 5.11.12(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react-dom@18.2.0)(react@18.2.0) + '@mui/system': 5.14.5(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0) + '@mui/utils': 5.14.5(react@18.2.0) + '@types/react-transition-group': 4.4.6 + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) + dev: false + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + + /@popperjs/core@2.11.8: + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + dev: false + + /@tailwindcss/forms@0.5.3(tailwindcss@3.3.2): + resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==} + peerDependencies: + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' + dependencies: + mini-svg-data-uri: 1.4.4 + tailwindcss: 3.3.2 + dev: false + + /@trysound/sax@0.2.0: + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + dev: true + + /@types/parse-json@4.0.0: + resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} + dev: false + + /@types/prop-types@15.7.5: + resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + + /@types/react-is@18.2.1: + resolution: {integrity: sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==} + dependencies: + '@types/react': 18.2.20 + dev: false + + /@types/react-transition-group@4.4.6: + resolution: {integrity: sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==} + dependencies: + '@types/react': 18.2.20 + + /@types/react@18.2.20: + resolution: {integrity: sha512-WKNtmsLWJM/3D5mG4U84cysVY31ivmyw85dE84fOCk5Hx78wezB/XEjVPWl2JTZ5FkEeaTJf+VgUAUn3PE7Isw==} + dependencies: + '@types/prop-types': 15.7.5 + '@types/scheduler': 0.16.3 + csstype: 3.1.2 + + /@types/scheduler@0.16.3: + resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + /arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + /array-buffer-byte-length@1.0.0: + resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + dependencies: + call-bind: 1.0.2 + is-array-buffer: 3.0.2 + dev: true + + /arraybuffer.prototype.slice@1.0.1: + resolution: {integrity: sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + call-bind: 1.0.2 + define-properties: 1.2.0 + get-intrinsic: 1.2.1 + is-array-buffer: 3.0.2 + is-shared-array-buffer: 1.0.2 + dev: true + + /asn1.js@5.4.1: + resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} + dependencies: + bn.js: 4.12.0 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + safer-buffer: 2.1.2 + dev: true + + /assert@1.5.0: + resolution: {integrity: sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==} + dependencies: + object-assign: 4.1.1 + util: 0.10.3 + dev: true + + /autoprefixer@10.4.12(postcss@8.4.23): + resolution: {integrity: sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + dependencies: + browserslist: 4.21.10 + caniuse-lite: 1.0.30001520 + fraction.js: 4.2.0 + normalize-range: 0.1.2 + picocolors: 1.0.0 + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /available-typed-arrays@1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + dev: true + + /babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + dependencies: + '@babel/runtime': 7.22.10 + cosmiconfig: 7.1.0 + resolve: 1.22.4 + dev: false + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: true + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + + /bn.js@4.12.0: + resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} + dev: true + + /bn.js@5.2.1: + resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} + dev: true + + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + + /brorand@1.1.0: + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + dev: true + + /browserify-aes@1.2.0: + resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==} + dependencies: + buffer-xor: 1.0.3 + cipher-base: 1.0.4 + create-hash: 1.2.0 + evp_bytestokey: 1.0.3 + inherits: 2.0.4 + safe-buffer: 5.2.1 + dev: true + + /browserify-cipher@1.0.1: + resolution: {integrity: sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==} + dependencies: + browserify-aes: 1.2.0 + browserify-des: 1.0.2 + evp_bytestokey: 1.0.3 + dev: true + + /browserify-des@1.0.2: + resolution: {integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==} + dependencies: + cipher-base: 1.0.4 + des.js: 1.1.0 + inherits: 2.0.4 + safe-buffer: 5.2.1 + dev: true + + /browserify-rsa@4.1.0: + resolution: {integrity: sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==} + dependencies: + bn.js: 5.2.1 + randombytes: 2.1.0 + dev: true + + /browserify-sign@4.2.1: + resolution: {integrity: sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==} + dependencies: + bn.js: 5.2.1 + browserify-rsa: 4.1.0 + create-hash: 1.2.0 + create-hmac: 1.1.7 + elliptic: 6.5.4 + inherits: 2.0.4 + parse-asn1: 5.1.6 + readable-stream: 3.6.2 + safe-buffer: 5.2.1 + dev: true + + /browserify-zlib@0.2.0: + resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} + dependencies: + pako: 1.0.11 + dev: true + + /browserslist@4.21.10: + resolution: {integrity: sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001520 + electron-to-chromium: 1.4.492 + node-releases: 2.0.13 + update-browserslist-db: 1.0.11(browserslist@4.21.10) + dev: true + + /buffer-xor@1.0.3: + resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==} + dev: true + + /buffer@4.9.2: + resolution: {integrity: sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + isarray: 1.0.0 + dev: true + + /builtin-status-codes@3.0.0: + resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} + dev: true + + /call-bind@1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.2.1 + dev: true + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: false + + /camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + /caniuse-api@3.0.0: + resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} + dependencies: + browserslist: 4.21.10 + caniuse-lite: 1.0.30001520 + lodash.memoize: 4.1.2 + lodash.uniq: 4.5.0 + dev: true + + /caniuse-lite@1.0.30001520: + resolution: {integrity: sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==} + dev: true + + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + + /cipher-base@1.0.4: + resolution: {integrity: sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==} + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + dev: true + + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + + /clsx@1.2.1: + resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + engines: {node: '>=6'} + dev: false + + /clsx@2.0.0: + resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} + engines: {node: '>=6'} + dev: false + + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + dev: true + + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + /commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + /console-browserify@1.2.0: + resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==} + dev: true + + /constants-browserify@1.0.0: + resolution: {integrity: sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==} + dev: true + + /convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + dev: false + + /core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: true + + /cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + dependencies: + '@types/parse-json': 4.0.0 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + dev: false + + /create-ecdh@4.0.4: + resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==} + dependencies: + bn.js: 4.12.0 + elliptic: 6.5.4 + dev: true + + /create-hash@1.2.0: + resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} + dependencies: + cipher-base: 1.0.4 + inherits: 2.0.4 + md5.js: 1.3.5 + ripemd160: 2.0.2 + sha.js: 2.4.11 + dev: true + + /create-hmac@1.1.7: + resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} + dependencies: + cipher-base: 1.0.4 + create-hash: 1.2.0 + inherits: 2.0.4 + ripemd160: 2.0.2 + safe-buffer: 5.2.1 + sha.js: 2.4.11 + dev: true + + /create-react-class@15.7.0: + resolution: {integrity: sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + dev: false + + /cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + dependencies: + cross-spawn: 7.0.3 + dev: false + + /cross-spawn@6.0.5: + resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} + engines: {node: '>=4.8'} + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.2 + shebang-command: 1.2.0 + which: 1.3.1 + dev: true + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: false + + /crypto-browserify@3.12.0: + resolution: {integrity: sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==} + dependencies: + browserify-cipher: 1.0.1 + browserify-sign: 4.2.1 + create-ecdh: 4.0.4 + create-hash: 1.2.0 + create-hmac: 1.1.7 + diffie-hellman: 5.0.3 + inherits: 2.0.4 + pbkdf2: 3.1.2 + public-encrypt: 4.0.3 + randombytes: 2.1.0 + randomfill: 1.0.4 + dev: true + + /css-declaration-sorter@6.4.1(postcss@8.4.23): + resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==} + engines: {node: ^10 || ^12 || >=14} + peerDependencies: + postcss: ^8.0.9 + dependencies: + postcss: 8.4.23 + dev: true + + /css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.1.0 + nth-check: 2.1.1 + dev: true + + /css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.0.2 + dev: true + + /css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.0.2 + dev: true + + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + dev: true + + /cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + /cssnano-preset-default@6.0.1(postcss@8.4.23): + resolution: {integrity: sha512-7VzyFZ5zEB1+l1nToKyrRkuaJIx0zi/1npjvZfbBwbtNTzhLtlvYraK/7/uqmX2Wb2aQtd983uuGw79jAjLSuQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + css-declaration-sorter: 6.4.1(postcss@8.4.23) + cssnano-utils: 4.0.0(postcss@8.4.23) + postcss: 8.4.23 + postcss-calc: 9.0.1(postcss@8.4.23) + postcss-colormin: 6.0.0(postcss@8.4.23) + postcss-convert-values: 6.0.0(postcss@8.4.23) + postcss-discard-comments: 6.0.0(postcss@8.4.23) + postcss-discard-duplicates: 6.0.0(postcss@8.4.23) + postcss-discard-empty: 6.0.0(postcss@8.4.23) + postcss-discard-overridden: 6.0.0(postcss@8.4.23) + postcss-merge-longhand: 6.0.0(postcss@8.4.23) + postcss-merge-rules: 6.0.1(postcss@8.4.23) + postcss-minify-font-values: 6.0.0(postcss@8.4.23) + postcss-minify-gradients: 6.0.0(postcss@8.4.23) + postcss-minify-params: 6.0.0(postcss@8.4.23) + postcss-minify-selectors: 6.0.0(postcss@8.4.23) + postcss-normalize-charset: 6.0.0(postcss@8.4.23) + postcss-normalize-display-values: 6.0.0(postcss@8.4.23) + postcss-normalize-positions: 6.0.0(postcss@8.4.23) + postcss-normalize-repeat-style: 6.0.0(postcss@8.4.23) + postcss-normalize-string: 6.0.0(postcss@8.4.23) + postcss-normalize-timing-functions: 6.0.0(postcss@8.4.23) + postcss-normalize-unicode: 6.0.0(postcss@8.4.23) + postcss-normalize-url: 6.0.0(postcss@8.4.23) + postcss-normalize-whitespace: 6.0.0(postcss@8.4.23) + postcss-ordered-values: 6.0.0(postcss@8.4.23) + postcss-reduce-initial: 6.0.0(postcss@8.4.23) + postcss-reduce-transforms: 6.0.0(postcss@8.4.23) + postcss-svgo: 6.0.0(postcss@8.4.23) + postcss-unique-selectors: 6.0.0(postcss@8.4.23) + dev: true + + /cssnano-utils@4.0.0(postcss@8.4.23): + resolution: {integrity: sha512-Z39TLP+1E0KUcd7LGyF4qMfu8ZufI0rDzhdyAMsa/8UyNUU8wpS0fhdBxbQbv32r64ea00h4878gommRVg2BHw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + dev: true + + /cssnano@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-RGlcbzGhzEBCHuQe3k+Udyj5M00z0pm9S+VurHXFEOXxH+y0sVrJH2sMzoyz2d8N1EScazg+DVvmgyx0lurwwA==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + cssnano-preset-default: 6.0.1(postcss@8.4.23) + lilconfig: 2.1.0 + postcss: 8.4.23 + dev: true + + /csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + dependencies: + css-tree: 2.2.1 + dev: true + + /csstype@3.1.2: + resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + + /define-properties@1.2.0: + resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + dev: true + + /dependency-graph@0.11.0: + resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} + engines: {node: '>= 0.6.0'} + dev: true + + /des.js@1.1.0: + resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + dev: true + + /didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + /diffie-hellman@5.0.3: + resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} + dependencies: + bn.js: 4.12.0 + miller-rabin: 4.0.1 + randombytes: 2.1.0 + dev: true + + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + /dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dependencies: + '@babel/runtime': 7.22.10 + csstype: 3.1.2 + dev: false + + /dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + dev: true + + /domain-browser@1.2.0: + resolution: {integrity: sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==} + engines: {node: '>=0.4', npm: '>=1.2'} + dev: true + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: true + + /domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: true + + /domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dev: true + + /electron-to-chromium@1.4.492: + resolution: {integrity: sha512-36K9b/6skMVwAIEsC7GiQ8I8N3soCALVSHqWHzNDtGemAcI9Xu8hP02cywWM0A794rTHm0b0zHPeLJHtgFVamQ==} + dev: true + + /elliptic@6.5.4: + resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} + dependencies: + bn.js: 4.12.0 + brorand: 1.1.0 + hash.js: 1.1.7 + hmac-drbg: 1.0.1 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + dev: true + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + dev: true + + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + + /es-abstract@1.22.1: + resolution: {integrity: sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + arraybuffer.prototype.slice: 1.0.1 + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + es-set-tostringtag: 2.0.1 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.5 + get-intrinsic: 1.2.1 + get-symbol-description: 1.0.0 + globalthis: 1.0.3 + gopd: 1.0.1 + has: 1.0.3 + has-property-descriptors: 1.0.0 + has-proto: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.5 + is-array-buffer: 3.0.2 + is-callable: 1.2.7 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-typed-array: 1.1.12 + is-weakref: 1.0.2 + object-inspect: 1.12.3 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.5.0 + safe-array-concat: 1.0.0 + safe-regex-test: 1.0.0 + string.prototype.trim: 1.2.7 + string.prototype.trimend: 1.0.6 + string.prototype.trimstart: 1.0.6 + typed-array-buffer: 1.0.0 + typed-array-byte-length: 1.0.0 + typed-array-byte-offset: 1.0.0 + typed-array-length: 1.0.4 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.11 + dev: true + + /es-set-tostringtag@2.0.1: + resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.1 + has: 1.0.3 + has-tostringtag: 1.0.0 + dev: true + + /es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + dev: true + + /escalade@3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: true + + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: false + + /events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + dev: true + + /evp_bytestokey@1.0.3: + resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} + dependencies: + md5.js: 1.3.5 + safe-buffer: 5.2.1 + dev: true + + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + + /find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + dev: false + + /for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + dependencies: + is-callable: 1.2.7 + dev: true + + /fraction.js@4.2.0: + resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} + dev: true + + /fs-extra@11.1.1: + resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} + engines: {node: '>=14.14'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.0 + dev: true + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + optional: true + + /function-bind@1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + + /function.prototype.name@1.1.5: + resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + functions-have-names: 1.2.3 + dev: true + + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true + + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true + + /get-intrinsic@1.2.1: + resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-proto: 1.0.1 + has-symbols: 1.0.3 + dev: true + + /get-stdin@9.0.0: + resolution: {integrity: sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==} + engines: {node: '>=12'} + dev: true + + /get-symbol-description@1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + dev: true + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + + /glob@7.1.6: + resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + /globalthis@1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + dependencies: + define-properties: 1.2.0 + dev: true + + /globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.3.1 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 4.0.0 + dev: true + + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.1 + dev: true + + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true + + /has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: true + + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + /has-property-descriptors@1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + dependencies: + get-intrinsic: 1.2.1 + dev: true + + /has-proto@1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + dev: true + + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: true + + /has-tostringtag@1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /has@1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + + /hash-base@3.1.0: + resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==} + engines: {node: '>=4'} + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + safe-buffer: 5.2.1 + dev: true + + /hash.js@1.1.7: + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + dev: true + + /highlight.js@11.5.1: + resolution: {integrity: sha512-LKzHqnxr4CrD2YsNoIf/o5nJ09j4yi/GcH5BnYz9UnVpZdS4ucMgvP61TDty5xJcFGRjnH4DpujkS9bHT3hq0Q==} + engines: {node: '>=12.0.0'} + dev: false + + /hmac-drbg@1.0.1: + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + dependencies: + hash.js: 1.1.7 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + dev: true + + /hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + dependencies: + react-is: 16.13.1 + dev: false + + /hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + dev: true + + /https-browserify@1.0.0: + resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==} + dev: true + + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: true + + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: true + + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: false + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + /inherits@2.0.1: + resolution: {integrity: sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==} + dev: true + + /inherits@2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /internal-slot@1.0.5: + resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.1 + has: 1.0.3 + side-channel: 1.0.4 + dev: true + + /is-array-buffer@3.0.2: + resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + is-typed-array: 1.1.12 + dev: true + + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + /is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + dev: true + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + + /is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: true + + /is-core-module@2.13.0: + resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} + dependencies: + has: 1.0.3 + + /is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + + /is-negative-zero@2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + dev: true + + /is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + /is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-shared-array-buffer@1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /is-typed-array@1.1.12: + resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} + engines: {node: '>= 0.4'} + dependencies: + which-typed-array: 1.1.11 + dev: true + + /is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.2 + dev: true + + /isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + dev: true + + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + /jiti@1.19.1: + resolution: {integrity: sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==} + hasBin: true + + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: false + + /json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + dev: true + + /json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: false + + /jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.0 + optionalDependencies: + graceful-fs: 4.2.11 + dev: true + + /lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + /load-json-file@4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + dependencies: + graceful-fs: 4.2.11 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + dev: true + + /lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + dev: true + + /lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + dev: true + + /loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + dev: false + + /md5.js@1.3.5: + resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} + dependencies: + hash-base: 3.1.0 + inherits: 2.0.4 + safe-buffer: 5.2.1 + dev: true + + /mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + dev: true + + /mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + dev: true + + /memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + dev: true + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + + /miller-rabin@4.0.1: + resolution: {integrity: sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==} + hasBin: true + dependencies: + bn.js: 4.12.0 + brorand: 1.1.0 + dev: true + + /mini-svg-data-uri@1.4.4: + resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} + hasBin: true + dev: false + + /minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + dev: true + + /minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + dev: true + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + /nanoid@3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + /nice-try@1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + dev: true + + /node-libs-browser@2.2.1: + resolution: {integrity: sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==} + dependencies: + assert: 1.5.0 + browserify-zlib: 0.2.0 + buffer: 4.9.2 + console-browserify: 1.2.0 + constants-browserify: 1.0.0 + crypto-browserify: 3.12.0 + domain-browser: 1.2.0 + events: 3.3.0 + https-browserify: 1.0.0 + os-browserify: 0.3.0 + path-browserify: 0.0.1 + process: 0.11.10 + punycode: 1.4.1 + querystring-es3: 0.2.1 + readable-stream: 2.3.8 + stream-browserify: 2.0.2 + stream-http: 2.8.3 + string_decoder: 1.3.0 + timers-browserify: 2.0.12 + tty-browserify: 0.0.0 + url: 0.11.1 + util: 0.11.1 + vm-browserify: 1.1.2 + dev: true + + /node-releases@2.0.13: + resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} + dev: true + + /normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.4 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + dev: true + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + /normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + dev: true + + /npm-run-all@4.1.5: + resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==} + engines: {node: '>= 4'} + hasBin: true + dependencies: + ansi-styles: 3.2.1 + chalk: 2.4.2 + cross-spawn: 6.0.5 + memorystream: 0.3.1 + minimatch: 3.1.2 + pidtree: 0.3.1 + read-pkg: 3.0.0 + shell-quote: 1.8.1 + string.prototype.padend: 3.1.4 + dev: true + + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + dev: true + + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + /object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + /object-inspect@1.12.3: + resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + dev: true + + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true + + /object.assign@4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + + /os-browserify@0.3.0: + resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} + dev: true + + /pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + dev: true + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: false + + /parse-asn1@5.1.6: + resolution: {integrity: sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==} + dependencies: + asn1.js: 5.4.1 + browserify-aes: 1.2.0 + evp_bytestokey: 1.0.3 + pbkdf2: 3.1.2 + safe-buffer: 5.2.1 + dev: true + + /parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + dependencies: + error-ex: 1.3.2 + json-parse-better-errors: 1.0.2 + dev: true + + /parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + dependencies: + '@babel/code-frame': 7.22.10 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + dev: false + + /path-browserify@0.0.1: + resolution: {integrity: sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==} + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + /path-key@2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + dev: true + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: false + + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + /path-type@3.0.0: + resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} + engines: {node: '>=4'} + dependencies: + pify: 3.0.0 + dev: true + + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + /pbkdf2@3.1.2: + resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} + engines: {node: '>=0.12'} + dependencies: + create-hash: 1.2.0 + create-hmac: 1.1.7 + ripemd160: 2.0.2 + safe-buffer: 5.2.1 + sha.js: 2.4.11 + dev: true + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + /pidtree@0.3.1: + resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==} + engines: {node: '>=0.10'} + hasBin: true + dev: true + + /pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + /pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + dev: true + + /pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + /postcss-calc@9.0.1(postcss@8.4.23): + resolution: {integrity: sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.2 + dependencies: + postcss: 8.4.23 + postcss-selector-parser: 6.0.13 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-cli@10.1.0(postcss@8.4.23): + resolution: {integrity: sha512-Zu7PLORkE9YwNdvOeOVKPmWghprOtjFQU3srMUGbdz3pHJiFh7yZ4geiZFMkjMfB0mtTFR3h8RemR62rPkbOPA==} + engines: {node: '>=14'} + hasBin: true + peerDependencies: + postcss: ^8.0.0 + dependencies: + chokidar: 3.5.3 + dependency-graph: 0.11.0 + fs-extra: 11.1.1 + get-stdin: 9.0.0 + globby: 13.2.2 + picocolors: 1.0.0 + postcss: 8.4.23 + postcss-load-config: 4.0.1(postcss@8.4.23) + postcss-reporter: 7.0.5(postcss@8.4.23) + pretty-hrtime: 1.0.3 + read-cache: 1.0.0 + slash: 5.1.0 + yargs: 17.7.2 + transitivePeerDependencies: + - ts-node + dev: true + + /postcss-colormin@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-EuO+bAUmutWoZYgHn2T1dG1pPqHU6L4TjzPlu4t1wZGXQ/fxV16xg2EJmYi0z+6r+MGV1yvpx1BHkUaRrPa2bw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.10 + caniuse-api: 3.0.0 + colord: 2.9.3 + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-convert-values@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-U5D8QhVwqT++ecmy8rnTb+RL9n/B806UVaS3m60lqle4YDFcpbS3ae5bTQIh3wOGUSDHSEtMYLs/38dNG7EYFw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.10 + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-discard-comments@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-p2skSGqzPMZkEQvJsgnkBhCn8gI7NzRH2683EEjrIkoMiwRELx68yoUJ3q3DGSGuQ8Ug9Gsn+OuDr46yfO+eFw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + dev: true + + /postcss-discard-duplicates@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-bU1SXIizMLtDW4oSsi5C/xHKbhLlhek/0/yCnoMQany9k3nPBq+Ctsv/9oMmyqbR96HYHxZcHyK2HR5P/mqoGA==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + dev: true + + /postcss-discard-empty@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-b+h1S1VT6dNhpcg+LpyiUrdnEZfICF0my7HAKgJixJLW7BnNmpRH34+uw/etf5AhOlIhIAuXApSzzDzMI9K/gQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + dev: true + + /postcss-discard-overridden@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-4VELwssYXDFigPYAZ8vL4yX4mUepF/oCBeeIT4OXsJPYOtvJumyz9WflmJWTfDwCUcpDR+z0zvCWBXgTx35SVw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + dev: true + + /postcss-import@15.1.0(postcss@8.4.23): + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + dependencies: + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.4 + + /postcss-js@4.0.1(postcss@8.4.23): + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.23 + + /postcss-load-config@4.0.1(postcss@8.4.23): + resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.1.0 + postcss: 8.4.23 + yaml: 2.3.1 + + /postcss-merge-longhand@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-4VSfd1lvGkLTLYcxFuISDtWUfFS4zXe0FpF149AyziftPFQIWxjvFSKhA4MIxMe4XM3yTDgQMbSNgzIVxChbIg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + stylehacks: 6.0.0(postcss@8.4.23) + dev: true + + /postcss-merge-rules@6.0.1(postcss@8.4.23): + resolution: {integrity: sha512-a4tlmJIQo9SCjcfiCcCMg/ZCEe0XTkl/xK0XHBs955GWg9xDX3NwP9pwZ78QUOWB8/0XCjZeJn98Dae0zg6AAw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.10 + caniuse-api: 3.0.0 + cssnano-utils: 4.0.0(postcss@8.4.23) + postcss: 8.4.23 + postcss-selector-parser: 6.0.13 + dev: true + + /postcss-minify-font-values@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-zNRAVtyh5E8ndZEYXA4WS8ZYsAp798HiIQ1V2UF/C/munLp2r1UGHwf1+6JFu7hdEhJFN+W1WJQKBrtjhFgEnA==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-minify-gradients@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-wO0F6YfVAR+K1xVxF53ueZJza3L+R3E6cp0VwuXJQejnNUH0DjcAFe3JEBeTY1dLwGa0NlDWueCA1VlEfiKgAA==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + colord: 2.9.3 + cssnano-utils: 4.0.0(postcss@8.4.23) + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-minify-params@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-Fz/wMQDveiS0n5JPcvsMeyNXOIMrwF88n7196puSuQSWSa+/Ofc1gDOSY2xi8+A4PqB5dlYCKk/WfqKqsI+ReQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.10 + cssnano-utils: 4.0.0(postcss@8.4.23) + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-minify-selectors@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-ec/q9JNCOC2CRDNnypipGfOhbYPuUkewGwLnbv6omue/PSASbHSU7s6uSQ0tcFRVv731oMIx8k0SP4ZX6be/0g==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + postcss-selector-parser: 6.0.13 + dev: true + + /postcss-nested@6.0.1(postcss@8.4.23): + resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.23 + postcss-selector-parser: 6.0.13 + + /postcss-normalize-charset@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-cqundwChbu8yO/gSWkuFDmKrCZ2vJzDAocheT2JTd0sFNA4HMGoKMfbk2B+J0OmO0t5GUkiAkSM5yF2rSLUjgQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + dev: true + + /postcss-normalize-display-values@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-Qyt5kMrvy7dJRO3OjF7zkotGfuYALETZE+4lk66sziWSPzlBEt7FrUshV6VLECkI4EN8Z863O6Nci4NXQGNzYw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-normalize-positions@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-mPCzhSV8+30FZyWhxi6UoVRYd3ZBJgTRly4hOkaSifo0H+pjDYcii/aVT4YE6QpOil15a5uiv6ftnY3rm0igPg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-normalize-repeat-style@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-50W5JWEBiOOAez2AKBh4kRFm2uhrT3O1Uwdxz7k24aKtbD83vqmcVG7zoIwo6xI2FZ/HDlbrCopXhLeTpQib1A==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-normalize-string@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-KWkIB7TrPOiqb8ZZz6homet2KWKJwIlysF5ICPZrXAylGe2hzX/HSf4NTX2rRPJMAtlRsj/yfkrWGavFuB+c0w==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-normalize-timing-functions@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-tpIXWciXBp5CiFs8sem90IWlw76FV4oi6QEWfQwyeREVwUy39VSeSqjAT7X0Qw650yAimYW5gkl2Gd871N5SQg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-normalize-unicode@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-ui5crYkb5ubEUDugDc786L/Me+DXp2dLg3fVJbqyAl0VPkAeALyAijF2zOsnZyaS1HyfPuMH0DwyY18VMFVNkg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.10 + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-normalize-url@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-98mvh2QzIPbb02YDIrYvAg4OUzGH7s1ZgHlD3fIdTHLgPLRpv1ZTKJDnSAKr4Rt21ZQFzwhGMXxpXlfrUBKFHw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-normalize-whitespace@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-7cfE1AyLiK0+ZBG6FmLziJzqQCpTQY+8XjMhMAz8WSBSCsCNNUKujgIgjCAmDT3cJ+3zjTXFkoD15ZPsckArVw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-ordered-values@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-K36XzUDpvfG/nWkjs6d1hRBydeIxGpKS2+n+ywlKPzx1nMYDYpoGbcjhj5AwVYJK1qV2/SDoDEnHzlPD6s3nMg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + cssnano-utils: 4.0.0(postcss@8.4.23) + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-reduce-initial@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-s2UOnidpVuXu6JiiI5U+fV2jamAw5YNA9Fdi/GRK0zLDLCfXmSGqQtzpUPtfN66RtCbb9fFHoyZdQaxOB3WxVA==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.10 + caniuse-api: 3.0.0 + postcss: 8.4.23 + dev: true + + /postcss-reduce-transforms@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-FQ9f6xM1homnuy1wLe9lP1wujzxnwt1EwiigtWwuyf8FsqqXUDUp2Ulxf9A5yjlUOTdCJO6lonYjg1mgqIIi2w==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-reporter@7.0.5(postcss@8.4.23): + resolution: {integrity: sha512-glWg7VZBilooZGOFPhN9msJ3FQs19Hie7l5a/eE6WglzYqVeH3ong3ShFcp9kDWJT1g2Y/wd59cocf9XxBtkWA==} + engines: {node: '>=10'} + peerDependencies: + postcss: ^8.1.0 + dependencies: + picocolors: 1.0.0 + postcss: 8.4.23 + thenby: 1.3.4 + dev: true + + /postcss-selector-parser@6.0.13: + resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + /postcss-svgo@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-r9zvj/wGAoAIodn84dR/kFqwhINp5YsJkLoujybWG59grR/IHx+uQ2Zo+IcOwM0jskfYX3R0mo+1Kip1VSNcvw==} + engines: {node: ^14 || ^16 || >= 18} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + svgo: 3.0.2 + dev: true + + /postcss-unique-selectors@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-EPQzpZNxOxP7777t73RQpZE5e9TrnCrkvp7AH7a0l89JmZiPnS82y216JowHXwpBCQitfyxrof9TK3rYbi7/Yw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.23 + postcss-selector-parser: 6.0.13 + dev: true + + /postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + /postcss@8.4.23: + resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.6 + picocolors: 1.0.0 + source-map-js: 1.0.2 + + /pretty-hrtime@1.0.3: + resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==} + engines: {node: '>= 0.8'} + dev: true + + /process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + dev: true + + /process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + dev: true + + /prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + dev: false + + /public-encrypt@4.0.3: + resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==} + dependencies: + bn.js: 4.12.0 + browserify-rsa: 4.1.0 + create-hash: 1.2.0 + parse-asn1: 5.1.6 + randombytes: 2.1.0 + safe-buffer: 5.2.1 + dev: true + + /punycode@1.4.1: + resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} + dev: true + + /qs@6.11.2: + resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: true + + /querystring-es3@0.2.1: + resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==} + engines: {node: '>=0.4.x'} + dev: true + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + /randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /randomfill@1.0.4: + resolution: {integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==} + dependencies: + randombytes: 2.1.0 + safe-buffer: 5.2.1 + dev: true + + /react-dom@18.2.0(react@18.2.0): + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + peerDependencies: + react: ^18.2.0 + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.0 + dev: false + + /react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + dev: false + + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: false + + /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + dependencies: + '@babel/runtime': 7.22.10 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /react@18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + dev: false + + /read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + dependencies: + pify: 2.3.0 + + /read-pkg@3.0.0: + resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} + engines: {node: '>=4'} + dependencies: + load-json-file: 4.0.0 + normalize-package-data: 2.5.0 + path-type: 3.0.0 + dev: true + + /readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + dev: true + + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: true + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + + /readline-sync@1.4.10: + resolution: {integrity: sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==} + engines: {node: '>= 0.8.0'} + dev: true + + /regenerator-runtime@0.14.0: + resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + dev: false + + /regexp.prototype.flags@1.5.0: + resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + functions-have-names: 1.2.3 + dev: true + + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + + /reselect@4.1.8: + resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==} + dev: false + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: false + + /resolve@1.22.4: + resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} + hasBin: true + dependencies: + is-core-module: 2.13.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + /ripemd160@2.0.2: + resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} + dependencies: + hash-base: 3.1.0 + inherits: 2.0.4 + dev: true + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + + /safe-array-concat@1.0.0: + resolution: {integrity: sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==} + engines: {node: '>=0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + has-symbols: 1.0.3 + isarray: 2.0.5 + dev: true + + /safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + dev: true + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: true + + /safe-regex-test@1.0.0: + resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + is-regex: 1.1.4 + dev: true + + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: true + + /scheduler@0.23.0: + resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + dependencies: + loose-envify: 1.4.0 + dev: false + + /semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + dev: true + + /setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + dev: true + + /sha.js@2.4.11: + resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + hasBin: true + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + dev: true + + /shadow-cljs-jar@1.3.4: + resolution: {integrity: sha512-cZB2pzVXBnhpJ6PQdsjO+j/MksR28mv4QD/hP/2y1fsIa9Z9RutYgh3N34FZ8Ktl4puAXaIGlct+gMCJ5BmwmA==} + dev: true + + /shadow-cljs@2.25.2: + resolution: {integrity: sha512-2Pe6LW839rGIPsWxXrjiWRETlCrNPH19u881nwsSkLLLt540lTt1OmbjlGLJjQta2t74WJI7gu6c2tjMSyUGcQ==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + node-libs-browser: 2.2.1 + readline-sync: 1.4.10 + shadow-cljs-jar: 1.3.4 + source-map-support: 0.4.18 + which: 1.3.1 + ws: 7.5.9 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + + /shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + dependencies: + shebang-regex: 1.0.0 + dev: true + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: false + + /shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + dev: true + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: false + + /shell-quote@1.8.1: + resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + dev: true + + /side-channel@1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + object-inspect: 1.12.3 + dev: true + + /slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + dev: true + + /slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} + dev: true + + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + + /source-map-support@0.4.18: + resolution: {integrity: sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==} + dependencies: + source-map: 0.5.7 + dev: true + + /source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + /spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.13 + dev: true + + /spdx-exceptions@2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true + + /spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.13 + dev: true + + /spdx-license-ids@3.0.13: + resolution: {integrity: sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==} + dev: true + + /stream-browserify@2.0.2: + resolution: {integrity: sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==} + dependencies: + inherits: 2.0.4 + readable-stream: 2.3.8 + dev: true + + /stream-http@2.8.3: + resolution: {integrity: sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==} + dependencies: + builtin-status-codes: 3.0.0 + inherits: 2.0.4 + readable-stream: 2.3.8 + to-arraybuffer: 1.0.1 + xtend: 4.0.2 + dev: true + + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + + /string.prototype.padend@3.1.4: + resolution: {integrity: sha512-67otBXoksdjsnXXRUq+KMVTdlVRZ2af422Y0aTyTjVaoQkGr3mxl2Bc5emi7dOQ3OGVVQQskmLEWwFXwommpNw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + dev: true + + /string.prototype.trim@1.2.7: + resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + dev: true + + /string.prototype.trimend@1.0.6: + resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + dev: true + + /string.prototype.trimstart@1.0.6: + resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + dev: true + + /string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + dependencies: + safe-buffer: 5.1.2 + dev: true + + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true + + /stylehacks@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-+UT589qhHPwz6mTlCLSt/vMNTJx8dopeJlZAlBMJPWA3ORqu6wmQY7FBXf+qD+FsqoBJODyqNxOUP3jdntFRdw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.10 + postcss: 8.4.23 + postcss-selector-parser: 6.0.13 + dev: true + + /stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + dev: false + + /stylis@4.3.0: + resolution: {integrity: sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==} + dev: false + + /sucrase@3.34.0: + resolution: {integrity: sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==} + engines: {node: '>=8'} + hasBin: true + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + commander: 4.1.1 + glob: 7.1.6 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + /svgo@3.0.2: + resolution: {integrity: sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 5.1.0 + css-tree: 2.3.1 + csso: 5.0.5 + picocolors: 1.0.0 + dev: true + + /tailwindcss@3.3.2: + resolution: {integrity: sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.5.3 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.1 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.19.1 + lilconfig: 2.1.0 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.0 + postcss: 8.4.23 + postcss-import: 15.1.0(postcss@8.4.23) + postcss-js: 4.0.1(postcss@8.4.23) + postcss-load-config: 4.0.1(postcss@8.4.23) + postcss-nested: 6.0.1(postcss@8.4.23) + postcss-selector-parser: 6.0.13 + postcss-value-parser: 4.2.0 + resolve: 1.22.4 + sucrase: 3.34.0 + transitivePeerDependencies: + - ts-node + + /thenby@1.3.4: + resolution: {integrity: sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==} + dev: true + + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + + /timers-browserify@2.0.12: + resolution: {integrity: sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==} + engines: {node: '>=0.6.0'} + dependencies: + setimmediate: 1.0.5 + dev: true + + /to-arraybuffer@1.0.1: + resolution: {integrity: sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==} + dev: true + + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + dev: false + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + + /ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + /tty-browserify@0.0.0: + resolution: {integrity: sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==} + dev: true + + /typed-array-buffer@1.0.0: + resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + is-typed-array: 1.1.12 + dev: true + + /typed-array-byte-length@1.0.0: + resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + for-each: 0.3.3 + has-proto: 1.0.1 + is-typed-array: 1.1.12 + dev: true + + /typed-array-byte-offset@1.0.0: + resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + has-proto: 1.0.1 + is-typed-array: 1.1.12 + dev: true + + /typed-array-length@1.0.4: + resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + dependencies: + call-bind: 1.0.2 + for-each: 0.3.3 + is-typed-array: 1.1.12 + dev: true + + /unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.2 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + dev: true + + /universalify@2.0.0: + resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} + engines: {node: '>= 10.0.0'} + dev: true + + /update-browserslist-db@1.0.11(browserslist@4.21.10): + resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.21.10 + escalade: 3.1.1 + picocolors: 1.0.0 + dev: true + + /url@0.11.1: + resolution: {integrity: sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==} + dependencies: + punycode: 1.4.1 + qs: 6.11.2 + dev: true + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + /util@0.10.3: + resolution: {integrity: sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ==} + dependencies: + inherits: 2.0.1 + dev: true + + /util@0.11.1: + resolution: {integrity: sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==} + dependencies: + inherits: 2.0.3 + dev: true + + /validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + dev: true + + /vm-browserify@1.1.2: + resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} + dev: true + + /which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + dev: true + + /which-typed-array@1.1.11: + resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + dev: true + + /which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: false + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + /ws@7.5.9: + resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: true + + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: false + + /yaml@2.3.1: + resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} + engines: {node: '>= 14'} + + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + dev: true + + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + dev: true diff --git a/contrib/clojure/postcss.config.js b/contrib/clojure/postcss.config.js new file mode 100644 index 00000000..219d96e4 --- /dev/null +++ b/contrib/clojure/postcss.config.js @@ -0,0 +1,7 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + cssnano: process.env.NODE_ENV == 'production' ? {} : false + } +} diff --git a/contrib/clojure/public/index.html b/contrib/clojure/public/index.html new file mode 100644 index 00000000..f81298f4 --- /dev/null +++ b/contrib/clojure/public/index.html @@ -0,0 +1,14 @@ + + + + OpenLLM + + + + + + +
+ + + diff --git a/contrib/clojure/public/static/atom-one-light.css b/contrib/clojure/public/static/atom-one-light.css new file mode 100644 index 00000000..fde3193e --- /dev/null +++ b/contrib/clojure/public/static/atom-one-light.css @@ -0,0 +1,75 @@ +.hljs { + color: #383a42; + background: #fafafa; + } + + .hljs-comment, + .hljs-quote { + color: #a0a1a7; + font-style: italic; + } + + .hljs-doctag, + .hljs-keyword, + .hljs-formula { + color: #a626a4; + } + + .hljs-section, + .hljs-name, + .hljs-selector-tag, + .hljs-deletion, + .hljs-subst { + color: #e45649; + } + + .hljs-literal { + color: #0184bb; + } + + .hljs-string, + .hljs-regexp, + .hljs-addition, + .hljs-attribute, + .hljs-meta .hljs-string { + color: #50a14f; + } + + .hljs-attr, + .hljs-variable, + .hljs-template-variable, + .hljs-type, + .hljs-selector-class, + .hljs-selector-attr, + .hljs-selector-pseudo, + .hljs-number { + color: #986801; + } + + .hljs-symbol, + .hljs-bullet, + .hljs-link, + .hljs-meta, + .hljs-selector-id, + .hljs-title { + color: #4078f2; + } + + .hljs-built_in, + .hljs-title.class_, + .hljs-class .hljs-title { + color: #c18401; + } + + .hljs-emphasis { + font-style: italic; + } + + .hljs-strong { + font-weight: bold; + } + + .hljs-link { + text-decoration: underline; + } + \ No newline at end of file diff --git a/contrib/clojure/public/static/logo-dark.svg b/contrib/clojure/public/static/logo-dark.svg new file mode 100644 index 00000000..bb8a2178 --- /dev/null +++ b/contrib/clojure/public/static/logo-dark.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contrib/clojure/public/static/logo-light.svg b/contrib/clojure/public/static/logo-light.svg new file mode 100644 index 00000000..395b03b7 --- /dev/null +++ b/contrib/clojure/public/static/logo-light.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/contrib/clojure/shadow-cljs.edn b/contrib/clojure/shadow-cljs.edn new file mode 100644 index 00000000..38b8399f --- /dev/null +++ b/contrib/clojure/shadow-cljs.edn @@ -0,0 +1,9 @@ +{:deps {:aliases [:dev]} + :dev-http {8420 "public"} + + :builds + {:app {:target :browser + :output-dir "public/js" + :asset-path "/js" + :modules {:main {:init-fn openllm.app/init}} + :build-hooks [(openllm.build/generate-models-json)]}}} diff --git a/contrib/clojure/src/css/tailwind.css b/contrib/clojure/src/css/tailwind.css new file mode 100644 index 00000000..b65e93fd --- /dev/null +++ b/contrib/clojure/src/css/tailwind.css @@ -0,0 +1,96 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + textarea { + @apply appearance-none border border-gray-300 rounded-none shadow-sm placeholder-gray-400 focus:outline-none focus:ring-gray-500 focus:border-gray-500 scrollbar; + } + + /*button[type='button'] { + @apply text-white bg-gray-950 rounded hover:bg-gray-800 focus:outline-none; + }*/ + + input[type='range'] { + height: 0.25rem; + @apply appearance-none bg-gray-900 rounded-md; + } + + select { + @apply appearance-none text-sm border-gray-300 focus:outline-none focus:ring-gray-500 focus:border-gray-500; + } + + input[type='checkbox'] { + @apply appearance-none w-4 h-4 border border-gray-400 checked:bg-gray-900 checked:border-transparent focus:outline-none focus:ring-gray-500 focus:border-gray-500; + } + + input[type='range']::-webkit-slider-thumb { + @apply appearance-none bg-white border-2 border-gray-900 border-solid rounded-full h-4 w-4 mt-px hover:bg-gray-200; + } + + input[type='range']::-moz-range-thumb { + @apply appearance-none bg-white border-2 border-gray-900 border-solid rounded-full h-3 w-3 hover:bg-gray-200; + } + + input[type='range']::-ms-thumb { + @apply appearance-none bg-white border-2 border-gray-400 rounded-full h-4 w-4; + } + + input[type='number'] { + @apply appearance-none text-gray-700 border-gray-300 shadow-sm focus:ring-gray-500 focus:border-gray-500; + } + + input[type='text'] { + @apply appearance-none text-gray-700 border-gray-300 shadow-sm focus:ring-gray-500 focus:border-gray-500; + } + + input[type='number']::-webkit-outer-spin-button, + input[type='number']::-webkit-inner-spin-button, + input[type='number'] { + -webkit-appearance: none; + margin: 0; + -moz-appearance: textfield !important; + } +} + +@layer utilities { + /* Chrome, Safari and Opera */ + .no-scrollbar::-webkit-scrollbar { + display: none; + } + + .no-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } + + /* Firefox */ + .scrollbar { + scrollbar-width: thin; + scrollbar-color: #000000 #8d8d8d; + } + + /* Chrome, Safari and Opera */ + .scrollbar::-webkit-scrollbar { + width: 10px; + height: 20px; + } + + /* Chrome, Safari and Opera */ + .scrollbar::-webkit-scrollbar-track { + border-radius: 100vh; + background: #8d8d8d; + } + + /* Chrome, Safari and Opera */ + .scrollbar::-webkit-scrollbar-thumb { + background: #000000; + border-radius: 100vh; + /*border: 1px solid #f9a8d4;*/ + } + + /* Chrome, Safari and Opera */ + .scrollbar::-webkit-scrollbar-thumb:hover { + background: #333333; + } +} diff --git a/contrib/clojure/src/generated/README.md b/contrib/clojure/src/generated/README.md new file mode 100644 index 00000000..b022d7a8 --- /dev/null +++ b/contrib/clojure/src/generated/README.md @@ -0,0 +1,4 @@ +> [!IMPORTANT] +> The files in this directory are generated during the build process. **Do not edit it directly.** +> +>For more information, see the `openllm.build` namespace, as well as it's reference in the `shadow-cljs.edn` file. The documentation can be found [here](https://shadow-cljs.github.io/docs/UsersGuide.html#build-hooks) and [here](https://shadow-cljs.github.io/docs/UsersGuide.html#compile-stages). diff --git a/contrib/clojure/src/main/openllm/api/components.cljs b/contrib/clojure/src/main/openllm/api/components.cljs new file mode 100644 index 00000000..6df13ad4 --- /dev/null +++ b/contrib/clojure/src/main/openllm/api/components.cljs @@ -0,0 +1,30 @@ +(ns openllm.api.components + (:require [reagent-mui.icons.file-upload :refer [file-upload]] + [reagent-mui.material.icon-button :refer [icon-button]] + [re-frame.core :as rf] + [reagent.core :as r])) + +(defn file-upload-button + "The file upload reagent custom component." + [{:keys [callback-event]}] + (let [file-reader (js/FileReader.)] + (r/create-class + {:component-did-mount + (fn [_] + (.addEventListener file-reader "load" + (fn [evt] + (let [content (-> evt .-target .-result)] + (rf/dispatch [callback-event content]))))) + :render + (fn [] + [:<> + [:input {:type "file" + :style {:display "none"} + :id "file-upload" + :on-change (fn [evt] + (let [file (-> evt .-target .-files (aget 0))] + (.readAsText file-reader file)))}] + [icon-button {:on-click #(-> (.querySelector js/document "#file-upload") + .click) + :color "primary"} + (r/as-element [file-upload])]])}))) diff --git a/contrib/clojure/src/main/openllm/api/http.cljs b/contrib/clojure/src/main/openllm/api/http.cljs new file mode 100644 index 00000000..2d940077 --- /dev/null +++ b/contrib/clojure/src/main/openllm/api/http.cljs @@ -0,0 +1,43 @@ +(ns openllm.api.http + (:require [ajax.core :as ajax] + [re-frame.core :refer [reg-event-fx]])) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn- get-uri + "Returns the URI for the given endpoint." + [api-base-url endpoint] + (str api-base-url endpoint)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Events ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(reg-event-fx + ::v1-generate + [] + (fn [{:keys [db]} [_ prompt llm-config & {:keys [on-success on-failure]}]] + (let [base-url (get db :api-base-url)] + {:http-xhrio {:method :post + :uri (get-uri base-url "/v1/generate") + :params {:prompt prompt + :llm_config llm-config} + :format (ajax/json-request-format) + :response-format (ajax/json-response-format {:keywords? true}) + :on-success on-success + :on-failure on-failure}}))) + +(reg-event-fx + ::v1-metadata + [] + (fn [{:keys [db]} [_ json & {:keys [on-success on-failure]}]] + (let [base-url (get db :api-base-url)] + {:http-xhrio {:method :post + :uri (get-uri base-url "/v1/metadata") + :params json + :format (ajax/json-request-format) + :response-format (ajax/json-response-format {:keywords? true}) + :on-success on-success + :on-failure on-failure}}))) diff --git a/contrib/clojure/src/main/openllm/api/indexed_db/core.cljs b/contrib/clojure/src/main/openllm/api/indexed_db/core.cljs new file mode 100644 index 00000000..d135b91d --- /dev/null +++ b/contrib/clojure/src/main/openllm/api/indexed_db/core.cljs @@ -0,0 +1,338 @@ +(ns openllm.api.indexed-db.core + "This namespace is a wrapper for the IndexedDB API. It provides + functions to create object stores, add objects to them and retrieve + objects from them. + + The functions in this namespace are meant to be used by other + namespaces, which will provide a higher level API for the application + to use. + + If you stumble upon a parameter named `obj-store-fqn`, this is the fully + qualified name of the object store. This name (or identifier rather) + must consist of a map with two keys: `:db-name` and `:os-name`. `:db-name` + must be a string, which identifies the database. `:os-name` must be a + string, which is the name of the object store." + (:require [openllm.api.log4cljs.core :refer [log]])) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Private API ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(def ^:private ^:const READ_WRITE "readwrite") +(def ^:private ^:const READ_ONLY "readonly") + +(declare create-object-store!) + +(def ^:private ^:const name->db + "This map will hold the database objects for each database name. + The database name is the key and the database object is the value. + This map is used to prevent the creation of multiple database objects + for the same database name. This is important, because the database + object is the only way to interact with the database. + + The lookup into this map is automatically done. While using an atom + adds state to the namespace, the performance and convinience gains + are worth it." + (atom {})) + +(defn- idb-error-callback + "This function is called when an error occurs during an IndexedDB + request. + It will log the error to the browser's console." + [e] + (log :error "Error during IndexedDB request" e)) + +(defn- create-transaction + "Create a transaction for the object store identified by the + `obj-store-fqn` (see namespace docstring for more information). This + function is meant to be used by the object store ('os-*') functions. + This function will create a transaction and return the object store, + which can be used to interact with the database right away. + + We consider this function semi-pure since there are no *notable* + direct side effects." + [obj-store-fqn mode] + (let [{:keys [db-name os-name]} obj-store-fqn + db (get @name->db db-name) + transaction (.transaction db #js [os-name] mode)] + (set! (.-onerror transaction) idb-error-callback) + (-> transaction + (.objectStore os-name)))) + +(defn- on-upgrade-needed! + "This function is called as a callback when the database is upgraded. + It will create the object stores for the application and and save the + backing database in the app-db for later use. + + There are two possible reasons that the database got upgraded: + 1. The database did not exist before and was created. + 2. The database existed before, but the version (and presumably the + schema) was lower/older than the current version. + + Returns `nil`." + [table-info user-callback e] + (let [db (.. e -target -result) + old-version (.-oldVersion db) + new-version (.-version db)] + (when (and (> 0 old-version) (nil? user-callback)) + (throw + (ex-info "The database version was upgraded, but no 'on-upgrade-db-version' callback was provided to 'initialize!'." + {:old-version old-version :new-version new-version}))) + (if (some? user-callback) + (do (log :info (str "Received upgrade needed event, current version is " new-version ", old version is " old-version ". Calling user callback.")) + (user-callback old-version new-version)) + (do (log :info (str "Database and object store created. Current database version is " (.-version db) ".")) + (create-object-store! {:db-name db :os-name (:name table-info)} table-info))) + nil)) + +(defn- on-initialize-success! + "This function is called as a callback when the database is initialized. + It will save the database object in our `name->db` atom for later use. + + Returns `nil`." + [db-name user-callback e] + (let [db (.. e -target -result)] + (swap! name->db assoc db-name db) + (user-callback) + (log :debug "Database initialized and callback function triggered." e)) + nil) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Public API ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn create-object-store! + "Create an object store identified by the `obj-store-fqn` (see namespace + docstring for more information) inside a database. The `:os-name` key of + the `obj-store-fqn` parameter can be chosen freely, but it must be unique + within the database (see namespace docstring for more info) and it should + match the `:name` key of the `table-info` parameter. The `table-info` + parameter must be a map with the following structure: + ```clojure + {:name \"my-obj-store-name\" + :index [{:name \"my-field\" + :unique false}]} + ``` + The `:index` key is a vector of maps, each of which will describe one + index (aka field) of the object store. The `:name` key of the index map + must be unique within the object store. + + Will return the object store object with an open transaction attached, + so that it can be used right away." + [obj-store-fqn table-info] + (let [{:keys [db-name os-name]} obj-store-fqn + db (get @name->db db-name) + object-store (.createObjectStore db os-name #js {:keyPath "id" :autoIncrement true})] + (for [table-idx (:index table-info)] + (.createIndex + object-store + (:name table-idx) (:name table-idx) #js {:unique (:unique table-idx)})) + (set! (.. object-store -transaction -oncomplete) + #(-> db + (.transaction os-name READ_WRITE) + (.objectStore os-name))))) + +(defn os-add! + "Add a single object to the given object store identified by the + `obj-store-fqn` (see namespace docstring for more information). This + function will create a transaction and add the object to the object store. + + Returns `nil`." + [obj-store-fqn entry] + (let [object-store (create-transaction obj-store-fqn READ_WRITE)] + (-> object-store + (.put (clj->js entry)))) + nil) + +(defn os-add-all! + "Add an vector of objects to the given object store identified by the + `obj-store-fqn` (see namespace docstring for more information). This + function will create a transaction and add the objects to the object store. + Note that we create a new transaction for each object. + TODO: Figure out if there is a way with clojure looping constructs to + create a single transaction and add all objects to the object store + at once. + + An exception will be thrown, if the second argument is not a vector! + There are no guarantees that the objects will be added in the excpected + order if the algorithm is not adjusted, so for not no other collections + are allowed. + + Returns `nil`." + [obj-store-fqn entries] + (when-not (vector? entries) + (throw + (ex-info "os-add-all! expects a vector of objects as its third argument." + {:entries entries}))) + (loop [entries entries] + (if (empty? entries) + nil + (do (os-add! obj-store-fqn (first entries)) + (recur (rest entries)))))) + +(defn os-index->object + "Use this function to retrieve a single object from the object store. In + order to retrieve all objects from the object store, use the function + `os-get-all` instead. + You will need to pass `callback-fn`, which must be a function that takes + a single argument. This argument will be the object that was retrieved + from the object store. + + Returns `nil`." + [obj-store-fqn idx callback-fn] + (let [object-store (create-transaction obj-store-fqn READ_ONLY) + request (.get object-store idx)] + (set! (.-onerror request) idb-error-callback) + (set! (.-onsuccess request) + (fn [e] + (callback-fn (.-result (.-target e)))))) + nil) + +(defn os-get-all + "Get all objects from the object store identified by the `obj-store-fqn` + (see namespace docstring for more information). This function will create + a transaction and get all objects from the object store. + `callback-fn` should be a function that takes a vector of objects + as its only argument. + + It is up for discussion, whether this function should be considered + to have side effects or not. I think it should be given some thoughts, + because it opens a transaction and thus locks the data-base, and it + will call the `callback-fn` with the objects from the object store. + + Returns `nil`." + [obj-store-fqn callback-fn] + (let [values (atom []) ;; TODO: grrr + request + (-> + (create-transaction obj-store-fqn READ_ONLY) + .openCursor)] + (set! (.-onerror request) idb-error-callback) + (set! (.-onsuccess request) + (fn [e] + (if-let [cursor (.. e -target -result)] + (do + (swap! values conj (.-value cursor)) + (.continue cursor)) + (callback-fn @values))))) + nil) + +(defn wipe-object-store! + "Wipe the object store identified by the `obj-store-fqn`, see the + docstring of this namespace for more information. + This function should be used with great care, as the wipe will not be + reversible. It will create a transaction and clear the object store. + + Returns `nil`." + [obj-store-fqn] + (let [object-store (create-transaction obj-store-fqn READ_WRITE) + transaction (.-transaction object-store)] + (set! (.-oncomplete transaction) #(log :info "Object store wiped.")) + (set! (.-onerror transaction) idb-error-callback) + (.clear object-store)) + nil) + +(defn initialize! + "Initialize the indexed-db. This function should be called once + when the application starts. The `db-init-callback` function will + be called when the database is initialized. + You should retain the db-name and os-name, as they are required to + interact with the database and object store. For more information see: + `obj-store-fqn` docstring of this namespace. + + The `table-info` parameter must be a map with the following structure: + ```clojure + {:name \"my-obj-store-name\" + :index [{:name \"my-field\" + :unique false}]} + ``` + The `:index` key is a vector of maps, each of which will describe one + index (the equivalend of a field in a SQL table) of the object store. + + Optionally, you may pass a `success-callback` function, which will be + called when the database is initialized. The function will be called + with no arguments. + + Also optionally, you can pass an `on-upgrade` callback function, which + will be called when the database is upgraded to a new version. The + function will be called with the old version number as its first + and the new version as it's second argument. If you do pass a function, + it will be called *instead* of the default callback function. This + means, that you will have to create the object store yourself! + An example of how to do this: + ```clojure + (create-object-store! {:db-name db-name :os-name store-name} + your-table-definition) + ``` + + Returns `nil`." + ([db-info table-info] (initialize! db-info table-info nil nil)) + ([db-info table-info success-callback] (initialize! db-info table-info success-callback nil)) + ([db-info table-info success-callback on-upgrade-db-version] + (let [{:keys [db-name db-version]} db-info + upgrade-callback! (partial on-upgrade-needed! + table-info + on-upgrade-db-version) + request (.open + (. js/window -indexedDB) + db-name db-version)] + (set! (.-onerror request) idb-error-callback) + (set! (.-onupgradeneeded request) upgrade-callback!) + (set! (.-onsuccess request) (partial on-initialize-success! db-name success-callback))) + nil)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Rich Comments ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(comment + (def db-name "test-db") ;; => ["test-db"] + + (def obj-store-name "chat-history") ;; => ["chat-history"] + + (def obj-store-fqn {:db-name db-name :os-name obj-store-name}) + + (def test-messages [{:user :user :text "Hey"} + {:user :model :text "Hey, how are you?"} + {:user :user :text "I'm fine, thanks."} + {:user :model :text "That's good to hear."}]) + + + ;; very simple sanity check + (. js/window -indexedDB) ;; => #object[IDBFactory [object IDBFactory]] + + + ;; initialize the database and creates the object stores + (initialize! {:db-name "test-db" :db-version 1} + {:name "chat-history" + :index [{:name "user" + :unique false}]} + nil) ;; => nil + + + ;; add a single test message to the object store + (os-add! obj-store-fqn + {:user :model :text "test message"}) ;; => nil + + + ;; add the test messages from above to the object store + (os-add-all! obj-store-fqn + test-messages) ;; => nil + + + ;; get the second object from the object store + (os-index->object obj-store-fqn + 2 + #(print (js->clj % :keywordize-keys true))) ;; => nil + ;; and prints: {:user :user, :text Hey} + + + ;; get all objects from the object store + (os-get-all obj-store-fqn + #(print (js->clj % :keywordize-keys true))) ;; => nil + ;; and prints a vector of size 5 with the test messages added above + + + + ;; this will wipe the object store + (wipe-object-store! obj-store-fqn)) diff --git a/contrib/clojure/src/main/openllm/api/log4cljs/core.cljs b/contrib/clojure/src/main/openllm/api/log4cljs/core.cljs new file mode 100644 index 00000000..b8039f96 --- /dev/null +++ b/contrib/clojure/src/main/openllm/api/log4cljs/core.cljs @@ -0,0 +1,44 @@ +(ns openllm.api.log4cljs.core + "Ultra advanced logging framework. Checks all the boxes for an enterprise + grade logging framework: + 1. Logs stuff + 2. Does not allow RCE + -> This technology is years ahead of the competition. looking at you, + log4j.") + +(let [out js/console] + (def ^:private kw->js-log-fn {:debug out.debug + :info out.info + :warn out.warn + :error out.error + :log out.log})) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Public API ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn log + "Log a message to the browser's console. It can log to the levels + `:debug`, `:info`, `:warn` and `:error`. Additionally you can use + `:log` to log to the `log` level, although you can do that with + clojure's `print` function as well, assuming you have enabled + console printing with `enable-console-print!`. + + It is a consideration to also persist any incoming messages to the + database in the future. This could be done using IndexedDB API and + offering a possibility to download (archived?) logs. + + Returns `nil`." + [level & args] + (let [log-fn (kw->js-log-fn level)] + (when (nil? log-fn) + (throw + (ex-info "Invalid log level. Valid log levels are :debug, :info, :warn, :error and :log." + {:level level + :original-args args}))) + (apply log-fn args)) + nil) + + +(comment + ;; will print a message to the console, level "warn" + (log :warn "uptempo hardcore" 200 "bpm, gabber hakken hardcore")) ;; => nil diff --git a/contrib/clojure/src/main/openllm/api/persistence.cljs b/contrib/clojure/src/main/openllm/api/persistence.cljs new file mode 100644 index 00000000..fa4cf967 --- /dev/null +++ b/contrib/clojure/src/main/openllm/api/persistence.cljs @@ -0,0 +1,121 @@ +(ns openllm.api.persistence + (:require [openllm.components.chat.db :as chat-db] + [openllm.api.indexed-db.core :as idb] + [openllm.api.log4cljs.core :refer [log]] + [re-frame.core :as rf])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(def idb-info {:db-name "OpenLLM_clj_GutZuFusss" + :db-version 1}) + +(def idb-table-info + {:name "chat-history" + :index [{:name "user" :unique false}]}) + +(def obj-store-fqn {:db-name (get idb-info :db-name) + :os-name (get idb-table-info :name)}) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn on-db-initialized + "Passed as the callback function to `idb/initialize!` to set the + :idb key in the app-db. + + This function also dispatches the `::sync-chat-history` event to + populate the chat history in the app-db with the data from the + IndexedDB database." + [] + (log :debug "IndexedDB database initialized") + (rf/dispatch [::sync-chat-history])) + +(defn init-idb + "Initializes the IndexedDB database and creates the object store + if it does not exist. + + This function notably registers the `on-db-initialized` function + as a callback function to be called when the IndexedDB database + is initialized." + [] + (log :debug "Initializing IndexedDB database...") + (idb/initialize! idb-info idb-table-info on-db-initialized)) + +(defn chat-history->sanitized + "Takes the chat history from the IndexedDB database and cleans it + up to be used in the app-db. + + First, the `:id` key is removed from each message, and then the + values belonging to the `:user` keys are converted to a keyword. + Finally, the chat history is converted to a vector." + [chat-history] + (let [mapping-fn (fn [message] (-> message + (dissoc , :id) + (assoc , :user (keyword (:user message)))))] + (vec (map mapping-fn chat-history)))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Events ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Adds a chat message to the IndexedDB database. Is dispatched when the user +;; sends or receives a message in the chat. +(rf/reg-event-fx + ::add-to-indexed-db-history + [] + (fn [_ [_ timestamp user message]] + (idb/os-add! obj-store-fqn + {:user user :text message + :timestamp timestamp}))) + +;; This event will override the chat history in the app-db with the data from +;; the IndexedDB database. It will be dispatched as a callback function to +;; `idb/os-get-all`, which is called in the `::sync-chat-history` event from +;; this namespace. +(rf/reg-event-db + ::set-chat-history-app-db + (fn [db [_ chat-history]] + (let [clean-chat-history (chat-history->sanitized chat-history)] + (log :debug "Synchronized chat history with IndexedDB database, loaded" + (count clean-chat-history) "messages.") + (assoc-in db + (chat-db/key-seq :chat-history) + clean-chat-history)))) + +;; Will be dispatched when the IndexedDB database is initialized, and will +;; populate the chat history in the app-db with the data from the IndexedDB +;; database. +;; Passes the `dispatch` function for the `::set-chat-history-app-db` event +;; to the callback function of `idb/os-get-all`. +(rf/reg-event-fx + ::sync-chat-history + [] + (fn [_ [_]] + (let [callback-fn (fn [result] + (rf/dispatch [::set-chat-history-app-db (js->clj result :keywordize-keys true)]))] + (idb/os-get-all obj-store-fqn callback-fn)))) + +(rf/reg-event-fx + ::clear-chat-history + [] + (fn [_ [_]] + (idb/wipe-object-store! obj-store-fqn))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Rich Comments ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(comment + ;; add a chat message to the database + (rf/dispatch [::add-to-indexed-db-history :model "hello"]) + + ;; sanitizing chat history demo + (def chat-history [{:id 1 :user "model" :text "hi"} + {:id 2 :user "user" :text "hey"}]) + + (chat-history->sanitized chat-history) ;; => [{:user :model, :text "hi"} {:user :user, :text "hey"}] + + ;; set the chat history in the app-db to the data from the indexed-db database + (rf/dispatch [::sync-chat-history])) diff --git a/contrib/clojure/src/main/openllm/app.cljs b/contrib/clojure/src/main/openllm/app.cljs new file mode 100644 index 00000000..45b2e53d --- /dev/null +++ b/contrib/clojure/src/main/openllm/app.cljs @@ -0,0 +1,54 @@ +(ns openllm.app + (:require [openllm.api.persistence :as persistence] + [reagent.dom.client :as rdom] + [openllm.views :as views] + [re-frame.core :as rf] + [reagent-mui.styles :as styles] + [reagent-mui.colors :as colors] + [reagent-mui.material.css-baseline :refer [css-baseline]] + ;; the following are only required to make the compiler load the namespaces + [day8.re-frame.http-fx] + [openllm.events] + [openllm.subs] + [openllm.api.http])) + +(def openllm-theme + {:palette {:primary {:main (:black colors/common) + :light (get colors/grey 800) + :dark (get colors/deep-purple 900)} + :secondary {:main (:white colors/common) + :light (get colors/grey 200) + :dark (get colors/grey 800)} + :error colors/red}}) + + +(defn app + "The main app component, which is rendered into the DOM. This component + just wraps the dashboard component, which is the effective root + component of the application." + [] + [:<> + [css-baseline] + [styles/theme-provider (styles/create-theme openllm-theme) + [views/dashboard]]]) + +(defonce root (rdom/create-root (js/document.getElementById "app"))) +(defn ^:dev/after-load start + "Starts the app by rendering the app component into the DOM. This + function is the root rendering function, and is called by the + `init` function right after the databases are initialized." + [] + (rdom/render root [app])) + +(defn init + "This init function is called exactly once when the page loads. + Responsible for initializing the app-db as well as the IndexedDB + (persistent) database. + + This marks the entry point of the application, and is called by shadow-cljs + directly." + [] + (enable-console-print!) ;; so that print writes to `console.log` + (rf/dispatch-sync [:initialise-db]) + (persistence/init-idb) + (start)) diff --git a/contrib/clojure/src/main/openllm/build.clj b/contrib/clojure/src/main/openllm/build.clj new file mode 100644 index 00000000..f9006507 --- /dev/null +++ b/contrib/clojure/src/main/openllm/build.clj @@ -0,0 +1,41 @@ +(ns openllm.build + "This namespace contains build-time functions. These functions are run + at compile time and are used to generate files that are later used by + the application. For example, the `models-data.json` file is generated + here. This file is later used to populate the Model-ID and Model-Type + dropdowns with their respective entries. + See `src/main/openllm/components/model_selection/data.cljs` for an example usage." + (:require [clojure.java.shell :refer [sh]]) + (:refer-clojure :exclude [slurp])) + +(def ^:private ^:const generic-io-error-msg-lol + (str "It is strongly recommended to fix this error, otherwise the UI might not work as expected.\n" + "Checks: Is openllm in your PATH?\n" + " Do you have sufficient priviledges?\n" + " Does the directory './src/generated/' exist?")) + +(defn generate-models-json + "Generates the `models-data.json` file from the `openllm models -o json` + command. That file is later used to populate the Model-ID and Model-Type + dropdowns with their respective entries. + Runs before any compilation is done, as the stage is set to + `:compile-prepare`. + + Returns the build-state as it was received." + {:shadow.build/stage :compile-prepare} + [build-state] + (try + (let [models-json (sh "openllm" "models" "-o" "json")] + (spit "./src/generated/models-data.json" (:out models-json))) + (catch Exception e + (println "Failed to generate models-data.json file. Error: " e) + (println generic-io-error-msg-lol))) + build-state) + + +;; this macro is later used to read the generated `models-data.json` file +;; at compile time. See `src/main/openllm/components/model_selection/data.cljs` +;; for an example usage. +(defmacro slurp + [file] + (clojure.core/slurp file)) diff --git a/contrib/clojure/src/main/openllm/components/chat/db.cljs b/contrib/clojure/src/main/openllm/components/chat/db.cljs new file mode 100644 index 00000000..68633216 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/chat/db.cljs @@ -0,0 +1,45 @@ +(ns openllm.components.chat.db + "The branch of the `app-db` that saves data related to the chat view. This + includes the chat history, the current input value, and the layout of the + prompt. + The path to this branch can be expressed as: + *root -> components -> chat*" + (:require [cljs.spec.alpha :as s])) + +(defn key-seq + "Returns the key sequence to access the chat-db. This is useful for + `assoc-in` and `get-in`. The `more-keys` argument is optional and can be + used to access a sub-key of the chat-db. + Returns the key sequence to access the chat-db." + [& more-keys] + (into [:components-db :chat-db] more-keys)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Spec ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(s/def ::chat-input-value string?) ;; the current value of the input field +(s/def ::chat-history (s/coll-of (s/keys :req-un [::user ::text]) ;; the chat history + :kind vector?)) +(s/def ::layout-modal-open? boolean?) ;; whether the prompt layout modal is open +(s/def ::prompt-layout string?) ;; the current prompt layout + +(s/def ::chat-db (s/keys :req-un [::chat-input-value ;; the spec for the chat-db + ::chat-history + ::layout-modal-open? + ::prompt-layout])) + +(defn initial-db + "Initial values for this branch of the app-db." + [] + {:chat-input-value "" + :chat-history [] + :prompt-layout "" + :layout-modal-open? false}) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Rich Comments ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(comment + ;; check if initial-db is valid + (s/valid? ::chat-db (initial-db))) diff --git a/contrib/clojure/src/main/openllm/components/chat/events.cljs b/contrib/clojure/src/main/openllm/components/chat/events.cljs new file mode 100644 index 00000000..313966f8 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/chat/events.cljs @@ -0,0 +1,122 @@ +(ns openllm.components.chat.events + (:require [openllm.components.chat.db :as db] + [openllm.events :refer [check-spec-interceptor]] + [openllm.api.http :as api] + [openllm.api.persistence :as persistence] + [openllm.api.log4cljs.core :refer [log]] + [re-frame.core :refer [reg-cofx reg-event-db reg-event-fx inject-cofx]] + [clojure.string :as str])) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Coeffects ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(reg-cofx + ::chat-history-element + (fn [cofx _] + (let [element (js/document.getElementById "chat-history-container")] + (assoc cofx :chat-history-element element)))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Events ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(reg-event-db + ::set-chat-input-value + [check-spec-interceptor] + (fn [db [_ new-value]] + (assoc-in db (db/key-seq :chat-input-value) new-value))) + +(reg-event-db + ::add-to-app-db-history + [check-spec-interceptor] + (fn [db [_ timestamp user text]] + (assoc-in db + (db/key-seq :chat-history) + (conj (get-in db (db/key-seq :chat-history)) {:user user + :text text + :timestamp timestamp})))) + +;; Puts the received or sent message into the IndexedDB database aswell +;; as the app-db. +(reg-event-fx + ::add-to-chat-history + [(inject-cofx :time-now)] + (fn [cofx [_ user text]] + (let [{:keys [time-now]} cofx] + {:dispatch-n [[::add-to-app-db-history time-now user text time-now] + [::persistence/add-to-indexed-db-history time-now user text]]}))) + +(reg-event-fx + ::send-prompt-success + [] + (fn [_ [_ response]] + {:dispatch [::add-to-chat-history :model (first (:responses response))]})) + +(reg-event-fx + ::send-prompt-failure + [] + (fn [_ [_ e]] + (log :error "Failed to send prompt" e) + {:dispatch-later [{:ms 10 :dispatch [::add-to-chat-history :model "Sorry, something went wrong."]} + {:ms 20 :dispatch [::auto-scroll]}]})) + +(reg-event-fx + ::on-send-button-click + [] + (fn [{:keys [db]} [_ prompt llm-config]] + (let [input-value (get-in db (db/key-seq :chat-input-value))] + (when (not (str/blank? input-value)) + {:dispatch-n [[::add-to-chat-history :user input-value] + [::api/v1-generate prompt llm-config {:on-success [::send-prompt-success] + :on-failure [::send-prompt-failure]}] + [::set-chat-input-value ""]] + :dispatch-later [{:ms 10 :dispatch [::auto-scroll]}]})))) + +(reg-event-db + ::toggle-modal + [check-spec-interceptor] + (fn [db [_ _]] + (let [new-value (not (get-in db (db/key-seq :layout-modal-open?)))] + (assoc-in db (db/key-seq :layout-modal-open?) new-value)))) + +(reg-event-db + ::set-prompt-layout + [check-spec-interceptor] + (fn [db [_ layout]] + (assoc-in db (db/key-seq :prompt-layout) layout))) + +(reg-event-fx + ::auto-scroll + [(inject-cofx ::chat-history-element)] + (fn [cofx _] + (let [history-elem (get cofx :chat-history-element)] + (if (some? history-elem) + (do (set! (.-scrollTop history-elem) + (.-scrollHeight history-elem)) + {}) + {:dispatch-later [{:ms 10 :dispatch [::auto-scroll]}]})))) + +(reg-event-db + ::clear-chat-history + [check-spec-interceptor] + (fn [db _] + (assoc-in db (db/key-seq :chat-history) []))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Rich Comments ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(comment + ;; import re-frame + #_{:clj-kondo/ignore [:duplicate-require]} + (require '[re-frame.core :as rf]) + + ;; add a chat message to the app-db (makes is appear in the chat history screen) + (rf/dispatch [::add-to-app-db-history :model "hello"]) + + ;; scroll to the bottom + (rf/dispatch [::auto-scroll]) + + ;; clear the chat history + (rf/dispatch [::clear-chat-history])) diff --git a/contrib/clojure/src/main/openllm/components/chat/subs.cljs b/contrib/clojure/src/main/openllm/components/chat/subs.cljs new file mode 100644 index 00000000..e2231461 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/chat/subs.cljs @@ -0,0 +1,49 @@ +(ns openllm.components.chat.subs + (:require [openllm.components.subs :as components-subs] + [openllm.util :as util] + [re-frame.core :refer [reg-sub]])) + +(reg-sub + ::chat-input-value + :<- [::components-subs/chat-db] + (fn [chat-db _] + (:chat-input-value chat-db))) + +(reg-sub + ::chat-history + :<- [::components-subs/chat-db] + (fn [chat-db _] + (:chat-history chat-db))) + +(reg-sub + ::modal-open? + :<- [::components-subs/chat-db] + (fn [chat-db _] + (:layout-modal-open? chat-db))) + +;; This subscription informs it's subscribers about the current prompt layout and +;; possible changes. The user can freely choose any prompt layout, right now it +;; only acts as some kind of preamble. +(reg-sub + ::prompt-layout + :<- [::components-subs/chat-db] + (fn [chat-db _] + (:prompt-layout chat-db))) + +;; This subscription materializes all the data neccessary to build a prompt for a +;; chat model. +;; In essence, it is a concatenation of the prompt layout, the chat history, and +;; the current input value. Lastly we indicate to the model, that it should keep +;; generating tokens from the AI persona. +;; TODO: The names should probably be configurable by the user in the future. +(reg-sub + ::prompt + :<- [::prompt-layout] + :<- [::chat-input-value] + :<- [::chat-history] + (fn [[prompt-layout chat-input-value chat-history] _] + (let [conversation (util/chat-history->string chat-history)] + (str prompt-layout "\n" + conversation "\n" + "user: " chat-input-value "\n" + "model: ")))) diff --git a/contrib/clojure/src/main/openllm/components/chat/views.cljs b/contrib/clojure/src/main/openllm/components/chat/views.cljs new file mode 100644 index 00000000..abdce788 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/chat/views.cljs @@ -0,0 +1,147 @@ +(ns openllm.components.chat.views + (:require [re-frame.core :as rf] + [openllm.components.chat.events :as events] + [openllm.components.chat.subs :as subs] + [openllm.components.side-bar.model-params.subs :as model-params-subs] + [openllm.components.chat.views :as views] + [openllm.api.components :as api-components] + [openllm.api.persistence :as persistence] + [reagent-mui.material.tooltip :refer [tooltip]] + [reagent-mui.material.icon-button :refer [icon-button]] + [reagent-mui.icons.delete-forever :as delete-icon] + [reagent-mui.material.button :refer [button]] + [reagent-mui.material.modal :refer [modal]] + [reagent-mui.material.box :refer [box]] + [reagent-mui.material.paper :refer [paper]] + [reagent-mui.material.typography :refer [typography]] + [reagent-mui.icons.design-services :as ds-icon] + [reagent-mui.icons.send :as send-icon] + [reagent.core :as r])) + +(defn chat-input-field + "The chat input field. The `on-submit` callback is called when the user + presses the enter key without pressing the shift key. + The same event is also dispatched in the `chat-controls` function, if + the user clicks the send button." + [on-submit] + (let [chat-input-sub (rf/subscribe [::subs/chat-input-value]) + on-change #(rf/dispatch [::events/set-chat-input-value (.. % -target -value)])] + (fn [] + [:textarea {:class "py-1 h-10 w-[calc(100%_-_195px)] block self-end" + :style {:resize "none"} + :value @chat-input-sub + :on-change on-change + :on-key-press (fn [e] + (when (and (= (.-charCode e) 13) (not (.-shiftKey e))) + (on-submit))) + :id "chat-input"}]))) + +(defn chat-controls + "Aggregates the chat input field and the send button as well as the + prompt layout button." + [] + (let [llm-config (rf/subscribe [::model-params-subs/model-config]) + submit-prompt (rf/subscribe [::subs/prompt]) + on-submit-event [::events/on-send-button-click @submit-prompt @llm-config]] + (fn chat-controls [] + [:form {:class "flex mr-2.5 mt-2"} + [chat-input-field #(rf/dispatch on-submit-event)] + [:div {:class "ml-1.5 mr-0.5"} + [tooltip {:title "Edit prompt layout"} + [icon-button {:on-click #(rf/dispatch [::events/toggle-modal]) + :color "primary" + :size "medium"} + [ds-icon/design-services]]] + [tooltip {:title "Clear chat history"} + [icon-button {:on-click #(do (rf/dispatch [::events/clear-chat-history]) + (rf/dispatch [::persistence/clear-chat-history])) + :size "medium" + :color "primary"} + [delete-icon/delete-forever]]] + [button {:on-click #(rf/dispatch on-submit-event) + :variant "outlined" + :end-icon (r/as-element [send-icon/send]) + :style {:width "96px" + :margin-left "8px"} + :color "primary"} + "Send"]]]))) + +(defn user->bubble-style + "Produces additional style attributes for a chatbubble contingent upon + the provided user. + This can be done a lot smarter, but it works for now." + [user] + (str "p-2 rounded-xl border " (if (= user :model) + "bg-gray-50 mr-10 rounded-bl-none border-gray-200" + "bg-gray-300 ml-10 rounded-br-none border-gray-400"))) + +(defn user->text-style + "Produces additional style attributes forthe text of a text message + upon the provided user. + This can be done a lot smarter, but it works for now." + [user] + (str "whitespace-pre-wrap " (if (= user :model) "text-gray-700" "text-gray-950"))) + +(defn chat-message-entry + "Displays a single chat message of the chat history. + Will be used as a mapping function in the `chat-history` function. The collection + being mapped is the entire chat history." + [{:keys [user text]}] + (let [display-user (if (= user :model) "System" "You") + alignment (if (= user :model) "flex-row" "flex-row-reverse")] + [:div {:class (str "flex " alignment " items-end my-2 w-full")} + [:h3 {:class "font-bold text-lg mx-2"} display-user] + [:div {:class (user->bubble-style user)} + [:p {:class (user->text-style user)} + text]]])) + +(defn chat-history + "The chat history. Transforms the chat history into DOM/hiccup elements by + mapping the `chat-message-entry` function over the chat history." + [] + (let [history (rf/subscribe [::subs/chat-history])] + (fn chat-history [] + (into [:div {:class "px-8 flex flex-col items-center"}] + (map chat-message-entry @history))))) + +(defn prompt-layout-modal + "The modal for editing the prompt layout. The modal is opened by the + `toggle-modal` event and closed by the `toggle-modal` event. The modal + is closed by clicking the save button or somewhere outside of the modal." + [] + (let [modal-open? (rf/subscribe [::subs/modal-open?]) + prompt-layout-value (rf/subscribe [::subs/prompt-layout]) + on-change #(rf/dispatch [::events/set-prompt-layout (.. % -target -value)])] + (fn [] + [modal {:open @modal-open? + :on-close #(rf/dispatch [::events/toggle-modal])} + [box {:style {:position "absolute" + :width 800, + :top "50%" + :left "50%" + :transform "translate(-50%, -50%)"}} + [paper {:elevation 24 + :style {:padding "20px 30px"}} + [typography {:variant "h5"} "Prompt Layout"] + [:textarea {:class "pt-3 mt-1 w-full h-64 block border bg-gray-200" + :value @prompt-layout-value + :on-change on-change}] + [:div {:class "mt-4 flex justify-end space-x-2"} + [api-components/file-upload-button {:callback-event ::events/set-prompt-layout}] + [button {:type "button" + :variant "outlined" + :on-click #(rf/dispatch [::events/toggle-modal])} "Save"]]]]]))) + +(defn chat-tab-contents + "The component rendered if the chat tab is active. It contains the chat + history, the chat input field and the chat controls." + [] + [:<> + [prompt-layout-modal] + [paper {:class "mr-3.5 mt-6 h-[calc(100%_-_78px)]" + :square true} + [:div {:id "chat-history-container" + :class "overflow-y-scroll w-full h-full no-scrollbar" + :style {:scrollBehavior "smooth"}} + [chat-history]]] + [chat-controls]]) diff --git a/contrib/clojure/src/main/openllm/components/common/views.cljs b/contrib/clojure/src/main/openllm/components/common/views.cljs new file mode 100644 index 00000000..f1f2719a --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/common/views.cljs @@ -0,0 +1,12 @@ +(ns openllm.components.common.views + "This namespace contains common components for other components. This + excludes API wrapping, which we will consider as another level of + abstaction; these components are located in the `openllm.api.components`.") + +(defn headline + "The headlines in bold font and all caps found all over the application." + [text padding-x] + (let [padding-x (or padding-x 4)] + [:h3 {:class (str "px-" padding-x " text-xs font-semibold text-gray-500 uppercase tracking-wider") + :id (str "headline-" text)} + text])) diff --git a/contrib/clojure/src/main/openllm/components/db.cljs b/contrib/clojure/src/main/openllm/components/db.cljs new file mode 100644 index 00000000..ad9e56a4 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/db.cljs @@ -0,0 +1,31 @@ +(ns openllm.components.db + "The branch of the app-db that saves data related to the chat view. Components + are an abstract concept, this namespace is used to group all components' db + branches; I do not think that there will be any actual fields in this namespace. + The path to this branch can be expressed as: + *root -> components*" + (:require [openllm.components.chat.db :as chat-db] + [openllm.components.nav-bar.db :as nav-bar-db] + [openllm.components.playground.db :as playground-db] + [openllm.components.side-bar.db :as side-bar-db] + [clojure.spec.alpha :as s])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Spec ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(s/def ::components-db (s/keys :req-un [::chat-db/chat-db + ::nav-bar-db/nav-bar-db + ::playground-db/playground-db + ::side-bar-db/side-bar-db])) + +(defn initial-db + "Initial values for this branch of the app-db." + [] + {:chat-db (chat-db/initial-db) + :nav-bar-db (nav-bar-db/initial-db) + :playground-db (playground-db/initial-db) + :side-bar-db (side-bar-db/initial-db)}) + +(comment + ;; check if initial-db is valid + (s/valid? ::components-db (initial-db))) diff --git a/contrib/clojure/src/main/openllm/components/nav_bar/db.cljs b/contrib/clojure/src/main/openllm/components/nav_bar/db.cljs new file mode 100644 index 00000000..319c7738 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/nav_bar/db.cljs @@ -0,0 +1,36 @@ +(ns openllm.components.nav-bar.db + "The branch of the `app-db` that saves data related to the `nav-bar`. This + should have very few keys, since the `nav-bar` mostly affects other components. + The path to this branch can be expressed as: + *root -> components -> nav-bar*" + (:require [cljs.spec.alpha :as s])) + +(defn key-seq + "Returns the key sequence to access the nav-bar-db This is useful for + `assoc-in` and `get-in`. The `more-keys` argument is optional and can be + used to access a sub-key of the nav-bar-db + Returns the key sequence to access the nav-bar-db" + [& more-keys] + (into [:components-db :nav-bar-db] more-keys)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Spec ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(s/def ::nav-bar-db #(and (map? %) (empty? %))) + +(defn initial-db + "Initial values for this branch of the app-db." + [] + {}) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Rich Comments ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(comment + ;; check if initial-db is valid + (s/valid? ::nav-bar-db (initial-db)) + + ;; check if the map is allowed to have keys + (s/valid? ::nav-bar-db {:foo "bar"})) diff --git a/contrib/clojure/src/main/openllm/components/nav_bar/events.cljs b/contrib/clojure/src/main/openllm/components/nav_bar/events.cljs new file mode 100644 index 00000000..2aa51876 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/nav_bar/events.cljs @@ -0,0 +1,47 @@ +(ns openllm.components.nav-bar.events + (:require [openllm.util :as util] + [openllm.api.log4cljs.core :refer [log]] + [re-frame.core :refer [reg-event-fx]])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn- start-download! + "Starts the download of a file." + [file-name content] + (let [blob (js/Blob. #js [content] #js {:type "text/plain"}) + link (js/document.createElement "a")] + (set! (.-href link) (js/window.URL.createObjectURL blob)) + (set! (.-download link) file-name) + (set! (.-target link) "_blank") + (.click link))) + +(defn- build-playground-export-contents + "Returns the contents of the playground export file." + [prompt response] + (str "Prompt: " prompt + "\n\n\n" + "Response: " response)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Events ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(reg-event-fx + ::export-button-clicked + (fn [cofx _] + {:dispatch-sync true + :fx (let [active-screen (get-in cofx [:db :screen-id])] + (condp = active-screen + :playground + (let [input-value (get-in cofx [:db :playground-input-value]) + response-value (get-in cofx [:db :playground-last-response])] + (start-download! "export-playground.txt" + (build-playground-export-contents input-value + response-value))) + :chat + (let [chat-history (get-in cofx [:db :chat-history])] + (start-download! "export-chat.txt" + (util/chat-history->string chat-history))) + ;default + (log :error "Export button clicked in unknown screen.")))})) diff --git a/contrib/clojure/src/main/openllm/components/nav_bar/subs.cljs b/contrib/clojure/src/main/openllm/components/nav_bar/subs.cljs new file mode 100644 index 00000000..6235a2cd --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/nav_bar/subs.cljs @@ -0,0 +1,15 @@ +(ns openllm.components.nav-bar.subs + (:require [openllm.components.chat.subs :as chat-subs] + [re-frame.core :refer [reg-sub]])) + +(reg-sub + ::chat-history-empty? + :<- [::chat-subs/chat-history] + (fn [chat-history _] + (empty? chat-history))) + +(reg-sub + ::tooltip-text-export + :<- [:screen-id] + (fn [screen-id _] + (str "Export " (if (= screen-id :playground) "playground data" "chat history")))) diff --git a/contrib/clojure/src/main/openllm/components/nav_bar/views.cljs b/contrib/clojure/src/main/openllm/components/nav_bar/views.cljs new file mode 100644 index 00000000..6947b57f --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/nav_bar/views.cljs @@ -0,0 +1,87 @@ +(ns openllm.components.nav-bar.views + (:require [re-frame.core :as rf] + [openllm.events :as root-events] + [openllm.components.nav-bar.subs :as subs] + [openllm.components.nav-bar.events :as events] + [openllm.components.side-bar.events :as side-bar-events] + [openllm.components.chat.events :as chat-events] + [openllm.api.persistence :as persistence] + [reagent-mui.material.app-bar :refer [app-bar]] + [reagent-mui.material.toolbar :refer [toolbar]] + [reagent-mui.material.icon-button :refer [icon-button]] + [reagent-mui.material.tooltip :refer [tooltip]] + [reagent-mui.material.svg-icon :refer [svg-icon]] + [reagent-mui.icons.delete-forever :as delete-icon] + [reagent-mui.icons.ios-share :as share-icon] + [reagent-mui.icons.git-hub :as github-icon] + [reagent-mui.material.tabs :refer [tabs]] + [reagent-mui.material.tab :refer [tab]])) + +(def discord-icon-d "M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z") + +(def github-url "https://github.com/bentoml/OpenLLM") + +(def discord-url "https://l.bentoml.com/join-openllm-discord") + +(defn- context-icon-buttons + "Displays the icon buttons on the very right of the navigation bar. Some + of them are only displayed conditionally (e.g. if a certain screen is + active)." + [] + (let [active-screen (rf/subscribe [:screen-id]) + chat-history-empty? (rf/subscribe [::subs/chat-history-empty?]) + tooltip-text-export (rf/subscribe [::subs/tooltip-text-export])] + (fn [] + [:<> + [tooltip {:title @tooltip-text-export} + [icon-button {:on-click #(rf/dispatch [::events/export-button-clicked]) + :size "large" + :color "inherit"} + [share-icon/ios-share]]] + (when (and (= @active-screen :chat) (not @chat-history-empty?)) + [tooltip {:title "Clear chat history"} + [icon-button {:on-click #(do (rf/dispatch [::chat-events/clear-chat-history]) + (rf/dispatch [::persistence/clear-chat-history])) + :size "large" + :color "inherit"} + [delete-icon/delete-forever]]])]))) + +(defn nav-bar + "Renders the navigation bar. The navigation bar is always visible and contains the + navigation buttons and the context dependent icon buttons. There are also small + buttons that will open socials in a new tab." + [] + [:div {:class "w-full static"} + [app-bar {:position "static" + :color "primary"} + [toolbar {:variant "dense" + :style {:height "48px"}} + [:div + (let [screen-id @(rf/subscribe [:screen-id])] + [tabs {:value (if (= :chat screen-id) 1 0) + :text-color "inherit"} + [tab {:label "Playground" + :id "playground-tab" + :on-click #(rf/dispatch-sync [:set-screen-id :playground])}] + [tab {:label "Chat" + :id "chat-tab" + :on-click #(do (rf/dispatch-sync [:set-screen-id :chat]) + (rf/dispatch [::chat-events/auto-scroll]))}]])] + [:div {:class "w-full flex justify-end items-center"} + ;;[:div {:class "mr-8"} + ;; [context-icon-buttons]] + [tooltip {:title github-url} + [icon-button {:on-click #(rf/dispatch [::root-events/open-link-in-new-tab github-url]) + :color "secondary" + :size "small"} + [github-icon/git-hub]]] + [tooltip {:title discord-url} + [icon-button {:on-click #(rf/dispatch [::root-events/open-link-in-new-tab discord-url]) + :color "primary" + :size "small"} + [svg-icon + [:circle {:cx 12 + :cy 12 + :r 12 + :fill "#fff"}] + [:path {:d discord-icon-d}]]]]]]]]) diff --git a/contrib/clojure/src/main/openllm/components/playground/db.cljs b/contrib/clojure/src/main/openllm/components/playground/db.cljs new file mode 100644 index 00000000..c226b317 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/playground/db.cljs @@ -0,0 +1,41 @@ +(ns openllm.components.playground.db + "The branch of the `app-db` that saves data related to the playground view. + This includes the input box, the response box and the response modal. + The path to this branch can be expressed as: + *root -> components -> playground*" + (:require [cljs.spec.alpha :as s])) + +(defn key-seq + "Returns the key sequence to access the playground-db This is useful for + `assoc-in` and `get-in`. The `more-keys` argument is optional and can be + used to access a sub-key of the playground-db + Returns the key sequence to access the playground-db" + [& more-keys] + (into [:components-db :playground-db] more-keys)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Spec ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(s/def ::playground-input-value string?) ;; string the user entered into the input box +(s/def ::playground-last-response string?) ;; the last response from the server +(s/def ::response-modal-open? boolean?) ;; whether the response modal is open + +(s/def ::playground-db (s/keys :req-un [::playground-input-value ;; the spec for the playground-db + ::playground-last-response + ::response-modal-open?])) + +(defn initial-db + "Initial values for this branch of the app-db." + [] + {:playground-input-value "" + :playground-last-response "" + :response-modal-open? false}) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Rich Comments ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(comment + ;; check if initial-db is valid + (s/valid? ::playground-db (initial-db))) diff --git a/contrib/clojure/src/main/openllm/components/playground/events.cljs b/contrib/clojure/src/main/openllm/components/playground/events.cljs new file mode 100644 index 00000000..d8a49082 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/playground/events.cljs @@ -0,0 +1,49 @@ +(ns openllm.components.playground.events + (:require [openllm.components.playground.db :as db] + [openllm.events :refer [check-spec-interceptor]] + [re-frame.core :as rf :refer [reg-event-db reg-event-fx]] + [openllm.api.http :as api] + [openllm.api.log4cljs.core :refer [log]])) + +(reg-event-db + ::set-prompt-input + [check-spec-interceptor] + (fn [db [_ value]] + (assoc-in db (db/key-seq :playground-input-value) value))) + +(reg-event-fx + ::send-prompt-success + [check-spec-interceptor] + (fn [db [_ response]] + {:db (assoc-in db (db/key-seq :playground-last-response) (first (:responses response))) + :dispatch [::toggle-modal]})) + +(reg-event-fx + ::send-prompt-failure + [check-spec-interceptor] + (fn [{:keys [db]} [_ e]] + (log :error "Failed to send prompt" e) + {:db (assoc-in db (db/key-seq :playground-last-response) "Sorry, something went wrong.") + :dispatch [::toggle-modal]})) + +(reg-event-fx + ::on-send-button-click + [check-spec-interceptor] + (fn [_ [_ prompt llm-config]] + {:dispatch [::api/v1-generate prompt llm-config {:on-success [::send-prompt-success] + :on-failure [::send-prompt-failure]}]})) + +(reg-event-db + ::toggle-modal + [check-spec-interceptor] + (fn [db _] + (let [new-value (not (get-in db (db/key-seq :response-modal-open?)))] + (assoc-in db (db/key-seq :response-modal-open?) new-value)))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Rich Comments ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (comment + ;; clear input field + (rf/dispatch [::set-prompt-input ""])) diff --git a/contrib/clojure/src/main/openllm/components/playground/subs.cljs b/contrib/clojure/src/main/openllm/components/playground/subs.cljs new file mode 100644 index 00000000..c8d686bb --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/playground/subs.cljs @@ -0,0 +1,21 @@ +(ns openllm.components.playground.subs + (:require [openllm.components.subs :as components-subs] + [re-frame.core :refer [reg-sub]])) + +(reg-sub + ::playground-input-value + :<- [::components-subs/playground-db] + (fn [playground-db _] + (:playground-input-value playground-db))) + +(reg-sub + ::last-response + :<- [::components-subs/playground-db] + (fn [playground-db _] + (:playground-last-response playground-db))) + +(reg-sub + ::response-modal-open? + :<- [::components-subs/playground-db] + (fn [playground-db _] + (:response-modal-open? playground-db))) diff --git a/contrib/clojure/src/main/openllm/components/playground/views.cljs b/contrib/clojure/src/main/openllm/components/playground/views.cljs new file mode 100644 index 00000000..6e30fb6d --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/playground/views.cljs @@ -0,0 +1,105 @@ +(ns openllm.components.playground.views + (:require [openllm.components.playground.events :as events] + [openllm.components.playground.subs :as subs] + [openllm.api.components :as api-components] + [openllm.components.common.views :as ui] + [openllm.components.side-bar.model-params.subs :as model-params-subs] + [re-frame.core :as rf] + [reagent-mui.material.button :refer [button]] + [reagent-mui.material.modal :refer [modal]] + [reagent-mui.material.box :refer [box]] + [reagent-mui.material.paper :refer [paper]] + [reagent-mui.material.typography :refer [typography]] + [reagent-mui.material.tooltip :refer [tooltip]] + [reagent-mui.icons.send :as send-icon] + [reagent.core :as r])) + +(defn input-field + "The input field for the prompt to send to the backend." + [] + (let [value (rf/subscribe [::subs/playground-input-value])] + (fn [] + [:textarea {:class "pt-3 w-full h-[calc(100%_-_64px)] block border resize-none" + :value @value + :on-change #(rf/dispatch [::events/set-prompt-input (.. % -target -value)])}]))) + +(defn- upload-button + "The little button to upload a file to be used as prompt." + [] + [tooltip {:title "Upload file to use as prompt"} + [:div + [api-components/file-upload-button {:callback-event ::events/set-prompt-input}]]]) + +(defn input-field-controls + "Control buttons for the input field, where the user enters his/her + prompt." + [] + (let [input-value (rf/subscribe [::subs/playground-input-value]) + llm-config (rf/subscribe [::model-params-subs/model-config])] + (fn [] + [:div {:class "mt-2.5 flex justify-end space-x-2 h-9"} + [upload-button] + [button {:type "button" + :variant "outlined" + :on-click #(rf/dispatch [::events/set-prompt-input ""])} "Clear"] + [button {:type "button" + :variant "outlined" + :end-icon (r/as-element [send-icon/send]) + :style {:width "96px"} + :on-click #(rf/dispatch [::events/on-send-button-click @input-value @llm-config])} "Send"]]))) + +(defn response-area + "The latest response retrieved from the backend will be displayed in this + component. + By default this is not visible, but it will be shown once the user has + resized the input field. After that, resizing the browser window will no + longer scale the input field." + [] + (let [last-response (rf/subscribe [::subs/last-response])] + (fn [] + [:div + [ui/headline "Response" 0] + [:textarea {:class "pt-3 mt-1 w-full h-64 block border bg-gray-200" + :value @last-response + :disabled true}]]))) + +(defn- response-modal + "The modal that is shown to display server response in the playground view. + It is opened and closed by the `::toggle-modal` event. The modal is closed + by clicking the close button or somewhere outside of the modal." + [] + (let [modal-open? (rf/subscribe [::subs/response-modal-open?]) + last-response (rf/subscribe [::subs/last-response])] + (fn [] + [modal + {:open @modal-open? + :on-close #(rf/dispatch [::events/toggle-modal]) + :aria-labelledby "response-modal-title" + :aria-describedby "response-modal-description" + :actions [{:label "Close" + :on-click #(rf/dispatch [::events/toggle-modal])}]} + [box {:style {:position "absolute" + :width 800, + :top "50%" + :left "50%" + :transform "translate(-50%, -50%)"}} + [paper {:elevation 24 + :style {:padding "20px 30px"}} + [typography {:id "response-modal-title" + :variant "h6"} + "Response"] + [typography {:id "response-modal-description" + :variant "body1"} + @last-response]]]]))) + +(defn playground-tab-contents + "This function aggregates all contents of the playground tab, and is + called by the `tab-content` function residing in the `views` namespace + directly." + [] + [:div {:class "mt-6 pb-3.5 pr-3 h-full"} + [response-modal] + [input-field] + [input-field-controls] + [:div {:class "mt-6"} + [response-area]]]) diff --git a/contrib/clojure/src/main/openllm/components/side_bar/db.cljs b/contrib/clojure/src/main/openllm/components/side_bar/db.cljs new file mode 100644 index 00000000..d293efb3 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/side_bar/db.cljs @@ -0,0 +1,41 @@ +(ns openllm.components.side-bar.db + "The branch of the `app-db` that saves data related to the `side-bar` view. This + mostly revolves around the model parameters. + The path to this branch can be expressed as: + *root -> components -> side-bar*" + (:require [openllm.components.side-bar.model-selection.db :as model-selection-db] + [openllm.components.side-bar.model-params.db :as model-params-db] + [cljs.spec.alpha :as s])) + +(defn key-seq + "Returns the key sequence to access the side-bar-db This is useful for + `assoc-in` and `get-in`. The `more-keys` argument is optional and can be + used to access a sub-key of the side-bar-db + Returns the key sequence to access the side-bar-db" + [& more-keys] + (into [:components-db :side-bar-db] more-keys)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Spec ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(s/def ::side-bar-open? boolean?) + +(s/def ::side-bar-db (s/keys :req-un [::side-bar-open? + ::model-selection-db/model-selection-db + ::model-params-db/model-params-db])) + +(defn initial-db + "Initial values for this branch of the app-db." + [] + {:side-bar-open? true + :model-selection-db (model-selection-db/initial-db) + :model-params-db (model-params-db/initial-db)}) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Rich Comments ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(comment + ;; check if initial-db is valid + (s/valid? ::side-bar-db (initial-db))) diff --git a/contrib/clojure/src/main/openllm/components/side_bar/events.cljs b/contrib/clojure/src/main/openllm/components/side_bar/events.cljs new file mode 100644 index 00000000..f632d207 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/side_bar/events.cljs @@ -0,0 +1,11 @@ +(ns openllm.components.side-bar.events + (:require [openllm.components.side-bar.db :as db] + [re-frame.core :refer [reg-event-db]] + [openllm.events :refer [check-spec-interceptor]])) + +(reg-event-db + ::toggle-side-bar + [check-spec-interceptor] + (fn [db _] + (let [new-value (not (get-in db (db/key-seq :side-bar-open?)))] + (assoc-in db (db/key-seq :side-bar-open?) new-value)))) diff --git a/contrib/clojure/src/main/openllm/components/side_bar/model_params/db.cljs b/contrib/clojure/src/main/openllm/components/side_bar/model_params/db.cljs new file mode 100644 index 00000000..3b1f7545 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/side_bar/model_params/db.cljs @@ -0,0 +1,108 @@ +(ns openllm.components.side-bar.model-params.db + "The branch of the `app-db` that saves data related to the model-params-db view. + This includes all the configuration parameters for the models. + The path to this branch can be expressed as: + *root -> components -> side-bar -> model-params*" + (:require [clojure.spec.alpha :as s])) + +(defn key-seq + "Returns the key sequence to access the model-params-db This is useful for + `assoc-in` and `get-in`. The `more-keys` argument is optional and can be + used to access a sub-key of the model-params-db + Returns the key sequence to access the model-params-db" + [& more-keys] + (into [:components-db :side-bar-db :model-params-db] more-keys)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Spec ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(def parameter-meta-data + "A map with parameter id's as keys and some metadata for easier rendering." + {:temperature {:display-type :slider :type-pred float? :advanced-opt false :val-constraint [0.0 1.0]} + :top_k {:display-type :slider :type-pred int? :advanced-opt false :val-constraint [0 100]} + :top_p {:display-type :slider :type-pred float? :advanced-opt false :val-constraint [0.1 1.0]} + :typical_p {:display-type :slider :type-pred float? :advanced-opt false :val-constraint [0.1 1.0]} + :epsilon_cutoff {:display-type :slider :type-pred float? :advanced-opt true :val-constraint [0.0 1.0]} + :eta_cutoff {:display-type :slider :type-pred float? :advanced-opt true :val-constraint [0.0 1.0]} + :diversity_penalty {:display-type :slider :type-pred float? :advanced-opt true :val-constraint [0.0 5.0]} + :repetition_penalty {:display-type :slider :type-pred float? :advanced-opt true :val-constraint [0.0 5.0]} + :encoder_repetition_penalty {:display-type :slider :type-pred float? :advanced-opt true :val-constraint [0.0 5.0]} + :length_penalty {:display-type :slider :type-pred float? :advanced-opt true :val-constraint [0.0 5.0]} + :num_beams {:display-type :field :type-pred int? :advanced-opt true :val-constraint [0 10]} + :penalty_alpha {:display-type :slider :type-pred float? :advanced-opt true :val-constraint [0.0 10.0]} + :max_new_tokens {:display-type :field :type-pred int? :advanced-opt true :val-constraint [0 ##Inf]} + :min_length {:display-type :field :type-pred int? :advanced-opt true :val-constraint [0 ##Inf]} + :min_new_tokens {:display-type :field :type-pred int? :advanced-opt true :val-constraint [0 ##Inf]} + :early_stopping {:display-type :binary :type-pred boolean? :advanced-opt true :val-constraint [true false]} + :max_time {:display-type :field :type-pred float? :advanced-opt true :val-constraint [0.0 ##Inf]} + :num_beam_groups {:display-type :field :type-pred int? :advanced-opt true :val-constraint [0 ##Inf]} + :use_cache {:display-type :binary :type-pred boolean? :advanced-opt true :val-constraint [true false]}}) + +(defn get-validate-range-predicate + "Returns a predicate that checks if the value is within the range of the + parameter. The parameter is specified by the `type-predicate` argument. + The predicate is a function that takes a value and returns true if the value + is within the range of the parameter and false otherwise." + [keyword type-predicate] + (let [param (get-in parameter-meta-data [keyword :val-constraint])] + (s/and type-predicate + #(<= (first param) % (second param))))) + +(s/def ::temperature (get-validate-range-predicate :temperature float?)) +(s/def ::top_k (get-validate-range-predicate :top_k int?)) +(s/def ::top_p (get-validate-range-predicate :top_p float?)) +(s/def ::typical_p (get-validate-range-predicate :typical_p float?)) +(s/def ::epsilon_cutoff (get-validate-range-predicate :epsilon_cutoff float?)) +(s/def ::eta_cutoff (get-validate-range-predicate :eta_cutoff float?)) +(s/def ::diversity_penalty (get-validate-range-predicate :diversity_penalty float?)) +(s/def ::repetition_penalty (get-validate-range-predicate :repetition_penalty float?)) +(s/def ::encoder_repetition_penalty (get-validate-range-predicate :encoder_repetition_penalty float?)) +(s/def ::length_penalty (get-validate-range-predicate :length_penalty float?)) +(s/def ::num_beams (get-validate-range-predicate :num_beams int?)) +(s/def ::penalty_alpha (get-validate-range-predicate :penalty_alpha float?)) +(s/def ::max_new_tokens int?) +(s/def ::min_length int?) +(s/def ::min_new_tokens int?) +(s/def ::early_stopping boolean?) +(s/def ::max_time float?) +(s/def ::num_beam_groups int?) +(s/def ::use_cache boolean?) +(s/def ::model-params-db (s/keys :req-un [::temperature ::top_k ::top_p ::typical_p + ::epsilon_cutoff ::eta_cutoff ::diversity_penalty + ::repetition_penalty ::encoder_repetition_penalty + ::length_penalty ::max_new_tokens ::min_length + ::min_new_tokens ::early_stopping ::max_time + ::num_beams ::num_beam_groups ::penalty_alpha + ::use_cache])) + +(defn initial-db + "Very arbitrary. Should be fetched from metadata endpoint eventually." ;; TODO: fetch from metadata endpoint + [] + (array-map :temperature 0.9 + :top_k 50 + :top_p 0.4 + :typical_p 1.0 + :epsilon_cutoff 0.0 + :eta_cutoff 0.0 + :diversity_penalty 0.0 + :repetition_penalty 1.0 + :encoder_repetition_penalty 1.0 + :length_penalty 1.0 + :max_new_tokens 2048 + :min_length 0 + :min_new_tokens 0 + :early_stopping false + :max_time 0.0 + :num_beams 1 + :num_beam_groups 1 + :penalty_alpha 0.0 + :use_cache true)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Rich Comments ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(comment + ;; check if initial-db is valid + (s/valid? ::model-params-db (initial-db))) diff --git a/contrib/clojure/src/main/openllm/components/side_bar/model_params/events.cljs b/contrib/clojure/src/main/openllm/components/side_bar/model_params/events.cljs new file mode 100644 index 00000000..6e133424 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/side_bar/model_params/events.cljs @@ -0,0 +1,16 @@ +(ns openllm.components.side-bar.model-params.events + (:require [openllm.components.side-bar.model-params.db :as db] + [re-frame.core :refer [reg-event-db]] + [openllm.events :refer [check-spec-interceptor]])) + +(reg-event-db + ::set-model-config-parameter + [check-spec-interceptor] + (fn [db [_ parameter value]] + (let [type-pred (get-in db/parameter-meta-data [parameter :type-pred]) + parsed-value (condp = type-pred ;; this can probably be rewritten smarter... TODO i guess... + boolean? (if (boolean? value) value (parse-boolean value)) + int? (if (int? value) value (parse-long value)) + float? (if (float? value) value (parse-double value)) + value)] ;; best effort probably was not enough ;_; + (assoc-in db (db/key-seq parameter) parsed-value)))) diff --git a/contrib/clojure/src/main/openllm/components/side_bar/model_params/subs.cljs b/contrib/clojure/src/main/openllm/components/side_bar/model_params/subs.cljs new file mode 100644 index 00000000..bca263e5 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/side_bar/model_params/subs.cljs @@ -0,0 +1,40 @@ +(ns openllm.components.side-bar.model-params.subs + (:require [openllm.components.side-bar.subs :as side-bar-subs] + [re-frame.core :refer [reg-sub]])) + +(def parameter-id->human-readable + "Maps paramter ids to human readable names." + {:temperature "Temperature" + :top_k "Top K" + :top_p "Top P" + :typical_p "Typical P" + :epsilon_cutoff "Epsilon Cutoff" + :eta_cutoff "Eta Cutoff" + :diversity_penalty "Diversity Penalty" + :repetition_penalty "Repetition Penalty" + :encoder_repetition_penalty "Encoder Repetition Penalty" + :length_penalty "Length Penalty" + :max_new_tokens "Maximum New Tokens" + :min_length "Minimum Length" + :min_new_tokens "Minimum New Tokens" + :early_stopping "Early Stopping" + :max_time "Maximum Time" + :num_beams "Number of Beams" + :num_beam_groups "Number of Beam Groups" + :penalty_alpha "Penalty Alpha" + :use_cache "Use Cache"}) + +(reg-sub + ::model-config + :<- [::side-bar-subs/model-params-db] + (fn [model-params-db _] + model-params-db)) + +(reg-sub + ::human-readable-config + :<- [::model-config] + (fn [model-config _] + (vec (map (fn [[k v]] + [k {:name k ;;(parameter-id->human-readable k) + :value v}]) + model-config)))) diff --git a/contrib/clojure/src/main/openllm/components/side_bar/model_params/views.cljs b/contrib/clojure/src/main/openllm/components/side_bar/model_params/views.cljs new file mode 100644 index 00000000..dac27344 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/side_bar/model_params/views.cljs @@ -0,0 +1,104 @@ +(ns openllm.components.side-bar.model-params.views + (:require [openllm.components.side-bar.model-params.db :as db] + [openllm.components.side-bar.model-params.subs :as subs] + [openllm.components.side-bar.model-params.events :as events] + [reagent-mui.material.input :refer [input]] + [reagent-mui.material.accordion :refer [accordion]] + [reagent-mui.material.accordion-details :refer [accordion-details]] + [reagent-mui.material.accordion-summary :refer [accordion-summary]] + [reagent-mui.material.typography :refer [typography]] + [reagent-mui.icons.expand-more :refer [expand-more]] + [clojure.string :as str] + [re-frame.core :as rf] + [reagent.core :as r])) + +(defn parameter-slider + "Renders a slider with an input field next to it. The num-type logic needs to be + revamped big time xx" + [id value] + (let [min-max (get-in db/parameter-meta-data [id :val-constraint]) + num-type? (= int? (get-in db/parameter-meta-data [id :type-pred])) + on-change #(rf/dispatch [::events/set-model-config-parameter id (.. % -target -value)])] + [:div {:class "flex flex-row items-center w-full"} + [:input {:type "range" + :min (first min-max) + :max (second min-max) + :step (if num-type? 1 0.01) + :value value + :class "w-full mt-2 mb-1" + :on-change on-change}]])) + +(defn parameter-small-input + "Renders a small input field, used in combination with the sliders. The num-type logic + needs to be revamped big time xx" + [id value] + (let [on-change #(rf/dispatch [::events/set-model-config-parameter id (.. % -target -value)]) + num-type? (= int? (get-in db/parameter-meta-data [id :type-pred]))] + [input {:type "number" + :class "w-10 text-center border" + :input-props {:style {:text-align "center" + :background-color "#ffffff" + :height "12px"}} + :step (if num-type? 1 0.01) + :value value + :on-change on-change}])) + +(defn parameter-checkbox + "Renders a checkbox." + [id value] + [:input {:type "checkbox" + :class "ml-6 mt-1" + :checked value + :on-change #(rf/dispatch [::events/set-model-config-parameter id (not value)])}]) + +(defn parameter-number + "Renders a number input field." + [id value] + [input {:type "number" + :class "w-16 border" + :value value + :input-props {:style {:padding "2px" + :background-color "#ffffff" + :height "24px"}} + :on-change #(rf/dispatch [::events/set-model-config-parameter id (parse-long (.. % -target -value))])}]) + +(defn parameter-list-entry + "Renders a single parameter in the sidebar's parameter list. Used as a mapping function + on the collection of all parameters." + [[id {:keys [value name]}]] + (let [display-type (get-in db/parameter-meta-data [id :display-type])] + [:<> + [:div {:class "flex flex-col px-2 pt-1"} + [:label {:class "flex w-full text-xs justify-between"} + [:code {:class "self-center"} name] + (condp = display-type + :slider + [parameter-small-input id value] + :binary + [parameter-checkbox id value] + :field + [parameter-number id value])] + (when (= :slider display-type) + [:div {:class "mb-0.5"} + [parameter-slider id value]])] + [:hr {:class "mt-1 border-1 border-gray-100 last:border-0 last:mt-0 last:-mb-1.5"}]])) + +(defn parameter-list + "Renders the parameters in the sidebar. The parameters are retrieved from the + `human-readable-config` subscription." + [] + (let [model-config (rf/subscribe [::subs/human-readable-config])] + (fn [] + (let [basic-params (filterv (fn [[id _]] (not (get-in db/parameter-meta-data [id :advanced-opt]))) @model-config) ;; TODO: views shouldn't make such heave calculations + advanced-params (filterv (fn [[id _]] (get-in db/parameter-meta-data [id :advanced-opt])) @model-config)] + [:<> + (into [:<>] (map parameter-list-entry basic-params)) + [:div {:class "mt-2 -mx-1.75"} + [accordion {:square true + :class "w-full" + :elevation 0 + :style {:background-color "#fafafa"}} + [accordion-summary {:expand-icon (r/as-element [expand-more])} + [typography "Advanced"]] + [accordion-details {:class "-mt-1.5 -mx-3"} + (into [:<>] (map parameter-list-entry advanced-params))]]]])))) diff --git a/contrib/clojure/src/main/openllm/components/side_bar/model_selection/db.cljs b/contrib/clojure/src/main/openllm/components/side_bar/model_selection/db.cljs new file mode 100644 index 00000000..6502f0e2 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/side_bar/model_selection/db.cljs @@ -0,0 +1,66 @@ +(ns openllm.components.side-bar.model-selection.db + "The branch of the `app-db` that saves data related to the model-selection view. + This includes the current model selection, as well as the data for all available + models. + The path to this branch can be expressed as: + *root -> components -> side-bar -> model-selection*" + (:require [re-frame.core :as rf] + [clojure.spec.alpha :as s])) + +(defn key-seq + "Returns the key sequence to access the model-selection-db This is useful for + `assoc-in` and `get-in`. The `more-keys` argument is optional and can be + used to access a sub-key of the model-selection-db + Returns the key sequence to access the model-selection-db" + [& more-keys] + (into [:components-db :side-bar-db :model-selection-db] more-keys)) + +(def loading-text "Loading...") + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Spec ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(s/def ::vec-of-runtimes? (s/coll-of + (s/and string? + #(or (= % "pt") + (= % "flax") ;; all available runtimes + (= % "tf"))) + :kind vector?)) + +(s/def ::model_id (s/coll-of string? :kind vector?)) ;; model_id is a vector of all models for a given model_type +(s/def ::url string?) ;; url to the model's page +(s/def ::requires_gpu boolean?) ;; whether the model requires a gpu +(s/def ::runtime_impl ::vec-of-runtimes?) ;; supported runtimes +(s/def ::installation string?) ;; installation instructions (pip command) +(s/def ::model-spec (s/keys :req-un [::model_id ::url ::requires_gpu ;; the spec for a single model (aggregates all the above) + ::runtime_impl ::installation])) +(s/def ::all-models #(or loading-text ;; -- this is the case when the file with the model data has not been loaded yet by the ::set-model-data effect + (s/map-of keyword? ::model-spec))) ;; map of all models + +(s/def ::selected-model (s/keys :req-un + [::model-type #(or (keyword? %) ;; currently selected model-id and model-type + (= % loading-text)) ;; -- same as above + ::model-id string?])) + +(s/def ::model-selection-db (s/keys :req-un [::all-models + ::selected-model])) ;; the spec of the model-selection-db + +(defn initial-db + "Initial values for this branch of the app-db. + Triggers the loading of the model data by dispatching the `:slurp-model-data-json` + event." + [] + (rf/dispatch [:slurp-model-data-json]) + (rf/dispatch [:fetch-metadata-endpoint]) + {:all-models loading-text ;; will be overwritten by the event dispatched above + :selected-model {:model-type loading-text + :model-id loading-text}}) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Rich Comments ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(comment + ;; check if initial-db is valid + (s/valid? ::model-selection-db (initial-db))) diff --git a/contrib/clojure/src/main/openllm/components/side_bar/model_selection/events.cljs b/contrib/clojure/src/main/openllm/components/side_bar/model_selection/events.cljs new file mode 100644 index 00000000..af3f3763 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/side_bar/model_selection/events.cljs @@ -0,0 +1,83 @@ +(ns openllm.components.side-bar.model-selection.events + (:require [openllm.components.side-bar.model-selection.db :as db] + [openllm.events :refer [check-spec-interceptor]] + [re-frame.core :refer [reg-event-db reg-event-fx reg-cofx inject-cofx]] + [openllm.api.http :as api] + [openllm.api.log4cljs.core :refer [log]] + [clojure.string :as str]) + (:require-macros [openllm.build :refer [slurp]])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Coeffects ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(reg-cofx + ::model-data-json-parsed + (fn [cofx _] + (assoc cofx + :model-data-json-parsed + (-> "./src/generated/models-data.json" + (slurp ,) ;; look @ `openllm.build/slurp` to see how this sorcery works + (js/JSON.parse ,) + (js->clj , :keywordize-keys true))))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Events ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Sets the `model-type` in the db to the selected model-type. It also sets the +;; `model-id` to the first `model-id` of the selected `model-type`. +;; This event is dispatched by the `model-type` dropdown in the `views.cljs` +;; namespace of this component. +(reg-event-db + ::set-model-type + [check-spec-interceptor] + (fn [db [_ model-type]] + (assoc-in db (db/key-seq :selected-model :model-type) model-type))) + +(reg-event-db + ::set-model-id + [check-spec-interceptor] + (fn [db [_ model-id]] + (assoc-in db (db/key-seq :selected-model :model-id) model-id))) + +;; This event is dispatched by the `initial-db` function in `db.cljs`. It +;; slurps the model data json file and parses it into a clojure map. It then +;; checks if the db already contains data. If it does, it logs a warning and +;; does nothing. If it doesn't, it adds the parsed data to the db. +;; The `models-data.json` file is generated by the `generate-models-data-json` +;; hook in `build.clj` (from JVM Clojure). +(reg-event-fx + :slurp-model-data-json + [check-spec-interceptor (inject-cofx ::model-data-json-parsed)] + (fn [{:keys [db model-data-json-parsed]} _] + {:db (let [all-models (get db (db/key-seq :all-models))] + (if (or (= db/loading-text all-models) (nil? all-models)) + (assoc-in db (db/key-seq :all-models) model-data-json-parsed) + (do (log :warn "Attempted to slurp and parse model data json, but the db already contained data:" all-models) + db)))})) + +(reg-event-fx + ::log-error + [] + (fn [_ [_ error]] + {:fx [(log :error "Error while fetching metadata:" error)]})) + +(reg-event-fx + ::received-metadata + [check-spec-interceptor] + (fn [_ [_ metadata]] + (let [model-type (-> metadata + (:model_name ,) + (str/replace , "_" "-") + (keyword ,)) + model-id (:model_id metadata)] + {:dispatch-n [[::set-model-type model-type] + [::set-model-id model-id]]}))) + +(reg-event-fx + :fetch-metadata-endpoint + [] + (fn [_ _] + {:dispatch [::api/v1-metadata "" + {:on-success [::received-metadata] + :on-failure [::log-error]}]})) diff --git a/contrib/clojure/src/main/openllm/components/side_bar/model_selection/subs.cljs b/contrib/clojure/src/main/openllm/components/side_bar/model_selection/subs.cljs new file mode 100644 index 00000000..caf8410c --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/side_bar/model_selection/subs.cljs @@ -0,0 +1,50 @@ +(ns openllm.components.side-bar.model-selection.subs + (:require [openllm.components.side-bar.model-selection.db :as db] + [openllm.components.side-bar.subs :as side-bar-subs] + [re-frame.core :refer [reg-sub]])) + +(reg-sub + ::selected-model + :<- [::side-bar-subs/model-selection-db] + (fn [model-selection-db _] + (:selected-model model-selection-db))) + +(reg-sub + ::all-models-data + :<- [::side-bar-subs/model-selection-db] + (fn [model-selection-db _] + (:all-models model-selection-db))) + +(reg-sub + ::selected-model-type + :<- [::selected-model] + (fn [selected-model _] + (:model-type selected-model))) + +(reg-sub + ::selected-model-id + :<- [::selected-model] + (fn [selected-model _] + (:model-id selected-model))) + +;; Returns a list of all `model-types`. +(reg-sub + ::all-model-types + :<- [::all-models-data] + (fn [all-models-data _] + (-> all-models-data + (keys ,) + (conj , db/loading-text)))) + +;; Returns a list of all `model-ids` for all `model-types`. +(reg-sub + ::all-model-ids + :<- [::all-models-data] + (fn [all-models-data _] + (conj + (->> all-models-data + (mapv (fn [[_ model-type]] + (:model_id model-type)) ,) + (apply concat ,) + (vec ,)) + db/loading-text))) diff --git a/contrib/clojure/src/main/openllm/components/side_bar/model_selection/views.cljs b/contrib/clojure/src/main/openllm/components/side_bar/model_selection/views.cljs new file mode 100644 index 00000000..444e36b0 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/side_bar/model_selection/views.cljs @@ -0,0 +1,29 @@ +(ns openllm.components.side-bar.model-selection.views + (:require [openllm.components.side-bar.model-selection.subs :as subs] + [openllm.components.side-bar.model-selection.events :as events] + [reagent-mui.material.select :refer [select]] + [re-frame.core :as rf])) + +(defn model-selection + "The dropdowns selecting the model. The `model-type` dropdown is populated + with the available `model-types`, the `model-id` dropdown is populated with + the available `model-ids` for the currently selected `model-type`." + [] + (let [model-type (rf/subscribe [::subs/selected-model-type]) + model-id (rf/subscribe [::subs/selected-model-id]) + all-model-types (rf/subscribe [::subs/all-model-types]) + all-model-ids (rf/subscribe [::subs/all-model-ids])] + (fn [] + [:div {:class "px-5 mb-3 mt-1"} + [:label {:class "text-black"} "Model" + (into [select {:class "w-full h-8 mb-1" + :value @model-type + :read-only true}] + (map #(do [:option {:value %} %]) + @all-model-types))] + [:label {:class "text-black"} "Model ID" + (into [select {:class "w-full h-8" + :value @model-id + :read-only true}] + (map #(do [:option {:value %} (str %)]) + @all-model-ids))]]))) diff --git a/contrib/clojure/src/main/openllm/components/side_bar/subs.cljs b/contrib/clojure/src/main/openllm/components/side_bar/subs.cljs new file mode 100644 index 00000000..88e99596 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/side_bar/subs.cljs @@ -0,0 +1,37 @@ +(ns openllm.components.side-bar.subs + (:require [openllm.components.subs :as components-subs] + [reagent-mui.icons.keyboard-double-arrow-right :as right-icon] + [reagent-mui.icons.keyboard-double-arrow-left :as left-icon] + [re-frame.core :refer [reg-sub]])) + +(reg-sub + ::side-bar-open? + :<- [::components-subs/side-bar-db] + (fn [side-bar-db _] + (:side-bar-open? side-bar-db))) + +(reg-sub + ::model-selection-db + :<- [::components-subs/side-bar-db] + (fn [side-bar-db _] + (:model-selection-db side-bar-db))) + +(reg-sub + ::model-params-db + :<- [::components-subs/side-bar-db] + (fn [side-bar-db _] + (:model-params-db side-bar-db))) + +(reg-sub + ::tooltip-text-collapse-sidebar + :<- [::side-bar-open?] + (fn [side-bar-open? _] + (str (if side-bar-open? "Collapse" "Expand") " side bar"))) + +(reg-sub + ::collapse-icon + :<- [::side-bar-open?] + (fn [side-bar-open? _] + (if side-bar-open? + [right-icon/keyboard-double-arrow-right {:style {:font-size "18px"}}] + [left-icon/keyboard-double-arrow-left {:style {:font-size "18px"}}]))) diff --git a/contrib/clojure/src/main/openllm/components/side_bar/views.cljs b/contrib/clojure/src/main/openllm/components/side_bar/views.cljs new file mode 100644 index 00000000..9506b532 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/side_bar/views.cljs @@ -0,0 +1,73 @@ +(ns openllm.components.side-bar.views + (:require [re-frame.core :as rf] + [openllm.components.side-bar.model-selection.views :as model-selection-views] + [openllm.components.side-bar.model-params.views :as model-params-views] + [openllm.components.side-bar.subs :as subs] + [openllm.components.side-bar.events :as events] + [openllm.components.common.views :as ui] + [reagent-mui.material.typography :refer [typography]] + [reagent-mui.material.button :refer [button]] + [reagent-mui.material.input :refer [input]] + [reagent-mui.material.tooltip :refer [tooltip]] + [reagent-mui.material.collapse :refer [collapse]])) + +(defn- collapse-side-bar-button + "The collapse side bar button. It changes its icon depending on whether + the side bar is collapsed or not." + [] + (let [tooltip-text-collapse-sidebar (rf/subscribe [::subs/tooltip-text-collapse-sidebar]) + collapse-icon (rf/subscribe [::subs/collapse-icon])] + (fn [] + [:div {:class "-ml-4"} + [tooltip {:title @tooltip-text-collapse-sidebar} + [button {:on-click #(rf/dispatch [::events/toggle-side-bar]) + :color "secondary" + :class "h-4" + :style {:max-width "16px" + :min-width "16px" + :background-color "black" + :border-radius "8px 0px 0px 8px" + :margin-top "-2px"}} + [:div {:class "-mr-1 -mt-px"} + @collapse-icon]]]]))) + +(defn- base-url-selection + "Section of the sidebar, where you select the base-url of the API." + [] + (let [base-url (rf/subscribe [:api-base-url])] + (fn [] + [:div {:class "h-12 w-full px-5"} + [typography {:variant "title" + :class "text-black mb-1"} + "LLM Endpoint"] + [input {:class "w-full h-8.5 mb-1 border" + :value @base-url + :on-change #(rf/dispatch [:set-api-base-url (-> % .-target .-value)])}]]))) + +(defn side-bar-with-mui-collapse + "The sidebar wrapped with a Material UI Collapse component. The collapse + component is used to animate the sidebar when it is opened or closed." + [] + (let [side-bar-open? (rf/subscribe [::subs/side-bar-open?])] + (fn [] + [collapse {:in @side-bar-open? + :orientation "horizontal" + :class "flex flex-col w-80"} + [:div {:class "flex flex-col w-80 h-full border-l border-gray-200 pt-0.5 pb-4"} + [model-selection-views/model-selection] + [:hr {:class "mb-2 border-1 border-gray-200"}] + [ui/headline "Parameters" 2] + [:div {:class "my-0 h-0 flex-1 flex flex-col overflow-y-auto scrollbar"} + [:div {:class "px-3 mt-0 relative inline-block text-left"} + [model-params-views/parameter-list]]] + [:hr {:class "mb-2 border-1 border-gray-200"}] + [base-url-selection]]]))) + +(defn side-bar + "The render function of the toolbar on the very right of the screen. Contains the + model selection dropdowns and the parameter list." + [] + [:<> + [collapse-side-bar-button] + [:div {:class "hidden lg:flex lg:flex-shrink-0"} + [side-bar-with-mui-collapse]]]) diff --git a/contrib/clojure/src/main/openllm/components/subs.cljs b/contrib/clojure/src/main/openllm/components/subs.cljs new file mode 100644 index 00000000..238d2841 --- /dev/null +++ b/contrib/clojure/src/main/openllm/components/subs.cljs @@ -0,0 +1,37 @@ +(ns openllm.components.subs + "The subscriptions from this namespace are used to access the components' + db branches. + Every subscription in this namespace is an extractor (layer 2) of the + `app-db`." + (:require [openllm.subs :as root-subs] + [re-frame.core :refer [reg-sub]])) + +(reg-sub + ::chat-db + :<- [::root-subs/components-db] + (fn [components-db _] + (:chat-db components-db))) + +(reg-sub + ::model-selection-db + :<- [::root-subs/components-db] + (fn [components-db _] + (:model-selection-db components-db))) + +(reg-sub + ::nav-bar-db + :<- [::root-subs/components-db] + (fn [components-db _] + (:nav-bar-db components-db))) + +(reg-sub + ::playground-db + :<- [::root-subs/components-db] + (fn [components-db _] + (:playground-db components-db))) + +(reg-sub + ::side-bar-db + :<- [::root-subs/components-db] + (fn [components-db _] + (:side-bar-db components-db))) diff --git a/contrib/clojure/src/main/openllm/db.cljs b/contrib/clojure/src/main/openllm/db.cljs new file mode 100644 index 00000000..0561b8dd --- /dev/null +++ b/contrib/clojure/src/main/openllm/db.cljs @@ -0,0 +1,67 @@ +(ns openllm.db + "This namespace acts as the root app-db for the application. The `db` namespaces + define the structure of the `app-db`. They each define a schema for the + respective branch of the `app-db`. The `db` namespaces should only be used by + the `events` and `subs` namespaces. + The `clojure.spec` schema is checked against the `app-db` after each event + handler has run. This is done by the `events` namespaces with the help of the + `check-spec-interceptor` from the `openllm.events` namespace. + + Please note that each `db` namespace has an `initial-db` function which + returns the initial value for each branch of the `app-db`. + + Furthermore be aware that the `app-db` is immutable. This means that you cannot + change the `app-db` directly. Instead you have to dispatch an event which will + then change the `app-db` as a whole." + (:require [openllm.components.db :as components-db] + [cljs.spec.alpha :as s])) + +;; Below is is the root `clojure.spec` specification for the value in app-db. +;; It basically works like a like a schema. +;; See: http://clojure.org/guides/spec +;; +;; The value in app-db should always match this spec. Only event handlers +;; can change the value in app-db so, after each event handler +;; has run, we re-check app-db for correctness (compliance with the Schema). +;; +;; How is this done? Look in events.cljs and you'll notice that all handlers +;; have an "after" interceptor which does the spec re-check. +;; None of this is strictly necessary. It could be omitted. But we (the +;; re-frame people) find it good practice. +(s/def ::screen-id keyword?) +(s/def ::api-base-url string?) +(s/def ::db (s/keys :req-un [::components-db/components-db + ::screen-id + ::api-base-url])) + +(def default-db + "What gets put into app-db by default. This is the very root of the `app-db`. + See `core.cljs` for `(dispatch-sync [:initialise-db])` and 'events.cljs' + for the registration of `:initialise-db` effect handler." + {:components-db (components-db/initial-db) + :screen-id :playground + :api-base-url "http://localhost:3000"}) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Rich Comments ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(comment + ;; some examples on namespaced keywords + (= :components-db/components-db :openllm.components.db/components-db) ;; => false + (= ::components-db/components-db :openllm.components.db/components-db) ;; => true + (= ::components-db/components-db ::openllm.components.db/components-db) ;; => true + (= :components-db :openllm.components.db) ;; => false + + + ;; check if default db complies with spec + (s/valid? ::db default-db) ;; => true + + + ;; check if manipulated db (no screen-id key) complies with spec + (s/valid? ::db (dissoc default-db :screen-id)) ;; => false + + + ;; reset app-db to default-db + (do (require '[re-frame.core :as rf]) + (rf/dispatch-sync [:initialise-db]))) diff --git a/contrib/clojure/src/main/openllm/events.cljs b/contrib/clojure/src/main/openllm/events.cljs new file mode 100644 index 00000000..45ae085a --- /dev/null +++ b/contrib/clojure/src/main/openllm/events.cljs @@ -0,0 +1,70 @@ +(ns openllm.events + (:require [cljs.spec.alpha :as s] + [openllm.db :as db] + [re-frame.core :refer [after reg-cofx reg-event-db reg-event-fx]])) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn check-and-throw + "Throws an exception if `db` does not match the Spec `a-spec`. Acts as a helper + for our spec checking interceptor." + [a-spec db] + (when-not (s/valid? a-spec db) + (throw (ex-info (str "spec check failed: " (s/explain-str a-spec db)) {})))) + +(def check-spec-interceptor + "The interceptor we will use to check the app-db after each event handler runs. + It will check that the app-db is valid against the spec `::db`." + (after (partial check-and-throw ::db/db))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Coeffects ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(reg-cofx + :time-now + (fn [cofx _] + (assoc cofx :time-now (js/Date.)))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Events ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(reg-event-db + :initialise-db + [check-spec-interceptor] + (fn [_ _] + db/default-db)) + +(reg-event-db + :set-screen-id + [check-spec-interceptor] + (fn [db [_ new-screen-id]] + (assoc db :screen-id new-screen-id))) + +(reg-event-db + :set-api-base-url + [check-spec-interceptor] + (fn [db [_ new-api-base-url]] + (assoc db :api-base-url new-api-base-url))) + +(reg-event-fx + ::open-link-in-new-tab + (fn [_ [_ url]] + {:fx (js/window.open url "_blank")})) ;; hitchu with da side fx's *new wave uptempo kick* + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Rich Comments ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(comment + #_{:clj-kondo/ignore [:duplicate-require]} + (require '[re-frame.core :as rf]) + + ;; set screen-id to :chat + (rf/dispatch-sync [:set-screen-id :chat]) + + ;; reset app-db to default-db + (rf/dispatch-sync [:initialise-db])) diff --git a/contrib/clojure/src/main/openllm/subs.cljs b/contrib/clojure/src/main/openllm/subs.cljs new file mode 100644 index 00000000..39e25f25 --- /dev/null +++ b/contrib/clojure/src/main/openllm/subs.cljs @@ -0,0 +1,17 @@ +(ns openllm.subs + (:require [re-frame.core :refer [reg-sub]])) + +(reg-sub + :screen-id + (fn [db _] + (:screen-id db))) + +(reg-sub + :api-base-url + (fn [db _] + (:api-base-url db))) + +(reg-sub + ::components-db + (fn [db _] + (:components-db db))) diff --git a/contrib/clojure/src/main/openllm/util.cljs b/contrib/clojure/src/main/openllm/util.cljs new file mode 100644 index 00000000..a0e451f7 --- /dev/null +++ b/contrib/clojure/src/main/openllm/util.cljs @@ -0,0 +1,19 @@ +(ns openllm.util + "A collection of utility functions used throughout the application. + All functions of this namespace must be pure." + (:require [clojure.string :as str])) + +(defn chat-history->string + "Converts a chat history to a string representation. Basically, it joins + the user name and the text of each entry with a colon. The entries are + separated by a newline. + Useful for building a chat prompt or exporting the chat history to a + file. + Returns the chat history as a string." + [chat-history] + (let [entry->chat-line (fn [current-entry] + (str/join ": " + [(name (:user current-entry)) + (:text current-entry)]))] + (str/join "\n" (map entry->chat-line + chat-history)))) diff --git a/contrib/clojure/src/main/openllm/views.cljs b/contrib/clojure/src/main/openllm/views.cljs new file mode 100644 index 00000000..96288364 --- /dev/null +++ b/contrib/clojure/src/main/openllm/views.cljs @@ -0,0 +1,43 @@ +(ns openllm.views + "This is the root views namespace, while the first DOM/hiccup is created in + the `openllm.app` namespace, this namespace is the first to be pure and + only create hiccup/DOM, derived from data :) + The `openllm.app` namespace dealt with the initialisation of the `app-db`, + created the root DOM/hiccup node and handeled the material-ui theming. + + From this point onward all the views are pure and only depend on the `app-db`, + which is queried by subscriptions and the single source of truth for the + entire application." + (:require [re-frame.core :as rf] + [openllm.components.nav-bar.views :as nav-bar-views] + [openllm.components.side-bar.views :as side-bar-views] + [openllm.components.playground.views :as playground-views] + [openllm.components.chat.views :as chat-views])) + +(defn tab-content + "The content of the currently active tab. Essentially, this is the + main router of the app, deciding what content should be rendered + based on the current screen-id." + [screen-id] + (case screen-id + :playground [playground-views/playground-tab-contents] + :chat [chat-views/chat-tab-contents])) + +(defn dashboard + "The main dashboard component, which is rendered into the DOM. Called + directly by the `app` function residing inside the `app` namespace, + which is the main component and rendered by the root rendering function + of this application. Called is putting it mildly, as this function is + basically wrapped by the `app` function, thus this component contains + everything that is rendered into the DOM as it's children." + [] + (let [screen-id (rf/subscribe [:screen-id])] + (fn [] + [:<> + [nav-bar-views/nav-bar] + [:div {:class "h-[calc(100vh-3rem)] flex bg-white overflow-hidden"} + [:div {:class "flex flex-col flex-1 w-screen"} + [:main {:class "flex-1 relative z-0 overflow-hidden focus:outline-none" :tabIndex "0"} + [:div {:class "mt-2 pr-0.5 pl-4 w-full h-full"} + [tab-content @screen-id]]]] + [side-bar-views/side-bar]]]))) diff --git a/contrib/clojure/tailwind.config.js b/contrib/clojure/tailwind.config.js new file mode 100644 index 00000000..a848540a --- /dev/null +++ b/contrib/clojure/tailwind.config.js @@ -0,0 +1,49 @@ +const defaultTheme = require('tailwindcss/defaultTheme') + +module.exports = { + content: process.env.NODE_ENV == 'production' ? ["./public/js/main.js"] : ["./src/main/**/*.cljs"/*, "./www/js/cljs-runtime/*.js"*/], + theme: { + extend: { + fontFamily: { + sans: ["Inter var", ...defaultTheme.fontFamily.sans], + }, + }, + + /* These basically behave like the h1, h2, h3, etc. tags in HTML */ + fontSize: { + 'sm': ['1.0rem', { + lineHeight: '1.5rem', + letterSpacing: '0.00em', + fontWeight: '400', + }], + 'xl': ['1.25rem', { + lineHeight: '1.75rem', + letterSpacing: '-0.01em', + fontWeight: '400', + }], + '2xl': ['1.5rem', { + lineHeight: '2rem', + letterSpacing: '-0.01em', + fontWeight: '500', + }], + '3xl': ['1.875rem', { + lineHeight: '2.25rem', + letterSpacing: '-0.02em', + fontWeight: '700', + }], + '4xl': ['2.0rem', { + lineHeight: '2.5rem', + letterSpacing: '-0.02em', + fontWeight: '900', + }], + '5xl': ['3rem', { + lineHeight: '3rem', + letterSpacing: '-0.02em', + fontWeight: '1200', + }], + }, + }, + plugins: [ + require('@tailwindcss/forms'), + ], +} diff --git a/hatch.toml b/hatch.toml index 0a11fe1a..f952b0b9 100644 --- a/hatch.toml +++ b/hatch.toml @@ -24,20 +24,32 @@ pre-install-commands = [ ] [envs.default.scripts] changelog = "towncrier build --version main --draft" -check-stubs = ["./tools/update-config-stubs.py", "./tools/update-models-import.py", "update-dummy"] +check-stubs = [ + "./tools/update-config-stubs.py", + "./tools/update-models-import.py", + "update-dummy", +] compile = "bash ./compile.sh {args}" inplace-changelog = "towncrier build --version main --keep" quality = [ "./tools/dependencies.py", "./tools/update-readme.py", "- ./tools/update-brew-tap.py", + "bash ./tools/sync-readme.sh", "check-stubs", "- pre-commit run --all-files", ] recompile = ["bash ./clean.sh", "compile"] -setup = ["pre-commit install", "- ln -s .python-version-default .python-version"] +setup = [ + "pre-commit install", + "- ln -s .python-version-default .python-version", + "curl -fsSL https://raw.githubusercontent.com/clj-kondo/clj-kondo/master/script/install-clj-kondo | bash -", +] tool = ["quality", "recompile -nx"] -typing = ["- pre-commit run mypy {args:-a}", "- pre-commit run pyright {args:-a}"] +typing = [ + "- pre-commit run mypy {args:-a}", + "- pre-commit run pyright {args:-a}", +] update-dummy = ["- ./tools/update-dummy.py", "./tools/update-dummy.py"] [envs.tests] dependencies = [ @@ -77,3 +89,13 @@ generate-summary = "python tools/generate-coverage.py" report-uncovered-html = "coverage html --skip-covered --skip-empty {args}" report-xml = "coverage xml {args}" write-summary-report = "python tools/write-coverage-report.py" +[envs.ui] +[envs.ui.scripts] +server = """BENTOML_CONFIG_OPTIONS='api_server.http.cors.enabled=true api_server.http.cors.access_control_allow_origins="*" api_server.http.cors.access_control_allow_methods[0]="GET" api_server.http.cors.access_control_allow_methods[1]="OPTIONS" api_server.http.cors.access_control_allow_methods[2]="POST" api_server.http.cors.access_control_allow_methods[3]="HEAD" api_server.http.cors.access_control_allow_methods[4]="PUT"' openllm start {args:flan-t5} --working-dir {root:uri} --debug""" +clojure = ["bash tools/run-clojure-ui.sh"] +[envs.ci] +[envs.ci.scripts] +lock = [ + "bash tools/lock-actions", + "pushd contrib/clojure && pnpm i --frozen-lockfile", +] diff --git a/openllm-node/package.json b/openllm-node/package.json index be191306..2d5e8d71 100644 --- a/openllm-node/package.json +++ b/openllm-node/package.json @@ -18,5 +18,5 @@ "flash_attention" ], "author": "Aaron Pham <29749331+aarnphm@users.noreply.github.com>", - "license": "Apache 2.0" + "license": "Apache-2.0" } diff --git a/ADDING_NEW_MODEL.md b/openllm-python/ADDING_NEW_MODEL.md similarity index 86% rename from ADDING_NEW_MODEL.md rename to openllm-python/ADDING_NEW_MODEL.md index c7a68a90..fb6c9bd2 100644 --- a/ADDING_NEW_MODEL.md +++ b/openllm-python/ADDING_NEW_MODEL.md @@ -8,8 +8,8 @@ environment by referring to our ## Procedure All the relevant code for incorporating a new model resides within -`src/openllm/models`. Start by creating a new folder named after your -`model_name` in snake_case. Here's your roadmap: +[`src/openllm/models`](./src/openllm/models/) `model_name` in snake_case. +Here's your roadmap: - [ ] Generate model configuration file: `src/openllm/models/{model_name}/configuration_{model_name}.py` @@ -17,15 +17,13 @@ All the relevant code for incorporating a new model resides within `src/openllm/models/{model_name}/modeling_{runtime}_{model_name}.py` - [ ] Create module's `__init__.py`: `src/openllm/models/{model_name}/__init__.py` -- [ ] Adjust the entrypoints for files at `src/openllm/models/auto/*` +- [ ] Adjust the entrypoints for files at `src/openllm/models/auto/*` If it is a + new runtime, then add it a `src/openllm/models/auto/modeling_{runtime}_auto.py`. + See the other auto runtime for example. - [ ] Modify the main `__init__.py`: `src/openllm/models/__init__.py` - [ ] Run the following to update stubs: `hatch run check-stubs` -For a working example, check out any pre-implemented model. - -> We are developing a CLI command and helper script to generate these files, -> which would further streamline the process. Until then, manual creation is -> necessary. +For a working example, check out any existing model. ### Model Configuration diff --git a/openllm-python/src/openllm/cli/_factory.py b/openllm-python/src/openllm/cli/_factory.py index 21e1d8a9..c93e649f 100644 --- a/openllm-python/src/openllm/cli/_factory.py +++ b/openllm-python/src/openllm/cli/_factory.py @@ -1,8 +1,9 @@ from __future__ import annotations -import functools, importlib.util, os, typing as t +import functools, importlib.util, os, typing as t, logging import click, click_option_group as cog, inflection, orjson, bentoml, openllm from bentoml_cli.utils import BentoMLCommandGroup from click.shell_completion import CompletionItem +from openllm.utils import DEBUG from bentoml._internal.configuration.containers import BentoMLContainer from openllm._typing_compat import LiteralString, DictStrAny, ParamSpec, Concatenate from . import termui @@ -11,21 +12,27 @@ if t.TYPE_CHECKING: import subprocess from openllm._configuration import LLMConfig +logger = logging.getLogger(__name__) + P = ParamSpec("P") LiteralOutput = t.Literal["json", "pretty", "porcelain"] _AnyCallable = t.Callable[..., t.Any] FC = t.TypeVar("FC", bound=t.Union[_AnyCallable, click.Command]) -def parse_config_options(config: LLMConfig, server_timeout: int, workers_per_resource: float, device: t.Tuple[str, ...] | None, environ: DictStrAny,) -> DictStrAny: +def parse_config_options(config: LLMConfig, server_timeout: int, workers_per_resource: float, device: t.Tuple[str, ...] | None, cors: bool, environ: DictStrAny) -> DictStrAny: # TODO: Support amd.com/gpu on k8s _bentoml_config_options_env = environ.pop("BENTOML_CONFIG_OPTIONS", "") _bentoml_config_options_opts = ["tracing.sample_rate=1.0", f"api_server.traffic.timeout={server_timeout}", f'runners."llm-{config["start_name"]}-runner".traffic.timeout={config["timeout"]}', f'runners."llm-{config["start_name"]}-runner".workers_per_resource={workers_per_resource}'] if device: if len(device) > 1: _bentoml_config_options_opts.extend([f'runners."llm-{config["start_name"]}-runner".resources."nvidia.com/gpu"[{idx}]={dev}' for idx, dev in enumerate(device)]) else: _bentoml_config_options_opts.append(f'runners."llm-{config["start_name"]}-runner".resources."nvidia.com/gpu"=[{device[0]}]') + if cors: + _bentoml_config_options_opts.extend(["api_server.http.cors.enabled=true", 'api_server.http.cors.access_control_allow_origins="*"']) + _bentoml_config_options_opts.extend([f'api_server.http.cors.access_control_allow_methods[{idx}]="{it}"' for idx, it in enumerate(["GET", "OPTIONS", "POST", "HEAD", "PUT"])]) _bentoml_config_options_env += " " if _bentoml_config_options_env else "" + " ".join(_bentoml_config_options_opts) environ["BENTOML_CONFIG_OPTIONS"] = _bentoml_config_options_env + if DEBUG: logger.debug("Setting BENTOML_CONFIG_OPTIONS=%s", _bentoml_config_options_env) return environ _adapter_mapping_key = "adapter_map" @@ -89,7 +96,7 @@ Available official model_id(s): [default: {llm_config['default_id']}] @click.pass_context def start_cmd( ctx: click.Context, /, server_timeout: int, model_id: str | None, model_version: str | None, workers_per_resource: t.Literal["conserved", "round_robin"] | LiteralString, device: t.Tuple[str, ...], quantize: t.Literal["int8", "int4", "gptq"] | None, bettertransformer: bool | None, runtime: t.Literal["ggml", "transformers"], fast: bool, - serialisation_format: t.Literal["safetensors", "legacy"], adapter_id: str | None, return_process: bool, **attrs: t.Any, + serialisation_format: t.Literal["safetensors", "legacy"], cors: bool, adapter_id: str | None, return_process: bool, **attrs: t.Any, ) -> LLMConfig | subprocess.Popen[bytes]: fast = str(fast).upper() in openllm.utils.ENV_VARS_TRUE_VALUES if serialisation_format == "safetensors" and quantize is not None and os.environ.get("OPENLLM_SERIALIZATION_WARNING", str(True)).upper() in openllm.utils.ENV_VARS_TRUE_VALUES: @@ -124,7 +131,7 @@ Available official model_id(s): [default: {llm_config['default_id']}] # NOTE: This is to set current configuration start_env = os.environ.copy() - start_env = parse_config_options(config, server_timeout, wpr, device, start_env) + start_env = parse_config_options(config, server_timeout, wpr, device, cors, start_env) if fast: termui.echo(f"Fast mode is enabled. Make sure the model is available in local store before 'start': 'openllm import {model}{' --model-id ' + model_id if model_id else ''}'", fg="yellow") start_env.update({"OPENLLM_MODEL": model, "BENTOML_DEBUG": str(openllm.utils.get_debug_mode()), "BENTOML_HOME": os.environ.get("BENTOML_HOME", BentoMLContainer.bentoml_home.get()), "OPENLLM_ADAPTER_MAP": orjson.dumps(adapter_map).decode(), "OPENLLM_SERIALIZATION": serialisation_format, env.runtime: env["runtime_value"], env.framework: env["framework_value"]}) @@ -193,6 +200,7 @@ def start_decorator(llm_config: LLMConfig, serve_grpc: bool = False) -> t.Callab model_version_option(factory=cog.optgroup), cog.optgroup.option("--server-timeout", type=int, default=None, help="Server timeout in seconds"), workers_per_resource_option(factory=cog.optgroup), + cors_option(factory=cog.optgroup), fast_option(factory=cog.optgroup), cog.optgroup.group( "LLM Optimization Options", help="""Optimization related options. @@ -303,11 +311,11 @@ def fast_option(f: _AnyCallable | None = None, **attrs: t.Any) -> t.Callable[[FC This is useful if you already downloaded or setup the model beforehand. """, **attrs )(f) +def cors_option(f: _AnyCallable | None = None, **attrs: t.Any) -> t.Callable[[FC], FC]: return cli_option("--cors/--no-cors", show_default=True, default=False, envvar="OPENLLM_CORS", show_envvar=True, help="Enable CORS for the server.", **attrs)(f) def machine_option(f: _AnyCallable | None = None, **attrs: t.Any) -> t.Callable[[FC], FC]: return cli_option("--machine", is_flag=True, default=False, hidden=True, **attrs)(f) def model_id_option(f: _AnyCallable | None = None, *, model_env: openllm.utils.EnvVarMixin | None = None, **attrs: t.Any) -> t.Callable[[FC], FC]: return cli_option("--model-id", type=click.STRING, default=None, envvar=model_env.model_id if model_env is not None else None, show_envvar=model_env is not None, help="Optional model_id name or path for (fine-tune) weight.", **attrs)(f) def model_version_option(f: _AnyCallable | None = None, **attrs: t.Any) -> t.Callable[[FC], FC]: return cli_option("--model-version", type=click.STRING, default=None, help="Optional model version to save for this model. It will be inferred automatically from model-id.", **attrs)(f) def model_name_argument(f: _AnyCallable | None = None, required: bool = True) -> t.Callable[[FC], FC]: return cli_argument("model_name", type=click.Choice([inflection.dasherize(name) for name in openllm.CONFIG_MAPPING]), required=required)(f) - def quantize_option(f: _AnyCallable | None = None, *, build: bool = False, model_env: openllm.utils.EnvVarMixin | None = None, **attrs: t.Any) -> t.Callable[[FC], FC]: return cli_option( "--quantise", "--quantize", "quantize", type=click.Choice(["int8", "int4", "gptq"]), default=None, envvar=model_env.quantize if model_env is not None else None, show_envvar=model_env is not None, help="""Dynamic quantization for running this LLM. @@ -327,7 +335,6 @@ def quantize_option(f: _AnyCallable | None = None, *, build: bool = False, model ) + """ > [!NOTE] that quantization are currently only available in *PyTorch* models.""", **attrs )(f) - def workers_per_resource_option(f: _AnyCallable | None = None, *, build: bool = False, **attrs: t.Any) -> t.Callable[[FC], FC]: return cli_option( "--workers-per-resource", default=None, callback=workers_per_resource_callback, type=str, required=False, help="""Number of workers per resource assigned. @@ -347,12 +354,10 @@ def workers_per_resource_option(f: _AnyCallable | None = None, *, build: bool = > ensure it has the same effect with 'openllm start --workers ...'""" if build else "" ), **attrs )(f) - def bettertransformer_option(f: _AnyCallable | None = None, *, build: bool = False, model_env: openllm.utils.EnvVarMixin | None = None, **attrs: t.Any) -> t.Callable[[FC], FC]: return cli_option( "--bettertransformer", is_flag=True, default=None, envvar=model_env.bettertransformer if model_env is not None else None, show_envvar=model_env is not None, help="Apply FasterTransformer wrapper to serve model. This will applies during serving time." if not build else "Set default environment variable whether to serve this model with FasterTransformer in build time.", **attrs )(f) - def serialisation_option(f: _AnyCallable | None = None, **attrs: t.Any) -> t.Callable[[FC], FC]: return cli_option( "--serialisation", "--serialization", "serialisation_format", type=click.Choice(["safetensors", "legacy"]), default="safetensors", show_default=True, show_envvar=True, envvar="OPENLLM_SERIALIZATION", help="""Serialisation format for save/load LLM. @@ -374,7 +379,6 @@ def serialisation_option(f: _AnyCallable | None = None, **attrs: t.Any) -> t.Cal > [!NOTE] that GGML format is working in progress. """, **attrs )(f) - def container_registry_option(f: _AnyCallable | None = None, **attrs: t.Any) -> t.Callable[[FC], FC]: return cli_option( "--container-registry", "container_registry", type=str, default="ecr", show_default=True, show_envvar=True, envvar="OPENLLM_CONTAINER_REGISTRY", callback=container_registry_callback, help="""The default container registry to get the base image for building BentoLLM. @@ -383,7 +387,7 @@ def container_registry_option(f: _AnyCallable | None = None, **attrs: t.Any) -> \b > [!NOTE] that in order to build the base image, you will need a GPUs to compile custom kernel. See ``openllm ext build-base-container`` for more information. - """ + """, **attrs )(f) _wpr_strategies = {"round_robin", "conserved"} diff --git a/openllm-python/src/openllm/cli/_sdk.py b/openllm-python/src/openllm/cli/_sdk.py index 6fba8e74..0e1d39d3 100644 --- a/openllm-python/src/openllm/cli/_sdk.py +++ b/openllm-python/src/openllm/cli/_sdk.py @@ -15,7 +15,8 @@ if t.TYPE_CHECKING: logger = logging.getLogger(__name__) -def _start(model_name: str, /, *, model_id: str | None = None, timeout: int = 30, workers_per_resource: t.Literal["conserved", "round_robin"] | float | None = None, device: tuple[str, ...] | t.Literal["all"] | None = None, quantize: t.Literal["int8", "int4", "gptq"] | None = None, bettertransformer: bool | None = None, runtime: t.Literal["ggml", "transformers"] = "transformers", fast: bool = False, adapter_map: dict[LiteralString, str | None] | None = None, framework: LiteralRuntime | None = None, additional_args: list[str] | None = None, _serve_grpc: bool = False, __test__: bool = False, **_: t.Any) -> LLMConfig | subprocess.Popen[bytes]: +def _start(model_name: str, /, *, model_id: str | None = None, timeout: int = 30, workers_per_resource: t.Literal["conserved", "round_robin"] | float | None = None, device: tuple[str, ...] | t.Literal["all"] | None = None, quantize: t.Literal["int8", "int4", "gptq"] | None = None, bettertransformer: bool | None = None, runtime: t.Literal["ggml", "transformers"] = "transformers", + adapter_map: dict[LiteralString, str | None] | None = None, framework: LiteralRuntime | None = None, additional_args: list[str] | None = None, cors: bool = False, _serve_grpc: bool = False, __test__: bool = False, **_: t.Any) -> LLMConfig | subprocess.Popen[bytes]: """Python API to start a LLM server. These provides one-to-one mapping to CLI arguments. For all additional arguments, pass it as string to ``additional_args``. For example, if you want to @@ -50,13 +51,12 @@ def _start(model_name: str, /, *, model_id: str | None = None, timeout: int = 30 - gptq: Quantize the model with GPTQ (auto-gptq required) bettertransformer: Convert given model to FastTransformer with PyTorch. runtime: The runtime to use for this LLM. By default, this is set to ``transformers``. In the future, this will include supports for GGML. - fast: Enable fast mode. This will skip downloading models, and will raise errors if given model_id does not exists under local store. + cors: Whether to enable CORS for this LLM. By default, this is set to ``False``. adapter_map: The adapter mapping of LoRA to use for this LLM. It accepts a dictionary of ``{adapter_id: adapter_name}``. framework: The framework to use for this LLM. By default, this is set to ``pt``. additional_args: Additional arguments to pass to ``openllm start``. """ from .entrypoint import start_command, start_grpc_command - fast = os.environ.get("OPENLLM_FAST", str(fast)).upper() in openllm.utils.ENV_VARS_TRUE_VALUES llm_config = openllm.AutoConfig.for_model(model_name) _ModelEnv = openllm.utils.EnvVarMixin(model_name, openllm.utils.first_not_none(framework, default=llm_config.default_implementation()), model_id=model_id, bettertransformer=bettertransformer, quantize=quantize, runtime=runtime) os.environ[_ModelEnv.framework] = _ModelEnv["framework_value"] @@ -69,7 +69,7 @@ def _start(model_name: str, /, *, model_id: str | None = None, timeout: int = 30 if quantize and bettertransformer: raise OpenLLMException("'quantize' and 'bettertransformer' are currently mutually exclusive.") if quantize: args.extend(["--quantize", str(quantize)]) elif bettertransformer: args.append("--bettertransformer") - if fast: args.append("--fast") + if cors: args.append("--cors") if adapter_map: args.extend(list(itertools.chain.from_iterable([["--adapter-id", f"{k}{':'+v if v else ''}"] for k, v in adapter_map.items()]))) if additional_args: args.extend(additional_args) if __test__: args.append("--return-process") diff --git a/package.json b/package.json index ae163d1c..64487e2c 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,13 @@ "description": "OpenLLM: Operating LLMs in production", "repository": "git@github.com:bentoml/OpenLLM.git", "author": "Aaron Pham <29749331+aarnphm@users.noreply.github.com>", - "license": "Apache 2.0", + "license": "Apache-2.0", "engines": { "node": ">=16" }, "workspaces": [ - "openllm-node" + "openllm-node", + "contrib/clojure" ], "private": true, "devDependencies": { diff --git a/tools/assert-model-table-latest b/tools/assert-model-table-latest.py similarity index 100% rename from tools/assert-model-table-latest rename to tools/assert-model-table-latest.py diff --git a/tools/lock-actions b/tools/lock-actions.sh similarity index 100% rename from tools/lock-actions rename to tools/lock-actions.sh diff --git a/tools/run-clojure-ui.sh b/tools/run-clojure-ui.sh new file mode 100755 index 00000000..73893909 --- /dev/null +++ b/tools/run-clojure-ui.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +set -ex -o pipefail + +GIT_ROOT=$(git rev-parse --show-toplevel) + +cd "$GIT_ROOT" || exit 1 + +if ! command -v node @ >&1 > /dev/null; then + echo "Cannot find 'node' executable in PATH. Make sure to have Node.js setup. Refer to" +fi + +if ! command -v pnpm @ >&1 > /dev/null; then + curl -fsSL https://get.pnpm.io/install.sh | sh - +fi + +if ! command -v clojure @ >&1 > /dev/null; then + curl -fsSL https://github.com/clojure/brew-install/releases/latest/download/posix-install.sh | bash - +fi + +if ! command -v hatch @ >&1 > /dev/null; then + echo "ERROR: hatch not installed. Aborting..." + exit 1 +fi + +pushd contrib/clojure > /dev/null +pnpm install +popd > /dev/null + +pnpm run -C "$GIT_ROOT"/contrib/clojure dev diff --git a/tools/sync-readme b/tools/sync-readme.sh similarity index 100% rename from tools/sync-readme rename to tools/sync-readme.sh